本文將介紹瓦片坐標相關知識,並提供高德地圖、百度地圖、谷歌地圖的經緯度坐標與瓦片坐標的相互轉換方法和類庫。
背景
互聯網地圖服務商的在線地圖都通過瓦片的方式提供,稱為瓦片地圖服務。最常見的地圖瓦片是圖片格式的,現在有的地圖服務商也提供了矢量的瓦片數據,然后在用戶端使用Canvas渲染成圖片,如node-canvas實現百度地圖個性化底圖繪制。
在進行地圖開發時,為獲取特定經緯度所在區域的瓦片和獲取瓦片上像素點對應的經緯度,經常需要進行經緯度坐標與瓦片坐標、像素坐標的相互轉換。本文將介紹瓦片坐標相關知識,並提供高德地圖、百度地圖、谷歌地圖的經緯度坐標與瓦片坐標的相互轉換方法和轉換類庫–tile-lnglat-transform。
主要經緯度坐標系
國際標准的經緯度坐標是WGS84
,Open Street Map、外國版的Google Map都是采用WGS84
;高德地圖使用的坐標系是GCJ-02
;百度地圖使用的坐標系是BD-09
。高德地圖和百度地圖都提供了在線的單向坐標轉換接口,將其他坐標系換化到自己的坐標系,但這種轉換受限於http url請求字段長度和網絡請求延遲,批量處理並不實用。離線相互轉換可以通過開源JavaScript庫coordtransform實現,誤差在10米左右。
雖然各地圖服務商經緯度坐標系不同,但某一互聯網地圖的經緯度坐標與瓦片坐標相互轉換只與該地圖商的墨卡托投影和瓦片編號的定義有關,跟地圖商采用的大地坐標系標准無關。
墨卡托投影
使用經緯度表示位置的大地坐標系雖然可以描述地球上點的位置,但是對於地圖地理數據在二維平面內展示的場景,需要通過投影的方式將三維空間中的點映射到二維空間中。地圖投影需要建立地球表面點與投影平面點的一一對應關系,在互聯網地圖中常使用墨卡托投影。墨卡托投影是荷蘭地理學家墨卡托於1569年提出的一種地球投影方法,該方法是圓柱投影的一種。投影的更多內容,可以查看地圖投影的N種姿勢。
據我了解,各大地圖服務商都采用了Web Mercator進行投影,瓦片坐標系的不同主要是投影截取的地球范圍不同、瓦片坐標起點不同。
值得注意的是:
- 墨卡托投影並不是一種坐標系,而是為了在二維平面上展示三維地球而進行的一種空間映射。所以在GIS地圖和互聯網地圖中,雖然用戶看到的地圖經過了墨卡托投影,但依然使用經緯度坐標來表示地球上點的位置。
- 在地圖繪制和地圖可視化時,就需要將地圖數據使用投影的方式來呈現。
瓦片切割和瓦片坐標
對於經過墨卡托投影為平面的世界地圖,在不同的地圖分辨率(整個世界地圖的像素大小)下,通過切割的方式將世界地圖划分為像素為$256\times256$的地圖單元,划分成的每一塊地圖單元稱為地圖瓦片。
地圖瓦片具有以下特點:
- 具有唯一的瓦片等級(Level)和瓦片坐標編號(tileX, tileY)。
- 瓦片分辨率為256$\times$256。
- 最小的地圖等級是0,此時世界地圖只由一張瓦片組成。
- 瓦片等級越高,組成世界地圖的瓦片數越多,可以展示的地圖越詳細。
- 某一瓦片等級地圖的瓦片是由低一級的各瓦片切割成的4個瓦片組成,形成了瓦片金字塔。
高德地圖瓦片坐標
坐標系定義
高德地圖瓦片坐標與Google Map、Open Street Map相同。高德地圖的墨卡托投影截取了緯度(約85.05ºS, 約85.05ºN)之間部分的地球,使得投影后的平面地圖水平方向和垂直方向長度相等。將墨卡托投影地圖的左上角作為瓦片坐標系起點,往左方向為X軸,X軸與北緯85.05º重合且方向向左;往下方向為Y軸,Y軸與東經180º(亦為西經180º)重合且方向向下。瓦片坐標最小等級為0級,此時平面地圖是一個像素為256*256的瓦片。在某一瓦片層級Level下,瓦片坐標的X軸和Y軸各有$2^{Level}$個瓦片編號,瓦片地圖上的瓦片總數為$2^{Level}\times2^{Level}$。
如上圖所示,此時X方向和Y方向各有4個瓦片編號,總瓦片數為16。中國大概位於高德瓦片坐標的(3,1)中。
坐標轉換圖解
從高德地圖坐標轉換圖解中可以看出,高德地圖的坐標轉換具有以下特點:
- 所有坐標轉換都在某一瓦片等級下進行,不同瓦片等級下的轉換結果不同。
- 經緯度坐標可以直接轉換為瓦片坐標和瓦片像素坐標。
- 瓦片像素坐標需要結合其瓦片坐標才能得到該像素坐標的經緯度坐標。
坐標轉換公式
方法參考:Slippy map tilenames
- 經緯度坐標(lng, lat)轉瓦片坐標(tileX, tileY):
$$tileX=\lfloor\frac{lng + 180}{360}\times{2^{Level}}\rfloor$$
$$tileY=\lfloor{(\frac{1}{2}-\frac{\ln(\tan(lat\times\pi/180)+\sec(lat\times\pi/180))}{2\timesπ}})\times{2^{Level}}\rfloor$$
- 經緯度坐標(lng, lat)轉像素坐標(pixelX, pixelY)
$$pixelX=\lfloor\frac{lng + 180}{360}\times{2^{Level}}\times256\%256\rfloor$$
$$pixelY=\lfloor{(1-\frac{\ln(\tan(lat\times\pi/180)+\sec(lat\times\pi/180))}{2\timesπ}})\times{2^{Level}}\times256\%256\rfloor$$
- 瓦片(tileX, tileY)的像素坐標(pixelX, pixelY)轉經緯度坐標(lng, lat)
$$lng=\frac{tileX+\frac{pixelX}{256}}{2^{Level}}\times360-180$$
$$lat=\arctan({\sinh({\pi-2\times\pi\times\frac{tileY+\frac{pixelY}{256}}{2^{Level}}})})\times\frac{180}{\pi}$$
百度地圖瓦片坐標
坐標系定義
百度地圖的瓦片坐標系定義與高德地圖並不相同,其墨卡托投影的參數也不同。百度地圖瓦片坐標以墨卡托投影地圖中赤道與0º經線相交位置為原點,沿着赤道往左方向為X軸,沿着0º經線向上方向為Y軸。
百度瓦片坐標定義了另一種二維坐標系,稱為百度平面坐標系。百度平面坐標系的坐標原點與百度瓦片坐標原點相同,以瓦片等級18級為基准,規定18級時百度平面坐標的一個單位等於屏幕上的一個像素。平面坐標與地圖所展示的級別沒有關系,也就是說在1級和18級下,同一個經緯度坐標的百度平面坐標都是一致的。
此時X方向和Y方向各有4個瓦片編號,但是外圍的某些瓦片只有部分區域有地圖或完全沒有地圖。沒有地圖的區域也可以認為其瓦片是無效的,即百度地圖中X方向或Y方向的有效瓦片不一定達到$2^{Level}$個。
中國大概位於百度瓦片坐標的(0,0)中。
坐標轉換圖解
從百度地圖坐標轉換圖解中可以看出,百度地圖的坐標轉換具有以下特點:
- 百度經緯度坐標與百度平面坐標可以直接相互轉換,並且與瓦片地圖等級無關。
- 經緯度坐標需要先轉換為平面坐標,然后才能在某一瓦片等級下轉換為瓦片坐標和瓦片像素坐標。
- 瓦片像素坐標需要結合其瓦片坐標才能得到該像素坐標的平面坐標,然后再轉換為經緯度坐標。
坐標轉換公式
方法參考:百度地圖API詳解之地圖坐標系統
發現百度JavaScript API的一個bug:百度JavaScript API中經緯度坐標轉瓦片坐標bug
-
經緯度坐標(lng, lat)轉平面坐標(pointX, pointY)
百度經緯度坐標與百度平面坐標的相互轉換,並沒有公開的公式,需要通過百度地圖的API實現。
主要代碼為:1234567891011// Bmap為百度JavaScript API V2.0的地圖對象lnglatToPoint(longitude, latitude) {let projection = new BMap.MercatorProjection();let lnglat = new BMap.Point(longitude, latitude);let point = projection.lngLatToPoint(lnglat);return {pointX: point.x,pointY: point.y};} -
平面坐標(pointX, pointY)轉經緯度坐標(lng, lat)
也需要通過百度地圖的API實現。
主要代碼為:12345678910pointToLnglat(pointX, pointY) {let projection = new BMap.MercatorProjection();let point = new BMap.Pixel(pointX, pointY);let lnglat = projection.pointToLngLat(point);return {lng: lnglat.lng,lat: lnglat.lat};} -
平面坐標(pointX, pointY)轉瓦片坐標(tileX, tileY)
$$tileX=\lfloor\frac{pointX\times2^{Level-18}}{256}\rfloor$$
$$tileY=\lfloor\frac{pointY\times2^{Level-18}}{256}\rfloor$$
- 平面坐標(pointX, pointY)轉像素坐標(pixelX, pixelY)
$$pixelX=\lfloor{pointX\times2^{Level-18}-\lfloor\frac{pointX\times2^{Level-18}}{256}\rfloor\times256}\rfloor$$
$$pixelY=\lfloor{pointY\times2^{Level-18}-{\lfloor\frac{pointY\times2^{Level-18}}{256}\rfloor\times256}}\rfloor$$
- 瓦片(tileX, tileY)的像素坐標(pixelX, pixelY)轉平面坐標(pointX, pointY)
$$pointX=\frac{tileX\times256+pixelX}{2^{Level-18}}$$
$$pointY=\frac{tileY\times256+pixelY}{2^{Level-18}}$$
- 經緯度坐標與瓦片坐標、像素坐標的相互轉換,以平面坐標為中間量進行轉換。
吐槽
百度地圖JavaScript的代碼非常奇葩,非常迷惑:
經緯度類是Point
,平面坐標類是Pixel
。
經緯度轉平面坐標是lngLatToPoint
,接收一個Point
對象,返回一個Pixel
對象。
平面坐標轉經緯度坐標是在pointToLngLat
,接收Pixel
對象,返回一個Point
對象。
WTF!
轉換類庫
本文筆者根據前文介紹的經緯度坐標與瓦片坐標、像素坐標相互轉換規則,編寫了一個JavaScript類庫–tile-lnglat-transform,提供了高德地圖、百度地圖、谷歌地圖的經緯度坐標與瓦片坐標的相互轉換。該模塊是使用了UMD
模塊打包方式,可以在node和broswer中使用。
類庫地址:https://github.com/CntChen/tile-lnglat-transform
該類庫的詳細信息及使用方法請在項目主頁中查看。
瓦片地圖等級范圍
- 瓦片地圖等級范圍反映了地圖可縮放的程度。
- 雖然最小的瓦片等級是0,但是部分地圖並不提供0級或其他較小瓦片等級的地圖,因為此時的世界地圖將會很小,不能鋪滿用戶設備窗口。
經過實際測試,各地圖服務商的瓦片等級和測試鏈接如下:
- 百度圖片瓦片的層級是[3~18] http://online1.map.bdimg.com/onlinelabel/?qt=tile&x=49310&y=10242&z=18
- 百度主頁的層級是[3~19] http://map.baidu.com/
- 高德圖片瓦片的層級是[1~19] http://wprd03.is.autonavi.com/appmaptile?style=7&x=427289&y=227618&z=19
-
高德地圖官網介紹的高德地圖層級:
獲取當前地圖縮放級別,在PC上,默認取值范圍為[3,18];在移動設備上,默認取值范圍為[3-19]
-
谷歌地圖瓦片層級是[0~21] http://mt2.google.cn/vt/lyrs=m@167000000&hl=zh-CN&gl=cn&x=1709157&y=910472&z=21&s=Galil
需注意的問題
- 瓦片像素坐標的起始點
- 高德地圖、谷歌地圖的瓦片坐標起點在左上角,像素坐標(pixelX, pixelY)在瓦片中的起點為左上角。
- 百度地圖中,像素坐標(pixelX, pixelY)的起點為左下角。
參考資料
瓦片地圖服務
node-canvas實現百度地圖個性化底圖繪制
http://www.cnblogs.com/well1010/articles/baidu-map-node-canvas.html
tile-lnglat-transform
coordtransform
地圖投影的N種姿勢
Web Mercator
Slippy map tilenames
百度地圖API詳解之地圖坐標系統
http://www.cnblogs.com/jz1108/archive/2011/07/02/2095376.html
百度JavaScript API中經緯度坐標轉瓦片坐標bug
http://cntchen.github.io/2016/05/09/百度JavaScirpt%20%20API中經緯度坐標轉瓦片坐標bug/
高德地圖層級
完
轉載自:http://cntchen.github.io/2016/05/09/%E5%9B%BD%E5%86%85%E4%B8%BB%E8%A6%81%E5%9C%B0%E5%9B%BE%E7%93%A6%E7%89%87%E5%9D%90%E6%A0%87%E7%B3%BB%E5%AE%9A%E4%B9%89%E5%8F%8A%E8%AE%A1%E7%AE%97%E5%8E%9F%E7%90%86/