Pythonでポイントがポリゴン内にあるかどうかを確認する最も速い方法は何ですか


84

ポイントがポリゴン内にあるかどうかを確認する2つの主な方法を見つけました。1つはここで使用されているレイトレーシング方法を使用することです。これは最も推奨される答えです。もう1つはmatplotlibを使用path.contains_pointsすることです(これは私には少しわかりにくいようです)。たくさんのポイントを継続的にチェックする必要があります。これらの2つのいずれかが他よりも推奨されるかどうか、またはさらに優れた3番目のオプションがあるかどうかを誰かが知っていますか?

更新:

2つのメソッドを確認したところ、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が非常に広く使用されていることを考えると、これは非常に基本的な関数であるため、正しく機能していると想定することも安全です(「あいまい」に見えるかもしれませんが)。最後になりましたが、単にテストしてみませんか?
セバスチャン2016

あなたが予測したように、私はテストで質問を更新しました、matplotlibははるかに高速です。matplotlibは、私が見たさまざまな場所で最も有名な応答ではないため、心配していました。何か(またはより良いパッケージ)を見落としているかどうかを知りたいと思いました。また、matplotlibは、このような単純な質問に対しては大物のように見えました。
ルーベンペレスカラスコ2016

回答:


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))

あなたが言及した方法から、私は2番目の、、を使用しただけpath.contains_pointsで、それはうまく機能します。いずれにせよ、テストに必要な精度に応じて、ポリゴン内のすべてのノードがTrue(そうでない場合はFalse)になるようにnumpyboolグリッドを作成することをお勧めします。多くのポイントのテストを行う場合、これはより高速になる可能性があります(ただし、これは「ピクセル」許容範囲内でテストを行うことに依存していることに注意してください)。

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に固執します。それにもかかわらず、私はスペース離散化の答えが本当に好きです、私は将来それを必要とするかもしれません。この種の問題に特化したパッケージに見えるので、私も形を整えてチェックします
Ruben Perez-Carrasco

18

速度が必要であり、追加の依存関係が問題にならない場合は、numba非常に便利です(これで、どのプラットフォームにも非常に簡単にインストールできます)。ray_tracingあなたが提案した古典的なアプローチは、デコレータをnumba使用しnumba @jit、ポリゴンをnumpy配列にキャストすることで簡単に移植できます。コードは次のようになります。

@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

私が使用したnumbaコードでは: 'b1(f8、f8、f8 [:、:])'

でコンパイルするにはnopython=True、各変数をの前に宣言する必要がありますfor loop

ビルド前のsrcコードでは、次の行があります。

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

関数名とそのI / O変数タイプ、ブール出力b1と2つのfloat f8、およびf8[:,:]入力としてのfloatの2次元配列を宣言するために使用されます。


11

あなたのテストは良いですが、それはいくつかの特定の状況だけを測定します:多くの頂点を持つ1つのポリゴンと、ポリゴン内でそれらをチェックするための長いポイントの配列があります。

さらに、matplotlib-inside-polygon-methodとray-methodではなく、matplotlib-somehow-optimized-iterationと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

1つのpolyと1つのx、yに対してtrueまたはfalseのみを返すにはどうすればよいですか?
Jasar Orion

ポリを1つだけ実行する場合は、@ epifanioソリューションを使用します。NumPyソリューションは、より大きなバッチでの計算に適しています。
Hicabi Tartanoglu
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.