Das Problem besteht darin, herauszufinden, wie stark die Bögen gebogen werden müssen, um ihre visuelle Auflösung zu verbessern.
Hier ist eine Lösung (unter den vielen möglichen). Betrachten wir alle Bögen, die von einem gemeinsamen Ursprung ausgehen. Die Bögen werden hier am meisten überfüllt. Um sie am besten zu trennen, ordnen wir sie so an, dass sie sich in gleichmäßigen Winkeln ausbreiten . Es ist ein Problem, wenn wir gerade Liniensegmente vom Ursprung zu den Zielen zeichnen, da es normalerweise Zielgruppen in verschiedenen Richtungen gibt. Lassen Sie uns unsere Freiheit nutzen, um die Bögen zu biegen, um die Abweichungswinkel so gleichmäßig wie möglich zu verteilen.
Zur Vereinfachung verwenden wir Kreisbögen auf der Karte. Ein natürliches Maß für die "Biegung" in einem Bogen von Punkt y zu Punkt x ist die Differenz zwischen seiner Peilung bei y und der Peilung direkt von y zu x . Ein solcher Bogen ist ein Kreisabschnitt, auf dem y und x liegen; Die Elementargeometrie zeigt, dass der Biegewinkel die Hälfte des im Bogen enthaltenen Winkels beträgt.
Um einen Algorithmus zu beschreiben, brauchen wir etwas mehr Notation. Sei y der Ursprungspunkt (wie auf der Karte projiziert) und sei x_1 , x_2 , ..., x_n der Zielpunkt. Definieren Sie a_i als Peilung von y nach x_i , i = 1, 2, ..., n .
Als ersten Schritt nehmen wir an, dass die Lager (alle zwischen 0 und 360 Grad) in aufsteigender Reihenfolge sind: Dies erfordert, dass wir die Lager berechnen und sie dann sortieren; beides sind unkomplizierte aufgaben.
Idealerweise möchten wir, dass die Peilung der Bögen 360 / n , 2 * 360 / n usw. entspricht, bezogen auf ein Startlager. Die Differenzen zwischen den gewünschten Lagern und den tatsächlichen Lagern betragen daher i * 360 / n - a_i zuzüglich des Startlagers a0 . Die größte Differenz ist das Maximum dieser n Differenzen und die kleinste Differenz ist ihr Minimum. Stellen wir a0 so ein , dass es auf halbem Weg zwischen dem Maximum und dem Minimum liegt. Dies ist ein guter Kandidat für das Startlager, da es das maximale Ausmaß der auftretenden Biegung minimiert . Folglich definieren
b_i = i * 360 / n - a0 - a_i:
Dies ist das Biegen zu verwenden .
Es ist eine Frage der Elementargeometrie, einen Kreisbogen von y nach x zu zeichnen, der einen Winkel von 2 b_i einschließt. Daher überspringe ich die Details und gehe direkt zu einem Beispiel. Hier finden Sie Abbildungen der Lösungen für 64, 16 und 4 zufällige Punkte in einer rechteckigen Karte
Wie Sie sehen können, scheinen die Lösungen zu erhalten schöner als die Anzahl der Zielpunkte erhöht. Die Lösung für n = 4 zeigt deutlich, wie die Lager gleichmäßig verteilt sind, denn in diesem Fall beträgt der Abstand 360/4 = 90 Grad und offensichtlich wird dieser Abstand genau erreicht.
Diese Lösung ist nicht perfekt: Sie können wahrscheinlich mehrere Bögen identifizieren, die manuell optimiert werden könnten, um die Grafik zu verbessern. Aber es wird keine schreckliche Arbeit leisten und scheint ein wirklich guter Anfang zu sein.
Der Algorithmus hat auch den Vorteil, dass er einfach ist: Der komplizierteste Teil besteht darin, die Ziele nach ihrer Peilung zu sortieren.
Codierung
Ich kenne PostGIS nicht, aber vielleicht kann der Code, mit dem ich die Beispiele gezeichnet habe, als Leitfaden für die Implementierung dieses Algorithmus in PostGIS (oder einem anderen GIS) dienen.
Betrachten Sie Folgendes als Pseudocode (aber Mathematica führt es aus :-). (Wenn diese Site TeX unterstützt, wie es Mathe, Statistik und TCS tun, könnte ich dies viel besser lesbar machen.) Die Notation beinhaltet:
- Variablen- und Funktionsnamen unterscheiden zwischen Groß- und Kleinschreibung.
- [Alpha] ist ein griechischer Kleinbuchstabe. ([Pi] hat den Wert, den Sie denken, dass es haben sollte.)
- x [[i]] ist das Element i eines Arrays x (indexiert ab 1).
- f [a, b] wendet die Funktion f auf die Argumente a und b an. Funktionen im richtigen Fall, wie 'Min' und 'Table', sind systemdefiniert. Funktionen mit einem anfänglichen Kleinbuchstaben wie "Winkel" und "Versatz" sind benutzerdefiniert. Kommentare erklären unbekannte Systemfunktionen (wie 'Arg').
- Tabelle [f [i], {i, 1, n}] erstellt das Array {f [1], f [2], ..., f [n]}.
- Kreis [o, r, {a, b}] erzeugt einen Kreisbogen, der um o mit dem Radius r von Winkel a bis Winkel b zentriert ist (beide im Bogenmaß gegen den Uhrzeigersinn von genau nach Osten).
- Ordering [x] gibt ein Array von Indizes der sortierten Elemente von x zurück. x [[Ordering [x]]] ist die sortierte Version von x. Wenn y dieselbe Länge wie x hat, sortiert y [[Ordering [x]]] y parallel zu x.
Der ausführbare Teil des Codes ist erfreulicherweise kurz - weniger als 20 Zeilen -, da über die Hälfte davon entweder deklarativer Overhead oder Kommentare sind.
Zeichnen Sie eine Karte
z
ist eine Liste von Zielen und y
ist der Ursprung.
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}]
}]
]
Erstellen Sie einen Kreisbogen von Punkt x
zu Punkt, y
beginnend in einem Winkel \[Beta]
relativ zur x -> y-Peilung.
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}]
]
Berechnen Sie die Peilungen von einem Ursprung zu einer Liste von Punkten.
angles[origin_, x_] := Arg[Complex@@(#-origin)] & /@ x;
Berechnen Sie den Mittenbereich der Reste eines Lagersatzes.
x
ist eine Liste der Lager in sortierter Reihenfolge. Idealerweise 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
]