Leaflet

一個開放原始碼的 JavaScript 函式庫
適用於行動裝置友善的互動式地圖

← 教學

互動式等值區域地圖

這是一個案例研究,說明如何使用 GeoJSON 和一些自訂控制項來建立美國各州人口密度的彩色互動式等值區域地圖 (希望能說服所有尚未使用 Leaflet 的主要新聞和政府網站開始使用)。

本教學的靈感來自 德州論壇報的美國參議院決選結果地圖(同樣由 Leaflet 提供支援),由 Ryan Murphy 建立。

查看此獨立範例。

資料來源

我們將建立美國各州人口密度的視覺化呈現。由於資料量(各州形狀和每個州的密度值)不大,因此最方便且簡單的儲存和顯示方式是 GeoJSON

我們的 GeoJSON 資料 (us-states.js) 的每個特徵都會像這樣

{
	"type": "Feature",
	"properties": {
		"name": "Alabama",
		"density": 94.65
	},
	"geometry": ...
	...
}

州形狀的 GeoJSON 是由 D3Mike Bostock 好心分享的,並根據 維基百科的這篇文章(基於美國人口調查局 2011 年 7 月 1 日的資料)擴充了密度值,並指派給 statesData JS 變數。

基本州地圖

讓我們在地圖上顯示我們的州資料

var map = L.map('map').setView([37.8, -96], 4);

var tiles = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
	maxZoom: 19,
	attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);

L.geoJson(statesData).addTo(map);
查看此獨立範例。

新增一些色彩

現在我們需要根據各州的人口密度為其著色。為地圖選擇合適的顏色可能很棘手,但有一個很棒的工具可以提供協助 — ColorBrewer。 使用從它取得的值,我們建立一個根據人口密度傳回顏色的函式

function getColor(d) {
	return d > 1000 ? '#800026' :
	       d > 500  ? '#BD0026' :
	       d > 200  ? '#E31A1C' :
	       d > 100  ? '#FC4E2A' :
	       d > 50   ? '#FD8D3C' :
	       d > 20   ? '#FEB24C' :
	       d > 10   ? '#FED976' :
	                  '#FFEDA0';
}

接下來,我們為 GeoJSON 圖層定義一個樣式函式,使其 fillColor 取決於 feature.properties.density 屬性,同時調整外觀並新增虛線筆觸的精美效果。

function style(feature) {
	return {
		fillColor: getColor(feature.properties.density),
		weight: 2,
		opacity: 1,
		color: 'white',
		dashArray: '3',
		fillOpacity: 0.7
	};
}

L.geoJson(statesData, {style: style}).addTo(map);

現在看起來好多了!

查看此獨立範例。

新增互動功能

現在,當滑鼠懸停在州上時,讓我們以某種方式在視覺上將其突出顯示。首先,我們將為圖層 mouseover 事件定義一個事件監聽器

function highlightFeature(e) {
	var layer = e.target;

	layer.setStyle({
		weight: 5,
		color: '#666',
		dashArray: '',
		fillOpacity: 0.7
	});

	layer.bringToFront();
}

在這裡,我們可以透過 e.target 存取懸停的圖層,在圖層上設定粗灰色的邊框作為我們的突出顯示效果,同時將其移到最前面,以免邊框與附近的州衝突。

接下來,我們將定義在 mouseout 上會發生的情況

function resetHighlight(e) {
	geojson.resetStyle(e.target);
}

方便的 geojson.resetStyle 方法會將圖層樣式重設為其預設狀態 (由我們的 style 函式定義)。為了讓此方法正常運作,請確保我們的 GeoJSON 圖層可透過 geojson 變數存取,方法是在我們的監聽器之前定義它,並稍後將圖層指派給它

var geojson;
// ... our listeners
geojson = L.geoJson(...);

作為額外的附加效果,讓我們定義一個 click 監聽器,以便縮放到該州

function zoomToFeature(e) {
	map.fitBounds(e.target.getBounds());
}

現在,我們將使用 onEachFeature 選項在我們的州圖層上新增監聽器

function onEachFeature(feature, layer) {
	layer.on({
		mouseover: highlightFeature,
		mouseout: resetHighlight,
		click: zoomToFeature
	});
}

geojson = L.geoJson(statesData, {
	style: style,
	onEachFeature: onEachFeature
}).addTo(map);

這使得各州在懸停時可以完美地突出顯示,並讓我們能夠在監聽器內新增其他互動。

自訂資訊控制項

我們可以使用點擊時的常用快顯視窗來顯示有關不同州的資訊,但我們將選擇不同的方式 — 在 自訂控制項內部的州懸停時顯示資訊。

以下是我們控制項的程式碼

var info = L.control();

info.onAdd = function (map) {
	this._div = L.DomUtil.create('div', 'info'); // create a div with a class "info"
	this.update();
	return this._div;
};

// method that we will use to update the control based on feature properties passed
info.update = function (props) {
	this._div.innerHTML = '<h4>US Population Density</h4>' +  (props ?
		'<b>' + props.name + '</b><br />' + props.density + ' people / mi<sup>2</sup>'
		: 'Hover over a state');
};

info.addTo(map);

當使用者懸停在州上時,我們需要更新控制項,因此我們也會如下修改我們的監聽器

function highlightFeature(e) {
	...
	info.update(layer.feature.properties);
}

function resetHighlight(e) {
	...
	info.update();
}

控制項也需要一些 CSS 樣式才能看起來美觀

.info {
	padding: 6px 8px;
	font: 14px/16px Arial, Helvetica, sans-serif;
	background: white;
	background: rgba(255,255,255,0.8);
	box-shadow: 0 0 15px rgba(0,0,0,0.2);
	border-radius: 5px;
}
.info h4 {
	margin: 0 0 5px;
	color: #777;
}

自訂圖例控制項

建立帶有圖例的控制項更容易,因為它是靜態的,不會在州懸停時變更。JavaScript 程式碼

var legend = L.control({position: 'bottomright'});

legend.onAdd = function (map) {

	var div = L.DomUtil.create('div', 'info legend'),
		grades = [0, 10, 20, 50, 100, 200, 500, 1000],
		labels = [];

	// loop through our density intervals and generate a label with a colored square for each interval
	for (var i = 0; i < grades.length; i++) {
		div.innerHTML +=
			'<i style="background:' + getColor(grades[i] + 1) + '"></i> ' +
			grades[i] + (grades[i + 1] ? '&ndash;' + grades[i + 1] + '<br>' : '+');
	}

	return div;
};

legend.addTo(map);

控制項的 CSS 樣式 (我們也重複使用先前定義的 info 類別)

.legend {
	line-height: 18px;
	color: #555;
}
.legend i {
	width: 18px;
	height: 18px;
	float: left;
	margin-right: 8px;
	opacity: 0.7;
}

在本頁頂端或在單獨頁面上欣賞成果。