Преобразование координат

В веб-картографии координаты существуют одновременно в нескольких пространствах, каждое из которых решает свою задачу: хранение географического положения, математическая проекция на плоскость и отображение на экране. Внутри MapLibre GL JS вся цепочка преобразований строится вокруг строгого разделения этих уровней.

Первичный уровень — географические координаты:

  • долгота (longitude)
  • широта (latitude)

Они задаются в системе WGS84, которая соответствует стандарту EPSG:4326. В этой системе поверхность Земли описывается эллипсоидом, а координаты выражаются в градусах.

Однако экранное отображение требует плоской модели, что приводит к использованию картографических проекций.


WGS84 (EPSG:4326) как исходная система

EPSG:4326 — это сферическая модель координат, где:

  • долгота ∈ [-180°, 180°]
  • широта ∈ [-85.0511°, 85.0511°] (практически ограничение Web Mercator)

Внутри MapLibre GL JS эти значения представлены объектом LngLat, который инкапсулирует пару:

{lng: number, lat: number}

Особенность системы: она не пригодна для прямого отображения на плоскости без искажений, так как не содержит проекционного преобразования.


Web Mercator (EPSG:3857) и его роль

Основная рабочая проекция в веб-картах — Web Mercator (EPSG:3857), используемая для тайловой системы и рендеринга.

Её ключевая особенность — преобразование сферических координат в псевдоплоские:

  • долгота преобразуется линейно
  • широта преобразуется через логарифмическую функцию

Формулы преобразования:

x = R * λ
y = R * ln(tan(π/4 + φ/2))

где:

  • λ — долгота в радианах
  • φ — широта в радианах
  • R — радиус Земли в модели

Искажения возрастают к полюсам, поэтому карта обрезается примерно на ±85.0511°.


Проекционный слой MapLibre: world coordinates

Внутри рендерера MapLibre GL JS используется промежуточное пространство — world coordinates.

Это нормализованная плоскость, где:

  • центр карты соответствует (0, 0)
  • один “мир” соответствует квадрату 0–1 на одном уровне зума
  • при увеличении zoom пространство масштабируется экспоненциально

Переход выглядит так:

LngLat → Web Mercator → World Coordinates → Screen Coordinates

Масштабирование и уровни zoom

Ключевой механизм трансформации — экспоненциальное масштабирование при изменении zoom.

Фундаментальная зависимость:

scale = 2^zoom

Это означает:

  • zoom +1 → увеличение в 2 раза
  • zoom +2 → увеличение в 4 раза
  • zoom +n → увеличение в 2^n раз

Таким образом, координаты в world space масштабируются относительно центра карты.


Tile-система и координатная сетка

Карты разбиваются на квадратные тайлы размером 256×256 пикселей.

На уровне zoom z:

tiles_per_axis = 2^z

Каждый тайл имеет координаты:

(x_tile, y_tile)

где:

  • x растёт слева направо
  • y растёт сверху вниз

Переход от Web Mercator к тайлам:

x_tile = (x_world * scale) / tileSize
y_tile = (y_world * scale) / tileSize

Эта система обеспечивает:

  • ленивую загрузку данных
  • кэширование
  • сегментированное отрисовывание

Преобразование координат: project и unproject

В API MapLibre GL JS ключевые методы:

  • map.project(lngLat)
  • map.unproject(point)

Проекция (LngLat → ScreenPoint)

Алгоритм включает три шага:

  1. Перевод в радианы
  2. Web Mercator трансформация
  3. Применение матрицы камеры

Результат:

Point = { x: pixelX, y: pixelY }

Обратное преобразование (ScreenPoint → LngLat)

unproject выполняет обратную цепочку:

  1. Убирается камера transform (матрица вида)
  2. Пиксели нормализуются в world space
  3. Обратная Mercator функция
  4. Возврат в градусы

Формула обратной проекции широты:

φ = 2 * atan(e^y / R) - π/2

Камерная матрица трансформации

Внутренний рендер использует матричную модель:

M = P × V × W

где:

  • W — модель (географические координаты → world)
  • V — view (панорамирование, zoom, rotation)
  • P — проекция на экран

Каждая точка проходит через цепочку:

LngLat → world matrix → view transform → screen

Панорамирование и wrap-around долготы

Долгота имеет циклическую природу:

-180° ≡ 180°

Поэтому MapLibre GL JS поддерживает “wrap”:

  • объекты могут отображаться несколько раз по горизонтали
  • карта бесшовно повторяется по оси X

Математически это реализуется через модуль:

x_wrapped = x mod worldWidth

Ограничения широты и клиппинг

Из-за особенностей Web Mercator:

  • широта ограничена диапазоном
  • при выходе за пределы происходит обрезка (clamp)
lat ∈ [-85.0511°, 85.0511°]

Это предотвращает бесконечный рост y-координаты в проекции.


Координаты пикселей и DPI-адаптация

Экранное пространство зависит от:

  • devicePixelRatio
  • canvas scaling
  • CSS pixel vs device pixel distinction

Внутренний pipeline переводит координаты в device pixels:

devicePixel = cssPixel × devicePixelRatio

Это обеспечивает корректную отрисовку на Retina-дисплеях.


Поворот карты и его влияние на координаты

При rotation вводится дополнительное преобразование:

  • world координаты вращаются вокруг центра карты
  • применяется матрица поворота Z-axis
x' = x cos θ - y sin θ
y' = x sin θ + y cos θ

Это влияет на:

  • направление движения камеры
  • ориентацию тайлов
  • вычисление hit-test координат

Геометрические объекты и их трансформация

Все GeoJSON-объекты проходят ту же цепочку преобразований:

  • Point → одиночная проекция
  • LineString → последовательность точек
  • Polygon → замкнутые кольца

Каждая вершина трансформируется независимо, после чего применяется triangulation на уровне world space.


Итоговая цепочка преобразований

Полный pipeline координат:

GeoJSON (LngLat)
        ↓
WGS84 (EPSG:4326)
        ↓
Web Mercator (EPSG:3857)
        ↓
World coordinates
        ↓
Camera transform (zoom / pan / rotate)
        ↓
Screen coordinates

Каждый этап строго детерминирован и обратим (за исключением клиппинга и ограничений Mercator-проекции), что обеспечивает согласованность геометрии, тайлов и интерактивных операций в MapLibre GL JS.