查找不规则多边形质心(标签点)的算法


13

我需要在Google地图中找到不规则形状的多边形的质心(或标签点)。我正在显示用于包裹的InfoWindows,并且需要一个锚定InfoWindow的位置,该窗口一定会在表面上。参见下面的图片。

替代文字 替代文字

实际上,我不需要任何Google Maps专用的东西,只是在寻找一种如何自动找到这一点的想法。

我的第一个想法是通过取平均经纬度和经度并从那里随机放置点来找到“假”质心,直到找到与多边形相交的点。我已经有了多边形点代码。在我看来,这简直是“骇客”。

我应该注意,我无权访问任何输出几何图形的服务器端代码,因此无法执行ST_PointOnSurface(the_geom)之类的操作。

Answers:


6

快速又肮脏:如果多边形中没有“假”质心,请使用离该点最近的顶点。


我没想过 理想情况下,我希望此点位于多边形中而不是在边缘上,但这可能是我倒退的原因。
杰森

找到边缘点后,可以将以该点为中心的小正方形与多边形相交,然后选择相交的质心。当正方形足够小时,可以保证这是一个内部点(尽管它当然会非常靠近边缘)。
ub

@Jason如果您使用真实的质心,则遇到此问题的可能性较小。将某些内容快速转换为JavaScript 不应太难
Dandy

虽然我的解决方案(来自假质心的射线)在大多数情况下都可以使用,但我认为该解决方案可能会效果最好,因为它的简单性以及可以确保至少在边缘找到一个点并且可以轻松移动的事实毫不费力地将其放置在多边形内。
杰森

3

您可能需要看一下:http : //github.com/tparkin/Google-Maps-Point-in-Polygon

似乎使用的Ray Casting算法应与您提出的情况相匹配。

这里有一篇关于它的博客文章。 http://appdelegateinc.com/blog/2010/05/16/point-in-polygon-checking/


如果要在服务器端实现此功能,则JTS(Java)和Geos(C)都可以实现此功能。
DavidF 2010年

是的,我可能应该补充说,我已经有了确定我的“计算出的”质心是否在多边形内的代码。我真正想要的是某种在多边形内创建质心的方法。
杰森

3

(较旧的)ESRI算法计算质心,并在对其进行测试以包含在多边形中之后,如有必要,将其水平移动,直到其位于多边形内。(可以根据编程环境中可用的基本操作以多种方式完成此操作。)这往往会产生标签点,该标签点非常接近多边形的视觉中心:请在插图中进行尝试。


1

我通过扩展http://econym.org.uk/gmap中流行的epoly代码解决了我的问题。基本上,我最终要做的是:

  • 创建一系列从“假质心”开始并延伸到每个角和边的射线(共8条)
  • 逐渐在每个射线下创建一个点,分别为10、20、30 ...%,以查看该点是否在我们的原始多边形中

扩展的epoly代码如下:

google.maps.Polygon.prototype.Centroid = function() {
var p = this;
var b = this.Bounds();
var c = new google.maps.LatLng((b.getSouthWest().lat()+b.getNorthEast().lat())/2,(b.getSouthWest().lng()+b.getNorthEast().lng())/2);
if (!p.Contains(c)){
    var fc = c; //False Centroid
    var percentages = [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9]; //We'll check every 10% down each ray and see if we're inside our polygon
    var rays = [
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(b.getNorthEast().lat(),fc.lng())]}),
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(fc.lat(),b.getNorthEast().lng())]}),
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(b.getSouthWest().lat(),fc.lng())]}),
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(fc.lat(),b.getSouthWest().lng())]}),
        new google.maps.Polyline({path:[fc,b.getNorthEast()]}),
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(b.getSouthWest().lat(),b.getNorthEast().lng())]}),
        new google.maps.Polyline({path:[fc,b.getSouthWest()]}),
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(b.getNorthEast().lat(),b.getSouthWest().lng())]})
    ];
    var lp;
    for (var i=0;i<percentages.length;i++){
        var percent = percentages[i];
        for (var j=0;j<rays.length;j++){
            var ray = rays[j];
            var tp = ray.GetPointAtDistance(percent*ray.Distance()); //Test Point i% down the ray
            if (p.Contains(tp)){
                lp = tp; //It worked, store it
                break;
            }
        }
        if (lp){
            c = lp;
            break;
        }
    }
}
return c;}

还是有点骇人听闻,但似乎确实可行。


对于某些曲折的多边形,此方法将失败。例如,缓冲折线{{0,9},{10,20},{9,9},{20,10},{9,0},{20,-10},{9,-9} ,{10,-20},{0,-9},{-10,-20},{-9,-9},{-20,-10},{-9、0},{-20, 10},{-9、9},{-10、20},{0,9}}小于1/2。例如,与Dandy的QAD方法相比,它的效率也不高。
ub

1

另一个“脏”算法可以做到这一点:

  • 取几何的边界框 (Xmax, Ymax, Xmin, Ymin)

  • 循环直到( Xmin+rand*(Xmax-Xmin), Ymin+rand*(Ymax-Ymin) ) 在几何体中找到随机点(使用Google-Maps-Point-in-Polygon


+1,因为这可能有第二次击中的机会。只要您的“随机性”每次都可重现而不会惹恼用户,那么这也是一个有效的解决方案。不能很快达到有效点的可能性很小,特别是如果您从一个好的猜测点开始。
丹迪

1
@Dandy:实际上,在某些情况下,这可能是一个非常糟糕的算法。例如考虑一个窄的对角条。这些在实践中存在(例如,长长的路面),并且很容易占据不到边界框的0.1%(有时要少得多)。要合理确定(95%的置信度)使用此技术命中这样的多边形将需要大约3,000次迭代。
ub

@Whuber:如果您选择了错误的出发地点,是的,这可能需要一段时间才能完成。如果您还假设假设有95%的点击将出现在更理想的几何形状上,那么这可能只是5%的问题。就像另一个GIS.se问题一样,如果要以性能为目标,就永远不会有单一的解决方案,那么最好根据启发式方法来改变策略。没有理由将其运行3000次迭代。您总是可以在10点后退出我的QAD。我认为值得尝试几次迭代,因为这个位置可能更理想。
丹迪2010年

@Dandy:但是您的QAD解决方案有什么问题?您甚至可以通过从原始试验标签点移动到多边形的某些内部缓冲区中的最近顶点来进行少许修改:仍然是QAD,但现在保证可以降落在原始要素的内部位置上。顺便说一句,您的紧急救助策略是一个不错的选择。每当我这样编写一个随机探针时,我都会预先计算特征的面积与边界框的面积之比,使用该比率找到成功的预期时间,并在警告可能很长时立即警告用户。
ub

@Whuber面积比启发法是一个好主意,因为您在计算面积时就只计算质心。至于我的QAD解决方案存在的问题:它处于边缘。如果我选择该点并像您说的那样对其进行缓冲,则“小”半径可能大于该狭窄部分的长度。总是有一个极端的情况。要做很多事情,只是要制作一个气球,它将使UI混乱并反而使几何结构模糊。选择最高或最低的顶点可能更好。
丹迪

1

考虑到最近的澄清,您宁愿选择严格的内部位置,也可以选择“中轴变换”上不在多边形边界上的任何点。(如果您没有MAT的代码,则可以通过负缓冲多边形来使其近似。通过二进制或割线搜索将快速生成一个小的内部多边形,它近似于MAT的一部分;使用其边界上的任何点。)


我理解您在使用几何图形的边缘时要说的话,以使该边缘位于感兴趣的多边形的内部。我不明白您将如何创建该边/顶点。我唯一能想到的就是通过将垂直线从感兴趣点到与所选点的线段相反的线段相交来制作一个虚拟三角形。这两个点之间的中点可能是该虚拟三角形的顶部。
丹迪

@Dandy:这很重要。有多种方法可以完成此操作,具体取决于GIS的本机功能。例如,一旦找到一条与原始特征相交的射线,其长度为一组正值,则该相交将是线段的不相交的并集。使用任何这些线段的中心。另一种方法是从要素上的任何点开始(最好是在其中间,这是您的QED方法完成的工作),在该点的中心创建一个小的简单多边形(例如,正方形),将其与原始要素相交,选择唯一的连接组件...
笨蛋

(继续)...包含起点,然后为该组件递归选择一个中心。当您的GIS让您遍历描述要素边界的顶点序列时,将有大量可用的方法。如果支持负缓冲区,则可以迭代找到一组最大距离内部点(“骨架”,它是MAT的子集)。这有点贵,但是很容易编程,并且可以产生出色的标签点。
ub

0

为什么不将质心仅用于垂直(纬度)位置?然后,您可以通过选择该纬度的平均经度来水平放置标签。(为此,您需要在特定纬度下找到多边形边的经度值,这不会给您带来任何麻烦)。

另外,请注意U形以及更复杂的U形。:)可能的话,请选择最右边的经度的平均值(每对经度对应于多边形的一部分),因为信息窗口的方向是这样的?

这也使您对定位有了更多的控制。例如,最好将信息窗口垂直放置在66或75%位置,以使更多多边形可见。(或者可能不会!但是您可以调整旋钮。)


0

如果用户选择了该点,仅使用用户单击的点怎么样。


可以通过单击鼠标或非空间查询来选择它,因此这并不总是可行。
杰森

0

我也想解决这个问题。我在多边形上施加了一个条件,即它们不能具有进入我要描述的范围的交叉线。

因此,我的方法使用三角剖分。取一个随机顶点(可能取一个极端N,E,W或S的顶点可能会简化事情)。

在此顶点上,画一条线到该顶点以外的某个顶点,即,如果您的顶点是顶点3,则查看顶点3 + 2。

构造一条从原始顶点到该顶点的线。如果构造的行:

  1. 没有越过其他线
  2. 它的中点不在多边形之外

然后,您构造了一个在多边形内的三角形。如果成功顶点为n + 2,则您的三角形为{n,n + 1,n + 2},我们将其称为{v,v1,v2}。如果不是,请尝试下一个顶点,然后继续进行操作,直到尝试了所有顶点。

当找到三角形时,通过从顶点v到v1和v2的中点画一条线,找到三角形的中心。保证该线的中点在三角形内和多边形内。

我尚未对此进行编码,但是我可以看到,交叉线的多边形实际上会导致某些无法正常工作的情况。如果这是多边形的类型,则需要测试多边形上的每个线段,并确保未交叉。跳过交叉的线段,我认为它会起作用。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.