Il problema è capire quanto piegare gli archi per migliorare la loro risoluzione visiva.
Ecco una soluzione (tra le tante possibili). Consideriamo tutti gli archi che provengono da un'origine comune. Gli archi sono più affollati qui. Per separarli al meglio, sistemiamoli in modo che si estendano in angoli equidistanti . È un problema se tracciamo segmenti di linea retta dall'origine alle destinazioni, perché in genere ci saranno gruppi di destinazioni in varie direzioni. Usiamo la nostra libertà per piegare gli archi per spaziare gli angoli di partenza il più uniformemente possibile.
Per semplicità usiamo archi circolari sulla mappa. Una misura naturale della "piega" in un arco dal punto y al punto x è la differenza tra il suo rilevamento su y e il rilevamento direttamente da y a x . Un tale arco è un settore di un cerchio su cui y e x sia menzogna; la geometria elementare mostra che l'angolo di curvatura è uguale alla metà dell'angolo incluso nell'arco.
Per descrivere un algoritmo abbiamo bisogno di un po 'più di notazione. Permettere y il punto di origine (come proiettato sulla mappa) e che x_1 , x_2 , ..., x_n siano i punti di destinazione. Definire a_i come cuscinetto da y a x_i , i = 1, 2, ..., n .
Come passo preliminare, supponiamo che i cuscinetti (tutti compresi tra 0 e 360 gradi) siano in ordine crescente: questo richiede di calcolare i cuscinetti e quindi ordinarli; entrambi sono compiti semplici.
Idealmente, vorremmo che i cuscinetti degli archi fossero uguali a 360 / n , 2 * 360 / n , ecc., Rispetto ad alcuni cuscinetti di partenza. Le differenze tra i cuscinetti desiderati e i cuscinetti effettivi sono pertanto pari a i * 360 / n - a_i più il cuscinetto iniziale, a0 . La differenza più grande è il massimo di queste n differenze e la più piccola differenza è il loro minimo. Set di Let a0 di essere a metà strada tra il massimo ed il minimo; questo è un buon candidato per il cuscinetto iniziale perché minimizza la quantità massima di flessione che si verificherà . Di conseguenza, definire
b_i = i * 360 / n - a0 - a_i:
questa è la piega da usare .
È una questione di geometria elementare da cui disegnare un arco circolare y a x che sottende un angolo di 2 b_i, quindi salterò i dettagli e passerò direttamente ad un esempio. Ecco le illustrazioni delle soluzioni per 64, 16 e 4 punti casuali posizionati all'interno di una mappa rettangolare
Come puoi vedere, le soluzioni sembrano migliorare con l'aumentare del numero di punti di destinazione. La soluzione per n = 4 mostra chiaramente come i cuscinetti siano equidistanti, poiché in questo caso la spaziatura è pari a 360/4 = 90 gradi e ovviamente tale spaziatura è esattamente raggiunta.
Questa soluzione non è perfetta: probabilmente puoi identificare diversi archi che potrebbero essere modificati manualmente per migliorare la grafica. Ma non farà un lavoro terribile e sembra essere davvero un buon inizio.
L'algoritmo ha anche il merito di essere semplice: la parte più complicata consiste nell'ordinare le destinazioni in base ai loro cuscinetti.
Coding
Non conosco PostGIS, ma forse il codice che ho usato per disegnare gli esempi può servire da guida per l'implementazione di questo algoritmo in PostGIS (o in qualsiasi altro GIS).
Considera quanto segue come pseudocodice (ma Mathematica lo eseguirà :-). (Se questo sito supporta TeX, come fanno quelli matematici, statistici e TCS, potrei renderlo molto più leggibile.) La notazione include:
- I nomi delle variabili e delle funzioni fanno distinzione tra maiuscole e minuscole.
- [Alpha] è un carattere greco minuscolo. ([Pi] ha il valore che pensi che dovrebbe avere.)
- x [[i]] è l'elemento i di un array x (indicizzato a partire da 1).
- f [a, b] applica la funzione f agli argomenti a e b. Le funzioni nel caso appropriato, come "Min" e "Tabella", sono definite dal sistema; le funzioni con una lettera iniziale minuscola, come "angoli" e "offset", sono definite dall'utente. I commenti spiegano eventuali funzioni di sistema oscure (come "Arg").
- La tabella [f [i], {i, 1, n}] crea l'array {f [1], f [2], ..., f [n]}.
- Cerchio [o, r, {a, b}] crea un arco del cerchio centrato a o del raggio r dall'angolo a all'angolo b (entrambi in radianti in senso antiorario da est).
- L'ordinamento [x] restituisce una matrice di indici degli elementi ordinati di x. x [[Ordering [x]]] è la versione ordinata di x. Quando y ha la stessa lunghezza di x, y [[Ordine [x]]] ordina y in parallelo con x.
La parte eseguibile del codice è misericordiosamente breve - meno di 20 righe - perché oltre la metà è costituita da spese generali dichiarative o commenti.
Disegna una mappa
z
è un elenco di destinazioni ed y
è l'origine.
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}]
}]
]
Crea un arco circolare da un punto x
all'altro y
iniziando dall'angolo \[Beta]
relativo al cuscinetto 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}]
]
Calcola i cuscinetti da un'origine a un elenco di punti.
angles[origin_, x_] := Arg[Complex@@(#-origin)] & /@ x;
Calcola la gamma media dei residui di un set di cuscinetti.
x
è un elenco di cuscinetti in ordine ordinato. Idealmente, 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
]