互联网地图坐标系简介及转换(地理坐标系)

经常有客户反馈这样的问题,为什么接收到gps设备(手机定位)点叠加到地图里会有很大的偏移呢?这个问题本身不复杂,但是对于非gis专业的客户来说,可能就有点晕了。其实产生偏移,无非就是定位点基于的坐标系与底图的坐标系不一致导致。简单介绍下互联网地图所采用的坐标系(仅讨论地理坐标系,也就是常说的经纬度坐标系)。目前常用的地理坐标系主要有国际标准坐标系WGS-84,火星坐标系GCJ-02,百度坐标系BD-09。
WGS-84坐标系

为一种国际标准的大地坐标线,是目前GPS全球卫星定位系统使用广泛的坐标系。
国际地图提供商使用的坐标系,如open street map。
天地图的在线地图是WGS-84坐标系,Google Earth采用的也是WGS-84坐标系。

GCJ-02坐标系

火星坐标系GCJ-02也叫国家测绘局坐标系,国家规定国内出版的各种地图系统(包括电子形式),必须至少采用GCJ-02对地理位置进行首次加密。
国行移动设备中定位获取的坐标数据使用的是GCJ-02坐标系。
超图云在线地图,谷歌国内在线地图,高德地图,腾讯地图采用的都是GCJ-02坐标系。

BD-09坐标系

百度地图标准的坐标系,其实是百度地图在火星坐标系上进行了二次加密。
百度地图针对海外地区提供的WGS-84坐标系。

GPS定位点坐标

原生ios或android库获取的定位点坐标为WGS-84。
百度移动sdk获取的定位点坐标可以是BD-09或GCJ-02,默认BD-09。

高德移动sdk,腾讯移动sdk等获取的定位点坐标为GCJ-02。

​ 现在我们基本搞清楚了为什么会产生偏移,要解决偏移,只需要对坐标点进行坐标系转换。举例:我用SuperMap iMobile for Android组件产品开发了一个地图app,其底图的坐标系是WGS-84,而定位点的获取采用的是高德地图sdk,要保证定位点能在底图上正常显示出来,那么只需对定位点进行GCJ-02到WGS-84坐标系的转换。坐标转换,有很多在线服务,例如超图公网服务的坐标转换,提供了这三种常用坐标系之间的相互转换。有了在线服务坐标转换,那么定位点就可以完美的在移动端或前端地图上叠加显示了。

离线坐标系转换

对于Gis地图软件开发,经常会接触到除了点之外的矢量线和矢量面等数据,这些数据在与底图叠加时也需要坐标系转换,但在线服务无法转换线,面。这时我们可以离线转换,自己扩展转换算法,因为线,面本身也是由点构造的,故将线面的点取到后进行坐标系转换,然后将由结果点构造为线,面。附上离线转换代码:

 public class CoordinateConvertTool {
     public static double pi = 3.1415926535897932384626;
     public static double a = 6378140.0;//1975年国际椭球体长半轴
     public static double ee = 0.0033528131778969143;//1975年国际椭球体扁率
     //百度坐标系转换有0.001级别的误差,若对精度要求高,可采用百度官方接口
     //gps实体类可自行构造,其属性字段为经纬度。
     public static Gps GPS84ToBD09(double lon, double lat) {
         if (outOfChina(lon, lat)) {
             return null;
         }
         double dLat = transformLat(lon - 105.0, lat - 35.0);
         double dLon = transformLon(lon - 105.0, lat - 35.0);
         double radLat = lat / 180.0 * Math.PI;
         double magic = Math.sin(radLat);
         magic = 1 - ee * magic * magic;
         double sqrtMagic = Math.sqrt(magic);
         dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * Math.PI);
         dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * Math.PI);
         double mgLon = lon + dLon;
         double mgLat = lat + dLat;
         double z = Math.sqrt(mgLon * mgLon + mgLat * mgLat) + 0.00002 * Math.sin(mgLat * Math.PI);
         double theta = Math.atan2(mgLat, mgLon) + 0.000003 * Math.cos(mgLon * Math.PI);
         double longitude = z * Math.cos(theta) + 0.0065;
         double latitude = z * Math.sin(theta) + 0.006;
         return new Gps(longitude, latitude);
     }
     public static Gps GPS84ToGCJ02(double lon, double lat) {
         if (outOfChina(lon, lat)) {
             return null;
         }
         double dLat = transformLat(lon - 105.0, lat - 35.0);
         double dLon = transformLon(lon - 105.0, lat - 35.0);
         double radLat = lat / 180.0 * pi;
         double magic = Math.sin(radLat);
         magic = 1 - ee * magic * magic;
         double sqrtMagic = Math.sqrt(magic);
         dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
         dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
         double mgLat = lat + dLat;
         double mgLon = lon + dLon;
         return new Gps(mgLon, mgLat);
     }
     public static Gps GCJ02ToGPS84(double lon, double lat) {
         Gps gps = transform(lon, lat);
         double lontitude = lon * 2 - gps.getLongitude();
         double latitude = lat * 2 - gps.getLatitude();
         return new Gps(lontitude, latitude);
     }
     public static Gps GCJ02ToBD09(double gg_lon, double gg_lat) {
         double x = gg_lon, y = gg_lat;
         double z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * pi);
         double theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * pi);
         double bd_lon = z * Math.cos(theta) + 0.0065;
         double bd_lat = z * Math.sin(theta) + 0.006;
         return new Gps(bd_lon, bd_lat);
     }
     public static Gps BD09ToGCJ02(double bd_lon, double bd_lat) {
         double x = bd_lon - 0.0065, y = bd_lat - 0.006;
         double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * pi);
         double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * pi);
         double gg_lon = z * Math.cos(theta);
         double gg_lat = z * Math.sin(theta);
         return new Gps(gg_lon, gg_lat);
     }
     public static Gps BD09ToGPS84(double bd_lon, double bd_lat) {
         Gps gcj02 = BD09ToGCJ02(bd_lon, bd_lat);
         Gps map84 = GCJ02ToGPS84(gcj02.getLongitude(), gcj02.getLatitude());
         return map84;
     }
     private static boolean outOfChina(double lon, double lat) {
         if (lon < 72.004 || lon > 137.8347)
             return true;
         return lat < 0.8293 || lat > 55.8271;
     }
     private static Gps transform(double lon, double lat) {
         if (outOfChina(lon, lat)) {
             return new Gps(lon, lat);
         }
         double dLat = transformLat(lon - 105.0, lat - 35.0);
         double dLon = transformLon(lon - 105.0, lat - 35.0);
         double radLat = lat / 180.0 * pi;
         double magic = Math.sin(radLat);
         magic = 1 - ee * magic * magic;
         double sqrtMagic = Math.sqrt(magic);
         dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
         dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
         double mgLat = lat + dLat;
         double mgLon = lon + dLon;
         return new Gps(mgLon, mgLat);
     }
     private static double transformLat(double x, double y) {
         double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y
 + 0.2 * Math.sqrt(Math.abs(x));
      ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
      ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0;
      ret += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0;
      return ret;
  }
  private static double transformLon(double x, double y) {
      double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1
              * Math.sqrt(Math.abs(x));
      ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
      ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0;
      ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0
              * pi)) * 2.0 / 3.0;
      return ret;
  }
}

由于偏移不是线性的,所以该离线转换代码不能保证百分百的精确度。经测试,与百度坐标系之间的转换有误差。影响误差的因素很多,比如选取的椭球体,转换算法等,精确度不在本文讨论范围。如需高精度转换,请使用百度地图官方转换接口。

转自SuperMap技术问答社区 http://ask.supermap.com https://blog.csdn.net/supermapsupport/article/details/80318735

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值