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


  • 1 引言

  • 2 JTS 介绍

    • 2.1 引入 jar 包

    • 2.2 基本的几何模型

    • 2.3 几何模型的描述格式

    • 2.4 空间关系

    • 2.5 空间操作

  • 3 快速判断是否支持上门

    • 3.1 最小外接矩形(MBR)

    • 3.2 空间索引

    • 3.3 整体方案流程

  • 4 几何图形的修复处理

  • 5 总结

  • 6 参考


1 引言

如上图所示,在转转上门履约的场景中,上门服务的覆盖区域是在地图上画电子围栏来划定的。这就涉及到一些几何图形的操作和空间关系判断,其中最核心问题就是要解决如何判断位置是否在上门覆盖范围内。下面介绍下 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 中的各几何模型对象关系如下所示:

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

  • 点(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

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

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) 从一个几何对象中减去另一个几何对象

以该图为例,操作示例代码如下:

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 合并后的效果

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)));

修复之后如下图所示

5 总结

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

6 参考

  • Java Topology Suite (JTS):https://github.com/locationtech/jts
  • OSGeo中国:https://www.osgeo.cn/

关于作者

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

想了解更多转转公司的业务实践,欢迎点击关注下方公众号:



相关推荐

  • 实现LLM应用的可观测,难在哪里?
  • JetBrains IDE全系列采用新的默认“皮肤”:即将面向所有用户提供
  • 谁该有“金融羞耻感”?
  • 5年融资87亿,苏州明星独角兽要IPO了
  • 腾讯和去哪儿网官宣两件大事,上热搜了!
  • CVPR'24 Highlight|一个框架搞定人物动作生成,精细到手部运动
  • ControlNet作者又出爆款!一张图生成绘画全过程,两天狂揽1.4k Star
  • 这些VLM竟都是盲人?GPT-4o、Sonnet-3.5相继败于「视力」测试
  • GitHub 8k Star,一作实习生,字节这个大模型成果被苹果选中了
  • 18个月326项能力,这家大厂猛猛上新生成式AI,如今纯靠Prompt就搞定企业级应用了
  • 微软宣布放弃OpenAI观察员席位,苹果也将搁置,反垄断见效?
  • 全现金!6.65亿美刀!AMD大手一挥收购欧洲最大私人AI实验室
  • DeepMind CEO称AI智力不如猫!LeCun持相同看法!曝光谷歌新项目AI将突破聊天限制
  • 魔改Transformer!9种提速又提效的模型优化方案
  • XGBoost和LightGBM时间序列预测对比
  • AI 正在取代工作岗位、ChatBot 进入厌倦期、向量数据库崛起,人工智能现状报告有这些重要发现!
  • 万字巨制!阿里、百度、腾讯、字节、蚂蚁等 9 大技术专家同台,“卷”出 AI 编码新未来
  • 程序员凭一己之力自研浏览器,挑战Chrome:GitHub联合创始人力挺,投百万美元支持!
  • 微软放弃在 OpenAI 董事会的观察员席位
  • 免费!小白入门、职场进阶… IT 全学科自学,助你成为技术大牛!| 极客时间