Una forma rápida y sucia utiliza una subdivisión esférica recursiva . Comenzando con una triangulación de la superficie de la tierra, divida recursivamente cada triángulo desde un vértice hasta el centro de su lado más largo. (Lo ideal sería dividir el triángulo en dos partes de igual diámetro o partes de igual área, pero debido a que implican algunos cálculos complicados, simplemente divido los lados exactamente por la mitad: esto hace que los diversos triángulos eventualmente varíen un poco en tamaño, pero eso no parece crítico para esta aplicación).
Por supuesto, mantendrá esta subdivisión en una estructura de datos que permite identificar rápidamente el triángulo en el que se encuentra cualquier punto arbitrario. Un árbol binario (basado en las llamadas recursivas) funciona bien: cada vez que se divide un triángulo, el árbol se divide en el nodo de ese triángulo. Los datos relacionados con el plano de división se retienen, de modo que puede determinar rápidamente en qué lado del plano se encuentra cualquier punto arbitrario: eso determinará si viajará hacia la izquierda o la derecha hacia abajo del árbol.
(¿Dije dividir "plano"? Sí, si modelar la superficie de la tierra como una esfera y usar coordenadas geocéntricas (x, y, z), entonces la mayoría de nuestros cálculos tienen lugar en tres dimensiones, donde los lados de los triángulos son piezas de intersecciones de la esfera con planos a través de su origen. Esto hace que los cálculos sean rápidos y fáciles).
Ilustraré mostrando el procedimiento en un octante de una esfera; Los otros siete octantes se procesan de la misma manera. Tal octante es un triángulo 90-90-90. En mis gráficos dibujaré triángulos euclidianos que abarcan las mismas esquinas: no se ven muy bien hasta que se vuelven pequeños, pero se pueden dibujar fácil y rápidamente. Aquí está el triángulo euclidiano correspondiente al octante: es el comienzo del procedimiento.
Como todos los lados tienen la misma longitud, se elige uno al azar como el "más largo" y se subdivide:
Repita esto para cada uno de los nuevos triángulos:
Después de n pasos tendremos 2 ^ n triángulos. Aquí está la situación después de 10 pasos, mostrando 1024 triángulos en el octante (y 8192 en la esfera en general):
Como ilustración adicional, generé un punto aleatorio dentro de este octante y recorrí el árbol de subdivisión hasta que el lado más largo del triángulo alcanzó menos de 0.05 radianes. Los triángulos (cartesianos) se muestran con el punto de la sonda en rojo.
Incidentalmente, para reducir la ubicación de un punto a un grado de latitud (aproximadamente), notarás que esto es aproximadamente 1/60 radianes y, por lo tanto, cubre alrededor de (1/60) ^ 2 / (Pi / 2) = 1/6000 de superficie total Como cada subdivisión reduce aproximadamente a la mitad el tamaño del triángulo, unas 13 a 14 subdivisiones del octante harán el truco. Eso no es mucho cálculo, como veremos a continuación, lo que hace que sea eficiente no almacenar el árbol en absoluto, sino realizar la subdivisión sobre la marcha. Al principio, observaría en qué octante se encuentra el punto, que está determinado por los signos de sus tres coordenadas, que se pueden registrar como un número binario de tres dígitos, y en cada paso desea recordar si el punto se encuentra en la izquierda (0) o derecha (1) del triángulo. Eso le da otro número binario de 14 dígitos. Puede usar estos códigos para agrupar puntos arbitrarios.
(En general, cuando dos códigos están cerca como números binarios reales, los puntos correspondientes están cerca; sin embargo, los puntos aún pueden estar cerca y tener códigos notablemente diferentes. Considere dos puntos separados por un metro por separado del Ecuador, por ejemplo: sus códigos deben diferir incluso antes del punto binario, porque están en octantes diferentes. Este tipo de cosas es inevitable con cualquier partición fija del espacio).
Utilicé Mathematica 8 para implementar esto: puede tomarlo tal cual o como pseudocódigo para una implementación en su entorno de programación favorito.
Determine en qué lado del plano 0-ab se encuentra el punto p :
side[p_, {a_, b_}] := If[Det[{p, a, b}] >= 0, left, right];
Refinar el triángulo abc basado en el 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}]
]
La última figura se dibujó mostrando el octante y, además, representando la siguiente lista como un conjunto de polígonos:
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
aplica repetidamente una operación ( refine
) mientras se aplica una condición (el triángulo es grande) o hasta que se haya alcanzado un conteo máximo de operación (16).
Para mostrar la triangulación completa del octante, comencé con el primer octante e iteré el refinamiento diez veces. Esto comienza con una ligera modificación de 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 diferencia es que split
devuelve las dos mitades de su triángulo de entrada en lugar de en la que se encuentra un punto dado. La triangulación completa se obtiene iterando esto:
triangles = NestList[Flatten[split /@ #, 1] &, {IdentityMatrix[3] // N}, 10];
Para verificar, calculé una medida del tamaño de cada triángulo y miré el rango. (Este "tamaño" es proporcional a la figura en forma de pirámide subtendida por cada triángulo y el centro de la esfera; para triángulos pequeños como estos, este tamaño es esencialmente proporcional a su área esférica).
Through[{Min, Max}[Map[Round[Det[#], 0.00001] &, triangles[[10]] // N, {1}]]]
{0.00523, 0.00739}
Por lo tanto, los tamaños varían hacia arriba o hacia abajo en aproximadamente un 25% de su promedio: eso parece razonable para lograr una forma aproximadamente uniforme de agrupar puntos.
Al escanear sobre este código, notará que no hay trigonometría : el único lugar donde será necesario, si es que lo hará, será en la conversión de ida y vuelta entre coordenadas esféricas y cartesianas. El código tampoco proyecta la superficie de la tierra en ningún mapa, evitando así las distorsiones concomitantes. De lo contrario, solo usa el promedio ( Mean
), el Teorema de Pitágoras ( Norm
) y un determinante 3 por 3 ( Det
) para hacer todo el trabajo. (Hay algunos comandos simples de manipulación de listas como RotateLeft
y Flatten
, también, junto con una búsqueda del lado más largo de cada triángulo).