Estoy tratando de crear un punto 2D rápido dentro del algoritmo de polígono, para usar en pruebas de impacto (por ejemplo Polygon.contains(p:Point)
). Se agradecerán sugerencias para técnicas efectivas.
performance
graphics
collision-detection
polygon
point-in-polygon
Scott Evernden
fuente
fuente
Respuestas:
Para los gráficos, prefiero no preferir los enteros. Muchos sistemas usan enteros para la pintura de la interfaz de usuario (los píxeles son ints después de todo), pero macOS, por ejemplo, usa flotante para todo. macOS solo conoce puntos y un punto puede traducirse a un píxel, pero dependiendo de la resolución del monitor, podría traducirse a otra cosa. En las pantallas de retina, medio punto (0.5 / 0.5) es píxel. Aún así, nunca noté que las interfaces de usuario de macOS son significativamente más lentas que otras interfaces de usuario. Después de todo, las API 3D (OpenGL o Direct3D) también funcionan con flotadores y las bibliotecas de gráficos modernos a menudo aprovechan la aceleración de la GPU.
Ahora dijiste que la velocidad es tu principal preocupación, de acuerdo, vamos por la velocidad. Antes de ejecutar cualquier algoritmo sofisticado, primero haga una prueba simple. Cree un cuadro delimitador alineado con el eje alrededor de su polígono. Esto es muy fácil, rápido y ya puede ahorrarle muchos cálculos. ¿Cómo funciona? Itere sobre todos los puntos del polígono y encuentre los valores mínimo / máximo de X e Y.
Por ejemplo, tienes los puntos
(9/1), (4/3), (2/7), (8/2), (3/6)
. Esto significa que Xmin es 2, Xmax es 9, Ymin es 1 e Ymax es 7. Un punto fuera del rectángulo con los dos bordes (2/1) y (9/7) no puede estar dentro del polígono.Esta es la primera prueba que se ejecuta en cualquier punto. Como puede ver, esta prueba es ultra rápida pero también es muy tosca. Para manejar puntos que están dentro del rectángulo delimitador, necesitamos un algoritmo más sofisticado. Hay un par de maneras de cómo se puede calcular esto. El método que funcione también depende del hecho de si el polígono puede tener agujeros o siempre será sólido. Aquí hay ejemplos de sólidos (uno convexo, uno cóncavo):
Y aquí hay uno con un agujero:
¡El verde tiene un agujero en el medio!
El algoritmo más fácil, que puede manejar los tres casos anteriores y que sigue siendo bastante rápido, se llama proyección de rayos . La idea del algoritmo es bastante simple: dibuje un rayo virtual desde cualquier lugar fuera del polígono hasta su punto y cuente con qué frecuencia golpea un lado del polígono. Si el número de golpes es par, está fuera del polígono, si es impar, está dentro.
El algoritmo de número de bobinado sería una alternativa, es más preciso para los puntos que están muy cerca de una línea de polígono, pero también es mucho más lento. La proyección de rayos puede fallar para puntos demasiado cercanos a un lado del polígono debido a la precisión limitada del punto flotante y los problemas de redondeo, pero en realidad eso no es un problema, ya que si un punto se encuentra tan cerca de un lado, a menudo no es posible visualmente espectador para reconocer si ya está adentro o afuera.
Todavía tienes el cuadro delimitador de arriba, ¿recuerdas? Simplemente elija un punto fuera del cuadro delimitador y úselo como punto de partida para su rayo. Por ejemplo, el punto
(Xmin - e/p.y)
está fuera del polígono seguro.Pero que es
e
? Bueno,e
(en realidad épsilon) le da al cuadro delimitador algo de relleno . Como dije, el trazado de rayos falla si comenzamos demasiado cerca de una línea de polígono. Dado que el cuadro delimitador puede ser igual al polígono (si el polígono es un rectángulo alineado con el eje, el cuadro delimitador es igual al polígono mismo), necesitamos algo de relleno para que esto sea seguro, eso es todo. ¿Qué tan grande debes elegire
? No tan grande. Depende de la escala del sistema de coordenadas que use para dibujar. Si su ancho de paso de píxeles es 1.0, simplemente elija 1.0 (sin embargo, 0.1 también habría funcionado)Ahora que tenemos el rayo con sus coordenadas de inicio y fin, el problema cambia de " es el punto dentro del polígono " a "con qué frecuencia el rayo interseca un lado del polígono ". Por lo tanto, no podemos simplemente trabajar con los puntos poligonales como antes, ahora necesitamos los lados reales. Un lado siempre se define por dos puntos.
Necesita probar el rayo contra todos los lados. Considere que el rayo es un vector y cada lado es un vector. El rayo tiene que golpear cada lado exactamente una vez o nunca. No puede golpear el mismo lado dos veces. Dos líneas en el espacio 2D siempre se cruzan exactamente una vez, a menos que sean paralelas, en cuyo caso nunca se cruzan. Sin embargo, dado que los vectores tienen una longitud limitada, es posible que dos vectores no sean paralelos y que nunca se crucen porque son demasiado cortos para encontrarse.
Hasta ahora todo bien, pero ¿cómo se prueba si dos vectores se cruzan? Aquí hay un código C (no probado), que debería hacer el truco:
Los valores de entrada son los dos puntos finales del vector 1 (
v1x1/v1y1
yv1x2/v1y2
) y el vector 2 (v2x1/v2y1
yv2x2/v2y2
). Entonces tienes 2 vectores, 4 puntos, 8 coordenadas.YES
yNO
son clarosYES
aumenta las intersecciones,NO
no hace nada.¿Qué hay de COLLINEAR? Significa que ambos vectores se encuentran en la misma línea infinita, dependiendo de la posición y la longitud, no se cruzan en absoluto o se cruzan en un número interminable de puntos. No estoy absolutamente seguro de cómo manejar este caso, no lo consideraría como una intersección de ninguna manera. Bueno, este caso es bastante raro en la práctica de todos modos debido a errores de redondeo de coma flotante; un código mejor probablemente no probaría
== 0.0f
sino algo como< epsilon
, donde epsilon es un número bastante pequeño.Si necesita probar una mayor cantidad de puntos, sin duda puede acelerar un poco todo manteniendo las formas estándar de ecuaciones lineales de los lados del polígono en la memoria, para que no tenga que volver a calcularlas cada vez. Esto le ahorrará dos multiplicaciones de coma flotante y tres restas de coma flotante en cada prueba a cambio de almacenar tres valores de coma flotante por lado de polígono en la memoria. Es un intercambio típico de memoria vs tiempo de cálculo.
Por último, pero no menos importante: si puede usar hardware 3D para resolver el problema, existe una alternativa interesante. Solo deja que la GPU haga todo el trabajo por ti. Cree una superficie de pintura que esté fuera de la pantalla. Rellenar completamente con el color negro. Ahora deja que OpenGL o Direct3D pinten tu polígono (o incluso todos tus polígonos si solo quieres probar si el punto está dentro de alguno de ellos, pero no te importa cuál) y rellena los polígonos con un polígono diferente. color, por ejemplo, blanco. Para verificar si un punto está dentro del polígono, obtenga el color de este punto de la superficie de dibujo. Esto es solo una recuperación de memoria O (1).
Por supuesto, este método solo se puede usar si la superficie de dibujo no tiene que ser enorme. Si no puede caber en la memoria de la GPU, este método es más lento que hacerlo en la CPU. Si tuviera que ser enorme y su GPU admite sombreadores modernos, aún puede usar la GPU implementando la proyección de rayos que se muestra arriba como un sombreador de GPU, lo cual es absolutamente posible. Para una mayor cantidad de polígonos o una gran cantidad de puntos para probar, esto valdrá la pena, tenga en cuenta que algunas GPU podrán probar 64 a 256 puntos en paralelo. Sin embargo, tenga en cuenta que transferir datos de la CPU a la GPU y viceversa siempre es costoso, por lo que solo para probar un par de puntos contra un par de polígonos simples, donde los puntos o los polígonos son dinámicos y cambiarán con frecuencia, un enfoque de GPU raramente pagará apagado.
fuente
Creo que el siguiente fragmento de código es la mejor solución (tomada de aquí ):
Argumentos
Es a la vez corto y eficiente y funciona tanto para polígonos convexos como cóncavos. Como se sugirió anteriormente, primero debe verificar el rectángulo delimitador y tratar los agujeros de polígono por separado.
La idea detrás de esto es bastante simple. El autor lo describe de la siguiente manera:
La variable c cambia de 0 a 1 y de 1 a 0 cada vez que el rayo horizontal cruza cualquier borde. Básicamente, se trata de realizar un seguimiento de si el número de bordes cruzados es par o impar. 0 significa par y 1 significa impar.
fuente
verty[i]
yverty[j]
están a ambos ladostesty
, por lo que nunca son iguales.Aquí hay una versión C # de la respuesta dada por nirg , que proviene de este profesor de RPI . Tenga en cuenta que el uso del código de esa fuente RPI requiere atribución.
Se ha agregado una casilla de verificación en la parte superior. Sin embargo, como señala James Brown, el código principal es casi tan rápido como la casilla del cuadro delimitador en sí mismo, por lo que la verificación del cuadro delimitador en realidad puede ralentizar la operación general, en el caso de que la mayoría de los puntos que está verificando estén dentro del cuadro delimitador . Por lo tanto, puede dejar el cuadro delimitador desprotegido, o una alternativa sería calcular previamente los cuadros delimitadores de sus polígonos si no cambian de forma con demasiada frecuencia.
fuente
Aquí hay una variante de JavaScript de la respuesta de M. Katz basada en el enfoque de Nirg:
fuente
Calcule la suma orientada de ángulos entre el punto p y cada uno de los vértices del polígono. Si el ángulo orientado total es de 360 grados, el punto está dentro. Si el total es 0, el punto está afuera.
Me gusta más este método porque es más robusto y menos dependiente de la precisión numérica.
Los métodos que calculan la uniformidad del número de intersecciones son limitados porque puede 'golpear' un vértice durante el cálculo del número de intersecciones.
EDITAR: Por cierto, este método funciona con polígonos cóncavos y convexos.
EDITAR: Recientemente encontré un artículo completo de Wikipedia sobre el tema.
fuente
Esta pregunta es muy interesante. Tengo otra idea viable diferente de otras respuestas a esta publicación. La idea es usar la suma de los ángulos para decidir si el objetivo está dentro o fuera. Mejor conocido como número de bobinado .
Deje x ser el punto objetivo. Deje que array [0, 1, .... n] sea todos los puntos del área. Conecte el punto objetivo con cada punto de borde con una línea. Si el punto objetivo está dentro de esta área. La suma de todos los ángulos será de 360 grados. Si no, los ángulos serán inferiores a 360.
Consulte esta imagen para obtener una comprensión básica de la idea:
Mi algoritmo asume que el sentido horario es la dirección positiva. Aquí hay una entrada potencial:
El siguiente es el código de Python que implementa la idea:
fuente
El artículo de Eric Haines citado por bobobobo es realmente excelente. Particularmente interesantes son las tablas que comparan el rendimiento de los algoritmos; El método de suma de ángulos es realmente malo en comparación con los demás. También es interesante que las optimizaciones como el uso de una cuadrícula de búsqueda para subdividir aún más el polígono en sectores de "entrada" y "salida" pueden hacer que la prueba sea increíblemente rápida incluso en polígonos con> 1000 lados.
De todos modos, son los primeros días, pero mi voto va al método de "cruces", que es más o menos lo que Mecki describe, creo. Sin embargo, lo encontré descrito y codificado más sucintamente por David Bourke . Me encanta que no se requiera trigonometría real, y funciona para convexos y cóncavos, y funciona razonablemente bien a medida que aumenta el número de lados.
Por cierto, aquí está una de las tablas de rendimiento del artículo de Eric Haines para interesar, probar en polígonos aleatorios.
fuente
Versión rápida de la respuesta de nirg :
fuente
Realmente me gusta la solución publicada por Nirg y editada por bobobobo. Acabo de hacerlo compatible con JavaScript y un poco más legible para mi uso:
fuente
Trabajé un poco en esto cuando era investigador de Michael Stonebraker , ya sabes, el profesor que ideó Ingres , PostgreSQL , etc.
Nos dimos cuenta de que la forma más rápida era primero hacer un cuadro delimitador porque es SUPER rápido. Si está fuera del cuadro delimitador, está afuera. De lo contrario, haces el trabajo más duro ...
Si desea un gran algoritmo, busque el código fuente del proyecto de código abierto PostgreSQL para el trabajo geográfico ...
Quiero señalar que nunca tuvimos una idea de la diestra frente a la zurda (también expresable como un problema "interno" frente a "externo" ...
ACTUALIZAR
El enlace de BKB proporcionó una buena cantidad de algoritmos razonables. Estaba trabajando en problemas de Ciencias de la Tierra y, por lo tanto, necesitaba una solución que funcione en latitud / longitud, y tiene el peculiar problema de la mano: ¿el área está dentro del área más pequeña o más grande? La respuesta es que la "dirección" de las verticies importa: es zurda o diestra y de esta manera puede indicar cualquier área como "dentro" de cualquier polígono dado. Como tal, mi trabajo utilizó la solución tres enumerada en esa página.
Además, mi trabajo utilizaba funciones separadas para las pruebas "en línea".
... Ya que alguien preguntó: descubrimos que las pruebas de cuadro delimitador eran mejores cuando el número de vértices superaba algún número; haga una prueba muy rápida antes de hacer la prueba más larga si es necesario ... Se crea un cuadro delimitador simplemente tomando el x más grande, x más pequeña, y más grande y y más pequeña y juntarlas para formar cuatro puntos de una caja ...
Otro consejo para los que siguen: hicimos toda nuestra informática más sofisticada y de "atenuación de la luz" en un espacio de cuadrícula, todo en puntos positivos en un avión y luego volvimos a proyectarlo en longitud / latitud "real", evitando así posibles errores de cuando se cruza una línea 180 de longitud y cuando se manejan regiones polares. Funcionó genial!
fuente
La respuesta de David Segond es más o menos la respuesta general estándar, y la de Richard T es la optimización más común, aunque hay algunas otras. Otras optimizaciones fuertes se basan en soluciones menos generales. Por ejemplo, si va a verificar el mismo polígono con muchos puntos, triangular el polígono puede acelerar las cosas enormemente, ya que hay una serie de algoritmos de búsqueda TIN muy rápidos. Otra es que si el polígono y los puntos están en un plano limitado a baja resolución, digamos una visualización en pantalla, puede pintar el polígono en un búfer de visualización mapeado en memoria en un color dado, y verificar el color de un píxel dado para ver si se encuentra en los polígonos
Al igual que muchas optimizaciones, estas se basan en casos específicos en lugar de generales, y generan beneficios basados en el tiempo amortizado en lugar de un solo uso.
Trabajando en este campo, encontré que la Geometría de Computación de Joeseph O'Rourkes en C 'ISBN 0-521-44034-3 es de gran ayuda.
fuente
La solución trivial sería dividir el polígono en triángulos y probar probar los triángulos como se explica aquí.
Sin embargo, si su polígono es CONVEXO , podría haber un mejor enfoque. Mira el polígono como una colección de líneas infinitas. Cada línea divide el espacio en dos. para cada punto es fácil decir si está en un lado o en el otro lado de la línea. Si un punto está en el mismo lado de todas las líneas, entonces está dentro del polígono.
fuente
Me doy cuenta de que esto es antiguo, pero aquí hay un algoritmo de proyección de rayos implementado en Cocoa, en caso de que alguien esté interesado. No estoy seguro de que sea la forma más eficiente de hacer las cosas, pero puede ayudar a alguien.
fuente
Versión Obj-C de la respuesta de nirg con un método de muestra para puntos de prueba. La respuesta de Nirg funcionó bien para mí.
fuente
CGPathContainsPoint()
es tu amigo.CGPathContainsPoint()
No hay nada más bello que una definición inductiva de un problema. En aras de la exhaustividad, aquí tiene una versión en prólogo que también podría aclarar los pensamientos detrás del lanzamiento de rayos :
Basado en la simulación del algoritmo de simplicidad en http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
Algunos predicados auxiliares:
La ecuación de una línea dada 2 puntos A y B (Línea (A, B)) es:
Es importante que la dirección de rotación de la línea se establezca en sentido horario para los límites y antihorario para los agujeros. Vamos a verificar si el punto (X, Y), es decir, el punto probado está en el semiplano izquierdo de nuestra línea (es cuestión de gustos, también podría ser el lado derecho, pero también la dirección de los límites las líneas deben cambiarse en ese caso), esto es para proyectar el rayo desde el punto a la derecha (o izquierda) y reconocer la intersección con la línea. Hemos optado por proyectar el rayo en dirección horizontal (de nuevo es una cuestión de gustos, también podría hacerse en vertical con restricciones similares), por lo que tenemos:
Ahora necesitamos saber si el punto está en el lado izquierdo (o derecho) del segmento de línea solamente, no en todo el plano, por lo que debemos restringir la búsqueda solo a este segmento, pero esto es fácil ya que estar dentro del segmento solo un punto en la línea puede ser más alto que Y en el eje vertical. Como esta es una restricción más fuerte, debe ser el primero en verificar, por lo que tomamos primero solo aquellas líneas que cumplen con este requisito y luego verificamos su posición. Según el teorema de la curva de Jordan, cualquier rayo proyectado en un polígono debe intersecarse en un número par de líneas. Así que hemos terminado, tiraremos el rayo hacia la derecha y luego, cada vez que cruce una línea, cambiará su estado. Sin embargo, en nuestra implementación, vamos a verificar la longitud de la bolsa de soluciones que cumplan con las restricciones dadas y decidir la incorporación al respecto. para cada línea en el polígono esto debe hacerse.
fuente
La versión C # de la respuesta de nirg está aquí: compartiré el código. Puede salvar a alguien algo de tiempo.
fuente
Versión Java:
fuente
Puerto neto:
fuente
VBA VERSIÓN:
Nota: Recuerde que si su polígono es un área dentro de un mapa, Latitud / Longitud son valores Y / X en lugar de X / Y (Latitud = Y, Longitud = X) debido a lo que entiendo son implicaciones históricas desde hace mucho tiempo. La longitud no era una medida.
MÓDULO DE CLASE: CPoint
MÓDULO:
fuente
He hecho una implementación de Python de la nirg C ++ código :
Entradas
bounding_box_positions: puntos candidatos para filtrar. (En mi implementación creada desde el cuadro delimitador.
(Las entradas son listas de tuplas en el formato:
[(xcord, ycord), ...]
)Devoluciones
Nuevamente, la idea se toma de aquí.
fuente
Sorprendido, nadie mencionó esto antes, pero para los pragmáticos que requieren una base de datos: MongoDB tiene un excelente soporte para consultas Geo, incluida esta.
Lo que estás buscando es:
Neighborhoods
es la colección que almacena uno o más polígonos en formato estándar GeoJson. Si la consulta devuelve un valor nulo, no se intersecta, de lo contrario lo es.Muy bien documentado aquí: https://docs.mongodb.com/manual/tutorial/geospatial-tutorial/
El rendimiento de más de 6,000 puntos clasificados en una cuadrícula de 330 polígonos irregulares fue de menos de un minuto sin optimización alguna e incluido el tiempo para actualizar documentos con sus respectivos polígonos.
fuente
Aquí hay un punto en la prueba de polígono en C que no está utilizando la proyección de rayos. Y puede funcionar para áreas superpuestas (auto intersecciones), vea el
use_holes
argumento.Nota: este es uno de los métodos menos óptimos, ya que incluye muchas llamadas a
atan2f
, pero puede ser de interés para los desarrolladores que leen este hilo (en mis pruebas es ~ 23 veces más lento que el método de intersección de línea).fuente
Para detectar el impacto en el polígono, debemos probar dos cosas:
fuente
Para tratar los siguientes casos especiales en el algoritmo de fundición de Ray :
Verifique Determinar si un punto está dentro de un polígono complejo . El artículo proporciona una manera fácil de resolverlos para que no se requiera un tratamiento especial para los casos anteriores.
fuente
Puede hacerlo comprobando si el área formada al conectar el punto deseado a los vértices de su polígono coincide con el área del polígono mismo.
O podría verificar si la suma de los ángulos internos desde su punto a cada par de dos vértices poligonales consecutivos a su punto de verificación suma 360, pero tengo la sensación de que la primera opción es más rápida porque no implica divisiones ni cálculos de inverso de funciones trigonométricas.
No sé qué sucede si su polígono tiene un agujero en su interior, pero me parece que la idea principal puede adaptarse a esta situación.
También puede publicar la pregunta en una comunidad matemática. Apuesto a que tienen un millón de formas de hacerlo
fuente
Si está buscando una biblioteca java-script, hay una extensión javascript google maps v3 para la clase Polygon para detectar si un punto reside o no dentro de ella.
Google Extention Github
fuente
Cuando usas qt(Qt 4.3+), uno puede usar la función QPolygon contienePoint
fuente
La respuesta depende de si tiene los polígonos simples o complejos. Los polígonos simples no deben tener ninguna intersección de segmento de línea. Entonces pueden tener los agujeros pero las líneas no pueden cruzarse entre sí. Las regiones complejas pueden tener las intersecciones de línea, por lo que pueden tener las regiones superpuestas, o regiones que se tocan entre sí con un solo punto.
Para polígonos simples, el mejor algoritmo es el algoritmo de fundición de rayos (número de cruce). Para polígonos complejos, este algoritmo no detecta puntos que están dentro de las regiones superpuestas. Entonces, para los polígonos complejos, debe usar el algoritmo de número de Winding.
Aquí hay un excelente artículo con implementación en C de ambos algoritmos. Los probé y funcionan bien.
http://geomalgorithms.com/a03-_inclusion.html
fuente
Versión Scala de la solución por nirg (se supone que la verificación previa del rectángulo delimitador se realiza por separado):
fuente
Aquí está la versión golang de @nirg answer (inspirada en el código C # por @@ m-katz)
fuente