Який найшвидший спосіб перевірити, чи є точка всередині багатокутника в 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 дуже широко використовується, і оскільки це дуже фундаментальна функція - можливо, також можна вважати, що він працює коректно (хоча це може здатися "неясним"). Останнє, але не менш важливе: чому б просто не протестувати?
Себастьян,

Я оновив питання тестом, як ви передбачали, matplotlib набагато швидший. Я був стурбований тим, що matplotlib - це не найвідоміша відповідь у різних місцях, куди я заглядав, і я хотів знати, чи не помічав я чогось (або кращого пакета). Також matplotlib виглядав великим хлопцем для такого простого запитання.
Рубен Перес-Карраско,

Відповіді:


99

Ви можете розглянути стрункі :

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цілком корисну (зараз її досить легко встановити на будь-якій платформі). Запропонований 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кожною var слід оголосити перед for loop.

У коді попередньої збірки src рядок:

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

Використовується для оголошення імені функції та її типів змінних вводу-виводу, логічного виводу b1та двох поплавків f8та двовимірного масиву поплавців f8[:,:]як вхідних даних.


11

Ваш тест хороший, але він вимірює лише певну ситуацію: у нас є один багатокутник з безліччю вершин і довгий масив точок, щоб перевірити їх у полігоні.

Більше того, я припускаю, що ви вимірюєте не метод matplotlib-inside-polygon vs ray-method, а matplotlib-якимось чином оптимізовану ітерацію проти простої ітерації списку

Давайте зробимо 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    

Загорнутий промінь_трасування в

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

як я можу повернути тільки true або false для одного полі та одного x, y?
Ясар Оріон

Я б використовував рішення @epifanio, якщо ви робите лише один полі. Рішення NumPy краще для обчислення більшими партіями.
Can Hicabi Tartanoglu
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.