Я пытаюсь создать быструю 2D точку внутри алгоритма многоугольника для использования при тестировании попаданий (например Polygon.contains(p:Point)
). Предложения для эффективных методов будут оценены.
performance
graphics
collision-detection
polygon
point-in-polygon
Скотт Эверден
источник
источник
Ответы:
Для графики я бы предпочел не предпочитать целые числа. Многие системы используют целые числа для рисования пользовательского интерфейса (в конце концов, пиксели - это целые числа), но, например, macOS использует float для всего. macOS знает только точки, и точка может переводиться в один пиксель, но в зависимости от разрешения монитора она может переводиться во что-то другое. На экранах сетчатки половина точки (0,5 / 0,5) составляет пиксель. Тем не менее, я никогда не замечал, что пользовательские интерфейсы macOS значительно медленнее, чем другие пользовательские интерфейсы. Ведь 3D API (OpenGL или Direct3D) также работают с плавающей точкой, а современные графические библиотеки очень часто используют преимущества ускорения GPU.
Теперь вы сказали, что скорость - ваша главная задача, хорошо, давайте перейдем к скорости. Прежде чем запускать какой-либо сложный алгоритм, сначала выполните простой тест. Создайте ограничивающий прямоугольник с осями вокруг вашего многоугольника. Это очень легко, быстро и уже может обезопасить вас от многих расчетов. Как это работает? Выполните итерацию по всем точкам многоугольника и найдите минимальные / максимальные значения X и Y.
Например, у вас есть очки
(9/1), (4/3), (2/7), (8/2), (3/6)
. Это означает, что Xmin равно 2, Xmax равно 9, Ymin равно 1 и Ymax равно 7. Точка за пределами прямоугольника с двумя ребрами (2/1) и (9/7) не может быть внутри многоугольника.Это первый тест для любой точки. Как видите, этот тест очень быстрый, но он также очень грубый. Для обработки точек, которые находятся внутри ограничительного прямоугольника, нам нужен более сложный алгоритм. Есть несколько способов, как это можно рассчитать. Какой метод работает, также зависит от того, может ли многоугольник иметь отверстия или всегда будет сплошным. Вот примеры твердых (одна выпуклая, одна вогнутая):
А вот один с дыркой:
Зеленый имеет отверстие в середине!
Самый простой алгоритм, который может обрабатывать все три приведенных выше случая и который все еще довольно быстр, называется приведением лучей . Идея алгоритма довольно проста: нарисуйте виртуальный луч из любой точки за пределами многоугольника к своей точке и посчитайте, как часто он попадает на сторону многоугольника. Если число совпадений четное, оно находится за пределами многоугольника, если оно нечетное, оно внутри.
Алгоритмом обмотки числа будет являться альтернативой, является более точным для точек , находящихся в непосредственной близости от полигона линии , но это также гораздо медленнее. Приведение лучей может быть неудачным для точек, расположенных слишком близко к стороне многоугольника из-за ограниченной точности с плавающей точкой и проблем с округлением, но в действительности это вряд ли проблема, так как если точка находится близко к стороне, это часто визуально даже невозможно для зритель распознает, находится ли он уже внутри или еще снаружи.
У вас все еще есть ограничительная рамка выше, помните? Просто выберите точку за пределами ограничительной рамки и используйте ее в качестве отправной точки для вашего луча. Например, точка
(Xmin - e/p.y)
находится вне многоугольника точно.А что есть
e
? Ну,e
(на самом деле эпсилон) дает ограничивающей рамке немного отступов . Как я уже сказал, трассировка лучей завершится неудачей, если мы начнем слишком близко к линии многоугольника. Поскольку ограничивающий прямоугольник может равняться многоугольнику (если многоугольник является выровненным по оси прямоугольником, ограничивающий прямоугольник равен самому многоугольнику!), Нам нужно добавить отступы, чтобы сделать это безопасно, вот и все. Насколько большой вы должны выбратьe
? Не слишком большой Это зависит от масштаба системы координат, который вы используете для рисования. Если ширина шага вашего пикселя равна 1,0, просто выберите 1,0 (но 0,1 также сработало бы)Теперь, когда у нас есть луч с его начальной и конечной координатами, проблема смещается с « точки внутри многоугольника » на « как часто луч пересекает сторону многоугольника ». Поэтому мы не можем просто работать с точками многоугольника, как раньше, теперь нам нужны фактические стороны. Сторона всегда определяется двумя точками.
Вам нужно проверить луч со всех сторон. Считайте, что луч - вектор, а каждая сторона - вектор. Луч должен поразить каждую сторону ровно один раз или никогда. Он не может попасть в одну и ту же сторону дважды. Две линии в 2D-пространстве всегда будут пересекаться ровно один раз, если они не параллельны, и в этом случае они никогда не пересекаются. Однако, поскольку векторы имеют ограниченную длину, два вектора могут не быть параллельными и никогда не пересекаться, потому что они слишком короткие, чтобы когда-либо встречаться.
Пока все хорошо, но как проверить, пересекаются ли два вектора? Вот некоторый код на C (не проверенный), который должен сделать свое дело:
Входными значениями являются две конечные точки вектора 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) выборка из памяти.
Конечно, этот метод можно использовать только в том случае, если ваша поверхность для рисования не обязательно должна быть огромной. Если он не может поместиться в память графического процессора, этот метод медленнее, чем в ЦП. Если он должен быть огромным, и ваш графический процессор поддерживает современные шейдеры, вы все равно можете использовать графический процессор, реализовав приведенное выше приведение лучей в качестве графического шейдера, что абсолютно возможно. Для большего количества полигонов или большого количества точек для тестирования это окупится, учитывая, что некоторые графические процессоры смогут тестировать от 64 до 256 точек параллельно. Тем не менее, обратите внимание, что передача данных из CPU в GPU и обратно всегда обходится дорого, поэтому для простого тестирования пары точек на пару простых многоугольников, где либо точки, либо многоугольники являются динамическими и будут часто меняться, подход с использованием графического процессора редко платит выкл.
источник
Я думаю, что следующий фрагмент кода - лучшее решение (взято отсюда ):
аргументы
Он короткий и эффективный и работает как для выпуклых, так и для вогнутых многоугольников. Как предлагалось ранее, вы должны сначала проверить ограничивающий прямоугольник и обработать отверстия многоугольника отдельно.
Идея этого довольно проста. Автор описывает это следующим образом:
Переменная c переключается с 0 на 1 и с 1 на 0 каждый раз, когда горизонтальный луч пересекает любое ребро. Так что в основном он отслеживает, является ли количество пересеченных ребер четным или нечетным. 0 означает четное, а 1 означает нечетное.
источник
verty[i]
иverty[j]
обе стороныtesty
, поэтому они никогда не равны.Вот версия ответа Nirg на языке C # от профессора RPI . Обратите внимание, что использование кода из этого источника RPI требует указания авторства.
Ограничительный флажок был добавлен вверху. Тем не менее, как указывает Джеймс Браун, основной код работает почти так же быстро, как и сам флажок ограничивающего прямоугольника, поэтому флажок ограничивающего прямоугольника может фактически замедлить всю операцию, в случае, если большинство проверяемых точек находятся внутри ограничивающего прямоугольника. , Таким образом, вы можете оставить флажок ограничивающим, или альтернативой может быть предварительный расчет ограничивающих прямоугольников ваших полигонов, если они не меняют форму слишком часто.
источник
Вот вариант ответа М. Каца на JavaScript, основанный на подходе Нирга:
источник
Вычислить ориентированную сумму углов между точкой р и каждым из вершин многоугольника. Если общий угол ориентирования составляет 360 градусов, точка находится внутри. Если сумма равна 0, точка находится за пределами.
Мне больше нравится этот метод, потому что он более надежный и менее зависит от точности чисел.
Методы, которые вычисляют равномерность количества пересечений, ограничены, потому что вы можете «поразить» вершину во время вычисления количества пересечений.
РЕДАКТИРОВАТЬ: Кстати, этот метод работает с вогнутыми и выпуклыми полигонами.
РЕДАКТИРОВАТЬ: я недавно нашел целую статью в Википедии по этой теме.
источник
Этот вопрос так интересен. У меня есть другая работоспособная идея, отличная от других ответов на этот пост. Идея состоит в том, чтобы использовать сумму углов, чтобы решить, находится ли цель внутри или снаружи. Лучше известный как число обмоток .
Пусть х будет целевой точкой. Пусть массив [0, 1, .... n] будет всеми точками области. Соедините целевую точку с каждой граничной точкой линией. Если целевая точка находится внутри этой области. Сумма всех углов будет 360 градусов. Если не углы будут меньше 360.
Обратитесь к этому изображению, чтобы получить общее представление о идее:
Мой алгоритм предполагает, что по часовой стрелке это положительное направление. Вот потенциальный вклад:
Ниже приведен код Python, который реализует идею:
источник
Статья Эрик Haines цитируется bobobobo действительно отлично. Особенно интересными являются таблицы, сравнивающие производительность алгоритмов; метод суммирования углов действительно плох по сравнению с другими. Также интересно то, что оптимизация, например использование поисковой сетки для дальнейшего разделения полигона на секторы «in» и «out», может сделать тест невероятно быстрым даже на полигонах с> 1000 сторонами.
Во всяком случае, это первые дни, но мой голос идет по методу «пересечений», который, по-моему, описывает Меки. Однако я обнаружил, что это наиболее кратко описано и кодифицировано Дэвидом Бурком . Мне нравится, что не требуется никакой реальной тригонометрии, она работает для выпуклых и вогнутых поверхностей и работает достаточно хорошо, так как количество сторон увеличивается.
Кстати, вот одна из таблиц производительности из статьи Эрика Хейнса для интереса, тестирование на случайных полигонах.
источник
Свифт версия ответа по nirg :
источник
Очень нравится решение, опубликованное Nirg и отредактированное bobobobo. Я только сделал его дружественным к JavaScript и немного более разборчивым для моего использования:
источник
Я работал над этим, когда был исследователем под руководством Майкла Стоунбрейкера - вы знаете, профессора, который придумал Ingres , PostgreSQL и т. Д.
Мы поняли, что самым быстрым способом было сначала создать ограничивающую рамку, потому что это СУПЕР быстро. Если это вне ограничительной рамки, это снаружи. В противном случае, вы делаете тяжелую работу ...
Если вы хотите отличный алгоритм, посмотрите на исходный код PostgreSQL с открытым исходным кодом для гео работы ...
Я хочу отметить, что мы никогда не понимали право против левши (также выражаемое как проблема «внутри» и «снаружи» ...
ОБНОВИТЬ
Ссылка БКБ предоставила множество разумных алгоритмов. Я работал над проблемами наук о Земле и поэтому нуждался в решении, которое работает по широте / долготе, и у него есть особая проблема с ручностью - область внутри меньшей области или большей области? Ответ заключается в том, что «направление» вершин имеет значение - оно либо левостороннее, либо правостороннее, и таким образом вы можете указать любую область как «внутри» любого данного многоугольника. Таким образом, моя работа использовала решение три, перечисленное на этой странице.
Кроме того, в моей работе использовались отдельные функции для тестов «на линии».
... Так как кто-то спросил: мы выяснили, что тесты ограничивающего прямоугольника были наилучшими, когда число вершин превысило некоторое число - проведите очень быстрый тест, прежде чем выполнять более длинный тест, если это необходимо ... Ограничивающий прямоугольник создается простым взятием наибольший x, наименьший x, наибольший y и наименьший y и объединение их в четыре точки в квадрате ...
Еще один совет для тех, кто следует: мы выполнили все наши более сложные и «светорегуляторные» вычисления в пространстве сетки, все в положительных точках на плоскости, а затем снова проецировали обратно в «реальную» долготу / широту, избегая, таким образом, возможных ошибок обтекание, когда одна пересекает линию 180 долготы и при работе с полярными областями. Работал отлично!
источник
Ответ Дэвида Сегонда в значительной степени является стандартным общим ответом, а Ричард Т - наиболее распространенная оптимизация, хотя есть и другие. Другие сильные оптимизации основаны на менее общих решениях. Например, если вы собираетесь проверять один и тот же многоугольник с множеством точек, триангуляция многоугольника может значительно ускорить процесс, поскольку существует ряд очень быстрых алгоритмов поиска TIN. Другой вариант: если многоугольник и точки находятся на ограниченной плоскости при низком разрешении, например, на экране, вы можете нарисовать многоугольник в отображаемом в память буфере отображения заданного цвета и проверить цвет данного пикселя, чтобы увидеть, лежит ли он в полигонах.
Как и многие оптимизации, они основаны на конкретных, а не общих случаях, и приносят выгоды, основанные на амортизированном времени, а не на единичном использовании.
Работая в этой области, я обнаружил, что вычислительная геометрия Джозефа О'Руркса в C 'ISBN 0-521-44034-3 очень помогает.
источник
Тривиальным решением было бы разделить многоугольник на треугольники и нажать «проверить треугольники», как описано здесь
Если у вас многоугольник CONVEX, то может быть лучше. Посмотрите на многоугольник как на коллекцию бесконечных линий. Каждая строка делит пространство на две части. для каждой точки легко сказать, находится ли она на одной стороне или другой стороне линии. Если точка находится на одной стороне всех линий, она находится внутри многоугольника.
источник
Я понимаю, что это старый, но вот алгоритм наложения лучей, реализованный в Какао, на случай, если кому-то будет интересно. Не уверен, что это самый эффективный способ сделать что-то, но он может помочь кому-то.
источник
Obj-C версия ответа nirg с примером метода для тестирования точек. Ответ Нирга хорошо сработал для меня.
источник
CGPathContainsPoint()
это твой друг.CGPathContainsPoint()
Нет ничего более красивого, чем индуктивное определение проблемы. Для полноты здесь у вас есть версия в прологе, которая может также прояснить соображения, лежащие в основе приведения лучей :
Основано на алгоритме симуляции простоты в http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
Некоторые предикаты помощника:
Уравнение прямой с учетом 2 точек A и B (Линия (A, B)) имеет вид:
Важно, чтобы направление вращения линии было установлено по часовой стрелке для границ и против часовой стрелки для отверстий. Мы собираемся проверить, находится ли точка (X, Y), то есть проверенная точка, в левой полуплоскости нашей линии (это вопрос вкуса, это также может быть правая сторона, но также и направление границ линии должны быть изменены в этом случае), это проецирует луч из точки вправо (или влево) и подтверждает пересечение с линией. Мы решили проецировать луч в горизонтальном направлении (опять же, это вопрос вкуса, это также может быть сделано по вертикали с аналогичными ограничениями), поэтому мы имеем:
Теперь нам нужно знать, находится ли точка только на левой (или правой) стороне сегмента линии, а не на всей плоскости, поэтому нам нужно ограничить поиск только этим сегментом, но это легко, поскольку находиться внутри сегмента только одна точка на линии может быть выше, чем Y на вертикальной оси. Поскольку это более сильное ограничение, оно должно быть первым, чтобы проверить, поэтому мы сначала выбираем только те строки, которые отвечают этому требованию, а затем проверяем его возможность. По теореме Кривая Джордана любой луч, проецируемый на многоугольник, должен пересекаться на четном числе линий. Итак, мы закончили, мы бросим луч вправо, а затем каждый раз, когда он пересекает линию, переключают свое состояние. Однако в нашей реализации мы собираемся проверить длину пакета решений, отвечающих заданным ограничениям, и принять решение об участии в нем. для каждой линии в многоугольнике это должно быть сделано.
источник
C # версия ответа nirg здесь: я просто поделюсь кодом. Это может сэкономить кому-то время.
источник
Версия Java:
источник
Порт .Net:
источник
VBA VERSION:
Примечание. Помните, что если полигон является областью на карте, то широта / долгота являются значениями Y / X, а не X / Y (широта = Y, долгота = X) из-за того, что, как я понимаю, являются историческими последствиями, когда Долгота не была измерением.
МОДУЛЬ КЛАССА: CPoint
МОДУЛЬ:
источник
Я сделал реализацию Python из nirg в C ++ код :
входные
bounding_box_positions: кандидат указывает на фильтр. (В моей реализации создано из ограничительной рамки.
(Входы списки кортежей в формате:
[(xcord, ycord), ...]
)Возвращает
Опять же, идея взята отсюда
источник
Удивило, что никто не поднял этот вопрос раньше, кроме тех прагматиков, которым нужна база данных: MongoDB имеет отличную поддержку запросов Geo, включая этот.
То, что вы ищете, это:
Neighborhoods
это коллекция, которая хранит один или несколько полигонов в стандартном формате GeoJson. Если запрос возвращает значение NULL, он не пересекается, иначе это так.Очень хорошо задокументировано здесь: https://docs.mongodb.com/manual/tutorial/geospatial-tutorial/
Производительность для более чем 6000 точек, классифицированных в сетке из 330 нерегулярных многоугольников, была меньше одной минуты без какой-либо оптимизации вообще, включая время для обновления документов соответствующим полигоном.
источник
Вот точка в тесте полигонов в C, которая не использует приведение лучей. И это может работать для перекрывающихся областей (самопересечения), смотрите
use_holes
аргумент.Примечание: это один из менее оптимальных методов, так как он включает в себя множество вызовов
atan2f
, но он может быть интересен разработчикам, читающим этот поток (в моих тестах он примерно в 23 раза медленнее, чем при использовании метода пересечения линий).источник
Для обнаружения попадания в полигон нам нужно проверить две вещи:
источник
Чтобы справиться со следующими частными случаями в алгоритме приведения лучей :
Проверьте, находится ли точка внутри сложного многоугольника . В статье представлен простой способ их решения, поэтому для указанных выше случаев не требуется никакого специального лечения.
источник
Вы можете сделать это, проверив, совпадает ли область, образованная путем соединения нужной точки с вершинами вашего многоугольника, с областью самого многоугольника.
Или вы можете проверить, что сумма внутренних углов от вашей точки до каждой пары из двух последовательных вершин многоугольника до вашей контрольной точки равна 360, но у меня есть ощущение, что первый вариант быстрее, потому что он не включает деления или вычисления инверсии тригонометрических функций.
Я не знаю, что случится, если внутри вашего полигона будет дыра, но мне кажется, что основная идея может быть адаптирована к этой ситуации.
Вы также можете опубликовать вопрос в математическом сообществе. Могу поспорить, у них есть миллион способов сделать это
источник
Если вы ищете библиотеку java-script, есть класс javascript google maps v3 для класса Polygon, чтобы определить, находится ли точка внутри него.
Google Extention Github
источник
Когда используешь кварты(Qt 4.3+), можно использовать функцию QPolygon containsPoint
источник
Ответ зависит от того, есть ли у вас простые или сложные полигоны. Простые многоугольники не должны иметь пересечений отрезков. Таким образом, они могут иметь отверстия, но линии не могут пересекаться друг с другом. Сложные области могут иметь пересечения линий, поэтому они могут иметь перекрывающиеся области или области, которые касаются друг друга только одной точкой.
Для простых полигонов лучшим алгоритмом является алгоритм преобразования лучей (число пересечений). Для сложных полигонов этот алгоритм не обнаруживает точки, которые находятся внутри перекрывающихся областей. Так что для сложных полигонов вы должны использовать алгоритм числа обмоток.
Вот отличная статья с реализацией C обоих алгоритмов. Я попробовал их, и они хорошо работают.
http://geomalgorithms.com/a03-_inclusion.html
источник
Scala-версия решения по nirg (предполагается, что предварительная проверка ограничивающего прямоугольника выполняется отдельно):
источник
Вот golang-версия ответа @nirg (вдохновлена кодом C # @@ m-katz)
источник