使用 JTS 進行地理空間操作
1.概述
無論我們建構的是配送應用、無人機區域、車隊追蹤或地理分析,地理空間功能在當今的軟體領域都至關重要。 Java拓撲套件 ( JTS ) 是一個用 Java 編寫的幾何引擎,它提供了豐富的 API 用於建模和處理二維空間資料。
在本教程中,我們將透過程式碼範例和實際用例探索 JTS 的基本知識。
2. 設定 JTS
要在我們的 Java 專案中開始使用JTS ,我們需要新增 Maven 依賴項:
<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
<version>1.20.0</version>
</dependency>
3. 使用 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);
}
}
3.1. 包含:一個點是否位於多邊形內?
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));
}
point (10, 20)
位於一個正方形多邊形內,多邊形的座標範圍從 (0, 0) 到 (40, 40)。當該點完全位於多邊形邊界內時, contains()
方法傳回true
。
3.2. 相交:兩個幾何圖形是否重疊?
intersects()
方法檢查兩個幾何體是否接觸或重疊。我們也可以使用intersection()
方法來確定重疊區域。
讓我們為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 來確認此行為,從而證明相交檢測正確。
3.3 緩衝區:圍繞點建立半徑
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 個單位的緩衝區,並檢查該點確實位於產生的緩衝區內來驗證這一點,從而確保緩衝區操作正確運作。
3.4 距離:兩點相距多遠?
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 個單位。
3.5. 並集:合併兩個多邊形
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)。斷言確認了合併操作產生的幾何形狀正確,從而確保實現按預期工作。
3.6. 差異:從一個形狀中減去另一個形狀
The 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 () 方法減去重疊區域,只留下基礎矩形的左半部分getDifference
座標範圍從 (0,0) 到 (5,10)。然後,測試檢查輸出是否與預期的剩餘形狀匹配,以確保差分運算按預期工作。
3.7. 幾何驗證與修復
無效的幾何圖形(例如,自相交多邊形)可能會導致空間計算出現問題。 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 無法將其清楚地拆分成兩個有效部分。因此,它保守地只保留它所能找到的最大有效環,在本例中,就是靠近右邊的三角形。
4. 結論
在本文中,我們了解到 Java 拓樸套件 (JTS) 讓 Java 中的空間資料處理變得直觀且強大。它支援高級幾何建模、驗證和空間計算,所有這些對於地理空間應用至關重要。
我們現在擁有以下實踐知識:
- 使用 WKT 建構幾何圖形
- 執行空間操作(包含、緩衝、距離、聯合等)
- 處理無效幾何體
- 在實際場景中應用這些工具
無論我們建構的是地圖工具、物流引擎或位置感知應用程序,JTS 都能為我們提供大規模、精確地進行空間工作的核心工具。
與往常一樣,範例的完整原始程式碼可在 GitHub 上找到。