在入库和查询中需要自动的让geometry的类型和实体类中的类型映射。
实体类中接收参数是String类型(geoJson)
PostGis中的geometry类型是十六进制的扩展WKB类型(EWKB),
虽然Postgis数据库中提供类类型转换函数,能转换各种类型postgis常用命令_yaoct的博客-CSDN博客
但是基于mybatis框架查询时,就需要用java代码来转换。初步方案时mybatis中的类型转换器。
先引入java处理GIS的库,这里用的是Geotools库。
geotools的maven引入:
27.0
osgeo
OSGeo Release Repository
https://repo.osgeo.org/repository/release/
false
true
org.geotools
gt-geojson
${geotools.version}
org.geotools
gt-main
${geotools.version}
org.geotools
gt-opengis
${geotools.version}
org.geotools
gt-referencing
${geotools.version}
org.geotools
gt-metadata
${geotools.version}
org.geotools
gt-epsg-hsql
${geotools.version}
Geotools工具类转换WKB和Geojson
WKBReader reader = new WKBReader( );
Geometry geometry = reader.read(WKBReader.hexToBytes("0101000020E61000002C39382229FD5D4085716007088C3E40"));
// 设置保留6位小数,否则GeometryJSON默认保留4位小数
GeometryJSON geometryJson = new GeometryJSON(7);
String s = geometryJson.toString(geometry);
System.out.println(s);
//{"type":"Point","coordinates":[119.9556356,30.5469975]}
//EWKB->转geojson丢失信息
Geometry read = geometryJson.read("{\"type\":\"Point\",\"coordinates\":[119.9556356,30.5469975]}");
System.out.println(read.toString());
WKBWriter wkbWriter = new WKBWriter();
byte[] write = wkbWriter.write(geometry);
String s1 = WKBWriter.toHex(write);
System.out.println(s1);
这里框架用的是mybatis-plus,所有使用转换时,mybatis-plus中的实体类也要配置一些转换注解,其他和mybaitis中的xml配置属性相同。参考
@Data
@ToString
@TableName (value = "test_table",autoResultMap = true)
@ApiModel ("test")
public class TestTable implements Serializable {
private static final long serialVersionUID = 8881418345724766899L;
@TableId(value = "id")
private Integer id;
@ApiModelProperty(value = "二进制WKB数据")
@TableField(value = "position",typeHandler = WKB2GeoJsonTypeHandler.class)
@JsonRawValue
private String position;
@TableField("name")
private String name;
}
mybatis-plus配置添加转换器包路径,类似mybatis,之后用于写xml中sql
mybatis-plus:
type-handlers-package: com.zjzy.mapper.typehandler
configuration:
map-underscore-to-camel-case: true
auto-mapping-behavior: full
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath*:mapper/**/*Mapper.xml
因为缺少JDBC映射,数据库开始报错:‘类型为 geometry, 但表达式的类型为 character varying’
参考:Mybatis-plus读取和保存Postgis geometry数据 - 简书
应该是缺少JDBC-type类型。引入响应的postGIS驱动
添加maven包:
net.postgis
postgis-jdbc
2.5.0
类型转换器代码:
package com.zjzy.mapper.typehandler;
import com.zjzy.mapper.CommonCodeMapper;
import com.zjzy.util.SpringContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.apache.ibatis.type.TypeHandler;
import org.geotools.geojson.geom.GeometryJSON;
import org.geotools.geometry.jts.WKBReader;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.ParseException;
import org.postgis.PGgeometry;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Set;
/**
* @version 2.0
* @description
* @Author yaoct
* @create 2021/12/22 16:19
*/
@Slf4j
public class WKB2GeoJsonTypeHandler implements TypeHandler {
/**
* 插入数据,转换,geoJson2EWKB
* @param ps
* @param i
* @param parameter
* @param jdbcType
* @throws SQLException
*/
@Override
public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
//EWKB是pg数据库自定义的类型,所以需要用数据库函数,插入数据库一般次数较少,这样并不会对数据库造成过大负担
// CommonCodeMapper mapper = SpringContextUtil.getBean(CommonCodeMapper.class);
// String EWKB = mapper.geo2EWKB(parameter);
// PGgeometry ewkb = new PGgeometry(EWKB);
// ps.setObject(i,ewkb);
//通过geoTool转换WKB
GeometryJSON geometryJson = new GeometryJSON(7);
Geometry read = null;
try {
read = geometryJson.read(parameter);
} catch (IOException e) {
e.printStackTrace();
}
// System.out.println(read.toString());
read.setSRID(4326);
// GeometryJSON geometryJson = new GeometryJSON(7);
WKBWriter wkbWriter = new WKBWriter(2, ByteOrderValues.BIG_ENDIAN,true);
byte[] write = wkbWriter.write(read);
String s1 = WKBWriter.toHex(write);
PGgeometry ewkb = new PGgeometry(s1);
ps.setObject(i,ewkb);
}
/**
* 取出数据转换,WKB->Geojson
* @param rs
* @param columnName
* @return
* @throws SQLException
*/
@Override
public String getResult(ResultSet rs, String columnName) throws SQLException {
String WKB = rs.getString(columnName);
if(WKB==null){
return null;
}
WKBReader reader = new WKBReader( );
Geometry geometry = null;
try {
geometry = reader.read(WKBReader.hexToBytes(WKB));
} catch (ParseException e) {
//转换失败
return null;
}
//转换成4326
try {
int srid = geometry.getSRID();
CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:"+srid,true);
CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:4326",true);
MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS,true);
geometry = JTS.transform(geometry, transform);
} catch (Exception e) {
e.printStackTrace();
}
// 设置保留6位小数,否则GeometryJSON默认保留4位小数
GeometryJSON geometryJson = new GeometryJSON(7);
return geometryJson.toString(geometry);
}
@Override
public String getResult(ResultSet rs, int columnIndex) throws SQLException {
return null;
}
@Override
public String getResult(CallableStatement cs, int columnIndex) throws SQLException {
return null;
}
}
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
}
//获取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
//通过name获取 Bean.
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
//通过class获取Bean.
public static T getBean(Class clazz) {
return getApplicationContext().getBean(clazz);
}
//通过name,以及Clazz返回指定的Bean
public static T getBean(String name, Class clazz) {
return getApplicationContext().getBean(name, clazz);
}
//关闭springboot
public static void close(){
((ConfigurableApplicationContext)(getApplicationContext())).close();
}
//得到当前线程的HttpServletResponse,可在静态方法中得到
public static HttpServletResponse getHttpServletResponse(){
return ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getResponse();
}
//得到当前线程的HttpServletRequest,可在静态方法中得到
public static HttpServletRequest getHttpServletRequest(){
return ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
}
}
@Repository
public interface CommonCodeMapper extends BaseMapper {
/**
* EWKB是pg数据库自定义的类型,所以需要用数据库函数
* @param geoJson
* @return
*/
@Select("select ST_GeomFromGeoJSON(#{geoJson})")
String geo2EWKB(String geoJson);
}
=============================mybatis中使用================================
在mapper类中使用示例:https://www.jianshu.com/p/6465189984b0
/**
* 使用ResultMap
*/
@Results(id = "userMap", value = {
//可以使用这种方式来处理字段名和数据库表字段名不一致的情况
@Result(column = "username", property = "username", jdbcType=JdbcType.VARCHAR,typeHandler = WKB2GeoJsonTypeHandler.class),
@Result(column = "passwd", property = "passwd", jdbcType=JdbcType.VARCHAR),
@Result(column = "birth_day", property = "birthDay", jdbcType=JdbcType.VARCHAR)
})
@Select("SELECT * FROM t_user WHERE id=#{id}")
User loadByIdResultMap(Long id);
/**
* 引用其他的Result
*/
@ResultMap("userMap")
@Select("SELECT * FROM t_user WHERE id=#{id}")
User loadByIdResultMapReference(Long id);
在xml中使用示例示例:
注意:
1.postgresql的JDBC实现中(驱动),数据自定义的类型对应java类型一般都通过实现
org.postgresql.util.PGobject。
2.对应数据库geometry类型的java类不在PG的JDBC驱动的实现类中,因为geometry类型在PG的扩展postgis中,所以响应的要引入postGIS这部分的驱动,
3. PG的JDBC驱动底层也是通过SOCKET连接org.postgresql.core.PGStream#createSocket
4.原生jdbc接口在java.sql包中。
5.一定要实现org.postgresql.util.PGobject#getValue方法因为,org.postgresql.jdbc.PgPreparedStatement#setPGobject底层调用org.postgresql.jdbc.PgPreparedStatement#setString(int, java.lang.String, int)时
要用到org.postgresql.util.PGobject#getValue方法
以下方式不用导入 net.postgis包
package com.zjzy.mapper.typehandler;
import com.alibaba.fastjson.JSONObject;
import com.zjzy.util.GisUtil;
import com.zjzy.util.SpringContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.geotools.geojson.geom.GeometryJSON;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.WKBReader;
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.ByteOrderValues;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKBWriter;
import org.locationtech.jts.io.WKTWriter;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.postgis.PGbox2d;
import org.postgis.PGgeometry;
import org.postgresql.util.PGobject;
import org.springframework.core.env.Environment;
import java.io.IOException;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @version 1.0
* @description
* @Author yaoct
* @create 2021/12/22 16:19
*/
@Slf4j
public class WKB2GeoJsonTypeHandler implements TypeHandler {
/**
* 插入数据,转换,geoJson2EWKB
* @param ps
* @param i
* @param parameter
* @param jdbcType
* @throws SQLException
*/
@Override
public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
//EWKB是pg数据库自定义的类型,所以需要用数据库函数,插入数据库一般次数较少,这样并不会对数据库造成过大负担
// CommonCodeMapper mapper = SpringContextUtil.getBean(CommonCodeMapper.class);
// String EWKB = mapper.geo2EWKB(parameter);
// PGgeometry ewkb = new PGgeometry(EWKB);
// ps.setObject(i,ewkb);
//通过geoTool转换WKB
GeometryJSON geometryJson = new GeometryJSON(7);
Geometry geometry = null;
try {
geometry = geometryJson.read(parameter);
} catch (IOException e) {
e.printStackTrace();
}
// System.out.println(read.toString());
geometry.setSRID(4326);
// GeometryJSON geometryJson = new GeometryJSON(7);
//方式1.基于net.postgis
// WKBWriter wkbWriter = new WKBWriter(2, ByteOrderValues.BIG_ENDIAN,true);
// byte[] write = wkbWriter.write(geometry);
// String s1 = WKBWriter.toHex(write);
// PGgeometry ewkb = new PGgeometry(s1);//EWKB比WKB多了SRID头字符串,PGgeometry.toString()会自动添加SRID
// ps.setObject(i,ewkb);
//方式2.设置ewkb,Postgresql驱动会先去数据库 pg_catalog.pg_type 查出geometry的类型id,这里的geometry字符串对应数据库pg_catalog.pg_type表中的geometry
//以下方式不用导入net.postgis包
// PGobject pGobject = new PGobject();
// pGobject.setType("geometry");
// pGobject.setValue(s1);
// ps.setObject(i,pGobject);
//方式3.设置拼接ewkt
//以下方式不用导入net.postgis包,ewkb太长会超出sql限制,以下转换为ewkt
PGobject pGobject = new PGobject();
pGobject.setType("geometry");
String pre="SRID="+4326+";";
pGobject.setValue(pre+GisUtil.Geometry2WKT(geometry));
ps.setObject(i,pGobject);
}
/**
* 取出数据转换,WKB->Geojson
* @param rs
* @param columnName
* @return
* @throws SQLException
*/
@Override
public String getResult(ResultSet rs, String columnName) throws SQLException {
String WKB = rs.getString(columnName);
if(WKB==null){
return null;
}
WKBReader reader = new WKBReader( );
Geometry geometry = null;
try {
geometry = reader.read(WKBReader.hexToBytes(WKB));
} catch (ParseException e) {
//转换失败
return null;
}
//转换成4326
try {
int srid = geometry.getSRID();
if(srid==0)srid=4549;//默认4549
int targetSrid=4326;//默认4326
Environment environment = SpringContextUtil.getBean(Environment.class);
if(environment!=null){
String sridTarget = environment.getProperty("sridTarget");
if(sridTarget!=null){
try {
targetSrid=Integer.valueOf(sridTarget);
} catch (Exception e){
//转换失败
}
}
}
CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:"+srid,true);
CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:"+targetSrid,true);
MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS,true);
geometry = JTS.transform(geometry, transform);
} catch (Exception e) {
e.printStackTrace();
}
// 设置保留6位小数,否则GeometryJSON默认保留4位小数
GeometryJSON geometryJson = new GeometryJSON(7);
String json = geometryJson.toString(geometry);
JSONObject jsonObject = JSONObject.parseObject(json);
jsonObject.put("srid",geometry.getSRID());
return jsonObject.toJSONString();
}
@Override
public String getResult(ResultSet rs, int columnIndex) throws SQLException {
return null;
}
@Override
public String getResult(CallableStatement cs, int columnIndex) throws SQLException {
return null;
}
}
package com.zjzy.util;
import org.geotools.geojson.geom.GeometryJSON;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.WKBReader;
import org.geotools.referencing.CRS;
import org.geotools.referencing.GeodeticCalculator;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.*;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import java.io.IOException;
/**
* @Author yaoct
* @Date 2022/9/16 10:55
* @Version 1.0
* @description
*/
public class GisUtil {
/**
* WKB字符串转Geometry格式
* @param WKB
* @return
*/
public static Geometry WKB2Geometry(String WKB){
WKBReader reader = new WKBReader();
Geometry geometry = null;
try {
geometry = reader.read(WKBReader.hexToBytes(WKB));
} catch (ParseException e) {
//转换失败
throw new RuntimeException("转换失败",e);
}
return geometry;
}
/**
* Geometry格式转WKB字符串
* @param geometry
* @return
*/
public static String Geometry2WKB(Geometry geometry){
WKBWriter wkbWriter = new WKBWriter(2, ByteOrderValues.BIG_ENDIAN,true);
byte[] write = wkbWriter.write(geometry);
String ret = WKBWriter.toHex(write);
return ret;
}
/**
* WKT字符串转Geometry格式
* @param WKT
* @return
*/
public static Geometry WKT2Geometry(String WKT){
WKTReader reader = new WKTReader();
Geometry geometry = null;
try {
geometry = reader.read(WKT);
} catch (ParseException e) {
//转换失败
throw new RuntimeException("转换失败",e);
}
return geometry;
}
/**
* Geometry格式转WKT字符串
* @param geometry
* @return
*/
public static String Geometry2WKT(Geometry geometry){
WKTWriter wktWriter = new WKTWriter();
String wkt= wktWriter.write(geometry);
return wkt;
}
/**
* 转换Geometry坐标
* @param geometry
* @param targetSrid 新坐标
* @return
*/
public static Geometry coordinateTransfer(Geometry geometry, int targetSrid){
int originSrid = geometry.getSRID();
CoordinateReferenceSystem sourceCRS = null;
CoordinateReferenceSystem targetCRS = null;
MathTransform transform=null;
try {
sourceCRS = CRS.decode("EPSG:"+originSrid,true);
targetCRS = CRS.decode("EPSG:"+targetSrid,true);
transform = CRS.findMathTransform(sourceCRS, targetCRS,true);
} catch (FactoryException e) {
throw new RuntimeException("坐标错误",e);
}
Geometry ret = null;
try {
ret = JTS.transform(geometry, transform);
} catch (TransformException e) {
throw new RuntimeException("坐标转换错误",e);
}
return ret;
}
/**
* Geometry->geoJson
* @param geometry
* @return
*/
public static String geometry2GeoJson(Geometry geometry){
// 设置保留6位小数,否则GeometryJSON默认保留4位小数
GeometryJSON geometryJson = new GeometryJSON(7);
String json = geometryJson.toString(geometry);
// JSONObject jsonObject = JSONObject.parseObject(json);
// jsonObject.put("srid",geometry.getSRID());
// return jsonObject.toJSONString();
return json;
}
/**
* geoJson->Geometry
* @param geoJson
* @return
*/
public static Geometry geoJson2Geometry(String geoJson){
GeometryJSON geometryJson = new GeometryJSON(7);
Geometry ret=null;
try {
Geometry read = geometryJson.read(geoJson);
} catch (IOException e) {
throw new RuntimeException("转换失败",e);
}
return ret;
}
/**
* 计算两点间的距离
* @param lon1
* @param lat1
* @param lon2
* @param lat2
* @return
*/
public static double calculateDistance(double lon1,double lat1,double lon2,double lat2){
//CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:"+srid,true);
GeodeticCalculator geodeticCalculator = new GeodeticCalculator(DefaultGeographicCRS.WGS84);
// 起点经纬度
geodeticCalculator.setStartingGeographicPoint(lon1,lat1);
// 末点经纬度
geodeticCalculator.setDestinationGeographicPoint(lon2,lat2);
// 计算距离,单位:米
double distance = geodeticCalculator.getOrthodromicDistance();
return distance;
}
}
转换成map类型,实体类接受前端传入json兼容性更好
package com.wisdomcity.laian.monitor.mapper.typehandler;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fhs.common.spring.SpringContextUtil;
import com.kingbase8.util.KBobject;
import com.wisdomcity.laian.monitor.utils.GisUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.geotools.geojson.geom.GeometryJSON;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.WKBReader;
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.ByteOrderValues;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKBWriter;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import java.io.IOException;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
/**
* @version 1.0
* @description
* @Author yaoct
* @create 2021/12/22 16:19
*/
@Slf4j
public class WKB2GeoJsonTypeHandler implements TypeHandler
实体类字段改为 :
@ApiModelProperty(value = "二进制WKB数据")
@TableField(value = "location",typeHandler = WKB2GeoJsonTypeHandler.class)
private Map location;
xml中使用
===================================================================
参考:
Mybatis-plus读取和保存Postgis geometry数据 - 简书
MyBatis Plus 自动类型转换之TypeHandler - 周世元ISO8859-1 - 博客园
GeoTools The Open Source Java GIS Toolkit — GeoTools
Geotools中Geometry对象与GeoJson的相互转换_mathyrs的博客-CSDN博客_geojson转geometry
mybatis 自定义TypeHandler映射Geometry空间几何数据 PGPoint (java +mybatis+ pgsql) - 灰信网(软件开发博客聚合)
Mybatis常用注解