简体   繁体   English

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

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

This problem was solved using the sympy library, however, I believe my solution is not optimal and I am looking for a better algorithm.使用sympy库解决了这个问题,但是,我相信我的解决方案不是最优的,我正在寻找更好的算法。 Answers that utilize better data structures than the ones I have used are welcomed however, other libraries used as a substitute for sympy (Ex: shapely ) will not be considered the correct answer.欢迎使用比我使用的数据结构更好的数据结构的答案,但是,其他用作sympy替代品的库(例如: shapely )不会被视为正确答案。

I am trying to solve a problem involving a line segment and a bunch of polygons.我正在尝试解决涉及线段和一堆多边形的问题。 I have used a list to hold all my polygons.我用一个list来保存我所有的多边形。 A polygon is then plotted on a 2D space and a check is conducted to see if the entire or part of the line segment is obscured by the current polygon.然后在 2D 空间上绘制一个多边形,并进行检查以查看整个或部分线段是否被当前多边形遮挡。 If yes, then the line segment part getting obscured by the polygon is chopped and only the visible part wrt to the current polygon remains.如果是,则被多边形遮挡的线段部分被截断,仅保留当前多边形的可见部分。

In the next step, another polygon from the list is plotted but this time the check is conducted on the visible part of the line segment produced from the previous step.在下一步中,将绘制列表中的另一个多边形,但这次检查是对上一步生成的线段的可见部分进行的。 In this way, all the parts of the line segment that are obscured by the polygons are dropped and finally, I am left with a list that is either empty or not.通过这种方式,被多边形遮挡的线段的所有部分都被丢弃,最后,我留下了一个要么为空要么不为空的list If the list is empty, it means that all the polygons cover the line segment and True is returned.如果list为空,则表示所有多边形都覆盖了线段,返回True If not, it means there is a portion of the line that is visible and not covered by all the polygons so a False is returned.如果不是,这意味着有一部分线是可见的,并且没有被所有多边形覆盖,因此返回False

I hope the screenshots below make the problem statement clearer.我希望下面的屏幕截图使问题陈述更清楚。

代码返回 True 的情况

In the case above, you can see all the polygons don't fully cover the line segment, hence, the code for this problem will return True .在上述情况下,您可以看到所有多边形都没有完全覆盖线段,因此,此问题的代码将返回True

代码返回 False 的情况

In this image, all the polygons completely cover the line segment, hence the code will return False here.在此图像中,所有多边形完全覆盖了线段,因此代码将在此处返回False

EDIT:编辑:

@MattTimmermans brought up a great point which alerted me to a couple of possible edge cases where a line segment can lie on the boundary of the polygon. @MattTimmermans 提出了一个很好的观点,它提醒我注意一些可能的边缘情况,其中线段可能位于多边形的边界上。 In such cases the encloses() sympy method will return False when the actual desired behaviour for it is to return True .在这种情况下,当它的实际期望行为是返回True时,encloses encloses() sympy 方法将返回False I have modified the code to account for that edge case.我已经修改了代码以解决这种极端情况。 Furthermore, the blue polygon's boundary lies exactly on the line segment and calling the intersect method will result in a Segment2D object being returned and not the points.此外,蓝色多边形的边界正好在线段上,调用intersect方法将导致返回Segment2D对象而不是点。 To account for this I have created a new function called sortAllPoints() .为了解决这个问题,我创建了一个名为sortAllPoints()的新函数。

This screenshot below will illustrate this scenario better.下面的屏幕截图将更好地说明这种情况。 边缘案例

Here is my solution to the problem statement defined using a brute force technique.这是我对使用蛮力技术定义的问题陈述的解决方案。

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)

Im looking for a solution with code as well as links to reading material that I can use to better myself.我正在寻找一个带有代码的解决方案以及可以用来改善自己的阅读材料的链接。 If you have got this far, thank you for your time.如果你已经走到这一步,谢谢你的时间。

Imagine walking along the line that contains the target line segment.想象一下沿着包含目标线段的线行走。

Keep a counter.留个柜台。 Increment it each time you enter a polygon, and decrement it every time you leave a polygon.每次进入多边形时递增,每次离开多边形时递减。

If your counter is at 0 for any non-zero distance inside the line segment, then the line segment is not entirely obscured.如果对于线段内的任何非零距离,您的计数器都为 0,则线段不会完全被遮挡。

To turn this into an efficient algorithm for your problem:要将其变成解决您问题的有效算法:

  • For each polygon, generate the lists of entry and exit positions by intersecting with the line (be careful of the boundary conditions)对于每个多边形,通过与线相交生成入口和出口位置列表(注意边界条件)
  • Merge all the entry position lists and sort合并所有入口位置列表并排序
  • Merge all the exit position lists and sort合并所有退出位置列表并排序
  • walk through the lists to check the above condition.浏览列表以检查上述情况。

The total time would be O(N log N) in the number of polygon edges.多边形边数的总时间为 O(N log N)。

Let P be your list of polygons, a and b be the end points of your curve/segments.让 P 是您的多边形列表, a 和 b 是您的曲线/段的端点。 Let A be the list of polygons that contains a, similar definition for B. You should be able to build these efficiently enough.让 A 是包含 B 的类似定义的多边形列表。您应该能够足够有效地构建这些。 If A or B is empty, return True.如果 A 或 B 为空,则返回 True。

suppose there exist p in A intersection B. Count the intersection of [a,b] with p.假设A交集B中存在p。计算[a,b]与p的交集。 If theres is none, the answer is False, as P entirely contains the curve.如果没有,则答案为 False,因为 P 完全包含曲线。 If there is for instance two intersection (notice it must always be an even number), say a' and b', call the function on curve (a', b') and Pp.例如,如果有两个交点(注意它必须始终是偶数),例如 a' 和 b',则调用曲线 (a', b') 和 Pp 上的函数。 If there are 2k intersection, do multiple similar calls.如果有 2k 个交叉点,做多个类似的调用。

Suppose a and b are inside different polygons.假设 a 和 b 在不同的多边形内。 Let a' be farthest vertex that is on the curve such that there exist some polygon p_a in A, such that [a,a'] is in p_a.设a'是曲线上最远的顶点,使得在A中存在一些多边形p_a,使得[a,a']在p_a中。 Define b' in similar way.以类似的方式定义 b'。 Call the function on (a', b') and P.在 (a', b') 和 P 上调用函数。

Example of execution (based on your second example):执行示例(基于您的第二个示例):
Let denote the point by their position on horizontal axis.用它们在水平轴上的位置来表示该点。
First call: a = -11, b =5.第一次调用: a = -11,b =5。 a and b are both contained in some polygon, but different. a 和 b 都包含在某个多边形中,但不同。 A = purple, B = yellow. A = 紫色,B = 黄色。 a' = -1, b'=3. a' = -1,b' = 3。
Second call: a = -1, b=3.第二次调用: a = -1,b=3。 They are both contained in some polygon, but different.它们都包含在某个多边形中,但不同。 A = purple, red; A = 紫色、红色; B = yellow, blue. B = 黄色、蓝色。 a' = 1, b=2. a' = 1,b = 2。
Third call: a=1, b=2.第三次调用: a=1,b=2。 Both are contained in green.两者都包含在绿色中。 Green polygon does not intersect (a,b), so we may return false.绿色多边形不与 (a,b) 相交,因此我们可能会返回 false。

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

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