iT邦幫忙

2023 iThome 鐵人賽

DAY 27
0
Mobile Development

Google Maps SDK for Android 與 GIS App 開發筆記系列 第 27

Day 27: Android 上的 WKT 空間資料格式介紹與使用

  • 分享至 

  • xImage
  •  

在台灣的政府公開資料中,許多地理資訊空間資料也常提供 WKT 的資料格式,因此今天帶大家來看一下 Android 上基本的 WKT 資料轉換。

介紹

Well-known Text (WKT) 是由開放地理空間協會 (OGC) 所訂定的純文字標記語言(text markup language),用來表示點、線、面等向量資料。

常見的 WKT 格式

WKT 格式是一段字串,最前面會有大寫單字說明此 WKT 的類型,後面則是由括號包裹 X, Y 或是經緯度的經度、緯度。

點:Point

地圖上的單點,以空格區分 X、Y。

POINT (120.556230978 24.29811730900007)

線:LineString

地圖上的線段,由 X、Y 組成單點,並以逗號 , 區分各組點位。

LINESTRING (30 10, 10 30, 40 40)

面:Polygon

地圖上的多邊形,由 X、Y 組成單點,並以逗號 , 區分各組點位,多點間以括號群組多邊形點位。

可能只有單組 Polygon,也有可能有中間中空的情況,這時候,會是多組的群組點位,同樣以, 分組,第一組為此多邊形最外圍的點位,後面的組別則為中空的點位。

POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))

多點:MultiPoint

地圖上的多點,由 X、Y 組成單點,並以括號分組、逗號 , 區分各組點位。

MULTIPOINT ((10 40), (40 30), (20 20), (30 10))

多線:MultiLineString

不相連的多組線段。

MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))

多面:MultiPolygon

多組以上的 Polygon,基本上就是前面的 Polygon 的變形,就是將前面 Polygon 的 WKT 去除前面的類型文字後,以括號逗號分組排列。

https://ithelp.ithome.com.tw/upload/images/20231010/20160271RVpqUvnR5j.png
圖片來源:維基百科

MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20)))

解析

基本上 WKT 的解析,可視為單純的字串拆解,所以在本文的範例,是挑較為複雜的多邊形 Polygon 來跟大家分享。

以下的程式碼,可以用來解析 MULTIPOLYGONPOLYGON 的 WKT,並將其轉成多個 PolygonDataPolygonData 內以 List<LatLng> 儲存外圍多邊形與中間空白的多邊形點位。

// 多邊形空洞
data class HoleList(
    var holes: List<LatLng> = listOf()
)

// 多邊形
data class PolygonData(
    var multiPolygon: List<LatLng> = listOf(),
    var holeLists: MutableList<HoleList> = mutableListOf()
)

// 解析
private fun parsePolygon(wktString: String?): List<PolygonData> {
    val areaList = mutableListOf<PolygonData>()
    if (wktString == null) return areaList
    var polygonObject: PolygonData
    val wktSpilt: Array<String>
    val wktStr: String
    if (wktString.startsWith("POLYGON")) {
        wktStr = wktString.replace("POLYGON", "")
        wktSpilt = wktStr.substring(1).split("\\|".toRegex()).toTypedArray()
    } else {
        wktStr = wktString.replace("MULTIPOLYGON", "")
        wktSpilt = wktStr.substring(1).split("\\)\\).\\(\\(".toRegex()).toTypedArray()
    }
    for (wkt in wktSpilt) {
        val boundList: MutableList<LatLng> = mutableListOf()
        // 內容有")("代表包含hole,第一組為區域範圍
        if (wkt.contains("),(")) {
            val coordinates = wkt.split("\\),\\(".toRegex()).toTypedArray()
            val bounds = coordinates[0].replace("(", "").replace(")", "").split(",".toRegex())
                .toTypedArray()
            for (bound in bounds) try {
                boundList.add(
                    LatLng(
                        bound.split(" ".toRegex()).toTypedArray()[1].toDouble(),
                        bound.split(" ".toRegex()).toTypedArray()[0].toDouble()
                    )
                )
            } catch (e: NumberFormatException) {
                Log.e(TAG, "Polygon parser error(bound) : $bound")
            }
            polygonObject = PolygonData(boundList)
            // 第一組以後全部為hole
            for (h in 1 until coordinates.size) {
                val latLngList = mutableListOf<LatLng>()
                val holes = coordinates[h].replace(")", "").split(",".toRegex()).toTypedArray()
                for (hole in holes) try {
                    latLngList.add(
                        LatLng(
                            hole.split(" ".toRegex()).toTypedArray()[1].toDouble(),
                            hole.split(" ".toRegex()).toTypedArray()[0].toDouble()
                        )
                    )
                } catch (e: NumberFormatException) {
                    Log.e(TAG, "Polygon parser error(hole) : $hole")
                }
                polygonObject.addHoles(HoleList(latLngList))
            }
        } else {
            val coordinates =
                wkt.replace("(", "").replace(")", "").split(",".toRegex()).toTypedArray()
            for (coordinate in coordinates) boundList.add(
                LatLng(
                    coordinate.split(" ".toRegex()).toTypedArray()[1].toDouble(),
                    coordinate.split(" ".toRegex()).toTypedArray()[0].toDouble()
                )
            )
            polygonObject = PolygonData(boundList)
        }
        areaList.add(polygonObject)
    }
    return areaList
}

範例

MULTIPOLYGON(((120.556230978 24.29811730900007,120.556173968 24.29787322400005,120.55588675 24.29782493100003,120.55580922 24.29818251100005,120.555868604 24.29819630200006,120.556230978 24.29811730900007)))

https://ithelp.ithome.com.tw/upload/images/20231010/20160271s27y8JMrYx.png

參考資料

小結

以上就是今天的內容,有任何問題歡迎留言討論~

明天見囉!!


上一篇
Day 26: Geofence 地理圍欄
下一篇
Day 28: 手機上的空間資料庫--Sptialite 介紹
系列文
Google Maps SDK for Android 與 GIS App 開發筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言