交互式地图

这是一个案例研究,在GeoJSON和一些自定义控件的帮助下,创建了彩色交互式的美国各州人口密度图。

See this example stand-alone.

数据源

我们让美国人口密度的数据可视化。由于数据量不是很大,所以最简便的存储和显示数据的方法是GeoJSON。

我们的GeoJSON数据的每个特征将是这样的:

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

基本状态图

让我们用定制的Mapbox样式在地图上显示我们的状态数据。

var mapboxAccessToken = {your access token here};
var map = L.map('map').setView([37.8, -96], 4);

L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=' + mapboxAccessToken, {
    id: 'mapbox.light',
    attribution: ...
}).addTo(map);

L.geoJson(statesData).addTo(map);
查看本实例

添加颜色

现在我们需要根据人口密度对各州进行着色。为地图选择好的颜色可能需要比较高的技巧,但有一个伟大的工具可以帮助我们---ColorBrewer。 我们创建一个函数,使用从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层的样式函数,使其填充颜色取决于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);

现在看起来好多了!

查看本实例

Adding Interaction

现在让我们让鼠标经过地图上代表美国各州的色块时,该色块高亮显示。首先,我们将为层的鼠标事件定义一个事件监听器:

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

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

    if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
        layer.bringToFront();
    }
}

在此我们通过e.target 获取到鼠标悬停的层,并在这个层上设置了一个宽的灰色边界作为我们的突出效果,同时将该层置于顶层, 这样边界就不会与附近的州发生冲突(而不是IE,Opera或者Edge)。

接下来,我们将定义mouseout发生的事情:

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

方便的geojson.resetStyle方法将将层的样式重置为默认状态(由我们的style函数定义)。为此,请确保我们的GeoJSON层可以被之前监听器定义的geojson变量访问,然后把这个GeoJSON层分配给geojson变量:

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

让我们定义一个点击监听器控来放大状态:

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;
}

在这个页面的顶部享受成果,或者在一个单独的页面上 .