iT邦幫忙

2023 iThome 鐵人賽

DAY 22
0
Mobile Development

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

Day 22: Maps SDK for Android Utility–Heat map 熱視圖

  • 分享至 

  • xImage
  •  

Heat Map 介紹

Heat Map (熱視圖)又可稱為熱圖,是一種在地圖上以顏色呈現資料的分布位置與強度的一種圖層,讓讀者可以一目了然地看見資料在空間上的分佈與變化。Heat Map 在資料視覺化的領域裡,算是十分常見的一種呈現方式。

舉例來說,下圖是 台灣動物路死觀察網的路殺紀錄熱點圖

https://ithelp.ithome.com.tw/upload/images/20231005/20160271znerr86yhg.png

從圖面來看,我們大概可以知道,哺乳類動物的路殺事件,主要集中在大台北地區與雲嘉高一帶。

資料格式

在 Google Maps 上建立 Heat Map,所需的資料很簡單。最基本的只要有經緯度即可,頂多再多一個數值作為權重值。

範例資料

找了好久終於讓我找到一個比較合適拿來玩的經緯度資料,那就是 ctiml/convenience-store-data 的全台超商資料。雖然資料的最後更新時間已經是八年前了,但作為測試用的資料綽綽有餘啦~

https://ithelp.ithome.com.tw/upload/images/20231005/20160271dm3VzRd7zS.png

文章中的練習,將用 7-11 台中市的資料。

實作

在 Google Maps 上加入 Heat Map,整體的邏輯大致上是:

  1. 建立 HeatmapTileProvider 並傳遞資料的 LatLng
  2. 建立 TileOverlayOptions 並將前面建立的物件設為 tileProvider
  3. 將建立好的 TileOverlayOptions 加入到 GoogleMap

1. 讀入原始資料

private fun readStoreDataFromAssets(): List<StoreData> {
    val resultList = mutableListOf<StoreData>()
    try {
        val inputStream = resources.assets.open("taichung_7_11.json")
        val rawJson = JSONObject(BufferedReader(InputStreamReader(inputStream)).readText())
        val storesJsonArray = rawJson.getJSONArray("stores")
        for (i in 0 until storesJsonArray.length()) {
            val storeJson = storesJsonArray.getJSONObject(i)
            val lng = storeJson.getDouble("X")
            val lat = storeJson.getDouble("Y")
            val isIceCream = (storeJson.getString("isIceCream") == "Y")
            resultList.add(StoreData(lng, lat, isIceCream))
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }
    return resultList
}

2. 轉換成經緯度資料集並建立 HeatmapTileProvider

一般經緯度

private fun createHeatmapTileProvider(storeDataList: List<StoreData>): HeatmapTileProvider {
    // 原始資料轉經緯度資料
    val latLngList = mutableListOf<LatLng>()
    storeDataList.forEach {
        latLngList.add(LatLng(it.lat, it.lng))
    }
    
    // 建立 HeatmapTileProvider
    return HeatmapTileProvider.Builder().data(latLngList).build()
}

加權經緯度

權重值代表的是該位置的重要性,數值越大,圖層的漸層色彩強度就越高,若以預設的顏色來說就是紅色。

如果要替每個點位加上不同的權重值,要改用 HeatmapTileProvider.Builder().weightedData(dataList),並傳入 WeightedLatLng 資料集。

// 有冰淇淋的店加重權重
private fun createWeightedHeatmapTileProvider(storeDataList: List<StoreData>): HeatmapTileProvider {
    val weightedLatLngs = mutableListOf<WeightedLatLng>()
    storeDataList.forEach {
        val intensity: Double = if (it.isIceCream) {
            2
        } else {
            0.5
        }
        weightedLatLngs.add(WeightedLatLng(LatLng(it.lat, it.lng), intensity))
    }
    return HeatmapTileProvider.Builder().weightedData(weightedLatLngs).build()
}

4. 加入至地圖

map.addTileOverlay(TileOverlayOptions().tileProvider(heatmapTileProvider))

成果展示:一般經緯度

https://ithelp.ithome.com.tw/upload/images/20231005/201602716NhoeszJMy.png

成果展示:加權經緯度

https://ithelp.ithome.com.tw/upload/images/20231005/201602717lLPwunsvz.png

5. 自訂外觀

要變更 Heat Map 的外觀,可以在 Builder 階段呼叫方法設定,也可以在建立好 HeatmapTileProvider 後呼叫相關的 setter 方法來變更樣式,並接著呼叫 clearTileCache() 清除快取強制地圖重新繪製。

半徑

預設值為 20,單位為 px,值必須介於 10 ~ 50 之間。

HeatmapTileProvider.Builder().radius()

// 或者
HeatmapTileProvider.setRadius()

漸層

Gradient 用來設定 Heat Map 使用的色彩範圍,從低強度到最高強度。

設定色彩會使用兩個陣列,一個用來指定色彩(rgb),一個用來指出個顏色起始點的浮點陣列,代表占最高強度顏色的百分比 (數值範圍 0 ~ 1)。

設定方法如下:

  1. 建立色彩陣列與其對應的強度百分比陣列,最後用這兩個陣列建立 Gradient 物件。
  2. HeatmapTileProvider.Builder() 階段呼叫 HeatmapTileProvider.Builder().gradient() 方法傳入參數。或是在 HeatmapTileProvider 建立好後,呼叫 HeatmapTileProvider.setGradient() 更新色彩設定。
// 漸層的顏色
val colors = intArrayOf(
    Color.rgb(102, 225, 0),  // 綠色
    Color.rgb(255, 0, 0) // 紅色
)
// 各漸層顏色開始的百分比
val startPoints = floatArrayOf(0.2f, 1f)
// 建立 Gradient
val gradient = Gradient(colors, startPoints)


// 在建立 TileProvider 時傳入色彩設定
val provider = HeatmapTileProvider.Builder()
    .data(latLngs)
    .gradient(gradient)
    .build()

不透明度

呼叫 HeatmapTileProvider.Builder().opacity() 或是 HeatmapTileProvider.setOpacity() 更新整個圖層的透明度,其值預設為 0.7,可設定的範圍為 0 ~ 1。

// 設定不透明度
provider.setOpacity(0.3)
// 呼叫圖層清除快取重繪
tileOverlay?.clearTileCache()

綜合範例

private fun updateHeatmapStyle(heatmapTileProvider: HeatmapTileProvider) {
        // 設定半徑
        heatmapTileProvider.setRadius(50)
        // 設定透明度
        heatmapTileProvider.setOpacity(0.5)
        // 設定漸層
        val colorIntArray = intArrayOf(
            // lightskyblue
            Color.rgb(135, 206, 250),
            // mediumblue
            Color.rgb(0, 0, 205)
        )

        val percentFloatArray = floatArrayOf(
            0.2f, 1f
        )
        heatmapTileProvider.setGradient(Gradient(colorIntArray, percentFloatArray))
    }

https://ithelp.ithome.com.tw/upload/images/20231005/20160271kJGv2MPit9.png

6. 更新資料集

要新增、更新、刪除已建立的 Heat map 資料點,可以使用以下方法。

// 更新帶有權重值的版本
val weightedData: List<WeightedLatLng> = ArrayList()
provider.setWeightedData(weightedData)

// 更新一般版本
val data: List<LatLng> = ArrayList()
provider.setData(data)

// 呼叫圖層清除快取重繪
tileOverlay?.clearTileCache()

7. 移除地圖上的 Heat Map

因為實際上是以 TileOverlay 的形式加入到地圖上,所以移除方法跟 TileOverlay 一樣。

tileOverlay?.remove()

參考資料

小結

以上就是今天的 Heat map 介紹,大家如果還想試試其他的資料,可以參考上方的Google Maps API 學習筆記 – 3:用熱圖 / Heat map 製作全台 12 小時雨量分佈圖這篇文章,串中央氣象署的雨量分布資料來玩玩看。

如果串接上遇到問題歡迎留言討論喔~

那就明天見啦~/images/emoticon/emoticon08.gif


上一篇
Day 21: Maps SDK for Android Utility–KML 套疊
下一篇
Day 23: Maps SDK for Android Utility–Marker Clustering 標記叢集
系列文
Google Maps SDK for Android 與 GIS App 開發筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言