首先,删除所有至少有一个自由端点且与其他线段不重合的线段。重复此操作直到不再存在这样的线段。
现在你已经将平面很好地分成了多边形区域。
找到距离你的点最近的线段。注意,不是最近的端点,而是最近的线段。找出你需要沿着线段的哪个方向(你的点应该在定向线段的右侧)。前往端点,向右转(也就是说,取从你来的那条线段开始顺时针计数的下一条线段)。继续前往下一个端点并向右转,直到再次遇到最近的线段。
然后,检查多边形是否包围给定点。如果没有,那么你找到了一个“孤岛”;删除该多边形及其所属的整个连通组件,并重新选择最近的线段。可以使用简单的DFS找到连通组件。
这会给你一个顺时针方向的多边形。如果你想要逆时针方向,这通常是软件和文献中默认的“正”方向,请从你左侧的点开始,在每个交点处向左转。
如果能够快速找到与端点相连的所有线段,那肯定会有所帮助。
方法. 我建议将输入解释为一个由顶点和边组成的 PSLG G。然后你的问题就变成了找到被点 p 打中的 G 的面。这可以通过从 p 射出一条射线来找到面边界的一条边,并沿着面边界向某个方向遍历来完成。然而,第一条被击中的边可能是一个没有被 p 击中的面,而是被 p 击中的面所包围的面。因此,我们可能需要沿着由 p 发出的射线继续搜索。
实现细节. 在下面的代码中,我朝东方射出一条射线,并按顺时针方向沿着面运行,即在每个顶点处取下一个逆时针边,直到再次回到第一个顶点。结果的面以 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)。测试contains(face, p)可以通过检查face是否包含由G.iter_edges_hit(ray)返回的奇数条边来完成,即在O(m)的时间内完成。
通过计算几何中传统的点定位算法,可以进行一些预处理,建立一个数据结构,在O(log n)的时间内找到被p击中的面,并可以事先存储简单和/或非简单情况下的结果多边形。
这实际上只是@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
+---------------------------+
| |
| +-------------------+ |
| | | |
| | +-----------+ | |
| | | | | |
| | | | | |
+---+---+-----------+---+---+