确定线段集合的非凸包

时间:2012-03-22 20:44:44

标签: python algorithm language-agnostic geometry computational-geometry

我有一个计算几何问题,我认为应该有一个相对简单的解决方案,但我无法弄明白。

我需要确定由多个线段定义的区域的非凸轮廓。

我知道各种非凸壳体算法(例如alpha形状),但我不需要完全通用的算法,因为线段在大多数情况下定义了一个独特的解决方案。


正如@ Jean-FrançoisCorbett指出的,有些情况下有多种解决方案。我显然需要更多地考虑我的定义。

但是,我要做的是逆向工程并使用专有文件格式,以便我可以对自己和其他人收集的数据进行基本分析。文件格式很简单,但确定用于定义边界的算法要困难得多。

在许多可能导致非唯一解决方案的边缘情况下,导致相关软件在没有警告的情况下崩溃或者无法读取文件。

因此,当存在多个解决方案时,要么生成一个可接受的解决方案,要么能够确定存在多个解决方案,这是可以接受的。


问题定义:

多边形的轮廓不应该穿过任何线段,并且应该由连接所有线段端点的线组成。所有段必须完全位于多边形的边界内或沿着多边形的边界。大纲中不能使用多个端点(通过在需要多边形关闭的软件库的末尾添加第一个点来忽略“关闭”多边形。)。

如果有多个解决方案符合此标准,那么这些解决方案中的任何一个都是可以接受的。 (能够确定解决方案何时是非唯一的,但这不是绝对必要的,这将是很好的。)


示例:

举个例子,我有以下几点: Segments Defining the Area

我想描述以下几个方面: Desired Outline

它也适用于非交叉段。 E.g。

enter image description here enter image description here

我认为(?)在任何一种情况下都有一个独特的解决方案,符合之前的标准。 (编辑:一般来说,没有一个独特的解决方案,正如@ Jean-FrançoisCorbett所指出的那样。但是,我仍然对能够产生一种可接受的解决方案的算法感兴趣。)

测试用例

对于测试用例,这里是生成上述数字的代码。我在这里使用python,但这个问题与语言无关。

import matplotlib.pyplot as plt

def main():
    test1()
    test2()
    plt.show()

def test1():
    """Intersecting segments."""
    segments = [[(1, 1), (1, 3)],
                [(3.7, 1), (2, 4)],
                [(2, 0), (3.7, 3)],
                [(4, 0), (4, 4)],
                [(4.3, 1), (4.3, 3)],
                [(0, 2), (6, 3)]]

    desired_outline = [segments[0][0], segments[5][0], segments[0][1], 
                       segments[1][1], segments[2][1], segments[3][1],
                       segments[4][1], segments[5][1], segments[4][0],
                       segments[3][0], segments[1][0], segments[2][0],
                       segments[0][0]]

    plot(segments, desired_outline)

def test2():
    """Non-intersecting segments."""
    segments = [[(0, 1), (0, 3)],
                [(1, 0), (1, 4)],
                [(2, 1), (2, 3)],
                [(3, 0), (3, 4)]]

    desired_outline = [segments[0][0], segments[0][1], segments[1][1],
                       segments[2][1], segments[3][1], segments[3][0], 
                       segments[2][0], segments[1][0], segments[0][0]]

    plot(segments, desired_outline)


def plot(segments, desired_outline):
    fig, ax = plt.subplots()
    plot_segments(ax, segments)
    ax.set_title('Segments')

    fig, ax = plt.subplots()
    ax.fill(*zip(*desired_outline), facecolor='gray')
    plot_segments(ax, segments)
    ax.set_title('Desired Outline')

def plot_segments(ax, segments):
    for segment in segments:
        ax.plot(*zip(*segment), marker='o', linestyle='-')
    xmin, xmax, ymin, ymax = ax.axis()
    ax.axis([xmin - 0.5, xmax + 0.5, ymin - 0.5, ymax + 0.5])

if __name__ == '__main__':
    main()

有什么想法吗?

我开始怀疑其结果我试图再现该软件使用径向扫描算法在某种“内部”的坐标系(例如坐标与系统x-prime和{{1沿着由点的扩展定义的主轴进行缩放和旋转。这使得问题更加“圆形”。)然而,这产生了在许多情况下轮廓与线段相交的解。很容易发现它并从那里强力发出它,但肯定有更好的方法吗?

2 个答案:

答案 0 :(得分:17)

  1. 选择一个安全的起点。可以是例如最大x的端点。
  2. 三月沿线段。
  3. 遇到任何交叉路口时,请始终左转并沿此新路段行进。
  4. 遇到端点时,请记录它。转到2.
  5. 当您返回起点时停止。现在,您记录的端点列表构成了凹形船体顶点的有序列表。
  6. 注意:如果存在不与任何其他线段相交的“自由浮动”外围线段,则会失败。但是,您指定“条形图唯一定义解决方案”,这将排除此失败条件。 (边远部分使两种不同的解决方案成为可能。)

    编辑 ...或者更确切地说,外围细分可以制作两种截然不同的解决方案 - 具体取决于确切的布局。证明:下面是一个例子,我添加的黄色部分使得两种解决方案成为可能(蓝色和灰色可怕的手绘线)。如果黄色部分的方向垂直于它现在绘制的方式,那么只有一个解决方案是可能的。听起来你的问题定义不明确。

    enter image description here

    编辑实际上,如果你的网段集合是“非常凹的”,也可能会失败,也就是说,如果在你的一堆网段隐藏的角落里隐藏着端点。在下图中,我添加了一个黑色部分。我的算法会非法将其端点连接到另一个端点(灰色虚线)。我会留下我的答案,万一其他人倾向于建立它。

    编辑之后再多想一想: 即使在“非常凹”的情况下,这个解决方案肯定会给你所有您的凹形船体的点以正确的顺序排列,但它们可能会散布着额外的不合适的点,例如黑色的点。所以可能会有太多的点。

    当然,答案是做一些修剪。这将是相当复杂的修剪,特别是如果你可以有多个连续的“隐藏点”,如黑色,所以我没有考虑智能算法。但即使是盲目的,蛮力也是可行的。每个点都可以被接受或拒绝(布尔值),因此如果你的凹形船体中有 N 正确排序的候选点,那么只有2 ^ N 可以检查。这样,方式比原始排列问题的蛮力更少的可能性,这将有SUM of (n!/(n-k)!) for k=1:(n-1)种可能性(请原谅我的注释)。因此,该算法显着缩小了您的问题。

    我认为这是要走的路。

    enter image description here

答案 1 :(得分:2)

不是一个完全充实的想法,但无论如何:假设你开始使用圆形扫描算法来进行凸包(你在那里排序,然后按照它们从中心点的角度处理点)。如果所有的点都在这个船体中,你就完成了。如果没有,那么你必须“收紧”船体以包括这些点。这些点中的每一个都是凸包的候选者,并且因为它们打破了凸起而被移除。有时(与第一个例子中的顶部紫色点一样),我们可以简单地将它们留在。我们不能的地方,因为船体的新段穿过一段(从底部绿色到底部紫色)第一个例子,假设底部浅绿色点在绿色之前得到处理),修复更多一些(并且我没有充实的部分,并且是问题的最新编辑中提到的部分)。