• Geotools Error looking up crs identifier 错误原因分析、解决以及自定义坐标系


    Geotools Error looking up crs identifier 错误原因分析、解决以及自定义坐标系

    简介

    为了方便查看 shp(shapefile) 文件,不用每次都打开 Arcgis 或者 QGIS 软件,搞了一个在线解析 shp,并且转为 Geojson 格式数据的小工具。

    使用了一段时间,都还比较稳定,不过在一次上传 CGCS2000_3_Degree_GK_Zone_39 投影数据的时候,后台解析错误,经过长时间调试(资料比较少,多数都是介绍坐标转换的),终于解决问题,记录一下。

    本文主要介绍:Geotools Error looking up crs identifier 错误原因、geotools定义坐标系、解决问题方法


    Geotools Error looking up crs identifier 错误原因

    首先,错误提示为 Geotools Error looking up crs identifier ,提示很明确:即找不到 crs 定义,也就是不认识数据的投影坐标系

    看到错误提示,笔者认为设置坐标系就可以解决问题,于是搜索 Geotools 定义坐标系的方法:最后找到 CRS.decode(“EPSG:4326”);

    在 Geotools 中是通过 EPSG 的码来创建投影坐标系对象的,因此需要先获取 EPSG 码。

    经过 Coordinate Systems Worldwide 查询可以获得 CGCS2000_3_Degree_GK_Zone_39 的 EPSG 码为 EPSG: 4527

    当时初步的想法是,定义好投影坐标系之后,将数据转为 Geotools 支持的 EPSG:4326,应该就可以了。

    贴上坐标转换工具代码:

    public static SimpleFeatureCollection transfer(
                SimpleFeatureCollection featureCollection,
                CoordinateReferenceSystem source,
                CoordinateReferenceSystem target) throws FactoryException, TransformException, IOException {
    
            boolean lenient = true; // allow for some error due to different datums
            //定义坐标转换
            MathTransform transform = CRS.findMathTransform(source, target, lenient);
    
            SimpleFeatureIterator features = featureCollection.features();
    
            DefaultFeatureCollection simpleFeatures = new DefaultFeatureCollection();
    
            try {
                while (features.hasNext()) {
                    SimpleFeature feature = features.next();
                    //坐标系转换
                    Geometry geometry = (Geometry) feature.getDefaultGeometry();
                    Geometry geometry2 = JTS.transform(geometry, transform);
                    feature.setDefaultGeometry(geometry2);
                    simpleFeatures.add(feature);
                }
            } catch (NoSuchElementException e) {
                e.printStackTrace();
            } catch (MismatchedDimensionException e) {
                e.printStackTrace();
            } catch (TransformException e) {
                e.printStackTrace();
            } finally {
                features.close();
            }
            return simpleFeatures.collection();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    尝试之后并没有解决,依然报错,错误信息也还是一样。

    经过 debugger 调试 Geotools 源码,发现是 CRS.lookupIdentifier() 方法抛出的异常,找到相关代码块,排查问题。最后确认是此方法的 throw 异常,如果不支持投影坐标系,就是抛出异常,导致直接跳出,终止执行

    贴上源码:

    /**
     * Create a properties map for the provided crs.
     *
     * @param crs CoordinateReferenceSystem or null for default
     * @return properties map naming crs identifier
     * @throws IOException
     */
    Map<String, Object> createCRS(CoordinateReferenceSystem crs) throws IOException {
      Map<String, Object> obj = new LinkedHashMap<String, Object>();
      obj.put("type", "name");
      Map<String, Object> props = new LinkedHashMap<String, Object>();
      if (crs == null) {
        props.put("name", "EPSG:4326");
      } else {
        try {
          String identifier = CRS.lookupIdentifier(crs, true);
          props.put("name", identifier);
        } catch (FactoryException e) {
          throw (IOException) new IOException("Error looking up crs identifier").initCause(e);
        }
      }
      obj.put("properties", props);
      return obj;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    按理说,Geotools 不认识的投影坐标,直接跳出,也没什么问题,属于方法的正统操作。

    后来,在调试过程发现,EPSG: 4527 的数据已经转换为 EPSG: 4326,手动叠加到地图上,位置也是正确的

    这里基本就确定了,Geotools 是可以识别 EPSG: 4527 投影坐标系的,而且数据转换也是成功的,只是在转换过程中 CRS.lookupIdentifier() 出了问题,导致抛出异常,使方法不生效。

    初步认为是 Geotools 源码的问题,由于没有克隆 Geotools 源码,就不在源码层面解决这个问题了。

    解决方法

    笔者的处理方式是,转换坐标之后,手动拼接 Geojson数据(主要是 FeatureCollection)。其中,坐标转换过程中,源数据的投影坐标系,读取数据会自动识别,统一转为 EPSG: 4326

    Geojson 数据中返回的 crs,不能直接通过数据获取,可以通过 CRS.lookupIdentifier() 方法获取,但是源码的方法遇到未知投影坐标会出现问题,本文通过重写 CRS.lookupIdentifier() 方法来解决问题,获取 crs 之后,存入 Geojson 对象中即可。

    核心代码:

    1. shp 获取 Feature,转为 json 字符串数组

    //获取图形数组
    SimpleFeatureCollection result = featureSource.getFeatures();
    
    // 源数据投影坐标系
    CoordinateReferenceSystem crs = shpDataStore.getSchema().getCoordinateReferenceSystem();
    
    // EPSG:4326 
    CoordinateReferenceSystem worldCRS = DefaultGeographicCRS.WGS84;
    
    //定义坐标转换
    MathTransform transform = CRS.findMathTransform(crs, worldCRS, true);
    
    // 获取图形要素
    SimpleFeatureIterator features = result.features();
    
    // 定义图形要素容器,存放转换后图形要素数据
    DefaultFeatureCollection simpleFeatures = new DefaultFeatureCollection();
    
    // 存放 geojson 数据
    JSONArray jsonArray = new JSONArray();
    try {
        while (features.hasNext()) {
        
            SimpleFeature feature = features.next();
            // 源几何数据
            Geometry geometry = (Geometry) feature.getDefaultGeometry();
            // 转换后几何数据
            Geometry geometry2 = JTS.transform(geometry, transform);
            // 图形要素设置几何数据
            feature.setDefaultGeometry(geometry2);
    
            //将feature转为geojson
            StringWriter writer = new StringWriter();
            // 创建 FeatureJSON 对象,注意参数:精度 8 位小数
            FeatureJSON json = new FeatureJSON(new GeometryJSON(8));
    
            json.writeFeature(feature, writer);
    
    		// 保存 geojson 数组
            jsonArray.add(JSON.parse(writer.toString()));
    
            simpleFeatures.add(feature);
        }
    
    } catch (NoSuchElementException e) {
        e.printStackTrace();
    } catch (MismatchedDimensionException e) {
        e.printStackTrace();
    } catch (TransformException e) {
        e.printStackTrace();
    } finally {
        features.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    2. 拼接最终 Geojson 数据

    // 图形要素数组
    // json 字符数组
    public Map writeFeatureCollection(FeatureCollection features, JSONArray featuresJson) throws IOException {
    
        LinkedHashMap<String, Object> obj = new LinkedHashMap();
        
        // 设置 geojson 类型
        obj.put("type", "FeatureCollection");
        
        if (features.getSchema().getGeometryDescriptor() != null) {
            ReferencedEnvelope bounds = features.getBounds();
            // 获取数据投影坐标系
            CoordinateReferenceSystem crs = bounds != null ? bounds.getCoordinateReferenceSystem() : null;
            if (bounds != null) {
                // 设置数据四至范围
                obj.put("bbox", Arrays.asList(bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY()));
            }
    
            if (crs != null) {
                // 设置 geojson 数据投影坐标系(方法在文章后边)
                obj.put("crs", createCRS(crs));
            }
        }
    
        // 设置图形要素 json 字符串数组
        obj.put("features", featuresJson);
    
        return obj;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    Geotools 定义坐标系

    创建 CRS 投影坐标系对象, 注意:如果是 Geotools 不认识的投影坐标系,CRS.lookupIdentifier() 的第一个参数必须设置为 (Citations.EPSG) !!! 不设置的话,方法会抛出异常,导致定义坐标系失败。

    当然也可以选择不抛出,直接不做任何操作,但是建议还是沿用 Geotools 的做法,设置第一个参数: Citations.EPSG

    对于 Geotools 认识的投影坐标系,可以直接:

    CoordinateReferenceSystem crs = CRS.decode("EPSG:4326"); 
    
    • 1

    对于其他不认识的标准投影坐标系,可以:

    
        /**
         * Create a properties map for the provided crs.
         *
         * @param crs CoordinateReferenceSystem or null for default
         * @return properties map naming crs identifier
         */
        Map<String, Object> createCRS(CoordinateReferenceSystem crs) throws IOException {
        
            Map<String, Object> obj = new LinkedHashMap<>();
            obj.put("type", "name");
    
            Map<String, Object> props = new LinkedHashMap<>();
            if (crs == null) {
                props.put("name", "EPSG:4326");
            } else {
                try {
                    // 获取 EPSG 码
                    String code = crs.getCoordinateSystem().getName().getCode();
                    // 如果不设置第一个参数,会抛出异常
                    // String identifier = CRS.lookupIdentifier(crs, true);
                    // 注意,这里的方法需要设置第一个参数,设置为标准投影坐标系
                    String identifier = CRS.lookupIdentifier(Citations.EPSG, crs, true);
                    props.put("name", StringUtils.isNotEmpty(identifier) ? identifier : EPSGChina.defaultEPSG8.get(code));
                } catch (FactoryException e) {
                    throw (IOException) new IOException("Error looking up crs identifier").initCause(e);
                }
            }
            obj.put("properties", props);
            return obj;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    参考博客:

    GeoTools入门(五)-- CRS操作
    org.geotools.referencing.CRS.lookupIdentifier()方法的使用及代码示例
    geotools 官网 CRS

  • 相关阅读:
    Gin框架中的Cookie怎么搞(会话控制)
    R语言使用lm函数构建线性回归模型、使用subset函数指定对于数据集的子集构建回归模型(使用floor函数和length函数选择数据前部分构建回归模型)
    Github Actions 自动同步到 Gitee
    springboot集成junit单元测试框架
    java计算机毕业设计双峰县在线房屋租售网站源代码+数据库+系统+lw文档
    pytorch -- 构建自己的Dateset,DataLoader如何使用
    3.Node-事件循环用法
    java计算机毕业设计留守儿童帮扶网站源码+mysql数据库+系统+lw文档+部署
    阿里三面:CAP和BASE理论了解么?可以结合实际案例说下?
    matlab使用hampel滤波,去除异常值
  • 原文地址:https://blog.csdn.net/linzi19900517/article/details/126406650