iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 23
0
Modern Web

《你的地圖會說話? WebGIS與JavaScript的情感交織》系列 第 23

[8-2] heatmap.js 熱區- 以Leaflet地圖實作

本系列文章為Leaflet Plugins介紹:
[8-1] 展點多到爆?那就試試看 Leaflet MarkerCluster吧!


上篇文章介紹了Leaflet Plugins的MarkerCluster群聚,
今天要來介紹另個也是很有趣的功能,熱區地圖。

空間分析(Spatial Analysis),是GIS中的核心概念之一。
最早最著名的空間分析,要從19世紀的英國倫敦說起。
西元1854年8月31日,倫敦霍亂爆發事件,在短短三天內造成127人死亡,至9月10日時,死亡人數已超過500人。在那個細菌尚未被人們發現的年代,人們不知道霍亂的傳染途徑為何。

當時的內科醫生John Snow懷疑霍亂是依據「水」為傳播途徑,於是他把倫敦街頭的公共水泵畫在地圖上,並把感染者從住家到公共水泵的路線及活動範圍圈出來,並用點子圖呈現,最終發現感染途徑為以公共水泵為中心向外擴散,成為空間分析最早具有「熱區」概念的地圖。
後來當局採用了醫生John Snow的說法,拆除了水泵閥,使得霍亂得以控制。
John Snow的貢獻也為流行病學及空間地理學有重要的開端。

heatmap.js

今天使用的套件為heatmap.js,它是一個可以根據每個點的數值,計算過後,並用HTML5 canvas畫出熱區圖。給它座標後也可以套用在Google地圖或是Leaflet地圖上。

↓ 透過npm下載heatmap.js,不愛用npm的人可以去githubheatmap.js文件中下載

    npm install heatmap.js

↓ 下載完後,引入heatmap.js

    <script src="node_modules/heatmap.js/build/heatmap.min.js"></script>

↓建立一個用以存放heatmap canvas的div

    <div class="heatmap"></div>

↓ 起手式:h337.create()

        const heatmapInstance = h337.create({
            container: document.querySelector('.heatmap'),  //存放heatmap的div
        });

↓ 然後我們利用昨天寫的亂數產生器來產生範例點座標

        function random(min, max) {
            return Math.random() * (max - min) + min;
        }

↓ 相較於昨天,陣列中除了x座標及y座標外,熱區地圖還需要每個點的數值大小,以決定熱區的核密度,也就是點資料聚集的程度。

        let arr = [];
        function CreatePoint(count) {
            for (let i = 0; i < count; i++) {
                let x = Math.floor(random(0, window.innerWidth));
                let y = Math.floor(random(0, window.innerHeight));
                let value = Math.floor(random(0, 100));

                arr.push({ x: x, y: y, value: value });
            }
        }

HTML5 Canvas是以左上角為中心,x向右為正,y向下為正。因此x座標範圍設定從0~畫面寬度,y從0~畫面高度。

↓ 產生點

        CreatePoint(1500);
        const data = {  // 熱區繪製的資料格式
            max: 100,
            data: arr
        };
        console.log(data)

↓ console亂數產生的點
https://ithelp.ithome.com.tw/upload/images/20201008/20130604MEWMHWeLBp.jpg

↓ 呼叫熱區圖

        heatmapInstance.setData(data);

↓ 結果
https://ithelp.ithome.com.tw/upload/images/20201008/201306049BlZ2zygtA.jpg

熱區圖設定及滑鼠事件

  • 打造自己的熱區圖

↓ 熱區圖可以設定核密度半徑、背景顏色、梯度等等,來打造自己的熱區圖吧!

        const heatmapInstance = h337.create({
            container: document.querySelector('.heatmap'),
            backgroundColor: 'rgba(0,0,0,.75)',
            //radius: 30,
            gradient: {
                // enter n keys between 0 and 1 here
                // for gradient color customization
                '.5': 'blue',
                '.8': 'red',
                '.95': 'yellow'
            },
            maxOpacity: .9,
            minOpacity: .3
        });

h337的熱區圖設定

  • container: 存放canvas的容器
  • backgroundColor: 背景顏色
  • radius: 核密度半徑
  • gradient:梯度及梯度顏色
  • opacity: 預設透明度
  • minOpacity: 最小數值透明度
  • maxOpacity: 最大數值的透明度
  • xField: x座標欄位名稱,預設為"x"
  • yField: y座標欄位名稱,預設為"y"
  • valueField : 數值欄位名稱,預設為"value"
  • onExtremaChange: 極值更新的change事件,用於動態算級距時改變圖例使用
  • blur: 模糊度,越高則邊界越順暢,預設為0.85

↓ 結果
https://ithelp.ithome.com.tw/upload/images/20201008/201306046yUZOkYPyq.jpg

  • 滑鼠事件

↓ 可以綁定dom元素的滑鼠事件,並且把滑鼠位置塞值給熱區圖物件。

        document.querySelector('.heatmap').onmousemove = function (e) {
            heatmapInstance.addData({
                x: e.layerX,
                y: e.layerY,
                value: 1
            });
        };

↓ 可以用滑鼠畫的熱區畫布
https://ithelp.ithome.com.tw/upload/images/20201008/201306047AOaqFbC2F.jpg

Leaflet熱區地圖

接下來我們要把熱區呈現在地圖上,結合Leaflet API。

  • 前置作業

↓ 引入heatmap.js外,還要引入leaflet-heatmap.js。

<script src="node_modules/heatmap.js/build/heatmap.min.js"></script>
<script src="node_modules/heatmap.js/plugins/leaflet-heatmap/leaflet-heatmap.js"></script>

↓ 存放地圖的div

    <div id="lmap"></div>

↓ 讓地圖滿板

    <style>
        html,
        body {
            padding: 0;
            margin: 0;
            height: 100%;
        }

        #lmap {
            height: 100%;
        }
    </style>

↓ 初始化地圖

        const LMap = L.map(document.getElementById('lmap'), {
            center: [23.5, 121],
            zoom: 7,
            crs: L.CRS.EPSG3857,
        });
        L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', {
            maxZoom: 18,
            id: 'mapbox.streets'
        }).addTo(LMap);

↓ 結果
https://ithelp.ithome.com.tw/upload/images/20201008/20130604Dwo3KfUXus.jpg

↓ 這次我們亂數產生點,給定經度範圍及緯度範圍。

        let arr = [];
        function CreatePoint(count) {
            for (let i = 0; i < count; i++) {
                let longitude = random(120.5, 121.4);
                let latitude = random(23, 24.6);
                let value = Math.floor(random(0, 100));

                arr.push({ x: longitude, y: latitude, value: value });
            }
        }
        function random(min, max) {
            return Math.random() * (max - min) + min;
        }
        CreatePoint(100);
  • 熱區地圖

↓ 熱區地圖設定

        const option = {
            "scaleRadius": false,
            "radius": 50,
            "useLocalExtrema": true,
            latField: 'y',
            lngField: 'x',
            valueField: 'value',
            "maxOpacity": .5
        };

leaflet-heatmap.js使用HeatmapOverlay物件,
比起heatmap.js的h337物件,多了三種設定

  • radius: 核密度半徑
  • scaleRadius: 如果為true會根據地圖縮放層級來測量;如果為false,不論縮放層級為何均以核密度半徑(單位為px)來計算。
  • useLocalExtrema:當前極值或定極值。如果為false,使用全域極值,也就是固定的最大最小極距;如果為true,則是使用當前最大數值及最小數值作為級距。

↓ 呼叫熱區地圖

        const heatmapLayer = new HeatmapOverlay(option);

↓ 設定熱區地圖物件的資料

        const testData = {
            max: 100,
            data: arr
        };
        heatmapLayer.setData(testData);

↓ 把熱區地圖加入Leaflet圖台

        heatmapLayer.addTo(LMap);
  • 結果比較

核密度半徑

↓ radius 30
https://ithelp.ithome.com.tw/upload/images/20201008/20130604GRXa0GBVB5.jpg

↓ 放大
https://ithelp.ithome.com.tw/upload/images/20201008/20130604A2Q3iNt932.jpg

↓ radius 50
https://ithelp.ithome.com.tw/upload/images/20201008/20130604TLXC4FMXDj.jpg

↓ 放大
https://ithelp.ithome.com.tw/upload/images/20201008/20130604FEGaOypHE3.jpg

可以發現在小比例尺時,差距較不明顯,
中大比例尺時,半徑較大的,較能顯示核密度中心及其擴展性。

然而,半徑太大又會全部混雜在一起。
因此,根據不同的資料型態,要選擇不一樣的核密度半徑。

當前極值及定極值
useLocalExtrema:當前極值或定極值。如果為false,使用全域極值,也就是固定的最大最小極距;如果為true,則是使用當前最大數值及最小數值作為級距。

↓ useLocalExtrema: false 定極值(全域極值)
https://ithelp.ithome.com.tw/upload/images/20201008/20130604tYx8jow4eo.jpg

↓ useLocalExtrema: true 當前極值
https://ithelp.ithome.com.tw/upload/images/20201008/2013060455jmxtI3xL.jpg

可以發現使用當前極值,從當下的所有數值值中找出極大極小值,
較能看出它的顏色差異,可是級距變動下可能會混淆視聽,也要時常更動圖例。

定極值(全域極值)顏色差異較不明顯,視覺上熱區密度較不直觀,
但級距固定,較不易混淆。


今天從流行病學歷史講到空間地理學,再從熱區介紹到heatmap.js。
大家有沒有收穫呀?

明天再續Leaflet! /images/emoticon/emoticon12.gif


上一篇
[8-1] 展點多到爆?那就試試看 Leaflet MarkerCluster吧!
下一篇
[8-3] 讓Marker動起來! 實作Leaflet.MovingMarker與bouncemarker
系列文
《你的地圖會說話? WebGIS與JavaScript的情感交織》30

尚未有邦友留言

立即登入留言