【问题标题】:An algorithm for inflating/deflating (offsetting, buffering) polygons膨胀/放气(偏移、缓冲)多边形的算法
【发布时间】:2010-11-09 17:17:21
【问题描述】:

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

要求是新的(膨胀的)多边形的边缘/点与旧的(原始)多边形的距离相同(在示例图片上它们不是,因为那时它必须使用弧来膨胀顶点,但让我们暂时忘掉它;))。

我正在寻找的数学术语实际上是向内/向外的多边形偏移。 +1 balint 指出这一点。替代命名是多边形缓冲

我的搜索结果:

这里有一些链接:

【问题讨论】:

  • 这根本不是一个小问题:如果通货紧缩/通货膨胀很小,不会发生任何严重的事情,但在某些时候,顶点会消失。可能以前已经做过,所以我想说:使用别人的算法,不要构建自己的。
  • 确实,如果你的多边形一开始是凹的(如上面的例子),你必须决定在天真的算法想要制作一个自相交的“多边形”时应该发生什么。 ..
  • 是的,主要问题是多边形的凹入部分,这就是复杂性所在。我仍然认为计算何时必须消除某个顶点不应该是一个问题。主要问题是这需要什么样的渐近复杂度。
  • 您好,这也是我的问题,只是我需要在 3D 中执行此操作。是否有替代论文arxiv.org/pdf/0805.0022.pdf 中描述的三维多面体直骨架方法?

标签: algorithm geometry polygon computational-geometry buffering


【解决方案1】:

我想我可以简单地提一下我自己的多边形裁剪和偏移库 - Clipper

虽然Clipper 主要用于多边形裁剪操作,但它也可以进行多边形偏移。该库是用 Delphi、C++ 和 C# 编写的开源免费软件。它有一个非常不受限制的Boost 许可证,允许它在免费软件和商业应用程序中免费使用。

可以使用三种偏移样式之一执行多边形偏移 - 方形、圆形和斜接。

【讨论】:

  • 非常酷! 2年前你在哪里? :) 最后我不得不实现自己的偏移逻辑(并且为此浪费了很多时间)。顺便说一句,您使用什么算法进行多边形偏移?我用的是草火。你会处理多边形中的洞吗?
  • 2 年前,我一直在寻找一个不错的多边形裁剪解决方案,它不会受到棘手的许可证问题的困扰:)。边缘偏移是通过为所有边缘生成单位法线来实现的。边缘连接由我的多边形剪裁器整理,因为这些重叠交叉点的方向与多边形的方向相反。孔肯定会像自相交多边形等一样被处理。它们的类型或数量没有限制。另请参阅此处的“通过计算绕组数进行多边形偏移”:me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf
  • 哇!不要以为这个问题是“被遗忘的”!我上周看过这里——我没想到会回到这里!非常感谢!
  • Clipper 关于多边形缓冲的文档在这里:angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/…
  • 对于任何想要这样做的人,另一种选择是使用 GEOS,如果你使用 python,GEOS 的包装器 Shapely。一个非常漂亮的例子:toblerity.github.com/shapely/manual.html#object.buffer
【解决方案2】:

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

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

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

正如在其他 cmets 中所指出的那样,根据您计划对多边形“膨胀/收缩”的程度,您最终可能会获得不同的输出连接性。

从计算的角度来看:一旦你有了直骨架,就应该能够相对容易地构造偏移多边形。开源和(非商业免费)CGAL 库有一个实现这些结构的包。请参阅this code example 以使用 CGAL 计算偏移多边形。

package manual 应该为您提供一个很好的起点,让您了解如何构建这些结构,即使您不打算使用 CGAL,并且包含对具有数学定义和属性的论文的引用:

CGAL manual: 2D Straight Skeleton and Polygon Offsetting

【讨论】:

    【解决方案3】:

    对于这些类型的事情,我通常使用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 坐标,其他一切都相同。示例:

    【讨论】:

      【解决方案4】:

      听起来你想要的是:

      • 从一个顶点开始,沿相邻边逆时针朝向。
      • 将边缘替换为新的平行边缘,该边缘放置在距离旧边缘“左侧”d 处。
      • 对所有边重复。
      • 找到新边的交点以获得新的顶点。
      • 检测您是否已成为交叉多边形并决定如何处理。可能在交叉点添加一个新顶点并删除一些旧顶点。我不确定是否有更好的方法来检测这一点,而不仅仅是比较每对非相邻边以查看它们的交点是否位于两对顶点之间。

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

      我不知道这个算法是否有名称、网络上的示例代码或可怕的优化,但我认为它描述了你想要的。

      【讨论】:

        【解决方案5】:

        在 GIS 世界中,有人为此任务使用负缓冲: http://www-users.cs.umn.edu/~npramod/enc_pdf.pdf

        JTS library 应该为您执行此操作。缓冲操作见文档:http://tsusiatsoftware.net/jts/javadoc/com/vividsolutions/jts/operation/buffer/package-summary.html

        有关粗略概述,另请参阅开发人员指南: http://www.vividsolutions.com/jts/bin/JTS%20Developer%20Guide.pdf

        【讨论】:

          【解决方案6】:

          每一行都应该将平面分割为“内部”和“轮廓”;您可以使用通常的内积方法找到它。

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

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

          通过删除任何相交部分来清理新顶点。 -- 我们这里有几个案例

          (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算法吗?
          【解决方案7】:

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

          1. 做一个triangulation,它不必是delaunay——任何三角测量都可以。

          2. 膨胀每个三角形——这应该是微不足道的。如果您按逆时针顺序存储三角形,只需将线移动到右侧并进行交叉。

          3. 使用修改后的Weiler-Atherton clipping algorithm合并它们

          【讨论】:

          • 如何准确地给三角形充气?您的输出是否取决于三角测量?用这种方法你可以处理缩小多边形的情况吗?
          • 你确定这种方法真的适用于多边形膨胀吗?当多边形的凹入部分膨胀到必须消除某些顶点的程度时会发生什么。问题是:当您查看多边形之后的三角形会发生什么时。膨胀,三角形没有膨胀,而是扭曲了。
          • Igor:Weiler-Atherton 裁剪算法可以正确处理“必须消除某些顶点”的情况;
          • @balint:膨胀一个三角形是微不足道的:如果您以正常顺序存储顶点,则右侧总是“向外”。只需将这些线段视为线,将它们向外移动,然后找到相互作用——它们是新的顶点。对于三角剖分本身,再想一想,德劳内三角剖分可能会给出更好的结果。
          • 我认为这种方法很容易产生不好的结果。即使对于使用对角线进行四边三角化的简单示例也是如此。对于你得到的两个放大的三角形:img200.imageshack.us/img200/2640/counterm.png,它们的联合并不是你想要的。我看不出这个方法有什么用。
          【解决方案8】:

          非常感谢 Angus Johnson 的 Clipper 库。 在剪辑器主页http://www.angusj.com/delphi/clipper.php#code 上有很好的代码示例用于进行剪辑。 但我没有看到多边形偏移的示例。所以我想如果我发布我的代码,它可能对某人有用:

              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;
              }
          

          【讨论】:

            【解决方案9】:

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

            poly += 2; // buffer polygon by 2
            

            【讨论】:

            • 我不明白你应该如何用 boost::polygon 做任何事情,因为它只支持整数坐标?假设我有一个通用(浮点坐标)多边形,我想扩展它 - 我会怎么做?
            • @DavidDoria:这取决于您的坐标所需的分辨率/精度和动态范围,但您可以使用具有适当比例因子的 32 位或 64 位整数。顺便说一句,我过去(不小心)使用了带有浮点坐标的 boost::polygon,它似乎工作正常,但它可能不是 100% 健壮的(文档警告不要这样做!)。跨度>
            • 我可以接受“它会在大多数情况下工作”:)。我试过这个:ideone.com/XbZeBf 但它没有编译 - 有什么想法吗?
            • 我看不出有什么明显的错误,但就我而言,我使用的是直线专业化(polygon_90),所以我不知道这是否会有所不同。不过,我玩这个已经好几年了。
            • 好的 - 它现在回到我的身边 - 你只能将 += 用于多边形 set,而不能用于单个多边形。尝试使用多边形的 std::vector 。 (当然向量只需要包含一个多边形)。
            【解决方案10】:

            根据@JoshO'Brian 的建议,R 语言中的rGeos 包似乎实现了此算法。见rGeos::gBuffer

            【讨论】:

              【解决方案11】:

              有几个可以使用的库(也可用于 3D 数据集)。

              1. https://github.com/otherlab/openmesh
              2. https://github.com/alecjacobson/nested_cages
              3. http://homepage.tudelft.nl/h05k3/Projects/MeshThickeningProj.htm

              您还可以找到这些库的相应出版物,以更详细地了解算法。

              最后一个依赖最少,自包含,可以读入.obj文件。

              最好的祝愿, 斯蒂芬

              【讨论】:

              • 这是否适用于内部偏移多边形? (负距离??)
              • @RudyVanDrie 我会说,是的,但请试一试。
              【解决方案12】:

              我使用简单的几何:向量和/或三角函数

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

              2. 如果您需要将多边形扩展(或收缩)每条边的 d 量;你应该向外(向内)移动 d/sin(midAngle) 的量以获得新的角点。

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

              *** 小心你的方向。使用定义角的三个点进行 CounterClockWise 测试;找出哪条路是出去还是进来。

              【讨论】:

                猜你喜欢
                • 2010-11-09
                • 2013-08-29
                • 2017-12-22
                • 1970-01-01
                • 2011-11-24
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多