目录
-
- 一、前言
- 二、环境准备
-
- [Maven 依赖](#Maven 依赖)
- 三、核心能力总览
- [四、Shp → GeoJSON](#四、Shp → GeoJSON)
- [五、GeoJSON → Shapefile(重点)](#五、GeoJSON → Shapefile(重点))
-
- [坑位一:Schema 写死导致属性丢失](#坑位一:Schema 写死导致属性丢失)
- [坑位二:未显式绑定 CRS 导致 QGIS 无法渲染](#坑位二:未显式绑定 CRS 导致 QGIS 无法渲染)
- [坑位三:3D 坐标导致写入异常](#坑位三:3D 坐标导致写入异常)
- 完整代码
- [2D 降维工具方法](#2D 降维工具方法)
- [六、Geometry 与 WKT 互转](#六、Geometry 与 WKT 互转)
-
- [Geometry → WKT](#Geometry → WKT)
- [WKT → Geometry](#WKT → Geometry)
- [七、Shapefile → WKT 批量导出](#七、Shapefile → WKT 批量导出)
- 八、完整可运行代码汇总
- 九、常见坑位与注意事项
- 十、工程实践建议
- 十一、小结
一、前言
在 GIS 开发中,空间数据格式转换是最常见的需求之一。不同系统、不同场景对数据格式的要求各不相同:
- WebGIS 前端渲染:通常使用 WKT或GeoJSON
- 永久化存储 / 专业 GIS 软件:数据库一般是Postgis或者是mysql spatial,文件一般是Shapefile,gdb
- 接口传输 / 日志 / SQL 存储:WKT 是最简洁的文本交换格式,数据库存储一般是使用WKT字符串或者是Geometry
本文将使用 GeoTools 29.3 + JTS 1.19.0,从工程实战视角讲解四种核心格式转换场景,并重点分析其中的坑位与解决方案。
二、环境准备
Maven 依赖
xml
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-shapefile</artifactId>
<version>29.3</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-geojson</artifactId>
<version>29.3</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-epsg-hsql</artifactId>
<version>29.3</version>
</dependency>
<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
<version>1.19.0</version>
</dependency>
三、核心能力总览
| 转换方向 | 核心技术 | 典型业务场景 |
|---|---|---|
| Shp → GeoJSON | FeatureJSON + GeometryJSON | WebGIS 前端渲染 |
| GeoJSON → Shp | FeatureJSON 读取 + ShapefileDataStore 写入 | 数据入库 / 分发 |
| Geometry ↔ WKT | WKTWriter / WKTReader | 日志 / 接口 / SQL |
| Shp → WKT | DataStore + WKTWriter | 数据导出 / 人工审查 |
四、Shp → GeoJSON
这是最常见的转换场景,将 Shapefile 转换为 GeoJSON,便于 WebGIS 前端渲染。
核心步骤
DataStoreFinder读取 Shapefile- 获取
FeatureCollection FeatureJSON写出为 GeoJSON 格式
关键代码
java
private static void shpToGeoJSON(String shpPath, String geojsonPath) throws Exception {
Map<String, Serializable> params = new HashMap<>();
params.put("url", new File(shpPath).toURI().toURL());
params.put("charset", StandardCharsets.UTF_8.name());
DataStore store = null;
try {
store = DataStoreFinder.getDataStore(params);
String typeName = store.getTypeNames()[0];
FeatureCollection<?, ?> collection =
store.getFeatureSource(typeName).getFeatures();
try (FileOutputStream fos = new FileOutputStream(geojsonPath);
BufferedOutputStream bos = new BufferedOutputStream(fos)) {
FeatureJSON fjson = new FeatureJSON(new GeometryJSON(7));
fjson.writeFeatureCollection(collection, bos);
}
} finally {
if (store != null) store.dispose();
}
}
注意事项
GeometryJSON(7)中的7表示小数保留 7 位精度,可根据需求调整DataStore在 GeoTools 29.x 中不再实现AutoCloseable,必须手动dispose()
五、GeoJSON → Shapefile(重点)
这是本文最重要的一个章节,也是坑最多的场景。
坑位一:Schema 写死导致属性丢失
初期写法中,Schema 被"写死"为固定的三个字段:the_geom / name / value。这导致 GeoJSON 中的原始属性字段全部丢失。
正确做法:动态读取 GeoJSON 的 Schema,继承其属性字段结构。
坑位二:未显式绑定 CRS 导致 QGIS 无法渲染
如果不显式设置 CRS,生成的 Shapefile 可能缺失 .prj 文件或 CRS 不正确,QGIS 会提示 "要素不含几何图形"。
解决方案 :显式调用 tb.setCRS(CRS.decode("EPSG:4326"))。
坑位三:3D 坐标导致写入异常
部分 GeoJSON 数据含有 Z 坐标,而 Shapefile 不支持 3D 几何。必须强制降维为 2D。
解决方案 :使用 CoordinateSequenceFilter 删除 Z 坐标。
完整代码
java
// ==================== GeoJSON → Shp ===================
private static void geojsonToShp(String geojsonPath, String shpPath) throws Exception {
try (InputStream in = new FileInputStream(geojsonPath)) {
FeatureJSON fjson = new FeatureJSON(new GeometryJSON());
FeatureCollection<?, ?> collection = fjson.readFeatureCollection(in);
Map<String, Serializable> params = new HashMap<>();
params.put(ShapefileDataStoreFactory.URLP.key, new File(shpPath).toURI().toURL());
params.put(ShapefileDataStoreFactory.CREATE_SPATIAL_INDEX.key, true);
ShapefileDataStore ds = null;
try {
ShapefileDataStoreFactory factory = new ShapefileDataStoreFactory();
ds = (ShapefileDataStore) factory.createNewDataStore(params);
ds.setCharset(StandardCharsets.UTF_8);
// 取第一个要素,推断几何类型 + 原始属性结构
SimpleFeature firstFeature = null;
try (FeatureIterator<?> it = collection.features()) {
if (it.hasNext()) {
firstFeature = (SimpleFeature) it.next();
}
}
if (firstFeature == null) {
throw new RuntimeException("GeoJSON 中没有任何要素");
}
Geometry firstGeom = (Geometry) firstFeature.getDefaultGeometry();
Class<?> geomType = firstGeom.getClass();
// Shapefile 只支持 2D,强制降维
if (geomType.getSimpleName().contains("3D") ||
firstGeom.getCoordinate().getZ() != Double.NaN) {
if (firstGeom instanceof Point) {
geomType = Point.class;
} else if (firstGeom instanceof LineString) {
geomType = LineString.class;
} else if (firstGeom instanceof Polygon) {
geomType = Polygon.class;
}
}
// 动态构建 Schema(继承 GeoJSON 的字段)
SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder();
tb.setName("layer");
tb.setCRS(org.geotools.referencing.CRS.decode("EPSG:4326"));
tb.add("the_geom", geomType);
// 继承 GeoJSON 中的非几何属性字段
SimpleFeatureType originalSchema =
(SimpleFeatureType) collection.getSchema();
for (int i = 0; i < originalSchema.getAttributeCount(); i++) {
org.opengis.feature.type.AttributeDescriptor ad =
originalSchema.getDescriptor(i);
if (ad instanceof org.opengis.feature.type.GeometryDescriptor) {
continue; // 跳过几何字段
}
String attrName = ad.getLocalName();
Class<?> attrType = ad.getType().getBinding();
tb.add(attrName, attrType);
}
ds.createSchema(tb.buildFeatureType());
// 写入要素(按字段名拷贝)
try (FeatureWriter<SimpleFeatureType, SimpleFeature> writer =
ds.getFeatureWriterAppend(
ds.getTypeNames()[0],
Transaction.AUTO_COMMIT)) {
try (FeatureIterator<?> it = collection.features()) {
while (it.hasNext()) {
SimpleFeature from = (SimpleFeature) it.next();
SimpleFeature to = writer.next();
// 写入几何(强制 2D)
Geometry g = to2D((Geometry) from.getDefaultGeometry());
to.setAttribute("the_geom", g);
// 按字段名拷贝属性(不再写死 name/value)
for (int i = 0; i < originalSchema.getAttributeCount(); i++) {
org.opengis.feature.type.AttributeDescriptor ad =
originalSchema.getDescriptor(i);
if (ad instanceof org.opengis.feature.type.GeometryDescriptor) {
continue;
}
String attrName = ad.getLocalName();
to.setAttribute(attrName, from.getAttribute(attrName));
}
writer.write();
}
}
}
} finally {
if (ds != null) {
ds.dispose();
}
}
}
System.out.println(" GeoJSON → Shp 完成");
}
2D 降维工具方法
java
private static Geometry to2D(Geometry geom) {
if (geom == null) return null;
geom = geom.copy();
geom.apply(new CoordinateSequenceFilter() {
@Override
public void filter(CoordinateSequence seq, int i) {
// 强制丢弃 Z / M
seq.setOrdinate(i, CoordinateSequence.X, seq.getX(i));
seq.setOrdinate(i, CoordinateSequence.Y, seq.getY(i));
}
@Override
public boolean isDone() {
return false;
}
@Override
public boolean isGeometryChanged() {
return true;
}
});
return geom;
}
六、Geometry 与 WKT 互转
WKT(Well-Known Text)是 OGC 标准定义的空间对象文本表示格式,常用于接口传输、日志记录和 SQL 存储。
Geometry → WKT
java
private static void geometryToWKT() {
Point point = GF.createPoint(new Coordinate(116.407, 39.904));
WKTWriter writer = new WKTWriter();
System.out.println(" Geometry → WKT:");
System.out.println(writer.write(point));
}
WKT → Geometry
java
private static void wktToGeometry() throws Exception {
WKTReader reader = new WKTReader(GF);
Geometry geom = reader.read("POINT (116.415 39.912)");
System.out.println(" WKT → Geometry:");
System.out.println(geom.getClass().getSimpleName() + " : " + geom);
}
七、Shapefile → WKT 批量导出
将 Shapefile 中的每个要素的几何对象逐个转换为 WKT 格式,便于人工审查或接口传输。
java
private static void shpToWKT(String shpPath, String wktPath) throws Exception {
Map<String, Serializable> params = new HashMap<>();
params.put("url", new File(shpPath).toURI().toURL());
params.put("charset", StandardCharsets.UTF_8.name());
DataStore store = null;
try (PrintWriter pw = new PrintWriter(
new OutputStreamWriter(
new FileOutputStream(wktPath), StandardCharsets.UTF_8))) {
store = DataStoreFinder.getDataStore(params);
String typeName = store.getTypeNames()[0];
FeatureCollection<?, ?> collection =
store.getFeatureSource(typeName).getFeatures();
WKTWriter wktWriter = new WKTWriter();
try (FeatureIterator<?> it = collection.features()) {
int idx = 0;
while (it.hasNext()) {
SimpleFeature f = (SimpleFeature) it.next();
Geometry geom = (Geometry) f.getDefaultGeometry();
pw.println("# Feature " + idx++);
pw.println(wktWriter.write(geom));
}
}
} finally {
if (store != null) store.dispose();
}
System.out.println(" Shp → WKT 完成");
}
八、完整可运行代码汇总
以下是本文所有转换功能的完整汇总,可直接复制到 IDE 运行。
java
import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFinder;
import org.geotools.data.FeatureWriter;
import org.geotools.data.Transaction;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geojson.feature.FeatureJSON;
import org.geotools.geojson.geom.GeometryJSON;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.io.WKTReader;
import org.locationtech.jts.io.WKTWriter;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class GeoToolsFormatConvertDemo {
// 全局 GeometryFactory(线程安全,推荐)
private static final GeometryFactory GF = new GeometryFactory();
public static void main(String[] args) throws Exception {
String inputShp = "your.shp"; // 替换成你自己的shp路径
shpToGeoJSON(inputShp, "output.geojson");
geojsonToShp("output.geojson", "output_from_geojson.shp");
geometryToWKT();
wktToGeometry();
shpToWKT(inputShp, "output.wkt");
}
// geojsonToShp / shpToGeoJSON / geometryToWKT /
// wktToGeometry / shpToWKT / to2D ...
// 方法实现请参见前文
}
运行结果

在QGIS中打开output.geojson和shp,结果如下

九、常见坑位与注意事项
| 坑位 | 原因 | 解决方案 |
|---|---|---|
| DataStore 不是 AutoCloseable | GeoTools 29.x 移除了该接口 | 使用 try/finally + dispose() |
| GeoJSON 属性丢失 | Schema 写死为固定字段 | 动态读取 GeoJSON 的 Schema 继承字段 |
| QGIS 提示无几何 | 未显式绑定 CRS | tb.setCRS(CRS.decode("EPSG:4326")) |
| 3D 坐标写入失败 | Shapefile 不支持 Z 坐标 | 使用 CoordinateSequenceFilter 强制降为 2D |
| GeometryTransformer 不可用 | JTS 1.19.0 已移除该类 | 使用 CoordinateSequenceFilter 替代 |
| 中文属性乱码 | 字符编码不一致 | 显式设置 UTF-8 |
十、工程实践建议
- 数据导入时动态推断几何类型,避免写死
- 始终显式绑定 CRS,确保生成的文件可被 QGIS / ArcGIS 正常识别
- GeoJSON → Shapefile 时动态继承原始属性字段,不要硬编码字段名
- 全局复用
GeometryFactory实例,避免重复创建 - 写入 Shapefile 后及时
dispose(),释放文件锁 - 使用
GeometryJSON(7)控制精度,平衡文件大小与精度
十一、小结
本文介绍了 GeoTools 中四种核心空间数据格式转换场景:Shapefile ↔ GeoJSON 、GeoJSON → Shapefile 、Geometry ↔ WKT 以及 Shapefile → WKT。通过动态 Schema 构建、显式 CRS 绑定和 2D 降维等技术,解决了 GeoJSON 转 Shapefile 过程中最常见的属性丢失和几何缺失问题。