Algoritmo para encontrar centróide de polígono irregular (ponto da etiqueta)


13

Preciso encontrar um centróide (ou ponto de etiqueta) para polígonos de formato irregular no Google Maps. Estou mostrando o InfoWindows para encomendas e preciso de um local para ancorar o InfoWindow que é garantido que está na superfície. Veja as imagens abaixo.

texto alternativo texto alternativo

Na realidade, não preciso de nada específico do Google Maps, apenas procurando uma idéia de como encontrar esse ponto automaticamente.

Minha primeira idéia foi encontrar o centróide "falso" pegando a média de lat e lngs e a colocação aleatória de pontos de lá até encontrar um que cruze o polígono. Eu já tenho o código point-in-polygon. Isso me parece terrivelmente "hacky".

Devo observar que não tenho acesso a nenhum código do lado do servidor que gera a geometria, portanto não posso fazer nada como ST_PointOnSurface (the_geom).

Respostas:


6

Rápido e sujo: se o centróide "falso" não estiver no polígono, use o vértice mais próximo a esse ponto.


Eu não tinha pensado nisso. Idealmente, eu gostaria desse ponto no polígono e não no limite, mas talvez seja nisso que eu recorro.
Jason

Depois de encontrar um ponto de aresta, você pode cruzar um pequeno quadrado centrado nesse ponto com o polígono e escolher o centróide da interseção. Quando o quadrado é pequeno o suficiente, isso é garantido como um ponto interior (embora, é claro, esteja muito perto de uma aresta).
whuber

@ Jason Se você usar o centróide real, é menos provável que encontre esse problema. Não deve ser muito difícil traduzir algo rapidamente para JavaScript: github.com/cartesiananalytics/Pipra/blob/master/src/…
Dandy

Embora minha solução (raios do falso centróide) funcione a maior parte do tempo, acho que essa solução provavelmente funcionaria melhor por causa de sua simplicidade e pelo fato de você estar garantido em encontrar um ponto pelo menos no limite e poder mudar facilmente estar dentro do polígono com muito pouco esforço.
Jason

3

Você pode olhar para isso: http://github.com/tparkin/Google-Maps-Point-in-Polygon

Parece usar um algoritmo de Ray Casting que deve corresponder ao caso que você apresentou.

Há um post sobre isso aqui. http://appdelegateinc.com/blog/2010/05/16/point-in-polygon-checking/


Se você deseja implementar isso no lado do servidor, JTS (Java) e Geos (C) implementam essa funcionalidade.
DavidF 23/09/10

Sim, eu provavelmente deveria ter acrescentado que já tenho o código para determinar se meu centróide "calculado" está dentro do polígono ou não. O que eu realmente quero é uma maneira de criar um centróide que esteja dentro do polígono.
Jason

3

Um algoritmo ESRI (mais antigo) calcula o centro de massa e, após testá-lo para inclusão no polígono, move-o horizontalmente, se necessário, até que fique dentro do polígono. (Isso pode ser feito de várias maneiras, dependendo de quais operações fundamentais estão disponíveis no seu ambiente de programação.) Isso tende a produzir pontos de etiqueta bem perto do centro visual do polígono: experimente na ilustração.


1

Eu resolvi meu problema estendendo o código epoly popular de http://econym.org.uk/gmap . Basicamente, o que acabei fazendo foi:

  • Crie uma série de raios que começam do "falso centróide" e se estendem a todos os cantos e laterais (total de 8)
  • Crie incrementalmente um ponto 10,20,30 ... por cento abaixo de cada raio e veja se esse ponto está em nosso polígono original

Código epoly extendido abaixo:

google.maps.Polygon.prototype.Centroid = function() {
var p = this;
var b = this.Bounds();
var c = new google.maps.LatLng((b.getSouthWest().lat()+b.getNorthEast().lat())/2,(b.getSouthWest().lng()+b.getNorthEast().lng())/2);
if (!p.Contains(c)){
    var fc = c; //False Centroid
    var percentages = [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9]; //We'll check every 10% down each ray and see if we're inside our polygon
    var rays = [
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(b.getNorthEast().lat(),fc.lng())]}),
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(fc.lat(),b.getNorthEast().lng())]}),
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(b.getSouthWest().lat(),fc.lng())]}),
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(fc.lat(),b.getSouthWest().lng())]}),
        new google.maps.Polyline({path:[fc,b.getNorthEast()]}),
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(b.getSouthWest().lat(),b.getNorthEast().lng())]}),
        new google.maps.Polyline({path:[fc,b.getSouthWest()]}),
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(b.getNorthEast().lat(),b.getSouthWest().lng())]})
    ];
    var lp;
    for (var i=0;i<percentages.length;i++){
        var percent = percentages[i];
        for (var j=0;j<rays.length;j++){
            var ray = rays[j];
            var tp = ray.GetPointAtDistance(percent*ray.Distance()); //Test Point i% down the ray
            if (p.Contains(tp)){
                lp = tp; //It worked, store it
                break;
            }
        }
        if (lp){
            c = lp;
            break;
        }
    }
}
return c;}

Ainda um pouco hacky, mas parece funcionar.


Este método falhará em alguns polígonos tortuosos. Por exemplo, armazene em buffer a polilinha {{0, 9}, {10, 20}, {9, 9}, {20, 10}, {9, 0}, {20, -10}, {9, -9} , {10, -20}, {0, -9}, {-10, -20}, {-9, -9}, {-20, -10}, {-9, 0}, {-20, 10}, {-9, 9}, {-10, 20}, {0,9}} em menos de 1/2. Também é ineficiente em comparação com o método QAD de Dandy, por exemplo.
whuber

1

Outro algoritmo "sujo" para fazer isso:

  • Pegue a caixa delimitadora da geometria (Xmax, Ymax, Xmin, Ymin)

  • Faça um loop até encontrar um ponto aleatório ( Xmin+rand*(Xmax-Xmin), Ymin+rand*(Ymax-Ymin) ) na geometria (usando Google-Maps-Point-in-Polygon )


+1 porque isso pode ter uma chance razoável de acerto na segunda vez. Desde que o seu "aleatório" seja reproduzível a cada vez para não incomodar o usuário, esta também é uma solução válida. As chances de ele não atingir um ponto válido em breve são pequenas, especialmente se você começar com um bom ponto de adivinhação.
Dandy

1
@ Dandy: Na verdade, em alguns casos, este pode ser um algoritmo muito ruim. Considere uma tira diagonal estreita, por exemplo. Eles existem na prática (por exemplo, longas parcelas de fachada) e podem facilmente ocupar menos de 0,1% da caixa delimitadora (às vezes muito menos). Para ter certeza razoável (95% de confiança) de atingir esse polígono com esta técnica, seriam necessárias cerca de 3.000 iterações.
whuber

@ Whuber: Se você escolher um local inicial ruim, isso pode levar um tempo para ser concluído. Se você também considerar que hipoteticamente 95% dos cliques estarão em geometrias mais desejáveis, isso pode ser um problema apenas 5% das vezes. Também como em outra pergunta do GIS.se, se o desempenho é o objetivo, nunca existe uma única solução, é melhor alterar as táticas com base nas heurísticas. Não há razão para executar isso por 3000 iterações. Você sempre pode resgatar meu QAD depois das 10. Acho que vale a pena tentar este por algumas iterações, pois o local pode ser mais desejável.
Dandy

@Dandy: Mas qual é o problema com sua solução QAD? Você pode até modificá-lo um pouco, movendo-se do ponto de rótulo da avaliação original para o vértice mais próximo em algum buffer interno do polígono: ainda QAD, mas agora com garantia de aterrissar em um local interno do recurso original. BTW, sua estratégia de resgate em breve é ​​boa. Sempre que codifico uma sonda aleatória como essa, pré-calculo a proporção da área do recurso para a da caixa delimitadora, uso-a para encontrar o tempo esperado para o sucesso e aviso imediatamente ao usuário se ele pode ser longo.
whuber

@Whuber, a heurística da proporção da área é uma ótima idéia, porque você calcula o centróide quando calcula a área. Quanto ao problema com a minha solução QAD: está no limite. Se eu escolher esse ponto e armazená-lo como você diz, esse raio "pequeno" pode ser maior que o comprimento naquela seção estreita. Sempre há uma caixa de canto. Há muito a considerar, apenas para fazer um balão que atrapalhe a interface do usuário e oculte a geometria de qualquer maneira. Provavelmente é melhor escolher o vértice mais alto ou mais baixo.
Dandy

1

À luz do seu esclarecimento recente de que você preferiria uma localização estritamente interior, você pode selecionar qualquer ponto na Transformação do Eixo Medial que também não esteja no limite do polígono. (Se você não possui código para um MAT, pode aproximá-lo armazenando em buffer negativo o polígono. Uma pesquisa binária ou secante produzirá rapidamente um pequeno polígono interno que se aproxima de parte do MAT; use qualquer ponto em seu limite.)


Entendo o que você estava dizendo sobre o uso da aresta de uma geometria, de modo que ela esteja dentro do interior do polígono de interesse. Não entendo como você criaria essa aresta / vértice. A única coisa em que consigo pensar é criar um triângulo virtual cruzando um raio perpendicular do ponto de interesse ao segmento oposto ao segmento do ponto selecionado. O ponto médio entre esses dois pontos pode ser o topo desse triângulo virtual.
Dandy

@ Dandy: Isso chega ao coração disso. Há muitas maneiras de fazer isso, dependendo do que o seu GIS faz nativamente. Por exemplo, depois de encontrar um raio que cruza o recurso original em um conjunto de comprimento positivo, essa interseção será uma união disjunta de segmentos de linha. Use o centro de qualquer um desses segmentos. Outra maneira é começar com qualquer ponto do recurso (de preferência próximo ao meio, que é o que o método QED realizou), criar um pequeno polígono simples (por exemplo, quadrado) centralizado ali, interceptá-lo com o recurso original, escolher o único dispositivo conectado component ...
whuber

(continuação) ... contendo o ponto de partida e escolha recursivamente um centro para esse componente. Haverá muitos métodos disponíveis quando o seu GIS permitir que você faça um loop pelas seqüências de vértices que descrevem os limites do recurso. Se buffers negativos forem suportados, é possível encontrar iterativamente um conjunto de pontos interiores de distância máxima (o "esqueleto", que é um subconjunto do MAT). Isso é um pouco caro, mas é bastante fácil de programar e produz excelentes pontos de etiqueta.
whuber

0

Por que não usar o centróide apenas na posição vertical (latitude)? Em seguida, você pode posicionar o rótulo horizontalmente escolhendo a longitude média nessa latitude . (Para isso, você precisa encontrar o valor da longitude de uma aresta de polígono em uma latitude específica, o que não deve causar problemas).

Além disso, tenha cuidado com as formas em U e as mais complexas. :) Possivelmente, escolha a média do par de longitudes mais à direita (cada par corresponderia a uma fatia do polígono), pois a janela de informações está orientada dessa maneira?

Isso também oferece um pouco mais de controle sobre o posicionamento; por exemplo, pode ser bom posicionar a janela de informações em 66 ou 75% verticalmente, a fim de deixar mais do polígono visível. (Ou talvez não! Mas você tem o botão para ajustar.)


0

Que tal usar apenas o ponto em que o usuário clicou para selecioná-lo, se ele for selecionado pelo usuário.


Ele pode ser selecionado com um clique do mouse ou uma consulta não espacial, portanto nem sempre funciona.
Jason

0

Também estou tentando resolver isso. Eu impus aos meus polígonos uma condição de que eles não podem ter linhas cruzadas que entram no que descreverei.

Então, minha abordagem usa triangulação. Pegue um vértice aleatório (possivelmente um vértice no extremo N, E, W ou S pode simplificar as coisas).

Nesse vértice, desenhe linhas para o vértice a um vértice de distância, ou seja, se o vértice for o vértice 3, observe o vértice 3 + 2.

Construa uma linha do seu vértice original para esse vértice. Se a linha construída:

  1. não cruza nenhuma outra linha e
  2. seu ponto médio não está fora do polígono

Então você construiu um triângulo que está dentro do polígono. Se o vértice bem-sucedido foi n + 2, seu triângulo é {n, n + 1, n + 2}, ao qual nos referiremos como {v, v1, v2}. Caso contrário, tente o próximo vértice e continue até que todos os vértices tenham sido tentados.

Quando você encontrar um triângulo, encontre o centro disso tomando uma linha do vértice v até o ponto médio de v1 e v2. É garantido que o ponto médio dessa linha esteja dentro do triângulo e dentro do polígono.

Ainda não codifiquei isso, mas percebo que um polígono com linhas cruzadas causará de fato algumas condições exóticas onde isso não funciona. Se esse é o tipo de polígono que você possui, você precisará testar cada segmento de linha no polígono e garantir que não esteja sendo cruzado. Pule os segmentos de linha cruzados e acho que funcionará.


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.