LBS,基於位置的服務(Location Based Service),近年來已經無處不在,尤其是我們前端,相信或多或少都有接觸一些地圖API服務,比如高德、百度啊、谷歌啊~
但是用的時候可能看到下面這些字眼:比如BD09、火星坐標、WGS84……不由得還是蒙圈了啊
那麼接下來,我們就來了解一下,關於當前用到的一些網際網路地圖的基礎坐標轉換知識~
1、首先給大家出一個題
地圖上的經緯度轉換到平面坐標時,和平面坐標的XY的對應關係是什麼,就是經度(longitude)和維度(latitude)分別給對應X,Y中的誰?
這是在實際中經常會用到的一個知識點,我之前沒有想太多,反正就把數值往裡嘗試,因為位置差異很大,正確還是錯誤一眼就看出來了的,但是這樣其實很不好,被師兄說了,我一個GISer的連這個都弄不明白不應該,哈哈哈。
不求甚解是可以的,但是專業性還是要強化的。
來看看上面的圖:經緯度大家都知道,地球上橫線是緯度,縱線是經度。這也導致了我們下意識就會覺得,橫線是X,縱線是Y。這樣的認知顯然是錯誤的。
但其實,橫線是刻畫了Y軸上的刻度,縱線是刻畫了X軸上的刻度,這裡要用到投影的角度來看問題。
所以大家要記住經緯XY:
經度 (longitude) —— 對應 X
維度 ( latitude ) —— 對應 Y
2、當前網際網路地圖的坐標系現狀
1.地球坐標 (WGS84)
國際標準,從專業 GPS 設備中取出的數據的坐標系。國際地圖提供商使用的坐標系。
2.火星坐標 (GCJ-02)也叫國測局坐標系
中國標準,從國行行動裝置中定位獲取的坐標數據使用這個坐標系。
國家規定: 國內出版的各種地圖系統(包括電子形式),必須至少採用GCJ-02對地理位置進行首次加密。
3.百度坐標 (BD-09)
百度標準,百度 SDK,百度地圖,Geocoding 使用(百度在火星坐標上的二次加密)。
網際網路在線地圖使用的坐標系
火星坐標系:
iOS 地圖(其實是高德)、Gogole地圖、搜搜、阿里雲、高德地圖
百度坐標系:當然只有百度地圖
WGS84坐標系:國際標準,谷歌國外地圖、osm地圖等國外的地圖一般都是這個。
從設備獲取經緯度(GPS)坐標
如果使用的是百度sdk那麼可以獲得百度坐標(bd09)或者火星坐標(GCJ02),默認是bd09。
如果使用的是ios的原生定位庫,那麼獲得的坐標是WGS84。
如果使用的是高德sdk,那麼獲取的坐標是GCJ02。
坐標轉換方法--JS版本
我在之前的一篇文章裡,基於Ionic框架的使用講到了地圖定位:ionic2入門教程(六)地圖服務(谷歌、高德、百度定位),現在重新寫一個小demo來實現我們的坐標轉換。
關於方法,我找到了應該是最通用的一種,源碼地址——作者wandergis,大部分的轉換方式應該都是基於他的這個版本,相關說明也是最清楚的。
實際中我們可能會用到不同的地圖,那麼就對應到不同坐標系的轉換,比如說,你有一份wgs84的數據服務,你要展現在百度或者高德地圖上,這時候你就需要轉換了。
我這裡的例子是,我用到百度搜索地名,得到經緯度,但是我要將它繪製在以84為坐標系的地圖leaflet之上,這時候我就需要將返回的經緯度進行轉換。
我們先用百度搜索廣州塔,定位中心
基於我們選擇的OpenStreetMap,未轉換之前,我們用百度搜索廣州塔返回的值畫點,可以看出很明顯是偏移了的:
<h1>百度地名搜索</h1> <input type="text" id="searchVal"> <button id="searchBtn">廣州市內搜索</button> <div id="map1"> </div> <script> var searchBtn = document.getElementById('searchBtn'); var mymap = L.map('map1').setView([39.897445, 116.331398], 13); L.tileLayer( 'https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', { maxZoom: 18, attribution: 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' + '<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' + 'Imagery <a href="http://mapbox.com">Mapbox</a>', id: 'mapbox.streets' }).addTo(mymap); // 創建地址解析器實例 searchBtn.onclick = function () { var searchVal = document.getElementById('searchVal').value; var myGeo = new BMap.Geocoder(); // 將地址解析結果顯示在地圖上,並調整地圖視野 myGeo.getPoint(searchVal, function (point) { if (point) { console.log(point); L.marker([point.lat,point.lng]).addTo(mymap); mymap.setView([point.lat,point.lng],15); } else { alert("您選擇地址沒有解析到結果!"); } }, "廣州市"); }
轉換代碼如下:
/** * copy from https://github.com/wandergis/coordtransform * Created by Wandergis on 2015/7/8. * 提供了百度坐標(BD09)、國測局坐標(火星坐標,GCJ02)、和WGS84坐標系之間的轉換 *///UMD魔法代碼// if the module has no dependencies, the above pattern can be simplified to(function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define([], factory); } else if (typeof module === 'object' && module.exports) { // Node. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like Node. module.exports = factory(); } else { // Browser globals (root is window) root.coordtransform = factory(); } }(this, function () { //定義一些常量 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 bd_lon * @param bd_lat * @returns {*[]} */ var bd09togcj02 = function bd09togcj02(bd_lon, bd_lat) { var bd_lon = +bd_lon; var bd_lat = +bd_lat; 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 lng * @param lat * @returns {*[]} */ var gcj02tobd09 = function gcj02tobd09(lng, lat) { var lat = +lat; var lng = +lng; 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 * @param lng * @param lat * @returns {*[]} */ var wgs84togcj02 = function wgs84togcj02(lng, lat) { var lat = +lat; var lng = +lng; if (out_of_china(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); var mglat = lat + dlat; var mglng = lng + dlng; return [mglng, mglat] } }; /** * GCJ02 轉換為 WGS84 * @param lng * @param lat * @returns {*[]} */ var gcj02towgs84 = function gcj02towgs84(lng, lat) { var lat = +lat; var lng = +lng; if (out_of_china(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); var mglat = lat + dlat; var mglng = lng + dlng; return [lng * 2 - mglng, lat * 2 - mglat] } }; var transformlat = function transformlat(lng, lat) { var lat = +lat; var lng = +lng; 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 }; var transformlng = function transformlng(lng, lat) { var lat = +lat; var lng = +lng; 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 * @returns {boolean} */ var out_of_china = function out_of_china(lng, lat) { var lat = +lat; var lng = +lng; // 緯度3.86~53.55,經度73.66~135.05 return !(lng > 73.66 && lng < 135.05 && lat > 3.86 && lat < 53.55); }; return { bd09togcj02: bd09togcj02, gcj02tobd09: gcj02tobd09, wgs84togcj02: wgs84togcj02, gcj02towgs84: gcj02towgs84 } }));
坐標轉換後顯示如下,我們需要將百度坐標轉成火星坐標再轉成wgs84,因為我們用的地圖是openstreet,是wgs84坐標系。
可以看到,下面中三個點中,有一個已經是正確了的。
myGeo.getPoint(searchVal, function (point) {
if (point) {
console.log(point);
// bd09->gcj02
var myPoint = coordtransform.bd09togcj02(point.lng, point.lat);
console.log(myPoint);
// gcj02->wgs84
var myPoint2 = coordtransform.gcj02towgs84(myPoint[0], myPoint[1]);
console.log(myPoint2);
var latlng = L.latLng([myPoint[1], myPoint[0]]);
var latlng2 = L.latLng([myPoint2[1], myPoint2[0]]);
// 畫點
L.marker(point).addTo(mymap);
L.marker(latlng).addTo(mymap);
L.marker(latlng2).addTo(mymap);
// 設置中心
mymap.setView([point.lat, point.lng], 13);
} else {
alert("您選擇地址沒有解析到結果!");
}
}, "廣州市");
3、EPSG:3857
如果你用到了leaflet/openlayers/arcgis jsAPI的話,應該還有一個點需要了解。
這個算是題外話,因為一般都是學gis的才會用到這些,一般情況下百度高德這些大概都能夠滿足需求了。
像用到這些地圖的情況,經常會涉及到EPSG:3857 或者OpenLayers:900913,acrgis: 102100(3857)。
EPSG:3857 其實是EPSG協會(European Petroleum Survey Group)為 Web Wercator 最終設立的WKID,也就是現在我們常用的Web 地圖的坐標系,並且給定官方命名 「WGS 84 / Pseudo-Mercator「。
Web Mercator 是一個投影坐標系統,其基準面是 WGS 1984 。
WGS 1984 是一個長半軸(a)為6378137,短半軸(b)為6356752.314245179 的橢球體,扁率(f)為298.257223563,f=(a-b)/a 。
但是,Web Mercator 坐標系使用的投影方法不是嚴格意義的墨卡託投影而是一個被 EPSG稱為偽墨卡託的投影方法,這個偽墨卡託投影方法的大名是 Popular Visualization Pseudo Mercator,PVPM。
Google 最先發明了這套系統,在投影過程中,將表示地球的參考橢球體近似的作為正球體處理(正球體半徑 R = 橢球體半長軸 a)。
後來,Web Mercator 在 Web 地圖領域被廣泛使用,這個坐標系就名聲大噪。儘管這個坐標系由於精度問題一度不被GIS專業人士接受,但最終 EPSG 還是給了 WKID:3857。
所以其實看到EPSG:3857,就知道,當前的坐標系是wgs84,而這個屬性,通常在地圖的默認設置中。就是說,如果你不改,這些地圖就應該是wgs84坐標系。
實力小編說
如果對你有幫助的話,能否考慮打賞a cup of coffee,筆芯喲~