寻找Leaflet多边形的中心?

35

我在创建的地图上有一堆leaflet多边形。每个多边形代表着不同的东西。根据用户所在页面显示特定集合的信息在弹出窗口中。我需要找到一种方法,使得"弹出"气泡可以在其所表示的多边形的中心位置打开。

每个多边形使用以下代码绘制:

var L20 = [
    [74.0995, -99.92615],
    [74.14008, -99.4043],
    [74.07691, -99.33838],
    [74.03617, -99.86023]
];



var L19 = [
    [74.02559, -99.84924],
    [74.06636, -99.32739],
    [74.0029, -99.26147],
    [73.96197, -99.77783]
];

var L18 = [
    [73.95142, -99.76684],
    [73.99235, -99.25048],
    [73.92889, -99.18456],
    [73.8878, -99.69543]
];

var set1 = L.polygon([L20, L19, L18], {
    color: "#fff",
    weight: 1,
    stroke: true,
    opacity: 0.05,
    fillColor: "#346B1F",

}).addTo(map);

以下代码用于绘制弹出窗口:

var popup = L.popup({})
    .setLatLng([73.64017, -100.32715])
    .setContent(content).openOn(map);
    var popup = L.popup();

所以我需要找到一种方法,让.setLatLng能够确定或者给定多边形的中心。

我想出了3种可能管用的解决方案,但是不确定该如何实现。

  1. 找到一种使用多边形坐标来确定多边形中心的方法,弹出框将在此处打开。

  2. 调用多边形中的一个点,然后偏移弹出框的位置。

  3. 为每个多边形使用一个ID,这样每个弹出框都知道它可以打开的盒子区域(即多边形)。

有人能帮我吗?


相关:https://dev59.com/MHM_5IYBdhLWcg3w2XBg - StayOnTarget
5个回答

74

Leaflet自某个时间以来已经内置了getCenter()方法:

polygon.getBounds().getCenter();

7
这应该是被接受的答案。还要注意的是,你甚至不需要多边形对象(我曾经愚蠢地尝试获取它一段时间)。你可以在任何Bounds对象上运行它,因此同样适用于图层,例如layer.getBounds().getCenter()。 - masterchief
2
对于大多数形状而言,边界框的中心点足够接近多边形的中心点。直角三角形和L形区域是差异明显的例子,但对于大多数情况来说,这种方法更好,我同意。 - Steve Clanton
3
如果您的多边形呈“U”形,那么这种方法行不通。边界框的中心会使其位于多边形外部。 - Kristopher
如果你想放大,无论中心点是否在形状之外都没有关系。 - undefined

43

有几种方法可以近似计算多边形的质心。

最简单(但最不准确的方法)是获取包含多边形的边界框的中心,正如yarl所建议的那样,使用polygon.getBounds().getCenter();

我最初回答了寻找点集重心的公式,可以通过平均其顶点坐标来找到。

var getCentroid = function (arr) { 
    return arr.reduce(function (x,y) {
        return [x[0] + y[0]/arr.length, x[1] + y[1]/arr.length] 
    }, [0,0]) 
}

centerL20 = getCentroid(L20);

虽然点的重心足以近似骗过我,但评论者指出这不是多边形的重心。

基于非自交闭合多边形的重心公式的实现会给出正确的结果:

var getCentroid2 = function (arr) {
    var twoTimesSignedArea = 0;
    var cxTimes6SignedArea = 0;
    var cyTimes6SignedArea = 0;

    var length = arr.length

    var x = function (i) { return arr[i % length][0] };
    var y = function (i) { return arr[i % length][1] };

    for ( var i = 0; i < arr.length; i++) {
        var twoSA = x(i)*y(i+1) - x(i+1)*y(i);
        twoTimesSignedArea += twoSA;
        cxTimes6SignedArea += (x(i) + x(i+1)) * twoSA;
        cyTimes6SignedArea += (y(i) + y(i+1)) * twoSA;
    }
    var sixSignedArea = 3 * twoTimesSignedArea;
    return [ cxTimes6SignedArea / sixSignedArea, cyTimes6SignedArea / sixSignedArea];        
}

1
完美,这正是我在寻找的。我只是修改了它,使其在每个页面上运行此函数以确定每个多边形的中心,效果非常好...非常感谢!!! - Nxlevel
2
这个答案是不正确的。想象一个右侧稍微圆润的矩形。你的方法将重心放在右侧边缘,因为左侧的两个顶点在平均值上比右侧任意大数量的点少。如果每个点“重量”相同,则此方法有效,但如果每个单位面积“权重”相同,则无效,这实际上就是人们谈论多边形重心时的真正意思。 - JounceCracklePop
谢谢你指出来,@CarlLeth。我已经纠正了答案(我认为)。你能帮我确认一下吗? - Steve Clanton
它不能与U型多边形一起使用。 - WhatIsHTML
有时候质心更好,有时候可达性极点更好。Polylabel库(Kristofer的答案中提到)已经使用了两者来找到最佳答案。如果你现在正在尝试找到一个多边形的中心,你应该绝对优先选择https://github.com/mapbox/polylabel解决方案。 - Steve Clanton

20
您正在尝试解决的问题被称为“难以到达的极点”问题。通过找到边界框的中心来确定在多边形中放置标签的最佳位置并不能完全解决该问题。考虑一个呈U形的多边形,边界框的中心将标签放置在多边形外部。我花了很长时间才找到这个优秀的库:https://github.com/mapbox/polylabelREADME.MD中可以看出:
这是一个快速算法,用于寻找多边形无法到达的极点,即与多边形轮廓最远的内部点(不要与质心混淆),实现为JavaScript库。适用于在多边形上放置文本标签的最佳位置。
它是一种迭代网格算法,受Garcia-Castellanos和Lombardo(2007)论文的启发。与论文中的算法不同,该算法:
- 保证在给定精度范围内找到全局最优解 - 速度更快(10-40倍)
用法:
以类似GeoJSON的格式给出多边形坐标和精度(默认为1.0),Polylabel会返回[x,y]格式的无法到达的极点坐标。 var p = polylabel(polygon, 1.0);

Pole of Inaccessibility


算法如何工作:
这是一个迭代的基于网格的算法,开始时用大正方形单元覆盖多边形,然后按最有前途的顺序迭代地分割它们,同时积极修剪不感兴趣的单元。
1. 生成完全覆盖多边形的初始正方形单元(单元大小等于宽度或高度中较小的值)。使用负值计算每个单元中心到外部多边形的距离(通过射线投射检测是否在多边形内部)。 2. 将单元放入按最大潜在距离从单元内部点排序的优先队列中,最大潜在距离定义为中心到单元半径(等于 cell_size * sqrt(2) / 2)的距离之和。 3. 计算多边形重心的距离,并将其选为第一个“最佳解”。 4. 逐个从优先队列中取出单元。如果单元的距离比当前最佳解更好,则将其保存为最佳解。然后,如果该单元可能包含比当前最佳解更好的解(cell_max - best_dist > precision),则将其分成 4 个子单元并将它们放入队列。 5. 当我们耗尽队列时停止算法,并返回最佳单元的中心作为无法到达区域的极点。它将保证在给定精度内是全局最优解。

The most distant internal point from the polygon outline


我喜欢将精度降低到约0.4,因为它可以在我的多边形中找到更精确的位置。这会导致代码多运行几次,但在我的项目中,我要找到大约150个多边形的无法访问极点,这并不明显。 - Kristopher

2
假设每个多边形只有4条边,那么这很简单。
var L20 = [
[74.0995, -99.92615],
[74.14008, -99.4043],
[74.07691, -99.33838],
[74.03617, -99.86023]
];

使用这个示例获取最大和最小纬度:分别为74.03617和74.14008,同样的,经度也是:-99.92615和99.33838。

然后获取每个值的中间值:(max-min)/ 2 = 0.051955和-0.293885,然后将它们添加到最小值中。

得出的中心点为74.088125,-99.632265


我可以通过数学计算来解决它。我只需要一段能够自动确定中心的代码。 - Nxlevel
那么,史蒂夫的答案是正确的——然而你的问题底部有三个选项,学习编写自己的代码更容易——我只是回答了你的问题 :-P - GrahamTheDevRel

1

要将多边形移动到视图中心,请使用以下代码:

    map.fitBounds(poly.getBounds())

这也将正确地设置缩放级别。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接