检查点是否在python中的多边形内的最快方法是什么


84

我发现了两种主要方法来查看点是否属于多边形内。一种是使用此处使用的光线跟踪方法,这是最推荐的答案,另一种是使用matplotlib path.contains_points(对我来说似乎有点晦涩)。我将不得不不断检查很多要点。有人知道这两个中的任何一个是否比另一个更可取,或者是否有更好的第三种选择?

更新:

我检查了这两种方法,matplotlib看起来更快。

from time import time
import numpy as np
import matplotlib.path as mpltPath

# regular polygon for testing
lenpoly = 100
polygon = [[np.sin(x)+0.5,np.cos(x)+0.5] for x in np.linspace(0,2*np.pi,lenpoly)[:-1]]

# random points set of points to test 
N = 10000
points = zip(np.random.random(N),np.random.random(N))


# Ray tracing
def ray_tracing_method(x,y,poly):

    n = len(poly)
    inside = False

    p1x,p1y = poly[0]
    for i in range(n+1):
        p2x,p2y = poly[i % n]
        if y > min(p1y,p2y):
            if y <= max(p1y,p2y):
                if x <= max(p1x,p2x):
                    if p1y != p2y:
                        xints = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
                    if p1x == p2x or x <= xints:
                        inside = not inside
        p1x,p1y = p2x,p2y

    return inside

start_time = time()
inside1 = [ray_tracing_method(point[0], point[1], polygon) for point in points]
print "Ray Tracing Elapsed time: " + str(time()-start_time)

# Matplotlib mplPath
start_time = time()
path = mpltPath.Path(polygon)
inside2 = path.contains_points(points)
print "Matplotlib contains_points Elapsed time: " + str(time()-start_time)

这使,

Ray Tracing Elapsed time: 0.441395998001
Matplotlib contains_points Elapsed time: 0.00994491577148

使用三角形而不是100个边的多边形获得了相同的相对差。我也会仔细检查一下,因为它看起来像是专门针对这些问题的软件包


由于matplotlib的实现是C ++,因此您可能会期望它更快。考虑到matplotlib的使用非常广泛,并且由于它是一个非常基本的功能-假定它工作正常(即使它看起来“晦涩难懂”)也很安全。最后但并非最不重要的一点:为什么不简单地进行测试?
sebastian 2016年

正如您所预料的那样,我通过测试更新了问题,matplotlib的速度要快得多。我很担心,因为matplotlib并不是我所看过的不同地方中最著名的响应,并且我想知道我是否正在忽略某些内容(或某些更好的软件包)。同样,对于这样一个简单的问题,matplotlib看起来也很重要。
鲁宾·佩雷斯·卡拉斯科

Answers:


98

你可以考虑匀称

from shapely.geometry import Point
from shapely.geometry.polygon import Polygon

point = Point(0.5, 0.5)
polygon = Polygon([(0, 0), (0, 1), (1, 1), (1, 0)])
print(polygon.contains(point))

从您提到的方法中,我只使用了第二个path.contains_points,并且工作正常。无论如何,根据测试所需的精度,我建议创建一个numpy bool网格,并将多边形内的所有节点都设置为True(否则为False)。如果要对很多点进行测试,则可能会更快(尽管请注意,这依赖于您在“像素”公差范围内进行测试):

from matplotlib import path
import matplotlib.pyplot as plt
import numpy as np

first = -3
size  = (3-first)/100
xv,yv = np.meshgrid(np.linspace(-3,3,100),np.linspace(-3,3,100))
p = path.Path([(0,0), (0, 1), (1, 1), (1, 0)])  # square with legs length 1 and bottom left corner at the origin
flags = p.contains_points(np.hstack((xv.flatten()[:,np.newaxis],yv.flatten()[:,np.newaxis])))
grid = np.zeros((101,101),dtype='bool')
grid[((xv.flatten()-first)/size).astype('int'),((yv.flatten()-first)/size).astype('int')] = flags

xi,yi = np.random.randint(-300,300,100)/100,np.random.randint(-300,300,100)/100
vflag = grid[((xi-first)/size).astype('int'),((yi-first)/size).astype('int')]
plt.imshow(grid.T,origin='lower',interpolation='nearest',cmap='binary')
plt.scatter(((xi-first)/size).astype('int'),((yi-first)/size).astype('int'),c=vflag,cmap='Greens',s=90)
plt.show()

,结果是这样的:

像素公差内的多边形内的点


1
谢谢,为此,目前我将坚持使用matplotlib,因为它似乎比自定义光线跟踪快得多。但是,我非常喜欢空间离散化的答案,将来可能会需要它。我还将检查匀称的,因为它看起来投入到这些类型的问题包
鲁本·佩雷斯·卡拉斯科

18

如果速度是您所需要的,而额外的依赖关系不是问题,那么您可能会发现它numba很有用(现在,在任何平台上都非常容易安装)。通过使用装饰器并将多边形转换为numpy数组,ray_tracing可以轻松移植您建议的经典方法。该代码应如下所示: numbanumba @jit

@jit(nopython=True)
def ray_tracing(x,y,poly):
    n = len(poly)
    inside = False
    p2x = 0.0
    p2y = 0.0
    xints = 0.0
    p1x,p1y = poly[0]
    for i in range(n+1):
        p2x,p2y = poly[i % n]
        if y > min(p1y,p2y):
            if y <= max(p1y,p2y):
                if x <= max(p1x,p2x):
                    if p1y != p2y:
                        xints = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
                    if p1x == p2x or x <= xints:
                        inside = not inside
        p1x,p1y = p2x,p2y

    return inside

第一次执行将比随后的任何调用花费更长的时间:

%%time
polygon=np.array(polygon)
inside1 = [numba_ray_tracing_method(point[0], point[1], polygon) for 
point in points]

CPU times: user 129 ms, sys: 4.08 ms, total: 133 ms
Wall time: 132 ms

经过编译后将减少为:

CPU times: user 18.7 ms, sys: 320 µs, total: 19.1 ms
Wall time: 18.4 ms

如果您在第一次调用该函数时需要提高速度,则可以使用来在模块中预编译代码pycc。将函数存储在src.py中,如下所示:

from numba import jit
from numba.pycc import CC
cc = CC('nbspatial')


@cc.export('ray_tracing',  'b1(f8, f8, f8[:,:])')
@jit(nopython=True)
def ray_tracing(x,y,poly):
    n = len(poly)
    inside = False
    p2x = 0.0
    p2y = 0.0
    xints = 0.0
    p1x,p1y = poly[0]
    for i in range(n+1):
        p2x,p2y = poly[i % n]
        if y > min(p1y,p2y):
            if y <= max(p1y,p2y):
                if x <= max(p1x,p2x):
                    if p1y != p2y:
                        xints = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
                    if p1x == p2x or x <= xints:
                        inside = not inside
        p1x,p1y = p2x,p2y

    return inside


if __name__ == "__main__":
    cc.compile()

编译python src.py并运行:

import nbspatial

import numpy as np
lenpoly = 100
polygon = [[np.sin(x)+0.5,np.cos(x)+0.5] for x in 
np.linspace(0,2*np.pi,lenpoly)[:-1]]

# random points set of points to test 
N = 10000
# making a list instead of a generator to help debug
points = zip(np.random.random(N),np.random.random(N))

polygon = np.array(polygon)

%%time
result = [nbspatial.ray_tracing(point[0], point[1], polygon) for point in points]

CPU times: user 20.7 ms, sys: 64 µs, total: 20.8 ms
Wall time: 19.9 ms

在我使用的数字代码中:“ b1(f8,f8,f8 [:,:])”

为了进行编译nopython=True,每个var需要在之前声明for loop

在prebuild src代码行中:

@cc.export('ray_tracing' , 'b1(f8, f8, f8[:,:])')

用于声明函数名称及其I / O var类型,布尔输出b1和两个浮点数f8以及一个浮点数二维数组f8[:,:]作为输入。


11

您的测试很好,但是它只测量某些特定情况:我们有一个具有许多顶点的多边形,并且有很长的点阵列可以在多边形中检查它们。

而且,我想您不是在测量matplotlib-inside-polygon-method与ray-method,而是测量matplotlib-以某种方式优化的迭代与simple-list-iteration

让我们进行N个独立的比较(N对点和多边形)吗?

# ... your code...
lenpoly = 100
polygon = [[np.sin(x)+0.5,np.cos(x)+0.5] for x in np.linspace(0,2*np.pi,lenpoly)[:-1]]

M = 10000
start_time = time()
# Ray tracing
for i in range(M):
    x,y = np.random.random(), np.random.random()
    inside1 = ray_tracing_method(x,y, polygon)
print "Ray Tracing Elapsed time: " + str(time()-start_time)

# Matplotlib mplPath
start_time = time()
for i in range(M):
    x,y = np.random.random(), np.random.random()
    inside2 = path.contains_points([[x,y]])
print "Matplotlib contains_points Elapsed time: " + str(time()-start_time)

结果:

Ray Tracing Elapsed time: 0.548588991165
Matplotlib contains_points Elapsed time: 0.103765010834

Matplotlib仍然好很多,但没有好100倍。现在让我们尝试更简单的多边形...

lenpoly = 5
# ... same code

结果:

Ray Tracing Elapsed time: 0.0727779865265
Matplotlib contains_points Elapsed time: 0.105288982391

6

我将其保留在此处,仅使用numpy重写了上面的代码,也许有人觉得它有用:

def ray_tracing_numpy(x,y,poly):
    n = len(poly)
    inside = np.zeros(len(x),np.bool_)
    p2x = 0.0
    p2y = 0.0
    xints = 0.0
    p1x,p1y = poly[0]
    for i in range(n+1):
        p2x,p2y = poly[i % n]
        idx = np.nonzero((y > min(p1y,p2y)) & (y <= max(p1y,p2y)) & (x <= max(p1x,p2x)))[0]
        if p1y != p2y:
            xints = (y[idx]-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
        if p1x == p2x:
            inside[idx] = ~inside[idx]
        else:
            idxx = idx[x[idx] <= xints]
            inside[idxx] = ~inside[idxx]    

        p1x,p1y = p2x,p2y
    return inside    

将ray_tracing包装成

def ray_tracing_mult(x,y,poly):
    return [ray_tracing(xi, yi, poly[:-1,:]) for xi,yi in zip(x,y)]

经过100000点测试,结果:

ray_tracing_mult 0:00:00.850656
ray_tracing_numpy 0:00:00.003769

我如何只为一个poly和一个x,y返回true或false?
Jasar Orion

如果您只做一个多边形,我会使用@epifanio解决方案。NumPy解决方案更适合大批量计算。
Can Hicabi Tartanoglu
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.