Un modo rapido e sporco utilizza una suddivisione sferica ricorsiva . A partire da una triangolazione della superficie terrestre, dividere in modo ricorsivo ogni triangolo da un vertice al centro del suo lato più lungo. (Idealmente divideresti il triangolo in due parti di uguale diametro o parti di uguale area, ma poiché queste comportano un calcolo complicato, ho appena diviso i lati esattamente a metà: questo fa sì che i vari triangoli alla fine varino un po 'di dimensioni, ma che non sembra fondamentale per questa applicazione.)
Naturalmente manterrai questa suddivisione in una struttura di dati che consente di identificare rapidamente il triangolo in cui si trova qualsiasi punto arbitrario. Un albero binario (basato sulle chiamate ricorsive) funziona bene: ogni volta che un triangolo viene diviso, l'albero viene diviso nel nodo di quel triangolo. I dati relativi al piano di divisione vengono conservati, in modo da poter determinare rapidamente su quale lato del piano si trova qualsiasi punto arbitrario: ciò determinerà se viaggiare a sinistra o a destra lungo l'albero.
(Ho detto "piano" di divisione? Sì - se si modella la superficie terrestre come una sfera e si usano coordinate geocentriche (x, y, z), la maggior parte dei nostri calcoli avviene in tre dimensioni, dove i lati dei triangoli sono pezzi di intersezioni della sfera con i piani attraverso la sua origine. Questo rende i calcoli facili e veloci.)
Illustrerò mostrando la procedura su un ottante di una sfera; gli altri sette ottanti vengono elaborati allo stesso modo. Tale ottante è un triangolo 90-90-90. Nella mia grafica disegnerò triangoli euclidei che attraversano gli stessi angoli: non sembrano molto belli fino a quando non diventano piccoli, ma possono essere disegnati facilmente e rapidamente. Ecco il triangolo euclideo corrispondente all'ottante: è l'inizio della procedura.
Poiché tutti i lati hanno la stessa lunghezza, uno viene scelto casualmente come il "più lungo" e suddiviso:
Ripeti l'operazione per ciascuno dei nuovi triangoli:
Dopo n passaggi avremo 2 ^ n triangoli. Ecco la situazione dopo 10 passaggi, mostrando 1024 triangoli nell'ottante (e 8192 sulla sfera in generale):
Come ulteriore illustrazione, ho generato un punto casuale all'interno di questo ottante e ho viaggiato l'albero di suddivisione fino a quando il lato più lungo del triangolo ha raggiunto meno di 0,05 radianti. I triangoli (cartesiani) sono mostrati con la punta della sonda in rosso.
Per inciso, per restringere la posizione di un punto a un grado di latitudine (circa), noterai che questo è circa 1/60 di radiante e quindi copre circa (1/60) ^ 2 / (Pi / 2) = 1/6000 del superficie totale. Poiché ogni suddivisione dimezza approssimativamente la dimensione del triangolo, circa 13-14 suddivisioni dell'ottante faranno il trucco. Questo non è molto calcolo - come vedremo di seguito - rendendo efficiente non memorizzare affatto l'albero, ma eseguire la suddivisione al volo. All'inizio noterai in quale ottante si trova il punto - che è determinato dai segni delle sue tre coordinate, che possono essere registrate come un numero binario di tre cifre - e ad ogni passo vuoi ricordare se il punto sta a sinistra (0) o destra (1) del triangolo. Questo dà un altro numero binario di 14 cifre. È possibile utilizzare questi codici per raggruppare punti arbitrari.
(Generalmente, quando due codici sono vicini come numeri binari effettivi, i punti corrispondenti sono vicini; tuttavia, i punti possono ancora essere vicini e avere codici notevolmente diversi. Considerare due punti distanti un metro l'uno dall'altro, ad esempio: i loro codici devono differire anche prima del punto binario, perché si trovano in diversi ottanti. Questo tipo di cose è inevitabile con qualsiasi partizionamento fisso dello spazio.)
Ho usato Mathematica 8 per implementare questo: potresti prenderlo così com'è o come pseudocodice per un'implementazione nel tuo ambiente di programmazione preferito.
Determina quale lato del piano 0-ab punto p giace:
side[p_, {a_, b_}] := If[Det[{p, a, b}] >= 0, left, right];
Perfeziona il triangolo abc in base al punto p.
refine[p_, {a_, b_, c_}] := Block[{sides, x, y, z, m},
sides = Norm /@ {b - c, c - a, a - b} // N;
{x, y, z} = RotateLeft[{a, b, c}, First[Position[sides, Max[sides]]] - 1];
m = Normalize[Mean[{y, z}]];
If[side[p, {x, m}] === right, {y, m, x}, {x, m, z}]
]
L'ultima figura è stata disegnata visualizzando l'ottante e, soprattutto, rendendo il seguente elenco come un insieme di poligoni:
p = Normalize@RandomReal[NormalDistribution[0, 1], 3] (* Random point *)
{a, b, c} = IdentityMatrix[3] . DiagonalMatrix[Sign[p]] // N (* First octant *)
NestWhileList[refine[p, #] &, {a, b, c}, Norm[#[[1]] - #[[2]]] >= 0.05 &, 1, 16]
NestWhileList
applica ripetutamente un'operazione ( refine
) mentre si verifica una condizione (il triangolo è grande) o fino al raggiungimento di un conteggio massimo di operazioni (16).
Per visualizzare la triangolazione completa dell'ottante, ho iniziato con il primo ottante e ho ripetuto la raffinatezza dieci volte. Questo inizia con una leggera modifica di refine
:
split[{a_, b_, c_}] := Module[{sides, x, y, z, m},
sides = Norm /@ {b - c, c - a, a - b} // N;
{x, y, z} = RotateLeft[{a, b, c}, First[Position[sides, Max[sides]]] - 1];
m = Normalize[Mean[{y, z}]];
{{y, m, x}, {x, m, z}}
]
La differenza è che split
restituisce entrambe le metà del suo triangolo di input piuttosto che quello in cui si trova un dato punto. La triangolazione completa si ottiene ripetendo questo:
triangles = NestList[Flatten[split /@ #, 1] &, {IdentityMatrix[3] // N}, 10];
Per verificare, ho calcolato una misura delle dimensioni di ogni triangolo e ho esaminato l'intervallo. (Questa "dimensione" è proporzionale alla figura a forma di piramide sottesa da ciascun triangolo e dal centro della sfera; per piccoli triangoli come questi, questa dimensione è essenzialmente proporzionale alla sua area sferica.)
Through[{Min, Max}[Map[Round[Det[#], 0.00001] &, triangles[[10]] // N, {1}]]]
{0,00523, 0,00739}
Pertanto le dimensioni variano di circa il 25% verso l'alto o verso il basso dalla loro media: ciò sembra ragionevole per ottenere un modo approssimativamente uniforme di raggruppare i punti.
Scansionando questo codice, non noterai alcuna trigonometria : l'unico posto in cui sarà necessario, se non del tutto, sarà la conversione avanti e indietro tra coordinate sferiche e cartesiane. Inoltre, il codice non proietta la superficie terrestre su nessuna mappa, evitando così le distorsioni che ne conseguono. Altrimenti, usa solo la media ( Mean
), il Teorema di Pitagora ( Norm
) e un determinante 3 per 3 ( Det
) per fare tutto il lavoro. (Ci sono alcuni semplici comandi di manipolazione dell'elenco come RotateLeft
e Flatten
, anche, insieme a una ricerca del lato più lungo di ogni triangolo.)