GIS 数据转换:将 GeoJSON 转换为 Shp 数据(点)

前言

在GIS开发中,经常需要进行数据的转换处理。在之前的文章中讲了如何使用GeoTools读取Shapefile数据,并且展示了【五种】将Shapefile数据导入PostGIS空间数据库的方式,但是还缺少Shapefile数据转换来源的操作。

本篇教程在之前文章的基础上讲解如何将GeoJSON数据转换为我们熟悉的Shapefile文件格式。

开发环境

本文使用如下开发环境,以供参考。

时间:2025年

GeoTools:34-SNAPSHOT

IDE:IDEA2025.1.2

JDK:17

1. 准备 GeoJSON 文件

GeoJSON是一种用于编码各种地理数据结构的格式,采用JSON方式表示。在WebGIS开发中,被广泛应用于数据传输和共享交换。

有关GeoJSON数据的详细介绍,请参考往期文章:GeoJSON 数据简介

如下是本文选取的一些地震点GeoJSON数据结构:

json 复制代码
{
  "type": "FeatureCollection",
  "features": [
   { "type": "Feature", "properties": {"name":"洛杉矶"}, "geometry": { "type": "Point", "coordinates": [ 149.042007, -35.349998 ] } },
   { "type": "Feature", "properties": {"name":"洛杉矶" }, "geometry": { "type": "Point", "coordinates": [ -71.660004, -35.423000 ] } },
   { "type": "Feature", "properties": {"name":"洛杉矶"}, "geometry": { "type": "Point", "coordinates": [ 174.315994, -35.721001 ] } },
   { "type": "Feature", "properties": {"name":"洛杉矶"}, "geometry": { "type": "Point", "coordinates": [ -64.288002, -36.627998 ] } },
   { "type": "Feature", "properties": {"name":"洛杉矶"}, "geometry": { "type": "Point", "coordinates": [ -73.055000, -36.826000 ] } },
   { "type": "Feature", "properties": {"name":"洛杉矶"}, "geometry": { "type": "Point", "coordinates": [ 174.761002, -36.903999 ] } },
   { "type": "Feature", "properties": {"name":"洛杉矶"}, "geometry": { "type": "Point", "coordinates": [ 176.154999, -37.700001 ] } },
   { "type": "Feature", "properties": {"name":"洛杉矶"}, "geometry": { "type": "Point", "coordinates": [ 175.272995, -37.776001 ] } },
   { "type": "Feature", "properties": {"name":"洛杉矶" }, "geometry": { "type": "Point", "coordinates": [ 145.075104, -37.852959 ] } },
   { "type": "Feature", "properties": {"name":"洛杉矶" }, "geometry": { "type": "Point", "coordinates": [ 178.016006, -38.659000 ] } },
   { "type": "Feature", "properties": {"name":"洛杉矶" }, "geometry": { "type": "Point", "coordinates": [ -62.274067, -38.725273 ] } },
   { "type": "Feature", "properties": {"name":"洛杉矶"}, "geometry": { "type": "Point", "coordinates": [ -72.575996, -38.729000 ] } },
   { "type": "Feature", "properties": {"name":"洛杉矶" }, "geometry": { "type": "Point", "coordinates": [ -68.283333, -38.944908 ] } },
   { "type": "Feature", "properties": {"name":"洛杉矶"}, "geometry": { "type": "Point", "coordinates": [ 174.078003, -39.070000 ] } },
   { "type": "Feature", "properties": {"name":"洛杉矶"}, "geometry": { "type": "Point", "coordinates": [ 176.841003, -39.639999 ] } },
   { "type": "Feature", "properties": {"name":"洛杉矶"}, "geometry": { "type": "Point", "coordinates": [ 175.035004, -39.930000 ] } },
   { "type": "Feature", "properties": {"name":"洛杉矶"}, "geometry": { "type": "Point", "coordinates": [ -62.995026, -40.815025 ] } },
  ]
}

注:properties属性字段name原数据为空,本文为了测试将其修改为"洛杉矶",仅供参考。

2. 安装依赖

pom.xml文件在之前开发的基础上增加gt-geojsonjts-core两个依赖包。

xml 复制代码
<dependency>
    <groupId>org.geotools</groupId>
    <artifactId>gt-geojson</artifactId>
    <version>${geotools.version}</version>
</dependency>
<!-- JTS 几何库 -->
<dependency>
    <groupId>org.locationtech.jts</groupId>
    <artifactId>jts-core</artifactId>
    <version>1.18.2</version>
</dependency>

3. 创建 Shp 要素结构

使用showOpenFile方法打开文件选择框,然后使用createType构造要素结构,第一个参数"HeatPoint"为要素类型,第二个参数为要素属性。the_geom字段表明数据几何类型为Pointsrid表明数据坐标系为4326以及后面的字段名称和对应字段类型,此处根据GeoJSON文件结构修改。

java 复制代码
// 设置外观
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());

// 选择json文件
File file = JFileDataStoreChooser.showOpenFile(".json",null);
if(file == null ){
    System.out.println("GeoJSON 文件不正确,请检查?");
    return;
}

// 创建要素结构
final SimpleFeatureType TYPE = DataUtilities.createType(
        "HeatPoint",
        "the_geom:Point:srid=4326,"+
                "name:String"
);
SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);

除了使用DataUtilitiescreateType方法构造要素结构之外,还可以使用SimpleFeatureTypeBuilder构造器。使用SimpleFeatureTypeBuilder可以更灵活的设置要素属性,如坐标系统和字段长度。

csharp 复制代码
// 构造要素结构方法
private static SimpleFeatureType createFeatureType() {

    SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
    builder.setName("HeatPoint"); // 类型名称
    builder.setCRS(DefaultGeographicCRS.WGS84); // 坐标系统

    // 按要素结构顺序添加要素属性
    builder.add("the_geom", Point.class);
    builder.length(15).add("name", String.class); // 限制字段长度

    // 构造类型
    return builder.buildFeatureType();
}

4. 创建 Shapefile

使用ShapefileDataStoreFactory工厂方法生产Shp,在createDataStore参数中将属性"create spatial index"设置为true表示为Shp数据创建空间索引。

ini 复制代码
// 选择shp路径
File shpFile = getNewShapeFile(file);
// 创建Shapefile
ShapefileDataStoreFactory factory = new ShapefileDataStoreFactory();
Map<String, Serializable> params = new HashMap<>();
params.put("url", shpFile.toURI().toURL());
params.put("charset", "GBK");
params.put("create spatial index",Boolean.TRUE);

ShapefileDataStore dataStore = (ShapefileDataStore) factory.createNewDataStore(params);
dataStore.createSchema(featureCollection.getSchema()); // 应用定义好的Schema
dataStore.forceSchemaCRS(CRS.decode("EPSG:4326"));  // 明确CRS

对于中文字段,需要设置字符集,以防出现乱码,此处将"charset"设置为"GBK",对于中文最好将字符集设置为"GBK",设置为"UTF-8"还是有可能难以正确解析。

在创建Shp对象时,还是需要指定坐标信息,否则转换结果将缺少坐标系统。在GIS软件中打开数据可以查看元素据信息,下图是未指定坐标系时在ArcMap中的打开情况。

指定坐标系为4326后转换结果元数据信息,使用GIS软件ArcMap打开如下,可以看到此时显示正确。

5. 读取 GeoJSON 并写入 Shp

使用readFeatureCollection方法将GeoJSON文件转换为SimpleFeatureCollection对象。

ini 复制代码
// 存储要素
List<SimpleFeature> features = new ArrayList<>();

// 读取 GeoJSON
FeatureJSON featureJSON = new FeatureJSON();

SimpleFeatureCollection featureCollection;
try(FileInputStream fis = new FileInputStream(file)){
    featureCollection = (SimpleFeatureCollection) featureJSON.readFeatureCollection(fis);
}

之后根据要素结构解析Feature对象。对于geometry对象需要对其数据类型进行判断,如果为字符串需要使用WKTReader类进行读取转换,然后使用buildFeature方法创建一个新要素,将其放入新的集合列表中。

ini 复制代码
// 创建事务对象,准备提交数据
Transaction transaction = Transaction.AUTO_COMMIT;
// 获取存储特征对象
String typeName = dataStore.getTypeNames()[0];
SimpleFeatureSource featureSource = dataStore.getFeatureSource(typeName);
// 写入数据到shp文件
if(featureSource instanceof SimpleFeatureStore){
    SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
    // 准备要写入的特征集合
    ListFeatureCollection collection = new ListFeatureCollection(TYPE,features);
    // 复制要素特征
    try(FeatureIterator<SimpleFeature> it = featureCollection.features()){
        while (it.hasNext()) {
            SimpleFeature feature = it.next();
            // 处理几何字段
            Object geom =  feature.getDefaultGeometry();
            Geometry geometry = null;
            if(geom instanceof String){
                // 解析WKT字符串(如 "POINT (116 39)")
                geometry = new WKTReader().read((String) geom);
            }else if(geom instanceof Geometry){
                geometry = (Geometry) geom;
            }

            String name = (String) feature.getAttribute("name");
            featureBuilder.add(geometry);
            featureBuilder.add(name);
            // 创建目标要素
            SimpleFeature tarFeature = featureBuilder.buildFeature(null);
            collection.add(tarFeature);
        }
    }
    try{
        // 写入特征到Shapefile
        featureStore.addFeatures(collection);
        transaction.commit();
    }catch (Exception e){
        e.printStackTrace();
        transaction.rollback();
    }finally {
        transaction.close();
    }
    System.exit(0);
}
// 释放资源
dataStore.dispose();

转换完成后使用GIS软件ArcMap打开属性表结构如下。

注:使用GeoTools构造GIS数据时有些特殊,在添加要素属性时,一定要按照要素结构类型属性顺序添加,不然会读取错乱

如下要素结构中要先添加the_geom几何字段,然后再添加name字段。

java 复制代码
// 创建要素结构
final SimpleFeatureType TYPE = DataUtilities.createType(
        "HeatPoint",
        "the_geom:Point:srid=4326,"+
        "name:String"

6. Shapefile 输出位置

使用getNewShapeFile方法选择Shp输出位置,最后输出的结果就是GIS开发中常用的Shp格式了。

ini 复制代码
// 提示输出Shapefilew文件位置
private static File getNewShapeFile(File jsonFile){
    String path = jsonFile.getAbsolutePath();
    String newPath = path.substring(0,path.length()-5)+".shp";

    JFileDataStoreChooser chooser = new JFileDataStoreChooser(".shp");
    chooser.setDialogTitle("保存ShapeFile");
    chooser.setSelectedFile(new File(newPath));

    int returnVal = chooser.showSaveDialog(null);

    if(returnVal != JFileDataStoreChooser.APPROVE_OPTION){
        System.exit(0);
    }

    File newFile = chooser.getSelectedFile();
    if(newFile.equals(jsonFile)){
        System.out.println("Error:不能替换" + jsonFile);
        System.exit(0);
    }
    return newFile;
}

7. 错误信息

错误1:class java.lang.String cannot be cast to class org.locationtech.jts.geom.Geometry

未正确转换Geometry字段,需要进行类型转换。

错误2:Unable to delete the file:shpheatNew4.dbf when attempting to replace with temporary copy

文件存在多处读取,检查代码逻辑

错误3:Current fid index is null, next must be called before write()

在读取数据时 未正确调用 next() 方法就直接尝试写入数据。

图片效果

OpenLayers示例数据下载,请回复关键字:ol数据

全国信息化工程师-GIS 应用水平考试资料,请回复关键字:GIS考试

【GIS之路】 已经接入了智能助手,欢迎关注,欢迎提问。

欢迎访问我的博客网站-长谈GIShttp://shanhaitalk.com

都看到这了,不要忘记点赞、收藏 + 关注

本号不定时更新有关 GIS开发 相关内容,欢迎关注 !

相关推荐
anganing几秒前
Web 浏览器预览 Excel 及打印
前端·后端
Chad2 分钟前
Vue3 + vite 首屏优化加载速度
前端
Ace_317508877612 分钟前
义乌购平台店铺商品接口开发指南
前端
ZJ_14 分钟前
Electron自动更新详解—包教会版
前端·javascript·electron
哆啦美玲15 分钟前
Callback 🥊 Promise 🥊 Async/Await:谁才是异步之王?
前端·javascript·面试
brzhang23 分钟前
我们复盘了100个失败的AI Agent项目,总结出这3个“必踩的坑”
前端·后端·架构
万能的小裴同学30 分钟前
让没有小窗播放的视频网站的视频小窗播放
前端·javascript
小小琪_Bmob后端云1 小时前
引流之评论区截流实验
前端·后端·产品
三脚猫的喵1 小时前
微信小程序使用画布实现飘落泡泡功能
前端·javascript·微信小程序·小程序
我科绝伦(Huanhuan Zhou)1 小时前
MOP数据库备份脚本生成工具
前端·css·数据库