Здравствуйте!
Хочу рассказать об кластеризации (группировке) геокоординат на стороне БД. Хотелось бы все это сделать в максимально простом виде, используя школьную математику. На работе появилась задача "отображение тепловой карты пожаров в МО". Пожары представлены виде географических координат. Нужно сгруппировать пожары, которые находятся в определенном радиусе, и на основе этих данных построить тепловую карту.
Готовые решения
Можно использовать стандартный тип geography Sql Server и с помощью distance процедуры рассчитать расстояния между точками и группировать их. Этот способ дорогой по времени, больше 10000 записей занимает примерно 5 минут на core i5 9600к.
Также можно использовать Tile38 (https://tile38.com/) не было времени внедрять, скорость вычисления идеальная. Но нет интеграции с SqlServer и для этого придеться синхронизировать координаты, и обеспечить отказаустойчивость для обеих систем.
Решение
Для решения задачи будем использовать округление координат, так как округление нам нужно в метрах, а координаты представлены в градусах. Долгота зависит от широты и это надо учитывать, когда будем преобразовывать метры в градусы.
Широту можно округлить не зависимо, например:
один градус широты = 2 * Pi * "Полярный радиус земли" / 360 ~ 111 км.
И так как по широте максимум может быть 90 градусов, получается ~ 9985 км. Это число нужно для того чтобы определить коэффициент разбиения по группам. Например Требуется округлить в радиусе 100 км, получается по широте может поместится ~ 100 групп. Итак, нам нужно еще получить до какой степени нужно округлить, - это мы получим следующей формулой.
6 - lg "масштаб группировки в метрах" - оставляем только целую часть
например 6 - lg 100000м = 1 и так нужно округлять первую цифру после запятой.
Итого формула для широты
declare @metersLatDegree float = round(9985238.0901698 / @maxMeters, 0);
declare @latituteCenter float = Round(Round(@latitude * @metersLatDegree / 90.0, 0) * (90.0 /@metersLatDegree), @precsion);
где @maxMeters это максимальная разница между координатами в метрах, @latitude широта, @precsion - степень округления.
Переходим к долготе и она будет зависеть от широты, так как при приближении к полюсу его периметр будет уменьшаться от ~ 40000 км до 0. и для того чтобы определить периметр нужно использовать cos(широты) по след формуле:
один градус долготы = 2 * Pi * "экваториальный радиус земли" * COS(широты) / 360 ~ 111 км. на экваторе когда широта равна 0 и когда широта равна 60 ~ 55 км.
Итоговая формула для долготы почти такая же
declare @metersLatDegree float= round(
20037077.9445957 * cos(radians(@latitude)) / @maxMeters, 0);
declare @longitudeCenter float = Round(Round(@longitude * @metersLatDegree / 90.0, 0) * (90.0 /@metersLatDegree), @precsion);
@longitude - долгота
Чтобы получить @precsion формула одинаковая
@precsion int = 6 - round(log(@maxMeters), 10), 0)
Визуализируем результат для случайных 1000 точек по земле
на полюсах погрешность хромает, но для моего проекта подошло.
Комментарии (7)
StpMax
02.02.2022 10:48Альтернативное быстрое решение - вычислить для всех точек geohash, и группировать по первым совпадающим в хэше символам (чем больше совпадает - тем меньше группа).
pankraty
Как зарядка для ума - неплохое упражнение. С практической точки зрения - вариант не очень универсальный.
Думаю, проблемы с подсчётом расстояний между между точками с использованием geography были вызваны тем, что вы считали расстояния от каждой точки до каждой, что, очевидно, слишком ресурсозатратно. Правильнее было бы спроецировать географические координаты в целевую СК (в ту, в которой будет происходить отображение), а в ней уже кластеризовать по квадратной сетке нужного размера. В вашем же случае сделать сетку квадратной, а не прямоугольной или трапецевидной - задача нетривиальная.
pankraty
И ещё, чтобы тепловая карта показывала объективный результат, величина ячеек должна быть одинаковой на всей площади. Иначе даже абсолютно равномерное распределение точек будет визуализировано как имеющее бОльшую плотность в той части, где ячейки крупнее. Поэтому проекцию стоит выбирать равновеликую.
devhazhi Автор
Не совсем понятно что не так квадратом, как видишь из результата все хорошо
devhazhi Автор
у меня то задача была не только для отображения, а для отчётов тоже , таком случии нужно сделать для всех. А так там можно пред насчёт ещё куча вариантов. А так погрешность уменьшить можно точность считать в нанометрах