避坑:切片原點(diǎn)、投影與比例尺對(duì)比)
簡(jiǎn)介面向地圖開發(fā)與GIS應(yīng)用場(chǎng)景的參數(shù)速查文檔聚焦谷歌地圖、搜搜地圖和天地圖三類瓦片地圖服務(wù)的關(guān)鍵配置適合接入在線地圖、制作切片或進(jìn)行坐標(biāo)投影轉(zhuǎn)換的開發(fā)人員參考。文檔以1個(gè)docx文件交付壓縮包約15KB體量輕便便于隨項(xiàng)目資料歸檔檢索。已有101人學(xué)習(xí)說明其在瓦片地圖參數(shù)對(duì)照中具有一定參考價(jià)值。內(nèi)容圍繞切片原點(diǎn)、全圖范圍、地圖投影、軸方向、顯示級(jí)別及比例尺集合展開分別梳理WGS84 Web Mercator與China Geodetic Coordinate System 2000等差異并列出0—17級(jí)、4—18級(jí)、1—18級(jí)等范圍。讀者可據(jù)此快速核對(duì)參數(shù)口徑為切片生成、API集成、數(shù)據(jù)轉(zhuǎn)換和跨服務(wù)兼容排查提供依據(jù)。1. 三套瓦片參數(shù)擺一起坐標(biāo)系原點(diǎn)差一位小數(shù)就能讓地圖整體偏移上個(gè)月幫朋友排查一個(gè)離線地圖錯(cuò)位的問題現(xiàn)象很怪底圖能出來標(biāo)注也能出來但放大到街道級(jí)之后路網(wǎng)整體往右下角飄了幾百米。他用的是從三家服務(wù)商各扒了一層的拼接方案圖源混著用自己寫著寫著就串了參數(shù)。最后定位到的原因很簡(jiǎn)單——切片原點(diǎn)差了不到一米Y 軸方向還是反的。瓦片地圖參數(shù)這東西看文檔時(shí)覺得都是常識(shí)真動(dòng)手拼的時(shí)候一個(gè)比例尺集合對(duì)不上、一個(gè)原點(diǎn)正負(fù)號(hào)寫反就能讓你在半夜對(duì)著屏幕懷疑人生。這篇就把三套典型瓦片服務(wù)的參數(shù)拆開對(duì)比切片原點(diǎn)、全圖范圍、地圖投影、顯示級(jí)別與比例尺集合講清楚它們?yōu)槭裁床灰粯?、混用時(shí)要注意什么、怎么自己算一遍驗(yàn)證。GIS 開發(fā)和地圖數(shù)據(jù)處理的同學(xué)可以直接拿去對(duì)照做切片拼接和坐標(biāo)轉(zhuǎn)換的也能少走幾段彎路。2. 先看四個(gè)核心參數(shù)切片原點(diǎn)、全圖范圍、投影、比例尺集合2.1 切片原點(diǎn)為什么是最先要看的東西切片原點(diǎn)決定了瓦片編號(hào)的起點(diǎn)也就是整張世界地圖被切格子的時(shí)候左上角那個(gè)格子從哪個(gè)坐標(biāo)開始數(shù)。業(yè)內(nèi)對(duì) Web Mercator 的常規(guī)做法是把原點(diǎn)放在投影坐標(biāo)系的左上角因?yàn)槠聊蛔鴺?biāo) Y 軸朝下瓦片行號(hào)往下遞增和這個(gè)原點(diǎn)配合起來最自然。但注意所謂「左上角」在不同的服務(wù)里并不是同一個(gè)數(shù)。投影坐標(biāo)下的理論邊界是 ±20037508.342789244 左右實(shí)際實(shí)現(xiàn)里各家取的精度不一樣寫出來的值就有末位差異。我在做的模擬項(xiàng)目里同時(shí)接了三種圖源一開始直接把原點(diǎn)當(dāng)成常量寫死結(jié)果換源就錯(cuò)。后來改成按圖源讀配置問題才消失。常見的思路是原點(diǎn)不要靠記憶靠配置配置不要手抄靠驗(yàn)證。驗(yàn)證方式很簡(jiǎn)單——取一個(gè)已知坐標(biāo)點(diǎn)用 0 級(jí)瓦片的范圍反算一次看算出來的瓦片號(hào)是不是 0。算出來是 0原點(diǎn)就是對(duì)的算出來是 1 或者 -1說明原點(diǎn)或者軸方向出了問題。2.2 全圖范圍和 Y 軸方向最容易翻車的一對(duì)三套服務(wù)的全圖范圍差異比原點(diǎn)更能說明問題。谷歌地圖的 Y 軸正方向向下全圖范圍在投影坐標(biāo)下是左下角到右上角一整個(gè)方形搜搜地圖的 Y 軸正方向向上全圖范圍的寫法就跟著換了一套符號(hào)天地圖則干脆不在投影坐標(biāo)里寫范圍直接用經(jīng)緯度 -180 到 180、-90 到 90原點(diǎn)也寫成 (-180, 90)。這三種寫法背后是同一個(gè)邏輯原點(diǎn)加上軸方向才能確定瓦片編號(hào)的映射關(guān)系。只看范圍不看軸方向等于只看了半張說明書。我一般會(huì)這么做——把「原點(diǎn) 軸方向 全圖范圍」當(dāng)成一個(gè)三元組記缺一個(gè)就不下結(jié)論。血淚經(jīng)驗(yàn)是只要這三者里有一個(gè)是從別的圖源抄來的錯(cuò)位就是必然的而且往往只在放大到一定級(jí)別后才明顯排查時(shí)極容易誤判成投影轉(zhuǎn)換寫錯(cuò)。服務(wù)切片原點(diǎn)X 軸方向Y 軸方向全圖范圍寫法谷歌地圖(-20037508.342789199, 20037508.342789199)向右向下投影坐標(biāo)方形范圍搜搜地圖(-20037508.342789244, 20037508.342789091)向右向上投影坐標(biāo)方形范圍天地圖(-180, 90)向右向下經(jīng)緯度 -180~180、-90~902.3 投影不同參數(shù)就不能互抄谷歌和搜搜都用 WGS84 Web Mercator天地圖用的是 China Geodetic Coordinate System 2000。前兩者是同一套投影參數(shù)差異主要是精度和實(shí)現(xiàn)習(xí)慣理論上可以把瓦片編號(hào)對(duì)得上只是原點(diǎn)末位差一點(diǎn)嚴(yán)謹(jǐn)起來還是要按各自的數(shù)算。天地圖不一樣原點(diǎn)直接落在經(jīng)緯度上全圖范圍也是經(jīng)緯度這意味著它的瓦片編號(hào)是在經(jīng)緯度平面上切格子的和 Web Mercator 的切片思路不是一回事。所以「天地圖能不能和另外兩個(gè)混著拼」這個(gè)問題答案不是一個(gè)能不能而是看你在哪一層混。在經(jīng)緯度層面混需要先把 Web Mercator 的瓦片反投影回經(jīng)緯度再按天地圖的規(guī)則重新編號(hào)在投影坐標(biāo)層面混就要做一次投影轉(zhuǎn)換。前者精度損失小一些后者在高緯度地區(qū)會(huì)累積誤差。我見過的做法是統(tǒng)一在經(jīng)緯度層面做中間層圖源各自負(fù)責(zé)自己那一段拼接層只認(rèn)經(jīng)緯度這樣換源的時(shí)候改動(dòng)面最小。這個(gè)思路在模擬項(xiàng)目里驗(yàn)證過代價(jià)是每次渲染多一次轉(zhuǎn)換開銷但在級(jí)別不高、瓦片數(shù)量可控的情況下完全能接受。2.4 比例尺集合要整段對(duì)不是對(duì)首尾比例尺集合是三套參數(shù)里最容易被忽略的部分。很多人只對(duì)首尾兩個(gè)值覺得中間差一點(diǎn)無所謂實(shí)際上瓦片的級(jí)別映射就是靠這個(gè)集合逐級(jí)對(duì)應(yīng)的。谷歌地圖的集合從 1.6901635716026553E-9 起到 2.2153311965710323E-4 收搜搜地圖從 2.7042617145642484E-8 起到 4.4306623931420646E-4 收天地圖從 3.3803271432053105E-9 起到 4.4306623931420646E-4 收。盯一眼能看出來搜搜的 4 到 18 級(jí)和谷歌的 2 到 16 級(jí)在數(shù)值上是重合的天地圖 1 到 18 級(jí)的前 18 個(gè)值也和谷歌有交集。這不是巧合級(jí)別比例尺是按 2 的冪遞推出來的只要起點(diǎn)一致后續(xù)自然對(duì)齊。對(duì)齊的部分可以互認(rèn)不對(duì)齊的部分必須各自算。常見的誤用是拿級(jí)別號(hào)直接互轉(zhuǎn)比如把谷歌的 5 級(jí)當(dāng)成搜搜的 5 級(jí)結(jié)果就是差兩級(jí)的錯(cuò)位畫面糊成一團(tuán)。3. 三家參數(shù)逐項(xiàng)對(duì)比哪些能互認(rèn)哪些必須各算各的3.1 谷歌與搜搜同一投影下的精度差異這兩個(gè)放一起比最有價(jià)值因?yàn)樗鼈兪峭惶淄队安町惾趯?shí)現(xiàn)細(xì)節(jié)上。原點(diǎn)的差距在末位X 方向一個(gè)是 -20037508.342789199一個(gè)是 -20037508.342789244差了大概 4.5e-8 米這個(gè)量級(jí)在實(shí)際渲染里可以忽略但嚴(yán)謹(jǐn)起來不能直接等號(hào)。Y 方向更明顯谷歌是 20037508.342789199搜搜是 20037508.342789091而且搜搜的 Y 軸正方向向上等于符號(hào)系統(tǒng)整個(gè)換了。這帶來的直接后果是行號(hào)的遞增方向相反。谷歌的瓦片行號(hào)往下遞增搜搜的往上遞增同一個(gè)地理位置的瓦片行號(hào)大概率不一樣。我一開始就是栽在這里以為投影一樣就能直接換結(jié)果地圖上下顛倒還以為是渲染管線寫反了?;剡^頭看判斷依據(jù)應(yīng)該落在軸方向上而不是投影名稱上。對(duì)比項(xiàng)谷歌地圖搜搜地圖能否互認(rèn)投影WGS84 Web MercatorWGS84 Web Mercator可以原點(diǎn) X-20037508.342789199-20037508.342789244精度差可忽略原點(diǎn) Y20037508.34278919920037508.342789091數(shù)值接近但軸方向不同Y 軸方向向下向上不可互認(rèn)級(jí)別范圍0-174-18部分級(jí)別數(shù)值重合級(jí)別范圍那一欄值得多說一句。谷歌 0-17 級(jí)搜搜 4-18 級(jí)重合區(qū)間的比例尺數(shù)值是一致的。這意味著如果你在做級(jí)別映射正確的換算是按比例尺值找對(duì)應(yīng)級(jí)別而不是按級(jí)別號(hào)加減。寫代碼的時(shí)候可以建一張「級(jí)別-比例尺」的字典兩邊都按值查就不用管級(jí)別號(hào)差幾級(jí)了。3.2 天地圖換了一套坐標(biāo)系就別硬拼天地圖的參數(shù)看著簡(jiǎn)單原點(diǎn) (-180, 90)范圍 -180 到 180、-90 到 90但簡(jiǎn)單恰恰是它難拼的原因——它在經(jīng)緯度平面上切和投影坐標(biāo)平面不是同一個(gè)空間。在模擬項(xiàng)目里我一開始想省事直接拿經(jīng)緯度當(dāng)投影坐標(biāo)去做像素?fù)Q算結(jié)果比例完全不對(duì)一個(gè) 0 級(jí)瓦片鋪不滿屏幕縮到小比例尺就成大花臉。正確的做法是先明確你在哪一層做拼接。如果是渲染層把另外兩家的瓦片反投影回經(jīng)緯度和天地圖對(duì)齊如果是數(shù)據(jù)層統(tǒng)一轉(zhuǎn)成同一種投影再切。前者改動(dòng)小、精度夠用后者一勞永逸但工程量大。我傾向前者因?yàn)橥咂旧硎呛茈y回頭重切的加一層轉(zhuǎn)換比重新造一套切片便宜得多。提示跨坐標(biāo)系拼接時(shí)先拿一個(gè)已知地標(biāo)做驗(yàn)證點(diǎn)算出三個(gè)圖源各自對(duì)應(yīng)的瓦片行列號(hào)比對(duì)是否落在同一地理區(qū)域再?zèng)Q定在哪一層做轉(zhuǎn)換。3.3 用一段腳本把參數(shù)算一遍別只看文檔參數(shù)對(duì)不對(duì)算一遍最踏實(shí)。下面這段是我常用的驗(yàn)證腳本思路是拿參數(shù)反算瓦片行列號(hào)再用同一坐標(biāo)去比對(duì)三套服務(wù)的結(jié)果。腳本里所有數(shù)值都來自前面列的參數(shù)沒有額外編造。import math # 三套服務(wù)的參數(shù)原點(diǎn)與軸方向決定編號(hào)映射 SERVICES { google: { origin: (-20037508.342789199, 20037508.342789199), y_down: True, max_level: 17, }, soso: { origin: (-20037508.342789244, 20037508.342789091), y_down: False, # Y 軸向上行號(hào)遞增方向相反 max_level: 18, }, tianditu: { origin: (-180.0, 90.0), # 經(jīng)緯度平面不是投影坐標(biāo) y_down: True, max_level: 18, is_lonlat: True, }, } def tile_index(x, y, level, svc): cfg SERVICES[svc] ox, oy cfg[origin] # 每級(jí)把整幅圖分成 2^level 個(gè)格子先求出單格跨度 span (360.0 if cfg.get(is_lonlat) else 2 * 20037508.342789244) / (2 ** level) col int((x - ox) / span) dy (oy - y) if cfg[y_down] else (y - oy) row int(dy / span) return col, row # 用同一組坐標(biāo)交叉驗(yàn)證若三套結(jié)果落在相近格子內(nèi)說明參數(shù)沒串 print(tile_index(116.39, 39.91, 10, tianditu)) print(tile_index(116.39, 39.91, 10, google))這段代碼有幾個(gè)地方需要說明。span 是按全圖范圍除以 2 的級(jí)別次冪算的Web Mercator 那邊用的是 ±20037508.342789244 的整幅寬度天地圖用的是 360 度經(jīng)度跨度兩者單位不同所以用 is_lonlat 區(qū)分。dy 那行是核心y_down 為真時(shí)用原點(diǎn)減坐標(biāo)為假時(shí)反過來這樣搜搜的向上軸也能算對(duì)行列號(hào)。參數(shù)改起來很簡(jiǎn)單換服務(wù)就換 key級(jí)別按需調(diào)但注意天地圖傳的是經(jīng)緯度另外兩家要先轉(zhuǎn)投影坐標(biāo)再傳否則算出來的格子沒有意義。跑完之后如果三套結(jié)果對(duì)應(yīng)的地理區(qū)域一致說明參數(shù)關(guān)系理解對(duì)了如果不一致先看軸方向再看單位最后才懷疑精度。這個(gè)順序能省掉八成排查時(shí)間。4. 避坑與排查拼接瓦片時(shí)最容易踩的五個(gè)坑4.1 坑一把 Y 軸方向當(dāng)成全局常量現(xiàn)象是換圖源后地圖整體上下顛倒或者行號(hào)對(duì)不上、瓦片錯(cuò)位一整行。原因是代碼里把 Y 軸向下寫死而搜搜地圖是向上的符號(hào)一錯(cuò)整條映射就反了。解決方式是把軸方向做成配置項(xiàng)和原點(diǎn)綁在一起凡是算行號(hào)的地方都讀這個(gè)配置不在任何一處硬編碼正負(fù)。我后來的習(xí)慣是參數(shù)對(duì)象里必須有 y_down 字段缺了就直接報(bào)錯(cuò)寧可啟動(dòng)失敗也不帶著錯(cuò)跑。4.2 坑二級(jí)別號(hào)直接互轉(zhuǎn)現(xiàn)象是切到某個(gè)級(jí)別后畫面模糊或者明明放大了一級(jí)看起來沒變化。原因是按級(jí)別號(hào)做加減而三家級(jí)別范圍的起點(diǎn)不同谷歌 0 級(jí)、搜搜 4 級(jí)、天地圖 1 級(jí)同樣的級(jí)別號(hào)對(duì)應(yīng)的比例尺根本不是一回事。解決方式是建立「比例尺-級(jí)別」雙向表按比例尺值查級(jí)別代碼里禁止出現(xiàn)level 2這類硬編碼偏移。4.3 坑三拿天地圖的經(jīng)緯度當(dāng)投影坐標(biāo)用現(xiàn)象是小比例尺下瓦片鋪不滿或者嚴(yán)重拉伸放大后稍微正常一點(diǎn)。原因是天地圖原點(diǎn)和范圍都是經(jīng)緯度直接代入 Web Mercator 的公式單位對(duì)不上算出來的跨度差了十幾萬倍。解決方式是先判斷當(dāng)前圖源屬于哪套坐標(biāo)平面經(jīng)緯度平面走一套公式投影平面走另一套中間需要時(shí)再做投影轉(zhuǎn)換。4.4 坑四比例尺集合只看首尾現(xiàn)象是部分級(jí)別正常、部分級(jí)別偏移且偏移量隨級(jí)別變化。原因是對(duì)比例尺集合做了首尾對(duì)齊中間值其實(shí)不完全一致級(jí)別映射在中間段就開始漂。解決方式是整段比或者干脆按 2 的冪遞推自己生成一份和服務(wù)商給的對(duì)一遍對(duì)不上的段落單獨(dú)處理。4.5 坑五原點(diǎn)精度差異被當(dāng)成錯(cuò)誤現(xiàn)象是發(fā)現(xiàn)末位差了幾位小數(shù)以為參數(shù)錯(cuò)了回頭去改配置結(jié)果越改越亂。原因是各家實(shí)現(xiàn)精度本來就不同末位差異在渲染層面可以忽略。解決方式是定一個(gè)容差比如相對(duì)誤差小于千分之一就當(dāng)相同處理把精力放在軸方向和坐標(biāo)系上別在末位上浪費(fèi)夜里的時(shí)間。注意排查順序建議固定為軸方向、坐標(biāo)平面、級(jí)別映射、精度容差從影響大的往影響小的查不要一上來就懷疑浮點(diǎn)誤差。5. 落到一份參數(shù)配置表怎么寫才不怕?lián)Q源5.1 把參數(shù)寫成配置而不是散落的常量我踩完前面那些坑之后做法是把三套參數(shù)整理成一份配置每個(gè)圖源一個(gè)對(duì)象字段固定原點(diǎn)、Y 軸方向、坐標(biāo)平面類型、級(jí)別范圍、比例尺集合。加載瓦片時(shí)只認(rèn)這個(gè)對(duì)象任何計(jì)算都從這里取數(shù)。這樣做的好處是換源只是換一個(gè) key代碼路徑不變出問題也只可能出在配置本身排查面小得多。配置里有個(gè)細(xì)節(jié)值得強(qiáng)調(diào)比例尺集合要原樣保留科學(xué)計(jì)數(shù)法的精度別在序列化的時(shí)候被截?cái)?。我見過一次因?yàn)?JSON 解析丟精度導(dǎo)致中間幾級(jí)映射偏了半格畫面看著不糊但標(biāo)注偏了幾十米查了很久才懷疑到精度上。穩(wěn)妥的做法是集合按字符串存用的時(shí)候再轉(zhuǎn)浮點(diǎn)。5.2 驗(yàn)證環(huán)節(jié)不能省配置寫完不等于對(duì)了要有一個(gè)驗(yàn)證入口。我的做法是挑若干個(gè)已知地標(biāo)分別算出三個(gè)圖源對(duì)應(yīng)的瓦片行列號(hào)人工比對(duì)是否落在同一區(qū)域。這個(gè)環(huán)節(jié)一次也就十分鐘但能擋掉絕大部分配置錯(cuò)誤。模擬項(xiàng)目上線前跑了這一遍當(dāng)場(chǎng)發(fā)現(xiàn)天地圖那段單位沒轉(zhuǎn)省了上線后的一次回滾。5.3 換源時(shí)的檢查清單換源之前先問四個(gè)問題投影是不是同一套、原點(diǎn)是不是同一套符號(hào)系統(tǒng)、級(jí)別范圍有沒有交集、比例尺集合能不能逐級(jí)對(duì)上。四個(gè)都能答上來再動(dòng)手改答不上來就先補(bǔ)參數(shù)。這個(gè)清單我用到現(xiàn)在還沒失效過后悔藥能少吃不少。檢查項(xiàng)判斷依據(jù)不通過的后果投影一致投影名稱與坐標(biāo)單位比例失真、拉伸軸方向一致行號(hào)遞增方向上下顛倒、錯(cuò)行級(jí)別有交集級(jí)別范圍重疊區(qū)間部分級(jí)別缺圖比例尺可對(duì)逐級(jí)數(shù)值比對(duì)中間級(jí)別偏移5.4 一個(gè)具體技巧用 0 級(jí)瓦片反推原點(diǎn)最后留一個(gè)我自己常用的技巧。不管參數(shù)從哪來先算 0 級(jí)瓦片的行列號(hào)整幅世界地圖在 0 級(jí)只有一張瓦片行列號(hào)必須是 0。如果算出來是別的數(shù)說明原點(diǎn)或者軸方向有問題如果算出來是 0 但地圖還是偏問題就在級(jí)別映射或者投影轉(zhuǎn)換上不在原點(diǎn)。這個(gè)檢查只用一行計(jì)算卻能第一時(shí)間把問題范圍砍掉一半。從那以后我每次接新圖源都強(qiáng)制先用 0 級(jí)瓦片驗(yàn)一遍原點(diǎn)再往下做別的希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取