Як я можу визначити, чи 2D точка знаходиться в полігоні?


497

Я намагаюся створити швидку 2D точку в алгоритмі полігону, для використання в хіт-тесті (наприклад Polygon.contains(p:Point)). Пропозиції щодо ефективних методик будуть вдячні.


Ви забули розповісти нам про своє сприйняття питання щодо правильності чи ліворукості - що також можна трактувати як "всередині" проти "зовні" - RT
Річард Т

13
Так, я розумію, зараз питання залишає багато деталей не визначеними, але в цей момент я начебто зацікавлений бачити різноманітність відповідей.
Скотт Еверден

4
90-сторонній багатокутник називається еннеаконтагоном, а 10-сторонній багатокутник - міріагоном.

"Найелегантніше" знаходиться поза ціллю, оскільки у мене виникли проблеми з пошуку алгоритму "робота взагалі". Я повинен зрозуміти це сам: stackoverflow.com/questions/14818567 / ...
davidkonrad

Відповіді:


731

Для графіки я не віддаю перевагу цілим числам. Багато систем використовують цілі числа для малювання в інтерфейсі користувача (пікселі все-таки є ints), але macOS, наприклад, використовує float для всього. macOS знає лише точки, і точка може переводитись на один піксель, але залежно від роздільної здатності монітора, це може перекласти щось інше. На екранах сітківки половина точки (0,5 / 0,5) - піксель. Проте я ніколи не помічав, що користувальницькі інтерфейси macOS значно повільніші за інші інтерфейси. Зрештою, 3D-API (OpenGL або Direct3D) також працює з поплавками, а сучасні графічні бібліотеки дуже часто використовують переваги прискорення GPU.

Тепер ви сказали, що швидкість - це ваша головна турбота, добре, давайте продовжимо швидкість. Перш ніж запустити будь-який складний алгоритм, спочатку зробіть простий тест. Створіть навколо свого полігону обмежувальне поле, вирівняне по осі . Це дуже просто, швидко і вже може вберегти вас від багатьох розрахунків. Як це працює? Ітерації над усіма точками багатокутника і знайдіть значення min / max X і Y.

Наприклад, у вас є бали (9/1), (4/3), (2/7), (8/2), (3/6). Це означає, що Xmin дорівнює 2, Xmax - 9, Ymin - 1, а Ymax - 7. Точка поза прямокутника з двома ребрами (2/1) та (9/7) не може знаходитись у полігоні.

// p is your point, p.x is the x coord, p.y is the y coord
if (p.x < Xmin || p.x > Xmax || p.y < Ymin || p.y > Ymax) {
    // Definitely not within the polygon!
}

Це перший тест, який виконується для будь-якої точки. Як бачите, цей тест є надшвидким, але також дуже грубим. Для обробки точок, що знаходяться в межах обмежуючого прямокутника, нам потрібен більш складний алгоритм. Існує кілька способів, як це можна обчислити. Який метод працює, також залежить від того, чи може багатокутник мати отвори чи завжди буде суцільним. Ось приклади суцільних (один опуклий, один увігнутий):

Полігон без отвору

І ось один з отвором:

Багатокутник з отвором

У зеленому є отвір посередині!

Найпростіший алгоритм, який може впоратися з усіма трьома вищевикладеними випадками і все ще досить швидким, називається рейндж-кастинг . Ідея алгоритму досить проста: намалюйте віртуальний промінь з будь-якої точки поза полігоном до вашої точки і порахуйте, як часто він потрапляє в бік полігона. Якщо кількість ударів парне, воно знаходиться поза полігоном, якщо воно непарне - це всередині.

Демонструючи, як промінь прорізає багатокутник

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

У вас ще є обмежувальний ящик зверху, пам’ятаєте? Просто виберіть точку поза межами рамки та використовуйте її як вихідну точку для вашого променя. Наприклад, точка (Xmin - e/p.y)знаходиться поза полігоном точно.

Але що таке e? Ну, e(насправді епсілон) надає обмежувальній коробці деяку підкладку . Як я вже говорив, трасування променів провалюється, якщо ми починаємо занадто близько до лінії багатокутника. Оскільки обмежувальний ящик може дорівнювати багатокутнику (якщо полігон є прямокутником, орієнтованим на вісь, то обмежувальний ящик дорівнює самому багатокутнику!), Нам потрібно трохи прокладки, щоб зробити це безпечним, ось і все. Яку велику слід вибрати e? Не надто великий. Це залежить від шкали системи координат, яку ви використовуєте для малювання. Якщо ширина кроку пікселя дорівнює 1,0, просто виберіть 1,0 (все-таки 0,1 працювало б також)

Тепер, коли у нас є промінь з його початковою і кінцевою координатами, проблема зміщується з " точки в полігоні " на " як часто промінь перетинає сторону полігона ". Тому ми не можемо просто працювати з точками багатокутника, як раніше, тепер нам потрібні фактичні сторони. Сторона завжди визначається двома точками.

side 1: (X1/Y1)-(X2/Y2)
side 2: (X2/Y2)-(X3/Y3)
side 3: (X3/Y3)-(X4/Y4)
:

Вам потрібно протестувати промінь з усіх боків. Розглянемо промінь як вектор, а кожен бік - вектор. Промінь повинен вдарити в кожну сторону рівно один раз або взагалі ніколи. Він не може вдарити по одній стороні двічі. Дві лінії у двомірному просторі завжди будуть перетинатися рівно один раз, якщо вони не паралельні, і в цьому випадку вони ніколи не перетинаються. Однак оскільки вектори мають обмежену довжину, два вектори можуть не бути паралельними і все одно ніколи не перетинатися, оскільки вони занадто короткі, щоб коли-небудь зустріти один одного.

// Test the ray against all sides
int intersections = 0;
for (side = 0; side < numberOfSides; side++) {
    // Test if current side intersects with ray.
    // If yes, intersections++;
}
if ((intersections & 1) == 1) {
    // Inside of polygon
} else {
    // Outside of polygon
}

Поки що добре, але як ви перевірите, чи перетинаються два вектори? Ось якийсь код C (не перевірений), який повинен зробити трюк:

#define NO 0
#define YES 1
#define COLLINEAR 2

int areIntersecting(
    float v1x1, float v1y1, float v1x2, float v1y2,
    float v2x1, float v2y1, float v2x2, float v2y2
) {
    float d1, d2;
    float a1, a2, b1, b2, c1, c2;

    // Convert vector 1 to a line (line 1) of infinite length.
    // We want the line in linear equation standard form: A*x + B*y + C = 0
    // See: http://en.wikipedia.org/wiki/Linear_equation
    a1 = v1y2 - v1y1;
    b1 = v1x1 - v1x2;
    c1 = (v1x2 * v1y1) - (v1x1 * v1y2);

    // Every point (x,y), that solves the equation above, is on the line,
    // every point that does not solve it, is not. The equation will have a
    // positive result if it is on one side of the line and a negative one 
    // if is on the other side of it. We insert (x1,y1) and (x2,y2) of vector
    // 2 into the equation above.
    d1 = (a1 * v2x1) + (b1 * v2y1) + c1;
    d2 = (a1 * v2x2) + (b1 * v2y2) + c1;

    // If d1 and d2 both have the same sign, they are both on the same side
    // of our line 1 and in that case no intersection is possible. Careful, 
    // 0 is a special case, that's why we don't test ">=" and "<=", 
    // but "<" and ">".
    if (d1 > 0 && d2 > 0) return NO;
    if (d1 < 0 && d2 < 0) return NO;

    // The fact that vector 2 intersected the infinite line 1 above doesn't 
    // mean it also intersects the vector 1. Vector 1 is only a subset of that
    // infinite line 1, so it may have intersected that line before the vector
    // started or after it ended. To know for sure, we have to repeat the
    // the same test the other way round. We start by calculating the 
    // infinite line 2 in linear equation standard form.
    a2 = v2y2 - v2y1;
    b2 = v2x1 - v2x2;
    c2 = (v2x2 * v2y1) - (v2x1 * v2y2);

    // Calculate d1 and d2 again, this time using points of vector 1.
    d1 = (a2 * v1x1) + (b2 * v1y1) + c2;
    d2 = (a2 * v1x2) + (b2 * v1y2) + c2;

    // Again, if both have the same sign (and neither one is 0),
    // no intersection is possible.
    if (d1 > 0 && d2 > 0) return NO;
    if (d1 < 0 && d2 < 0) return NO;

    // If we get here, only two possibilities are left. Either the two
    // vectors intersect in exactly one point or they are collinear, which
    // means they intersect in any number of points from zero to infinite.
    if ((a1 * b2) - (a2 * b1) == 0.0f) return COLLINEAR;

    // If they are not collinear, they must intersect in exactly one point.
    return YES;
}

Вхідні значення - це дві кінцеві точки вектора 1 ( v1x1/v1y1і v1x2/v1y2) та вектора 2 ( v2x1/v2y1і v2x2/v2y2). Отже, у вас є 2 вектори, 4 точки, 8 координат. YESі NOзрозумілі. YESзбільшує перехрестя, NOнічого не робить.

А що з КОЛЛІНАРОМ? Це означає, що обидва вектори лежать на одній нескінченній лінії, залежно від положення та довжини, вони взагалі не перетинаються або перетинаються у нескінченній кількості точок. Я не зовсім впевнений, як поводитися з цим випадком, я б не вважав це перехрестям в будь-якому випадку. Що ж, такий випадок на практиці досить рідкісний через помилки округлення плаваючої точки; кращий код, ймовірно, не перевірятиме, == 0.0fа натомість для чогось подібного < epsilon, де epsilon - досить невелика кількість.

Якщо вам потрібно перевірити більшу кількість балів, ви, звичайно, можете трохи прискорити все, зберігаючи в пам’яті стандартні форми лінійних рівнянь сторін полігону, тому вам не доведеться їх перераховувати кожен раз. Це дозволить вам заощадити два множення плаваючої точки та три віднімання плаваючої точки на кожному тесті в обмін на збереження в пам'яті трьох значень плаваючої точки на стороні багатокутника. Це типова компенсація часу на пам'ять проти обчислень.

І останнє, але не менш важливе: якщо для вирішення проблеми ви можете використовувати апаратне забезпечення 3D, є цікава альтернатива. Просто дозвольте GPU зробити всю роботу за вас. Створіть поверхню фарбування, яка відключена. Заповніть його повністю кольором чорного. Тепер дозвольте OpenGL або Direct3D пофарбувати ваш багатокутник (або навіть усі ваші багатокутники, якщо ви просто хочете перевірити, чи крапка знаходиться в будь-якому з них, але вам не байдуже, який саме), і заповніть багатокутник іншим колір, наприклад, білий. Щоб перевірити, чи є точка у полігоні, знайдіть колір цієї точки з поверхні малювання. Це лише вибір O (1) пам'яті.

Звичайно, цей метод корисний лише у тому випадку, якщо поверхня малюнка не повинна бути величезною. Якщо він не може вписатися в пам'ять GPU, цей спосіб проходить повільніше, ніж робити це на процесорі. Якщо це повинно бути величезним, і ваш GPU підтримує сучасні шейдери, ви все одно можете використовувати GPU, застосувавши кастинг променів, показаний вище як шейдер GPU, що абсолютно можливо. Для більшої кількості полігонів або великої кількості балів для тестування це окупиться, вважайте, деякі графічні процесори зможуть тестувати 64 - 256 балів паралельно. Зауважте, однак, що передача даних з процесора в GPU і назад завжди дорога, тому для тестування пари очок на пару простих полігонів, де точки або полігони є динамічними і часто змінюються, підхід GPU рідко платить вимкнено.


26
+1 Фантастична відповідь. Використовуючи обладнання для цього, я читав в інших місцях, що це може бути повільним, оскільки вам потрібно повернути дані з відеокарти. Але мені дуже подобається принцип зняття навантаження з процесора. Хтось має хороші посилання на те, як це можна зробити в OpenGL?
Гавін

3
+1, тому що це так просто! Основна проблема полягає в тому, що якщо ваш багатокутник і тестова точка розташовуються на сітці (не рідкість), тоді вам доведеться мати справу з "двояковими" перетинами, наприклад, прямо через точку полігона! (виходить парність двох замість одного). Потрапляє в цю дивну область: stackoverflow.com/questions/2255842/… . Комп'ютерна графіка рясніє цими особливими випадками: теоретично простими, волохатими на практиці.
Jared Updike

7
@RMorrisey: Чому ти так вважаєш? Я не бачу, як це не вдалося б увігнутому багатокутнику. Промінь може виїжджати і повторно входити в полігон кілька разів, коли полігон увігнутий, але врешті-решт лічильник ударів буде непарним, якщо точка знаходиться всередині, і навіть якщо вона знаходиться поза, також для увігнутих багатокутників.
Мецькі

6
"Алгоритм швидкого намотування чисел", описаний на softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm працює досить швидко ...
SP

10
Ваше використання / для розділення координат x і y заплутане, воно читається як x, поділене на y. Набагато зрозуміліше використовувати x, y (тобто x кома y) Загалом, корисна відповідь.
Еш

583

Я думаю, що наступний фрагмент коду - найкраще рішення (взято звідси ):

int pnpoly(int nvert, float *vertx, float *verty, float testx, float testy)
{
  int i, j, c = 0;
  for (i = 0, j = nvert-1; i < nvert; j = i++) {
    if ( ((verty[i]>testy) != (verty[j]>testy)) &&
     (testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
       c = !c;
  }
  return c;
}

Аргументи

  • nvert : Кількість вершин у багатокутнику. Чи потрібно повторити першу вершину в кінці, було обговорено у статті, згаданій вище.
  • vertx, verty : Масиви, що містять x- і y-координати вершин багатокутника.
  • testx, testy : X- і y-координати точки тесту.

Це і коротко, і ефективно, і працює як для опуклих, так і увігнутих багатокутників. Як було запропоновано раніше, слід спочатку перевірити обмежуючий прямокутник і обробити багатокутні отвори окремо.

Ідея цього досить проста. Автор описує це так:

Я запускаю напівскінченний промінь по горизонталі (збільшуючи х, фіксований у) від точки тестування і рахую, скільки ребер він перетинається. При кожному перетині промінь перемикається між всередині і зовні. Це називається теоремою кривої Йордана.

Змінна c перемикається з 0 на 1 і 1 на 0 щоразу, коли горизонтальний промінь перетинає будь-який край. Так що в основному це відстеження того, чи число перекреслених країв парне чи непарне. 0 означає парне і 1 означає непарне.


5
Питання. Які саме змінні я передаю? Що вони представляють?
текнолагі

9
@Mick Це перевіряє це verty[i]і verty[j]є обома сторонами testy, тому вони ніколи не є рівними.
Пітер Вуд

4
Цей код не є надійним, і я б не рекомендував його використовувати. Ось посилання, що дає детальний аналіз: www-ma2.upc.es/geoc/Schirra-pointPolygon.pdf
Микола

13
Цей підхід насправді має обмеження (крайні випадки): перевірка точки (15,20) на полігоні [(10,10), (10,20), (20,20), (20,10)] повернеться false замість істинного. Те саме з (10,20) або (20,15). Для всіх інших випадків алгоритм працює чудово, а помилкові негативи у кращих випадках добре для мого застосування.
Олександр Пача

10
@ Олександр, це насправді є дизайном: якщо обробляти ліві та нижні межі в протилежному значенні до верхнього та правого кордонів, якщо два різних багатокутника мають спільний край, будь-яка точка вздовж цього краю буде розташована в одному і лише одному многокутнику. ..корисна властивість.
палату

69

Ось версія C # відповіді, даної nirg , яка походить від цього професора RPI . Зауважте, що використання коду з цього джерела RPI вимагає атрибуції.

У верхній частині додано обмежувальний прапорець. Однак, як зазначає Джеймс Браун, основний код майже такий же швидкий, як і сама перевірка обмежувального поля, тому перевірка обмежувального поля може насправді уповільнити загальну роботу, якщо ви бачите, що більшість точок, які ви перевіряєте, знаходяться всередині обмежувального поля . Таким чином, ви можете залишити обмежувальне поле, щоб перевірити, або альтернативою може бути попередньо обчислити обмежувальні поля ваших багатокутників, якщо вони не змінюють форму занадто часто.

public bool IsPointInPolygon( Point p, Point[] polygon )
{
    double minX = polygon[ 0 ].X;
    double maxX = polygon[ 0 ].X;
    double minY = polygon[ 0 ].Y;
    double maxY = polygon[ 0 ].Y;
    for ( int i = 1 ; i < polygon.Length ; i++ )
    {
        Point q = polygon[ i ];
        minX = Math.Min( q.X, minX );
        maxX = Math.Max( q.X, maxX );
        minY = Math.Min( q.Y, minY );
        maxY = Math.Max( q.Y, maxY );
    }

    if ( p.X < minX || p.X > maxX || p.Y < minY || p.Y > maxY )
    {
        return false;
    }

    // https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
    bool inside = false;
    for ( int i = 0, j = polygon.Length - 1 ; i < polygon.Length ; j = i++ )
    {
        if ( ( polygon[ i ].Y > p.Y ) != ( polygon[ j ].Y > p.Y ) &&
             p.X < ( polygon[ j ].X - polygon[ i ].X ) * ( p.Y - polygon[ i ].Y ) / ( polygon[ j ].Y - polygon[ i ].Y ) + polygon[ i ].X )
        {
            inside = !inside;
        }
    }

    return inside;
}

5
Чудово працює, дякую, я перетворив на JavaScript. stackoverflow.com/questions/217578/…
Philipp Lenssen

2
Це> 1000 разів швидше, ніж використання GraphicsPath.IsVisible !! Перевірка обмежувального поля робить функцію приблизно на 70% повільнішою.
Джеймс Браун

Мало того, що GraphicsPath.IsVisible () проходить повільніше, але й не дуже добре працює з дуже маленькими багатокутниками зі стороною в діапазоні 0,01f
Патрік з команди NDepend

50

Ось варіант JavaScript на відповідь М. Каца на основі підходу Нірга:

function pointIsInPoly(p, polygon) {
    var isInside = false;
    var minX = polygon[0].x, maxX = polygon[0].x;
    var minY = polygon[0].y, maxY = polygon[0].y;
    for (var n = 1; n < polygon.length; n++) {
        var q = polygon[n];
        minX = Math.min(q.x, minX);
        maxX = Math.max(q.x, maxX);
        minY = Math.min(q.y, minY);
        maxY = Math.max(q.y, maxY);
    }

    if (p.x < minX || p.x > maxX || p.y < minY || p.y > maxY) {
        return false;
    }

    var i = 0, j = polygon.length - 1;
    for (i, j; i < polygon.length; j = i++) {
        if ( (polygon[i].y > p.y) != (polygon[j].y > p.y) &&
                p.x < (polygon[j].x - polygon[i].x) * (p.y - polygon[i].y) / (polygon[j].y - polygon[i].y) + polygon[i].x ) {
            isInside = !isInside;
        }
    }

    return isInside;
}

32

Обчисліть орієнтовану суму кутів між точкою p та кожним з вершин багатокутника. Якщо загальний орієнтований кут становить 360 градусів, точка знаходиться всередині. Якщо загальна сума 0, точка знаходиться поза.

Мені подобається цей метод краще, тому що він більш міцний і менш залежить від числової точності.

Методи, які обчислюють рівномірність кількості перехресть, обмежені, оскільки ви можете «вдарити» по вершині під час обчислення кількості перехресть.

EDIT: До речі, цей метод працює з увігнутими та опуклими багатокутниками.

EDIT: Нещодавно я знайшов цілу статтю у Вікіпедії на цю тему.


1
Ні, це неправда. Це працює незалежно від опуклості багатокутника.
Девід Сегондс

2
@DarenW: лише один acos на вершину; з іншого боку, цей алгоритм повинен бути найпростішим для реалізації в SIMD, оскільки він абсолютно не має розгалуження.
Джаспер Беккерс

1
@emilio, якщо точка далека від трикутника, я не бачу, як кут, утворений точкою та двома верхівками трикутника, буде 90 градусів.
Девід Сегондс

2
Спочатку використовуйте прапорець для обмеження, щоб вирішити випадки "крапка далеко". Для триги можна використовувати попередньо створені таблиці.
JOM

3
Це оптимальне рішення, оскільки це O (n), і вимагає мінімальних обчислень. Працює для всіх багатокутників. Я досліджував це рішення 30 років тому на своїй першій роботі в IBM. Вони підписалися на нього і досі його використовують у своїх ГІС-технологіях.
Домінік Серісано

24

Це питання таке цікаве. У мене є ще одна працездатна ідея, відмінна від інших відповідей на цю посаду. Ідея полягає у використанні суми кутів, щоб визначити, ціль знаходиться всередині чи зовні. Більш відомий як обмотка номер .

Нехай х - цільова точка. Нехай масив [0, 1, .... n] є усіма точками області. З'єднайте цільову точку з кожною граничною точкою за допомогою лінії. Якщо цільова точка знаходиться всередині цієї області. Сума всіх кутів складе 360 градусів. Якщо ні, кути будуть менше 360.

Щоб отримати базове розуміння ідеї, зверніться до цього зображення: введіть тут опис зображення

Мій алгоритм припускає, що за годинниковою стрілкою є позитивним напрямком. Ось потенційний внесок:

[[-122.402015, 48.225216], [-117.032049, 48.999931], [-116.919132, 45.995175], [-124.079107, 46.267259], [-124.717175, 48.377557], [-122.92315, 47.047963], [-122.402015, 48.225216]]

Далі йде код python, який реалізує ідею:

def isInside(self, border, target):
degree = 0
for i in range(len(border) - 1):
    a = border[i]
    b = border[i + 1]

    # calculate distance of vector
    A = getDistance(a[0], a[1], b[0], b[1]);
    B = getDistance(target[0], target[1], a[0], a[1])
    C = getDistance(target[0], target[1], b[0], b[1])

    # calculate direction of vector
    ta_x = a[0] - target[0]
    ta_y = a[1] - target[1]
    tb_x = b[0] - target[0]
    tb_y = b[1] - target[1]

    cross = tb_y * ta_x - tb_x * ta_y
    clockwise = cross < 0

    # calculate sum of angles
    if(clockwise):
        degree = degree + math.degrees(math.acos((B * B + C * C - A * A) / (2.0 * B * C)))
    else:
        degree = degree - math.degrees(math.acos((B * B + C * C - A * A) / (2.0 * B * C)))

if(abs(round(degree) - 360) <= 3):
    return True
return False

21

Стаття Еріка Хейнса, яку цитує bobobobo, справді відмінна. Особливо цікавими є таблиці, що порівнюють продуктивність алгоритмів; метод підсумовування кутів дійсно поганий порівняно з іншими. Також цікаво те, що такі оптимізації, як використання сітки пошуку для подальшого підрозділу полігону на "в" та "поза" сектори, можуть зробити тест неймовірно швидким навіть на полігонах з> 1000 сторонами.

У будь-якому випадку, це ранні дні, але моє голосування йде за методом "перетину", який, напевно, описує Мецький. Однак я знайшов це найбільш вдало описаним та кодифікованим Девідом Борком . Мені подобається, що не потрібна реальна тригонометрія, і вона працює на опуклу і увігнуту, і вона працює досить добре, оскільки кількість сторін збільшується.

До речі, ось одна із таблиць продуктивності із статті Еріка Хейнса, яка цікавить, тестуючи на випадкових багатокутників.

                       number of edges per polygon
                         3       4      10      100    1000
MacMartin               2.9     3.2     5.9     50.6    485
Crossings               3.1     3.4     6.8     60.0    624
Triangle Fan+edge sort  1.1     1.8     6.5     77.6    787
Triangle Fan            1.2     2.1     7.3     85.4    865
Barycentric             2.1     3.8    13.8    160.7   1665
Angle Summation        56.2    70.4   153.6   1403.8  14693

Grid (100x100)          1.5     1.5     1.6      2.1      9.8
Grid (20x20)            1.7     1.7     1.9      5.7     42.2
Bins (100)              1.8     1.9     2.7     15.1    117
Bins (20)               2.1     2.2     3.7     26.3    278

11

Швидка версія відповіді nirg :

extension CGPoint {
    func isInsidePolygon(vertices: [CGPoint]) -> Bool {
        guard !vertices.isEmpty else { return false }
        var j = vertices.last!, c = false
        for i in vertices {
            let a = (i.y > y) != (j.y > y)
            let b = (x < (j.x - i.x) * (y - i.y) / (j.y - i.y) + i.x)
            if a && b { c = !c }
            j = i
        }
        return c
    }
}

Це має потенційну поділу на нульову задачу при обчисленні b. Потрібно лише обчислити "b" і наступний рядок з "c", якщо обчислення для "a" показує, що існує можливість перетину. Немає можливості, якщо обидві точки вище, або обидві точки нижче - що описується обчисленням для "а".
anorskdev

11

Дуже подобається рішення, розміщене Nirg та відредаговане bobobobo. Я просто зробив це зручним для JavaScript і трохи зручніше для мого використання:

function insidePoly(poly, pointx, pointy) {
    var i, j;
    var inside = false;
    for (i = 0, j = poly.length - 1; i < poly.length; j = i++) {
        if(((poly[i].y > pointy) != (poly[j].y > pointy)) && (pointx < (poly[j].x-poly[i].x) * (pointy-poly[i].y) / (poly[j].y-poly[i].y) + poly[i].x) ) inside = !inside;
    }
    return inside;
}

8

Я трохи працював над цим, коли я був дослідником Майкла Стоунбракера - ви знаєте, професор, який придумав Інгреса , PostgreSQL тощо.

Ми зрозуміли, що найшвидшим способом було спочатку зробити обмежувальну коробку, оскільки це СУПЕР швидко. Якщо вона знаходиться поза межами рамки, вона знаходиться поза. Інакше ти робиш більш важку роботу ...

Якщо ви хочете чудовий алгоритм, зверніть увагу на вихідний код проекту PostgreSQL з відкритим кодом для гео-роботи ...

Я хочу зазначити, що ми ніколи не розуміли правильності та ліворукості (також виражається як проблема "всередині" проти "зовні" ...


ОНОВЛЕННЯ

Посилання BKB дало велику кількість розумних алгоритмів. Я працював над проблемами науки про Землю, і тому мені було потрібне рішення, яке працює в широті / довготі, і це особлива проблема рукотворності - це площа всередині меншої площі чи більшої площі? Відповідь полягає в тому, що "напрямок" вершин має значення - це ліворуч або праворук, і таким чином ви можете вказати будь-яку область як "всередині" будь-якого даного багатокутника. Як такий, моя робота використовувала рішення три, перелічене на цій сторінці.

Крім того, моя робота використовувала окремі функції для тестів "на лінії".

... Оскільки хтось запитав: ми з'ясували, що тести на обмежувальну коробку найкращі, коли кількість вершин перевищує деяку кількість - зробіть дуже швидкий тест, перш ніж робити довший тест, якщо це необхідно ... Обмежувальне поле створюється простим взяттям найбільший х, найменший х, найбільший у і найменший у і склавши їх разом, щоб скласти чотири точки коробки ...

Ще одна порада для тих, що випливають: ми зробили всі наші більш досконалі та «затуманення світла» обчислення в просторі сітки, все в позитивних точках на площині, а потім повторно проектували назад у «реальну» довготу / широту, таким чином уникаючи можливих помилок обертання навколо однієї перекресленої лінії 180 довготи і при поводженні з полярними областями. Працювали чудово!


Що робити, якщо у мене не трапляється обмежувальний ящик? :)
Скотт Еверден

8
Ви можете легко створити обмежувальне поле - це лише чотири точки, в яких використовується найбільше і найменше х і найбільше і найменше у. Більше скоро.
Річард Т

"... уникнення можливих помилок обгортання, коли одна перекреслена лінія 180 довготи та обробка полярних областей." Ви можете, можливо, описати це детальніше? Я думаю, що я можу зрозуміти, як перемістити все «вгору», щоб уникнути перетину багатокутника 0 довготи, але мені не зрозуміло, як обробити багатокутники, що містять будь-який із полюсів ...
tiritea

6

Відповідь Девіда Сегонда - це майже стандартна загальна відповідь, а Річард Т - найпоширеніша оптимізація, хоча там є деякі інші. Інші сильні оптимізації базуються на менш загальних рішеннях. Наприклад, якщо ви збираєтеся перевірити той самий багатокутник з великою кількістю точок, тріангулювання багатокутника може значно прискорити роботу, оскільки існує ряд дуже швидких алгоритмів пошуку TIN. Інша справа, якщо багатокутник і точки знаходяться на обмеженій площині з низькою роздільною здатністю, скажімо, на екрані, ви можете пофарбувати полігон на відображений в пам'яті буфер відображення в заданий колір і перевірити колір даного пікселя, щоб побачити, чи лежить він у полігонах.

Як і багато оптимізацій, вони базуються на конкретних, а не на загальних випадках, а користь для вигідності базується на амортизованому часі, а не на разовому використанні.

Працюючи в цій галузі, я знайшов відмінною підмогою Джозефа О'Руркеса «Обчислювальна геометрія в C» ISBN 0-521-44034-3.


4

Тривіальним рішенням було б поділити багатокутник на трикутники і вдарити тест на трикутники, як пояснено тут

Якщо ваш багатокутник CONVEX , можливо, буде кращий підхід. Подивіться на полігон як на сукупність нескінченних ліній. Кожен рядок ділить простір на два. для кожного пункту легко сказати, чи є його з тієї чи іншої сторони лінії. Якщо точка знаходиться на одній стороні всіх ліній, то вона знаходиться всередині многокутника.


дуже швидко і може наноситися на більш загальні форми. ще близько 1990 року у нас були «криві», де на деяких сторонах були круглі дуги. Аналізуючи ці сторони на кругові клини та пару трикутників, що з'єднують клин із початком (багатокутник центроїд), тестування ударів було швидко та легко.
DarenW

1
Ви отримали будь-які посилання на ці криві?
shoosh

Я б також полюбив реф для кривих.
Джоель у Ґо

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

4

Я усвідомлюю, що це старе, але ось алгоритм рентгенівського лиття, реалізований у Какао, на випадок, коли хтось зацікавлений. Не впевнений, що це найефективніший спосіб робити речі, але це може допомогти комусь.

- (BOOL)shape:(NSBezierPath *)path containsPoint:(NSPoint)point
{
    NSBezierPath *currentPath = [path bezierPathByFlatteningPath];
    BOOL result;
    float aggregateX = 0; //I use these to calculate the centroid of the shape
    float aggregateY = 0;
    NSPoint firstPoint[1];
    [currentPath elementAtIndex:0 associatedPoints:firstPoint];
    float olderX = firstPoint[0].x;
    float olderY = firstPoint[0].y;
    NSPoint interPoint;
    int noOfIntersections = 0;

    for (int n = 0; n < [currentPath elementCount]; n++) {
        NSPoint points[1];
        [currentPath elementAtIndex:n associatedPoints:points];
        aggregateX += points[0].x;
        aggregateY += points[0].y;
    }

    for (int n = 0; n < [currentPath elementCount]; n++) {
        NSPoint points[1];

        [currentPath elementAtIndex:n associatedPoints:points];
        //line equations in Ax + By = C form
        float _A_FOO = (aggregateY/[currentPath elementCount]) - point.y;  
        float _B_FOO = point.x - (aggregateX/[currentPath elementCount]);
        float _C_FOO = (_A_FOO * point.x) + (_B_FOO * point.y);

        float _A_BAR = olderY - points[0].y;
        float _B_BAR = points[0].x - olderX;
        float _C_BAR = (_A_BAR * olderX) + (_B_BAR * olderY);

        float det = (_A_FOO * _B_BAR) - (_A_BAR * _B_FOO);
        if (det != 0) {
            //intersection points with the edges
            float xIntersectionPoint = ((_B_BAR * _C_FOO) - (_B_FOO * _C_BAR)) / det;
            float yIntersectionPoint = ((_A_FOO * _C_BAR) - (_A_BAR * _C_FOO)) / det;
            interPoint = NSMakePoint(xIntersectionPoint, yIntersectionPoint);
            if (olderX <= points[0].x) {
                //doesn't matter in which direction the ray goes, so I send it right-ward.
                if ((interPoint.x >= olderX && interPoint.x <= points[0].x) && (interPoint.x > point.x)) {  
                    noOfIntersections++;
                }
            } else {
                if ((interPoint.x >= points[0].x && interPoint.x <= olderX) && (interPoint.x > point.x)) {
                     noOfIntersections++;
                } 
            }
        }
        olderX = points[0].x;
        olderY = points[0].y;
    }
    if (noOfIntersections % 2 == 0) {
        result = FALSE;
    } else {
        result = TRUE;
    }
    return result;
}

5
Зауважте, що якщо ви дійсно робите це в какао, то ви можете скористатися методом [NSBezierPath containsPoint:].
ThomasW

4

Obj-C версія відповіді nirg з методом вибірки для тестування балів. Відповідь Нірга спрацювала на мене добре.

- (BOOL)isPointInPolygon:(NSArray *)vertices point:(CGPoint)test {
    NSUInteger nvert = [vertices count];
    NSInteger i, j, c = 0;
    CGPoint verti, vertj;

    for (i = 0, j = nvert-1; i < nvert; j = i++) {
        verti = [(NSValue *)[vertices objectAtIndex:i] CGPointValue];
        vertj = [(NSValue *)[vertices objectAtIndex:j] CGPointValue];
        if (( (verti.y > test.y) != (vertj.y > test.y) ) &&
        ( test.x < ( vertj.x - verti.x ) * ( test.y - verti.y ) / ( vertj.y - verti.y ) + verti.x) )
            c = !c;
    }

    return (c ? YES : NO);
}

- (void)testPoint {

    NSArray *polygonVertices = [NSArray arrayWithObjects:
        [NSValue valueWithCGPoint:CGPointMake(13.5, 41.5)],
        [NSValue valueWithCGPoint:CGPointMake(42.5, 56.5)],
        [NSValue valueWithCGPoint:CGPointMake(39.5, 69.5)],
        [NSValue valueWithCGPoint:CGPointMake(42.5, 84.5)],
        [NSValue valueWithCGPoint:CGPointMake(13.5, 100.0)],
        [NSValue valueWithCGPoint:CGPointMake(6.0, 70.5)],
        nil
    ];

    CGPoint tappedPoint = CGPointMake(23.0, 70.0);

    if ([self isPointInPolygon:polygonVertices point:tappedPoint]) {
        NSLog(@"YES");
    } else {
        NSLog(@"NO");
    }
}

зразок багатокутника


2
Звичайно, в Objective-C CGPathContainsPoint()- твій друг.
Зев Айзенберг

@ZevEisenberg, але це було б занадто просто! Дякую за замітку. Я розкопаю цей проект в якийсь момент, щоб зрозуміти, чому я використовував власні рішення. Я , ймовірно , не знав проCGPathContainsPoint()
Джон

4

Немає нічого кращого, ніж спонукальне визначення проблеми. Для повноти тут ви маєте версію в пролозі, яка також може уточнити припущення про кастинг :

На основі моделювання алгоритму простоти в http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html

Деякі допоміжні предикати:

exor(A,B):- \+A,B;A,\+B.
in_range(Coordinate,CA,CB) :- exor((CA>Coordinate),(CB>Coordinate)).

inside(false).
inside(_,[_|[]]).
inside(X:Y, [X1:Y1,X2:Y2|R]) :- in_range(Y,Y1,Y2), X > ( ((X2-X1)*(Y-Y1))/(Y2-Y1) +      X1),toggle_ray, inside(X:Y, [X2:Y2|R]); inside(X:Y, [X2:Y2|R]).

get_line(_,_,[]).
get_line([XA:YA,XB:YB],[X1:Y1,X2:Y2|R]):- [XA:YA,XB:YB]=[X1:Y1,X2:Y2]; get_line([XA:YA,XB:YB],[X2:Y2|R]).

Рівняння прямої, заданої 2 точками A і B (Рядок (A, B)), дорівнює:

                    (YB-YA)
           Y - YA = ------- * (X - XA) 
                    (XB-YB) 

Важливо, щоб напрямок обертання лінії було встановлено в тактовому режимі для меж і проти годинникового режиму для отворів. Ми перевіримо, чи точка (X, Y), тобто тестована точка, знаходиться в лівій півплощині нашої прямої (це питання смаку, це також може бути права сторона, але також напрямок меж лінії повинні бути змінені в цьому випадку), це спроектувати промінь з точки вправо (або зліва) і визнати перетин з лінією. Ми вирішили спроектувати промінь у горизонтальному напрямку (знову-таки це питання смаку, його можна було б зробити і у вертикалі з подібними обмеженнями), тому у нас є:

               (XB-XA)
           X < ------- * (Y - YA) + XA
               (YB-YA) 

Тепер нам потрібно знати, чи точка знаходиться лише в лівій (або правій) частині відрізка лінії, а не на всій площині, тому нам потрібно обмежити пошук лише цим відрізком, але це легко, оскільки знаходимось всередині сегмента лише одна точка у рядку може бути вище, ніж Y у вертикальній осі. Оскільки це більш сильне обмеження, його потрібно першою перевірити, тому ми спочатку беремо лише ті рядки, які відповідають цій вимозі, а потім перевіряємо його можливість. За теоремою кривої Йордана будь-який промінь, спроектований на багатокутник, повинен перетинатися по парній кількості прямих. Так ми закінчили, ми будемо кидати промінь праворуч, а потім кожного разу, коли він перетинає лінію, перемикаємо його стан. Однак у нашій реалізації ми маємо намір перевірити довжину пакета рішень, що відповідає заданим обмеженням, та вирішити питання про власність. для кожного рядка в полігоні це потрібно зробити.

is_left_half_plane(_,[],[],_).
is_left_half_plane(X:Y,[XA:YA,XB:YB], [[X1:Y1,X2:Y2]|R], Test) :- [XA:YA, XB:YB] =  [X1:Y1, X2:Y2], call(Test, X , (((XB - XA) * (Y - YA)) / (YB - YA) + XA)); 
                                                        is_left_half_plane(X:Y, [XA:YA, XB:YB], R, Test).

in_y_range_at_poly(Y,[XA:YA,XB:YB],Polygon) :- get_line([XA:YA,XB:YB],Polygon),  in_range(Y,YA,YB).
all_in_range(Coordinate,Polygon,Lines) :- aggregate(bag(Line),    in_y_range_at_poly(Coordinate,Line,Polygon), Lines).

traverses_ray(X:Y, Lines, Count) :- aggregate(bag(Line), is_left_half_plane(X:Y, Line, Lines, <), IntersectingLines), length(IntersectingLines, Count).

% This is the entry point predicate
inside_poly(X:Y,Polygon,Answer) :- all_in_range(Y,Polygon,Lines), traverses_ray(X:Y, Lines, Count), (1 is mod(Count,2)->Answer=inside;Answer=outside).

3

C # версія відповіді nirg тут: я просто поділюсь кодом. Це може заощадити когось деякий час.

public static bool IsPointInPolygon(IList<Point> polygon, Point testPoint) {
            bool result = false;
            int j = polygon.Count() - 1;
            for (int i = 0; i < polygon.Count(); i++) {
                if (polygon[i].Y < testPoint.Y && polygon[j].Y >= testPoint.Y || polygon[j].Y < testPoint.Y && polygon[i].Y >= testPoint.Y) {
                    if (polygon[i].X + (testPoint.Y - polygon[i].Y) / (polygon[j].Y - polygon[i].Y) * (polygon[j].X - polygon[i].X) < testPoint.X) {
                        result = !result;
                    }
                }
                j = i;
            }
            return result;
        }

це працює в більшості випадків, але це неправильно і не працює належним чином завжди! скористайтеся правильним рішенням M Katz
Лукаш Ханачек

3

Версія Java:

public class Geocode {
    private float latitude;
    private float longitude;

    public Geocode() {
    }

    public Geocode(float latitude, float longitude) {
        this.latitude = latitude;
        this.longitude = longitude;
    }

    public float getLatitude() {
        return latitude;
    }

    public void setLatitude(float latitude) {
        this.latitude = latitude;
    }

    public float getLongitude() {
        return longitude;
    }

    public void setLongitude(float longitude) {
        this.longitude = longitude;
    }
}

public class GeoPolygon {
    private ArrayList<Geocode> points;

    public GeoPolygon() {
        this.points = new ArrayList<Geocode>();
    }

    public GeoPolygon(ArrayList<Geocode> points) {
        this.points = points;
    }

    public GeoPolygon add(Geocode geo) {
        points.add(geo);
        return this;
    }

    public boolean inside(Geocode geo) {
        int i, j;
        boolean c = false;
        for (i = 0, j = points.size() - 1; i < points.size(); j = i++) {
            if (((points.get(i).getLongitude() > geo.getLongitude()) != (points.get(j).getLongitude() > geo.getLongitude())) &&
                    (geo.getLatitude() < (points.get(j).getLatitude() - points.get(i).getLatitude()) * (geo.getLongitude() - points.get(i).getLongitude()) / (points.get(j).getLongitude() - points.get(i).getLongitude()) + points.get(i).getLatitude()))
                c = !c;
        }
        return c;
    }

}

2

.Net порт:

    static void Main(string[] args)
    {

        Console.Write("Hola");
        List<double> vertx = new List<double>();
        List<double> verty = new List<double>();

        int i, j, c = 0;

        vertx.Add(1);
        vertx.Add(2);
        vertx.Add(1);
        vertx.Add(4);
        vertx.Add(4);
        vertx.Add(1);

        verty.Add(1);
        verty.Add(2);
        verty.Add(4);
        verty.Add(4);
        verty.Add(1);
        verty.Add(1);

        int nvert = 6;  //Vértices del poligono

        double testx = 2;
        double testy = 5;


        for (i = 0, j = nvert - 1; i < nvert; j = i++)
        {
            if (((verty[i] > testy) != (verty[j] > testy)) &&
             (testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i]))
                c = 1;
        }
    }

2

ВЕРСІЯ VBA:

Примітка. Пам’ятайте, що якщо ваш багатокутник - це область на карті, то ширина / довгота є значеннями Y / X на відміну від X / Y (Широта = Y, Довгота = X) через те, що я розумію, це історичні наслідки від шляху назад, коли Довгота не була виміром.

КЛАСОВИЙ МОДУЛЬ: CPoint

Private pXValue As Double
Private pYValue As Double

'''''X Value Property'''''

Public Property Get X() As Double
    X = pXValue
End Property

Public Property Let X(Value As Double)
    pXValue = Value
End Property

'''''Y Value Property'''''

Public Property Get Y() As Double
    Y = pYValue
End Property

Public Property Let Y(Value As Double)
    pYValue = Value
End Property

МОДУЛЬ:

Public Function isPointInPolygon(p As CPoint, polygon() As CPoint) As Boolean

    Dim i As Integer
    Dim j As Integer
    Dim q As Object
    Dim minX As Double
    Dim maxX As Double
    Dim minY As Double
    Dim maxY As Double
    minX = polygon(0).X
    maxX = polygon(0).X
    minY = polygon(0).Y
    maxY = polygon(0).Y

    For i = 1 To UBound(polygon)
        Set q = polygon(i)
        minX = vbMin(q.X, minX)
        maxX = vbMax(q.X, maxX)
        minY = vbMin(q.Y, minY)
        maxY = vbMax(q.Y, maxY)
    Next i

    If p.X < minX Or p.X > maxX Or p.Y < minY Or p.Y > maxY Then
        isPointInPolygon = False
        Exit Function
    End If


    ' SOURCE: http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html

    isPointInPolygon = False
    i = 0
    j = UBound(polygon)

    Do While i < UBound(polygon) + 1
        If (polygon(i).Y > p.Y) Then
            If (polygon(j).Y < p.Y) Then
                If p.X < (polygon(j).X - polygon(i).X) * (p.Y - polygon(i).Y) / (polygon(j).Y - polygon(i).Y) + polygon(i).X Then
                    isPointInPolygon = True
                    Exit Function
                End If
            End If
        ElseIf (polygon(i).Y < p.Y) Then
            If (polygon(j).Y > p.Y) Then
                If p.X < (polygon(j).X - polygon(i).X) * (p.Y - polygon(i).Y) / (polygon(j).Y - polygon(i).Y) + polygon(i).X Then
                    isPointInPolygon = True
                    Exit Function
                End If
            End If
        End If
        j = i
        i = i + 1
    Loop   
End Function

Function vbMax(n1, n2) As Double
    vbMax = IIf(n1 > n2, n1, n2)
End Function

Function vbMin(n1, n2) As Double
    vbMin = IIf(n1 > n2, n2, n1)
End Function


Sub TestPointInPolygon()

    Dim i As Integer
    Dim InPolygon As Boolean

'   MARKER Object
    Dim p As CPoint
    Set p = New CPoint
    p.X = <ENTER X VALUE HERE>
    p.Y = <ENTER Y VALUE HERE>

'   POLYGON OBJECT
    Dim polygon() As CPoint
    ReDim polygon(<ENTER VALUE HERE>) 'Amount of vertices in polygon - 1
    For i = 0 To <ENTER VALUE HERE> 'Same value as above
       Set polygon(i) = New CPoint
       polygon(i).X = <ASSIGN X VALUE HERE> 'Source a list of values that can be looped through
       polgyon(i).Y = <ASSIGN Y VALUE HERE> 'Source a list of values that can be looped through
    Next i

    InPolygon = isPointInPolygon(p, polygon)
    MsgBox InPolygon

End Sub

2

Я зробив реалізацію Python з nirg в C ++ код :

Вхідні дані

  • bordering_points: вузли, що складають багатокутник.
  • limiting_box_positions: кандидатські бали для фільтрації. (У моїй реалізації створено з обмежувального поля.

    (Входи списки кортежів в форматі: [(xcord, ycord), ...])

Повертається

  • Усі точки, що знаходяться всередині багатокутника.
def polygon_ray_casting(self, bounding_points, bounding_box_positions):
    # Arrays containing the x- and y-coordinates of the polygon's vertices.
    vertx = [point[0] for point in bounding_points]
    verty = [point[1] for point in bounding_points]
    # Number of vertices in the polygon
    nvert = len(bounding_points)
    # Points that are inside
    points_inside = []

    # For every candidate position within the bounding box
    for idx, pos in enumerate(bounding_box_positions):
        testx, testy = (pos[0], pos[1])
        c = 0
        for i in range(0, nvert):
            j = i - 1 if i != 0 else nvert - 1
            if( ((verty[i] > testy ) != (verty[j] > testy))   and
                    (testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i]) ):
                c += 1
        # If odd, that means that we are inside the polygon
        if c % 2 == 1: 
            points_inside.append(pos)


    return points_inside

І знову ідея взята звідси


2

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

Що ви шукаєте:

db.neighborhoods.findOne ({геометрія: {$ geoIntersects: {$ геометрія: {тип: "Точка", координати: ["довгота", "широта"]}}}})

Neighborhoodsце колекція, яка зберігає один або кілька полігонів у стандартному форматі GeoJson. Якщо запит повертає нуль, він не перетинається інакше.

Дуже добре задокументовано тут: https://docs.mongodb.com/manual/tutorial/geospatial-tutorial/

Продуктивність для більш ніж 6000 балів, класифікованих в 330 неправильній багатокутній сітці, була менше однієї хвилини, без оптимізації взагалі і включаючи час на оновлення документів на відповідному полігоні.


1

Ось крапка в полігоновому тесті на C, яка не використовує рентгенівське лиття. І він може працювати для ділянок, що перекриваються (самоперехрестя), дивіться use_holesаргумент.

/* math lib (defined below) */
static float dot_v2v2(const float a[2], const float b[2]);
static float angle_signed_v2v2(const float v1[2], const float v2[2]);
static void copy_v2_v2(float r[2], const float a[2]);

/* intersection function */
bool isect_point_poly_v2(const float pt[2], const float verts[][2], const unsigned int nr,
                         const bool use_holes)
{
    /* we do the angle rule, define that all added angles should be about zero or (2 * PI) */
    float angletot = 0.0;
    float fp1[2], fp2[2];
    unsigned int i;
    const float *p1, *p2;

    p1 = verts[nr - 1];

    /* first vector */
    fp1[0] = p1[0] - pt[0];
    fp1[1] = p1[1] - pt[1];

    for (i = 0; i < nr; i++) {
        p2 = verts[i];

        /* second vector */
        fp2[0] = p2[0] - pt[0];
        fp2[1] = p2[1] - pt[1];

        /* dot and angle and cross */
        angletot += angle_signed_v2v2(fp1, fp2);

        /* circulate */
        copy_v2_v2(fp1, fp2);
        p1 = p2;
    }

    angletot = fabsf(angletot);
    if (use_holes) {
        const float nested = floorf((angletot / (float)(M_PI * 2.0)) + 0.00001f);
        angletot -= nested * (float)(M_PI * 2.0);
        return (angletot > 4.0f) != ((int)nested % 2);
    }
    else {
        return (angletot > 4.0f);
    }
}

/* math lib */

static float dot_v2v2(const float a[2], const float b[2])
{
    return a[0] * b[0] + a[1] * b[1];
}

static float angle_signed_v2v2(const float v1[2], const float v2[2])
{
    const float perp_dot = (v1[1] * v2[0]) - (v1[0] * v2[1]);
    return atan2f(perp_dot, dot_v2v2(v1, v2));
}

static void copy_v2_v2(float r[2], const float a[2])
{
    r[0] = a[0];
    r[1] = a[1];
}

Примітка. Це один з менш оптимальних методів, оскільки він включає в себе безліч викликів atan2f, але розробникам, які читають цю нитку, це може зацікавити (у моїх тестах його ~ 23x повільніше, ніж використовувати метод перетину ліній).


0

Для виявлення хіта на Polygon нам потрібно перевірити дві речі:

  1. Якщо точка знаходиться всередині полігону. (може бути виконано алгоритмом Рей-лиття)
  2. Якщо точка знаходиться на межі полігона (може бути виконана за тим самим алгоритмом, який використовується для виявлення точки на полілінії (лінії)).

0

Для вирішення наступних спеціальних випадків в алгоритмі реєстрування Рея :

  1. Промінь перекриває одну зі сторін багатокутника.
  2. Точка знаходиться всередині многокутника і промінь проходить через вершину багатокутника.
  3. Точка знаходиться поза полігоном, і промінь просто торкається одного кута полігона.

Перевірте, чи визначається, чи знаходиться точка всередині складного багатокутника . У статті запропоновано простий спосіб їх вирішити, щоб не було необхідного спеціального лікування для вищезазначених випадків.


0

Це можна зробити, перевіривши, чи відповідає площа, утворена підключенням потрібної точки до вершин вашого багатокутника, площі самого полігону.

Або ви можете перевірити, чи сума внутрішніх кутів від вашої точки до кожної пари двох послідовних вершин багатокутника до вашої контрольної точки становить 360, але я маю відчуття, що перший варіант швидше, тому що він не передбачає поділів та обчислень. зворотних тригонометричних функцій.

Я не знаю, що станеться, якщо ваш полігон має в ньому отвір, але мені здається, що основну ідею можна адаптувати до цієї ситуації

Ви також можете опублікувати питання в математичному співтоваристві. Надіюсь, у них є один мільйон способів зробити це


0

Якщо ви шукаєте бібліотеку java-скриптів, то для класу Polygon розширення google maps v3 для JavaScript визначає, чи знаходиться в ньому точка чи ні.

var polygon = new google.maps.Polygon([], "#000000", 1, 1, "#336699", 0.3);
var isWithinPolygon = polygon.containsLatLng(40, -90);

Google Extention Github



0

Відповідь залежить від того, чи є у вас прості чи складні багатокутники. Прості багатокутники не повинні мати жодних перетинів відрізка ліній. Таким чином, вони можуть мати отвори, але лінії не можуть перетинатися одна з одною. Складні регіони можуть мати перехрестя ліній - тому вони можуть мати області, що перекриваються, або регіони, які торкаються один одного лише однією точкою.

Для простих багатокутників найкращим алгоритмом є алгоритм відливання Рея (номер перехрещення). Для складних багатокутників цей алгоритм не виявляє точки, які знаходяться всередині областей, що перекриваються. Отже, для складних багатокутників потрібно використовувати алгоритм числення Winding.

Ось чудова стаття із С реалізацією обох алгоритмів. Я спробував їх, і вони добре працюють.

http://geomalgorithms.com/a03-_inclusion.html


0

Версія рішення Scala за допомогою nirg (передбачається, що попередня перевірка обмеження прямокутника проводиться окремо):

def inside(p: Point, polygon: Array[Point], bounds: Bounds): Boolean = {

  val length = polygon.length

  @tailrec
  def oddIntersections(i: Int, j: Int, tracker: Boolean): Boolean = {
    if (i == length)
      tracker
    else {
      val intersects = (polygon(i).y > p.y) != (polygon(j).y > p.y) && p.x < (polygon(j).x - polygon(i).x) * (p.y - polygon(i).y) / (polygon(j).y - polygon(i).y) + polygon(i).x
      oddIntersections(i + 1, i, if (intersects) !tracker else tracker)
    }
  }

  oddIntersections(0, length - 1, tracker = false)
}

0

Ось голангова версія відповіді @nirg (натхненна кодом C # @@ m-katz)

func isPointInPolygon(polygon []point, testp point) bool {
    minX := polygon[0].X
    maxX := polygon[0].X
    minY := polygon[0].Y
    maxY := polygon[0].Y

    for _, p := range polygon {
        minX = min(p.X, minX)
        maxX = max(p.X, maxX)
        minY = min(p.Y, minY)
        maxY = max(p.Y, maxY)
    }

    if testp.X < minX || testp.X > maxX || testp.Y < minY || testp.Y > maxY {
        return false
    }

    inside := false
    j := len(polygon) - 1
    for i := 0; i < len(polygon); i++ {
        if (polygon[i].Y > testp.Y) != (polygon[j].Y > testp.Y) && testp.X < (polygon[j].X-polygon[i].X)*(testp.Y-polygon[i].Y)/(polygon[j].Y-polygon[i].Y)+polygon[i].X {
            inside = !inside
        }
        j = i
    }

    return inside
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.