記一次 CesiumJS 中非 4326/3857 WMTS 資料的載入

嶺南燈火發表於2023-03-01

CesiumJS 能用的 WMTS 目前只支援兩種切片方案(TilingScheme):

  • 0 級瓦片有 2 個的 GeographicTilingScheme
  • 0 級瓦片只有 1 個的 WebMercatorTilingScheme

光說很抽象,上圖:

geographic-scheme.png

web-mercator-scheme.png

0 級瓦片有 2 個的投影,是直接以經緯度數值展平成平面,眾所周知:

$$ 緯度跨度:經度跨度 = 180:360 = 1:2 $$

所以 GeographicTilingScheme 的樣子就是一個一比二的 矩形,剛好就在 0 級瓦片時有兩個,後續就按常規的四叉樹切分即可。而 WebMercator 投影后的座標系 xy 值域是 $[-20037508.34,20037508.34]^2$,是一個 正方形,所以可以按單個 0 級瓦片進行四叉樹切分。

以上,是技術前提,CesiumJS 只能支援這兩種切分方案,也就是說,我國常用的其他投影方法,例如高斯投影、蘭伯特投影等是不支援的,主要是形狀不太滿足構造四叉樹瓦片。

既有 WMTS 的現狀

需求是這樣的,這一份 WMTS 的起切等級並不是 0 級,透過觀察能力文件,我可以得出如下幾個結論:

  • 它是自定義切片方案,但滿足 GeographicTilingScheme 方案的形狀,是 1:2 的矩形
  • 它的 0 級瓦片與 EPSG:4326 的 0 級瓦片不同
  • 它的 0 級瓦片解析度與 EPSG:4326 的 9 級瓦片相同
  • 它的 0 級瓦片共有 1024 列 × 512 行,而 EPSG:4326 是 2 列 × 1 行,是 4326 的 $512^2$ 倍

如果讀者足夠敏銳,可以得知這個 WMTS 的起切瓦片實際上幾乎就是 4326 的第 9 級瓦片:

<TileMatrixLimits>
    <TileMatrix>EPSG:4490:0</TileMatrix>
    <MinTileRow>190</MinTileRow>
    <MaxTileRow>192</MaxTileRow>
    <MinTileCol>835</MinTileCol>
    <MaxTileCol>838</MaxTileCol>
</TileMatrixLimits>eMatrix>

比對廣東省省界資料的 4326 座標系第 9 級瓦片陣(TileMatrix)定義:

<TileMatrixLimits>
    <TileMatrix>EPSG:4326:9</TileMatrix>
    <MinTileRow>183</MinTileRow>
    <MaxTileRow>198</MaxTileRow>
    <MinTileCol>823</MinTileCol>
    <MaxTileCol>845</MaxTileCol>
</TileMatrixLimits>

最大最小行列號略有差別,是因為資料所跨的範圍略有不同,前者範圍較後者(廣東省省界)小。

我只能說比較慶幸,這樣的 WMTS 基本還是可以滿足載入要求的,現有 API 可以滿足載入調整。

兵來將擋水來土掩 - 問題解決

先給程式碼,然後解釋:

const provider = new WebMapTileServiceImageryProvider({
  url: new Resource({
    url: 'http://127.0.0.1/server/wmts', // 簡寫 url,會意即可
    headers: {
      'tk': 'aaaaaaaaa', // 資料保密,需要傳遞 token
    }
  }),
  tileMatrixSetID: 'EPSG:4490',
  format: 'image/png',
  tileMatrixLabels: Object.keys(new Array(11).fill(0)).map(v => `EPSG:4490:${v}`),
  layer: 'demo',
  rectangle: Rectangle.fromDegrees(113.75549, 22.383494, 114.662777, 22.888641),
  style: "",
  tilingScheme: new GeographicTilingScheme({
    numberOfLevelZeroTilesX: 1024,
    numberOfLevelZeroTilesY: 512,
  })
})
viewer.imageryLayers.addImageryProvider(provider)

url 引數

WebMapTileServiceImageryProviderurl 可以是兩種型別的值:stringResource,這個 WMTS 資料需要在所有請求頭中加上訪問令牌(token),所以我選擇建立一個 Resource 物件來傳遞 token。

tileMatrixSetID 和 layer 引數

這裡設為 EPSG:4490,指的是能力文件中該圖層(layer: 'demo')下的 <TileMatrixSetLink></TileMatrixSetLink> 的瓦片陣集ID(TileMatrixSetID):

<TileMatrixSetLink>
    <TileMatrixSet>EPSG:4490</TileMatrixSet>
    <TileMatrixSetLimits><!-- ... --></TileMatrixSetLimits>
</TileMatrixSetLink>

tileMatrixLabels 引數

這個沒什麼好說的,就是每一層瓦片陣的訪問標籤,我這裡使用 JavaScript 的陣列語法糖快速生成了 11 級(0到10,共11層)瓦片陣的 ID,即:

Object.keys(new Array(11).fill(0)).map(v => `EPSG:4490:${v}`)

// ["EPSG:4490:0", "EPSG:4490:1", ..., "EPSG:4490:10"]

這樣,網路請求瓦片的連結:

http://127.0.0.1/server/wmts?tilematrix=EPSG%3A4490%3A1&layer=ZT%3ATDYT&style=&tilerow=382&tilecol=1671&tilematrixset=EPSG%3A4490&format=image%2Fpng&service=WMTS&version=1.0.0&request=GetTile

中的 tilematrix 引數的值:EPSG:4490:1 就是正確的了。tileMatrixLabels 陣列預設是數字 0 ~ 最大等級,如果圖層在能力文件中定義的 tilematrix 的 ID 不是 0 ~ 最大等級的數字的話,就需要這樣構造一個陣列來告訴 CesiumJS,要以什麼樣的 tilematrix 發出請求。

rectangle 引數

加上這個引數,就可以最大最佳化 WMTS 的請求效能,如果不加這個引數限制請求範圍,就會取相機視角下的所有篩選到的瓦片,這對這個例子十分有效,主要還是要加上最後一個引數:

tilingScheme 引數

上文已經提及,這個 WMTS 的切片方案與 4326 的是幾乎一致的,只不過其 0 級相當於 4326 的 9 級。

那麼,當 CesiumJS 發出第 0 級瓦片請求時,預設的 TilingScheme 是隻有 1 行 2 列的瓦片的,但是在能力文件中,我注意到了一個定義:

<Contents>
    <TileMatrixSet>
        <ows:Identifier>EPSG:4490</ows:Identifier>
        <ows:SupportedCRS>urn:ogc:def:crs:EPSG::4490</ows:SupportedCRS>
        <TileMatrix>
            <ows:Identifier>EPSG:4490:0</ows:Identifier>
            <ScaleDenominator>545978.7734655447</ScaleDenominator>
            <TopLeftCorner>90.0 -180.0</TopLeftCorner>
            <TileWidth>256</TileWidth>
            <TileHeight>256</TileHeight>
            <MatrixWidth>1024</MatrixWidth>
            <MatrixHeight>512</MatrixHeight>
        </TileMatrix>
        <!-- ... -->
    <TileMatrixSet>
</Contents>

這是這個 WMTS 服務下的 EPSG:4490 的瓦片陣集(TileMatrixSet)的第 0 個瓦片陣(TileMatrix)的定義,這裡定義了幾個比較重要的引數:

  • Identifier:瓦片陣的 ID
  • ScaleDenominator:比例(分母)
  • TileWidth / TileHeight:該瓦片陣的瓦片的畫素寬高
  • MatrixWidth / MatrixHeight:該瓦片陣的瓦片行列數

要用於前端的就是最後一個,行列數:

new GeographicTilingScheme({
  numberOfLevelZeroTilesX: 1024,
  numberOfLevelZeroTilesY: 512,
})

這樣就能在 CesiumJS 發出第 0 級瓦片請求時,行列號能與 WMTS 的瓦片行列號對應上了,TilingScheme API 的這兩個引數就是這麼個用法,前提是切片的形狀滿足 CesiumJS 支援的這兩個:GeographicTilingSchemeWebMercatorTilingScheme

小結

透過這次實踐,我又進一步學習了老舊但是又不得不用的 WMTS 規範,以及這種非標準 4326、3857 切片資料的載入細節,那就是精確地根據能力文件中各項引數(瓦片陣集的選擇、瓦片陣的範圍等),配合 JsAPI 控制發出準確的請求。

如果真遇上那種不滿足 CesiumJS 這倆切片方案的,估計就難搞了,水平有限。

相關文章