一、在進行地圖開發過程中,我們一般能接觸到以下三種類型的地圖坐標系:
1.WGS-84原始坐標系,一般用國際GPS紀錄儀記錄下來的經緯度,通過GPS定位拿到的原始經緯度,Google和高德地圖定位的的經緯度(國外)都是基於WGS-84坐標系的;但是在國內是不允許直接用WGS84坐標系標注的,必須經過加密后才能使用;
2.GCJ-02坐標系,又名“火星坐標系”,是我國國測局獨創的坐標體系,由WGS-84加密而成,在國內,必須至少使用GCJ-02坐標系,或者使用在GCJ-02加密后再進行加密的坐標系,如百度坐標系。高德和Google在國內都是使用GCJ-02坐標系,可以說,GCJ-02是國內最廣泛使用的坐標系;
3.百度坐標系:bd-09,百度坐標系是在GCJ-02坐標系的基礎上再次加密偏移后形成的坐標系,只適用於百度地圖。(目前百度API提供了從其它坐標系轉換為百度坐標系的API,但卻沒有從百度坐標系轉為其他坐標系的API)
4.CGCS2000,2000國家大地坐標系;我們其實很多時候直接用WGS84的坐標來代替CGCS2000坐標。因為CGCS2000的定義與WGS84實質一樣。采用的參考橢球非常接近。扁率差異引起橢球面上的緯度和高度變化最大達0.1mm。當前測量精度范圍內,可以忽略這點差異。
二、為什么會發生偏移?
1.由於坐標系之間不兼容,如在百度地圖上定位的經緯度拿到高德地圖上直接描點就肯定會發生偏移;只考慮國內的情況,高德地圖和Google地圖是可以不經過轉換也能夠准確顯示的(在國內用的都是GCJ-02坐標系);下面是收錄了網上的WGS-84,GCJ-02(高德、Google),百度坐標系(bd-09)之間的相互轉換的方法,經測試,是轉換后相對准確可用的。
三、javascript代碼
// 定義一些常量 var x_PI = 3.14159265358979324 * 3000.0 / 180.0; var PI = 3.1415926535897932384626; var a = 6378245.0; var ee = 0.00669342162296594323; /** * 百度坐標系 (BD-09) 與 火星坐標系 (GCJ-02)的轉換 / 即百度轉谷歌、高德 * @param { Number } bd_lon * @param { Number } bd_lat */ function bd09togcj02 (bd_lon, bd_lat) { var x_pi = 3.14159265358979324 * 3000.0 / 180.0 var x = bd_lon - 0.0065 var y = bd_lat - 0.006 var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_pi) var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi) var gg_lng = z * Math.cos(theta) var gg_lat = z * Math.sin(theta) return [gg_lng, gg_lat] } /** * 火星坐標系 (GCJ-02) 與百度坐標系 (BD-09) 的轉換 / 即谷歌、高德 轉 百度 * @param { Number } lng * @param { Number } lat */ function gcj02tobd09 (lng, lat) { var z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_PI) var theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_PI) var bd_lng = z * Math.cos(theta) + 0.0065 var bd_lat = z * Math.sin(theta) + 0.006 return [bd_lng, bd_lat] } /** * WGS84坐標系轉火星坐標系GCj02 / 即WGS84 轉谷歌、高德 * @param { Number } lng * @param { Number } lat */ function wgs84togcj02 (lng, lat) { if (outOfChina(lng, lat)) { return [lng, lat] }else { var dlat = transformlat(lng - 105.0, lat - 35.0) var dlng = transformlng(lng - 105.0, lat - 35.0) var radlat = lat / 180.0 * PI var magic = Math.sin(radlat) magic = 1 - ee * magic * magic var sqrtmagic = Math.sqrt(magic) dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI) dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI) const mglat = lat + dlat const mglng = lng + dlng return [mglng, mglat] } } /** * GCJ02(火星坐標系) 轉換為 WGS84 / 即谷歌高德轉WGS84 * @param { Number } lng * @param { Number } lat */ function gcj02towgs84 (lng, lat) { if (outOfChina(lng, lat)) { return [lng, lat] } else { var dlat = transformlat(lng - 105.0, lat - 35.0) var dlng = transformlng(lng - 105.0, lat - 35.0) var radlat = lat / 180.0 * PI var magic = Math.sin(radlat) magic = 1 - ee * magic * magic var sqrtmagic = Math.sqrt(magic) dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI) dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI) const mglat = lat + dlat const mglng = lng + dlng return [lng * 2 - mglng, lat * 2 - mglat] } } /** * 百度坐標系轉wgs84坐標系 * @param {*} lng * @param {*} lat */ function bd09towgs84 (lng, lat) { // 百度坐標系先轉為火星坐標系 const gcj02 = bd09togcj02(lng, lat) // 火星坐標系轉wgs84坐標系 const result = gcj02towgs84(gcj02[0], gcj02[1]) return result } /** * wgs84坐標系轉百度坐標系 * @param {*} lng * @param {*} lat */ function wgs84tobd09 (lng, lat) { // wgs84先轉為火星坐標系 const gcj02 = wgs84togcj02(lng, lat) // 火星坐標系轉百度坐標系 const result = gcj02tobd09(gcj02[0], gcj02[1]) return result } /** * 經度轉換 * @param { Number } lng * @param { Number } lat */ function transformlat (lng, lat) { var ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng)) ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0 ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0 ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0 return ret } /** * 緯度轉換 * @param { Number } lng * @param { Number } lat */ function transformlng (lng, lat) { var ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng)) ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0 ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0 ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0 return ret } /** * 判斷是否在國內,不在國內則不做偏移 * @param {*} lng * @param {*} lat */ function outOfChina (lng, lat) { return (lng < 72.004 || lng > 137.8347) || ((lat < 0.8293 || lat > 55.8271) || false) }