我们将学习以下实践知识:
- 使用 WKT 构建几何图形
- 执行空间操作(包含、缓冲、距离、联合等)
- 处理无效几何体
- 在实际场景中应用这些工具
无论我们构建的是配送应用、无人机区域、车队追踪还是地理分析,地理空间功能在当今的软件领域都至关重要。Java拓扑套件 ( JTS ) 是一个用 Java 编写的几何引擎,它提供了丰富的 API 用于建模和处理二维空间数据。
在本教程中,我们将通过代码示例和实际用例探索 JTS 的基本知识。
设置 JTS
要在我们的 Java 项目中开始使用JTS,我们需要添加 Maven 依赖项:
org.locationtech.jts
jts-core
1.20.0
使用 WKT 创建几何图形
在执行地理空间操作之前,我们必须首先定义空间数据。JTS 支持 Well-Known Text (WKT),这是一种用于描述点、线和多边形等几何类型的标准化格式。
以下是 WKT 几何语法:
POINT(x,y):单个坐标,例如POINT(10,20)
POLYGON ((x1 y1, x2 y2, …, xN yN)):闭合多边形,第一个点和最后一个点必须相同
让我们创建一个GeometryFactoryUtil类和一个readWKT()方法来读取 WKT 格式的几何图形:
public class GeometryFactoryUtil {
public static Geometry readWKT(String wkt) throws Exception {
WKTReader reader = new WKTReader();
return reader.read(wkt);
}
}
包含:一个点是否位于多边形内?
contains ()方法检查一个几何体是否完全位于另一个几何体之内。该方法常用于地理围栏、分区和追踪应用。
首先,让我们创建一个名为JTSOperationUtils的新类,然后创建方法来检查一个点是否在多边形内:
public class JTSOperationUtils {
private static final Logger log = LoggerFactory.getLogger(JTSOperationUtils.class);
public static boolean checkContainment(Geometry point, Geometry polygon) {
boolean isInside = polygon.contains(point);
log.info("Is the point inside polygon? {}", isInside);
return isInside;
}
}
现在让我们创建测试来检查一个点是否在多边形内:
@Test
public void givenPolygon2D_whenContainPoint_thenContainmentIsTrue() throws Exception {
Geometry point = GeometryFactoryUtil.readWKT("POINT (10 20)");
Geometry polygon = GeometryFactoryUtil.readWKT("POLYGON ((0 0, 0 40, 40 40, 40 0, 0 0))");
Assert.assertTrue(JTSOperationUtils.checkContainment(point, polygon));
}
点(10, 20)位于一个正方形多边形内,该多边形的坐标范围从 (0, 0) 到 (40, 40)。当该点完全位于多边形边界内时,contains()方法返回true 。
相交:两个几何图形是否重叠?
intersects ()方法检查两个几何体是否接触或重叠。我们也可以使用intersects()方法来确定重叠区域。
让我们 向JTSOperationUtils添加一个名为checkIntersect()的新方法来检查两个几何图形是否相交并确定重叠区域:
public static boolean checkIntersect(Geometry rectangle1, Geometry rectangle2) {
boolean intersect = rectangle1.intersects(rectangle2);
Geometry overlap = rectangle1.intersection(rectangle2);
log.info("Do both rectangle intersect? {}", intersect);
log.info("Overlapping Area: {}", overlap);
return intersect;
}
两个矩形部分相交。intersects()方法返回true,而intersection()方法给出重叠区域的几何形状。
现在让我们添加一个新测试来检查两个几何体是否相交:
@Test
public void givenRectangle1_whenIntersectWithRectangle2_thenIntersectionIsTrue() throws Exception {
Geometry rectangle1 = GeometryFactoryUtil.readWKT("POLYGON ((10 10, 10 30, 30 30, 30 10, 10 10))");
Geometry rectangle2 = GeometryFactoryUtil.readWKT("POLYGON ((20 20, 20 40, 40 40, 40 20, 20 20))");
Assert.assertTrue(JTSOperationUtils.checkIntersect(rectangle1, rectangle2));
}
checkIntersect () 函数使用 intersects 方法检查两个几何图形(在本例中为矩形)是否重叠。单元测试通过定义两个部分重叠的矩形并断言checkIntersect()返回 true来确认此行为 ,从而证明相交检测正确。
缓冲区:围绕点创建半径
buffer ()方法用于在几何体周围创建圆形或多边形区域。它通常用于接近度检测或安全区域建模。
我们将在 utils 类中创建一个名为getBuffer()的新方法 来处理向几何点添加缓冲区:
public static Geometry getBuffer(Geometry point, integer intBuffer) {
Geometry buffer = point.buffer(intBuffer);
log.info("Buffer Geometry: {}", buffer);
return buffer;
}
此方法会在指定几何体(在本例中通常为一个点)周围创建缓冲区。在地理空间处理中,缓冲区表示在指定距离内围绕几何体的区域。它通常用于邻近分析,例如查找距离某个位置一定距离内的所有要素。
现在让我们添加一个新测试来断言新的缓冲区几何包含我们的原始几何:
@Test
public void givenPoint_whenAddedBuffer_thenPointIsInsideTheBuffer() throws Exception {
Geometry point = GeometryFactoryUtil.readWKT("POINT (10 10)");
Geometry bufferArea = JTSOperationUtils.getBuffer(point, 5);
Assert.assertTrue(JTSOperationUtils.checkContainment(point, bufferArea));
}
getBuffer ()函数在给定几何体(例如一个点)周围以指定距离创建一个缓冲区,并返回一个表示该区域周围的多边形。单元测试通过创建一个点 (10,10),在其周围生成一个 5 个单位的缓冲区,并检查该点确实位于生成的缓冲区内来验证这一点,从而确保缓冲区操作正确运行。
距离:两点相距多远?
distance ()方法计算两个几何体之间的欧氏距离。这在最近位置搜索和警报中很有用。
我们将在 utils 类中将此方法命名为getDistance() :
public static double getDistance(Geometry point1, Geometry point2) {
double distance = point1.distance(point2);
log.info("Distance: {}",distance);
return distance;
}
此方法旨在使用 JTS 几何库计算两个几何对象(通常是点)之间的距离。如果两个输入都是点,则结果为欧氏距离。对于其他类型的几何体,例如线或面,它表示它们边界之间的最小距离。
现在,让我们添加一个新测试来断言我们从distance() 方法获得的距离是正确的:
@Test
public void givenTwoPoints_whenGetDistanceBetween_thenGetTheDistance() throws Exception {
Geometry point1 = GeometryFactoryUtil.readWKT("POINT (10 10)");
Geometry point2 = GeometryFactoryUtil.readWKT("POINT (13 14)");
double distance = JTSOperationUtils.getDistance(point1, point2);
double expectedResult = 5.00;
double delta = 0.00;
Assert.assertEquals(expectedResult, distance, delta);
}
该测试计算两点 (10, 10) 和 (13, 14) 之间的距离,欧几里得计算器返回 5.0 个单位。
并集:合并两个多边形
union ()方法将两个几何图形合并为一个形状。这对于合并区域(例如地块或行政区)非常有用。
我们将在 utils 类中添加一个名为getUnion()的新方法来组合两个几何图形:
public static Geometry getUnion(Geometry geometry1, Geometry geometry2) {
Geometry union = geometry1.union(geometry2);
log.info("Union Result: {}", union);
return union;
}
编写此测试是为了验证getUnion () 方法是否正确地将两个相邻的多边形组合成一个更大的多边形:
@Test
public void givenTwoGeometries_whenGetUnionOfBoth_thenGetTheUnion() throws Exception {
Geometry geometry1 = GeometryFactoryUtil.readWKT("POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))");
Geometry geometry2 = GeometryFactoryUtil.readWKT("POLYGON ((10 0, 10 10, 20 10, 20 0, 10 0))");
Geometry union = JTSOperationUtils.getUnion(geometry1, geometry2);
Geometry expectedResult = GeometryFactoryUtil.readWKT("POLYGON ((0 0, 0 10, 10 10, 20 10, 20 0, 10 0, 0 0))");
Assert.assertEquals(expectedResult, union);
}
在此测试中,两个正方形多边形相邻定义,共享一条公共边。两个相邻的矩形合并为一个更大的多边形,其坐标范围从 (0, 0) 到 (20, 10)。断言确认了合并操作生成的几何形状正确,从而确保实现按预期工作。
差异:从一个形状中减去另一个形状
Difference()方法从一个几何图形中减去另一个几何图形,例如,删除限制区域或遮罩区域。
让我们在 utils 类中定义一个新方法getDifference() :
public static Geometry getDifference(Geometry base, Geometry cut) {
Geometry result = base.difference(cut);
log.info("Resulting Geometry: {}", result);
return result;
}
该方法以两个几何体作为输入,其中第一个几何体作为基本形状,第二个几何体表示要切割的形状。
让我们添加一个新的单元测试来检查getDifference () 方法是否能够在一个矩形重叠时正确地从另一个矩形中减去一个矩形:
@Test
public void givenBaseRectangle_whenAnotherRectangleOverlapping_thenGetTheDifferenceRectangle() throws Exception {
Geometry base = GeometryFactoryUtil.readWKT("POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))");
Geometry cut = GeometryFactoryUtil.readWKT("POLYGON ((5 0, 5 10, 10 10, 10 0, 5 0))");
Geometry result = JTSOperationUtils.getDifference(base, cut);
Geometry expectedResult = GeometryFactoryUtil.readWKT("POLYGON ((0 0, 0 10, 5 10, 5 0, 0 0))");
Assert.assertEquals(expectedResult, result);
}
在这个测试中,基础矩形的坐标范围从 (0,0) 到 (10,10),而第二个矩形的右半部分与基础矩形的坐标范围从 (5,0) 到 (10,10) 重叠。getDifference () 方法减去重叠区域,只留下基础矩形的左半部分,坐标范围从 (0,0) 到 (5,10)。然后,测试检查输出是否与预期的剩余形状匹配,以确保差分运算按预期工作。
几何验证和修复
无效的几何图形(例如,自相交多边形)可能会导致空间计算出现问题。JTS 提供了isValid()方法来检查有效性,并使用buffer(0)方法来自动纠正它们。
让我们向我们的 utils 类添加一个名为validateAndRepair() 的新方法:
public static Geometry validateAndRepair(Geometry invalidGeo) throws Exception {
boolean valid = invalidGeo.isValid();
log.info("Is valid Geometry value? {}", valid);
Geometry repaired = invalidGeo.buffer(0);
log.info("Repaired Geometry: {}", repaired);
return repaired;
}
输入多边形自相交。应用buffer(0)方法可以通过从无效输入重建有效几何体来纠正此问题。JTS只能从原始无效蝴蝶结形状中提取出一个小的有效三角形,并且只保留了形成有效封闭形状的部分。
让我们添加一个新的测试,以确保buffer(0)方法修复后的预期几何形状与我们得到的实际结果相同:
@Test
public void givenInvalidGeometryValue_whenValidated_thenGiveFixedResult() throws Exception {
Geometry invalidGeo = GeometryFactoryUtil.readWKT("POLYGON ((0 0, 5 5, 5 0, 0 5, 0 0))");
Geometry result = JTSOperationUtils.validateAndRepair(invalidGeo);
Geometry expectedResult = GeometryFactoryUtil.readWKT("POLYGON ((2.5 2.5, 5 5, 5 0, 2.5 2.5))");
Assert.assertEquals(expectedResult, result);
}
无效几何体的原始值会产生自相交,JTS 无法将其清晰地拆分成两个有效部分。因此,它保守地只保留它能找到的最大有效环,在本例中,就是靠近右侧的三角形。