如何合并复杂的多边形?

96

给定两个多边形:

POLYGON((1 0, 1 8, 6 4, 1 0))
POLYGON((4 1, 3 5, 4 9, 9 5, 4 1),(4 5, 5 7, 6 7, 4 4, 4 5))
我如何计算并集(组合多边形)? enter image description here Dave的例子 使用SQL Server生成联合,但我需要在代码中完成相同的操作。 我正在寻找一个数学公式或任何语言的代码示例,以公开实际的数学概念。 我正试图制作将国家动态组合成地区的地图。 我在这里提出了一个相关问题:Grouping geographical shapes
10个回答

79

这是一个非常好的问题。我之前在c#上实现了相同的算法。该算法构建两个多边形的公共轮廓(即构建没有洞的并集)。如下所示。


目标

步骤1. 创建描述多边形的图形。

输入:第一个多边形(n个点),第二个多边形(m个点)。输出:图形。顶点-多边形交点。

我们应该找到交叉点。迭代遍历两个多边形中的所有多边形边缘[O(n*m)],找到任何交点。

  • 如果没有发现交点,则简单地添加顶点并将它们连接到边缘。

  • 如果发现任何交叉点,请按其到起点的长度对它们进行排序,添加所有顶点(起点、终点和交叉点)并将它们(已按排序顺序)连接到边。

    图形

步骤2. 检查构建的图形

如果在构建图形时未发现任何交点,则有以下几种情况:

  1. 多边形1包含多边形2-返回多边形1
  2. 多边形2包含多边形1-返回多边形2
  3. 多边形1和多边形2不相交。返回多边形1和多边形2。

步骤3. 找到左下角顶点。

找到最小的x和y坐标(minx,miny)。然后找到(minx,miny)与多边形的点之间的最小距离。这个点将是左下角的点。

左下角点

步骤4. 构建公共轮廓。

我们从左下角的点开始遍历图形,直到回到它为止。开始时,我们将所有边缘标记为未访问。在每次迭代中,您应该选择下一个点并将其标记为已访问。

选择下一个点时,选择逆时针方向具有最大内角的边。
我计算两个向量:当前边的向量1和每个未访问边的向量2(如图所示)。
对于向量,我计算:
1. 标量积(点积)。它返回与向量之间角度相关的值。 2. 向量积(叉积)。它返回一个新的向量。如果这个向量的z坐标是正的,则标量积给我逆时针方向的直角。否则(z坐标是负的),我计算从标量积得出的角度的360度减去的角度。
结果我得到了具有最大角度的边(和相应的下一个顶点)。
我将每个通过的顶点添加到结果列表中。结果列表是合并后的多边形。 备注:
1. 此算法允许我们合并多个多边形 - 以多边形的成对迭代应用。 2. 如果您有由许多贝塞尔曲线和直线组成的路径,则应首先将其展平。

2
我认为应该提到,在比较标量积时,计算它们的标量积之前应将向量归一化(即,将向量坐标除以其长度)。无论如何,感谢这个答案。 - eyalzba
1
我在某个地方读到过,但现在不记得是在哪里和什么时候了 =) - xtmq
3
要完成“(a)计算孔”的任务,需要寻找从未被“步骤4. 构造公共轮廓”访问过的点。使用其中一个点来“开始”孔的绘制。采用类似的“轮廓”算法,排除主输出多边形中已经使用的任何点。得到的多边形就是一个“孔”。重复以上步骤,直到所有点都被包含在某个多边形或孔中。 - ToolmakerSteve
我忘了提到在这个变化中(保留孔洞),你必须测试一个多边形的每个点,以查看它是否在另一个多边形内。如果是这样,就把点扔掉。也就是说,只有在没有任何其他多边形内的点上形成孔洞,加上交点(“偏爱”未计算出的外边缘的交点)。我存储了哪些点连接到每个点 - 随着添加“交点”,这变得棘手起来。通过链接已经连接的点来形成孔洞的边缘,在交叉点可能会切换到“另一个多边形”的点列表。 - ToolmakerSteve
这看起来像是Weiler-Atherton剪裁算法,除非我错了? - noblemaster
显示剩余3条评论

15

这是一个具有挑战性但易于理解的主题,通常被称为“多边形上的正则化布尔运算”。您可以查看这个MathOverflow答案,其中包括下面的图示(来自Alan Murta的剪辑库),其中粉色联合体是OP的combine


      BooleanOps



3
这个人确实是这方面的权威 ;) - Constantin

6

您需要确定哪些点在内部。在删除这些点之后,您可以将一个“外部”点集插入到另一个点集中。您的插入点(例如,在右侧图片中箭头所示的位置)是您必须从输入集中删除点的位置。


1
+1 链接到 Bourke。慢 30 秒,我就能赶上你了 :) - David Seiler

3

我曾经面临过同样的问题,而我是通过以下方式解决的:

使用Cython封装了Angus Johnson的Clipper库(版本6.4.2)的C++翻译 https://github.com/fonttools/pyclipper

pc = pyclipper.Pyclipper()
def get_poly_union(polygons):
    pc.AddPaths(polygons, pyclipper.PT_SUBJECT, True)
    solution = pc.Execute(pyclipper.CT_UNION, pyclipper.PFT_NONZERO, pyclipper.PFT_NONZERO)
    return solution[0]

print_image = image.copy()
solution = get_poly_union(polygons_array) 
#polygons_array=[polygon,polygon,polygon, ...,polygon] and polygon=[point,point,point...,point]

cv2.drawContours(print_image, [np.asarray(solution)], -1, (0, 255, 0), 2)

plt.imshow(print_image)

Clipper可以直接在C++中使用,网址为:http://www.angusj.com/delphi/clipper.php。 - Catskul

3

好问题!我以前从未尝试过这个,但我现在会试着解决。

首先:您需要知道这两个形状重叠的位置。为此,您可以查看多边形A中的每条边,并查看它与多边形B中的边相交的位置。在此示例中,应该有两个交点。

然后:制作联合形状。您可以获取A和B中的所有顶点以及交点,然后排除包含在最终形状中的顶点。要找到这些点,看起来您只需找到任何一个在B中的A的顶点,以及在A中的B的顶点即可。


是的,真正的问题是“我们如何计算这两个相交点的加和”? - Pacerier
不完全准确,你还需要考虑顶点的正确顺序。 - Icaro Amorim

3

看起来很有前途。我已经给作者发了电子邮件,因为他们的下载链接都返回403错误。 - grenade
1
源代码的链接对我来说可用:ftp://ftp.cs.man.ac.uk/pub/toby/gpc/gpc232-release.zip - lhf

2
这是一个很老的问题,但是来自 Boost 的 Union_ 函数对我有用。

请看下面的代码片段:

#include <iostream>
#include <vector>

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>

#include <boost/foreach.hpp>


int main()
{
    typedef boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > polygon;

    polygon green, blue;

    boost::geometry::read_wkt(
        "POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))", green);

    boost::geometry::read_wkt(
        "POLYGON((5 5, 5 15, 15 15, 15 5, 5 5))", blue);

    std::vector<polygon> output;
    boost::geometry::union_(green, blue, output);

    int i = 0;
    std::cout << "green || blue:" << std::endl;
    BOOST_FOREACH(polygon const& p, output)
    {
        std::cout << i++ << ": " << boost::geometry::area(p) << std::endl;

        for (int i = 0; i < p.outer().size(); i++)
        {
            std::cout << p.outer().at(i).x() << " " << p.outer().at(i).y() << std::endl;
        }
    }



    return 0;
}

1
请记得在必要时“修正”您的多边形。请参阅https://dev59.com/b33aa4cB1Zd3GeqPcFok。 - anumi

2

我看到过使用BSP树的解决方案,可以在这里找到描述。

基本上,它描述了交叉点是多边形A内部的边(包括部分边缘,并使用BSP树计算)的并集。然后,您可以将 A / B 定义为 ~(~A /\ ~B),其中 ~ 表示反转多边形的绕组, / 表示联合, /\ 表示交集。


1
当对国家进行分组时,我希望没有重叠--您可以采用相当简单的算法来查找共享顶点 - 一个简单的方法是迭代一个多边形上的点,看它是否在任何其他多边形上,并共享相同的下一个或前一个点以查看是否存在匹配。然后只需删除共享的顶点即可创建您的联合。

2
当对国家进行分组时,我希望不会出现重叠的情况......尽管并非所有国家都同意自己或邻国的边界,但如果能这样做就太好了。 - FrustratedWithFormsDesigner
2
@FrustratedWithFormsDesigner 的确如此,但大多数制图师要么将有争议的地区分配给他们的政治盟友,要么将其作为独立实体来处理,这也是我将我的算法描述为天真的原因之一... - Rowland Shaw

1

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