geometry:MySQL的空间数据类型(Spatial Data Type)与JTS(OSGeo)类型之间的序列化和反序列化
创始人
2025-05-29 09:02:34

现在很多应用都需要根据距离对信息进行排序,MYSQL5.7.28以后新增了空间数据类型(Spatial Data Type 即geometry)和相应的距离计算。

关于如何进行距离计算和排序不是本文要说的重点。本文要说明的是如何将MySQL存储的空间数据类型(Spatial Data Type)转为对应的Java对象以方便访问。

地理空间数据格式是有规范的,OpenGIS即(Open Geodata Interoperation Specification,OGIS-开放的地理数据互操作规范)由美国OGC(OpenGIS协会,Open Geospatial Consortium)提出。OGC是一个非盈利性组织,目的是促进采用新的技术和商业方式来提高地理信息处理的互操作性(Interoperability),它致力于消除地理信息应用(如地理信息系统,遥感,土地信息系统,自动制图/设施管理(AM/FM)系统)之间以及地理应用与其它信息技术应用之间的藩篱,建立一个无“边界”的、分布的、基于构件的地理数据互操作环境。
也就是说OpenGIS对地理空间数据做了规范定义,MySQL定义的空间数据类型(比如POINT–点,POLYGON–多边形)在存储时也都遵循OpenGIS的数据规范。参见MySQL官方文档《11.4 Spatial Data Types》
同时OpenGIS还提供了几何基础类库实现这些定义,对应到Java语言的就是JTS库《JTS Topology Suite》

根据MySQL官方文档《11.4 Spatial Data Types》说明,MySQL是以二进制形式存储存储空间数据类型(即WKB),比如对于一个POINT(1 -1),就是保存为长度25个字节的数组。格式如下:

ComponentSizeValue
SRID4 byte0
Byte order1 byte01
WKB type4 bytes01000000
X coordinate8 bytes000000000000F03F
Y coordinate8 bytes000000000000F0BF

上面的格式很简单,如果自己对这些数据进行解析也不是很难的事儿,但这样要自己定义相关的类,写好多代码。
事实上JTS库已经帮我们实现了实现了二进制数据到Java数据对象(Geometry)的相互转换。我们没有必要重复造轮子。
我们所要做的就是代码中使用JTS库的Geometry对象保存空间数据,并通过JTS库来实现将Geometry序列化为WKB数据保存到数据库,以及将从数据库中读取的WKB格式二进制数据反序列化为Geometry对象。

关于WKB,WKT格式的说明参见本文最后《参考资料》一节提供的链接

JTS库依赖引入

		com.vividsolutionsjts1.13

WKB解析

以下代码实现MySQL的WKB数据解析为com.vividsolutions.jts.geom.Geometry类的过程,

    /*** 将MySQL存储的WKB格式的二进制数据解析为{@link Geometry}对象* @param binary* @throws ParseException*/public Geometry fromWKB(byte[] binary) throws ParseException {if(null == binary) {return null;}if(binary.length < 25) {throw new ParseException("INVALID binary data length,more than 25 bytes required");}int srid = ByteBuffer.wrap(binary,0,4).asIntBuffer().get();WKBReader wkbReader = new WKBReader();Geometry geo = wkbReader.read(Arrays.copyOfRange(binary, 4, binary.length));geo.setSRID(srid);return geo;}public final T fromWKB(byte[] binary, Class targetType) throws ParseException {return targetType.cast(fromWKB(binary));}

WKBReader在解析WIKB数据时并不会处理最开始的SRID部分,所以上面的代码中会先从数组头部读取4个字节作为SRID,将数组索引4开始的剩余数据交给WKBReader解析。

Geomerty序列化为WKB

    /*** 将{@link Geometry}类型转为适合MySQL数据库存储的二进制格式* @param * @param input*/public byte[] toWKB(T input)  {if(null == input) {return null;  }WKBWriter wkbWriter = new WKBWriter(2, ByteOrderValues.LITTLE_ENDIAN);byte[] binary = wkbWriter.write(input);byte[] out = new byte[4 + binary.length];/** 写入SRID */ByteBuffer.wrap(out).asIntBuffer().put(input.getSRID());System.arraycopy(binary, 0, out, 4, binary.length);return out;}

根据MySQL的官方说明,WKB存储时字节序(Byte order)为小端(little-endian),而WKBWriter的默认构造方法创建对象时默认字节序为大端,所以这里不可以使用默认构造方法创建对象,必须指定为小端。

ResultSet

从数据查询结果集对象(ResultSet)中读取Geometry对象.

    /*** 读取数据记录指定字段的值转为空间数据对象* @param rs* @param columnIndex* @throws SQLException*/public final Object readGeometryData(ResultSet rs, int columnIndex) throws SQLException {try {return fromWKB(checkNotNull(rs,"rs is null").getObject(columnIndex));} catch (ParseException e) {throw new SQLException(e);}}

调用示例

    @Testpublic void test1() {try {Point point = new GeometryFactory().createPoint(new Coordinate(10,20));System.out.printf("point %s\n",point);byte[] binary = toWKB(point);Geometry p2 = fromWKB(binary);System.out.printf("p2 %s\n",p2);} catch (Exception e) {log(e.getMessage(),e);}}

完整代码参见我的码云仓库:https://gitee.com/l0km/sql2java/blob/dev/sql2java-base/src/main/java/gu/sql2java/geometry/MysqlGeometryDataCodec.java

https://gitee.com/l0km/sql2java/blob/dev/sql2java-base/src/test/java/gu/sql2java/SpatialDataCodecTest.java

参考资料

《mysql 5.7.28 空间地理位置计算》

《11.4 Spatial Data Types》

《JTS Topology Suite》

《Well-Known Text (WKT) Format》

《Well-Known Binary (WKB) Format》

相关内容

热门资讯

旅游购物更便利,海南开启新“流... 海南自由贸易港全岛封关运作正式启动后,国内外的游客前往海南没有任何手续方面的变化,而且在购物方面会更...
高原藏戏奏响文旅新声 甘孜州巴... 封面新闻记者 李庆 王越欣 图据活动主办方 12月19日,以“五彩藏乡·高原江南”为主题的甘孜州巴塘...
不做奶茶改卖螺蛳粉?阿嬷手作要... 在新茶饮行业增速缓至个位数、市场进入平稳发展期、竞争白热化的背景下,广西头部餐饮品牌阿嬷手作打破边界...
生活日常与美食:5分钟搞定早餐... 平日里进行烹饪,它可不单只是用来让肚子不饿的一种手段,更是一种对营造家庭氛围,以及对管理健康有着重要...
家常菜营养又美味的秘诀,让你每... 它不只是维系生命延续的手段,更是深深承载着丰富情感及独特文化内涵的日常饮食,在快节奏现代生活大背景下...