具有 leafletR 可变点大小的交互式地图

interactive map with leafletR variable point size

我创建的交互式地图如下:

library(leafletR)
data(quakes)

# store data in GeoJSON file (just a subset here)
q.dat <- toGeoJSON(data=quakes[1:99,], dest=tempdir(), name="quakes")

# make style based on quake magnitude
q.style <- styleGrad(prop="mag", breaks=seq(4, 6.5, by=0.5), style.val=rev(heat.colors(5)), leg="Richter Magnitude", fill.alpha=0.7, rad=8)

# create map
q.map <- leaflet(data=q.dat, dest=tempdir(), title="Fiji Earthquakes", base.map="osm", style=q.style, popup="mag")

# view map in browser
rstudio::viewer(q.map)

现在,我想让圆的大小取决于另一个变量。假设变量 'stations'。我怎样才能做到这一点?如果这个包不可能,我愿意使用另一个包......只要我能放一个图例,地图是交互式的,点击时会出现一个弹出窗口,颜色可以取决于值一个连续变量。

我通读了 leafletR 包的 documentation,在我看来(我可能是错的)当前版本不支持同一数据集的多种样式。他们给出了一些示例,其中通过列出它们来组合 2 个 styleSingles(例如 style=list(sty.1, sty.2)),但这仅适用于列出 2 个不同的数据集(有关更多详细信息,请参见文档中的第 8 页)。我尝试了各种技巧,但其中 none 对我有用。

但是,我提出了一个您可能想尝试的 hacky 解决方案。使用 leaflet() 函数创建 html 页面后,您可以编辑处理样式的 Javascript 代码,使 radius 属性 动态(这可能也适用于其他样式属性,例如 fillalpha 等)。

你需要知道的:

在 leaflet 创建的 HTML 文档中,搜索 style1(feature) 函数的定义。您应该找到以下代码段:

function style1(feature) {
    return {"color": getValue(feature.properties.mag),
            "fillOpacity": 0.7, 
            "radius": 8};
}

此函数基本上 returns 数据集中每条记录的样式。如您所见,当前形式的函数 returns 是 fillOpacityradius 的静态值。但是,当涉及到颜色时,它会调用另一个名为 getValue 的函数并将 mag(幅度)属性 传递给它。如果我们看一下 getValue 函数的定义,我们会发现它只是定义了每种颜色的幅度范围:

function getValue(x) {
    return x >= 6.5 ? "#808080" :
           x >= 6 ? "#FF0000" :
           x >= 5.5 ? "#FF5500" :
           x >= 5 ? "#FFAA00" :
           x >= 4.5 ? "#FFFF00" :
           x >= 4 ? "#FFFF80" :
           "#808080";
}  

函数定义真的很简单。如果 x(本例中的大小)大于或等于 6.5,则该数据点的颜色将为“#808080”。如果它在 6 和 6.5 之间,则颜色将为 #FF0000"。依此类推。

你能做什么:

既然我们了解了 Javascript 代码如何处理颜色分配给每个数据点的方式,我们可以对所有其他样式属性做类似的事情,而工作量很小。例如,以下代码段显示了如何根据该区域中的站点数使半径动态化:

/* The getValue function controls the color of the data points */ 
function getValue(x) {
    return x >= 6.5 ? "#808080" :
           x >= 6 ? "#FF0000" :
           x >= 5.5 ? "#FF5500" :
           x >= 5 ? "#FFAA00" :
           x >= 4.5 ? "#FFFF00" :
           x >= 4 ? "#FFFF80" :
           "#808080";
}

/* The getRadValue function controls the radius of the data points */ 
function getRadValue(x) {
    return x >= 100 ? 24 :
           x >= 80 ? 20 :
           x >= 60 ? 16 :
           x >= 40 ? 12 :
           8;
}

/* The updated definition of the style1 function */
function style1(feature) {
    return {"color": getValue(feature.properties.mag),
            "fillOpacity": 0.7, 
            "radius": getRadValue(feature.properties.stations)
            };
}

因此,有了 style1(feature) 的新定义,现在我们可以控制数据点的颜色和半径。代码修改的结果如下所示:

这种方法的好处在于,它可以让您更精细地控制样式属性和它们可以具有的值的范围。主要的缺点是,如果您想为这些属性添加图例,则必须手动进行。 adding/editing 图例的逻辑应该在 HTML 文档的最底部,如果你知道 Javascript/HTML/CSS,编辑该代码段应该不会太困难。

更新:

要为新的动态变量(在我们的示例中为半径)添加图例,您需要编辑附加到图例对象的 .onAdd 处理程序。正如我之前所说,此处理程序的定义通常位于 html 页面的底部,如果我们 运行 您在问题中提供的代码位,则处理程序应如下所示:

legend.onAdd = function (map) {
    var div = L.DomUtil.create('div', 'legend');
    var labels = [];
    var grades = [4, 4.5, 5, 5.5, 6, 6.5];

    div.innerHTML += 'Richter Magnitude<br>';
    for (var i = 0; i < grades.length - 1; i++) {
        div.innerHTML += '<i style="background:' + getValue(grades[i]) + '"></i> ' + grades[i] + '&ndash;' + grades[i + 1] + '<br>';
    }

    return div;
};

上面的代码简单地遍历了幅度值的范围,并创建了一个框(具有适当的颜色,引用了我们之前看过的 getValue 函数)和一个标签。如果你想为 stations 变量创建类似的东西,比方说,我们可以使用上面相同的逻辑。虽然在这种情况下,我们将改变圆圈的大小,而不是改变颜色。以下代码段显示了如何实现:

legend.onAdd = function (map) {
    var div = L.DomUtil.create('div', 'legend');
    var labels = [];
    var grades = [4, 4.5, 5, 5.5, 6, 6.5];

    div.innerHTML += 'Richter Magnitude<br>';
    for (var i = 0; i < grades.length - 1; i++) {
        div.innerHTML += '<i style="background:' + getValue(grades[i]) + '"></i> ' + grades[i] + '&ndash;' + grades[i + 1] + '<br>';
    }

    // Adding the range of possible of values that the variable might take
    // This should be in sync with the range of values you considered in
    // the getRadValue function. 

    var rad_grades = [40, 60, 80, 100];
    // The title for this section of the legend
    div.innerHTML += 'Number of stations<br>'
    for (var i = 0; i < rad_grades.length - 1; i++) {
        div.innerHTML += '<table style="border: none;"><tr><td class="circle" style="width: ' +  
            (getRadValue(rad_grades[rad_grades.length - 2]) * 2 + 6) + 'px;"><svg style="width: ' +  
            (getRadValue(rad_grades[i]) * 2 + 6) + 'px; height: ' + (getRadValue(rad_grades[i]) * 2 + 6) +  
            'px;" xmlns="http://www.w3.org/2000/svg" version="1.1"><circle cx="' + (getRadValue(rad_grades[i]) + 3) + '" cy="' + 
            (getRadValue(rad_grades[i]) + 3) + '" r="' + getRadValue(rad_grades[i]) + '" /></svg></td><td class="value">' + 
            rad_grades[i] + '&ndash;' + rad_grades[i + 1] + '</td></tr></table>';
        }

    return div;
};

如您所见,我们控制的样式类型 属性 将决定我们如何在图例中指定它。例如,如果您想为 alpha 属性 添加图例,那么您可能想尝试使用圆圈并控制其宽度和高度以外的其他方法。上述代码修改的最终结果如下所示:

此外,如果您想在弹出窗口中包含电台数量,则必须编辑 onEachFeature 函数。这将与我们对所有其他修改采用的方法相同,而且这是一个非常简单的更改。

onEachFeature 函数在原来的 HTML 中看起来像这样:

function onEachFeature(feature, layer) {
    if (feature.properties && feature.properties.mag) {
        layer.bindPopup("mag: " + feature.properties.mag);
    }
}

如果你想在弹出窗口中也包含站点数,那么你需要将其包含在bindPopup方法的参数中,如下所示:

function onEachFeature(feature, layer) {
    if (feature.properties && feature.properties.mag && feature.properties.stations) {
        layer.bindPopup("mag: " + feature.properties.mag + "<br> # Stations: " + feature.properties.stations);
    }
} 

此更改的最终结果如下:

希望这对您有所帮助。