Преобразование пикселей в координаты

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

Географические координаты (LatLng) описывают положение на поверхности Земли в градусах широты и долготы. Они независимы от масштаба и устройства отображения.

Пиксельные координаты описывают положение точки уже в плоскости карты, где единицей измерения выступает пиксель. Здесь важно различать несколько уровней:

  • Container coordinates — координаты внутри DOM-контейнера карты
  • Layer coordinates — координаты слоя карты с учётом текущего масштаба
  • Projected coordinates — координаты в проекции CRS (Coordinate Reference System)

Именно переход между этими пространствами лежит в основе всех интерактивных операций: кликов, перетаскиваний, оверлеев и кастомных слоёв.


Проекция и система координат CRS

Leaflet использует проекцию Web Mercator (EPSG:3857) как стандартную, через объект CRS.EPSG3857.

Проекция выполняет преобразование:

  • из географических координат (широта/долгота)
  • в плоские координаты карты (в метрах условной проекции)

На этом уровне применяется масштабирование, зависящее от zoom-уровня.

Базовая идея:

world size = tileSize * 2^zoom

При стандартном размере тайла 256 px:

  • zoom 0 → 256 px
  • zoom 1 → 512 px
  • zoom 2 → 1024 px
  • и так далее

Базовое преобразование LatLng ↔︎ пиксели

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

Преобразование географических координат в пиксели слоя

map.latLngToLayerPoint(latlng)

Результат — координата точки в пикселях относительно текущего слоя карты с учётом zoom и центра.

Пример:

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

Эти координаты удобны для позиционирования кастомных DOM-элементов внутри карты.


Обратное преобразование пикселей в LatLng

map.layerPointToLatLng(point)

Используется для получения географической координаты из пиксельной позиции слоя:

const latlng = map.layerPointToLatLng([200, 300]);
console.log(latlng.lat, latlng.lng);

ContainerPoint и взаимодействие с DOM

Помимо layerPoint существует ещё один уровень — координаты контейнера карты.

containerPointToLatLng

map.containerPointToLatLng(point)

Используется, когда координаты приходят напрямую из событий DOM:

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

latLngToContainerPoint

map.latLngToContainerPoint(latlng)

Позволяет вычислить положение элемента внутри видимой области карты:

const p = map.latLngToContainerPoint([55.75, 37.61]);

Разница между containerPoint и layerPoint проявляется при смещении карты: containerPoint зависит от текущего viewport, layerPoint — от общей системы слоя.


Преобразования через проекцию (низкий уровень)

Leaflet предоставляет прямой доступ к проекции:

map.project(latlng, zoom)
map.unproject(point, zoom)

project

Преобразует LatLng в координаты мировой проекции:

const projected = map.project([55.75, 37.61], 10);

unproject

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

const latlng = map.unproject([1200, 1500], 10);

Эти методы работают без привязки к текущему состоянию карты, что делает их полезными для вычислений вне UI-цикла.


Привязка пикселей к событиям мыши

События карты содержат сразу несколько представлений координат:

map.on('click', (e) => {
  e.latlng;          // географические координаты
  e.containerPoint;  // пиксели внутри контейнера
  e.layerPoint;      // пиксели слоя
});

Каждое поле соответствует уровню преобразования и может использоваться для разных задач:

  • latlng — для геоданных
  • containerPoint — для DOM-интеракций
  • layerPoint — для вычислений внутри слоя

Преобразование clientX/clientY в координаты карты

При работе с чистыми DOM-событиями вне Leaflet необходимо вручную переводить координаты:

map.on('mousemove', (e) => {
  const rect = map.getContainer().getBoundingClientRect();

  const point = {
    x: e.originalEvent.clientX - rect.left,
    y: e.originalEvent.clientY - rect.top
  };

  const latlng = map.containerPointToLatLng(point);
});

Здесь важно учитывать, что браузерные координаты не учитывают внутренние смещения карты, zoom и трансформации.


Масштабирование и зависимость от zoom

Любое преобразование пикселей напрямую связано с уровнем масштабирования:

1 пиксель ≈ (360° / 256) / 2^zoom

При увеличении zoom:

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

Это влияет на точность обратных преобразований pixel → LatLng, особенно при работе с высокими zoom-уровнями.


Позиционирование кастомных слоёв

Пиксельные координаты активно используются при создании пользовательских overlay-слоёв.

Пример ручного позиционирования элемента:

const div = document.createElement('div');
div.className = 'marker';

map.getPanes().overlayPane.appendChild(div);

function update() {
  const point = map.latLngToLayerPoint([55.75, 37.61]);
  L.DomUtil.setPosition(div, point);
}

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


Пересчёт координат при изменении состояния карты

Любое из следующих событий требует повторного преобразования пикселей:

  • zoom
  • move
  • resize
  • rotate (если используется кастомная реализация)
  • viewreset

Типовой обработчик:

map.on('move zoom', () => {
  updateMarkers();
});

Геометрические вычисления в пиксельном пространстве

Работа в layerPoint координатах позволяет выполнять быстрые вычисления:

  • расстояния между точками
  • столкновения элементов
  • кластеризация
  • hit-testing

Пример расстояния:

const p1 = map.latLngToLayerPoint(a);
const p2 = map.latLngToLayerPoint(b);

const dx = p2.x - p1.x;
const dy = p2.y - p1.y;

const distance = Math.sqrt(dx * dx + dy * dy);

Такие вычисления быстрее, чем работа в географических координатах, так как исключают сферическую геометрию.


Смещения карты и их влияние на преобразования

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

  • центра карты
  • zoom уровня
  • размеров контейнера

При изменении центра:

newLayerPoint = worldPoint - centerOffset

Именно поэтому прямое использование pixel координат без пересчёта приводит к некорректному позиционированию.


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

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

LatLng
  ↓ project
World coordinates
  ↓ scale by zoom
Pixel world space
  ↓ translate by center
Layer point
  ↓ DOM offset
Container point

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