本系列文章已出版實體書籍:
「你的地圖會說話?WebGIS 與 JavaScript 的情感交織」(博碩文化)
WebGIS啟蒙首選✖五家地圖API✖近百個程式範例✖實用簡易口訣✖學習難度分級✖補充ES6小知識
本系列文章為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,它是一個可以根據每個點的數值,計算過後,並用HTML5 canvas畫出熱區圖。給它座標後也可以套用在Google地圖或是Leaflet地圖上。
↓ 透過npm下載heatmap.js,不愛用npm的人可以去github或heatmap.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亂數產生的點
↓ 呼叫熱區圖
heatmapInstance.setData(data);
↓ 結果
↓ 熱區圖可以設定核密度半徑、背景顏色、梯度等等,來打造自己的熱區圖吧!
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的熱區圖設定
↓ 結果
↓ 可以綁定dom元素的滑鼠事件,並且把滑鼠位置塞值給熱區圖物件。
document.querySelector('.heatmap').onmousemove = function (e) {
heatmapInstance.addData({
x: e.layerX,
y: e.layerY,
value: 1
});
};
↓ 可以用滑鼠畫的熱區畫布
接下來我們要把熱區呈現在地圖上,結合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);
↓ 結果
↓ 這次我們亂數產生點,給定經度範圍及緯度範圍。
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物件,多了三種設定
:
↓ 呼叫熱區地圖
const heatmapLayer = new HeatmapOverlay(option);
↓ 設定熱區地圖物件的資料
const testData = {
max: 100,
data: arr
};
heatmapLayer.setData(testData);
↓ 把熱區地圖加入Leaflet圖台
heatmapLayer.addTo(LMap);
核密度半徑
↓ radius 30
↓ 放大
↓ radius 50
↓ 放大
可以發現在小比例尺時,差距較不明顯,
中大比例尺時,半徑較大的,較能顯示核密度中心及其擴展性。
然而,半徑太大又會全部混雜在一起。
因此,根據不同的資料型態,要選擇不一樣的核密度半徑。
當前極值及定極值
useLocalExtrema:當前極值或定極值。如果為false,使用全域極值,也就是固定的最大最小極距;如果為true,則是使用當前最大數值及最小數值作為級距。
↓ useLocalExtrema: false 定極值(全域極值)
↓ useLocalExtrema: true 當前極值
可以發現使用當前極值,從當下的所有數值值中找出極大極小值,
較能看出它的顏色差異,可是級距變動下可能會混淆視聽,也要時常更動圖例。
定極值(全域極值)顏色差異較不明顯,視覺上熱區密度較不直觀,
但級距固定,較不易混淆。
今天從流行病學歷史講到空間地理學,再從熱區介紹到heatmap.js。
大家有沒有收穫呀?
明天再續Leaflet!