目录
1 背景说明
最近的项目中涉及到空间数据的转换,主要是把CGCS200坐标转换为WGS84坐标和BD09坐标。我在网上找了许久,只找到了WGS84坐标转BD09坐标的工具类,但没有找到CGCS200转换到WGS84坐标的工具类。我实在没有办法,就只有自己写。但我没有GIS行业的教育背景,不了解坐标转换的专业知识,不过我断定GIS行业一定有现成的相关方法。通过查阅相关资料,最后在ArcGIS提供的 ArcGIS Runtime API for Java中找到了相关API。我借此实现了个工具类,测试效果也还不错。现在写出这篇文章,共享我的代码,希望能帮到有同样问题的开发者。
为了方便GIS行业外的朋友更流畅地阅读本文,先阐释下相关名词概念。
1.1 空间数据
从数据库的角度来看,是指在表中以geometry类型存在的数据。
从java语言的角度来看,是指com.esri.arcgisruntime.geometry包下各种类的对象,包括Point、Multipoint、Polyline、Polygon等等.
1.2 CGCS2000
CGCS2000:China Geodetic Coordinate System 2000,2000国家大地坐标系,是我国当前最新的国家大地坐标系。该坐标系在ArcGIS中有众多子坐标系。如下图所示。本文选用的是CGCS2000_3_Degree_GK_CM_105E。下方的坐标系详情中有WKID,您需要记录下这个值,后续会用到。
1.3 WGS84
WGS84:World Geodetic System 1984,是为GPS全球定位系统使用而建立的坐标系统,在全球有广泛的使用。
1.4 BD09
BD09:百度地图坐标系,它是在标准Web墨卡托的基础上进行GCJ-02加偏之后,再加上百度自身的加偏算法。
2 读取数据库中的geometry数据
我采用的方式是在数据库用SQL语句查询出geometry字段的String表达形式,再做后续处理。代码片段如下:
@Query( value = "select SHAPE.STAsText() FROM LAND WHERE ID=?", nativeQuery = true)
String getShapeString (String id);
下面这个字符串是本文通过此方法在数据库中查询出来的,此字符串表示一个地块的坐标,逗号分隔出每个坐标点,多个坐标点连成一个面。(此数据是测试数据,由笔者随意编造,没有描述任何真实意义)
POLYGON ((370907.330099999904697 3418398.496099999174576, 370899.395100000314615 3418410.75789999861853, 370909.574500000104318 3418415.690899999812245))
3 代码实现
3.1 CGCS2000转WGS84
上面的这个字符串表示的是CGCS2000坐标系下的数据。下面的代码实现了将多边形地块坐标转换为WGS84,再从WGS84转换为BD09。
package cn.wja.utils;
import cn.wja.model.BD09Point;
import cn.wja.model.PolygonResult;
import com.esri.arcgisruntime.geometry.*;
import java.util.ArrayList;
import java.util.List;
/**
* 用于坐标转换的工具类
*
* @author wang.jingan
* @since 2019-07-20 10:39:16
*/
public class CoordinateSystemTransform {
/**
* 通过String字符串得到三个坐标系的空间数据对象
*
* @param shape 转化为String的Geometry数据
* @return PolygonResult对象,内含三个坐标系的空间数据对象
*/
public static PolygonResult shapeStringToPolygons(String shape) {
//获取两个坐标系
SpatialReference cgcs2000 = SpatialReference.create(4544);
SpatialReference wgs84 = SpatialReference.create(4326);
//创建一个CGCS2000的点收集器,为创建Polygon对象做准备
PointCollection land = new PointCollection(cgcs2000);
//准备返回的百度坐标点集合
List<BD09Point> pointsBD09 = new ArrayList<>();
//获取需要的字符串
String shapeSubstring = shape.substring(10, shape.length() - 2);
//得到每个点的原始数据
String[] points = shapeSubstring.split(",");
//遍历每个点
for (String pointString : points) {
//去前后的空格
pointString = pointString.trim();
//把经纬度分开成两个数据
String[] xy = pointString.split(" ");
Double x = Double.valueOf(xy[0]);
Double y = Double.valueOf(xy[1]);
//把点添加到点收集器
land.add(x, y);
//得到CGCS2000点数据
Point pointCgcs2000 = new Point(x, y, cgcs2000);
//把CGCS2000点数据转化为WGS84点数据
Point pointwgs84 = (Point) GeometryEngine.project(pointCgcs2000, wgs84);
//获取点数据中的经纬度,X为经度,Y为纬度
double pointwgs84X = pointwgs84.getX();
double pointwgs84Y = pointwgs84.getY();
//把wgs84点坐标转化为bdo9点坐标
double[] bd09 = GPSUtil.gps84_To_bd09(pointwgs84Y, pointwgs84X);
BD09Point coordinatesDTO = new BD09Point();
coordinatesDTO.setLat(bd09[0]);
coordinatesDTO.setLon(bd09[1]);
pointsBD09.add(coordinatesDTO);
}
//从点收集器中创建Polygon,得到一个cgcs2000的Polygon
Polygon polygonCgcs2000 = new Polygon(land);
//转换为wgs84的Polygon
Polygon polygonWgs84 = (Polygon) GeometryEngine.project(polygonCgcs2000, wgs84);
//装入返回数据
PolygonResult result = new PolygonResult();
result.setCgcs2000(polygonCgcs2000);
result.setWgs84(polygonWgs84);
result.setBd09(pointsBD09);
return result;
}
}
3.2 BD09坐标系中点的表示
在ArcGIS中wkid用于表示相关坐标系的代码, 并用SpatialReference.create(int wkid) 进行创建对应的坐标系。但BD09坐标系 在ArcGIS中没有相关的wkid,所以BD09坐标是通过点的集合表示,而WGS84的坐标是通过com.esri.arcgisruntime.geometry 包下的Polygon类的对象表示。
BD09坐标系的点是笔者自己定义的一个类。代码如下:
package cn.wja.model;
public class BD09Point {
private Double lat;
private Double lon;
public Double getLat() {
return lat;
}
public void setLat(Double lat) {
this.lat = lat;
}
public Double getLon() {
return lon;
}
public void setLon(Double lon) {
this.lon = lon;
}
@Override
public String toString() {
return "BD09Point{" + "lat=" + lat + ", lon=" + lon + '}';
}
}
3.3 WGS84转BD09
此工具类网上有很多,同时涉及到很多测量学和数学知识,我也不太擅长那些方面,所以就没有自己写,直接用了 高德,百度,Google地图定位偏移以及坐标系转换 这篇文章的代码,您也可以自行在网上寻找类似工具。
3.4测试结果
如下图所示,测试结果未发现异常。
4 依赖和环境
这篇文章首次发表是2019年7月,当时我用使用的ArcGIS Runtime SDK for Java版本为100.4.0。
<dependency>
<groupId>com.esri.arcgisruntime</groupId>
<artifactId>arcgis-java</artifactId>
<version>100.4.0</version>
</dependency>
当时是只需要在pom.xml中加入上面的依赖即可,默认配置的maven仓库或者阿里云仓库都可以自动下载jar包,也不需要安装任何ArcGIS环境,就可以使用运行。
但文章发布后大约半年,就陆续有开发者留言反馈说,不能正确加载jar包,报错提示“程序包com.esri.arcgisruntime.geometry不存在”。开始我以为是他们的Maven配置出了问题,我就手动下载100.8.0的jar文件,放在了我的CSDN资源中,供需要的朋友下载。之所以下载100.8.0版本,是因为原来的100.4.0的jar包我也没有刻意保存,也找不到了。但解决完jar包加载问题后,又有不少开发者反馈出现了如下报错:
Caused by: java.lang.RuntimeException: Could not find runtime in any of:
- A directory specified by calling ArcGISRuntimeEnvironment.setInstallDirectory()
- The current directory F:\Code\Intimate\other\gis-demo
- A location specified by the environment variable ARCGISRUNTIMESDKJAVA_100_10_0
- Within the “.arcgis” directory in the user’s home path C:\Users\admin.arcgis
我在自己的电脑上在测试,也重现了这个问题。开始我以为是100.8.0作为更高的版本,有其新的要求。我就花了不少功夫到arcgis的官网中找到并下载到了100.4.0的jar包,但是运行后也会报一样的错。我看这个jar包上传时间是2020年10月30日,估计是新下载的100.4.0,相对于2019年的文件,也做了些更新吧。
其实上述问题的实质就是ArcGIS Runtime API for Java的运行环境没有成功构建。
那么解决办法就只有两个了:
- 第一个方法是找到老版本的100.4.0.jar。我尝试了很久,都没有找到相关资源,所以就暂时放弃了,如果有读者找到,可以联系我。
- 第二个方法是使用新版本的jar包,并在此基础上构建ArcGIS Runtime API for Java的运行环境。我进行了一些探索后,成功实现了。
解决办法请参见我新写的另一篇博客:《如何构建ArcGIS Runtime API for Java的运行环境》(左侧博客标题有超链接,请点击跳转)。
您也可以直接下载示例代码:ArcGIS Runtime API for Java使用示例:CGCS2000转WGS84和BD09 (左侧资源标题含有超链接,请点击跳转 )。
5 提示
虽然我是通过公开途径下载的 ArcGIS Runtime API for Java 的相关jar包和资源文件,并未曾接收到任何关于收费的提示,但我也未查询到 ArcGIS Runtime API for Java 的供应商Esri 有表明这款软件是可以免费使用的开源软件。因此如需在实际项目开发中使用ArcGIS Runtime API for Java,请先向Esri公司咨询授权的相关事宜,避免不必要的纠纷和损失。
我在撰写本文时就职的公司是购买了ArcGIS 的软件使用权,在使用 ArcGIS Runtime API for Java时,也得到了Esri的技术支持。
如果您希望使用开源软件完成类似功能,建议查询一下geotools。
6 结语
本文的参考文献已经在正文中注明了出处。如果转载本文,也请注明出处和作者。这篇文章是我在CSDN上写的第一篇博客,时间仓促,技术有限,也不太了解CSDN上写博客的技巧,难免有些纰漏,欢迎读者指出其中的不足,也希望大家在博客下的留言区积极讨论。