文件转换-cad转geojson

时间:2021-10-01 01:21:30

前言

基于前一篇文章GeoServer系列-通过mongodb发布geojson数据,业务上可将常见的地理文件统一为geojson保存到mongodb,方便统一维护和发布geoserver,这一篇将举例cad格式转geojson,并设置坐标

1,必要的依赖

  • 文件转换和解析用到了gdal,需要先下载并配置环境变量(我用的3.3),安装后可使用以下命令查看版本

    C:\Users\CDLX>gdalinfo.exe --version
    GDAL 3.3.0, released 2021/04/26

  • pom引入

    <dependencies>
        <dependency>
            <groupId>org.gdal</groupId>
            <artifactId>gdal</artifactId>
            <version>3.2.0</version>
        </dependency>
    </dependencies>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.7</version>
    </dependency>
    

2,转换入口

    /**
     * dwg转geojson
     *
     * @param fileName gdb文件名  xxx.dwg
     * @param fileMd5  文件md5值   1232131
     * @return
     */
    @Override
    public String dwgToJson(String fileName,String userId, String fileMd5) {
        String wsDirPath = fileDirectory + "/" + fileMd5 + userId;
        //文件名
        String dwgname = fileName.substring(0, fileName.lastIndexOf("."));
        String dwgpath = wsDirPath + "/" + fileName;
        //1,dwg转dxf D:\TS\temp\cadtest\cadtest.dxf
        String outFile = dwgpath.replace(".dwg", ".dxf");
        String res = dwg2dxf(dwgpath, outFile);
        if (res != null) {
            return res;
        }
        //2,dxf转换成json  D:\TS\temp\cadtest\cadtest.geojson
        String jsonPath = outFile.replace(".dxf", "转换前.geojson");
        res = dxfToJson(outFile, jsonPath);
        if (res != null) {
            return res;
        }
        //3,坐标转换  D:\TS\temp\cadtest\cadtest转换.geojson
        long begin = System.currentTimeMillis();
        String newJsonPath = jsonPath.replace("转换前.geojson", "") + ".geojson";
        res = geoJsonCov(jsonPath, newJsonPath, dwgname);
        if (res != null) {
            return res;
        }
        long end = System.currentTimeMillis();
        log.info("dwg坐标转换耗时:{}", (end - begin));
        return null;
    }

3,dwg转dxf

    public String dwg2dxf(String dwgPath, String outFile) {
        File dwgFile = new File(dwgPath);
        if (dwgFile.exists()) {
            if (!dwgFile.getName().endsWith(".dwg")) {
                return "文件格式错误";
            }
            LoadOptions loadOptions = new LoadOptions();
            loadOptions.setSpecifiedEncoding(CodePages.SimpChinese);
            CadImage cadImage = (CadImage) Image.load(dwgFile.getAbsolutePath(), loadOptions);
            cadImage.save(outFile);
            cadImage.close();
            return null;
        } else {
            return "dwg文件不存在," + dwgPath;
        }
    }

4,dxf转geojson

    /**
     * dxf转json
     *
     * @param dxfPath     D:\TS\sharding\filemd5\cadtest.dxf
     * @param geoJsonPath D:\TS\sharding\filemd5\cadtest.geojson
     */
    public String dxfToJson(String dxfPath, String geoJsonPath) {
        ogr.RegisterAll();
        //支持中文路径
        gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8", "YES");
        gdal.SetConfigOption("DXF_ENCODING", "UTF-8");

        //读取文件转化为DataSource
        DataSource ds = ogr.Open(dxfPath, 0);
        if (ds == null) {
            return "打开文件失败," + dxfPath;
        }
        //获取geojso
        Driver geojsonDriver = ogr.GetDriverByName("GeoJSON");
        DataSource geojsonDataSource = geojsonDriver.CopyDataSource(ds, geoJsonPath);
        geojsonDataSource.delete();
        ds.delete();
        return null;
    }

5,坐标转换(重点来了)

因为cad文件没有坐标系,需要预设一个坐标系,将上一步中geojson中的坐标进行转换

    /**
     * 坐标转换
     *
     * @param geoJsonPath D:\\ITS\\Sharding\filemd5\xx.geojson
     * @param outputJson  D:\\ITS\\Sharding\filemd5\xx转换.geojson
     * @param dwgName     图层名称
     */
    public String geoJsonCov(String geoJsonPath, String outputJson, String dwgName) {
        ogr.RegisterAll();
        gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8", "YES");//支持中文路径
        gdal.SetConfigOption("DXF_ENCODING", "UTF-8");
        DataSource ds2 = ogr.Open(geoJsonPath, 0);
        System.out.println("----------坐标转换开始-----------");
        //读取第一个图层,这个图层不是cad中的layer
        Layer oLayer = ds2.GetLayerByIndex(0);
        if (oLayer == null) {
            log.error("从dwg的geojson中获取layer 获取失败:{}", geoJsonPath);
            ds2.delete();
            return "从dwg的geojson中获取layer 获取失败";
        }

        //新创建一个图层geojson
        Driver geojsonDriver = ogr.GetDriverByName("GeoJSON");
        SpatialReference epsg2000 = new SpatialReference("");
        epsg2000.ImportFromEPSG(4490);
        DataSource dataSource = geojsonDriver.CreateDataSource(outputJson, null);
        Layer geojsonLayer = dataSource.CreateLayer(dwgName, epsg2000, ogrConstants.wkbUnknown, null);

        Feature feature;
        String geometryStr;
        String newGeometryStr;
        while ((feature = oLayer.GetNextFeature()) != null) {
            //获取空间属性
            Geometry geometry = feature.GetGeometryRef();
            // 解决自相交问题
            //geometry = geometry.Buffer(0.0);
            //System.out.println(geometry.ExportToJson());
            geometryStr = geometry.GetCurveGeometry().ExportToJson();
            if (geometryStr == null) {
                continue;
            }
            newGeometryStr = String.valueOf(paseCoordinates(geometryStr));
            Geometry newGeometry = Geometry.CreateFromJson(newGeometryStr);
            //传给新建的矢量图层
            Feature dstFeature = feature.Clone();
            dstFeature.SetGeometry(newGeometry);
            geojsonLayer.CreateFeature(dstFeature);
        }
        geojsonDriver.delete();
        ds2.delete();
        dataSource.delete();
        return null;
    }

6,一个朴实无华的逐行坐标转换

逐行读取geojson的Feature,遍历每个坐标

    /**
     * 转换每个Feature的坐标
     * @param coordinatesStr Feature原始json字符串
     * @return
     */
    public StringBuffer paseCoordinates(String coordinatesStr) {
        //String a = "{ \"type\": \"Feature\", \"id\": 0, \"properties\": { \"Layer\": \"SXSS\", \"SubClasses\": \"AcDbEntity:AcDbBlockReference\", \"Linetype\": \"Continuous\", \"EntityHandle\": \"4F2\" }, \"geometry\": { \"type\": \"MultiLineString\", \"coordinates\": [ [ [ 406567.373450083076023, 3058272.568403942044824 ], [ 406565.039480640378315, 3058269.633257158566266 ] ], [ [ 406565.039480640378315, 3058269.633257158566266 ], [ 406565.597624949878082, 3058269.85315527068451 ] ], [ [ 406565.039480640378315, 3058269.633257158566266 ], [ 406565.128001464472618, 3058270.226590381469578 ] ] ] } }";
        int offset = coordinatesStr.indexOf("coordinates\":") + 13;
        String b = coordinatesStr.substring(offset);
        //System.out.println(b);
        String[] c = b.split("\\],");
        StringBuffer stringBuffer = new StringBuffer();
        int n = c.length;
        for (String d : c) {
            //System.out.println(d);
            int begin = d.lastIndexOf("[");
            int end = d.indexOf("]");
            String oldZb = d.substring(begin < 0 ? 0 : begin + 1, end < 0 ? d.length() - 1 : end - 1);
            //System.out.println(oldZb);
            String newZb = CadUtil.covStr(oldZb);
            //System.out.println(newZb);
            if (begin > 0) {
                stringBuffer.append(d, 0, begin + 1).append(newZb);
            }
            if (n != 1) {
                stringBuffer.append("]");
            }
            if (end > 0) {
                stringBuffer.append(d.substring(end - 1));
            }
            if (n > 1) {
                stringBuffer.append(",");
            }
            n--;
        }
        stringBuffer.insert(0, coordinatesStr.substring(0, offset));
        return stringBuffer;
    }

纠正经纬度颠倒问题

    public static String covStr(String zb) {
        String[] poi = zb.trim().split(",");
        double x = Double.parseDouble(poi[0]);
        double y = Double.parseDouble(poi[1]);
        //坐标经纬度交换
        if (x > y) {
            return dxfToSwapxy(x, y);
        } else {
            return dxfToSwapxy(y, x);
        }
    }

硬菜(x_x)

    /**
     * 坐标转换
     *
     * @param X
     * @param Y
     * @return
     */
    public static String dxfToSwapxy(double X, double Y) {
        double lat, lon;
        //*子午线,结合这个网址进行查找,目前用的EPSG:4532中的123D。https://wenku.baidu.com/view/e219e246e3bd960590c69ec3d5bbfd0a7856d530.html
        double L0 = 105;
        Y -= 500000;
        double[] result = new double[2];
        double iPI = 0.0174532925199433;//pi/180
        double a = 6378137.0; //长半轴 m
        double b = 6356752.31414; //短半轴 m
        //扁率 a-b/a
        double e = 0.0818191910428; //第一偏心率 Math.sqrt(5)
        double ee = Math.sqrt(a * a - b * b) / b; //第二偏心率
        double bf = 0; //底点纬度
        double a0 = 1 + (3 * e * e / 4) + (45 * e * e * e * e / 64) + (175 * e * e * e * e * e * e / 256) + (11025 * e * e * e * e * e * e * e * e / 16384) + (43659 * e * e * e * e * e * e * e * e * e * e / 65536);
        double b0 = X / (a * (1 - e * e) * a0);
        double c1 = 3 * e * e / 8 + 3 * e * e * e * e / 16 + 213 * e * e * e * e * e * e / 2048 + 255 * e * e * e * e * e * e * e * e / 4096;
        double c2 = 21 * e * e * e * e / 256 + 21 * e * e * e * e * e * e / 256 + 533 * e * e * e * e * e * e * e * e / 8192;
        double c3 = 151 * e * e * e * e * e * e * e * e / 6144 + 151 * e * e * e * e * e * e * e * e / 4096;
        double c4 = 1097 * e * e * e * e * e * e * e * e / 131072;
        bf = b0 + c1 * Math.sin(2 * b0) + c2 * Math.sin(4 * b0) + c3 * Math.sin(6 * b0) + c4 * Math.sin(8 * b0); // bf =b0+c1*sin2b0 + c2*sin4b0 + c3*sin6b0 +c4*sin8b0 +...
        double tf = Math.tan(bf);
        double n2 = ee * ee * Math.cos(bf) * Math.cos(bf); //第二偏心率平方成bf余弦平方
        double c = a * a / b;
        double v = Math.sqrt(1 + ee * ee * Math.cos(bf) * Math.cos(bf));
        double mf = c / (v * v * v); //子午圈半径
        double nf = c / v;//卯酉圈半径

        //纬度计算
        lat = bf - (tf / (2 * mf) * Y) * (Y / nf) * (1 - 1 / 12 * (5 + 3 * tf * tf + n2 - 9 * n2 * tf * tf) * (Y * Y / (nf * nf)) + 1 / 360 * (61 + 90 * tf * tf + 45 * tf * tf * tf * tf) * (Y * Y * Y * Y / (nf * nf * nf * nf)));
        //经度偏差
        lon = 1 / (nf * Math.cos(bf)) * Y - (1 / (6 * nf * nf * nf * Math.cos(bf))) * (1 + 2 * tf * tf + n2) * Y * Y * Y + (1 / (120 * nf * nf * nf * nf * nf * Math.cos(bf))) * (5 + 28 * tf * tf + 24 * tf * tf * tf * tf) * Y * Y * Y * Y * Y;
        result[0] = retain6(lat / iPI);
        result[1] = retain6(L0 + lon / iPI);
        return result[1] + "," + result[0];
    }
    
     public static double retain6(double num) {
        String result = String.format("%.10f", num);//小数点后面保留10位小数
        return Double.valueOf(result);
    }