geotools操作GeoJSON过程中的问题及相关源码(上)

        GeoJSON是基于JavaScript的对象的地理信息数据格式,RFC 7946上对GeoJSON格式详细说明。一个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对象文本,包含一个Feature要素。首先新建一个测试类,以下所有操作都在测试类的main方法中进行,先看如下代码指出的问题:

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();

       listFeatureCollection即是最终的正确的对象。本回就介绍上述两个问题,下回介绍剩下的。

转载自:https://blog.csdn.net/aliasone/article/details/80186301

You may also like...