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
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:
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.
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