Один быстрый и грязный способ использует рекурсивное сферическое подразделение . Начиная с триангуляции земной поверхности, рекурсивно разбивайте каждый треугольник от вершины до середины его самой длинной стороны. (В идеале вы будете разбивать треугольник на две части равного диаметра или части равной площади, но поскольку они требуют некоторого сложного вычисления, я просто делю стороны ровно пополам: это приводит к тому, что различные треугольники в конечном итоге немного различаются по размеру, но это не кажется критическим для этого приложения.)
Конечно, вы будете поддерживать это подразделение в структуре данных, которая позволяет быстро идентифицировать треугольник, в котором лежит любая произвольная точка. Бинарное дерево (основанное на рекурсивных вызовах) работает хорошо: каждый раз, когда треугольник разбивается, дерево разбивается в узле этого треугольника. Данные, касающиеся плоскости расщепления, сохраняются, так что вы можете быстро определить, на какой стороне плоскости находится любая произвольная точка: это будет определять, перемещаться ли влево или вправо по дереву.
(Разве я говорил, что нужно разделить «плоскость»? Да - если моделировать земную поверхность как сферу и использовать геоцентрические (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
, вместе с поиском самой длинной стороны каждого треугольника.)