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

Внутри карты одновременно существуют несколько систем координат, каждая из которых решает свою задачу: географическая (широта/долгота), проекционная (плоские координаты в пикселях), тайловая (индексы тайлов) и координаты слоя (позиции объектов в рамках текущего масштаба и сдвига карты). Преобразование между ними — фундаментальная часть работы карты.

Географические координаты (LatLng)

Географические координаты описывают положение точки на поверхности Земли:

  • широта (latitude) — угол относительно экватора
  • долгота (longitude) — угол относительно нулевого меридиана

В Leaflet они представлены объектом L.LatLng.

const latlng = L.latLng(55.751244, 37.618423);

Этот объект не содержит информации о проекции или масштабе, он описывает только географическую позицию.


Проекция: перевод географии в плоскость

Карты не могут отображать сферу напрямую, поэтому используется проекция. По умолчанию применяется Web Mercator:

  • EPSG:3857
  • основана на проекции Меркатора
  • сохраняет углы, но искажает площади

Проекция в Leaflet реализуется через CRS (Coordinate Reference System).

const map = L.map('map', {
    crs: L.CRS.EPSG3857
});

Проекция точки

Переход от LatLng к проекционным координатам выполняется через project:

const point = map.options.crs.project(L.latLng(55.75, 37.61));

Результат — координаты в «мировой системе» (world coordinates), выраженные в пикселях при zoom = 0.

Обратное преобразование:

const latlng = map.options.crs.unproject(point);

Мировые координаты (World Coordinates)

После проекции координаты становятся линейными и удобными для масштабирования.

Особенности:

  • единица измерения — пиксели на zoom 0
  • ось X — долгота
  • ось Y — широта (инвертирована)
  • используется как базовая система для всех дальнейших преобразований

Переход от географии к пикселям карты

Ключевое преобразование в Leaflet:

map.latLngToLayerPoint(latlng);

Что происходит внутри:

  1. LatLng → world coordinates (projection)
  2. world coordinates → масштабирование по zoom
  3. применение смещения (origin)
  4. результат → pixel coordinates слоя

Пример:

const point = map.latLngToLayerPoint([55.75, 37.61]);
console.log(point.x, point.y);

Обратное преобразование: пиксели → географические координаты

const latlng = map.layerPointToLatLng(L.point(200, 300));

Это важно при обработке событий:

  • клики по карте
  • drag & drop объектов
  • кастомные оверлеи

Система пиксельных координат слоя

Leaflet оперирует не только географией, но и «слоем» (layer space), который зависит от:

  • текущего zoom
  • центра карты
  • размера контейнера

Координаты слоя — это смещение относительно верхнего левого угла видимой области.

const layerPoint = map.latLngToLayerPoint(latlng);

Переход между zoom-уровнями

Масштабирование карты — это изменение коэффициента преобразования между мировыми и экранными координатами.

Формула масштабирования:

scale = 2^zoom

В Leaflet это используется внутри:

map.getZoomScale(toZoom, fromZoom);

Пример:

const scale = map.getZoomScale(10, 8); // 4

Трансформации L.Transformation

Низкоуровневое преобразование координат реализуется через L.Transformation:

const t = new L.Transformation(1, 0, -1, 0);

Формально:

x' = a * x + b
y' = c * y + d

Используется для:

  • инверсии осей
  • перехода между системами координат
  • корректировки проекций

Тайловая система координат

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

Для получения тайла используется:

const tilePoint = map.project(latlng, zoom)
    .divideBy(256)
    .floor();

Каждый тайл имеет:

  • x — индекс по горизонтали
  • y — индекс по вертикали
  • z — уровень zoom

wrapLatLng и работа с меридианами

Долгота может выходить за диапазон [-180, 180], поэтому применяется нормализация:

const wrapped = map.wrapLatLng(L.latLng(10, 200));

Результат:

  • 200° → -160°

Это важно для бесшовного повторения карты по горизонтали.


Координаты контейнера и origin

Положение карты в пикселях зависит от origin:

const origin = map.getPixelOrigin();

И вычисление позиции:

const point = map.project(latlng, map.getZoom())
    .subtract(origin);

События и преобразование координат

При обработке событий координаты приходят в экранной системе:

map.on('click', (e) => {
    const latlng = e.latlng;
    const point = e.layerPoint;
});

Здесь:

  • latlng — географические координаты
  • layerPoint — пиксели слоя

Пользовательские CRS

Leaflet поддерживает замену системы координат:

const crs = L.CRS.Simple;

const map = L.map('map', {
    crs: crs
});

CRS.Simple:

  • не использует географию
  • работает в произвольных единицах
  • подходит для изображений и схем

Преобразование расстояний

Расстояния вычисляются через геодезическую модель:

const d = latlng1.distanceTo(latlng2);

Результат — метры.

Алгоритм учитывает кривизну Земли.


Связь координат с DOM-слоями

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

  • tile layer — тайловые координаты
  • marker layer — пиксельные позиции
  • overlay layer — географическая привязка

Marker позиционируется через преобразование:

const pos = map.latLngToLayerPoint(marker.getLatLng());
marker._icon.style.transform = `translate(${pos.x}px, ${pos.y}px)`;

Масштабирование и смещение

Полное преобразование можно представить как комбинацию:

screenPoint = (project(latlng) * scale) - origin

Где:

  • project — проекция
  • scale — zoom factor
  • origin — сдвиг центра

Инверсия преобразований

Для обратного расчёта:

latlng = unproject((screenPoint + origin) / scale)

В Leaflet:

map.containerPointToLatLng(point);

Контейнерные координаты

Есть три уровня координат:

  • containerPoint — относительно DOM контейнера
  • layerPoint — относительно слоя карты
  • latlng — географические координаты

Преобразования:

map.containerPointToLatLng(point);
map.latLngToContainerPoint(latlng);

Поворот и перспективные ограничения

Web Mercator не поддерживает:

  • наклон карты
  • 3D-перспективу

Поэтому все преобразования остаются аффинными и сохраняют линейную структуру.


Поведение при изменении zoom

При каждом изменении масштаба происходит:

  1. пересчёт projection scale
  2. перерасчёт origin
  3. перераспределение tile grid
  4. обновление layer points
map.on('zoom', () => {
    const z = map.getZoom();
});

Итоговая модель преобразований

Внутреннюю модель можно представить как цепочку:

LatLng
  → Projection (world coords)
    → Zoom scaling
      → Origin shift
        → Layer point
          → Container point

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