昨天看完 WMTS 的圖層套疊,今天我們要來看離線 GIS 系統常用的 MBTiles 離線圖資。
就如同第二天在介紹 GIS 資料格式的文章中所說過的,MBTiles 其實是一個 SQLite 中包含圖磚檔案(Blob),與該圖磚對應的階層資訊。
不同圖台廠商發行的 MBTiles 規格略有不同,但基本上一定會有一張 tiles
的資料表,並包含以下欄位:
zoom_level
: 該圖磚的所屬層級。tile_column
: 圖磚於網格系統上的 x 軸位置tile_row
: 圖磚於網格系統上的 y 軸位置tile_data
: 圖磚 Blob參考資料
今天我們使用的範例檔案--臺灣通用電子地圖(不含等高線)MBTiles 檔,一樣是由國土測繪服務雲所提供的,檔名是 TaiwanEMap6.mbtiles
,請到這個連結下載。
為了讓各位看看他的格式,比較簡單的方法是下載這個專門讀取 SQLite 檔案的 DB Browser for SQLite。
安裝完畢並開啟檔案,就會發現裡面的 tiles
資料表,真的就如上面的格式說明,擁有那四個欄位。
如果想實際看看套疊出來的效果,也可以使用 QGIS 這套開源的 GIS 軟體來開啟 MBTiles。
跟 WMTS 套疊一樣,因為都是圖磚形式的呈現,所以使用的都是 TileProvider
這個類別。所以整體實作邏輯大同小異,差別在於原本從網路讀取的圖片,改由 Local MBTiles 讀出。而又因為 MBTiles 屬於 SQLite 格式,所以整個邏輯會是:
SQLiteDatabase
開啟 MBTilesTileProvider
,並在 getTile()
中執行 SQL Query 讀取出正確的檔案。將 MBTiles 讀入至 App 內屬於基本的檔案 IO,在範例中就不贅述,以下範例將重點放在取得檔案 SQLite 連線後,如何轉換為 Tile
。
SQLiteDatabase
讀取 MBTilesclass MBTileSQLiteDatabase(private val dbFilePath: String) {
private val database: SQLiteDatabase by lazy {
SQLiteDatabase.openDatabase(dbFilePath, null, SQLiteDatabase.OPEN_READONLY)
}
fun getTileBlob(x: Int, y: Int, zoom: Int): ByteArray? {
val sql =
"SELECT tile_data FROM tiles WHERE zoom_level = $zoom AND tile_column = $x AND tile_row = $y"
Log.d("TAG", "getTileBlob: $sql")
database.rawQuery(sql, null).use { cursor ->
if (cursor != null && cursor.count > 0) {
cursor.moveToFirst()
return cursor.getBlob(0)
}
}
return null
}
}
OfflineTileProvider
class OfflineTileProvider(private val database: MBTileSQLiteDatabase): TileProvider {
private val TAG: String = OfflineTileProvider::class.java.simpleName
override fun getTile(x: Int, y: Int, zoom: Int): Tile? {
// 國土測繪的圖資需要做這個 Y 軸的轉換才能正常顯示 不確定原因
val yMax = 1 shl zoom
val convertY = yMax - y - 1
val blob = try {
database.getTileBlob(x, convertY, zoom)
} catch (e: Exception) {
Log.e(TAG, "getTile: ", e)
null
}
return if (blob != null) {
Tile(256, 256, blob)
} else {
null
}
}
}
有關 Y 軸的轉換,查了 Google Maps 的 Sample 雖然有實作成功。但沒有很清楚其背後的原理,礙於文章長度與時間,先將以下幾篇查到的資料整理在下方,待有空再回來消化。
private fun setMbtilesTileOverlay(map: GoogleMap): TileOverlay? {
// 建立 MBTiles 來源
val database = MBTileSQLiteDatabase(application.filesDir.absolutePath + "/TaiwanEMap6.mbtiles")
val provider = OfflineTileProvider(database)
// 建立 MBTiles 圖層設定
val tileOverlayOptions =
TileOverlayOptions().tileProvider(provider)
// 將圖層加入到圖台上
return map.addTileOverlay(tileOverlayOptions)
}
以上就是 MBTiles 在 Google Maps SDK 上的基本套疊方式。因為都是圖磚的形式,實作的大方向上跟昨天的 WMTS 大同小異。
那就一樣明天見啦