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依赖配置如下dependenciesdependencygroupIdorg.geotools/groupIdartifactIdgt-shapefile/artifactIdversion29.3/version/dependencydependencygroupIdorg.geotools/groupIdartifactIdgt-main/artifactIdversion29.3/version/dependencydependencygroupIdorg.geotools/groupIdartifactIdgt-epsg-hsql/artifactIdversion29.3/version/dependencydependencygroupIdorg.locationtech.jts/groupIdartifactIdjts-core/artifactIdversion1.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 最基本的操作流程如下通过 DataStoreFinder 创建 DataStore 实例获取图层名称typeName通过 FeatureSource 获取元素集合使用 FeatureIterator 遍历元素读取代码示例StringinputShpyour_path.shp;MapString,SerializableparamsnewHashMap();params.put(url,newFile(inputShp).toURI().toURL());params.put(charset,StandardCharsets.UTF_8.name());DataStoreinputStoreDataStoreFinder.getDataStore(params);if(inputStorenull){thrownewRuntimeException(无法识别的 Shapefile 格式);}StringtypeNameinputStore.getTypeNames()[0];FeatureSourceSimpleFeatureType,SimpleFeaturesourceinputStore.getFeatureSource(typeName);FeatureCollectionSimpleFeatureType,SimpleFeaturecollectionsource.getFeatures();System.out.println(要素数量: collection.size());System.out.println(属性结构: source.getSchema());遍历元素try(FeatureIteratorSimpleFeatureitcollection.features()){intcount0;while(it.hasNext()count3){SimpleFeaturefeatureit.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 对应 ShapefileDataStorecharset 参数解决中文属性乱码问题FeatureIterator 必须在 try-with-resources 中使用避免内存泄漏dispose() 释放资源生产环境一定不能遗漏五、创建新 Shapefile写入 Shapefile 比读取复杂一些因为需要先定义属性结构Schema再逐个写入元素。GeoTools 29.x 写入步骤创建 ShapefileDataStore 并设置编码使用 SimpleFeatureTypeBuilder 定义 Schema调用 createSchema() 创建层使用 FeatureWriter 写入元素完整写入代码privatestaticvoidcreateShapefile()throwsException{FilefilenewFile(output.shp);MapString,SerializableparamsnewHashMap();params.put(ShapefileDataStoreFactory.URLP.key,file.toURI().toURL());params.put(ShapefileDataStoreFactory.CREATE_SPATIAL_INDEX.key,Boolean.TRUE);ShapefileDataStoreFactoryfactorynewShapefileDataStoreFactory();ShapefileDataStoreds(ShapefileDataStore)factory.createNewDataStore(params);ds.setCharset(StandardCharsets.UTF_8);// 1. 构建 SchemaSimpleFeatureTypeBuildertypeBuildernewSimpleFeatureTypeBuilder();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);SimpleFeatureTypeschematypeBuilder.buildFeatureType();ds.createSchema(schema);// 2. 写入要素GeometryFactorygfnewGeometryFactory();String[]names{点A,点B,点C};double[][]coords{{116.407,39.904},{116.415,39.912},{116.423,39.908}};try(FeatureWriterSimpleFeatureType,SimpleFeaturewriterds.getFeatureWriterAppend(ds.getTypeNames()[0],Transaction.AUTO_COMMIT)){for(inti0;inames.length;i){SimpleFeaturefeaturewriter.next();feature.setDefaultGeometry(gf.createPoint(newCoordinate(coords[i][0],coords[i][1])));feature.setAttribute(name,names[i]);feature.setAttribute(value,100.0i*10);writer.write();}}ds.dispose();}GeoTools 29.x 写入核心三步曲这是 GeoTools 29.x 中创建 Shapefile 的写法SimpleFeaturefeaturewriter.next();// 获取占位 FeatureFID 在此生成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{StringinputShpD:/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// MapString,SerializableparamsnewHashMap();params.put(url,newFile(inputShp).toURI().toURL());params.put(charset,StandardCharsets.UTF_8.name());DataStoreinputStoreDataStoreFinder.getDataStore(params);if(inputStorenull){thrownewRuntimeException(无法识别的 Shapefile 格式);}StringtypeNameinputStore.getTypeNames()[0];FeatureSourceSimpleFeatureType,SimpleFeaturesourceinputStore.getFeatureSource(typeName);FeatureCollectionSimpleFeatureType,SimpleFeaturecollectionsource.getFeatures();System.out.println( Shapefile 读取结果 );System.out.println(要素数量: collection.size());System.out.println(属性结构: source.getSchema());try(FeatureIteratorSimpleFeatureitcollection.features()){intcount0;while(it.hasNext()count3){SimpleFeaturefeatureit.next();System.out.println(\nFeature #(count1));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{FilefilenewFile(output_fixed.shp);MapString,SerializableparamsnewHashMap();params.put(ShapefileDataStoreFactory.URLP.key,file.toURI().toURL());params.put(ShapefileDataStoreFactory.CREATE_SPATIAL_INDEX.key,Boolean.TRUE);ShapefileDataStoreFactoryfactorynewShapefileDataStoreFactory();ShapefileDataStoreds(ShapefileDataStore)factory.createNewDataStore(params);ds.setCharset(StandardCharsets.UTF_8);// 构建 SchemaSimpleFeatureTypeBuildertypeBuildernewSimpleFeatureTypeBuilder();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);SimpleFeatureTypeschematypeBuilder.buildFeatureType();ds.createSchema(schema);// 写入要素GeometryFactorygfnewGeometryFactory();String[]names{点A,点B,点C};double[][]coords{{116.407,39.904},{116.415,39.912},{116.423,39.908}};try(FeatureWriterSimpleFeatureType,SimpleFeaturewriterds.getFeatureWriterAppend(ds.getTypeNames()[0],Transaction.AUTO_COMMIT)){for(inti0;inames.length;i){// next() 返回当前可编辑 FeatureFID 在此生成SimpleFeaturefeaturewriter.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.0i*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_geomShapefile 规范中几何字段名称建议使用 the_geom否则可能导致 QGIS / ArcGIS 无法正确识别几何字段。4. FID 索引异常提示 Current fid index is null 时说明 writer.next() 没有被调用就直接 write() 了。记住固定模式SimpleFeaturefeaturewriter.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() 写法中文编码解决方案常见异常与调试技巧

相关新闻