不同地图定位偏移以及坐标系转换

Posted by fsoooo Blog on April 10, 2018

众所周知地球是一个不规则椭圆体,GPS中的坐标系定义由基准面地图投影两组参数确定,而基准面的定义则由特定椭球体及其对应的转换参数确定。

基准面是利用特定椭球体对特定地区地球表面的逼近,因此每个国家或地区均有各自的基准面。

基准面是在椭球体基础上建立的,椭球体可以对应多个基准面,而基准面只能对应一个椭球体。

意思就是无论是谷歌地图、搜搜地图还是高德地图、百度地图区别只是针对不同的大地地理坐标系标准制作的经纬度,不存在准不准的问题,大家都是准的只是参照物或者说是标准不一样。

谷歌地图采用的是WGS84地理坐标系(中国范围除外),谷歌中国地图和搜搜中国地图采用的是GCJ02地理坐标系百度采用的是BD09坐标系,而设备一般包含GPS芯片或者北斗芯片获取的经纬度为WGS84地理坐标系

这样就存在不同坐标系的坐标之间转换的问题了,高德地图和百度地图的开发者开放平台中都提供了坐标转换的API。

有哪些不同的地图坐标系?

在使用地图组件开发过程中,我们一般能接触到以下三种类型的地图坐标系:

1.原始坐标系(WGS-84)

WGS-84原始坐标系,一般用国际GPS纪录仪记录下来的经纬度,通过GPS定位拿到的原始经纬度,Google和高德地图定位的的经纬度(国外)都是基于WGS-84坐标系的;但是在国内是不允许直接用WGS84坐标系标注的,必须经过加密后才能使用;据说是为了保密。

GPS坐标形式如图,度分秒形式的经纬度:

2.火星坐标系(GCJ-02)

GCJ-02坐标系,又名“火星坐标系”,是我国国测局独创的坐标体系,由WGS-84加密而成,在国内,必须至少使用GCJ-02坐标系,或者使用在GCJ-02加密后再进行加密的坐标系,如百度坐标系。高德和Google在国内都是使用GCJ-02坐标系,GCJ-02也是国内最广泛使用的坐标体系

3.百度坐标系(bd-09)

百度坐标系是在GCJ-02坐标系的基础上再次加密偏移后形成的坐标系,只适用于百度地图。(目前百度API提供了从其它坐标系转换为百度坐标系的API,但却没有从百度坐标系转为其他坐标系的API)

为什么会发生偏移?

1.坐标系不兼容

由于坐标系之间不兼容,如在百度地图上定位的经纬度拿到高德地图上直接描点就肯定会发生偏移;只考虑国内的情况,高德地图和Google地图是可以不经过转换也能够准确显示的(在国内用的都是GCJ-02坐标系);下面是收录了网上的WGS-84,GCJ-02,百度坐标系(bd-09)之间的相互转换的方法,经测试,是转换后相对准确可用的:

2.国内外网速不同

在国内定位的经纬度,然后在国外网络下显示也会发生偏移(谷歌和高德会依据网络的情况选择使用WGS-84坐标还是GCJ-02坐标,百度地图则一直使用bd-02坐标系)

3.定位方式

在iOS定位的经纬度是通过GPS获取的,在android则可以通过网络或GPS获取经纬度。通过地图SDK定位获取的经纬度,地图SDK会自动选择加密的方式(如Google地图会根据国内国外选择不同的坐标系)然后再将点显示在地图上,这个时候是没有偏移的;如果直接将经纬度在地图上显示,可能就会因为地域或网络的问题导致使用的坐标系不同,进而发生来偏移。

有哪几种坐标?

首先明白,所有坐标体系的原点,都是非洲。

1.经纬度

这个是球面坐标,对于北京来说,就是(116.38817139.935961)这样的坐标。比如腾讯、高德、百度都是这样的经纬度坐标。谷歌是经纬度顺序写反的经纬度坐标。

如果是度分秒坐标,需要进行转换,才能得到这样的经纬度坐标。详见坐标转换。

2.墨卡托坐标

平面坐标,相当于是直线距离,数字一般都比较大,像这样的。

(215362.0002133333599526.00034912192)

墨卡托坐标,主要用于程序的后台计算。直线距离嘛,加加减减几乎计算方便。搜狗地图API就是直接使用的墨卡托坐标。

不同地图坐标怎么转换?

在各种web端平台,或者高德、腾讯、百度上取到的坐标,都不是GPS坐标,都是GCJ-02坐标,或者自己的偏移坐标系。

比如,你在谷歌地图API,高德地图API,腾讯地图API上取到的,都是GCJ-02坐标,他们三家都是通用的,也适用于大部分地图API产品,以及他们的地图产品。

例外,百度API上取到的,是BD-09坐标,只适用于百度地图相关产品。

例外,搜狗API上取到的,是搜狗坐标,只适用于搜狗地图相关产品。

例外,谷歌地球,googleearth上取到的,是GPS坐标,而且是度分秒形式的经纬度坐标。在国内不允许使用。必须转换为GCJ-02坐标。

一.经验转换

根据经验得到的:

(1)百度地图的差别是(0.01185,-0.00328)

如果百度地图的经纬度是(x,y)实际的应该是(x,y)+(-0.01185,-0.00328)=(x-0.01185,y-0.00328)

 /**
     * @param $gg_lon 百度经度
     * @param $gg_lat 百度纬度
     * @return mixed
     *
     * GCJ-02(火星,高德) 坐标转换成 BD-09(百度) 坐标
     */
    public function bd_encrypt($gg_lon, $gg_lat)

    {

        $x_pi = 3.14159265358979324 * 3000.0 / 180.0;

        $x = $gg_lon;

        $y = $gg_lat;

        $z = sqrt($x * $x + $y * $y) - 0.00002 * sin($y * $x_pi);

        $theta = atan2($y, $x) - 0.000003 * cos($x * $x_pi);

        $data['bd_lon'] = $z * cos($theta) + 0.0065;

        $data['bd_lat'] = $z * sin($theta) + 0.006;

        return $data;

    }

(2)google Map的差别是(0.0143,-0.014)

如果用getscreen截图,如果要截的范围为(x,y),输入getscreen的为(x-0.0143,y+0.014).

 /**
     * @param $bd_lon 百度经度
     * @param $bd_lat 百度纬度
     * @return mixed
     *
     * BD-09(百度) 坐标转换成  GCJ-02(火星,高德) 坐标
     */
    public function bd_decrypt($bd_lon, $bd_lat)
    {

        $x_pi = 3.14159265358979324 * 3000.0 / 180.0;

        $x = $bd_lon - 0.0065;

        $y = $bd_lat - 0.006;

        $z = sqrt($x * $x + $y * $y) - 0.00002 * sin($y * $x_pi);

        $theta = atan2($y, $x) - 0.000003 * cos($x * $x_pi);

        $data['gg_lon'] = $z * cos($theta);

        $data['gg_lat'] = $z * sin($theta);

        return $data;

    }
二.度分秒坐标转换为经纬度

比如,在GPS记录仪,或者googleearth上采集到的是39°31’20.51,那么应该这样换算,31分就是31/60度,20.51秒就是20.51/3600度,结果就是39+ 31/60 + 20.51/3600 度。

三.GPS转换为GCJ-02坐标

谷歌,高德,腾讯的地图API官网上,都不直接提供这样的坐标转换。如果要得到GCJ-02坐标,最好在他们的地图上直接取点,或者通过地址解析得到。(这个工具我后续会贴出来的。我就爱干这样的事情,哈哈。)

不过,在网上搜到了这样的接口,该接口的type=1就是GPS转到GCJ-02的墨卡托坐标。请大家对接口保密,哈哈。详见:

http://map.sogou.com/api/documentation/javascript/api2.5/interface_translate.html#late_intro

四.GCJ-02与BD-09之间互转

国测局GCJ-02坐标体系(谷歌、高德、腾讯),与百度坐标BD-09体系的转换

转换算法如下:

#include   
const double x_pi = 3.14159265358979324 * 3000.0 / 180.0;  
void bd_encrypt(double gg_lat, double gg_lon, double &bd_lat, double &bd_lon)  
{  

​    double x = gg_lon, y = gg_lat;  
​    double z = sqrt(x * x + y * y) + 0.00002 * sin(y * x_pi);  
​    double theta = atan2(y, x) + 0.000003 * cos(x * x_pi);  
​    bd_lon = z * cos(theta) + 0.0065;  
​    bd_lat = z * sin(theta) + 0.006;  
}  


void bd_decrypt(double bd_lat, double bd_lon, double &gg_lat, double &gg_lon)  
{  

​    double x = bd_lon - 0.0065, y = bd_lat - 0.006;  
​    double z = sqrt(x * x + y * y) - 0.00002 * sin(y * x_pi);  
​    double theta = atan2(y, x) - 0.000003 * cos(x * x_pi);  
​    gg_lon = z * cos(theta);  
​    gg_lat = z * sin(theta);  
}  

不过也有更简单的算法,线性算法(lat和lng是经纬度,球面坐标):

To_B是转到百度,To_G是转到GCJ-02。

var TO_BLNG =function(lng){return lng+0.0065;};

var TO_BLAT =function(lat){return lat+0.0060;};

var TO_GLNG =function(lng){return lng-0.0065;};

var TO_GLAT =function(lat){return lat-0.0060;};
五.经纬纬度转成墨卡托

在WebGIS的开发中经常用到的地图投影为Web墨卡托和WGS84,Google地图,bingmaps,百度地图,mapabc,mapbar,以及ArcGISonline上的大部分地图为Web墨卡托地图,ArcGIS online上最开始发布的地图投影为WGS84。 在开发过程中很多时候会遇到不同坐标系之间互转的问题,特别是底图使用Web墨卡托,定位(GPS,wifi等)信号坐标为WGS84坐标的时候,那么通用解决方案就是写一个坐标参考系的转换库,类似于proj4,但一般情况下很少用到那么多的参考系之间的互转,并且在客户端实现或者调用proj4都是一件很困难或者麻烦的事情,大多数情况下我们实现Web墨卡托坐标与WGS84坐标互转就可以了。

下面是使用objective-c实现的Web墨卡托坐标与WGS84坐标互转程序,当然也可以使用其他语言来实现,使用起来比较简单和方便。

//经纬度转墨卡托
-(CGPoint )lonLat2Mercator:(CGPoint ) lonLat
{
   CGPoint  mercator;
    double x =lonLat.x *20037508.34/180;
    double y =log(tan((90+lonLat.y)*M_PI/360))/(M_PI/180);
    y = y*20037508.34/180;
    mercator.x =x;
    mercator.y =y;
    returnmercator ;
}
//墨卡托转经纬度
-(CGPoint )Mercator2lonLat:(CGPoint ) mercator
{
    CGPointlonLat;
    double x =mercator.x/20037508.34*180;
    double y =mercator.y/20037508.34*180;
    y=180/M_PI*(2*atan(exp(y*M_PI/180))-M_PI/2);
    lonLat.x =x;
    lonLat.y =y;
    returnlonLat;
}

坐标转换之后为什么会出现偏移?

如果您的坐标在转换之后,还有偏移,那么考虑以下几个方面:

A.原始坐标系弄错

比如以为自己是GPS坐标,但其实已经是GCJ-02坐标。 解决方案:请确保采集到的数据是哪个坐标体系,需要转换到哪个坐标系,再进行坐标转换。

B.原始坐标准确度不够 解决方案:如果您是GPS坐标,请确保采集GPS数据时,搜到至少4颗以上的卫星。并且GPS数据准不准,还取决于周围建筑物的高度,越高越不准,因为有遮挡。 如果本来就是GCJ-02坐标,在不同地图放大级别的时候,看到的地方可能不一样。比如你在地图级别4(国家)取到的坐标,放大到地图12级(街道)时,坐标就偏了。请确保在地图最大放大级别时,拾取坐标。

C.度分秒的概念混淆 比如,在googleearth上采集到的是39°31’20.51,那么应该这样换算,31分就是31/60度,20.51秒就是20.51/3600度,结果就是39+ 31/60 + 20.51/3600 度。

D.经纬度顺序写反了 有些公司(比如高德,百度,腾讯)是先经度,再纬度,即Point(lng lat)。但谷歌坐标的顺序恰好相反,是(latlng)。