【问题标题】:How to determine which points are inside of a polygon and which are not (large number of points)?如何确定哪些点在多边形内,哪些不在(大量点)?
【发布时间】:2012-01-12 10:59:55
【问题描述】:

我有大量数据点(100,000+)存储在二维 numpy 数组中(第一列:x 坐标,第二列:y 坐标)。我还有几个一维数组存储每个数据点的附加信息。我现在想从这些一维数组的子集创建图,其中仅包含给定多边形中的点。

我想出了以下既不优雅也不快速的解决方案:

#XY is the 2D array.
#A is one of the 1D arrays.
#poly is a matplotlib.patches.Polygon

mask = np.array([bool(poly.get_path().contains_point(i)) for i in XY])

matplotlib.pylab.hist(A[mask], 100)
matplotlib.pylab.show()

您能帮我改进这段代码吗?我尝试使用 np.vectorize 而不是列表理解,但无法使其正常工作。

【问题讨论】:

    标签: python numpy matplotlib


    【解决方案1】:

    使用matplotlib.nxutils.points_inside_poly,它实现了非常高效的测试。

    matplotlib FAQ 上这个 40 年历史算法的示例和进一步解释。

    更新:请注意,points_inside_poly 自 matplotlib 版本 1.2.0 起已弃用。请改用matplotlib.path.Path.contains_points

    【讨论】:

    • 我知道 pnpoly 页面。回到那个时候(2 年前,我拿了 fortran 代码并用 f2py 包装了它......)我不知道它现在可以作为 mpl 模块使用!谢谢。
    • 只是指出这个函数是deprecated as of matplotlib 1.2.0 - 文档告诉你改用matplotlib.path.Path.contains_points
    • @ali_m 感谢提醒,我更新了答案。
    【解决方案2】:

    恐怕我不熟悉您正在使用的库,但我认为我对您可以使用的算法有一个合理的想法,我将介绍如何使用 vanilla python 实现它,然后我相信您可以使用这些库对其进行改进并实现它。此外,我并不是说这是实现这一目标的最佳方式,但我想以相当快的速度得到我的回应,所以就这样吧。


    现在,这个想法来自于在算法中使用两个向量的叉积来找到一组点的凸集,例如Graham's Scan。假设我们有两个点 p1 和 p2,它们定义了点向量 p1p2,从原点 (0,0) 到 (x1, y1) 和 (x2, y2) 分别。 p1 x p2 的叉积得到第三个向量 p3,它垂直于 p1 p2,大​​小由向量包围的平行四边形的面积给出。

    一个非常有用的结果是矩阵的行列式

    / x1, x2 \
    \ y1, y2 /
    

    ...即 x1*y2 - x2*y1 给出向量 p3 的大小,符号表示 p3 是否“从”平面中“出来”或“进入”它。这里的关键点是,如果这个量级是正的,那么 p2 就在 p1 的“左边”,如果它是负的,那么 p2 就是“ p1 的右侧”。

    希望这个 ascii 艺术示例会有所帮助:

        . p2(4, 5)
       /
      /
     /
    /_ _ _ _ _. p1(5, 0)
    

    x1*y2 - x2*y1 = 5*4 - 0*5 = 20 所以 p2p1

    的“左边”

    最后谈谈为什么这对我们有用!如果我们有一个多边形的顶点列表和图中的一组其他点,那么对于多边形的每条边,我们都可以获得该边的向量。我们还可以获得将起始顶点连接到图中所有其他点的向量,并通过测试这些向量是位于边的左侧还是右侧,我们可以消除每条边的一些点。在该过程结束时未删除的所有点都是多边形内的那些点。无论如何,写一些代码来更清楚地理解这一点!


    如果您以逆时针方向绘制多边形,则按照您访问它们的顺序获取多边形的顶点列表,例如某个五边形可能是:

    poly = [(1, 1), (4, 2), (5, 5), (3, 8), (0, 4)]

    获取一个包含图中所有其他点的集合,我们将逐渐从该集合中移除无效点,直到过程结束时留下的正是多边形内的那些点。

    points = set(['(3, 0), (10, -2), (3,3), ...])

    代码本身的主要部分实际上非常紧凑,因为我花了多长时间来写它是如何工作的。 to_right 接受两个表示向量的元组,如果 v2 位于 v1 的右侧,则返回 True。然后,循环将遍历多边形的所有边缘,如果点位于任何边缘的右侧,则从工作集中删除它们。

    def to_right(v1, v2):
        return (v1[0]*v2[1] - v1[1]*v2[0]) < 0
    
    for i in range(len(poly)):
        v1 = poly[i-1]
        v2 = poly[i]
        for p in points:
            if(to_right(v2-v1, p-v1)):
                points.remove(p)
    

    编辑:为了澄清,如果它们在右边而不是左边,它们被删除的事实与指定多边形顶点的顺序有关。如果它们按顺时针顺序排列,则您可能希望消除左侧点。对于这个问题,我目前没有特别好的解决方案。


    无论如何,希望我对这些东西是正确的,即使不是 OP,它也会对某人有所帮助。该算法的渐近复杂度为 O(mn),其中 n 是图中的点数,m 是多边形的顶点数,因为在最坏的情况下,所有点都位于多边形内,我们必须检查每个点对于每条边,都不会被删除。

    【讨论】:

    • 不幸的是,这并不是我想知道的,因为算法本身已经在 matplotlib 库中实现了。但是阅读起来非常有趣,我现在对它的工作原理有了一个很好的了解。感谢您的意见!
    • 没问题,今年我的算法课程中有一些关于这样的几何算法的材料,尝试回答你的问题确实帮助我自己更好地理解它们。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-14
    • 2020-10-15
    • 2014-04-05
    • 2021-12-02
    • 1970-01-01
    相关资源
    最近更新 更多