第02章 - 安装配置与快速开始
上一章讲清了 JTS 的定位,这一章开始动手。JTS 的安装简单到有些“反常识”:它没有安装程序、没有配置文件、没有服务端口,甚至没有必须初始化的运行时。你需要的只是一个 jar 包和一段能调用它的 Java 代码。
但“简单”不等于“没有坑”。事实是:初学者在这一步最容易犯三类错误——依赖坐标写错(org.locationtech.jts.io 与 org.locationtech.jts 混淆)、新旧包名混用(com.vividsolutions 与 org.locationtech 并存)、以及跑通了程序却不知道如何验证结果对不对。本章把这三件事彻底讲清楚,并介绍 JTS 自带的三个工具:TestBuilder、JtsOp、TestRunner——它们能在没有 IDE、没有测试框架的情况下,快速验证一个几何运算的结果。
读完后你应该能够:在 Maven/Gradle/纯 jar 三种方式下正确接入 JTS;写出第一个可运行、可调试的程序;用 JShell 做交互式验证;用 TestBuilder 可视化几何;用 JtsOp 做命令行批量运算;并知道遇到依赖冲突、类找不到、结果异常时从哪里排查。
2.1 环境要求
2.1.1 版本矩阵
JTS 本身对运行环境的要求很低,但“能跑”和“跑得顺”是两回事。下面这张表是实践中的稳妥组合:
| 组件 | 最低要求 | 推荐 | 说明 |
|---|---|---|---|
| JDK / JRE | Java 8 | Java 11 / 17 | jts-core 的字节码目标为 1.8,Java 8 即可运行 |
| 构建工具 | Maven 3.5+ | Maven 3.8/3.9 | 仅编译 JTS 源码时需要 |
| Gradle | 6.x | 8.x | 使用 Gradle 的项目 |
| 内存 | 256 MB | 1 GB+ | 大几何与索引会吃掉更多内存 |
| 磁盘 | 5 MB | 50 MB | jar 本身很小,源码与依赖另算 |
关于 JDK 版本有一个细节:JTS 的 pom.xml 中 maven.compiler.source 与 target 都是 1.8,意味着产出的 class 文件是 Java 8 字节码。你完全可以用 JDK 17 来编译和运行调用 JTS 的项目,但如果要编译 JTS 源码本身,注意 JDK 9+ 之后 -source 1.8 已被标记为过时(会有警告),JDK 21 起部分版本甚至不再支持 1.8 目标。稳妥做法是用 JDK 8 或 11 编译 JTS 源码。
2.1.2 检查本机环境
java -version
# openjdk version "17.0.9" 2023-10-17
# OpenJDK Runtime Environment (build 17.0.9+11)
# OpenJDK 64-Bit Server VM (build 17.0.9+11, mixed mode)
mvn -version
# Apache Maven 3.8.8
# Maven home: ...
# Java version: 17.0.9, vendor: Eclipse Adoptium
如果 java -version 显示的是 1.8,也完全没问题。真正需要确认的是编译目标与运行 JDK 一致:用高版本 JDK 编译、低版本 JDK 运行会报 UnsupportedClassVersionError。
2.1.3 依赖大小与内存
jts-core 的 jar 只有 1 MB 出头,这是它在嵌入式、Android、Serverless 场景受欢迎的原因之一。但要注意:几何对象本身的内存占用与顶点数量成正比。百万顶点的线串在默认堆内存下很容易 OOM,做大数据处理时要提前规划(第 20 章有专门的内存与性能讨论)。
2.2 三种接入方式
2.2.1 Maven(推荐)
在 pom.xml 中加入依赖:
<properties>
<jts.version>1.20.0</jts.version>
</properties>
<dependencies>
<!-- 核心:几何模型、算法、索引、WKT/WKB -->
<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
<version>${jts.version}</version>
</dependency>
</dependencies>
需要 GeoJSON / TWKB 时,追加:
<dependency>
<groupId>org.locationtech.jts.io</groupId>
<artifactId>jts-io-common</artifactId>
<version>${jts.version}</version>
</dependency>
注意 groupId 的差异:jts-core 属于 org.locationtech.jts,而 jts-io-common 属于 org.locationtech.jts.io(它的 parent 才是 org.locationtech.jts:jts-io)。这是 JTS 多模块设计留下的一个“历史包袱”,写错 groupId 会得到 “Could not find artifact” 错误。本章 2.9 节有一份完整的坐标对照表。
2.2.2 使用 SNAPSHOT 版本
如果你想试用 1.21 的新特性(CoverageCleaner、MinimumBoundingTriangle 等),需要从 Eclipse 的 LocationTech 仓库拉取快照。官方 USING.md 给出了配置:
<repositories>
<repository>
<id>locationtech-releases</id>
<url>https://repo.eclipse.org/content/repositories/jts</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>jts-snapshots</id>
<url>https://repo.eclipse.org/content/repositories/jts-snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
然后把版本改成 1.21.0-SNAPSHOT。生产项目不要依赖 SNAPSHOT:快照每天可能变化,构建不可复现,而且没有版本保证。正确姿势是等正式版发布,或把需要的类暂时复制到自己的 lab 包中(注意遵守许可证)。
2.2.3 Gradle
dependencies {
implementation 'org.locationtech.jts:jts-core:1.20.0'
// 需要 GeoJSON/TWKB 时
implementation 'org.locationtech.jts.io:jts-io-common:1.20.0'
}
Kotlin DSL 写法:
dependencies {
implementation("org.locationtech.jts:jts-core:1.20.0")
implementation("org.locationtech.jts.io:jts-io-common:1.20.0")
}
Gradle 的依赖仲裁比 Maven 更“聪明”一些:默认选最高版本。如果多个依赖传递引用了不同版本的 jts-core,Gradle 会选最高版本,这通常是安全的;但仍建议用 dependencyManagement(Maven)或 constraints(Gradle)显式锁定,避免“今天能跑、明天换个依赖就变版本”的问题。
2.2.4 纯 jar 与手工 classpath
不借助构建工具时,从 Maven Central 下载 jar:
# 目录结构
lib/
└── jts-core-1.20.0.jar
# 编译
javac -cp lib/jts-core-1.20.0.jar -d out src/HelloJts.java
# 运行
java -cp "lib/jts-core-1.20.0.jar;out" HelloJts # Windows
java -cp "lib/jts-core-1.20.0.jar:out" HelloJts # Linux/macOS
注意 Windows 用分号 ;、Linux/macOS 用冒号 : 分隔 classpath。这是跨平台脚本里最常见的错误之一。
2.2.5 在 IDE 中配置
IntelliJ IDEA:新建 Maven 项目后,在 pom.xml 里加依赖,IDEA 会自动下载并索引。若想调试 JTS 源码,可以把源码 jar 或仓库目录附加到依赖上:File → Project Structure → Libraries → jts-core → 添加 Sources,指向 D:/self/code/jts/modules/core/src/main/java。
Eclipse:File → New → Maven Project,勾选 “Create a simple project”,填入 groupId/artifactId,然后在 pom 里加依赖。
VS Code:安装 “Extension Pack for Java” 与 “Maven for Java”,打开含 pom.xml 的目录即可。
NetBeans:File → New Project → Java with Maven,后续同理。
2.2.6 Android 与 Kotlin
jts-core 是纯 Java 字节码,Android 可以直接使用:
android {
// ...
}
dependencies {
implementation 'org.locationtech.jts:jts-core:1.20.0'
}
注意 Android 的 minSdkVersion 与 Java 8 语言特性支持(desugar_jdk_libs)。JTS 本身不依赖 Java 8 的 lambda 之外的特性,兼容性良好。Kotlin 项目直接使用即可,Kotlin 的扩展函数可以给 Geometry 加上更符合习惯的包装。
2.3 第一个程序:完整可运行版本
2.3.1 工程结构
从零开始建一个最小工程:
jts-demo/
├── pom.xml
└── src/
└── main/
└── java/
└── demo/
└── QuickStart.java
2.3.2 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>demo</groupId>
<artifactId>jts-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jts.version>1.20.0</jts.version>
</properties>
<dependencies>
<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
<version>${jts.version}</version>
</dependency>
</dependencies>
</project>
2.3.3 QuickStart.java
package demo;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.io.WKTReader;
import org.locationtech.jts.io.WKTWriter;
public class QuickStart {
private static final GeometryFactory GF = new GeometryFactory();
private static final WKTReader READER = new WKTReader(GF);
private static final WKTWriter WRITER = new WKTWriter();
public static void main(String[] args) throws Exception {
demo1ParseAndPrint();
demo2CreateFromCoordinates();
demo3Relations();
demo4Buffer();
}
/** 演示 1:WKT 解析与输出 */
private static void demo1ParseAndPrint() throws Exception {
Geometry g = READER.read("LINESTRING (0 0, 10 10, 20 0)");
System.out.println("[1] 类型 = " + g.getGeometryType());
System.out.println("[1] 长度 = " + g.getLength()); // 28.284...
System.out.println("[1] 包络 = " + g.getEnvelopeInternal()); // Envelope[0 : 20, 0 : 10]
System.out.println("[1] WKT = " + WRITER.write(g));
}
/** 演示 2:用坐标数组创建几何 */
private static void demo2CreateFromCoordinates() {
Coordinate[] coords = new Coordinate[] {
new Coordinate(0, 0),
new Coordinate(10, 0),
new Coordinate(10, 10),
new Coordinate(0, 10),
new Coordinate(0, 0)
};
Polygon polygon = GF.createPolygon(coords);
System.out.println("[2] 面积 = " + polygon.getArea()); // 100.0
System.out.println("[2] 周长 = " + polygon.getLength()); // 40.0
Point centroid = polygon.getCentroid();
System.out.println("[2] 质心 = " + centroid); // POINT (5 5)
}
/** 演示 3:空间关系判断 */
private static void demo3Relations() throws Exception {
Geometry a = READER.read("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))");
Geometry b = READER.read("POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))");
System.out.println("[3] a 与 b 相交 = " + a.intersects(b)); // true
System.out.println("[3] a 包含 (6,6) = "
+ a.contains(GF.createPoint(new Coordinate(6, 6)))); // true
System.out.println("[3] 交集面积 = " + a.intersection(b).getArea()); // 25.0
}
/** 演示 4:缓冲区 */
private static void demo4Buffer() throws Exception {
LineString line = (LineString) READER.read("LINESTRING (0 0, 10 0)");
Geometry buffered = line.buffer(1.0);
System.out.println("[4] 缓冲后类型 = " + buffered.getGeometryType()); // Polygon
System.out.println("[4] 缓冲后面积 ≈ " + buffered.getArea()); // ≈ 20 + π
}
}
2.3.4 运行与输出
mvn -q compile exec:java -Dexec.mainClass=demo.QuickStart
或直接在 IDE 里运行 main。输出:
[1] 类型 = LineString
[1] 长度 = 28.284271247461902
[1] 包络 = Envelope[0.0 : 20.0, 0.0 : 10.0]
[1] WKT = LINESTRING (0 0, 10 10, 20 0)
[2] 面积 = 100.0
[2] 周长 = 40.0
[2] 质心 = POINT (5 5)
[3] a 与 b 相交 = true
[3] 交集面积 = 25.0
[4] 缓冲后类型 = Polygon
[4] 缓冲后面积 ≈ 22.283185307179585
如果输出与此一致,说明环境完全没问题。注意 [2] 质心 = POINT (5 5) 的打印格式来自 Point.toString(),它等价于 WKT 输出。
2.3.5 这段代码示范了什么
| 演示 | 覆盖的知识点 | 对应章节 |
|---|---|---|
| 1 | WKT 解析、长度、包络、序列化 | 第 6 章 |
| 2 | GeometryFactory 创建、面积/长度/质心 | 第 4、5、11 章 |
| 3 | 空间谓词、叠加 | 第 8、9 章 |
| 4 | 缓冲区 | 第 10 章 |
一个值得注意的细节:GeometryFactory、WKTReader、WKTWriter 都是线程安全的(无状态或内部状态只读),可以定义为 static final 复用。但 Geometry 对象本身不是严格线程安全的——不是说不能多线程读,而是不要在多线程间“一边算一边改”。这个区别在第 20 章会展开。
2.4 JShell 交互式体验
2.4.1 启动 JShell
JDK 9+ 自带 JShell,非常适合快速验证 API:
# 把 jts-core 加入 classpath 后启动
jshell --class-path lib/jts-core-1.20.0.jar
如果使用 Maven 项目,可以用:
mvn dependency:build-classpath -Dmdep.outputFile=cp.txt
jshell --class-path "lib/jts-core-1.20.0.jar:$(cat cp.txt)"
Windows 下路径分隔符换成 ;。
2.4.2 常用片段
import org.locationtech.jts.geom.*;
import org.locationtech.jts.io.*;
WKTReader r = new WKTReader();
Geometry a = r.read("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))");
Geometry b = r.read("POLYGON ((5 0, 15 0, 15 10, 5 10, 5 0))");
a.getArea(); // 100.0
a.intersection(b); // POLYGON ((5 0, 10 0, 10 10, 5 10, 5 0))
a.distance(b); // 0.0
a.relate(b); // 212101212 (DE-9IM 矩阵)
JShell 会实时打印表达式结果,非常适合“这个方法到底返回什么”的探索。比写一个 main 再运行快得多。
2.4.3 JShell 的局限
- 不能方便地调试(没有断点);
- 类路径需要手动维护,依赖多了很麻烦;
- 长代码不好管理。
所以 JShell 适合“验证单个 API”,不适合做完整实验。要做完整实验,还是建一个 Maven 工程,或者用 TestBuilder。
2.5 JTS TestBuilder:可视化试验场
2.5.1 它是什么
TestBuilder 是 JTS 自带的 Swing GUI 工具(modules/app),能:
- 手工输入/编辑 WKT 几何,或从文件加载;
- 执行 JTS TestBuilder 中注册的所有函数(几乎覆盖 JTS 全部能力);
- 对比运算前后的几何,查看结果 WKT;
- 运行 XML 测试用例,查看通过/失败;
- 可视化显示几何、顶点、标签。
对学习 JTS 来说,这是最高效的“所见即所得”工具。官方也用它做算法演示与 bug 复现。
2.5.2 获取与启动
方式一:从源码构建(需要 Maven):
cd D:/self/code/jts
mvn clean install -DskipTests
# 生成 modules/app/target/JTSTestBuilder.jar
启动(官方推荐带上内存参数):
java -jar modules/app/target/JTSTestBuilder.jar -Xmx2000M
注意 -Xmx2000M 要放在 -jar 之后,这是 TestBuilder 自己解析的参数。
macOS 上如果界面怪异,可以指定 Metal 外观:
java -Dswing.defaultlaf=javax.swing.plaf.metal.MetalLookAndFeel \
-jar modules/app/target/JTSTestBuilder.jar -Xmx2000M
方式二:用 bin 脚本:仓库 bin/ 目录下有 testbuilder.sh/testbuilder.bat```,内部已封装启动命令。
方式三:直接下载 Release:从 GitHub Releases 页面下载带 TestBuilder 的发行包。
2.5.3 界面速览
TestBuilder 的界面分为几个区域(不同版本略有差异):
┌────────────────────────────────────────────────────────────┐
│ 菜单栏:File / Edit / View / Tools ... │
├──────────────────────────────┬─────────────────────────────┤
│ │ 函数面板(Function Tree) │
│ 几何显示区 │ ├ Geometry │
│ (A、B、结果三种颜色) │ ├ Overlay │
│ │ ├ Buffer │
│ │ └ ... │
├──────────────────────────────┴─────────────────────────────┤
│ 几何输入区:A 的 WKT / B 的 WKT / 结果 WKT │
├────────────────────────────────────────────────────────────┤
│ 状态栏:坐标、提示信息 │
└────────────────────────────────────────────────────────────┘
2.5.4 一次典型操作
- 在 A 输入框粘贴:
POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0)) - 在 B 输入框粘贴:
POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5)) - 在函数树里找到
Overlay → Intersection,点击执行; - 结果框出现交集的 WKT,显示区高亮画出交集区域;
- 想调参数?
Buffer → Buffer的函数的参数框会显示 distance 等参数,改完再执行。
这个“改参数看结果”的循环,比在 IDE 里改代码快一个数量级,特别适合回答“这个参数到底怎么影响结果”。
2.5.5 XML 测试与 TestRunner
TestBuilder 还能加载 XML 测试文件(.xml),逐个用例执行并与期望结果比对。XML 测试是 JTS 官方的主要回归测试形式之一(另一种是 JUnit),格式大致如下:
<run>
<case>
<desc>buffer of a point</desc>
<a>POINT (0 0)</a>
<test><op name="buffer" arg1="A" arg2="1">POLYGON ((...))</op></test>
</case>
</run>
命令行版本是 TestRunner:
cd D:/self/code/jts
bin/testrunner.sh -files modules/tests/src/test/resources/testxml/general/TestBuffer.xml
Windows 用 bin\testrunner.bat。TestRunner 的输出会列出每个用例的期望值、实际值、通过状态。第 21 章讲测试体系时会详细展开。
2.5.6 TestBuilder 的价值与局限
| 适合 | 不适合 |
|---|---|
| 快速验证一个函数的语义 | 批量数据处理 |
| 复现 bug(保存成 XML 用例) | 集成进 CI |
| 观察几何形状与顶点 | 性能测试(GUI 开销大) |
| 学习算法参数的影响 | 生产环境部署(Swing GUI) |
2.6 JtsOp:命令行几何计算器
2.6.1 它是什么
JtsOp 是一个 CLI 工具,把 TestBuilder 里注册的函数搬到了命令行。它能读 WKT/WKB/GeoJSON/GML/SHP,执行任意一个函数,输出 WKT/WKB/GeoJSON/GML/SVG,还能用管道串联多个操作。
对运维、批处理、快速验证来说,这比写 Java 代码快得多。
2.6.2 启动
cd D:/self/code/jts
bin/jtsop.sh -help
Windows 用 bin\jtsop.bat -help。它会打印用法和所有可用函数列表(这个列表很长,就是 TestBuilder 的函数树)。
2.6.3 常用示例
# 计算 WKT 的面积
jtsop -a "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))" -f txt area
# 两个几何求交,输出 WKT
jtsop -a "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))" \
-b "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))" \
-f wkt Overlay.intersection
# 缓冲区,输出 GeoJSON
jtsop -a "POINT (10 10)" -f geojson Buffer.buffer 10
# 批量对文件里的几何做 unary union
jtsop -a geoms.wkt -collect -f wkb Overlay.unaryUnion
# 校验一批几何的有效性
jtsop -a geoms.wkt -limit 100 -offset 40 -f txt Geometry.isValid
# 用管道串联:生成随机点 → 缓冲区
jtsop -f wkb CreateRandomShape.randomPoints 10 | jtsop -a stdin -f wkt Buffer.buffer 1
2.6.4 输出格式与选项
| 选项 | 作用 |
|---|---|
-a / -b |
指定 A/B 输入(字面量、文件名或 stdin) |
-f |
输出格式:wkt、wkb、geojson、gml、svg、txt |
-v |
输出输入信息与耗时 |
-time |
只输出耗时 |
-index |
二元运算使用空间索引 |
-collect |
把 A 收集成一个 GeometryCollection |
-eacha / -eachb |
把输入拆成单个几何逐个处理 |
-explode |
结果拆成多个几何输出 |
-srid |
给输出设置 SRID |
-args |
一组参数多值执行(用于调参) |
-repeat |
重复执行 n 次(用于测性能) |
-geomfunc |
动态加载自定义函数类 |
2.6.5 一个真实用途:结果比对
假设你把 JTS 从 1.19 升级到 1.20,担心某个 buffer 结果变了。可以用 JtsOp 对同一批数据跑两遍(两个版本的 jar),再 diff:
# 用 1.19 跑
java -cp jts-core-1.19.0.jar:jts-app.jar ... jtsop -a data.wkt -f wkt Buffer.buffer 5 > out-119.txt
# 用 1.20 跑
java -cp jts-core-1.20.0.jar:jts-app.jar ... jtsop -a data.wkt -f wkt Buffer.buffer 5 > out-120.txt
diff out-119.txt out-120.txt
这比写测试框架轻,比肉眼看几何靠谱。第 21 章会把这个思路扩展成正式的升级验证流程。
2.7 从源码构建 JTS
2.7.1 为什么需要自己构建
三种情况需要:
- 想读/改 JTS 源码(第 21、22 章);
- 想要 TestBuilder / JtsOp 工具(发行包里不一定带);
- 需要未发布的 SNAPSHOT 特性。
2.7.2 构建命令
官方 DEVELOPING.md 给了标准命令(在仓库根目录执行):
# 完整构建(含测试)
mvn clean install
# 跳过测试(快)
mvn clean install -DskipTests
# 只编译并安装 core 模块
mvn install -pl modules/core -am
# 构建带 Oracle/SDE 支持的 io 模块(需要专有驱动)
mvn install -Poracle
mvn install -Parcsde
# 构建所有 profile
mvn install -Dall=true
# 只构建发布产物
mvn install -Drelease=true
2.7.3 质量检查
JTS 的 mvn verify 会跑 PMD、Checkstyle 等静态检查:
mvn verify
# 跳过静态检查(只跑测试)
mvn verify -Dpmd.skip=true -Dcheckstyle.skip=true
如果只是想看某个模块的报告:
mvn site:site
# 然后打开 modules/core/target/site/index.html
2.7.4 构建产物
modules/core/target/jts-core-1.21.0-SNAPSHOT.jar
modules/io/common/target/jts-io-common-1.21.0-SNAPSHOT.jar
modules/app/target/JTSTestBuilder.jar
modules/tests/target/jts-tests-1.21.0-SNAPSHOT.jar
构建完成后,本地 Maven 仓库(~/.m2/repository)里会有 SNAPSHOT 版本,其他项目可以直接引用。
2.7.5 常见构建问题
| 现象 | 原因 | 解决 |
|---|---|---|
Fatal error compiling: invalid target release: 1.8 |
JDK 版本过高 | 换 JDK 8/11,或改用 --release 8 |
| 下载依赖卡住 | 网络/代理 | 配置 Maven settings.xml 镜像 |
invalid LOC header |
jar 下载损坏 | 删除 ~/.m2 中对应目录重下 |
| TestBuilder 启动黑屏 | 缺少图形环境 / 字体 | 用 -Dswing.defaultlaf 换外观;无头服务器无法运行 GUI |
| 构建成功但找不到类 | 只 mvn compile 未 install |
用 mvn install 或 -am |
2.8 调试与验证技巧
2.8.1 打印几何的三种方式
Geometry g = ...;
// 1. 默认 toString():标准 WKT 风格
System.out.println(g);
// 2. WKTWriter:可控格式
System.out.println(new WKTWriter().write(g));
// 3. 调试用详细信息
System.out.println("type=" + g.getGeometryType()
+ ", srid=" + g.getSRID()
+ ", numPoints=" + g.getNumPoints()
+ ", area=" + g.getArea()
+ ", length=" + g.getLength()
+ ", envelope=" + g.getEnvelopeInternal());
2.8.2 断点调试点
JTS 的类层次不深,调试时建议在以下位置下断点:
Geometry.union/intersection的入口(看是否走了 overlayng);OverlayOp.overlay/OverlayNGRobust.overlay(看引擎);RobustLineIntersector(看精度问题);IsValidOp.checkValid(看无效原因)。
2.8.3 结果“看起来不对”时
按这个顺序排查(与第 18 章呼应):
- 坐标系:是不是经纬度直接算面积/距离(第 19 章);
- 几何有效性:用
IsValidOp检查(第 17 章); - 引擎:是否期望新引擎但实际走了旧的(本章 1.6 节、第 9 章);
- 精度:是否需要
PrecisionModel或 snap-rounding(第 18 章); - 版本:是不是 SNAPSHOT 行为变化(本章 2.2.2)。
这五步能排掉 90% 的“玄学问题”。
2.9 Maven 坐标对照表
JTS 的模块坐标容易记混,这里给一张完整表(版本统一用同一个属性):
| 模块 | groupId | artifactId | 说明 |
|---|---|---|---|
| 父 POM | org.locationtech.jts | jts | 版本管理 |
| 核心 | org.locationtech.jts | jts-core | 必引 |
| IO 父模块 | org.locationtech.jts | jts-io | 打包组织 |
| IO 通用 | org.locationtech.jts.io | jts-io-common | GeoJSON/TWKB |
| Oracle IO | org.locationtech.jts.io | jts-io-ora | 需专有驱动 |
| SDE IO | org.locationtech.jts.io | jts-io-sde | 需专有驱动 |
| TestBuilder | org.locationtech.jts | jts-app | GUI 工具 |
| 测试套件 | org.locationtech.jts | jts-tests | XML 测试 |
| 实验功能 | org.locationtech.jts | jts-lab | 不稳定 API |
小抄:只有 core/app/tests/lab 在 org.locationtech.jts 下,所有 io 模块在 org.locationtech.jts.io 下。
2.10 内网与离线环境接入
2.10.1 为什么需要离线方案
JTS 本身很小,但在一些场景下无法访问 Maven Central:内网构建机、涉密环境、CI 缓存受限、或者只是想让构建可复现。JTS 的优势在于依赖极少,离线方案因此特别简单。
2.10.2 方案一:本地仓库预置
在有网机器上下载 jar 与 pom,拷进内网机器的本地仓库目录:
# 有网机器:下载依赖到指定目录
mvn dependency:copy -Dartifact=org.locationtech.jts:jts-core:1.20.0 \
-DoutputDirectory=./offline-libs
# 内网机器:手工安装进本地仓库
mvn install:install-file \
-Dfile=./offline-libs/jts-core-1.20.0.jar \
-DgroupId=org.locationtech.jts \
-DartifactId=jts-core \
-Dversion=1.20.0 \
-Dpackaging=jar
之后内网项目就能像正常一样引用依赖。
2.10.3 方案二:system scope(不推荐)
<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
<version>1.20.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/jts-core-1.20.0.jar</systemPath>
</dependency>
system scope 会在打包时被排除(除非额外配置),且 Maven 官方已不推荐,仅作为临时手段。
2.10.4 方案三:私有仓库代理
企业内网更规范的做法是搭 Nexus/Artifactory 代理 Maven Central,或使用阿里云/华为云的镜像:
<mirrors>
<mirror>
<id>aliyun</id>
<mirrorOf>central</mirrorOf>
<url>https://maven.aliyun.com/repository/central</url>
</mirror>
</mirrors>
2.10.5 方案四:直接把源码编译进项目
如果只需要少量类(例如只想要 WKT 读写),可以把对应的几个源文件复制到自己的包下(注意遵守 EPL/EDL 的版权声明要求)。这种做法在 Android、小程序后端等对包体积敏感的场景偶尔可见,但要承担后续升级维护成本。
2.10.6 校验 jar 完整性
# 查看 jar 内容
jar tf jts-core-1.20.0.jar | head -20
# 校验 sha1(Maven Central 提供 .sha1 文件)
sha1sum jts-core-1.20.0.jar
2.11 依赖冲突排查实战
2.11.1 典型症状
JTS 的依赖冲突通常不报“找不到类”,而是报方法找不到或行为异常:
| 症状 | 可能原因 |
|---|---|
NoSuchMethodError: Geometry.getZ() |
运行期加载了 1.15 之前的旧 jar |
NoClassDefFoundError: org/locationtech/jts/.. |
依赖被其他库的 exclude 掉了 |
ClassCastException 涉及 com.vividsolutions |
新旧包名混用 |
| 同一方法在两个版本行为不同 | 传递依赖把 jts-core 降级了 |
2.11.2 第一步:看依赖树
# Maven:只显示 jts 相关
mvn dependency:tree -Dincludes=org.locationtech.jts
# 输出示例:
# [INFO] +- org.locationtech.jts:jts-core:jar:1.20.0:compile
# [INFO] | \- (无传递依赖)
# [INFO] +- com.example:geo-utils:jar:1.0:compile
# [INFO] | \- org.locationtech.jts:jts-core:jar:1.16.1:compile ← 冲突来源
Gradle 用 gradle dependencies --configuration runtimeClasspath | grep jts。
2.11.3 第二步:锁定版本
Maven 在父 pom 的 dependencyManagement 里锁定:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
<version>1.20.0</version>
</dependency>
<dependency>
<groupId>org.locationtech.jts.io</groupId>
<artifactId>jts-io-common</artifactId>
<version>1.20.0</version>
</dependency>
</dependencies>
</dependencyManagement>
Gradle 用 constraints 或 resolutionStrategy.force。
2.11.4 第三步:排除旧依赖
<dependency>
<groupId>com.example</groupId>
<artifactId>geo-utils</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
</exclusion>
</exclusions>
</dependency>
2.11.5 第四步:运行时确认
// 确认运行期实际加载的 JTS 版本
Package pkg = org.locationtech.jts.JTSVersion.class.getPackage();
System.out.println(pkg.getImplementationVersion());
// 或直接打印版本常量
System.out.println(org.locationtech.jts.JTSVersion.CURRENT_VERSION);
如果打印出来的版本与 pom 里写的不一致,就是依赖仲裁出了问题。
2.11.6 一个真实案例
某项目同时引入了 GeoTools 与一个自研工具库,后者依赖 JTS 1.16。Maven 的“最近优先”策略让 JTS 1.16 胜出,结果 GeoTools 里调用的 RelateNG 直接 NoSuchMethodError。修复方式是在父 pom 锁定 1.20.0 并排除自研库里的旧依赖——升级 JTS 版本时,务必确认所有下游库都兼容。
2.12 常见问题与避坑
2.10.1 依赖与类加载
Q1:Could not find artifact org.locationtech.jts:jts-io-common?
groupId 写错了,应该是 org.locationtech.jts.io:jts-io-common。
Q2:NoClassDefFoundError: org/locationtech/jts/geom/Geometry?
依赖没进 classpath。Maven 项目检查 mvn dependency:tree;纯 jar 检查 -cp。
Q3:项目里同时存在新旧包名?
检查依赖树里有没有还停留在 1.14 及更早的库(com.vividsolutions:jts)。用 mvn dependency:tree | grep -i jts 排查,统一升级。
Q4:多个库依赖不同版本的 jts-core 怎么办?
在父 pom 的 dependencyManagement 中锁定版本,这是最可靠的做法。
2.10.2 运行与输出
Q5:UnsupportedClassVersionError?
编译 JDK 版本高于运行 JDK 版本。统一版本,或降低 target。
Q6:中文乱码?
Windows 终端默认编码可能是 GBK。加 -Dfile.encoding=UTF-8@,或在 IDE 中设置控制台编码。
Q7:Geometry.toString() 输出的坐标精度很高?
默认保留坐标原始值。需要控制精度用 WKTWriter 的 setPrecisionModel / setFormatted(第 6 章)。
2.10.3 工具使用
Q8:TestBuilder 启动后窗口空白? 可能是无头环境(服务器)或缺少图形库。TestBuilder 是 Swing 程序,必须在有桌面环境的机器上运行。
Q9:JtsOp 找不到函数名?
用 jtsop -help 查看完整函数列表;大小写敏感,函数名通常形如 Overlay.intersection、Buffer.buffer。
Q10:TestRunner 与 TestBuilder 的关系? TestRunner 是命令行版,专跑 XML 测试;TestBuilder 是 GUI 版,可以手工调试同一个 XML。两者共享同一套测试引擎。
2.13 本章小结
- JTS 接入极简:一个
jts-corejar 即可,JDK 8+ 可运行; - Maven 坐标要记牢:core 在
org.locationtech.jts,io 在org.locationtech.jts.io; - 老项目迁移注意包名(
com.vividsolutions→org.locationtech)与坐标变化; - JShell 适合验证单个 API,TestBuilder 适合可视化调试,JtsOp 适合命令行/批处理,TestRunner 适合 XML 回归测试;
- 三个工具都来自 JTS 自身的
modules/app与modules/tests,构建mvn clean install后可用; - 从源码构建耗时但值得,尤其当你需要读源码或改源码时;
- 结果异常先查五件事:坐标系、有效性、引擎、精度、版本。
相关资源
- JTS 用户指南:
https://github.com/locationtech/jts/blob/master/USING.md - JTS 开发指南:
https://github.com/locationtech/jts/blob/master/DEVELOPING.md - JTS 工具说明:
https://github.com/locationtech/jts/blob/master/doc/TOOLS.md - JtsOp 用户指南:
https://github.com/locationtech/jts/blob/master/doc/JTSOp.md - TestBuilder 说明:
https://github.com/locationtech/jts/blob/master/doc/JTSTestBuilder.md - Maven Central:
https://mvnrepository.com/artifact/org.locationtech.jts - 上一章:第01章 - JTS 概述与生态定位
- 下一章:第03章 - 核心架构与源码结构