Zakrzywione „mapy tras”


39

Ostatnio przeglądałem strony internetowe linii lotniczych, które wyświetlają ich trasy odlatujące z określonego miasta do wszystkich obsługiwanych przez nich miast. Chciałbym móc tworzyć podobne zakrzywione trasy między punktami. Czy ktoś stworzył skrypty lub funkcje, które wygenerują zakrzywione łuki, takie jak te przedstawione w tym przykładzie ?

Ścieżki lotu

Czy w PostGIS jest implementacja ST_MakeLine, która pozwala określić ilość krzywych, które należy zastosować przy łączeniu 2 punktów?

Podczas gdy obecnie używam PostGIS i QGIS, chętnie usłyszę o innych opcjach oprogramowania, które mogą stworzyć taki sam wygląd.


Czy ktoś zna jakieś fajne implementacje tego? Przykłady czy cokolwiek?
Mark Boulder

Odpowiedzi:


26

Tworzenie świetnych kręgów może dać pożądany efekt.

Może coś takiego jak omówione na http://lists.osgeo.org/pipermail/postgis-users/2008-F February/018620.html

Aktualizacja:

Kontynuowałem ten pomysł w „Wizualizacji globalnych połączeń” . Jest to rozwiązanie oparte wyłącznie na PostGIS, wykorzystujące reprojection do tworzenia łuków.

SELECT ST_Transform(
  ST_Segmentize(
    ST_MakeLine(
      ST_Transform(a.the_geom, 953027),
      ST_Transform(b.the_geom, 953027)
    ), 
  100000), 
4326)

(Definicja CRS dla 953027 można znaleźć tutaj: http://spatialreference.org/ref/esri/53027/ )

wprowadź opis zdjęcia tutaj


4
Podoba mi się ten pomysł, chociaż przy wielkich kręgach problem napotykasz na to, że na krótszych dystansach nadal będziesz miał ogólnie prostą linię. Chciałbym mieć możliwość kontrolowania ilości łuku, który umieszczam w linii (tj. Długość pola = odległość * 2).
RyanDalton

1
Oto przykład dobro błąd prostu za wielkie kręgi: gc.kls2.com/cgi-bin/...
RyanDalton

1
Po kilku dodatkowych badaniach znalazłem ten post, który może być pomocny w tej metodzie. mail-archive.com/postgis-users@postgis.refractions.net/…
RyanDalton

Na użytek przyszłego czytelnika pomyślałem, że po prostu przejdę do linku do ostatniego postu na blogu @ underdark, który omawia ten temat. underdark.wordpress.com/2011/08/20/…
RyanDalton,

To wspaniale!! Wykorzystany w moim projekcie do narysowania linii między meldowaniami użytkowników a lokalizacjami, odebrany z Forsquare
Lorenzo Barbagli

24

Problem polega na tym, aby dowiedzieć się, jak zgiąć łuki, aby zwiększyć ich rozdzielczość wizualną.

Oto jedno rozwiązanie (spośród wielu możliwych). Rozważmy wszystkie łuki pochodzące ze wspólnego pochodzenia. Łuki są tutaj najbardziej zatłoczone. Aby je najlepiej rozdzielić, ułóżmy je tak, aby rozkładały się pod równymi kątami . Problem polega na tym, że narysujemy odcinki linii prostej od początku do miejsc docelowych, ponieważ zwykle będą skupienia miejsc docelowych w różnych kierunkach. Wykorzystajmy naszą swobodę do wyginania łuków, aby możliwie równomiernie rozmieszczać odchodzące kąty.

Dla uproszczenia użyjmy łuków kołowych na mapie. Naturalną miarą „zgięcia” łuku od punktu y do punktu x jest różnica między jego łożyskiem w punkcie y a łożyskiem bezpośrednio od y do x . Taki łuk jest sektorem koła, na którym leżą oba y i x ; geometria elementarna pokazuje, że kąt gięcia jest równy połowie kąta zawarcia w łuku.

Aby opisać algorytm, potrzebujemy trochę więcej notacji. Niech y będzie punktem początkowym (zgodnie z rzutem na mapie) i niech x_1 , x_2 , ..., x_n będą punktami docelowymi. Zdefiniuj a_i jako namiar od y do x_i , i = 1, 2, ..., n .

Na wstępie przyjmijmy, że łożyska (wszystkie od 0 do 360 stopni) są w porządku rosnącym: wymaga to obliczenia łożysk, a następnie ich posortowania; oba są prostymi zadaniami.

Idealnie byłoby, gdyby łożyska łuków były równe 360 ​​/ n , 2 * 360 / n itd. W stosunku do niektórych łożysk początkowych. Różnice między pożądanymi a rzeczywistymi łożyskami są zatem równe i * 360 / n - a_i plus łożysko początkowe, a0 . Największa różnica to maksimum tych n różnic, a najmniejsza różnica to ich minimum. Ustawmy a0 tak, aby znajdowało się w połowie drogi między wartością maksymalną a minimalną; jest to dobry kandydat na łożysko początkowe, ponieważ minimalizuje maksymalną liczbę zgięć, które wystąpią . W związku z tym zdefiniuj

b_i = i * 360 / n - a0 - a_i:

to jest gięcie do użycia .

Narysowanie okrągłego łuku od y do x, który obejmuje kąt 2 b_i, polega na elementarnej geometrii , więc pominę szczegóły i przejdę od razu do przykładu. Oto ilustracje rozwiązań dla 64, 16 i 4 losowych punktów umieszczonych na prostokątnej mapie

alternatywny tekst

alternatywny tekst

alternatywny tekst

Jak widać, rozwiązania wydają się ładniejsze wraz ze wzrostem liczby punktów docelowych. Rozwiązanie dla n = 4 wyraźnie pokazuje, w jaki sposób łożyska są równomiernie rozmieszczone, ponieważ w tym przypadku odstęp wynosi 360/4 = 90 stopni i oczywiście ten odstęp jest dokładnie osiągnięty.

To rozwiązanie nie jest idealne: prawdopodobnie możesz zidentyfikować kilka łuków, które można ręcznie ulepszyć, aby poprawić grafikę. Ale to nie zrobi okropnej roboty i wydaje się, że to naprawdę dobry początek.

Algorytm ma tę zaletę, że jest prosty: najbardziej skomplikowana część polega na sortowaniu miejsc docelowych według ich położenia.


Kodowanie

Nie znam PostGIS, ale być może kod użyty do narysowania przykładów może służyć jako przewodnik dla implementacji tego algorytmu w PostGIS (lub innym GIS).

Za pseudokod należy uważać następujące (ale Mathematica go wykona :-). (Jeśli ta strona obsługuje TeX, podobnie jak matematyka, statystyki i TCS, mógłbym to uczynić o wiele bardziej czytelnym.) Notacja obejmuje:

  • W nazwach zmiennych i funkcjach rozróżniana jest wielkość liter.
  • [Alpha] jest małą grecką postacią. ([Pi] ma wartość, którą Twoim zdaniem powinna mieć.)
  • x [[i]] to element i tablicy x (indeksowany od 1).
  • f [a, b] stosuje funkcję f do argumentów a i b. Funkcje w odpowiednim przypadku, takie jak „Min” i „Tabela”, są zdefiniowane w systemie; funkcje z początkową małą literą, takie jak „kąty” i „przesunięcie”, są zdefiniowane przez użytkownika. Komentarze wyjaśniają wszelkie niejasne funkcje systemu (takie jak „Arg”).
  • Tabela [f [i], {i, 1, n}] tworzy tablicę {f [1], f [2], ..., f [n]}.
  • Okrąg [o, r, {a, b}] tworzy łuk koła wyśrodkowany na o o promieniu r od kąta a do kąta b (oba w radianach przeciwnie do ruchu wskazówek zegara od wschodu).
  • Porządkowanie [x] zwraca tablicę indeksów posortowanych elementów x. x [[Zamawianie [x]]] to posortowana wersja x. Gdy y ma taką samą długość jak x, y [[Kolejność [x]]] sortuje y równolegle do x.

Wykonalna część kodu jest na szczęście krótka - mniej niż 20 wierszy - ponieważ ponad połowa z nich to albo deklaratywne obciążenie, albo komentarze.

Narysuj mape

zto lista miejsc docelowych i ypochodzenie.

circleMap[z_List, y_] := 
Module[{\[Alpha] = angles[y,z], \[Beta], \[Delta], n},
    (* Sort the destinations by bearing *)
    \[Beta] = Ordering[\[Alpha]];
    x = z[[\[Beta] ]]; (* Destinations, sorted by bearing from y *)
    \[Alpha] = \[Alpha][[\[Beta]]]; (* Bearings, in sorted order *)
    \[Delta] = offset[\[Alpha]];
    n = Length[\[Alpha]];
    Graphics[{(* Draw the lines *)
        Gray, Table[circle[y, x[[i]],2 \[Pi] i / n + \[Delta] - \[Alpha][[i]]], 
             {i, 1, Length[\[Alpha]]}],
        (* Draw the destination points *)
        Red, PointSize[0.02], Table[Point[u], {u, x}]
    }]
]

Utwórz łuk kołowy od punktu xdo punktu, yzaczynając od kąta \[Beta]względem łożyska x -> y.

circle[x_, y_, \[Beta]_] /; -\[Pi] < \[Beta] < \[Pi] := 
Module[{v,  \[Rho], r, o, \[Theta], sign},
    If[\[Beta]==0, Return[Line[{x,y}]]];

    (* Obtain the vector from x to y in polar coordinates. *)
    v = y - x; (* Vector from x to y *)
    \[Rho] = Norm[v]; (* Length of v *)
    \[Theta] = Arg[Complex @@ v]; (* Bearing from x to y *)

    (* Compute the radius and center of the circle.*)
    r = \[Rho] / (2 Sin[\[Beta]]); (* Circle radius, up to sign *)
    If[r < 0, sign = \[Pi], sign = 0];
    o = (x+y)/2 + (r/\[Rho]) Cos[\[Beta]]{v[[2]], -v[[1]]}; (* Circle center *)

    (* Create a sector of the circle. *)
    Circle[o, Abs[r], {\[Pi]/2 - \[Beta] + \[Theta] + sign, \[Pi] /2 + \[Beta] + \[Theta] + sign}]
]

Oblicz łożyska od początku do listy punktów.

angles[origin_, x_] := Arg[Complex@@(#-origin)] & /@ x;

Oblicz średnicę resztek zestawu łożysk.

xto lista łożysk w posortowanej kolejności. Idealnie, x [[i]] ~ 2 [Pi] i / n.

offset[x_List] :=
Module[
    {n = Length[x], y},
    (* Compute the residuals. *)
    y = Table[x[[i]] - 2 \[Pi] i / n, {i, 1, n}];
    (* Return their midrange. *)
    (Max[y] + Min[y])/2
]

Powinienem wspomnieć, że to rozwiązanie zakłada, że ​​miejsca docelowe mniej więcej otaczają pochodzenie. Gdy tak nie jest, cały pomysł (równomiernie rozmieszczonych łożysk) nie jest dobry. Ale można to łatwo naprawić, wprowadzając fałszywe miejsca docelowe w szczelinach kątowych, a następnie usuwając te miejsca docelowe (i ich łuki). Proces ten można zautomatyzować, obliczając średnią odległość między łożyskami i wykorzystując ją do identyfikacji dużych szczelin itp .
whuber

Ładna grafika. Zastanawiam się, czy linie lotnicze używają zautomatyzowanego narzędzia do sporządzania map tras pokazanych na końcu czasopisma pokładowego.
Kirk Kuykendall

1
@Kirk Prawdopodobnie płacą komuś za wykonanie kartografii ręcznie :-). Zainspirowało mnie to pytanie, czy proste podejście może stworzyć dość dobrą grafikę. Odpowiedź wygląda obiecująco. Nawiasem mówiąc, te grafiki zostały opracowane przez Mathematica 8 przy użyciu prymitywów Okrąg i Punkt oraz małej arytmetyki wektorowej w celu znalezienia środków koła.
whuber

Uwielbiam wyniki, które pokazałeś, a ja to jest właściwy sposób. Będę szczery, uważam się za techniczny, ale trochę się zagubiłem w formule, którą podałeś, i dlatego jak zamienić to w kod PostGIS jest prawie niemożliwe. Czy ktoś ma jakieś pomysły, jak przełożyć koncepcję Whubera na działający kod? Spróbuję dokonać przeglądu i spróbować, ale pomoc będzie bardzo mile widziana.
RyanDalton

@ whuber- Dzięki za zaktualizowany pseudokod. Będziemy musieli sprawdzić, czy rzeczywiście możemy to zaimplementować w PostGIS.
RyanDalton



3

Skończyło się na wypróbowaniu tego, aby zakrzywić zestaw „dwupunktowych” linijek za pomocą funkcji ST_CurveToLine, jak sugeruje @Nicklas Avén.

Do funkcji ST_OffsetCurve przekazałem następujące 3 zestawy współrzędnych:

  1. Początek oryginalnej linii
  2. Punkt środkowy odsunięcia linii równoległej do pierwotnej linii
  3. Koniec oryginalnej linii

Użyłem funkcji ST_OffsetCurve do obliczenia przesunięcia - 1/10 długości oryginalnej linii w moim przykładzie.

Oto SQL, którego użyłem do wygenerowania linii krzywych z oryginalnych linii prostych:

    ST_CurveToLine('CIRCULARSTRING(' || st_x(st_startpoint(the_geom)) || ' ' || st_y(st_startpoint(the_geom)) || ', ' || st_x(st_centroid(ST_OffsetCurve(the_geom, st_length(the_geom)/10, 'quad_segs=4 join=bevel'))) || ' ' || st_y(st_centroid(ST_OffsetCurve(the_geom, st_length(the_geom)/10, 'quad_segs=4 join=bevel'))) || ', ' || st_x(st_endpoint(the_geom)) || ' ' ||  st_y(st_endpoint(the_geom)) || ')') AS the_curved_geom

Naprawdę przydatne, ale z jakiegoś powodu wynik nie szanuje mojego okienka. Masz pomysł, dlaczego?
DMS02

Czy możesz podać więcej szczegółów - srid geometrii wejściowej, brak srid wyjściowy, jest inny, generowane błędy (jakie aplikacje (aplikacje) - QGIS, PostgreSQL).
Brent Edwards

Tabela, w której chcę wstawić wynikowe linie zakrzywione, ma ograniczenie enforce_srid_geom. Podczas wykonywania zapytania pojawia się błąd informujący, że to zapytanie narusza to ograniczenie. Z tabelą bez tego ograniczenia działa, ale po dodaniu go do QGIS jest wyszczególniony z wartością srid 0. Moje zapytanie: WSTAW test INT (the_curved_geom) WYBIERZ [tutaj swój SQL] Z wierszy
DMS02

Spróbuj uruchomić funkcje postgis.net/docs/ST_GeometryType.html i postgis.net/docs/ST_SRID.html w kolumnie geometrii (the_curved_geom) i sprawdź, czy nie ma konfliktów z tabelą testową i enforce_srid_geom. Jeśli tak, możesz w razie potrzeby przekształcić geometrię / siatkę lub zmodyfikować tabelę testową / ograniczenie.
Brent Edwards,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.