使用GeoTools进行GeoJSON和Shp的互相转换_geojson转shp格式工具-程序员宅基地

技术标签: GIS  poikit  geojson  Java  java  geotools  shp  

问题背景

最近在做的软件POIKit需要提供 geojson 与 shp 数据的相互转换,考虑使用 GeoTools 实现该功能,GeoTools 是基于 OGC 规范的开源 Java GIS 库,支持如 csv、geojson、shapefile、wfs 等矢量数据格式的读取和转换,但官网仅提供了关于csv 转换至 shp的教程,国内外关于二者数据转换的文章也不太丰富,经过了一番挫折之后,我找到了一种实现二者互相转换的简单方式。

使用 Maven 安装 geotools

本次使用 Maven 构建,pom.xml 中关于 geotools 的引用如下:

<repositories>
    <repository>
        <id>osgeo</id>
        <name>OSGeo Release Repository</name>
        <url>https://repo.osgeo.org/repository/release/</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
        <releases>
            <enabled>true</enabled>
        </releases>
    </repository>
</repositories>
<properties>
    <geotools.version>25.0</geotools.version>
</properties>
<dependency>
    <!-- shapefile组件 -->
    <groupId>org.geotools</groupId>
    <artifactId>gt-shapefile</artifactId>
    <version>${geotools.version}</version>
</dependency>
<dependency>
    <!-- geojson组件 -->
    <groupId>org.geotools</groupId>
    <artifactId>gt-geojson</artifactId>
    <version>${geotools.version}</version>
</dependency>
<dependency>
    <!-- geojson数据存储 -->
    <groupId>org.geotools</groupId>
    <artifactId>gt-geojsondatastore</artifactId>
    <version>${geotools.version}</version>
</dependency>

注意:国内用户一般会配置阿里云镜像,而一些镜像配置的教程往往是错误的,往往会将mirrorOf参数设置为*,这种情况下,阿里云镜像会拦截所有的 maven 请求,并向自己的镜像仓库请求数据下载,但事实上,阿里云镜像只提供对中央资源的镜像,不包含 GeoTools 的资源,因此这种情况下,maven 无法获取我们需要的 jar 包。因此,我们需要将mirrorOf参数值设置为central,同时配置 repository。

<!-- 阿里云镜像 -->
<mirror>
    <id>nexus-aliyun</id>
    <mirrorOf>central</mirrorOf>
    <name>Nexus aliyun</name>
    <url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>

以上是配置 repository 的简要原因,详细介绍可以参见Maven 中 GeoTools 的引入 - Maven 的 repository 与 mirror

GeoJSON To Shp

如果懒得看分析,可以直接跳转至GeoJSON To Shp查看完整代码。

正确的将 GeoJSON 转为 Shp 有以下要求:

  1. 空间数据和属性数据能正常显示。
  2. 若 GeoJSON 文件配置 crs 属性,需要读取 crs 以设置 shp 的坐标系,否则设置为 WGS84;
  3. 为避免乱码,shp 数据应提供 cpg 格式文件;

假设 geojson 文件路径为geojsonPath,输出 shp 文件路径为shpPath

首先根据文件路径获得 FeatureCollection

InputStream in = new FileInputStream(geojsonPath);
GeometryJSON gjson = new GeometryJSON();
FeatureJSON fjson = new FeatureJSON(gjson);
FeatureCollection<SimpleFeatureType, SimpleFeature> features = fjson.readFeatureCollection(in);

插一句:如果是 geojson 字符串呢?只需要:

Reader reader = new StringReader(geojson);
GeometryJSON gjson = new GeometryJSON();
FeatureJSON fjson = new FeatureJSON(gjson);
FeatureCollection<SimpleFeatureType, SimpleFeature> features = fjson.readFeatureCollection(reader);

geotools 规定转换为 shp 时,空间属性必须位于第一个,并强制命名为 the_geom,因此需要获取 geojson 的所有属性,并创建 the_geom 属性:

SimpleFeatureType schema = features.getSchema();
GeometryDescriptor geom = schema.getGeometryDescriptor();
// geojson文件所有属性
List<AttributeDescriptor> attributes = schema.getAttributeDescriptors();
// geojson文件空间类型
GeometryType geomType = null;
// 存储geojson非空间属性
List<AttributeDescriptor> attribs = new ArrayList<>();
for (AttributeDescriptor attrib : attributes) {
    
    AttributeType type = attrib.getType();
    if (type instanceof GeometryType) {
    
        geomType = (GeometryType) type;
    } else {
    
        attribs.add(attrib);
    }
}
if (geomType == null)
    return false;

// 使用geomType创建 the_geom type
GeometryTypeImpl gt = new GeometryTypeImpl(new NameImpl("the_geom"), geomType.getBinding(),
        geom.getCoordinateReferenceSystem() == null ? DefaultGeographicCRS.WGS84 : geom.getCoordinateReferenceSystem(), // 用户未指定则默认为wgs84
        geomType.isIdentified(), geomType.isAbstract(), geomType.getRestrictions(),
        geomType.getSuper(), geomType.getDescription());

// 根据the_geom type创建空间属性
GeometryDescriptor geomDesc = new GeometryDescriptorImpl(gt, new NameImpl("the_geom"), geom.getMinOccurs(),
        geom.getMaxOccurs(), geom.isNillable(), geom.getDefaultValue());

// the_geom 属性必须在第一个
attribs.add(0, geomDesc);

接着,根据创建的 attribs 和原 schema 的信息创建能够转换为 shapefile 的 schema,并使用 try-with-resources 方式获得输出的 features 集合

SimpleFeatureType outSchema = new SimpleFeatureTypeImpl(schema.getName(), attribs, geomDesc, schema.isAbstract(),
        schema.getRestrictions(), schema.getSuper(), schema.getDescription());
List<SimpleFeature> outFeatures = new ArrayList<>();
try (FeatureIterator<SimpleFeature> features2 = features.features()) {
    
    while (features2.hasNext()) {
    
        SimpleFeature f = features2.next();
        SimpleFeature reType = DataUtilities.reType(outSchema, f, true);

        reType.setAttribute(outSchema.getGeometryDescriptor().getName(),
                f.getAttribute(schema.getGeometryDescriptor().getName()));

        outFeatures.add(reType);
    }
}

最后,根据官网给出的csv 转换至 shp的教程,我们可以写出 features to shapefile 的方法。

该功能的完整代码如下:

/**
 * 保存features为shp格式
 *
 * @param features 要素类
 * @param TYPE     要素类型
 * @param shpPath  shp保存路径
 * @return 是否保存成功
 */
public static boolean saveFeaturesToShp(List<SimpleFeature> features, SimpleFeatureType TYPE, String shpPath) {
     
    try {
     
        ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();
        File shpFile = new File(shpPath);
        Map<String, Serializable> params = new HashMap<>();
        params.put("url", shpFile.toURI().toURL());
        params.put("create spatial index", Boolean.TRUE);

        ShapefileDataStore newDataStore =
                (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);
        newDataStore.setCharset(StandardCharsets.UTF_8);

        newDataStore.createSchema(TYPE);

        Transaction transaction = new DefaultTransaction("create");
        String typeName = newDataStore.getTypeNames()[0];
        SimpleFeatureSource featureSource = newDataStore.getFeatureSource(typeName);

        if (featureSource instanceof SimpleFeatureStore) {
     
            SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
            SimpleFeatureCollection collection = new ListFeatureCollection(TYPE, features);
            featureStore.setTransaction(transaction);
            try {
     
                featureStore.addFeatures(collection);
                FileUtil.generateCpgFile(shpPath, StandardCharsets.UTF_8);
                transaction.commit();
            } catch (Exception problem) {
     
                problem.printStackTrace();
                transaction.rollback();
            } finally {
     
                transaction.close();
            }
        } else {
     
            System.out.println(typeName + " does not support read/write access");
        }
    } catch (IOException e) {
     
        return false;
    }
    return true;
}

/**
 * GeoJson to Shp
 *
 * @param geojsonPath geojson 文件路径
 * @param shpPath     shp 文件路径
 * @return 转换是否成功
 */
public static boolean transformGeoJsonToShp(String geojsonPath, String shpPath) {
     
    try {
     
        // open geojson
        InputStream in = new FileInputStream(geojsonPath);
        GeometryJSON gjson = new GeometryJSON();
        FeatureJSON fjson = new FeatureJSON(gjson);
        FeatureCollection<SimpleFeatureType, SimpleFeature> features = fjson.readFeatureCollection(in);
        // convert schema for shapefile
        SimpleFeatureType schema = features.getSchema();
        GeometryDescriptor geom = schema.getGeometryDescriptor();
        // geojson文件属性
        List<AttributeDescriptor> attributes = schema.getAttributeDescriptors();
        // geojson文件空间类型(必须在第一个)
        GeometryType geomType = null;
        List<AttributeDescriptor> attribs = new ArrayList<>();
        for (AttributeDescriptor attrib : attributes) {
     
            AttributeType type = attrib.getType();
            if (type instanceof GeometryType) {
     
                geomType = (GeometryType) type;
            } else {
     
                attribs.add(attrib);
            }
        }
        if (geomType == null)
            return false;

        // 使用geomType创建gt
        GeometryTypeImpl gt = new GeometryTypeImpl(new NameImpl("the_geom"), geomType.getBinding(),
                geom.getCoordinateReferenceSystem() == null ? DefaultGeographicCRS.WGS84 : geom.getCoordinateReferenceSystem(), // 用户未指定则默认为wgs84
                geomType.isIdentified(), geomType.isAbstract(), geomType.getRestrictions(),
                geomType.getSuper(), geomType.getDescription());

        // 创建识别符
        GeometryDescriptor geomDesc = new GeometryDescriptorImpl(gt, new NameImpl("the_geom"), geom.getMinOccurs(),
                geom.getMaxOccurs(), geom.isNillable(), geom.getDefaultValue());

        // the_geom 属性必须在第一个
        attribs.add(0, geomDesc);

        SimpleFeatureType outSchema = new SimpleFeatureTypeImpl(schema.getName(), attribs, geomDesc, schema.isAbstract(),
                schema.getRestrictions(), schema.getSuper(), schema.getDescription());
        List<SimpleFeature> outFeatures = new ArrayList<>();
        try (FeatureIterator<SimpleFeature> features2 = features.features()) {
     
            while (features2.hasNext()) {
     
                SimpleFeature f = features2.next();
                SimpleFeature reType = DataUtilities.reType(outSchema, f, true);

                reType.setAttribute(outSchema.getGeometryDescriptor().getName(),
                        f.getAttribute(schema.getGeometryDescriptor().getName()));

                outFeatures.add(reType);
            }
        }
        return saveFeaturesToShp(outFeatures, outSchema, shpPath);
    } catch (IOException e) {
     
        e.printStackTrace();
        return false;
    }
}

Shp To GeoJSON

GeoTools 关于 Shapefile 的教程很多,支持也较好,比较简单,但需要注意将 shapefile 转换为 geojson 时应该生成 crs,转换代码如下:

public static boolean transformShpToGeoJson(String shpPath, String geojsonPath) {
     
    try {
     
        File file = new File(shpPath);
        FileDataStore myData = FileDataStoreFinder.getDataStore(file);
        // 设置解码方式
        ((ShapefileDataStore) myData).setCharset(StandardCharsets.UTF_8);
        SimpleFeatureSource source = myData.getFeatureSource();
        SimpleFeatureType schema = source.getSchema();
        Query query = new Query(schema.getTypeName());

        FeatureCollection<SimpleFeatureType, SimpleFeature> collection = source.getFeatures(query);
        FeatureJSON fjson = new FeatureJSON();
        File geojson = new File(geojsonPath);
        try (FeatureIterator<SimpleFeature> featureIterator = collection.features();
             StringWriter writer = new StringWriter();
             BufferedWriter buffer = new BufferedWriter(Files.newBufferedWriter(geojson.toPath(), StandardCharsets.UTF_8))) {
     
            writer.write("{\"type\":\"FeatureCollection\",\"crs\":");
            fjson.writeCRS(schema.getCoordinateReferenceSystem(), writer);
            writer.write(",");
            writer.write("\"features\":");
            writer.write("[");
            while (featureIterator.hasNext()) {
     
                SimpleFeature feature = featureIterator.next();
                fjson.writeFeature(feature, writer);
                if (featureIterator.hasNext())
                    writer.write(",");
            }
            writer.write("]");
            writer.write("}");
            buffer.write(writer.toString());
            return true;
        } catch (IOException e) {
     
            return false;
        }
    } catch (IOException e) {
     
        return false;
    }
}

后记

GeoJSON 和 Shapefile 的互相转换是 GISer 十分常见的问题,本人开发的软件 POIKit 便提供了该功能。目前支持 geojson 转为 shp,shp 转为 geojson/csv。

在这里插入图片描述

代码

你可以在这里找到我的空间格式转换工具类。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_40996400/article/details/116173604

智能推荐

不使用库函数,编写函数int strcmp(char *source, char *dest) 相等返回0,不等返回-1_不使用库函数,编写函数 int strcmp(char *source,char *dest) 相等-程序员宅基地

文章浏览阅读6k次,点赞2次,收藏6次。#include int strcmp(char *source, char *dest){while(*source == *dest && *source != '\0' && *dest != '\0'){ source++; dest++;}if (*source =='\0' && *dest == '\0') return 0;else return_不使用库函数,编写函数 int strcmp(char *source,char *dest) 相等返回0

voice call relevant_voice 状态 alerting ring incoming-程序员宅基地

文章浏览阅读1.7k次。每次收到底层上报的呼叫状态变化时(UNSOL_RESPONSE_CALL_STATE_CHANGED ),都会去查询变化了的具体内容给modem发的信息是RIL_REQUEST_GET_CALL_LIST.handlePollCalls是用来处理查询之后的结果,分析如下:handlePollCalls(AsyncResult ar) List polledC..._voice 状态 alerting ring incoming

原生JS实现点击按钮切换图片_原生js点击按钮替换图片-程序员宅基地

文章浏览阅读3.3k次,点赞4次,收藏14次。** 动态切换图片}li{width: 40px;height: 40px;margin-bottom:10px;background-color: pink;float: left;}#pic span{position: absolute;bottom: 10px;left: 0;}#pic p,#pic span{width: 400px;hei..._原生js点击按钮替换图片

laravel跨域的一个组件barryvdh / laravel-cors_barryvdh/laravel-cors-程序员宅基地

文章浏览阅读3.3k次。跨域的情况比如:很简单,只有三步。第一步将下面的require复制粘贴,执行(使用laravel-china的镜像吧)第二步,将文档中的这句代码复制粘贴到中间件kernel中第三部,发布(发布的意义在于服务提供者的自动加载,同时生成配置文件)配置文件默认是最大允许跨域的情况于是就拿到了数据。当然文档中说了可以将这个中间件放进中间件组中,比如放进api里总之这里和使用中..._barryvdh/laravel-cors

node.js 11 Web框架Express介绍,安装,静态页面,路由_express 静态页面-程序员宅基地

文章浏览阅读398次。本文参考原文-http://bjbsair.com/2020-03-22/tech-info/2815/前面介绍了node.js的文件模块,http server以及静态网站的创建。有了这些知识作为基础,我们可以了解一下node.js的Web框架了。从Java一路过来的朋友可能觉得Web框架还是比较重量级的,比如最初的Struts到后来的Spring,中间Apache组织也有过一些其他的模板框..._express 静态页面

基于AidLux平台的人流统计应用评测_online_tlwhs-程序员宅基地

文章浏览阅读507次。街道双向人流统计_online_tlwhs

随便推点

【Go语言】基本类型排序和 slice 排序_go slice sort-程序员宅基地

文章浏览阅读1.8w次,点赞5次,收藏24次。Go 是通过 sort 包提供排序和搜索,因为 Go 暂时不支持泛型(将来也不好说支不支持),所以,Go 的 sort 和 search 使用起来跟类型是有关的,或是需要像 c 一样写比较函数等,稍微显得也不是很方便。引言Go 的排序思路和 C 和 C++ 有些差别。 C 默认是对数组进行排序, C++ 是对一个序列进行排序, Go 则更宽泛一些,待排序的可以是任何对象, 虽然很多情况下是一个 s_go slice sort

pom文件报错:(execution:default-testCompile, phase: test-compile)" is not available_:compiletests (execution: default, phase: test- co-程序员宅基地

文章浏览阅读3.8k次。启动eclipse时,pom.xml报错:Multiple annotations found at this line: - Project configurator "com.springsource.sts.ide.maven.core.springProjectConfigurator" required by plugin execution "org.apache.mave..._:compiletests (execution: default, phase: test- compile)

阿里 神器 Arthas 的骚操作,定位线上BUG,超给力!-程序员宅基地

文章浏览阅读611次。点击上方“码农突围”,马上关注这里是码农充电第一站,回复“666”,获取一份专属大礼包真爱,请设置“星标”或点个“在看”作者:空无segmentfault.com/a/119000002...

梦幻星空html,制作梦幻星空效果图的滤镜教程-程序员宅基地

文章浏览阅读149次。一、把图像处理软件Photoshop打开,执行CTRL+N新建一个宽度和高度都为500像素的RGB图像,用黑色填充背景图层,再使用白色画笔工具在图像中点出一些白色的小圆点,这样看起来就像是满天的星星,刚好作为我们梦幻星空的背景图。二、执行菜单栏上的“视图-标尺”命令(快捷键:CTRL+R),显示出标尺以后,参考标尺上的刻度,在图像的中心分别拉出一条水平和垂直的参考线,然后创建一个新的图层,按住SH..._html梦幻星空

Data URL和图片_data url图片-程序员宅基地

文章浏览阅读1.7k次。Data URL给了我们一种很巧妙的将图片“嵌入”到HTML中的方法。跟传统的用img标记将服务器上的图片引用到页面中的方式不一样,在Data URL协议中,图片被转换成base64编码的字符串形式,并存储在URL中,冠以mime-type。本文中,我将介绍如何巧妙的使用Data URL优化网站加载速度和执行效率。Data URL基本原理为什么Data URL是个好东西_data url图片

iOS 用内置浏览器Safari 打开网页_xcode safariservices打开网址-程序员宅基地

文章浏览阅读9.5k次。iOS 开发的时候,我们需要打开某个网页,可以写一个web页面,也可直接使用浏览器打开网址那么我们怎么样使用iOS 内置的浏览器打开网址呢?如下:ios 10 之前使用[[UIApplication sharedApplication]openURLopenURL:打开的网址NSURL *URL = [NSURL URLWithString:@"http://ww..._xcode safariservices打开网址

推荐文章

热门文章

相关标签