有一個 TMS 的瓦片資料源,需要“模擬”一個 WMTS 服務出來,需要怎么做?
這個情況,其實有現成的基礎設施或者說輪子來解決,比如各個地圖服務器等,.net生態也有 tile-map-service-net5這種開源工具,這個問題之所以是個問題在于兩個限制條件,
- 所用客戶端不支持加載XYZ/TMS格式的資料,只能加載 WMS 和 WMTS 格式的資料,
- 使用的資料是切好片的 TMS 結構的資料,
- 客戶端不方便依賴外部地圖服務器,
模仿資源鏈接
一些我們熟悉的互聯網地圖,用的都是 XYZ 或者 TMS的方式,例如OSM、Google Map 和Mapbox 等等,從之前的柵格瓦片到如今矢量瓦片更為常見,想要用TMS “模仿” WMTS 的請求格式,需要先了解他們直接有啥不一樣,
XYZ(slippy map tilename)
- 256*256 像素的圖片
- 每個 Zoom 層級是一個檔案夾,每個Column 是個子檔案夾,每個瓦片是一個用 Row 命名的圖片檔案
- 格式類似
/zoom/x/y.png - x 在 (
180°W ~ 180°E),y 在(85.0511°N ~85.0551°S),Y軸從頂部向下,
可以從Openlayers TileDebug Example,看到一個簡單的 XYZ 瓦片的示例,
TMS
TMS的 Wiki wikipedia沒涉及什么細節、osgeo-specification
只描述了協議的一些應用細節,反倒是 geoserver docs 關于 TMS 的部分寫的更務實一些, TMS 是 WMTS 的前身,也是 OSGeo 制定的標準,
請求形如:
http://host-name/tms/1.0.0/layer-name/0/0/0.png
為了支持多種檔案格式和空間參考系統,也可以指定多個引數:
http://host-name/tms/1.0.0/layer-name@griset-id@format-extension/z/x/y
TMS 標準的瓦片格網從左下角開始,Y軸從底部向上,有的地圖服務器,例如geoserver,就支持一個額外的引數flipY=true 來翻轉 Y 坐標,這樣就可以兼容Y 軸從頂部向下的服務型別,比如 WMTS 和 XYZ,

WMTS
WMTS 相較上述兩個直觀的協議,內容更復雜,支持的場景也更多,2010 年由OGC第一次公布,起始在此之前,1997年 Allan Doyle的論文“Www mapping framework” 之后,OGC就開始謀劃網路地圖相關標準的制定了,在 WMTS 之前,最早的,也是應用最廣泛的網路地圖服務標準是 WMS,因為WMS每個請求是依據用戶地圖縮放級別和螢屏大小來組織地圖回應,這些回應大小各異,在多核CPU還沒那么普及的當年,這種按需實時生成地圖的方式非常奢侈, 同時想要提升回應速度非常困難,于是有開發者開始嘗試預先生成瓦片的方式,于是涌現出了許多方案,前面提到的 TMS 就是其中的一個,后面WMTS 應運而生,開始被廣泛應用, WMTS 支持鍵值對(kvp)和 Restful 的方式對請求引數編碼,
KVP 形如:
<baseUrl>/layer=<full layer name>&style={style}&tilematrixset={TileMatrixSet}}&Service=WMTS&Request=GetTile&Version=1.0.0&Format=<imageFormat>&TileMatrix={TileMatrix}&TileCol={TileCol}&TileRow={TileRow}
Restful形如:
<baseUrl>/<full layer name>/{style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}?format=<imageFormat>
由于是柵格瓦片,這里只需要找到 XYZ 與 瓦片矩陣和瓦片行列號的對應關系就好了,
- TileMatrix
- TileRow
- TileCol

這里的瓦片行列號是從左上角開始的,Y 軸從頂部向下,
這樣,就找到了 TMS 與 WMTS 的各引數對應關系,接下來就是如何把 TMS 轉換成 WMTS 的請求了,如下:
- TileRow = 2^zoom - 1 - y = (1 << zoom) - 1 - y
- TileCol = x
- TileMatrix = zoom
在不考慮其他空間參考的情況下,縮放層級對應瓦片矩陣,x對應瓦片列號,y取反(因為起始方向相反),
模擬一個 WMTS Capabilities 描述檔案
WMTS規范的要求,幾乎可以說是細到頭發絲,所以各個客戶端,不管是Web端的 Openlayers ,還是桌面端的QGIS或Skyline等,都支持直接決議Capabilities 描述檔案,然后根據描述檔案的內容來選擇圖層、樣式和空間參考,所以我們這里還要模擬一個 WMTS Capabilities 描述檔案出來,
Capabilities 描述檔案的構成
一個WMTS Capabilities描述檔案的例子可以在opengis schema,天地圖山東找到,
Capabilities 描述檔案的內容非常多,這里只列出一些重要的部分(忽略標題,聯系方式等):
OperationsMetadata:
- GetCapabilities >> 獲取 Capabilities 描述檔案的方式
- GetTile >> 獲取瓦片的方式
Contents:
- Layer
- boundingBox >> 圖層的經緯度范圍
- Style
- TileMatrixSetLink >> 圖層支持的空間參考
- TileMatrixSet >> 空間參考
- TileMatrixSetLimits >> 空間參考的縮放層級范圍
- TileMatrixLimits >> 每個縮放層級的瓦片行列號范圍
- Style
- TileMatrixSet
- TileMatrix
關鍵的部分就是 boundingBox、TileMatrixSetLimits、TileMatrixLimits ,只需要根據圖層的空間參考和縮放層級來計算出來就好了,
boundingBox 的計算比較簡單,就是圖層的經緯度范圍,這里就不展開了,
TileMatrixSetLimits 的計算比較簡單,就是圖層的空間參考的縮放層級范圍,
TileMatrixLimits 的計算比較復雜,可以只在圖層范圍比較小的時候再弄,全球地圖就沒必要了,需要根據圖層的空間參考和縮放層級來計算出來,下面是一段偽代碼(4326 到 3857),
FUNCTION GetTileRange(minLon, maxLon, minLat, maxLat, zoom, tile_size = 256)
minLonRad = minLon * PI / 180
maxLonRad = maxLon * PI / 180
minLatRad = minLat * PI / 180
maxLatRad = maxLat * PI / 180
tile_min_x = Floor((minLonRad + PI) / (2 * PI) * Pow(2, zoom))
tile_max_x = Floor((maxLonRad + PI) / (2 * PI) * Pow(2, zoom))
tile_min_y = Floor((PI - Log(Tan(minLatRad) + 1 / Cos(minLatRad))) / (2 * PI) * Pow(2, zoom))
tile_max_y = Floor((PI - Log(Tan(maxLatRad) + 1 / Cos(maxLatRad))) / (2 * PI) * Pow(2, zoom))
// adjust tile range based on tile size
tile_min_x = Floor((double)tile_min_x * tile_size / 256)
tile_max_x = Ceiling((double)tile_max_x * tile_size / 256)
tile_min_y = Floor((double)tile_min_y * tile_size / 256)
tile_max_y = Ceiling((double)tile_max_y * tile_size / 256)
RETURN (tile_min_x, tile_max_x, tile_min_y, tile_max_y)
生成 WMTS Capabilities 描述檔案
生成一個最小化的 WMTS Capabilities 描述檔案,把上面的關鍵部分填充上,之后構造一個指向標準描述檔案地址的的 Restful 風格的 URL,
后話
以上是一個簡單的 TMS 轉 WMTS 的思路,實際上還有很多細節需要考慮,比如空間參考的轉換,縮放層級的轉換,瓦片行列號的轉換,瓦片的格式轉換等等,
期間也踩了一些坑,感覺這部分更有意思,
第一部分,很快就參考 tile-map-service-net5 的思路,完成了 y >> tileRow的轉換,代碼在WebMercator.cs ,其實在StackOverflow上也有人問過這個問題,是有答案的,但我還是選擇從軟體里找答案,因為這樣自己心里更踏實,
第二部分就很頭大,首先模擬出了資源鏈接,構建了一個簡單的 XML,但是在目標客戶端上不能直接加載,很直接的想到了通過標準服務測驗一下,然后哪來一個Capabilities 描述檔案來修改,己想首先在比較熟悉的Openlayers上測驗,然后再去修改Capabilities 描述檔案,Openlayers的加載方式還是很靈活的,在沒有Capabilities 描述檔案的情況下,可以直接通過配置引數訪問,
// fetch the WMTS Capabilities parse to the capabilities
const options = optionsFromCapabilities(capabilities, {
layer: 'nurc:Pk50095',
matrixSet: 'EPSG:900913',
format: 'image/png',
style: 'default',
});
const wmts_layer =new TileLayer({
opacity: 1,
source: new WMTS(options),
})
很遺憾,瓦片沒有加載上,甚至networks里沒有發送請求,于是又去另一個WMTS相關的例子哪里,自定義了一個 TileGrid,然后把瓦片的行列號轉換成了3857的行列號,這時候可以加載了,
const projection = getProjection('EPSG:3857');
const projectionExtent = projection.getExtent();
const size = getWidth(projectionExtent) / 256;
const resolutions = new Array(31);
const matrixIds = new Array(31);
for (let z = 0; z < 31; ++z) {
// generate resolutions and matrixIds arrays for this WMTS
resolutions[z] = size / Math.pow(2, z);
matrixIds[z] = `EPSG:900913:${z}`;
}
var wmtsTileGrid = new WMTSTileGrid({
origin: getTopLeft(projectionExtent), resolutions: resolutions, matrixIds: matrixIds,
})
在確認了是 TileGrid 的問題之后,首先將自己生成的TileGrid與Openlayers從Capabilities決議出來的TileGrid進行對比,發現自己生成的TileGrid有一些欄位是空的,于是挨個測驗,最后發現設定fullTileRanges_和extent_兩個內部引數為空時,影像可以加載,
去翻OL原始碼,發現fullTileRanges_和extent_在 getFullTileRange中被用到,
也就是說,當fullTileRanges_和extent_為空時,getFullTileRange會回傳一個空的范圍,
而getFullTileRange在withinExtentAndZ中用到了,這里是用來判斷當前可視區域是否有該圖層的瓦片,
也就是說,當fullTileRanges_和extent_為空時,獲取不到 TileRange,withinExtentAndZ會一直回傳true,這樣就會一直加載瓦片了,也就是加載成功的原因,
相反,從Capabilities決議出來的fullTileRanges_和extent_指向了錯誤的TileRange,導致withinExtentAndZ一直回傳false,這樣就不會加載瓦片了,也就是加載失敗的原因,
終于找到了原因,但這里又被騙了,在wmts.js,建構式上有一行注釋:
class WMTS extends TileImage {
/**
* @param {Options} options WMTS options.
*/
constructor(options) {
// TODO: add support for TileMatrixLimits
}
}
這使我開始的時候誤以為,fullTileRanges_和extent_是根據經緯度范圍(boundingBox)計算出來的,而不是根據TileMatrixLimits算的,于是乎又檢查了一遍boundingBox,確認無誤后,才開始著手修改TileMatrixLimits,
開始的時候,以為 TileMatrixLimits 是每個層級的瓦片范圍,而不是圖層的范圍,所以沒注意到這個引數,這才走了彎路,
寫在 2023 年,WMTS 已經不是一個新的協議了,OGC Tile API 已經成為正式標準了,自己對WMTS了解還是半瓶水,真是汗顏??,
站外博客地址:https://blog.yuhang.ch轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/547206.html
標籤:GIS
上一篇:Vue框架快速上手
下一篇:從TMS逆向到 WMTS
