填充/缩小(偏移,缓冲)多边形的算法


202

如何将多边形“膨胀”?也就是说,我想做类似的事情:

替代文字

要求是新的(膨胀的)多边形的边/点都与旧的(原始)多边形的边/点都具有相同的恒定距离(在示例图片中,它们不是,因为从那以后,它必须对膨胀的顶点使用圆弧,但是让我们暂时忘记这一点;))。

我要寻找的数学术语实际上是向内/向外多边形偏移。+1表示要指出这一点。另一种命名方式是多边形缓冲

搜索结果:

以下是一些链接:


17
这根本不是一个琐碎的问题:如果通缩/通货膨胀很小,则不会发生严重的情况,但是在某些时候,顶点将消失。可能以前已经做过,所以我要说:使用别人的算法,不要构建自己的算法。
马丁于2009年

1
确实,如果多边形从一开始就是凹面的(如上例所示),您必须决定在天真的算法想要制作一个自相交的“多边形”时应该发生什么……
AakashM,2009年

是的,主要问题是多边形的凹入部分,这就是复杂性所在。我仍然认为计算何时必须消除某个顶点应该不是问题。主要问题是这将需要什么样的渐近复杂性。
Igor Brejc,2009年

您好,这也是我的问题,除了我需要在3D模式下进行。论文arxiv.org/pdf/0805.0022.pdf中描述的“三维多面体的直形骨骼”方法有替代方法吗?
stephanmg

Answers:


138

我想我可能提一提我自己的多边形裁剪和抵消库 - 快船

尽管Clipper主要是为多边形裁剪操作而设计的,但它也可以进行多边形偏移。该库是用Delphi,C ++和C#编写的开源免费软件。它具有非常宽松的Boost许可证,因此可以免费用于免费软件和商业应用程序。

可以使用以下三种偏移样式之一执行多边形偏移:正方形,圆形和斜切。

多边形偏移样式


2
很酷!你两年前在哪里?:)最后,我不得不实现自己的抵消逻辑(并为此浪费了很多时间)。您正在使用什么算法进行多边形偏移BTW?我用过草火。可以处理多边形中的孔吗?
Igor Brejc

2
2年前,我一直在寻找一种不错的多边形裁剪解决方案,而该解决方案不会遇到棘手的许可问题:)。边缘偏移是通过为所有边缘生成单位法线来实现的。边连接由我的多边形裁剪器整理,因为这些交叠的交点的方向与多边形的方向相反。肯定像自动相交的多边形等一样处理孔。对其类型或数量没有限制。另请参见此处的“通过计算绕线数产生的多边形偏移”: me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf
Angus Johnson,

哇!不要再以为这个问题是“被遗忘的”!上周我在这里看过-我没想到会再来的!谢谢一群!
克里斯·伯特·布朗


5
对于任何想这样做的人,另一种选择是使用GEOS,如果使用的是python,则使用GEOS的包装Shapely。一个非常漂亮的例子:toblerity.github.com/shapely/manual.html#object.buffer
pelson 2012年

40

您要查找的多边形在计算几何中称为向内/向外偏移多边形,它与直线骨架密切相关。

这些是复杂多边形的多个偏移多边形:

这是另一个多边形的笔直骨架:

正如其他注释中所指出的那样,取决于您计划对多边形进行“充气/放气”的距离,最终可以为输出提供不同的连接性。

从计算的角度来看:一旦有了笔直的骨架,就应该能够相对容易地构造偏移多边形。开源CGAL库(非商业性免费)具有实现这些结构的软件包。请参见此代码示例,以使用CGAL计算偏移多边形。

所述封装手册即使您不打算使用CGAL,应为您提供一个良好的起点,说明如何构造这些结构,并且包含对这些论文的引用以及其数学定义和属性:

CGAL手册:2D直形骨骼和多边形偏移


11

对于这些类型的事情,我通常使用JTS。出于演示的目的,我创造了这个的jsfiddle使用JSTS(JTS的JavaScript的端口)。您只需要将必须的坐标转换为JSTS坐标即可:

function vectorCoordinates2JTS (polygon) {
  var coordinates = [];
  for (var i = 0; i < polygon.length; i++) {
    coordinates.push(new jsts.geom.Coordinate(polygon[i].x, polygon[i].y));
  }
  return coordinates;
}

结果是这样的:

在此处输入图片说明

附加信息:我通常使用这种类型的充气/放气(为我的目的稍作修改)来设置在地图上绘制的多边形上的半径边界(使用Leaflet或Google地图)。您只需将(lat,lng)对转换为JSTS坐标,其他所有操作都相同。例:

在此处输入图片说明


9

在我看来,您想要的是:

  • 从顶点开始,沿相邻边沿逆时针方向。
  • 用新的,平行于d旧边的“左” 边的平行边替换边。
  • 重复所有边缘。
  • 找到新边的相交以获取新顶点。
  • 检测您是否已变成交叉多边形,并决定要怎么做。可能在交叉点添加一个新顶点,并摆脱一些旧顶点。我不确定是否有比仅比较每对不相邻的边以查看它们的交点是否位于两对顶点之间更好的检测方法。

生成的多边形位于距顶点“足够远”的旧多边形所需的距离处。d正如您所说,在顶点附近,与旧多边形相隔一定距离的点集不是多边形,因此无法满足上述要求。

我不知道该算法是否具有名称,网络上的示例代码或恶意优化,但我认为它描述了您想要的内容。



5

每条线应将平面分为“内侧”和“轮廓”;您可以使用常规的内积方法找出答案。

将所有线向外移动一定距离。

考虑所有成对的相邻线(线,而不是线段),找到交点。这些是新的顶点。

通过删除所有相交的部分来清理新顶点。-我们这里有一些情况

(a)情况1:

 0--7  4--3
 |  |  |  |
 |  6--5  |
 |        |
 1--------2

如果您将其花费一倍,就会得到:

0----a----3
|    |    |
|    |    |
|    b    |
|         |
|         |
1---------2

7和4重叠..如果看到此点,则删除此点及其之间的所有点。

(b)情况2

 0--7  4--3
 |  |  |  |
 |  6--5  |
 |        |
 1--------2

如果将其花费两倍,您将得到:

0----47----3
|    ||    |
|    ||    |
|    ||    |
|    56    |
|          |
|          |
|          |
1----------2

为了解决这个问题,对于每条线段,您必须检查它是否与后面的线段重叠。

(c)情况3

       4--3
 0--X9 |  |
 |  78 |  |
 |  6--5  |
 |        |
 1--------2

支出1。这是情况1的更一般情况。

(d)情况4

与case3相同,但花费两个。

实际上,如果可以处理第4种情况,则所有其他情况仅是特殊情况,其中有些线或顶点重叠。

要进行第4种情况,您需要保留一堆顶点..当发现与后一条线重叠的线时按下,在获得后一条线时将其弹出。-就像您在凸包中所做的一样。


你知道这个的任何psedo算法吗?
–EmptyData

5

这是一个替代解决方案,请看您是否更喜欢它。

  1. 进行三角剖分,不一定要进行delaunay -任何三角剖分都可以。

  2. 给每个三角形充气-这应该是微不足道的。如果按逆时针顺序存储三角形,则只需将线移到右侧并进行相交即可。

  3. 使用改良的Weiler-Atherton裁剪算法合并它们


您如何准确地使三角形膨胀?您的输出取决于三角剖分吗?使用这种方法可以缩小多边形时的情况吗?
balint.miklos

您确定这种方法确实适用于多边形膨胀吗?当多边形的凹入部分膨胀到必须消除某些顶点的程度时,会发生什么。问题是:当您查看多边形之后的三角形会发生什么。膨胀时,三角形不膨胀,而是变形。
Igor

1
伊戈尔:Weiler-Atherton裁剪算法可以正确处理“必须消除某些顶点”的情况;
J-16 SDiZ 2009年

@balint:给三角形充气很简单:如果以正常顺序存储vertrex,则右侧总是“向外”。只需将这些线段视为线,将其向外移动,然后找到相互作用即可-它们是新的顶点。对于三角剖分本身,再考虑一下,Delaunay三角剖分可能会给出更好的结果。
J-16 SDiZ 2009年

4
我认为这种方法很容易产生不良结果。即使是一个简单的示例,例如使用对角线对四边形进行了三角剖分。对于两个放大的三角形,您会得到:img200.imageshack.us/img200/2640/counterm.png,它们的并集不是您要的。我看不到这种方法的用处。
balint.miklos

3

非常感谢Angus Johnson的Clipper库。在http://www.angusj.com/delphi/clipper.php#code上的clipper主页上有一些很好的代码示例可用于执行剪切操作, 但我没有看到多边形偏移的示例。所以我认为如果我发布代码,也许对某人有用:

    public static List<Point> GetOffsetPolygon(List<Point> originalPath, double offset)
    {
        List<Point> resultOffsetPath = new List<Point>();

        List<ClipperLib.IntPoint> polygon = new List<ClipperLib.IntPoint>();
        foreach (var point in originalPath)
        {
            polygon.Add(new ClipperLib.IntPoint(point.X, point.Y));
        }

        ClipperLib.ClipperOffset co = new ClipperLib.ClipperOffset();
        co.AddPath(polygon, ClipperLib.JoinType.jtRound, ClipperLib.EndType.etClosedPolygon);

        List<List<ClipperLib.IntPoint>> solution = new List<List<ClipperLib.IntPoint>>();
        co.Execute(ref solution, offset);

        foreach (var offsetPath in solution)
        {
            foreach (var offsetPathPoint in offsetPath)
            {
                resultOffsetPath.Add(new Point(Convert.ToInt32(offsetPathPoint.X), Convert.ToInt32(offsetPathPoint.Y)));
            }
        }

        return resultOffsetPath;
    }

2

另一种选择是使用boost :: polygon-缺少文档,但是您应该发现实际上实现缓冲的方法resizebloat以及重载+=运算符。因此,例如,将某个多边形(或一组多边形)的大小增加某个值可以很简单:

poly += 2; // buffer polygon by 2

我不明白您应该如何使用boost :: polygon做任何事情,因为它仅支持整数坐标?假设我有一个普通的(浮点坐标)多边形,我想扩展它-该怎么办?
大卫·多里亚

@DavidDoria:这取决于坐标所需的分辨率/精度和动态范围,但是您可以使用具有适当缩放比例的32位或64位整数。顺便说一句,我过去曾经(偶然地)使用带有浮点坐标的boost :: polygon,它似乎可以正常工作,但是它可能不是100%健壮的(文档警告!)。
Paul R

我会同意“大多数情况下都能正常工作” :)。我试过这个:ideone.com/XbZeBf,但是它不能编译-有什么想法吗?
大卫·多里亚

我没有看到任何明显错误的东西,但是就我而言,我使用的是直线专业化(polygon_90),所以我不知道这是否有所不同。自从我玩了已经有几年了。
Paul R

好的-现在又回来了-您只能使用+=多边形,而不能使用单个多边形。尝试使用std :: vector多边形。(当然,向量只需要包含一个多边形)。
Paul R

1

根据@ JoshO'Brian的建议rGeos,该R语言中的软件包似乎实现了该算法。请参阅rGeos::gBuffer



0

我使用简单的几何:矢量和/或三角学

  1. 在每个角上找到中间向量和中间角度。中间向量是由拐角边缘定义的两个单位向量的算术平均值。中间角度是边缘定义的角度的一半。

  2. 如果您需要按每个边的d数量来扩展(或收缩)多边形;您应该以d / sin(midAngle)的量外出(进入)以获得新的拐角点。

  3. 对所有角落重复此操作

***小心自己的方向。使用定义拐角的三个点进行CounterClockWise测试;找出出路或进路。

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.