1. 程式人生 > >國內主要地圖瓦片座標系定義及計算原理

國內主要地圖瓦片座標系定義及計算原理

發現個好東西,原作者地址: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/

本文將介紹瓦片座標相關知識,並提供高德地圖、百度地圖、谷歌地圖的經緯度座標與瓦片座標的相互轉換方法和類庫。

背景

網際網路地圖服務商的線上地圖都通過瓦片的方式提供,稱為瓦片地圖服務

。最常見的地圖瓦片是圖片格式的,現在有的地圖服務商也提供了向量的瓦片資料,然後在使用者端使用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}$。

高德地圖Level=2的瓦片座標編號情況

高德地圖Level=2的瓦片座標編號情況

如上圖所示,此時X方向和Y方向各有4個瓦片編號,總瓦片數為16。中國大概位於高德瓦片座標的(3,1)中。

座標轉換圖解

高德地圖座標轉換圖解

高德地圖座標轉換圖解

從高德地圖座標轉換圖解中可以看出,高德地圖的座標轉換具有以下特點:

  • 所有座標轉換都在某一瓦片等級下進行,不同瓦片等級下的轉換結果不同。
  • 經緯度座標可以直接轉換為瓦片座標和瓦片畫素座標。
  • 瓦片畫素座標需要結合其瓦片座標才能得到該畫素座標的經緯度座標。

座標轉換公式

方法參考:Slippy map tilenames

  • 經緯度座標(lng, lat)轉瓦片座標(tileX, tileY):

 

tileX=⌊lng+180360×2Level⌋tileX=⌊lng+180360×2Level⌋

 

tileY=⌊(12−ln(tan(lat×π/180)+sec(lat×π/180))2×π)×2Level⌋tileY=⌊(12−ln⁡(tan⁡(lat×π/180)+sec⁡(lat×π/180))2×π)×2Level⌋

  • 經緯度座標(lng, lat)轉畫素座標(pixelX, pixelY)

 

pixelX=⌊lng+180360×2Level×256%256⌋pixelX=⌊lng+180360×2Level×256%256⌋

 

pixelY=⌊(1−ln(tan(lat×π/180)+sec(lat×π/180))2×π)×2Level×256%256⌋pixelY=⌊(1−ln⁡(tan⁡(lat×π/180)+sec⁡(lat×π/180))2×π)×2Level×256%256⌋

  • 瓦片(tileX, tileY)的畫素座標(pixelX, pixelY)轉經緯度座標(lng, lat)

 

lng=tileX+pixelX2562Level×360−180lng=tileX+pixelX2562Level×360−180

 

lat=arctan(sinh(π−2×π×tileY+pixelY2562Level))×180πlat=arctan⁡(sinh⁡(π−2×π×tileY+pixelY2562Level))×180π

百度地圖瓦片座標

座標系定義

百度地圖的瓦片座標系定義與高德地圖並不相同,其墨卡託投影的引數也不同。百度地圖瓦片座標以墨卡託投影地圖中赤道與0º經線相交位置為原點,沿著赤道往左方向為X軸,沿著0º經線向上方向為Y軸。
百度瓦片座標定義了另一種二維座標系,稱為百度平面座標系。百度平面座標系的座標原點與百度瓦片座標原點相同,以瓦片等級18級為基準,規定18級時百度平面座標的一個單位等於螢幕上的一個畫素。平面座標與地圖所展示的級別沒有關係,也就是說在1級和18級下,同一個經緯度座標的百度平面座標都是一致的。

百度地圖Level=2的瓦片座標編號情況

百度地圖Level=2的瓦片座標編號情況

此時X方向和Y方向各有4個瓦片編號,但是外圍的某些瓦片只有部分割槽域有地圖或完全沒有地圖。沒有地圖的區域也可以認為其瓦片是無效的,即百度地圖中X方向或Y方向的有效瓦片不一定達到$2^{Level}$個。
中國大概位於百度瓦片座標的(0,0)中。

座標轉換圖解

百度地圖座標轉換圖解

百度地圖座標轉換圖解

從百度地圖座標轉換圖解中可以看出,百度地圖的座標轉換具有以下特點:

  • 百度經緯度座標與百度平面座標可以直接相互轉換,並且與瓦片地圖等級無關。
  • 經緯度座標需要先轉換為平面座標,然後才能在某一瓦片等級下轉換為瓦片座標和瓦片畫素座標。
  • 瓦片畫素座標需要結合其瓦片座標才能得到該畫素座標的平面座標,然後再轉換為經緯度座標。

座標轉換公式

方法參考:百度地圖API詳解之地圖座標系統
發現百度JavaScript API的一個bug:百度JavaScript API中經緯度座標轉瓦片座標bug

  • 經緯度座標(lng, lat)轉平面座標(pointX, pointY)
    百度經緯度座標與百度平面座標的相互轉換,並沒有公開的公式,需要通過百度地圖的API實現。
    主要程式碼為:

     

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

     

    // 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實現。
    主要程式碼為:

     

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

     

    pointToLnglat(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=⌊pointX×2Level−18256⌋tileX=⌊pointX×2Level−18256⌋

 

tileY=⌊pointY×2Level−18256⌋tileY=⌊pointY×2Level−18256⌋

  • 平面座標(pointX, pointY)轉畫素座標(pixelX, pixelY)

 

pixelX=⌊pointX×2Level−18−⌊pointX×2Level−18256⌋×256⌋pixelX=⌊pointX×2Level−18−⌊pointX×2Level−18256⌋×256⌋

 

pixelY=⌊pointY×2Level−18−⌊pointY×2Level−18256⌋×256⌋pixelY=⌊pointY×2Level−18−⌊pointY×2Level−18256⌋×256⌋

  • 瓦片(tileX, tileY)的畫素座標(pixelX, pixelY)轉平面座標(pointX, pointY)

 

pointX=tileX×256+pixelX2Level−18pointX=tileX×256+pixelX2Level−18

 

pointY=tileY×256+pixelY2Level−18pointY=tileY×256+pixelY2Level−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級或其他較小瓦片等級的地圖,因為此時的世界地圖將會很小,不能鋪滿使用者裝置視窗。

經過實際測試,各地圖服務商的瓦片等級和測試連結如下:

需注意的問題

  • 瓦片畫素座標的起始點
    • 高德地圖、谷歌地圖的瓦片座標起點在左上角,畫素座標(pixelX, pixelY)在瓦片中的起點為左上角。
    • 百度地圖中,畫素座標(pixelX, pixelY)的起點為左下角。

參考資料

瓦片地圖服務

https://en.wikipedia.org/wiki/Tile_Map_Service

node-canvas實現百度地圖個性化底圖繪製

http://www.cnblogs.com/well1010/articles/baidu-map-node-canvas.html

tile-lnglat-transform

https://github.com/CntChen/tile-lnglat-transform

coordtransform

https://github.com/wandergis/coordtransform

地圖投影的N種姿勢

http://blog.sina.com.cn/s/blog_517eed9f0102w4rm.html

Web Mercator

https://en.wikipedia.org/wiki/Web_Mercator

Slippy map tilenames

http://wiki.openstreetmap.org/wiki/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://lbs.amap.com/api/javascript-api/reference/map/