如何从一组线中找到包围点的多边形?

时间:2013-04-22 06:36:00

标签: algorithm geometry computational-geometry planar-graph

我有一组非相交线,其中一些在顶点连接。我试图找到包含给定点的最小多边形(如果存在)。因此,在下面的图像中,在所有线段的列表中,给定红色点,我想只获得蓝色线段。我正在使用Python,但可能适应其他语言的算法;我不知道这个问题叫什么。

Example

4 个答案:

答案 0 :(得分:5)

首先,删除至少有一个空闲端点的所有线段,而不是与任何其他段重合。反复这样做,直到没有这样的段。

现在你有一个很好地细分为多边形区域的飞机。

找到最接近您的观点的细分。请注意,不是最近的终点,而是最近的段。找出您需要的段的方向(您的点应位于有向段的右侧)。转到端点,向右转(即,取出您来自的那个段,逆时针计数)。继续前往下一个终点并向右转,直到再次到达最近的段。

然后,检查多边形是否包含给定点。如果不是,那么你找到了一个“岛屿”;删除该多边形及其所属的整个连接组件,然后再次选择最近的段重新开始。通过简单的DFS可以找到连接的组件。

这为您提供了一个顺时针方向的多边形。如果你想要逆时针,这通常是软件和文献中的默认“正”方向,从左边的点开始,在每个交叉点左转。

如果给定端点,您可以快速找到与其一起发生的所有段,这肯定会有所帮助。

答案 1 :(得分:1)

这实际上只是@n.m.答案的实现。 这是我在赏金到期之前得到的;它完全没有经过测试。

def smallestPolygon(point,segments):
    """
    :param point: the point (x,y) to surrond
    :param segments: the line segments ( ( (a,b),(c,d) ) , . . . )
    that will make the polygon
    (assume no duplicates and no intersections)
    :returns: the segments forming the smallest polygon containing the point
    """
    connected = list(segments)

    def endPointMatches(segment1,segment2):
        """
        Which endpoints of segment1 are in segment2 (with (F,F) if they're equal)
        """
        if ( segment1 == segment2 or segment1 == segment2[::-1] ):
            return ( False, False )
        return ( segment1[0] in segment2 , segment1[1] in segment2 )

    keepPruning = True
    while ( keepPruning ):
        keepPruning = False
        for segment in connected:
            from functors import partial
            endPointMatcher = partial(endPointMatches,segment1=segment)
            endPointMatchings = map(endPointMatcher,connected)
            if ( not and(*map(any,zip(*endPointMatchings))) ):
                connected.remove(segment)
                keepPruning = True

    def xOfIntersection(seg,y):
        """
        :param seg: a line segment ( (x0,y0), (x1,y1) )
        :param y: a y-coordinate
        :returns: the x coordinate so that (x,y) is on the line through the segment
        """
        return seg[0][0]+(y-seg[0][1])*(seg[1][0]-seg[0][0])/(seg[1][1]-seg[0][1])

    def aboveAndBelow(segment):
        return ( segment[0][1] <= point[1] ) != ( segment[1][1] <= point[1] )

    # look for first segment to the right
    closest = None
    smallestDist = float("inf")
    for segment in filter(aboveAndBelow,connected):
        dist = xOfIntersection(segment,point[1])-point[0]
        if ( dist >= 0 and dist < smallestDist ):
            smallestDist = dist
            closest = segment

    # From the bottom of closest:
    # Go to the other end, look at the starting point, turn right until
    # we hit the next segment.  Take that, and repeat until we're back at closest.
    # If there are an even number of segments directly to the right of point[0],
    # then point is not in the polygon we just found, and we need to delete that
    # connected component and start over
    # If there are an odd number, then point is in the polygon we found, and we
    # return the polygon

答案 2 :(得分:0)

如果您使用相同的线和不同的点多次执行此操作,则需要进行预处理以找出所有多边形。然后它很简单:从点到无穷大画一条线(从概念上讲)。每次越过一条线时,增加该线所属的每个多边形的交叉计数。最后,具有奇数交叉计数的第一个多边形是最小的包围多边形。由于任何任意行都可以与其他行一样好(它甚至不需要是直线),通过绘制垂直或水平线来简化算术,但要注意穿越实际端点。

您可以在不跨预处理的情况下通过在跨越每条线时创建多边形来执行此操作。这基本上减少到了n.m.的算法,但没有进行所有特殊情况检查。

请注意,一条线可以属于两个多边形。事实上,它可能属于更多,但我不清楚你会怎么说:考虑以下几点:

+---------------------------+
|                           |
|   +-------------------+   |
|   |                   |   |
|   |   +-----------+   |   |
|   |   |           |   |   |
|   |   |           |   |   |
+---+---+-----------+---+---+

答案 3 :(得分:0)

<强>方法 我建议将输入解释为PSLG,G,它由顶点和边组成。然后你的问题减少到找到被点p击中的G的面。这是通过将光线从p射向某个方向以找到面部边界的边缘并在某个方向上穿过面部边界来完成的。然而,第一个边缘命中可能是一个没有被p击中的面,但是它本身被p击中的面所包围。因此,我们可能需要沿着p发出的光线进行搜索。

实施细节。 在下面的代码中,我向东方向拍摄一条光线并沿顺时针方向绕着面部运行,即,在每个顶点处,我采取下一个逆时针边缘,直到我再次在第一个顶点处结束。生成的面将作为G的顶点序列返回。

如果你想返回simple polygon,那么你必须通过修剪G中的树来清理输入图G,这样只剩下简单的面。

def find_smallest_enclosing_polygon(G, p, simple=False):
  """Find face of PSLG G hit by point p. If simple is True
     then the face forms a simple polygon, i.e., "trees"
     attached to vertices are pruned."""

  if simple:
    # Make G comprise simple faces only, i.e., all vertices
    # have degree >= 2.
    done = False
    while not done:
      done = True
      for v in [v in vertices if degree(v) <= 1]:
        # Remove vertex v and all incident edges
        G.remove(v)
        done = False

  # Shoot a ray from p to the east direction and get all edges.      
  ray = Ray(p, Vector(1, 0))
  for e in G.iter_edges_hit(ray):

    # There is no enclosing face; p is in the outer face of G
    if e is None:
      return None

    # Let oriented edge (u, v) point clockwise around the face enclosing p
    u, v = G.get_vertices(e)
    if u.y < v.y
       u, v = v, u

    # Construct the enclosing face in clockwise direction
    face = [u, v]
    # While not closed
    while face[-1] != face[0]:
      # Get next counter-clockwise edge at last vertex at last edge.
      # If face[-1] is of degree 1 then I assume to get e again.
      e = G.get_next_ccw_edge(face[-2], face[-1])
      face.append(G.get_opposite_vertex(e, face[-1]))

    # Check whether face encloses p.
    if contains(face, p):
       return face

  return None

<强>复杂度。 设n表示顶点数。注意,在PSLG中,边数是O(n)。修剪部分可以按照上面实现的方式花费O(n ^ 2)时间。但是,通过识别1度顶点并保持遍历,可以在O(n)时间内进行。

光线交叉例程可以在O(n)时间内平凡地实现。构造面需要O(m)时间,其中m是构造的多边形的大小。我们可能需要测试多个多边形,但所有多边形的大小总和仍然在O(n)中。测试包含(face,p)可以通过检查面是否包含G.iter_edges_hit(ray)返回的不均匀边数来完成,即在O(m)时间内。

通过一些预处理,您可以建立一个数据结构,通过计算几何中的经典point location算法找到O在(O n)时间内被p命中的面,并且您可以预先存储生成的多边形,和/或非简单的案例。