繁体   English   中英

检查线段是否被多边形列表覆盖的优化算法

[英]Optimized algorithm to check if a line segment is covered by a list of polygons

使用sympy库解决了这个问题,但是,我相信我的解决方案不是最优的,我正在寻找更好的算法。 欢迎使用比我使用的数据结构更好的数据结构的答案,但是,其他用作sympy替代品的库(例如: shapely )不会被视为正确答案。

我正在尝试解决涉及线段和一堆多边形的问题。 我用一个list来保存我所有的多边形。 然后在 2D 空间上绘制一个多边形,并进行检查以查看整个或部分线段是否被当前多边形遮挡。 如果是,则被多边形遮挡的线段部分被截断,仅保留当前多边形的可见部分。

在下一步中,将绘制列表中的另一个多边形,但这次检查是对上一步生成的线段的可见部分进行的。 通过这种方式,被多边形遮挡的线段的所有部分都被丢弃,最后,我留下了一个要么为空要么不为空的list 如果list为空,则表示所有多边形都覆盖了线段,返回True 如果不是,这意味着有一部分线是可见的,并且没有被所有多边形覆盖,因此返回False

我希望下面的屏幕截图使问题陈述更清楚。

代码返回 True 的情况

在上述情况下,您可以看到所有多边形都没有完全覆盖线段,因此,此问题的代码将返回True

代码返回 False 的情况

在此图像中,所有多边形完全覆盖了线段,因此代码将在此处返回False

编辑:

@MattTimmermans 提出了一个很好的观点,它提醒我注意一些可能的边缘情况,其中线段可能位于多边形的边界上。 在这种情况下,当它的实际期望行为是返回True时,encloses encloses() sympy 方法将返回False 我已经修改了代码以解决这种极端情况。 此外,蓝色多边形的边界正好在线段上,调用intersect方法将导致返回Segment2D对象而不是点。 为了解决这个问题,我创建了一个名为sortAllPoints()的新函数。

下面的屏幕截图将更好地说明这种情况。 边缘案例

这是我对使用蛮力技术定义的问题陈述的解决方案。

from sympy import Polygon, Point, Segment2D 

def sortAllPoints(intersectionSet, pointsOnLine, currentLine, polygon):
    # Do nothing if it is an empty set
    if not isinstance(intersectionSet, sympy.sets.sets.EmptySet):
        # Check if the intersection is a sympy.Segment2D object.
        if isinstance(intersectionSet, Segment2D):
            pointsOnLine.extend([intersectionSet.p1, intersectionSet.p2])
        # Check if the intersection is a sympy.sets.set.Union object. 
        elif isinstance(intersectionSet, sympy.sets.sets.Union):
            deserializedPoints = []
            for entity in intersectionSet.args:
                if isinstance(entity, Segment2D):
                    deserializedPoints.append(entity.p1)
                    deserializedPoints.append(entity.p2)
                elif isinstance(entity, Point):
                    deserializedPoints.append(entity)
            pointsOnLine.extend(deserializedPoints)
        else:
            pointsOnLine.extend(iter(polygon.intersect(currentLine)))

    sortedPoints = sorted(pointsOnLine, key=lambda coordinates: (coordinates[0], coordinates[1]))
    return sortedPoints

def lineFragments(line, listPolygon):

    # line is of type sympy.Segment2D. 
    # listPolygon contains [sympy.Polygon]

    activeLine = line
    
    # Contains sympy.Segment2D objects. The length of this list determines whether this function should return True or False.
    visibleFragments = []
    
    # iterate through the list of sympy.Polygon objects.
    for index, polygon in enumerate(listPolygon):
        
        # This is a list of lists. This was done so particularly because I don't want to create a line segment between points that have already been processed. This helps me isolate and work with only the line segments that are visible from the previous iteration.
        allSortedPoints = []

        if index == 0:
            pointsOnLine = [activeLine.p1, activeLine.p2]
            # Get all the points where the polygon intersects with the line segment.
            pointsOnLine.extend(iter(polygon.intersect(activeLine)))
            sortedPoints = sortAllPoints(intersectionSet, pointsOnLine, activeLine, polygon)
            allSortedPoints.append(sortedPoints)
        
        # Break out of the loop if the line segment has been completely covered by the polygons before it. 
        elif not visibleFragments:
            return False

        else:
            # Find the intersection points between the polygon and all the visible parts of the line segment that were computed in the previous iteration.
            for lineFragment in visibleFragments:
                pointsOnLine = [lineFragment.p1, lineFragment.p2]
                pointsOnLine.extend(iter(polygon.intersect(lineFragment)))
                sortedPoints = sortAllPoints(intersectionSet, pointsOnLine, activeLine, polygon)
            allSortedPoints.append(sortedPoints)

        tempList = []

        for sortedPoints in allSortedPoints:
            for idx, currentPoint in enumerate(sortedPoints):
                if idx < len(sortedPoints) - 1:

                    # Create a line segment using the points.
                    segment = Segment2D(currentPoint.evalf(3), sortedPoints[idx + 1].evalf(3))
                    # Use the sympy.Polygon.encloses method to determine if the polygon covers the current fragment of the line segment.
                    if isinstance(segment, Segment2D) and not polygon.encloses(segment.midpoint) and not \
                            polygon.contains(segment.midpoint):
                        tempList.append(segment)
        
        # Update the list of visible line segments, so that there is no data carry over from iterations previous to this.
        visibleFragments = tempList

    if not visibleFragments:
        return False

    return True


def main():
    # Experiment 1.
    polygon1 = Polygon(Point(-10, 4), Point(-5, -1), Point(2, 2), Point(0, 4), Point(-5, 2))
    polygon2 = Polygon(Point(2, 2), Point(4, 2), Point(4, 3), Point(2, 3))
    # polygon2 = Polygon(Point(2, 2), Point(4, 2), Point(4, 4), Point(2, 4))
    line2 = Segment2D(Point(-11, 3), Point(5, 3))
    listPolygons1 = [polygon1, polygon2]
    booleanVal1 = lineFragments(line2, listPolygons1)
    print(booleanVal1)

    # Experiment 2.
    polygon3 = Polygon(Point(-1, 2), Point(-12, 2), Point(-12, 4), Point(-1, 4))
    polygon4 = Polygon(Point(3, 5), Point(3, 0), Point(5, 3))
    listPolygons2 = [polygon1, polygon2, polygon3, polygon4]
    # booleanVal2 = lineFragments(line2, listPolygons2)
    # print(booleanVal2)

    # Experiment 3.
    polygon5 = Polygon(Point(1, 5), Point(0, 3), Point(1, 0), Point(2, 3))
    polygon6 = Polygon(Point(2, 2), Point(9, 2), Point(9, 3), Point(4, 3), Point(4, 5), Point(3, 5), Point(3, 3),
                       Point(2, 3))
    listPolygons3 = [polygon1, polygon6, polygon2, polygon3, polygon4, polygon5]
    # print(polygon6.area)
    # line3 = Segment2D(Point(0, 6), Point(0, -6))
    # print(type(polygon2.intersect(line2)))
    # booleanVal3 = lineFragments(line2, listPolygons3)
    # print(booleanVal3)

我正在寻找一个带有代码的解决方案以及可以用来改善自己的阅读材料的链接。 如果你已经走到这一步,谢谢你的时间。

想象一下沿着包含目标线段的线行走。

留个柜台。 每次进入多边形时递增,每次离开多边形时递减。

如果对于线段内的任何非零距离,您的计数器都为 0,则线段不会完全被遮挡。

要将其变成解决您问题的有效算法:

  • 对于每个多边形,通过与线相交生成入口和出口位置列表(注意边界条件)
  • 合并所有入口位置列表并排序
  • 合并所有退出位置列表并排序
  • 浏览列表以检查上述情况。

多边形边数的总时间为 O(N log N)。

让 P 是您的多边形列表, a 和 b 是您的曲线/段的端点。 让 A 是包含 B 的类似定义的多边形列表。您应该能够足够有效地构建这些。 如果 A 或 B 为空,则返回 True。

假设A交集B中存在p。计算[a,b]与p的交集。 如果没有,则答案为 False,因为 P 完全包含曲线。 例如,如果有两个交点(注意它必须始终是偶数),例如 a' 和 b',则调用曲线 (a', b') 和 Pp 上的函数。 如果有 2k 个交叉点,做多个类似的调用。

假设 a 和 b 在不同的多边形内。 设a'是曲线上最远的顶点,使得在A中存在一些多边形p_a,使得[a,a']在p_a中。 以类似的方式定义 b'。 在 (a', b') 和 P 上调用函数。

执行示例(基于您的第二个示例):
用它们在水平轴上的位置来表示该点。
第一次调用: a = -11,b =5。 a 和 b 都包含在某个多边形中,但不同。 A = 紫色,B = 黄色。 a' = -1,b' = 3。
第二次调用: a = -1,b=3。 它们都包含在某个多边形中,但不同。 A = 紫色、红色; B = 黄色、蓝色。 a' = 1,b = 2。
第三次调用: a=1,b=2。 两者都包含在绿色中。 绿色多边形不与 (a,b) 相交,因此我们可能会返回 false。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM