Algorytm znajdowania nieregularnych centroidów wielokątów (punkt etykiety)

13

Muszę znaleźć środek ciężkości (lub punkt etykiety) dla wielokątów o nieregularnym kształcie w Mapach Google. Pokazuję InfoWindows dla paczek i potrzebuję miejsca do zakotwiczenia InfoWindow, które na pewno będzie na powierzchni. Zobacz zdjęcia poniżej.

alternatywny tekst alternatywny tekst

W rzeczywistości nie potrzebuję niczego konkretnego w Mapach Google, tylko szukam pomysłu, jak automatycznie znaleźć ten punkt.

Moim pierwszym pomysłem było znalezienie „fałszywego” centroidu, biorąc średnie łaty i lngs i losowo umieszczając stamtąd punkty, aż znajdę taki, który przecina wielokąt. Mam już kod punktu w wielokącie. To po prostu wydaje mi się okropnie „hacky”.

Powinienem zauważyć, że nie mam dostępu do żadnego kodu po stronie serwera, który wyprowadza geometrię, więc nie mogę zrobić czegoś takiego jak ST_PointOnSurface (the_geom).

Jason
źródło

Odpowiedzi:

6

Szybkie i brudne: jeśli „fałszywego” środka masy nie ma w wielokącie, użyj najbliższego wierzchołka do tego punktu.

Elegant
źródło
Nie myślałem o tym. Idealnie chciałbym ten punkt w wielokącie, a nie na krawędzi, ale może do tego się wracam.
Jason
Po znalezieniu punktu krawędzi można przeciąć mały kwadrat wyśrodkowany w tym punkcie wielokąta, a następnie wybrać środek ciężkości przecięcia. Gdy kwadrat jest wystarczająco mały, gwarantuje to, że jest to punkt wewnętrzny (choć oczywiście będzie bardzo blisko krawędzi).
whuber
@Jason Jeśli użyjesz prawdziwego środka ciężkości, może być mniej prawdopodobne, że napotkasz ten problem. Nie powinno być zbyt trudno szybko przetłumaczyć coś na JavaScript: github.com/cartesiananalytics/Pipra/blob/master/src/…
Dandy
Podczas gdy moje rozwiązanie (promienie z fałszywego środka ciężkości) będzie działać przez większość czasu, myślę, że to rozwiązanie prawdopodobnie najlepiej by działało ze względu na jego prostotę i fakt, że masz gwarancję znalezienia punktu przynajmniej na krawędzi i można go łatwo przesunąć jest w środku wielokąta bez większego wysiłku.
Jason
3

Możesz na to spojrzeć: http://github.com/tparkin/Google-Maps-Point-in-Polygon

Wygląda na to, że korzysta z algorytmu Ray Casting, który powinien pasować do prezentowanego przypadku.

Tutaj jest post na blogu. http://appdelegateinc.com/blog/2010/05/16/point-in-polygon-checking/

DavidF
źródło
Jeśli chcesz to zaimplementować po stronie serwera, zarówno JTS (Java), jak i Geos (C) implementują tę funkcjonalność.
DavidF
Tak, prawdopodobnie powinienem był dodać, że mam już kod, aby ustalić, czy mój „obliczony” centroid jest w obrębie wielokąta, czy nie. Tak naprawdę chcę w jakiś sposób stworzyć środek ciężkości znajdujący się w wielokącie.
Jason
3

(Starszy) algorytm ESRI oblicza środek masy i po przetestowaniu go pod kątem włączenia do wielokąta przesuwa go w poziomie, jeśli to konieczne, aż znajdzie się w wielokącie. (Można to zrobić na wiele sposobów, w zależności od podstawowych operacji dostępnych w środowisku programistycznym.) Zazwyczaj tworzy to punkty etykiety dość blisko wizualnego środka wielokąta: wypróbuj to na ilustracji.

Whuber
źródło
1

Rozwiązałem swój problem, rozszerzając popularny kod epoli z http://econym.org.uk/gmap . Zasadniczo skończyłem na tym, że:

  • Utwórz serię promieni, które zaczynają się od „fałszywej centroidy” i rozciągają się na każdy narożnik i bok (łącznie 8)
  • Stopniowo utwórz punkt 10,20,30 ... procent w dół każdego promienia i sprawdź, czy ten punkt znajduje się w naszym oryginalnym wielokącie

Rozszerzony kod epoli poniżej:

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;}

Wciąż trochę zuchwały, ale wydaje się, że działa.

Jason
źródło
Ta metoda zawiedzie w przypadku niektórych krętych wielokątów. Na przykład buforuj polilinię {{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}} o mniej niż 1/2. Jest również nieefektywny w porównaniu do metody QAD Dandy'ego.
whuber
1

Kolejny „brudny” algorytm, aby to zrobić:

  • Weź obwiednię geometrii (Xmax, Ymax, Xmin, Ymin)

  • Pętla do momentu ( Xmin+rand*(Xmax-Xmin), Ymin+rand*(Ymax-Ymin) ) znalezienia losowego punktu w obrębie geometrii (za pomocą Google-Maps-Point-in-Polygon )

Julien
źródło
+1, ponieważ może to mieć szansę na trafienie za drugim razem. Tak długo, jak twój „losowy” jest odtwarzalny za każdym razem, aby nie drażnić użytkownika, jest to również prawidłowe rozwiązanie. Prawdopodobieństwo, że wkrótce nie trafi w ważny punkt, jest niewielkie, szczególnie jeśli zaczynasz z dobrym punktem zgadywania.
Dandy
1
@Dandy: Właściwie w niektórych przypadkach może to być naprawdę słaby algorytm. Weźmy na przykład wąski przekątny pasek. Istnieją one w praktyce (np. Długie paczki pieszych dróg) i mogą z łatwością zajmować mniej niż 0,1% obwiedni (czasem znacznie mniej). Aby mieć względną pewność (95% pewności), że trafimy w taki wielokąt za pomocą tej techniki, potrzeba około 3000 iteracji.
whuber
@ Whuber: Jeśli wybierzesz złą lokalizację początkową, tak, ukończenie może zająć trochę czasu. Jeśli weźmiesz również pod uwagę, że hipotetycznie 95% kliknięć będzie miało bardziej pożądaną geometrię, może to stanowić problem tylko w 5% przypadków. Podobnie jak w przypadku innego pytania GIS.se, jeśli celem jest wydajność, nie ma jednego rozwiązania, najlepiej zmienić taktykę opartą na heurystyce. Nie ma powodu, aby uruchamiać to dla 3000 iteracji. Zawsze możesz wpłacić kaucję na mój QAD po 10. Myślę, że warto spróbować tego na kilka iteracji, ponieważ lokalizacja może być bardziej pożądana.
Dandy
@Dandy: Ale co się dzieje z twoim rozwiązaniem QAD? Możesz nawet trochę go zmodyfikować, przechodząc od oryginalnego punktu etykiety próbnej do najbliższego wierzchołka w jakimś wewnętrznym buforze wielokąta: nadal QAD, ale teraz gwarantuje się, że wyląduje w wewnętrznej lokalizacji oryginalnej cechy. BTW, twoja strategia ratowania się wkrótce jest dobra. Za każdym razem, gdy koduję losową sondę w ten sposób, obliczam stosunek powierzchni obiektu do jego obwiedni, używam go do znalezienia oczekiwanego czasu do sukcesu i natychmiast ostrzegam użytkownika, jeśli może być długi.
whuber
@Wheurystyczny wskaźnik powierzchni heurystyczny to świetny pomysł, ponieważ prawie obliczasz środek ciężkości podczas obliczania powierzchni. Jeśli chodzi o problem z moim rozwiązaniem QAD: jest na krawędzi. Jeśli wybiorę ten punkt i buforuję go tak, jak mówisz, ten „mały” promień może być większy niż długość tego wąskiego odcinka. Zawsze jest narożnik. Tyle do rozważenia, aby zrobić balon, który zaśmieci interfejs użytkownika i i tak zasłoni geometrię. Prawdopodobnie lepiej wybrać najwyższy lub najniższy wierzchołek.
Dandy
1

W świetle ostatniego wyjaśnienia, że ​​wolisz lokalizację ściśle wewnętrzną, możesz wybrać dowolny punkt w transformacji osi środkowej, który nie znajduje się również na granicy wielokąta. (Jeśli nie masz kodu dla MAT, możesz go przybliżyć przez negatywne buforowanie wielokąta. Wyszukiwanie binarne lub sieczne szybko wygeneruje mały wewnętrzny wielokąt zbliżony do części MAT; użyj dowolnego punktu na jego granicy).

Whuber
źródło
Rozumiem, co mówiłeś o używaniu krawędzi geometrii, tak aby krawędź znajdowała się we wnętrzu interesującego wielokąta. Nie rozumiem, jak poszedłbyś na temat tworzenia tego brzegu / wierzchołka. Jedyne, co mogę wymyślić, to stworzyć wirtualny trójkąt, przecinając promień prostopadły od punktu zainteresowania do odcinka przeciwnego do odcinka wybranego punktu. Punkt środkowy między tymi dwoma punktami może być górą tego wirtualnego trójkąta.
Dandy
@Dandy: To trafia do sedna. Istnieje wiele sposobów rozwiązania tego problemu w zależności od tego, co robi Twój system GIS. Na przykład po znalezieniu promienia przecinającego pierwotny element w zestawie o długości dodatniej przecięcie to będzie rozłącznym połączeniem segmentów linii. Użyj środka dowolnego z tych segmentów. Innym sposobem jest rozpoczęcie od dowolnego punktu na obiekcie (najlepiej w pobliżu jego środka, co osiągnęła twoja metoda QED), utworzenie małego prostego wielokąta (np. Kwadratu) na środku, przecięcie go z oryginalnym elementem, wybranie unikalnego połączonego komponent ...
whuber
(kontynuacja) ... zawierający punkt początkowy i rekurencyjnie wybierz środek dla tego komponentu. Będzie wiele dostępnych metod, gdy twój GIS pozwoli ci zapętlić sekwencje wierzchołków opisujących granicę obiektu. Jeśli obsługiwane są bufory ujemne, można iteracyjnie znaleźć zestaw punktów wewnętrznych o maksymalnej odległości („szkielet”, który jest podzbiorem MAT). Jest to trochę drogie, ale dość łatwe do zaprogramowania i zapewnia doskonałe punkty etykiet.
whuber
0

Dlaczego nie użyć środka ciężkości tylko do pozycji pionowej (szerokości geograficznej)? Następnie możesz ustawić etykietę poziomo, wybierając średnią długość geograficzną na tej szerokości geograficznej . (W tym celu należy znaleźć wartość długości geograficznej krawędzi wielokąta na określonej szerokości geograficznej, co nie powinno sprawiać kłopotów).

Uważaj również na kształty U i bardziej złożone. :) Być może dla tych, wybierz średnią z najbardziej prawej pary długości (każda para odpowiada plasterkowi wielokąta), ponieważ okno informacyjne jest zorientowane w ten sposób?

Daje to również nieco większą kontrolę nad pozycjonowaniem; na przykład fajnie byłoby ustawić okno informacyjne na 66 lub 75% w pionie, aby pozostawić więcej widocznego wielokąta. (Lub może nie! Ale masz pokrętło, aby dostosować.)

Dan S.
źródło
0

A może wystarczy użyć punktu, który użytkownik kliknął, aby go wybrać, jeśli jest on wybrany przez użytkownika, który jest.

Elegant
źródło
Można go wybrać kliknięciem myszy lub zapytaniem nieprzestrzennym, więc nie zawsze będzie to działać.
Jason
0

Też próbuję to rozwiązać. Nałożyłem warunek na moje wielokąty, że nie mogą one przekraczać linii, co wchodzi w to, co opiszę.

Więc moje podejście wykorzystuje triangulację. Weź losowy wierzchołek (prawdopodobnie weź wierzchołek na skrajnym N, E, W lub S może uprościć rzeczy).

Z tego wierzchołka narysuj linie do wierzchołka oddalonego o jeden wierzchołek, tj. Jeśli twój wierzchołek to wierzchołek 3, spójrz na wierzchołek 3 + 2.

Zbuduj linię z oryginalnego wierzchołka do tego wierzchołka. Jeśli skonstruowana linia:

  1. nie przekracza żadnej innej linii i
  2. jego punkt środkowy nie znajduje się poza wielokątem

Następnie zbudowałeś trójkąt, który znajduje się w wielokącie. Jeśli pomyślnym wierzchołkiem było n + 2, to twój trójkąt to {n, n + 1, n + 2}, który będziemy określać jako {v, v1, v2}. Jeśli nie, wypróbuj następny wierzchołek i kontynuuj, aż wszystkie wierzchołki zostaną wypróbowane.

Kiedy znajdziesz trójkąt, znajdź jego środek, poprowadząc linię od wierzchołka v do punktu środkowego v1 i v2. Środek tej linii z pewnością znajdzie się wewnątrz trójkąta i wewnątrz wielokąta.

Jeszcze tego nie zakodowałem, ale widzę, jak się nad tym zastanawiam, że wielokąt z przecinającymi się liniami w rzeczywistości spowoduje pewne egzotyczne warunki, w których to nie zadziała. Jeśli masz taki typ wielokątów, musisz przetestować każdy segment linii na wielokącie i upewnić się, że nie został przekroczony. Pomiń odcinki linii, które są skrzyżowane i myślę, że to zadziała.


źródło