前言
基于前一篇文章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);
}