Один швидкий і брудний спосіб використовує рекурсивний сферичний підрозділ . Починаючи з тріангуляції земної поверхні, рекурсивно розділяйте кожен трикутник від вершини до середини його найдовшої сторони. (В ідеалі ви розділите трикутник на дві частини однакового діаметру або частини однакової площі, але оскільки вони передбачають певний розрахунок, я просто розділюю сторони рівно навпіл: це призводить до того, що різні трикутники з часом трохи змінюються за розміром, але що не здається критичним для цієї програми.)
Звичайно, ви будете підтримувати цей підрозділ у структурі даних, яка дозволяє швидко ідентифікувати трикутник, у якому лежить будь-яка довільна точка. Двійкове дерево (засноване на рекурсивних викликах) працює добре: кожного разу, коли розділяється трикутник, дерево розбивається на вузол цього трикутника. Дані, що стосуються площини розщеплення, зберігаються, так що ви можете швидко визначити, на якій стороні площини лежить будь-яка довільна точка: це визначатиме, чи рухатись ліворуч або праворуч вниз по дереву.
(Чи сказав я розщеплення "площини"? Так - якщо моделювати земну поверхню як сферу і використовувати геоцентричні координати (x, y, z), то більшість наших обчислень відбувається в трьох вимірах, де сторони трикутників - це шматки перетину сфери з площинами через її походження. Це робить обчислення швидкими та легкими.)
Я проілюструю, показуючи процедуру на одному октанті сфери; інші сім октантів обробляються таким же чином. Такий октант - трикутник 90-90-90. У своїй графіці я намалюю евклідові трикутники, що охоплюють однакові кути: вони виглядають не дуже добре, поки не стануть маленькими, але їх можна легко та швидко намалювати. Ось евклідовий трикутник, відповідний октанту: це початок процедури.
Оскільки всі сторони мають однакову довжину, один вибирається випадковим чином як "найдовший" і підрозділяється:
Повторіть це для кожного нового трикутника:
Після n кроків у нас буде 2 ^ n трикутників. Ось ситуація після 10 кроків, що показують 1024 трикутники в октанті (і 8192 на загальній кулі):
В якості подальшої ілюстрації я створив випадкову точку в межах цього октанта і об'їхав дерево підрозділу, поки довга сторона трикутника не досягла менше 0,05 радіана. (Декартові) трикутники зображені червоною точкою зонда.
Між іншим, щоб звузити розташування точки на одну ступінь широти (приблизно), ви зауважите, що це приблизно 1/60 радіана і так охоплює навколо (1/60) ^ 2 / (Pi / 2) = 1/6000 загальна поверхня. Оскільки кожен підрозділ приблизно вдвічі зменшує розмір трикутника, трюк виконає приблизно 13-14 підрозділів октанта. Це не так вже й багато обчислень - як ми побачимо нижче - що дозволяє не зберігати дерево взагалі, а виконувати підрозділ на льоту. На початку ви зазначили, в якому октанті лежить точка - це визначається знаками трьох його координат, які можна записати як трицифрове двійкове число - і на кожному кроці ви хочете пам'ятати, чи лежить точка ліворуч (0) або праворуч (1) трикутника. Це дає ще 14-значне двійкове число. Ви можете використовувати ці коди для групування довільних точок.
(Як правило, коли два коди близькі як фактичні двійкові числа, відповідні точки є близькими; однак, точки все одно можуть бути близькими і мають надзвичайно різні коди. Розглянемо, наприклад, два точки на метр один від одного, відокремлений екватор, наприклад: їхні коди повинні відрізнятися навіть перед двійковою точкою, оскільки вони знаходяться в різних октантах. Така річ неминуча при будь-якому фіксованому розділенні простору.)
Я використовував Mathematica 8 для цього: ви можете прийняти це як є або як псевдокод для реалізації у вашому улюбленому середовищі програмування.
Визначте, на якій стороні площини лежить 0-ab точка p :
side[p_, {a_, b_}] := If[Det[{p, a, b}] >= 0, left, right];
Уточнити трикутник abc на основі точки 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}]
]
Останню цифру було намальовано відображенням октанта, а поверх цього шляхом подання наступного списку у вигляді набору багатокутників:
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
неодноразово застосовує операцію ( refine
), коли дотримується умова (трикутник великий) або до досягнення максимальної кількості операцій (16).
Щоб відобразити повну тріангуляцію октанта, я почав з першого октанта і десять разів повторив уточнення. Це починається з незначної модифікації 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}}
]
Різниця полягає в тому, що split
повертає обидві половини свого вхідного трикутника, а не ту, в якій лежить задана точка. Повна триангуляція отримується шляхом повторення цього:
triangles = NestList[Flatten[split /@ #, 1] &, {IdentityMatrix[3] // N}, 10];
Для перевірки я обчислював міру розміру кожного трикутника і дивився на діапазон. (Цей "розмір" пропорційний фігурі у формі піраміди, що підлягає кожному трикутнику та центру сфери; для таких маленьких трикутників цей розмір по суті пропорційний його сферичній площі.)
Through[{Min, Max}[Map[Round[Det[#], 0.00001] &, triangles[[10]] // N, {1}]]]
{0,00523, 0,00739}
Таким чином, розміри змінюються вгору або вниз приблизно на 25% від їх середнього: це здається розумним для досягнення приблизно рівномірного способу групування балів.
При перегляді цього коду ви не помітите тригонометрії : єдине місце, яке воно знадобиться, - це перетворення вперед і назад між сферичними та декартовими координатами. Код також не проектує земну поверхню на будь-яку карту, тим самим уникаючи супутніх спотворень. В іншому випадку він використовує лише усереднення ( Mean
), теорему Піфагора ( Norm
) та детермінант 3 на 3 ( Det
) для виконання всієї роботи. (Існують декілька простих команд для маніпуляції зі списком, як-от, RotateLeft
і Flatten
, поряд з пошуком найдовшої сторони кожного трикутника.)