zoukankan      html  css  js  c++  java
  • Geotools操作GeoJSON:解析FeatureCollection对象文件

    Geotools操作GeoJSON:解析FeatureCollection对象文件

    GeoJSON是基于JavaScript的对象的地理信息数据格式。

    GeoJSON格式示例:

    {
        "type":"FeatureCollection",
        "features":[
            {
                "type":"Feature",
                "properties":{
                    "area": 3865207830,
                    "text": null
                },
                "id":"polygon.1",
                "geometry":{
                    "type":"Polygon",
                    "coordinates":[
                        [
                            [
                                116.19827270507814,
                                39.78321267821705
                            ],
                            [
                                116.04446411132814,
                                39.232253141714914
                            ],
                            [
                                116.89590454101562,
                                39.3831409542565
                            ],
                            [
                                116.86981201171876,
                                39.918162846609455
                            ],
                            [
                                116.19827270507814,
                                39.78321267821705
                            ]
                        ]
                    ]
                }
            }
        ],
        "crs":{
            "type":"name",
            "properties":{
                "name":"EPSG:4326"
            }
        }
    }

    一、Maven中GeoTools的引入

    引入的geojson模块

    <dependency>
       <groupId>org.geotools</groupId>
       <artifactId>gt-geojson</artifactId>
       <version>19.0</version>
    </dependency>

    然后添加GeoTools官方的仓库

    <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>
    <repository>
       <id>osgeo-snapshot</id>
       <name>OSGeo Snapshot Repository</name>
       <url>https://repo.osgeo.org/repository/snapshot/</url>
       <snapshots><enabled>true</enabled></snapshots>
       <releases><enabled>false</enabled></releases>
    </repository>
    如果在Maven中引入GeoTools时找不到jar包,则参考:https://www.cnblogs.com/SkyTreeDelivery/p/12667972.html

    二、解析FeatureCollection对象文件

    一个FeatureCollection对象文本,包含一个Feature要素。

    1.1 geotools操作GeoJSON过程中的问题及相关源码

    public static void main(String[] a) throws Exception {
            // 坐标顺序是EAST_NORTH,即经度在前
            String json = "{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"area":3865207830, "text": null},"id":"polygon.1","geometry":{"type":"Polygon","coordinates":[[[116.19827270507814,39.78321267821705],[116.04446411132814,39.232253141714914],[116.89590454101562,39.3831409542565],[116.86981201171876,39.918162846609455],[116.19827270507814,39.78321267821705]]]}}],"crs":{"type":"name","properties":{"name":"EPSG:4326"}}}";
            // 指定GeometryJSON构造器,15位小数
            FeatureJSON fjson_15 = new FeatureJSON(new GeometryJSON(15));
            // 读取为FeatureCollection
            FeatureCollection featureCollection = fjson_15.readFeatureCollection(json);
            // 获取SimpleFeatureType
            SimpleFeatureType simpleFeatureType = (SimpleFeatureType) featureCollection.getSchema();
            // 第1个问题。坐标顺序与实际坐标顺序不符合
            System.out.println(CRS.getAxisOrder(simpleFeatureType.getCoordinateReferenceSystem()));  //输出:NORTH_EAST
     
            //第2个问题。查看空间列名称
            System.out.println(simpleFeatureType.getGeometryDescriptor().getLocalName());  //输出:geometry
     
            //第3个问题。坐标精度丢失
            //第4个问题。默认无坐标系和空值输出
            OutputStream ostream = new ByteArrayOutputStream();
            GeoJSON.write(featureCollection, ostream);
            // 输出:{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[116.1983,39.7832],[116.0445,39.2323],[116.8959,39.3831],[116.8698,39.9182],[116.1983,39.7832]]]},"properties":{"area":3865207830},"id":"polygon.1"}]}
            System.out.println(ostream);
     
            // 第5个问题。坐标变换问题,由坐标顺序引发
            SimpleFeatureIterator iterator = (SimpleFeatureIterator) featureCollection.features();
            SimpleFeature simpleFeature = iterator.next();
            Geometry geom = (Geometry) simpleFeature.getDefaultGeometry();
            iterator.close();
            System.out.println(geom.getArea());  // 输出:0.4043554020447081
            MathTransform transform_1 = CRS.findMathTransform(CRS.decode("EPSG:4326"), CRS.decode("EPSG:3857"),true);
            // 下面一行代码会报异常:Exception in thread "main" org.geotools.referencing.operation.projection.ProjectionException: Latitude 116°11.8'N is too close to a pole.
            /*Geometry geom_3857 = JTS.transform(geom, transform_1);
            System.out.println(geom_3857.getArea());*/
        }

    上述事例代码给出了将GeoJSON解析成FeatureCollection时出现的一些问题。 第1个问题是得到的FeatureCollection坐标顺序是错误的,给出的GeoJSON坐标顺序是经度(EAST)在前,geotools读取时给出了默认的坐标顺序(纬度在前),看下面的org.geotools.geojson.feature.FeatureJSON的readFeatureCollection(Object input)方法源码:

    // org.geotools.geojson.feature.FeatureJSON
    // input可以是File,Reader,InputStream等
    public FeatureCollection readFeatureCollection(Object input) throws IOException {
     
            // 新建一个DefaultFeatureCollection对象,
            DefaultFeatureCollection features = new DefaultFeatureCollection(null, null);
     
            // FeatureCollectionIterator实现了FeatureIterator接口,是一个内部类,用于控制从geojson文本中读取要素和坐标系等信息。        
            FeatureCollectionIterator it = (FeatureCollectionIterator) streamFeatureCollection(input);
            while(it.hasNext()) {
                features.add(it.next());
            }
            
            if (features.getSchema() != null
                    && features.getSchema().getCoordinateReferenceSystem() == null 
                    && it.getHandler().getCRS() != null ) {
                try {
     
                    // 只将坐标系信息写入,即只更改了坐标系
                    return new ForceCoordinateSystemFeatureResults(features, it.getHandler().getCRS());
                } catch (SchemaException e) {
                    throw (IOException) new IOException().initCause(e);
                }
            }
            return features;
        }
    ForceCoordinateSystemFeatureResults是FeatureCollection接口的一个子类,直接更改坐标系信息,原数据中的坐标信息不变。坐标系的生成是在org.geotools.geojson.feature.CRSHandler类中,相关代码如下:
    //org.geotools.geojson.feature.CRSHandler
    public boolean primitive(Object value) throws ParseException, IOException {
     
            if (state == 2) {
                try {
                    try {
                        crs = CRS.decode(value.toString());   //坐标顺序默认NORTH_EAST,与实际数据不符
                    }
                    catch(NoSuchAuthorityCodeException e) {
                        //try pending on EPSG
                        try {
                            crs = CRS.decode("EPSG:" + value.toString());
                        }
                        catch(Exception e1) {
                            //throw the original
                            throw e;
                        }
                    }
                }
                catch(Exception e) {
                    throw (IOException) new IOException("Error parsing " + value + " as crs id").initCause(e);
                }
                state = -1;
            }
            
            return true;
        }
    第1个问题可做如下修改,使坐标系正常:
    String srs = CRS.lookupIdentifier(simpleFeatureType.getCoordinateReferenceSystem(),true);  // 获取EPSG
    featureCollection = new ForceCoordinateSystemFeatureResults(featureCollection, CRS.decode(srs, true));
    第2个问题,空间列名称不是我们想要的"the_geom",而是"geometry",这使和另外一些数据源的FeatureCollection一起做操作时空间列不一致。相关代码在org.geotools.geojson.feature.FeatureHandler类中:
    //org.geotools.geojson.feature.FeatureHandler
    void addGeometryType(SimpleFeatureTypeBuilder typeBuilder, Geometry geometry) {
            // 空间列名"geometry",而不是"the_geom" 
            typeBuilder.add("geometry", geometry != null ? geometry.getClass() : Geometry.class);
            typeBuilder.setDefaultGeometry("geometry");
        }
    SimpleFeatureTypeBuilder类用于构建SimpleFeatureType,SimpleFeatureType描述了FeatureCollection对象属性、数据类型、坐标系等信息。第2个问题可做如下修改,使空间列变为"the_geom":
    // 构建新的SimpleFeatureType
        public static SimpleFeatureType retype(SimpleFeatureType oldType){
            SimpleFeatureTypeBuilder typeBuilder = new SimpleFeatureTypeBuilder();
            typeBuilder.init(oldType);
            // the_geom
            if("geometry".equals(oldType.getGeometryDescriptor().getLocalName())){
                typeBuilder.remove("geometry");
                typeBuilder.add("the_geom",oldType.getType("geometry").getBinding());
            }
            //生成新的SimpleFeatureType
            return typeBuilder.buildFeatureType();
        }
     
        // 新建一个方法,用于变换feature的type
        public static SimpleFeature retypeFeature(SimpleFeature feature,SimpleFeatureType newType) {
            SimpleFeatureBuilder builder = new SimpleFeatureBuilder(newType);
            // 遍历属性
            for (AttributeDescriptor att : newType.getAttributeDescriptors()) {
                Object value = feature.getAttribute(att.getName());
                // 空间列
                if(Geometry.class.isAssignableFrom(att.getType().getBinding())){
                    builder.set("the_geom", feature.getDefaultGeometry());
                    continue;
                }
                builder.set(att.getName(), value);
            }
            return builder.buildFeature(feature.getID());
        }
    在测试代码中加入如下,得到最终的FeatureCollection:
    SimpleFeatureType newType = retype(simpleFeatureType);
        // ListFeatureCollection是FeatureCollection的一个子类
        ListFeatureCollection listFeatureCollection = new ListFeatureCollection(newType);
        SimpleFeatureIterator iterator_3 = (SimpleFeatureIterator) featureCollection.features();
        while (iterator_3.hasNext()){
            SimpleFeature newFeature = retypeFeature(iterator_3.next(),newType);
            listFeatureCollection.add(newFeature);
        }
        iterator_3.close();

    第3(坐标精度丢失)、第4(默认无坐标系和空值输出)、第5(由坐标顺序引发坐标变换)这三个问题。我用GeoJSON的static void write(Object obj, Object output)静态方法将FeatureCollection转化成了json文本输出,先看org.geotools.geojson.GeoJSON源码:

    // 该类用于FeatureCollection、Feature和坐标系的JSON输出
    public class GeoJSON {
        static GeometryJSON gjson = new GeometryJSON();
        static FeatureJSON fjson = new FeatureJSON();  // 用的默认构造器
        public static Object read(Object input) throws IOException {
            throw new UnsupportedOperationException();
        }
        public static void write(Object obj, Object output) throws IOException {
            if (obj instanceof Geometry) {
                gjson.write((Geometry)obj, output);
            }
            else if (obj instanceof Feature || obj instanceof FeatureCollection ||  
                    obj instanceof CoordinateReferenceSystem) {
                
                if (obj instanceof SimpleFeature) {
                    fjson.writeFeature((SimpleFeature)obj, output); 
                }
                else if (obj instanceof FeatureCollection) {
                    fjson.writeFeatureCollection((FeatureCollection)obj, output);
                }
                else if (obj instanceof CoordinateReferenceSystem) {
                    fjson.writeCRS((CoordinateReferenceSystem)obj, output);
                }
                else {
                    throw new IllegalArgumentException("Unable able to encode object of type " + obj.getClass());
                }
            }
        }
    }
    该类除写Geometry外都是调用FeatureJSON的方法,在看下FeatureJSON的构造器和实例变量:
    // org.geotools.geojson.feature.FeatureJSON
        GeometryJSON gjson;  // 决定坐标保留的位数
        SimpleFeatureType featureType;
        AttributeIO attio;
        boolean encodeFeatureBounds = false;   // true表示json文本中Feature输出bbox
        boolean encodeFeatureCollectionBounds = false;    // true表示json文本中FeatureCollection输出bbox
        boolean encodeFeatureCRS = false;   // true表示json文本中Feature输出坐标系
        boolean encodeFeatureCollectionCRS = false;   // true表示json文本中FeatureCollection输出坐标系
        boolean encodeNullValues = false;   // true表示识别值为null的属性
        
        public FeatureJSON() {
            this(new GeometryJSON());   // GeometryJSON默认保留4为小数
        }
        public FeatureJSON(GeometryJSON gjson) {  //  自定义GeometryJSON,可控制小数位数
            this.gjson = gjson; 
            attio = new DefaultAttributeIO();
        }
    GeometryJSON的相关代码就不列出来了。解决第3(坐标精度丢失)、第4(默认无坐标系和空值输出)问题我们只需做一些设置。如果想统一用GeoJSON.write()方法写json文本,可以重写该类,设置精度,代码如下:
    // 重写后的GeoJSON
    public class GeoJSON {
        static GeometryJSON gjson = new GeometryJSON(15);  // 15位小数
        static FeatureJSON fjson = new FeatureJSON(gjson);  // 指定GeometryJSON
        public static Object read(Object input) throws IOException {
            throw new UnsupportedOperationException();
        }
        public static void write(Object obj, Object output) throws IOException {
            if (obj instanceof Geometry) {
                gjson.write((Geometry)obj, output);
            }
            else if (obj instanceof Feature || obj instanceof FeatureCollection ||  
                    obj instanceof CoordinateReferenceSystem) {
     
                // 值为null的属性也识别
                fjson.setEncodeNullValues(true);
                // 输出坐标系文本
                fjson.setEncodeFeatureCollectionCRS(true);            
                if (obj instanceof SimpleFeature) {
                    fjson.writeFeature((SimpleFeature)obj, output); 
                }
                else if (obj instanceof FeatureCollection) {
                    fjson.writeFeatureCollection((FeatureCollection)obj, output);
                }
                else if (obj instanceof CoordinateReferenceSystem) {
                    fjson.writeCRS((CoordinateReferenceSystem)obj, output);
                }
                else {
                    throw new IllegalArgumentException("Unable able to encode object of type " + obj.getClass());
                }
            }
        }
    }
    如果就想用FeatureJSON操作输出,可以在测试代码中添加如下代码解决:

    // fjson_15已经保留15位
    fjson_15.setEncodeFeatureCollectionCRS(true);
     fjson_15.setEncodeNullValues(true);
     fjson_15.writeFeatureCollection(featureCollection,System.out);  // 控制台输出和原始geojson一致

    针对第5(由坐标顺序引发坐标变换)个问题,“org.geotools.referencing.operation.projection.ProjectionException: Latitude 116°11.8’N is too close to a pole”异常其实是由坐标顺序不正确导致,经纬度顺序调换后识别的坐标超出了范围,不是当前坐标系能表示的值了。这是一个隐藏问题,在处理另一些原数据或变换不同的坐标系时,不一定会产生这个异常,那用不合理的坐标顺序得到的结果是不正确的。调整代码如下即可以解决:

    String srs = CRS.lookupIdentifier(simpleFeatureType.getCoordinateReferenceSystem(),true);
    // CRS.decode()方法可以设置经纬度顺序
    MathTransform transform_2 = CRS.findMathTransform(CRS.decode(srs,true), CRS.decode("EPSG:3857",true),true);
    Geometry geom_3857 = JTS.transform(geom, transform_2);
    System.out.println(geom_3857.getArea());  // 输出:6.501222710260582E9

    测试代码里输出了几何对象geom的面积,但这个面积很粗糙,只做测试用。给出的GeoJSON文本中的"area"值也只做参考,不是最精确的面积。大家都知道,EPSG:3857以EPSG:4326地理坐标系和投影方式为伪墨卡托的平面坐标系,给出的面积偏差较大。

    1.2 方法二:读取本地txt文件进行解析

    FeatJson.class

    import com.geomesa.spark.SparkJTS.Operation;
    import com.vividsolutions.jts.geom.Coordinate;
    import com.vividsolutions.jts.geom.Geometry;
    import com.vividsolutions.jts.geom.GeometryFactory;
    import com.vividsolutions.jts.operation.distance.DistanceOp;
    import org.geotools.data.simple.SimpleFeatureIterator;
    import org.geotools.feature.FeatureCollection;
    import org.geotools.geojson.GeoJSON;
    import org.geotools.geojson.feature.FeatureJSON;
    import org.geotools.geojson.geom.GeometryJSON;
    import org.geotools.geometry.jts.JTSFactoryFinder;
    import org.opengis.feature.simple.SimpleFeature;
    import org.opengis.feature.simple.SimpleFeatureType;
    import java.io.*;
    
    public class FeatJson {
        static GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory(null);
        public static void main(String[] args) throws IOException {
            //读取本地文件
            FileReader reader = new FileReader("D:/GitProjects/GeoMesa/GeoMesaSpark/src/main/resources/gsmc.txt");
            BufferedReader bufferReader = new BufferedReader(reader);
            String dict = bufferReader.readLine();
            //按行读取文件
            //构造FeatureJSON对象,GeometryJSON保留15位小数
            FeatureJSON featureJSON = new FeatureJSON(new GeometryJSON(15));
            FeatureCollection featureCollection = featureJSON.readFeatureCollection(dict);
            SimpleFeatureType simpleFeatureType = (SimpleFeatureType) featureCollection.getSchema();
            System.out.println(simpleFeatureType.getGeometryDescriptor().getLocalName());
            OutputStream ostream = new ByteArrayOutputStream();
            GeoJSON.write(featureCollection, ostream);
            System.out.println(ostream);
    
            SimpleFeatureIterator iterator = (SimpleFeatureIterator) featureCollection.features();
            SimpleFeature simpleFeature = iterator.next();
            Geometry geom = (Geometry) simpleFeature.getDefaultGeometry();
            iterator.close();
            System.out.println(geom.getLength());
            System.out.println(geom.getCoordinate());
            System.out.println(geom.getBoundary());
            System.out.println(geom.getGeometryType());
    
            //新建一个经纬度坐标对象
            Coordinate coordinate1 = new Coordinate(1.357846020181606E7, 4505819.87283728);
            Coordinate[] coordinates2 = geom.getCoordinates();
            Operation op = new Operation();
            //求点到线的距离
            System.out.println("距离:"+op.distanceGeo(geometryFactory.createPoint(coordinate1),geometryFactory.createLineString(coordinates2)));
            //求点到线的最近一个点
            System.out.println(DistanceOp.nearestPoints(geometryFactory.createPoint(coordinate1),geometryFactory.createLineString(coordinates2)));
    
            bufferReader.close();
            reader.close();
        }
    }

    三、Java Code Examples

    Java Code Examples for org.geotools.data.simple.simplefeaturecollection.features()

    一些Java的示例代码https://www.programcreek.com/java-api-examples/?class=org.geotools.data.simple.simplefeaturecollection&method=features

    四、API

    SimpleFeatureCollection

    FeatureJSONhttp://docs.geotools.org/stable/javadocs/org/geotools/geojson/feature/FeatureJSON.html

  • 相关阅读:
    Zend Framework 2.1.5 中根据服务器的环境配置调用数据库等的不同配置
    在基于 Eclipse 的 IDE 中安装和使用 Emmet(ZenCoding)
    【翻译】Emmet(Zen Coding)官方文档 之六 自定义 Emmet
    【翻译】Emmet(Zen Coding)官方文档 之二 缩写
    【翻译】Emmet(Zen Coding)官方文档 之七 一览表
    【翻译】Emmet(Zen Coding)官方文档 之三 CSS 缩写
    【翻译】Emmet(Zen Coding)官方文档 之四 动作
    【翻译】Emmet(Zen Coding)官方文档 之一 web 编程的必备工具
    Zend Framework 2 时区设置警告问题的解决
    【翻译】Emmet (Zen Coding) 元素类型
  • 原文地址:https://www.cnblogs.com/haolb123/p/14859296.html
Copyright © 2011-2022 走看看