zoukankan      html  css  js  c++  java
  • 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"
            }
        }
    }
    

    一、解析FeatureCollection对象文件

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

    1.1 geotools操作GeoJSON过程中的问题及相关源码(转载自:Shanks7529)

    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地理坐标系和投影方式为伪墨卡托的平面坐标系,给出的面积偏差较大。

    原创作者:Shanks7529
    文章链接:geotools操作GeoJSON过程中的问题及相关源码

    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的示例代码

    三、API

    SimpleFeatureCollection

    FeatureJSON

  • 相关阅读:
    MyCat 概念与配置
    使用zookeeper管理远程MyCat配置文件、MyCat监控、MyCat数据迁移(扩容)
    数据库分库分表中间件MyCat的安装和mycat配置详解
    搭建dubbo+zookeeper+dubboadmin分布式服务框架(windows平台下)
    数据库分库分表的类型和特点
    数据库为什么要分库分表
    正确安装MySQL5.7 解压缩版(手动配置)方法
    SQL 查询相同记录下日期最大的一条数据
    HashMap在Jdk1.7和1.8中的实现
    Spring Bean的生命周期
  • 原文地址:https://www.cnblogs.com/aixing/p/13327373.html
Copyright © 2011-2022 走看看