geotrellis使用(四十)優雅的處理請求超過最大層級數據

前言

要說清楚這個題目對我來說可能都不是一件簡單的事情,我簡單嘗試。

研究 GIS 的人應該都清楚在 GIS 中最常用的技術是瓦片技術,無論是傳統的柵格瓦片還是比較新穎的矢量瓦片,一旦將數據切好瓦片就會造成其層級固定,假如說 0 - 11 級,請求此層級範圍內數據的時候能夠正常響應,但是當用戶請求超過最高級(假如爲 12 )的時候該如何處理呢?傳統方式只能返回 404 ,即顯示空白數據,然而有沒有更好的方式呢,能夠使得用戶在請求超過最高級數據的時候能夠優雅的並且正確的返回數據而不是直接 404。這個問題可以用手動擋汽車和自動擋汽車進行類比,傳統方式就像手動擋,最大隻能到 5 檔,而現在的需求是希望擋位能變的更高些。但願我已經清晰的說明了此問題,本文對此原理和實現方案進行簡單探討。

一、實現

1.1 原理分析

這個解決方案倒是很容易想象,當超過最大層級(以下簡稱 zoom)的時候(> 11 級)我們只需要讀出最大 zoom(11 級)的此範圍內數據對應的瓦片,然後將此瓦片根據此範圍進行切割並重新採樣到 256 * 256 即可。

這裏面涉及到了瓦片的金字塔體系的一些常用概念。首先層級越大表示分辨率越高,即顯示出來的數據越清晰,每提高一層數據量增加4倍,即一個低層級的瓦片包含了比他高一層級的四個瓦片,整個看下來便像一個金字塔一樣;而常用的每個瓦片的大小爲 256 * 256,直白的說就是一個 256 * 256 的 PNG 或者 JPG 圖片,當然也可以是其他尺寸,每個瓦片對應一個 x、y、z 編號,x、y 代表瓦片的行列號,z 代表瓦片的 zoom,屏幕範圍內數據所有瓦片按照 x、y、z 正常排列顯示出來便得到了整個地圖(或者其他數據,如遙感等),就像房頂的瓦片一樣,所以稱爲瓦片技術。有關具體技術和描述可以百度之。

1.2 實現方案

有了上面的分析,其實這件事情應該已經不困難了。

1.2.1 層級

首先獲取當前數據的最大層級並判斷當前請求是否大於此層級。

def getMaxZoom(layerName: String) =
    attributeStore.layerIds
      .groupBy(_.name)(layerName)
      .map(_.zoom)
      .max
      
def exist(layerId: LayerId) = 
    attributeStore.layerExists(layerId)

第一個函數取到當前 layerName 數據的最大層級,其中 attributeStore 爲你當前 backend 的後臺,可以參考此前文章。

exist 函數判斷當前 layerId 是否存在, layerId 包含 name 信息和 zoom 信息。當然此處你可以直接判斷此 layerId 的 zoom 是否大於第一個函數取到的 maxZoom,但是此處我這麼寫也是埋下一個伏筆,會在後文介紹。

1.2.2 取到請求瓦片的範圍

想要取到最大層的數據首先要取到瓦片包含數據的範圍,這個範圍我們只能根據所請求瓦片的 z、y、z 獲得,如下:

val layerId: LayerId = LayerId(name, maxZoom)
val rmd = attributeStore.read[TileLayerMetadata[SpatialKey]](layerId, Fields.metadata)
val layoutLevel = ZoomedLayoutScheme(rmd.crs).levelForZoom(rmd.extent, z)
val mapTransform = MapKeyTransform(rmd.crs, layoutLevel.layout.layoutCols, layoutLevel.layout.layoutRows)
val targetExtent = mapTransform(x, y)

首先取到 maxZoom 層的元數據,根據投影(rmd.crs)、範圍(rmd.extent)及 zoom 信息,獲取到當前 z 層的 layout,這個具體細節涉及到金字塔理論,大意是根據投影、範圍和層級就可以取到瓦片的編號和範圍情況,最終也正是根據 x、y 計算出瓦片數據範圍 targetExtent。

1.2.3 取到最大層級對應瓦片

有了瓦片的範圍,我們就可以在最大曾中取出此瓦片,如下:

val GridBounds(nx, ny, _, _) = rmd.mapTransform(targetExtent)
val sourceExtent = rmd.mapTransform(nx, ny)
val maxZoomTile = tileReader.reader[SpatialKey, Tile](layerId).read(SpatialKey(nx, ny))

tileReader 是 ValueReader 對象,同樣與所採用的 backend 有關。其中 nx、ny 正是 maxZoom 層對應的瓦片編號,此處同樣用到金字塔理論,高層級的瓦片必然包含在比他層級低的某一個瓦片裏,即 sourceExtent 必然能夠完全覆蓋 targetExtent。

1.2.4 將瓦片重採樣到所請求的 zoom

現在只需要我們對獲取到的瓦片進行裁切並重採樣到 256 * 256 即可,如下:

val targetTile = sourceTile.resample(sourceExtent, RasterExtent(targetExtent, 256, 256))

這樣就獲取到了最終的請求瓦片,將此瓦片返回瀏覽器等其他請求源即可。

1.3 效果

展示一下最終效果:

11 級

12 級

11 級瓦片是正常取得的瓦片,12 級瓦片即爲通過此種方式由 11 級瓦片重採樣得到的。

二、進一步思考

做產品和做項目有着本質的區別,一個項目可能只需要考慮到通用情況即可,而產品則必須考慮到方方面面,還記得我在上面留的伏筆嗎,在那裏我沒有采用判斷所請求 zoom 是否大於 maxZoom 的方式,而是直接判斷 exist,這裏面有個邏輯問題。假如切瓦片的時候不是每一層都有切到,必然我切了 0 - 5、7 - 11,而沒有切第 6 層,那麼採用這種方式肯定是有問題的,並且出現這種情況的時候整個邏輯都需要重新修改,因爲第 6 層的某個瓦片肯定包含了2 ^ (2 ^ 5) 個 11 層(maxZoom)的瓦片,這樣我們就不能簡單的只取出一個,而應該將其全部取出並進行拼接然後再重採樣。

再進一步思考,碰到這種方式的時候我們是不是可以取出第 5 層或者第 7 層的某個/些瓦片而不是非要 maxZoom 層的,因爲接近的層數據更相似(此處牽扯到層級可視化表達的問題)。

所以這些都需要我們豐富和設計好邏輯,只有這些都考慮清楚才能設計出完美的產品。具體代碼此處就不放出了,如果有需要可以探討。

三、總結

本文介紹瞭如何在所請求的瓦片層級不存在的情況時通過取出最大層或者相近層的瓦片並進行重採樣操作,從而優雅的返回瓦片數據。

Geotrellis系列文章鏈接地址http://www.cnblogs.com/shoufengwei/p/5619419.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章