当前位置: 首页 > news >正文

GeoTools 入门实战(一):Shapefile 读取与写入全解析

目录

  • 一、前言
  • 二、环境准备
  • 三、GeoTools 核心概念
  • 四、读取 Shapefile
  • 五、创建新 Shapefile
  • 六、完整可运行代码
  • 七、常见坑位与注意事项
  • 八、工程实践建议
  • 九、小结

一、前言

GeoTools 是 Java 生态中最重要的开源 GIS 库,它基于 JTS 提供了完整的空间数据读写能力。其中 Shapefile 作为 GIS 行业最通用的数据格式,是每个 GIS 开发者都会接触的第一个格式。

本文将从工程视角出发,系统讲解 GeoTools 读取和写入 Shapefile 的全过程,包含 DataStore 体系、Feature 模型、中文编码处理以及 GeoTools 29.x 版本的 API。

二、环境准备

Maven 依赖

本文使用 GeoTools 29.3 + JTS 1.19.0,依赖配置如下:

<dependencies><dependency><groupId>org.geotools</groupId><artifactId>gt-shapefile</artifactId><version>29.3</version></dependency><dependency><groupId>org.geotools</groupId><artifactId>gt-main</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></dependencies>

三、GeoTools 核心概念

在开始写代码之前,先理解 GeoTools 的核心概念,这对后续使用其他数据格式(GeoJSON、PostGIS 等)也非常有用。

DataStore —— 数据存储抽象

GeoTools 不直接操作文件,而是通过 DataStore 进行统一管理。它是所有空间数据源的抽象层,支持文件、数据库、Web 服务等多种形式。

FeatureSource / FeatureCollection / SimpleFeature

这三个接口是 GeoTools 的核心数据模型:

接口职责
FeatureSource数据源的只读视图,用于查询和获取元数据
FeatureCollection元素集合,支持遍历和过滤
SimpleFeature单个元素,包含几何字段和属性字段

SimpleFeatureType —— 属性结构定义

SimpleFeatureType 定义了一个图层的完整结构,包含:

  • 几何字段名称和类型(如 the_geom: Point)
  • 属性字段名称和类型(如 name: String)
  • 坐标系信息(CRS)

四、读取 Shapefile

读取 Shapefile 是 GeoTools 最基本的操作,流程如下:

  1. 通过 DataStoreFinder 创建 DataStore 实例
  2. 获取图层名称(typeName)
  3. 通过 FeatureSource 获取元素集合
  4. 使用 FeatureIterator 遍历元素

读取代码示例

StringinputShp="your_path.shp";Map<String,Serializable>params=newHashMap<>();params.put("url",newFile(inputShp).toURI().toURL());params.put("charset",StandardCharsets.UTF_8.name());DataStoreinputStore=DataStoreFinder.getDataStore(params);if(inputStore==null){thrownewRuntimeException("无法识别的 Shapefile 格式");}StringtypeName=inputStore.getTypeNames()[0];FeatureSource<SimpleFeatureType,SimpleFeature>source=inputStore.getFeatureSource(typeName);FeatureCollection<SimpleFeatureType,SimpleFeature>collection=source.getFeatures();System.out.println("要素数量: "+collection.size());System.out.println("属性结构: "+source.getSchema());

遍历元素

try(FeatureIterator<SimpleFeature>it=collection.features()){intcount=0;while(it.hasNext()&&count<3){SimpleFeaturefeature=it.next();System.out.println("ID: "+feature.getID());System.out.println("Geometry: "+feature.getDefaultGeometry());for(Objectattr:feature.getAttributes()){System.out.println("Attribute: "+attr);}count++;}}// 关闭 DataStoreinputStore.dispose();

关键点说明

  • DataStoreFinder 会自动识别数据源类型,Shapefile 对应 ShapefileDataStore
  • charset 参数解决中文属性乱码问题
  • FeatureIterator 必须在 try-with-resources 中使用,避免内存泄漏
  • dispose() 释放资源,生产环境一定不能遗漏

五、创建新 Shapefile

写入 Shapefile 比读取复杂一些,因为需要先定义属性结构(Schema),再逐个写入元素。

GeoTools 29.x 写入步骤

  1. 创建 ShapefileDataStore 并设置编码
  2. 使用 SimpleFeatureTypeBuilder 定义 Schema
  3. 调用 createSchema() 创建层
  4. 使用 FeatureWriter 写入元素

完整写入代码

privatestaticvoidcreateShapefile()throwsException{Filefile=newFile("output.shp");Map<String,Serializable>params=newHashMap<>();params.put(ShapefileDataStoreFactory.URLP.key,file.toURI().toURL());params.put(ShapefileDataStoreFactory.CREATE_SPATIAL_INDEX.key,Boolean.TRUE);ShapefileDataStoreFactoryfactory=newShapefileDataStoreFactory();ShapefileDataStoreds=(ShapefileDataStore)factory.createNewDataStore(params);ds.setCharset(StandardCharsets.UTF_8);// 1. 构建 SchemaSimpleFeatureTypeBuildertypeBuilder=newSimpleFeatureTypeBuilder();typeBuilder.setName("test_layer");typeBuilder.setCRS(CRS.decode("EPSG:4326"));typeBuilder.add("the_geom",Point.class);typeBuilder.add("name",String.class);typeBuilder.add("value",Double.class);SimpleFeatureTypeschema=typeBuilder.buildFeatureType();ds.createSchema(schema);// 2. 写入要素GeometryFactorygf=newGeometryFactory();String[]names={"点A","点B","点C"};double[][]coords={{116.407,39.904},{116.415,39.912},{116.423,39.908}};try(FeatureWriter<SimpleFeatureType,SimpleFeature>writer=ds.getFeatureWriterAppend(ds.getTypeNames()[0],Transaction.AUTO_COMMIT)){for(inti=0;i<names.length;i++){SimpleFeaturefeature=writer.next();feature.setDefaultGeometry(gf.createPoint(newCoordinate(coords[i][0],coords[i][1])));feature.setAttribute("name",names[i]);feature.setAttribute("value",100.0+i*10);writer.write();}}ds.dispose();}

GeoTools 29.x 写入核心三步曲

这是 GeoTools 29.x 中创建 Shapefile 的写法:

SimpleFeaturefeature=writer.next();// 获取占位 Feature(FID 在此生成)feature.setAttribute(...);// 填充字段writer.write();// 写入

这三步是严格顺序的,任何变式都会导致异常。

六、完整可运行代码

下面是读取 + 写入的完整代码,可直接复制到 IDE 中运行:

importorg.geotools.data.*;importorg.geotools.data.shapefile.ShapefileDataStore;importorg.geotools.data.shapefile.ShapefileDataStoreFactory;importorg.geotools.feature.FeatureCollection;importorg.geotools.feature.FeatureIterator;importorg.geotools.feature.simple.SimpleFeatureTypeBuilder;importorg.locationtech.jts.geom.Point;importorg.locationtech.jts.geom.GeometryFactory;importorg.opengis.feature.simple.SimpleFeature;importorg.opengis.feature.simple.SimpleFeatureType;importjava.io.File;importjava.io.IOException;importjava.io.Serializable;importjava.nio.charset.StandardCharsets;importjava.util.HashMap;importjava.util.Map;publicclassShapefileReadWriteDemo{publicstaticvoidmain(String[]args)throwsException{StringinputShp="D:/testdata/testshp/ne_110m_admin_0_tiny_countries/ne_110m_admin_0_tiny_countries.shp";// 替换为你的 shp 路径readShapefile(inputShp);// ===============================// 第二部分:创建新的 Shapefile// ===============================createShapefile();System.out.println("\n新 Shapefile 已生成:output.shp");}privatestaticvoidreadShapefile(StringinputShp)throwsIOException{// ===============================// 第一部分:读取 Shapefile// ===============================Map<String,Serializable>params=newHashMap<>();params.put("url",newFile(inputShp).toURI().toURL());params.put("charset",StandardCharsets.UTF_8.name());DataStoreinputStore=DataStoreFinder.getDataStore(params);if(inputStore==null){thrownewRuntimeException("无法识别的 Shapefile 格式");}StringtypeName=inputStore.getTypeNames()[0];FeatureSource<SimpleFeatureType,SimpleFeature>source=inputStore.getFeatureSource(typeName);FeatureCollection<SimpleFeatureType,SimpleFeature>collection=source.getFeatures();System.out.println("==== Shapefile 读取结果 ====");System.out.println("要素数量: "+collection.size());System.out.println("属性结构: "+source.getSchema());try(FeatureIterator<SimpleFeature>it=collection.features()){intcount=0;while(it.hasNext()&&count<3){SimpleFeaturefeature=it.next();System.out.println("\nFeature #"+(count+1));System.out.println(" ID: "+feature.getID());System.out.println(" Geometry: "+feature.getDefaultGeometry());for(Objectattr:feature.getAttributes()){System.out.println(" Attribute: "+attr);}count++;}}inputStore.dispose();}privatestaticvoidcreateShapefile()throwsException{Filefile=newFile("output_fixed.shp");Map<String,Serializable>params=newHashMap<>();params.put(ShapefileDataStoreFactory.URLP.key,file.toURI().toURL());params.put(ShapefileDataStoreFactory.CREATE_SPATIAL_INDEX.key,Boolean.TRUE);ShapefileDataStoreFactoryfactory=newShapefileDataStoreFactory();ShapefileDataStoreds=(ShapefileDataStore)factory.createNewDataStore(params);ds.setCharset(StandardCharsets.UTF_8);// 构建 SchemaSimpleFeatureTypeBuildertypeBuilder=newSimpleFeatureTypeBuilder();typeBuilder.setName("test_layer");typeBuilder.setCRS(org.geotools.referencing.CRS.decode("EPSG:4326"));typeBuilder.add("the_geom",Point.class);// Shapefile 几何字段推荐 the_geomtypeBuilder.add("name",String.class);typeBuilder.add("value",Double.class);SimpleFeatureTypeschema=typeBuilder.buildFeatureType();ds.createSchema(schema);// 写入要素GeometryFactorygf=newGeometryFactory();String[]names={"点A","点B","点C"};double[][]coords={{116.407,39.904},{116.415,39.912},{116.423,39.908}};try(FeatureWriter<SimpleFeatureType,SimpleFeature>writer=ds.getFeatureWriterAppend(ds.getTypeNames()[0],Transaction.AUTO_COMMIT)){for(inti=0;i<names.length;i++){// next() 返回当前可编辑 Feature(FID 在此生成)SimpleFeaturefeature=writer.next();feature.setDefaultGeometry(gf.createPoint(neworg.locationtech.jts.geom.Coordinate(coords[i][0],coords[i][1])));feature.setAttribute("name",names[i]);feature.setAttribute("value",100.0+i*10);writer.write();}}ds.dispose();}}

创建出来的shapefile在QGIS中打开,效果如下:

七、常见坑位与注意事项

1. 中文乱码问题

Shapefile 的 DBF 文件默认使用 ISO-8859-1 编码,中文属性会乱码。解决方法:

  • 读取时:params.put("charset", StandardCharsets.UTF_8.name())
  • 写入时:ds.setCharset(StandardCharsets.UTF_8)
  • 建议同时生成 .cpg 文件声明编码

2. GeoTools 28.x → 29.x 破坏性变更

FeatureWriter.write() 方法在 29.x 中变为无参,这是最常见的升级坑:

版本write 方法写法
28.xwrite(feature)writer.write(builder.buildFeature(null))
29.xwrite()feature = writer.next(); writer.write();

3. 几何字段名称必须为 the_geom

Shapefile 规范中,几何字段名称建议使用 the_geom,否则可能导致 QGIS / ArcGIS 无法正确识别几何字段。

4. FID 索引异常

提示 Current fid index is null 时,说明 writer.next() 没有被调用就直接 write() 了。记住固定模式:

SimpleFeaturefeature=writer.next();// 不能省略feature.setAttribute(...);writer.write();

5. 资源泄露风险

DataStore 和 FeatureIterator 都必须显式关闭:

  • DataStore.dispose() 释放文件锁和连接池
  • FeatureIterator 用 try-with-resources 自动关闭

长期不关闭会导致文件被锁定,无法删除或覆盖。

八、工程实践建议

  • 读取前先判断 DataStore 是否为 null,避免空指针异常
  • 生产环境建议将 Shapefile 路径配置化,不要硬编码
  • 批量写入时使用 Transaction 控制事务,出错时 rollback
  • 建议将属性名称和类型抽取为常量管理
  • 大文件处理时注意内存,考虑分块读取
  • 写入完成后立即 dispose,释放文件锁

九、小结

本文介绍了 GeoTools 读取和写入 Shapefile 的完整流程,覆盖了以下核心知识点:

  • DataStore 体系与 ShapefileDataStore 的使用
  • FeatureSource / FeatureCollection / SimpleFeature 的关系
  • Schema 定义与属性结构构建
  • GeoTools 29.x 的 FeatureWriter 无参 write() 写法
  • 中文编码解决方案
  • 常见异常与调试技巧
http://www.jsqmd.com/news/1131580/

相关文章:

  • Windows上的安卓应用安装神器:APK安装器完整指南
  • CA-MKD 置信度感知多教师蒸馏:PyTorch 复现与 CIFAR-100 3教师实验对比
  • 朴素贝叶斯分类器 Python 实现:从零手写 2 个核心函数与拉普拉斯平滑
  • Web 安全防御:从 4 个维度构建 XSS 防护体系(附代码示例)
  • 生产级GEO最小系统实现:20+项目验证单文件开箱即用完整代码、性能优化与踩坑汇总
  • M1 S50卡控制字节实战:4种常见权限组合(FF 07 80 69等)的生成与解析
  • AI4S 科研闭环实战:3步构建“假设-设计-验证”自主实验流水线(附代码)
  • 机器学习数据集划分实战:6:2:2 黄金比例与 10 折交叉验证的 5 个关键抉择
  • 信息熵与信息增益 Python 3.12 实战:从公式到代码,5步实现决策树特征选择
  • JDBC 连接串安全配置指南:SSL/TLS 与 3 类敏感参数避坑实践
  • 深入浅出 DeepSeek 多轮对话系统设计:手把手打造智能聊天助手
  • DQN 2015 Nature 论文复现:Atari Pong 游戏 84x84 像素输入实战(附 PyTorch 代码)
  • 如何一键获取八大网盘真实下载地址:开源下载助手的终极解决方案
  • 用友U8 API 单据生成实战:销售发货单等4类单据JSON参数映射与DOM构建
  • 如何用5个核心功能彻底解放你的明日方舟游戏时间?
  • sklearn 数据集划分进阶:2次调用 train_test_split 实现训练/验证/测试集 7:2:1 拆分
  • 把委托说透(2):深入理解委托
  • F3闪存检测工具:3分钟快速识别扩容盘的终极指南
  • OpenCV图像处理实战:通道拆分、灰度化与反色技术
  • Planetoid 数据集 PyG 2.6.0 实战:3 种数据分割模式对比与节点分类任务
  • 先进工艺节点(<110nm)互连线可靠性:EM 与 IR Drop 的 3 大协同优化策略
  • TD3 算法 PyTorch 实战:MuJoCo 环境 3 大核心改进点代码实现与调优
  • HiveWE:5个关键功能让魔兽争霸III地图创作变得轻松高效
  • TC78H660FTG与PIC18F87J50的直流电机驱动优化方案
  • 建行二代网银盾证书更新:E路护航组件下载与U盾密码输入3次全流程
  • CMS漏洞自动化检测脚本开发:Python批量验证4类漏洞(附PoC)
  • Claude Code 实战:AI 结对编程如何真正提效,从简历表达讲到项目复盘
  • OpenCV 4.8 车牌识别系统优化:3步提升蓝牌定位准确率至95%
  • 对抗学习 FGSM/PGD 攻击实战:PyTorch 实现 3 种主流图像对抗样本生成
  • 二值神经网络 PyTorch 1.13 实战:CIFAR-10 上实现 90%+ 精度的 3 步调优法