前面寫了幾篇博客介紹了Geotrellis的簡單使用,具體鏈接在文后,今天我主要介紹一下Geotrellis在數據處理的過程中需要注意的細節,或者一些簡單的經驗技巧以供參考。
一、直接操作本地Geotiff
如果不想將tiff數據切割成瓦片存放到集群中,也可以直接使用Geotrellis操作本地geotiff文件,可以直接使用SinglebandGeoTiff讀取單波段的tiff,使用MultibandGeoTiff讀取多波段tiff。
val geotiff = SinglebandGeoTiff("data/test.tif")
然后使用geotiff.tile就可以像處理普通瓦片那樣處理整幅tiff圖像。
二、Geotiff數據處理需要注意的細節
如果需要將geotiff數據切割並上傳到集群首先需要處理的是geotiff的數據類型、無數據值等元數據信息,即前期處理數據的時候需要將tiff文件處理到合適的情況以方便在程序中使用。
與數據類型和無數據值相關的屬性是Tile類的CellType,Geotrellis中定義了與各種類型相對應的CellType類型,具體在geotrellis.raster.CellType類中,當然程序中可以使用tile.convert(ShortConstantNoDataCellType)將瓦片的類型轉換為其他形式。
三、獲取瓦片編號或者瓦片的范圍(Extent)
將數據上傳到集群后,一般可以使用LayerReader將整層的瓦片信息全部讀出,
val r = reader.read[SpatialKey, Tile, TileLayerMetadata[SpatialKey]](layerId)
其中reader為LayerRander的實例,可以是AccumuloLayerReader等,具體看用戶將瓦片數據存放到什么位置,layerId是存放信息的實例,包含存放的layer名稱以及第幾層,然后就可以使用r.metadata.mapTransform函數獲取
瓦片的范圍或者瓦片的編號,如果該函數的參數是一個key(瓦片編號實例),結果就是瓦片的Extent,如果參數是一個point,算出來的就是包含該點的瓦片的key。
四、數據的重投影
程序中如果需要對tile進行點、線、面的相交取值等處理就必須使用與tile相同的投影方式,否則處理過程中會出現錯誤,可以使用ReProject首先對點、線、面進行重投影。
Reproject(geo, LatLng, WebMercator)
其中geo表示需要進行重投影的點、線、面,LatLng是原始投影方式,WebMercator是需要轉換到的投影方式。Geotrellis中定義了一個CRS類用於記錄投影信息。LatLng和WebMercator繼承了CRS類,是定義好的4326和3857投影方式,其他投影類型可以使用CRS類中提供的fromEpsgCode等方法進行設置。
五、獲取某個坐標點對應的值
如果我們想要獲取某個坐標點所對應的數據的值,有兩種方式,第一種是使用LayerReader先讀取整層瓦片信息,然后根據偏移得到改點的值,具體方法如下:
val r = reader.read[SpatialKey, Tile, TileLayerMetadata[SpatialKey]](layerId) val mapTransform = r.metadata.mapTransform val key = r.metadata.mapTransform(point) val dataValues: Seq[Double] = r.asRasters().lookup(key).map(_.getDoubleValueAtPoint(point)) val value = if(dataValues == null || dataValues.length <= 0) 0 else dataValues.head
其中reader和layerId的意義與上文相同,同樣key就是根據坐標點的偏移算出的瓦片坐標,然后在所有瓦片中查找此key並且獲取該坐標點的值,若多個瓦片均包含該坐標點會獲取多個值,取出第一個。
第二種方式是使用ValueReader直接找到包含改點的瓦片,然后根據偏移得到此點的數據,具體代碼如下:
val key = attributeStore.readMetadata[TileLayerMetadata[SpatialKey]](layerId).mapTransform(point) val (col, row) = attributeStore.readMetadata[TileLayerMetadata[SpatialKey]](layerId).toRasterExtent().mapToGrid(point) val tile: Tile = tileReader.reader[SpatialKey, Tile](layerId).read(key) val tileCol = col - key.col * tile.cols val tileRow = row - key.row * tile.rows println(s"tileCol=${tileCol} tileRow = ${tileRow}") tile.get(tileCol, tileRow)
其中attributeStore是元數據信息,與用戶數據上傳的位置有關,key是從元數據中根據坐標點偏移算出的瓦片編號,tilReader是ValueReader實例,col、row是根據偏移算出的坐標點在整個數據集中的像素偏移值,然后通過減去編號乘以瓦片像素個數來獲取相對當前瓦片的偏移,從而實現獲取當前坐標點的數據值。
兩種方式均能得到坐標點對應的值,但是其效率卻相差幾十倍,在我自己的測試中,使用ValueReader取到數據值大概需要20ms,而使用layerReader則大概需要6000ms,我猜測應當是使用LayerReader的方式會在所有瓦片中lookup,而ValueReader則直接獲取單個瓦片,所以效率存在差別。
六、結束語
本文簡單記錄了近期使用Geotrellis過程中遇到的一些問題,及其解決方案,目前項目只用到了柵格數據,所以只是針對Raster模塊,后續會探索其他模塊功能,並隨時將心得發布到博客園中,歡迎大家共同探討。
七、參考文獻
一、geotrellis使用初探
二、geotrellis使用(二)geotrellis-chatta-demo以及geotrellis框架數據讀取方式初探
三、geotrellis使用(三)geotrellis數據處理過程分析
四、geotrellis使用(四)geotrellis數據處理部分細節