Android LBS地图开发:地球地理GPS坐标系经纬度偏移偏差

本文作者:ZhangPhil
欢迎各位转载!但请注明转载出处:
http://blog.csdn.net/zhangphil/article/details/48024831


Android LBS地图开发基础知识之地球地理GPS坐标系经纬度偏移偏差


 通常,我们所说的地球地理经纬度是WGS-84坐标系(World Geodetic System-1984 Coordinate System)的经纬度。WGS-84坐标系是在1984年制定的全球坐标系,这个坐标系上的每一点经纬度能够精确映射到地球表面的任意一点。我们中学地理教科书中所讲述的地理坐标系其实就是WGS-84坐标系。WGS-84坐标系为GPS而生,是全球通用的坐标系。
 然而,在中国大陆,所有民用和商用的坐标系都不是WGS-84坐标系,而是GCJ-02坐标系(有些地图供应厂商可能在GCJ-02基础二次深度开发形成自家所有的多形式、多标准的地图坐标系,比如百度经纬度坐标系标准bd09ll)。什么是GCJ-02坐标系呢?GCJ-02坐标系是中国国家测绘局2002年制定的、不同于WGS-84坐标系的、中国大陆境内民用和商用经纬度的坐标系。
 GCJ-02坐标系不同于WGS-84坐标系在什么地方呢?
“GCJ-02是由中国国家测绘局制订的地理信息系统的坐标系统。它是一种对经纬度数据的加密算法,即加入随机的偏差。国内出版的各种地图系统(包括电子形式),必须至少采用GCJ-02对地理位置进行首次加密。”
GCJ-02坐标系最关键的地方,概括起来:如果说WGS-84坐标系是地球地理表面某一点真实的经纬度坐标系,那么GCJ-02坐标系是在WGS-84坐标系基础上,把WGS-84真实的经纬度坐标点随机添加一定经纬度偏移量(偏移量计算算法相当复杂,不是简单的随机)而形成的“不真实”的经纬度坐标系。GCJ-02坐标系因此也被称为火星坐标系。
 WGS-84坐标系到GCJ-02坐标系,最核心的地方就是偏移量的计算和添加,偏移量算法目前没有公开资料,据传闻下面一段算法是WGS-84到GCJ-02处理纬度偏移量的一小节算法:

-100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.Sqrt(Math.Abs(x)) + (20.0 * Math.Sin(6.0 * x * pi) + 20.0 * Math.Sin(2.0 * x * pi)) * 2.0 / 3.0 + (20.0 * Math.Sin(y * pi) + 40.0 * Math.Sin(y / 3.0 * pi)) * 2.0 / 3.0 + (160.0 * Math.Sin(y / 12.0 * pi) + 320 * Math.Sin(y * pi / 30.0)) * 2.0 / 3.0

算法相当精巧,该算法目的是保证在WGS-84坐标系添加一定偏移量形成GCJ-02的过程中,输出结果是连续、单调的,每一个WGS-84坐标系的坐标点经过GCJ-02偏移量算法处理后的坐标点是唯一的、一一对应的,只有这样才能保证GCJ-02即便不是真实的经纬度坐标,但是偏移形成后的GCJ-02依然可以完整显示地图。该算法不可逆,无法找到反函数,即便拿到输出结果,也无法推导真实函数工作原理。但是有些人通过GCJ-02偏移量算法输出的值,经过复杂的数量计算和分析,也是可以大概模拟偏移量算法,但还是存在不小误差。
 下图(网传)大体上较为模糊的显示了经过偏移量算法处理后的偏移量值,红色最大,蓝色最小。GCJ-02坐标系将WGS-84坐标系偏移了一定距离,偏移距离在数百米以内,不超过1000米。偏移量越大,意味着定位越不准确,偏移量越小,意味着定位越准确,换言之,偏移量为0即是真实的WGS-84坐标系:
 


中国国内民用商用的地图供应商和服务商,都至少是基于GCJ-02开发的。但GCJ-02明明不是真实的坐标系,那为什么显示的是真实正确的呢?是因为火星坐标系必须配上国内的火星地图,才能在表面上看上去是“真实、正确”的。比如百度,百度地图上给出的某点的坐标系都是处理后的火星坐标系,不是真实的。百度在自己的官方网站(http://developer.baidu.com/map/question.htm)给出了文档加以说明:
 

节选一段百度官方网站的文档内容:

2 坐标体系
2.1 坐标体系是否遵循国家对地理信息保密要求?
百度对外接口的坐标系,都是经过国家测绘局加密处理,符合国家测绘局对地理信息保密要求。
2.3 百度坐标为何有偏移?
国际经纬度坐标标准为WGS-84,国内必须至少使用国测局制定的GCJ-02,对地理位置进行首次加密。百度坐标在此基础上,进行了BD-09二次加密措施,更加保护了个人隐私。百度对外接口的坐标系并不是GPS采集的真实经纬度,需要通过坐标转换接口进行转换。
2.4 如何从其他体系的坐标迁移到百度坐标?
开发者可以使用坐标转换接口进行转换。JavaScript API 、Android SDK、iOS SDK的开发用户可直接调用相应方法进行转换。

百度在其官方网站(http://developer.baidu.com/map/geosdk-android-classv4.0.htm#import.E7.9B.B8.E5.85.B3.E7.B1.BB)给出了自家Android SDK的经纬度标准类型的设置方法:



百度已经开放API接口,提供不同标准的经纬度坐标系转换(纠偏)。百度官方的不同坐标系经纬度转换(纠偏)地址:http://developer.baidu.com/map/changeposition.htm

也有一些其他厂商提供的坐标系转换API接口,比如这个网站提供的GPS火星坐标转换纠偏接口:
 http://www.zdoz.net/apiList.html

 关于经纬度偏移偏差,还有一个重要情况需要在LBS开发时候引起注意,是关于GPS硬件模组本身的,分两类情况,黑盒测试发现,据推测也许应该存在(真实情况有待进一步论证):
(1)中国大陆境内经过正规行货渠道发售的智能手机。这类手机自带的GPS硬件模组,返回的GPS经纬度值不是真实的,也是经过一定偏移量算法处理后的经纬度。这是因为GPS模组在硬件级别的偏移量处理导致。这类国内行货手机的GPS模组在收到GPS卫星返回的真实WGS-84坐标系经纬度后,执行偏移量算法处理成GCJ-02经纬度之后,此时才将GCJ-02经纬度返回给上层软件系统。
(2)国外发售的手机(或者水货),不是国家正规途径许可的手机。由于这类智能手机目标市场不是中国,自身的GPS硬件模组收到WGS-84坐标系经纬度后不做任何偏移量处理,直接返回给上层软件,那么这种情况下,这类手机得到的经纬度是真实的WGS-84坐标系经纬度。但是,如果在中国大陆境内使用这类手机,把底层GPS硬件模组返回的真实WGS-84经纬度交给国内厂商的地图直接定位,也是会出现偏差,原因是国内地图厂商的地图是基于火星经纬度坐标系的!如果不做经纬度转换,国内的地图直接将真实的WGS-84坐标系经纬度误以为是火星坐标系经纬度进行定位,当然会出现偏差!


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.需要在 http://developer.baidu.com/ 注册开发者(个人或公司)账号 2.需要申请Key 打开网址 http://developer.baidu.com/map/index.php 点击 创建应用,跟流程创建应用app 3.点击相关下载->一键下载 4.调用百度地图的APP 需要在 AndroidManifest.xml 添加 <application android:name="baidumapsdk.demo.DemoApplication" android:icon="@drawable/ic_launcher" android:label="@string/app_name" > 这里需要添加key,创建应用后,会有这个key <meta-data android:name="com.baidu.lbsapi.API_KEY" android:value="6t2yuIFylnRG7ECj1xHYuelY" /> ..... package com.obtk.mapdemo; import com.baidu.location.BDLocation; import com.baidu.location.BDLocationListener; import com.baidu.location.LocationClient; import com.baidu.location.LocationClientOption; import com.baidu.mapapi.SDKInitializer; import com.baidu.mapapi.map.BaiduMap; import com.baidu.mapapi.map.BaiduMapOptions; import com.baidu.mapapi.map.MapStatusUpdate; import com.baidu.mapapi.map.MapStatusUpdateFactory; import com.baidu.mapapi.map.MapView; import com.baidu.mapapi.map.MyLocationData; import com.baidu.mapapi.map.MyLocationConfiguration.LocationMode; import com.baidu.mapapi.model.LatLng; import com.baidu.mapapi.search.core.SearchResult; import com.baidu.mapapi.search.geocode.GeoCodeResult; import com.baidu.mapapi.search.geocode.GeoCoder; import com.baidu.mapapi.search.geocode.OnGetGeoCoderResultListener; import com.baidu.mapapi.search.geocode.ReverseGeoCodeOption; import com.baidu.mapapi.search.geocode.ReverseGeoCodeResult; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.RelativeLayout; import android.widget.Toast; import android.app.Activity; import com.obtk.mapdemo.R; public class MapApiDemoActivity extends Activity implements OnGetGeoCoderResultListener { private MapView mMapView = null; private BaiduMap mBaiduMap = null; private GeoCoder mSearch = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // SDK初始化 SDKInitializer.initialize(getApplicationContext()); //当前视图 setContentView(R.layout.activity_map_api_demo); //创建地图对象 init(); final Button btn_location = (Button) findViewById(R.id.btn_location); btn_location.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub getLocation(); btn_location.setEnabled(false); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.activity_map_api_demo, menu); return true; } /** * 初始化方法 */ private void init() { //mMapView = (MapView) findViewById(R.id.bmapview); mMapView = new MapView(this, new BaiduMapOptions()); mBaiduMap = mMapView.getMap(); /**添加一个对象*/ RelativeLayout rlly_map = (RelativeLayout)findViewById(R.id.rlly_map); rlly_map.addView(mMapView); // 开启定位图层 mBaiduMap.setMyLocationEnabled(true); //初始化搜索模块,注册事件监听 mSearch = GeoCoder.newInstance(); mSearch.setOnGetGeoCodeResultListener(this); } @Override protected void onResume() { super.onResume(); mMapView.onResume(); } @Override protected void onPause() { super.onPause(); mMapView.onPause(); } @Override protected void onDestroy() { // 退出时销毁定位 mLocClient.stop(); // 关闭定位图层 mBaiduMap.setMyLocationEnabled(false); mMapView.onDestroy(); mMapView = null; super.onDestroy(); } // 定位相关 LocationClient mLocClient; public MyLocationListenner myListener = new MyLocationListenner(); private LocationMode mCurrentMode; private boolean isFirstLoc = true; /** * 定位SDK监听函数 */ public class MyLocationListenner implements BDLocationListener { @Override public void onReceiveLocation(BDLocation location) { // map view 销毁后不在处理新接收的位置 if (location == null || mMapView == null) return; MyLocationData locData = new MyLocationData.Builder() .accuracy(location.getRadius()) //此处设置开发者获取到的方向信息,顺时针0-360 .direction(100).latitude(location.getLatitude()) .longitude(location.getLongitude()).build(); mBaiduMap.setMyLocationData(locData); if (isFirstLoc) { isFirstLoc = false; LatLng ll = new LatLng(location.getLatitude(), location.getLongitude()); MapStatusUpdate u = MapStatusUpdateFactory.newLatLng(ll); mBaiduMap.animateMapStatus(u); } String addr = location.getAddrStr(); if (addr != null) { Log.i("Test", addr); } else { Log.i("Test","error"); } double longitude = location.getLongitude(); double latitude = location.getLatitude(); if (longitude > 0 && latitude > 0) { Log.i("Test",String.format("纬度:%f 经度:%f", latitude,longitude)); LatLng ptCenter = new LatLng(latitude,longitude); // 反Geo搜索 mSearch.reverseGeoCode(new ReverseGeoCodeOption() .location(ptCenter)); } //停止定位 mLocClient.stop(); } public void onReceivePoi(BDLocation poiLocation) { } } private void getLocation() { // 定位初始化 mLocClient = new LocationClient(this); mLocClient.registerLocationListener(myListener); LocationClientOption option = new LocationClientOption(); option.setOpenGps(true);//打开gps option.setCoorType("bd09ll"); //设置坐标类型 option.setScanSpan(5000); //定位时间间隔 mLocClient.setLocOption(option); mLocClient.start(); } @Override public void onGetGeoCodeResult(GeoCodeResult arg0) { // TODO Auto-generated method stub } @Override public void onGetReverseGeoCodeResult(ReverseGeoCodeResult result) { // TODO Auto-generated method stub if (result == null || result.error != SearchResult.ERRORNO.NO_ERROR) { Toast.makeText(MapApiDemoActivity.this, "抱歉,未能找到结果", Toast.LENGTH_LONG) .show(); return; } mBaiduMap.clear(); // mBaiduMap.addOverlay(new MarkerOptions().position(result.getLocation()) // .icon(BitmapDescriptorFactory // .fromResource(R.drawable.icon_marka))); mBaiduMap.setMapStatus(MapStatusUpdateFactory.newLatLng(result .getLocation())); Toast.makeText(MapApiDemoActivity.this, result.getAddress(), Toast.LENGTH_LONG).show(); String province = result.getAddressDetail().province; String city = result.getAddressDetail().city; if (province != null && city != null) { } } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值