交互式色阶地图
这是一个关于如何使用 色阶地图、GeoJSON 和一些 自定义控件 创建一个彩色交互式美国各州人口密度地图的案例研究(希望这能说服所有剩余的没有使用 Leaflet 的主要新闻和政府网站开始使用它)。
本教程的灵感来自 德州论坛报美国参议院决选结果地图(也是由 Leaflet 提供支持),由 Ryan Murphy 创建。
查看此独立示例。 |
数据来源
我们将创建一个美国各州人口密度可视化。由于数据量(州形状和每个州的密度值)并不大,最方便和简单的方法是使用 GeoJSON 来存储和显示它。
我们 GeoJSON 数据中的每个要素 (us-states.js) 将看起来像这样
{
"type": "Feature",
"properties": {
"name": "Alabama",
"density": 94.65
},
"geometry": ...
...
}
州形状的 GeoJSON 由 Mike Bostock(D3 的创始人)友情分享,并从 维基百科上的这篇文章 中扩展了密度值,这些值基于 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: '© <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] ? '–' + 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;
}
享受本页面顶部的结果,或在 单独页面 上查看。