欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 国际 > 点线面的智慧:转转JTS技术如何塑造上门履约地理布局

点线面的智慧:转转JTS技术如何塑造上门履约地理布局

2025/5/7 14:12:24 来源:https://blog.csdn.net/zhuanzhuantech/article/details/140843587  浏览:    关键词:点线面的智慧:转转JTS技术如何塑造上门履约地理布局

1 引言

image.png
如上图所示,在转转上门履约的场景中,上门服务的覆盖区域是在地图上画电子围栏来划定的。这就涉及到一些几何图形的操作和空间关系判断,其中最核心问题就是要解决如何判断位置是否在上门覆盖范围内。下面介绍下 JTS,以及如何通过 JTS 的空间之力来解决这些问题。

2 JTS 介绍

JTS,全称 Java Topology Suite,是一个用于创建和操作向量几何的 Java 库。提供了对几何模型的抽象,以及各种空间操作和空间关系判断,非常强大。

2.1 引入 jar 包

JTS 有多个模块,这里只使用了核心的模块。

  • jts-core:提供几何模型的抽象、空间操作、空间关系判断算法等
  • jts-io-common:提供各种格式描述几何模型的输入输出包,如对 WKT、WKB 等格式
<dependency><groupId>org.locationtech.jts</groupId><artifactId>jts-core</artifactId><version>1.19.0</version>
</dependency><dependency><groupId>org.locationtech.jts.io</groupId><artifactId>jts-io-common</artifactId><version>1.19.0</version>
</dependency>

2.2 基本的几何模型

JTS 提供了常见的几何模型抽象,并且各具特点。

模型定义常见应用
点(Point)空间中的单个位置,由一对 x,y 坐标表示兴趣点、事件位置等
多点(MultiPoint)由多个独立的点组成的几何对象表示多个相关但分散的位置,如连锁店分布,多个不同人位置
线(LineString)由一系列点组成的一维几何对象,有起点和终点,中间可以有任意数量的点表示道路、河流等线性特征
多线(MultiLineString)由多个不相连的 LineString 组成的几何对象表示复杂的道路网络、等高线等
多边形(Polygon)由一系列首尾相连的线段围成的平面区域(可以有内部空洞)表示行政区划、建筑物轮廓等
多多边形(MultiPolygon)由多个独立的 Polygon 组成的几何对象,可以表示不相连的多个区域表示群岛、复杂的行政区划
几何集合(GeometryCollection)可以包含任意类型几何对象的集合,最灵活的几何类型,可以混合包含点、线、面等表示复杂的空间场景,如包含多种类型要素的地图

在 JTS 中的各几何模型对象关系如下所示
image.png

在实际应用场景中,最常使用的模型如下

  • 点(Point):表示位置信息,如用户地址位置、工程师位置等
  • 多边形(Polygon)、多多边形(MultiPolygon):用来表示上门履约的覆盖区域

2.3 几何模型的描述格式

WKT(Well-Know Text)格式是一种文本格式,用于描述二维和三维几何对象的空间特征。
WKT 的基本语法格式如下:

几何模型类型 (模型数据)

示例如下所示

点:POINT (282 455)
线:LINESTRING (260 250, 485 248, 520 380)
多边形:POLYGON ((320 390, 370 330, 470 360, 460 430, 375 432, 320 390))

JTS 支持对该格式的读写操作,主要是两个对象WKTReaderWKTWriter,代码示例如下

// 读取wkt描述的几何对象
WKTReader wktReader = new WKTReader();
Geometry point = wktReader.read("POINT (282 455)");
Geometry line = wktReader.read("LINESTRING (260 250, 485 248, 520 380)");
Geometry polygon = wktReader.read("POLYGON ((320 390, 370 330, 470 360, 460 430, 375 432, 320 390))");// 输出几何对象的wkt描述
WKTWriter wktWriter = new WKTWriter();
System.out.println(wktWriter.write(point));
System.out.println(wktWriter.write(line));
System.out.println(wktWriter.write(polygon));

2.4 空间关系

JTS 中的空间关系是基于 DE-9IM(Dimensionally Extended Nine-Intersection Model)模型定义的,这里列举常见的空间关系

空间关系定义
相等 (Equals)两个几何对象在拓扑上相等
相离 (Disjoint)两个几何对象没有任何共同点
相交 (Intersects)两个几何对象有至少一个共同点
内含 (Within)几何对象 A 完全位于几何对象 B 内部
包含 (Contains)几何对象 A 完全包含几何对象 B

以该图形为例,两个多边形的关系判断的代码示例
image.png

WKTReader wktReader = new WKTReader();
Geometry geometryA = wktReader.read("POLYGON ((320 390, 370 330, 470 360, 460 430, 375 432, 320 390))");
Geometry geometryB = wktReader.read("POLYGON ((500 420, 430 360, 530 260, 500 420))");System.out.println("Equal: " + geometryA.equals(geometryB));
System.out.println("Disjoint: " + geometryA.disjoint(geometryB));
System.out.println("Intersects: " + geometryA.intersects(geometryB));
System.out.println("Within: " + geometryA.within(geometryB));
System.out.println("Contains: " + geometryA.contains(geometryB));

在实际场景中,判断上门位置是否在上门区域内,转换成空间关系的判断就是点是否在多边形内。解决该问题的实例代码如下

WKTReader wktReader = new WKTReader();
Geometry geometryA = wktReader.read("POLYGON ((320 390, 370 330, 470 360, 460 430, 375 432, 320 390))");
Geometry geometryB = wktReader.read("POLYGON ((500 420, 430 360, 530 260, 500 420))");
Geometry point = wktReader.read("POINT (390 380)");System.out.println("point in geometryA: " + geometryA.contains(point));
System.out.println("point in geometryB: " + geometryB.contains(point));

2.5 空间操作

JTS 提供了丰富的空间操作功能,用于处理和分析几何对象。这里列举常见的几种

空间操作定义
相交 (Intersection)计算两个几何对象的共同部分
并集 (Union)合并两个或多个几何对象
差集 (Difference)从一个几何对象中减去另一个几何对象

以该图为例,操作示例代码如下
image.png

WKTReader wktReader = new WKTReader();
Geometry geometryA = wktReader.read("POLYGON ((320 390, 370 330, 470 360, 460 430, 375 432, 320 390))");
Geometry geometryB = wktReader.read("POLYGON ((500 420, 430 360, 530 260, 500 420))");System.out.println("Intersection: " + wktWriter.write(geometryA.intersection(geometryB)));
System.out.println("Union: " + wktWriter.write(geometryA.union(geometryB)));
System.out.println("Difference: " + wktWriter.write(geometryA.difference(geometryB)));

下面是 Union 合并后的效果
image.png

3 快速判断是否支持上门

在上门履约实际场景中,需要快速的识别用户所在位置、地址位置是否在上门服务的覆盖区域内。转换成空间关系的判断上,也就是点是否在多边形内(PIP,Point-In-Polygon)问题了。

在上述的 JTS 介绍中,已经得知 JTS 提供了 contains 的关系判断能力。但是这只是解决了单个问题,假设全国共有 N 个多边形,那么就需要遍历 N 个多边形来判断,复杂度是 O(N),并且还需要全部多边形加载到内存中。可想而知,直接使用的话会存在性能问题。为此,我们需要一个快速解决 PIP 问题的方案。

3.1 最小外接矩形(MBR)

最小外接矩形 MBR (Minimum Bounding Retangle),是能够完全包含一个几何对象的最小矩形。
如下图所示,这个规则的矩形就是该多边形的 MBR 表示。
最小外接矩形

表示 MBR 非常简单,只需要知道他的左下角和右上角,那么就可以知道这个 MBR 图形了。如下图所示最小外接矩形表示

知道了这个最小外接矩形有什么用?
可以断定:如果点不在这个 MBR 内了,那么肯定不在这个多边形内。所以把点和 MBR 进行比较,就能够快速排除不可能有关系的多边形对象。

那么如何快速的判断点是否在 MBR 中?比较坐标值的大小就可以了。示例代码如下

mbr.getLngMin() <= point.getLng()
&& mbr.getLngMax() >= point.getLng()
&& mbr.getLatMin() <= point.getLat()
&& mbr.getLatMax() >= point.getLat()

综上,MBR 用简单的矩形来近似表示复杂的几何形状,将复杂的空间关系简化为矩形之间的关系。 通过 MBR 这一层的初步筛选,就能够快速排除不可能有关系的多边形对象。

在 JTS 中,Envelope 对象来表示 MBR。代码示例如下

WKTReader wktReader = new WKTReader();
Geometry geometryA = wktReader.read("POLYGON ((320 390, 370 330, 470 360, 460 430, 375 432, 320 390))");Envelope envelope = geometryA.getEnvelopeInternal();
System.out.println(envelope.getMaxX());
System.out.println(envelope.getMaxY());
System.out.println(envelope.getMinX());
System.out.println(envelope.getMinY());

3.2 空间索引

上述构建 MBR 可以理解为简单索引的一种,实际上有复杂的空间索引。常见空间索引有

  • R 树(R-tree):平衡树,适用于多维空间数据(类似一维的 B+树)
  • 四叉树(Quad-tree):将二维空间递归地分为四个象限
  • 网格(Grid):将空间划分为规则的网格单元

空间索引的基本原理基本类似,采用分割原理,逐级划分地理空间。举个不那么恰当的例子,一个自上而下、逐级划分地理空间的索引定位过程如下

北方 还是 南方 ? 南方
广东 还是 广西 ? 广东
深圳 还是 广州 ? 深圳
福田 还是 南山 ? 福田

JTS 提供了四叉树和 R 树的实现

  • Quadtree(四叉树)
  • STRtree(基于 R 树的变体)

以这个图形为例,使用 JTS 构建 R 树空间索引

示例代码如下

WKTReader wktReader = new WKTReader();
Geometry geometryA = wktReader.read("POLYGON ((320 390, 370 330, 470 360, 460 430, 375 432, 320 390))");
Geometry geometryB = wktReader.read("POLYGON ((500 420, 430 360, 530 260, 500 420))");STRtree rtree = new STRtree();
// 向R树种添加MBR,和自己的数据
rtree.insert(geometryA.getEnvelopeInternal(), "Polygon-A");
rtree.insert(geometryB.getEnvelopeInternal(), "Polygon-B");
rtree.build();// 点只在Polygon-A中
System.out.println(rtree.query(wktReader.read("POINT (337 391)").getEnvelopeInternal()));
// 点只在Polygon-B中
System.out.println(rtree.query(wktReader.read("POINT (496 390)").getEnvelopeInternal()));
// 点在Polygon-A和Polygon-B的交集中
System.out.println(rtree.query(wktReader.read("POINT (452 367)").getEnvelopeInternal()));

3.3 整体方案流程

综上所述,快速定位点(Point)在哪些多边形中的具体流程如下

  1. 先通过 STRtree 构建空间索引
  2. 利用空间索引快速筛选可能包含点的多边形
  3. 对筛选后的多边形进行精确的空间关系判断

多边形是随时都有可能可以调整,如果一个多边形发生了调整就需要重构整颗索引树。但是在实践中,为了降低构建索引树的频次,通过定时任务去间隔 10 分钟在内存中构建一次。并且为了减少索引树占用的内存大小,向索引树中添加 MBR 关联的是多边形的 Id,初筛后再根据 id 从缓存中取具体的多边形数据进行精确的空间关系判断,实现一个类似懒加载的过程。

具体流程如下图所示
空间索引整体流程

4 几何图形的修复处理

在实际运营过程中,画的图形各种形状,会出现不少异常的情况,如点重叠、边之间细微的间隙、自交等问题。实际操作中还提拱了图形合并的能力,合并出来的图像也有可能也是不符合规范的。为此,需要对这些异常的图像进行修复。

常见的修复手段有两种

  • Buffer 操作:在几何对象周围的创建缓冲区,一般用来修复自相交问题、精度导致的小间隙等
  • Snap 操作:一个几何对象的顶点捕捉到另一个几何对象的顶点或边缘,一般用来修复小的拓扑错误

这两种操作也不是万能,也是需要自己根据实际情况进行不断地调整。

下面来看一个修复自交的例子,一个自交的图形如下所示
空间修复案例

修复代码示例如下


WKTReader wktReader = new WKTReader();
Geometry geometryA = wktReader.read("POLYGON ((340 490, 370 330, 730 350, 700 270, 340 490))");WKTWriter wktWriter = new WKTWriter();
wktWriter.setPrecisionModel(new PrecisionModel(0));
System.out.println(wktWriter.write(geometryA.buffer(0)));

修复之后如下图所示
几何图形修复示例2

5 总结

Java Topology Suite (JTS) 作为一个功能强大的空间数据处理库,为开发者提供了丰富的工具来处理复杂的空间问题。它在许多地理信息系统得到了广泛的应用。这里只是对其的一个简单应用,后续还待更深入的挖掘。

6 参考

  • Java Topology Suite (JTS)
  • GIS原理在线教程

关于作者

揭荣,转转上门履约业务研发工程师

转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。
关注公众号「转转技术」(综合性)、「大转转FE」(专注于FE)、「转转QA」(专注于QA),更多干货实践,欢迎交流分享~

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词