web-dev-qa-db-fra.com

Quel est le moyen le plus rapide de vérifier si un point est à l'intérieur d'un polygone dans python

J'ai trouvé deux méthodes principales pour vérifier si un point appartient à un polygone. L'une utilise la méthode de traçage de rayons utilisée ici , qui est la réponse la plus recommandée, l'autre utilise matplotlib path.contains_points (qui me semble un peu obscur). Je vais devoir vérifier beaucoup de points en permanence. Est-ce que quelqu'un sait si l'une de ces deux options est plus recommandable que l'autre ou s'il existe une troisième option encore meilleure?

MISE À JOUR:

J'ai vérifié les deux méthodes et matplotlib a l'air beaucoup plus rapide.

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)

qui donne,

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

La même différence relative a été obtenue en utilisant un triangle au lieu du polygone à 100 côtés. Je vais aussi vérifier la forme car il semble un paquet vient de consacrer à ce genre de problèmes

46

Vous pouvez considérer galbé :

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

Parmi les méthodes que vous avez mentionnées, je n’ai utilisé que la seconde, path.contains_points, et ça marche bien. Dans tous les cas, en fonction de la précision dont vous avez besoin pour votre test, je suggérerais de créer une grille numpy bool avec tous les nœuds à l’intérieur du polygone à la valeur True (False sinon). Si vous allez faire un test pour beaucoup de points, cela pourrait être plus rapide ( bien que vous fassiez remarquer que cela dépend du fait que vous faites un test avec une tolérance de "pixel" ):

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

, le résultat est le suivant:

point inside polygon within pixel tolerance

56
armatita

Si la vitesse est ce dont vous avez besoin et que les dépendances supplémentaires ne posent pas de problème, vous pouvez peut-être trouver numba très utile (il est maintenant assez facile à installer, sur n’importe quelle plate-forme). L'approche classique ray_tracing Que vous avez proposée peut être facilement modifiée en numba en utilisant le décorateur numba @jit Et en convertissant le polygone en un tableau numpy. Le code devrait ressembler à:

@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

La première exécution prendra un peu plus longtemps que n'importe quel appel suivant:

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

Qui, après compilation, diminuera à:

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

Si vous avez besoin de rapidité au premier appel de la fonction, vous pouvez pré-compiler le code dans un module en utilisant pycc. Stocker la fonction dans un src.py comme:

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 __== "__main__":
    cc.compile()

Construisez-le avec python src.py Et lancez:

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

Dans le code numba j'ai utilisé: 'b1 (f8, f8, f8 [:,:])'

Pour pouvoir compiler avec nopython=True, Chaque variable doit être déclarée avant le for loop.

Dans le code de pré-génération src, la ligne:

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

Est utilisé pour déclarer le nom de la fonction et ses types de variables d'E/S, une sortie booléenne b1 Et deux flottants f8 Et un tableau à deux dimensions de flotteurs f8[:,:] En entrée.

9
epifanio

Votre test est bon, mais il ne mesure qu'une situation spécifique: nous avons un polygone avec plusieurs sommets et un grand tableau de points pour les vérifier dans un polygone.

De plus, je suppose que vous ne mesurez pas la méthode matplotlib-inside-polygon vs la méthode ray, mais la matplotlib-quelque-chose-optimisée-l'itération vs la simple liste-itération

Faisons N comparaisons indépendantes (N paires de points et de polygones)?

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

Résultat:

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

Matplotlib est encore beaucoup mieux, mais pas 100 fois mieux. Essayons maintenant un polygone beaucoup plus simple ...

lenpoly = 5
# ... same code

résultat:

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