一 概述
在实际GIS项目开发中,经常会使用PostGIS来存储矢量数据,在PostGIS中建立几何字段存储空间几何信息,对应于Java服务端,我们希望对应的表实体(MyBatis)中直接采用几何对象来进行数据操作,比如采用org.postgis.Geometry或者org.locationtech.jts.geom.Geometry(GeoTools)等JavaType来声明属性,以便于我们直接进行几何的数据操作,比如进行几何合并、求交、测距等等。但是这两种JavaType并非JdbcType,无法直接映射,需要我们利用MyBatis的类型转换器进行自定义转换处理。
关于MyBatis类型转换器的介绍与配置,可以参考我的另一篇文章 《MyBatis 类型转换器》。
二 转PostGIS在Java中组织的几何Java对象
(1)简述
PostGIS在Java中组织的几何有关的JavaType位于 org.postgis.**,具有Geometry(几何抽象类)及其实现类。由PostGIS组织的几何JavaType更接近PostGIS的JdbcType。
PostGIS的JdbcType,例如org.postgis.PGgeometry,内部包含由对应的JavaType的几何对象。
(2)类型转换器
首先定义一个对org.postgis.PGgeometry
类型的自定义类型转换器的抽象类,后续其余具体几何类型的转换只需要实现此抽象类指定类型即可。
public abstract class PgGeometryTypeHandler<T extends Geometry> extends BaseTypeHandler<T> {
/**
* SRS-EPSG
*/
private static final int EPSG_CODE = 4326;
/**
* 插入 - 插入时设置参数类型
*
* @param preparedStatement
* @param i
* @param parameter org.postgis组织的几何类型实体
* @param jdbcType
* @throws SQLException
*/
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, T parameter, JdbcType jdbcType) throws SQLException {
boolean typeCheck = parameter instanceof Geometry;
if (!typeCheck) {
preparedStatement.setObject(i, null);
System.out.println("MyBatis类型转换器插入参数非org.postgis.Geometry[Point,LineString,Polygon,MultiPoint,MultiLineString,MultiPolygon]");
}
PGgeometry pGgeometry = new PGgeometry(parameter);
parameter.setSrid(EPSG_CODE);
preparedStatement.setObject(i, pGgeometry);
}
/**
* 获取 - 获取时转换回的自定义类型 - 根据列名获取
*
* @param resultSet
* @param columnName
* @return
* @throws SQLException
*/
@Override
public T getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
// 获取读取到的指定列存储的值并进行类型检查
Object result = resultSet.getObject(columnName);
boolean typeCheck = result instanceof PGgeometry;
if (!typeCheck) {
System.out.println("MyBatis类型转换器获取参数转自定义类型错误,列实际存储类型非org.postgis.PGgeometry");
return null;
}
return (T) ((PGgeometry) result).getGeometry();
}
/**
* 获取 - 获取时转换回的自定义类型 - 根据索引位置获取
*
* @param resultSet
* @param columnIndex
* @return
* @throws SQLException
*/
@Override
public T getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException {
// 获取读取到的指定列存储的值并进行类型检查
Object result = resultSet.getObject(columnIndex);
boolean typeCheck = result instanceof PGgeometry;
if (!typeCheck) {
System.out.println("MyBatis类型转换器获取参数转自定义类型错误,列实际存储类型非org.postgis.PGgeometry");
return null;
}
return (T) ((PGgeometry) result).getGeometry();
}
/**
* 获取 - 获取时转换回的自定义类型 - 根据存储过程获取
*
* @param callableStatement
* @param columnIndex
* @return
* @throws SQLException
*/
@Override
public T getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
// 获取读取到的指定列存储的值并进行类型检查
Object result = callableStatement.getObject(columnIndex);
boolean typeCheck = result instanceof PGgeometry;
if (!typeCheck) {
System.out.println("MyBatis类型转换器获取参数转自定义类型错误,列实际存储类型非org.postgis.PGgeometry");
return null;
}
return (T) ((PGgeometry) result).getGeometry();
}
}
/**
* mybatis 几何字段类型转换处理器 - PostGIS - Point
*/
@MappedTypes({Point.class})
public class PgPointTypeHandler extends PgGeometryTypeHandler<Point> {
}
三 转GeoTools的几何Java对象
(1)简述
GeoTools组织的几何类型相比PostGIS组织的几何类型要更加“沉重”,前者提供了更多的功能,后者则更接近数据库,效率更高。之所以同时列举出两种转换,就是要清楚这个过程,进而根据实际情况选择。
PostGIS -> PGGeometry(org.postgis) -> Geometry(org.postgis) -> WKT ->Geometry(GeoTools)
(2)类型转换器
首先定义一个对org.locationtech.jts.geom.Geometry
(GeoTools)类型的自定义类型转换器的抽象类,后续其余具体几何类型的转换只需要实现此抽象类指定类型即可。
/**
* mybatis 几何字段类型转换处理器 - geometry抽象类,由具体几何类型继承
*
* @param <T>
*/
public abstract class GtGeometryTypeHandler<T extends Geometry> extends BaseTypeHandler<T> {
/**
* SRS-EPSG
*/
private static final int EPSG_CODE = 4326;
/**
* 插入 - 插入时设置参数类型
*
* @param preparedStatement
* @param i
* @param parameter GeoTools组织的几何类型实体
* @param jdbcType
* @throws SQLException
*/
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, T parameter, JdbcType jdbcType) throws SQLException {
// 获取要插入的自定义类型的(GeoTools的Geometry实体)实体,并进行类型检查
boolean typeCheck = parameter instanceof Geometry;
if (!typeCheck) {
preparedStatement.setObject(i, null);
System.out.println("MyBatis类型转换器插入参数非GeoTools的Geometry对象");
}
// 通过几何的WKT格式字符串构建PG的Geometry
PGgeometry pGgeometry = new PGgeometry(parameter.toText());
org.postgis.Geometry geometry = pGgeometry.getGeometry();
geometry.setSrid(EPSG_CODE);
preparedStatement.setObject(i, pGgeometry);
}
/**
* 获取 - 获取时转换回的自定义类型 - 根据列名获取
*
* @param resultSet
* @param columnName
* @return
* @throws SQLException
*/
@Override
public T getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
// 获取读取到的指定列存储的值并进行类型检查
Object result = resultSet.getObject(columnName);
boolean typeCheck = result instanceof PGgeometry;
if (!typeCheck) {
System.out.println("MyBatis类型转换器获取参数转自定义类型错误,列实际存储类型非org.postgis.PGgeometry,列实际存储类型");
return null;
}
// 检查并返回目标转换类型实例
return getResult((PGgeometry) result);
}
/**
* 获取 - 获取时转换回的自定义类型 - 根据索引位置获取
*
* @param resultSet
* @param columnIndex
* @return
* @throws SQLException
*/
@Override
public T getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException {
// 获取读取到的指定列存储的值并进行类型检查
Object result = resultSet.getObject(columnIndex);
boolean typeCheck = result instanceof PGgeometry;
if (!typeCheck) {
System.out.println("MyBatis类型转换器获取参数转自定义类型错误,列实际存储类型非org.postgis.PGgeometry,列实际存储类型");
return null;
}
// 检查并返回目标转换类型实例
return getResult((PGgeometry) result);
}
/**
* 获取 - 获取时转换回的自定义类型 - 根据存储过程获取
*
* @param callableStatement
* @param columnIndex
* @return
* @throws SQLException
*/
@Override
public T getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
// 获取读取到的指定列存储的值并进行类型检查
Object result = callableStatement.getObject(columnIndex);
boolean typeCheck = result instanceof PGgeometry;
if (!typeCheck) {
System.out.println("MyBatis类型转换器获取参数转自定义类型错误,列实际存储类型非org.postgis.PGgeometry,列实际存储类型");
return null;
}
// 检查并返回目标转换类型实例
return getResult((PGgeometry) result);
}
/**
* pgGeometry实例转GeoTools的Geometry实例
*
* @param pgGeometry
* @return
*/
private T getResult(PGgeometry pgGeometry) {
if (pgGeometry == null)
return null;
// 替换掉pgWKT中关于SRID的字符串部分
// WKT-PG:"SRID=4326;POINT(118.88888888 36.6666666666)"
// WKT:"POINT(118.88888888 36.6666666666)"
String pgWkt = pgGeometry.toString();
String wkt = pgWkt.replace(String.format("SRID=%s;", EPSG_CODE), "");
// 通过WKT构建GeoTools的Geometry对象
try {
return (T) new WKTReader().read(wkt);
} catch (Exception e) {
System.out.println("解析WKT为GeoTools的Geometry对象失败,异常信息:"+ e.toString());
return null;
}
}
}
/**
* mybatis 几何字段类型转换处理器 - Point
*/
@MappedTypes({Point.class})
public class GtPointTypeHandler extends GtGeometryTypeHandler<Point> {
}