Estou tentando criar um ponto 2D rápido dentro do algoritmo de polígono, para uso em testes de impacto (por exemplo Polygon.contains(p:Point)
). Sugestões para técnicas eficazes seriam apreciadas.
performance
graphics
collision-detection
polygon
point-in-polygon
Scott Evernden
fonte
fonte
Respostas:
Para gráficos, prefiro não preferir números inteiros. Muitos sistemas usam números inteiros para a pintura da interface do usuário (afinal, os pixels são ints), mas o macOS, por exemplo, usa float para tudo. O macOS conhece apenas pontos e um ponto pode ser traduzido para um pixel, mas, dependendo da resolução do monitor, pode ser traduzido para outra coisa. Nas telas de retina, meio ponto (0,5 / 0,5) é pixel. Ainda assim, nunca notei que as interfaces do macOS são significativamente mais lentas que as outras. Afinal, todas as APIs 3D (OpenGL ou Direct3D) também funcionam com carros alegóricos e as bibliotecas gráficas modernas costumam tirar proveito da aceleração da GPU.
Agora você disse que a velocidade é a sua principal preocupação, ok, vamos em velocidade. Antes de executar qualquer algoritmo sofisticado, primeiro faça um teste simples. Crie uma caixa delimitadora alinhada ao eixo em torno do seu polígono. Isso é muito fácil, rápido e já pode proteger muitos cálculos. Como isso funciona? Itere todos os pontos do polígono e encontre os valores mínimo / máximo de X e Y.
Por exemplo, você tem os pontos
(9/1), (4/3), (2/7), (8/2), (3/6)
. Isso significa que Xmin é 2, Xmax é 9, Ymin é 1 e Ymax é 7. Um ponto fora do retângulo com as duas arestas (2/1) e (9/7) não pode estar dentro do polígono.Este é o primeiro teste a ser executado em qualquer ponto. Como você pode ver, este teste é ultra rápido, mas também é muito grosseiro. Para lidar com pontos que estão dentro do retângulo delimitador, precisamos de um algoritmo mais sofisticado. Existem algumas maneiras de como isso pode ser calculado. O método que funciona também depende do fato de o polígono poder ter furos ou sempre ser sólido. Aqui estão exemplos de sólidos (um convexo, um côncavo):
E aqui está um com um buraco:
O verde tem um buraco no meio!
O algoritmo mais fácil, que pode lidar com todos os três casos acima e ainda é bastante rápido, é chamado de conversão de raios . A idéia do algoritmo é bastante simples: desenhe um raio virtual de qualquer lugar fora do polígono até o seu ponto e conte quantas vezes ele atinge um lado do polígono. Se o número de acertos for par, está fora do polígono, se for ímpar, está dentro.
O algoritmo do número do enrolamento seria uma alternativa, é mais preciso para os pontos estarem muito próximos de uma linha poligonal, mas também é muito mais lento. A conversão de raios pode falhar em pontos muito próximos ao lado de um polígono devido a problemas limitados de precisão e arredondamento de pontos flutuantes, mas, na realidade, isso dificilmente é um problema, como se um ponto estivesse tão próximo de um lado, geralmente não é visualmente possível para um visualizador para reconhecer se ele já está dentro ou fora.
Você ainda tem a caixa delimitadora acima, lembra? Basta escolher um ponto fora da caixa delimitadora e usá-lo como ponto de partida para o seu raio. Por exemplo, o ponto
(Xmin - e/p.y)
está fora do polígono, com certeza.Mas o que é isso
e
? Bem,e
(na verdade epsilon), dá à caixa delimitadora um pouco de preenchimento . Como eu disse, o traçado de raios falha se começarmos muito perto de uma linha poligonal. Como a caixa delimitadora pode ser igual ao polígono (se o polígono é um retângulo alinhado ao eixo, a caixa delimitadora é igual ao próprio polígono!), Precisamos de algum preenchimento para tornar isso seguro, é tudo. Quão grande você deve escolhere
? Não muito grande. Depende da escala do sistema de coordenadas que você usa para desenhar. Se a largura da etapa do pixel for 1.0, basta escolher 1,0 (ainda assim, 0,1 também funcionaria)Agora que temos o raio com suas coordenadas de início e fim, o problema muda de " é o ponto dentro do polígono " para "com que frequência o raio cruza um lado do polígono ". Portanto, não podemos apenas trabalhar com os pontos poligonais como antes, agora precisamos dos lados reais. Um lado é sempre definido por dois pontos.
Você precisa testar o raio contra todos os lados. Considere o raio como um vetor e cada lado como um vetor. O raio tem que atingir cada lado exatamente uma vez ou nunca. Não pode atingir o mesmo lado duas vezes. Duas linhas no espaço 2D sempre se cruzam exatamente uma vez, a menos que sejam paralelas; nesse caso, nunca se cruzam. No entanto, como os vetores têm um comprimento limitado, dois vetores podem não ser paralelos e ainda nunca se cruzarem, porque são muito curtos para se encontrarem.
Até aqui tudo bem, mas como você testa se dois vetores se cruzam? Aqui está um código C (não testado), que deve fazer o truque:
Os valores de entrada são os dois pontos finais do vetor 1 (
v1x1/v1y1
ev1x2/v1y2
) e do vetor 2 (v2x1/v2y1
ev2x2/v2y2
). Então você tem 2 vetores, 4 pontos, 8 coordenadas.YES
eNO
são claros.YES
aumenta interseções,NO
não faz nada.E o COLLINEAR? Isso significa que ambos os vetores estão na mesma linha infinita, dependendo da posição e do comprimento, não se cruzam nem se interceptam em um número infinito de pontos. Não tenho certeza absoluta de como lidar com este caso, não o contaria como interseção de qualquer maneira. Bem, este caso é bastante raro na prática, devido a erros de arredondamento de ponto flutuante; código melhor provavelmente não testaria,
== 0.0f
mas sim algo como< epsilon
, onde epsilon é um número bastante pequeno.Se você precisar testar um número maior de pontos, certamente poderá acelerar um pouco a coisa toda, mantendo na memória as formas padrão das equações lineares dos lados dos polígonos, para que você não precise recalculá-las todas as vezes. Isso economizará duas multiplicações de ponto flutuante e três subtrações de ponto flutuante em cada teste, em troca de armazenar três valores de ponto flutuante por lado do polígono na memória. É uma troca típica de memória versus tempo de computação.
Por último, mas não menos importante: se você pode usar o hardware 3D para resolver o problema, existe uma alternativa interessante. Apenas deixe a GPU fazer todo o trabalho para você. Crie uma superfície de pintura que esteja fora da tela. Encha-o completamente com a cor preta. Agora deixe o OpenGL ou o Direct3D pintar seu polígono (ou mesmo todos os seus polígonos, se você quiser apenas testar se o ponto está em algum deles, mas você não se importa com qual deles) e preencher o (s) polígono (s) com um diferente cor, por exemplo, branco. Para verificar se um ponto está dentro do polígono, obtenha a cor desse ponto na superfície de desenho. Esta é apenas uma busca de memória O (1).
É claro que esse método só pode ser usado se a superfície do desenho não precisar ser grande. Se não puder caber na memória da GPU, esse método é mais lento do que na CPU. Se precisar ser enorme e sua GPU suportar shaders modernos, você ainda poderá usá-la implementando a transmissão de raios mostrada acima como shader de GPU, o que é absolutamente possível. Para um número maior de polígonos ou um grande número de pontos a serem testados, isso será recompensado, considere que algumas GPUs poderão testar 64 a 256 pontos em paralelo. Observe, no entanto, que transferir dados da CPU para a GPU e vice-versa é sempre caro, portanto, apenas para testar alguns pontos em alguns polígonos simples, em que os pontos ou os polígonos são dinâmicos e mudam com frequência, uma abordagem da GPU raramente paga fora.
fonte
Eu acho que o seguinte trecho de código é a melhor solução (retirada daqui ):
Argumentos
É curto e eficiente e funciona tanto para polígonos convexos quanto côncavos. Como sugerido anteriormente, você deve verificar primeiro o retângulo delimitador e tratar os orifícios dos polígonos separadamente.
A ideia por trás disso é bastante simples. O autor descreve da seguinte maneira:
A variável c está alternando de 0 para 1 e 1 para 0 cada vez que o raio horizontal cruza qualquer aresta. Então, basicamente, é acompanhar se o número de arestas cruzadas é par ou ímpar. 0 significa par e 1 significa ímpar.
fonte
verty[i]
everty[j]
os dois ladostesty
, para que nunca sejam iguais.Aqui está uma versão em C # da resposta dada por nirg , que vem deste professor de RPI . Observe que o uso do código dessa fonte RPI requer atribuição.
Uma seleção de caixa delimitadora foi adicionada na parte superior. No entanto, como James Brown aponta, o código principal é quase tão rápido quanto a caixa delimitadora, então a verificação da caixa delimitadora pode realmente retardar a operação geral, no caso de a maioria dos pontos que você está verificando estarem dentro da caixa delimitadora . Portanto, você pode deixar a caixa delimitadora ou uma alternativa seria pré-calcular as caixas delimitadoras de seus polígonos se elas não mudarem de forma com muita frequência.
fonte
Aqui está uma variante JavaScript da resposta de M. Katz com base na abordagem de Nirg:
fonte
Calcule a soma orientada dos ângulos entre o ponto pe cada um dos ápices poligonais. Se o ângulo total orientado for 360 graus, o ponto estará dentro. Se o total for 0, o ponto está fora.
Eu gosto mais desse método porque é mais robusto e menos dependente da precisão numérica.
Os métodos que calculam a uniformidade do número de interseções são limitados porque você pode "atingir" um ápice durante o cálculo do número de interseções.
EDIT: By the Way, este método funciona com polígonos côncavos e convexos.
EDIT: Recentemente, encontrei um artigo completo da Wikipedia sobre o assunto.
fonte
Esta questão é tão interessante. Tenho outra ideia viável diferente de outras respostas a este post. A idéia é usar a soma dos ângulos para decidir se o alvo está dentro ou fora. Mais conhecido como número do enrolamento .
Seja x o ponto alvo. Seja o array [0, 1, .... n] todos os pontos da área. Conecte o ponto de destino a cada ponto de borda com uma linha. Se o ponto de destino estiver dentro desta área. A soma de todos os ângulos será 360 graus. Caso contrário, os ângulos serão inferiores a 360.
Consulte esta imagem para obter um entendimento básico da ideia:
Meu algoritmo assume que o sentido horário é a direção positiva. Aqui está uma entrada em potencial:
A seguir está o código python que implementa a ideia:
fonte
O artigo de Eric Haines citado por bobobobo é realmente excelente. Particularmente interessantes são as tabelas que comparam o desempenho dos algoritmos; o método da soma dos ângulos é realmente ruim comparado aos outros. Também interessante é que otimizações como o uso de uma grade de pesquisa para subdividir ainda mais o polígono em setores "dentro" e "fora" podem tornar o teste incrivelmente rápido, mesmo em polígonos com> 1000 lados.
Enfim, é cedo, mas meu voto vai para o método "travessias", que é basicamente o que Mecki descreve, eu acho. No entanto, achei mais sucintamente descrito e codificado por David Bourke . Eu amo que não há trigonometria real necessária, e funciona para convexo e côncavo, e funciona razoavelmente bem à medida que o número de lados aumenta.
A propósito, aqui está uma das tabelas de desempenho do artigo de Eric Haines, que interessa, testando em polígonos aleatórios.
fonte
Versão rápida da resposta por nirg :
fonte
Realmente gosto da solução publicada por Nirg e editada por bobobobo. Acabei de torná-lo compatível com javascript e um pouco mais legível para o meu uso:
fonte
Eu trabalhei nisso quando era pesquisador do Michael Stonebraker - você sabe, o professor que criou Ingres , PostgreSQL , etc.
Percebemos que a maneira mais rápida era primeiro fazer uma caixa delimitadora porque é SUPER rápido. Se estiver fora da caixa delimitadora, está fora. Caso contrário, você faz o trabalho mais difícil ...
Se você deseja um ótimo algoritmo, consulte o código-fonte PostgreSQL do projeto de código aberto para o trabalho geográfico ...
Quero ressaltar que nunca tivemos nenhuma visão sobre destros versus canhotos (também expressável como um problema "interno" vs "externo" ...
ATUALIZAR
O link do BKB forneceu um bom número de algoritmos razoáveis. Eu estava trabalhando nos problemas das ciências da terra e, portanto, precisava de uma solução que funcionasse em latitude / longitude, e ela tem o problema peculiar de destreza - é a área dentro da área menor ou maior? A resposta é que a "direção" dos vértices é importante - é canhoto ou destro e, dessa maneira, você pode indicar uma das áreas como "dentro" de qualquer polígono. Como tal, meu trabalho usou a solução três enumerada nessa página.
Além disso, meu trabalho usou funções separadas para testes "na linha".
... Como alguém perguntou: descobrimos que os testes de caixa delimitadora eram melhores quando o número de vértices ultrapassava algum número - faça um teste muito rápido antes de fazer o teste mais longo, se necessário ... Uma caixa delimitadora é criada simplesmente fazendo o x maior, menor x, maior y e menor y e juntando-os para formar quatro pontos de uma caixa ...
Outra dica para aqueles que se seguem: fizemos toda a nossa computação mais sofisticada e com "escurecimento da luz" em um espaço de grade, todos em pontos positivos em um plano e, em seguida, re-projetamos de volta em longitude / latitude "real", evitando possíveis erros de envolvendo quando cruzamos a linha 180 de longitude e ao lidar com regiões polares. Trabalhou muito bem!
fonte
A resposta de David Segond é praticamente a resposta geral padrão, e a de Richard T é a otimização mais comum, embora existam outras. Outras otimizações fortes são baseadas em soluções menos gerais. Por exemplo, se você estiver indo para verificar o mesmo polígono com muitos pontos, a triangulação do polígono pode acelerar bastante as coisas, pois existem vários algoritmos de pesquisa TIN muito rápidos. Outra é que, se o polígono e os pontos estiverem em um plano limitado em baixa resolução, digamos uma tela, você poderá pintar o polígono em um buffer de tela mapeado na memória em uma determinada cor e verificar a cor de um determinado pixel para ver se está nos polígonos.
Como muitas otimizações, elas são baseadas em casos específicos, e não gerais, e geram benefícios com base no tempo amortizado, e não no uso único.
Trabalhando neste campo, achei a Geometria de computação de Joeseph O'Rourkes no C 'ISBN 0-521-44034-3 uma grande ajuda.
fonte
A solução trivial seria dividir o polígono em triângulos e testar os triângulos como explicado aqui
Se o seu polígono for CONVEX , pode haver uma abordagem melhor. Veja o polígono como uma coleção de linhas infinitas. Cada linha dividindo o espaço em dois. para cada ponto, é fácil dizer se está de um lado ou do outro lado da linha. Se um ponto estiver no mesmo lado de todas as linhas, ele estará dentro do polígono.
fonte
Sei que isso é antigo, mas aqui está um algoritmo de conversão de raios implementado no cacau, caso alguém esteja interessado. Não tenho certeza se é a maneira mais eficiente de fazer as coisas, mas pode ajudar alguém.
fonte
Versão Obj-C da resposta de nirg com método de amostra para pontos de teste. A resposta de Nirg funcionou bem para mim.
fonte
CGPathContainsPoint()
é seu amigo.CGPathContainsPoint()
Não há nada mais bonito do que uma definição indutiva de um problema. Por uma questão de completude, aqui você tem uma versão em prólogo que também pode esclarecer os pensamentos por trás da fundição de raios :
Com base na simulação do algoritmo de simplicidade em http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
Alguns predicados auxiliares:
A equação de uma reta dada 2 pontos A e B (Linha (A, B)) é:
É importante que a direção de rotação da linha seja ajustada no sentido horário para limites e anti-horário para orifícios. Vamos verificar se o ponto (X, Y), ou seja, o ponto testado está no semiplano esquerdo da nossa linha (é uma questão de gosto, também pode ser o lado direito, mas também a direção dos limites Nesse caso, as linhas devem ser alteradas), para projetar o raio do ponto para a direita (ou esquerda) e reconhecer a interseção com a linha. Optamos por projetar o raio na direção horizontal (novamente, é uma questão de gosto, também pode ser feito na vertical, com restrições semelhantes), por isso temos:
Agora precisamos saber se o ponto está no lado esquerdo (ou direito) apenas do segmento de linha, não o plano inteiro, portanto, precisamos restringir a pesquisa apenas a esse segmento, mas isso é fácil, pois está dentro do segmento apenas um ponto na linha pode ser maior que Y no eixo vertical. Como essa é uma restrição mais forte, ela precisa ser a primeira a verificar, portanto, pegamos primeiro apenas as linhas que atendem a esse requisito e, em seguida, verificamos sua possibilidade. Pelo teorema da Curva de Jordan, qualquer raio projetado em um polígono deve se cruzar em um número par de linhas. Assim que terminamos, lançaremos o raio para a direita e sempre que ele cruzar uma linha, alternará seu estado. No entanto, em nossa implementação, vamos verificar o comprimento do pacote de soluções que atendem às restrições especificadas e decidir a adesão a ele. para cada linha no polígono, isso deve ser feito.
fonte
A versão em C # da resposta de nirg está aqui: apenas compartilharei o código. Isso pode economizar tempo para alguém.
fonte
Versão Java:
fonte
Porta .Net:
fonte
VERSÃO VBA:
Nota: Lembre-se de que, se o seu polígono for uma área dentro de um mapa, Latitude / Longitude são valores Y / X, em oposição a X / Y (Latitude = Y, Longitude = X), devido ao que eu entendo, há implicações históricas desde quando Longitude não era uma medida.
MÓDULO DE CLASSE: CPoint
MÓDULO:
fonte
Eu fiz uma implementação Python de de nirg c ++ código :
Entradas
bounding_box_positions: pontos candidatos a serem filtrados. (Na minha implementação criada a partir da caixa delimitadora.
(As entradas são listas de tuplas no formato:
[(xcord, ycord), ...]
)Devoluções
Novamente, a idéia é tirada daqui
fonte
Surpreso, ninguém falou disso antes, mas para os pragmáticos que precisam de um banco de dados: o MongoDB tem excelente suporte para consultas geográficas, incluindo esta.
O que você está procurando é:
Neighborhoods
é a coleção que armazena um ou mais polígonos no formato GeoJson padrão. Se a consulta retornar nula, ela não será interceptada, caso contrário, será.Muito bem documentado aqui: https://docs.mongodb.com/manual/tutorial/geospatial-tutorial/
O desempenho de mais de 6.000 pontos classificados em uma grade irregular de 330 polígonos foi inferior a um minuto sem nenhuma otimização e incluindo o tempo para atualizar documentos com o respectivo polígono.
fonte
Aqui está um ponto no teste de polígono em C que não está usando a projeção de raios. E pode funcionar para áreas sobrepostas (auto-interseções), veja o
use_holes
argumento.Nota: esse é um dos métodos menos ótimos, pois inclui muitas chamadas para
atan2f
, mas pode ser interessante para os desenvolvedores que estão lendo este segmento (nos meus testes é ~ 23x mais lento que o método de interseção de linhas).fonte
Para detectar hit no Polygon, precisamos testar duas coisas:
fonte
Para lidar com os seguintes casos especiais no algoritmo de conversão de raios :
Marque Determinando se um ponto está dentro de um polígono complexo . O artigo fornece uma maneira fácil de resolvê-los, para que não haja tratamento especial necessário para os casos acima.
fonte
Você pode fazer isso verificando se a área formada conectando o ponto desejado aos vértices do polígono corresponde à área do próprio polígono.
Ou você pode verificar se a soma dos ângulos internos do seu ponto para cada par de dois vértices poligonais consecutivos até o seu ponto de verificação é de 360, mas sinto que a primeira opção é mais rápida porque não envolve divisões nem cálculos inverso de funções trigonométricas.
Não sei o que acontece se o seu polígono tiver um furo dentro dele, mas me parece que a idéia principal pode ser adaptada a essa situação
Você também pode postar a pergunta em uma comunidade de matemática. Aposto que eles têm um milhão de maneiras de fazer isso
fonte
Se você está procurando uma biblioteca de scripts java, há uma extensão javascript do google maps v3 para a classe Polygon para detectar se um ponto reside ou não nela.
Github de extensão do Google
fonte
Ao usar qt(Qt 4.3+), pode-se usar a função QPolygon containsPoint
fonte
A resposta depende se você possui polígonos simples ou complexos. Polígonos simples não devem ter nenhuma interseção de segmento de linha. Eles podem ter os buracos, mas as linhas não podem se cruzar. Regiões complexas podem ter interseções de linha - para que possam ter regiões sobrepostas ou regiões que se tocam apenas por um único ponto.
Para polígonos simples, o melhor algoritmo é o algoritmo de vazamento de raios (número de cruzamento). Para polígonos complexos, esse algoritmo não detecta pontos que estão dentro das regiões sobrepostas. Portanto, para polígonos complexos, é necessário usar o algoritmo de número do enrolamento.
Aqui está um excelente artigo com a implementação em C de ambos os algoritmos. Eu tentei e eles funcionam bem.
http://geomalgorithms.com/a03-_inclusion.html
fonte
Versão Scala da solução por nirg (assume que a pré-verificação do retângulo delimitador é feita separadamente):
fonte
Aqui está a versão golang da resposta @nirg (inspirada no código C # de @@ m-katz)
fonte