Здравствуйте!

Хочу рассказать об кластеризации (группировке) геокоординат на стороне БД. Хотелось бы все это сделать в максимально простом виде, используя школьную математику. На работе появилась задача "отображение тепловой карты пожаров в МО". Пожары представлены виде географических координат. Нужно сгруппировать пожары, которые находятся в определенном радиусе, и на основе этих данных построить тепловую карту.

Готовые решения

Можно использовать стандартный тип 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)


  1. pankraty
    02.02.2022 06:35

    Как зарядка для ума - неплохое упражнение. С практической точки зрения - вариант не очень универсальный.

    Думаю, проблемы с подсчётом расстояний между между точками с использованием geography были вызваны тем, что вы считали расстояния от каждой точки до каждой, что, очевидно, слишком ресурсозатратно. Правильнее было бы спроецировать географические координаты в целевую СК (в ту, в которой будет происходить отображение), а в ней уже кластеризовать по квадратной сетке нужного размера. В вашем же случае сделать сетку квадратной, а не прямоугольной или трапецевидной - задача нетривиальная.


    1. pankraty
      02.02.2022 06:43

      И ещё, чтобы тепловая карта показывала объективный результат, величина ячеек должна быть одинаковой на всей площади. Иначе даже абсолютно равномерное распределение точек будет визуализировано как имеющее бОльшую плотность в той части, где ячейки крупнее. Поэтому проекцию стоит выбирать равновеликую.


      1. devhazhi Автор
        02.02.2022 10:52

        Не совсем понятно что не так квадратом, как видишь из результата все хорошо


    1. devhazhi Автор
      02.02.2022 10:55

      у меня то задача была не только для отображения, а для отчётов тоже , таком случии нужно сделать для всех. А так там можно пред насчёт ещё куча вариантов. А так погрешность уменьшить можно точность считать в нанометрах


  1. StpMax
    02.02.2022 10:48

    Альтернативное быстрое решение - вычислить для всех точек geohash, и группировать по первым совпадающим в хэше символам (чем больше совпадает - тем меньше группа).


  1. StpMax
    02.02.2022 10:48

    Альтернативное быстрое решение - вычислить для всех точек geohash, и группировать по первым совпадающим в хэше символам (чем больше совпадает - тем меньше группа).


    1. devhazhi Автор
      02.02.2022 10:49

      Это как раз таки и есть хэш