Позиционирование маркеров

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

Географическая привязка и система координат

Каждый маркер задаётся в виде пары координат:

L.marker([55.751244, 37.618423]).addTo(map);

Эти значения интерпретируются в системе координат карты (обычно WGS84), а затем преобразуются в пиксели текущего viewport.

Ключевой момент: Leaflet не хранит маркеры в пикселях — он хранит их в географических координатах и пересчитывает позицию при каждом изменении состояния карты.

Любое движение карты (панорамирование, масштабирование, изменение CRS) приводит к перерасчёту позиции всех маркеров.


Привязка маркера к точке карты

Базовый метод изменения положения маркера:

const marker = L.marker([55.75, 37.61]).addTo(map);

marker.setLatLng([55.76, 37.62]);

При вызове setLatLng происходит:

  • обновление географических координат
  • пересчёт пиксельной позиции
  • перерисовка DOM-элемента

Этот механизм используется при анимациях, трекинге объектов и обновлении данных в реальном времени.


Смещение и якорь иконки (iconAnchor)

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

const icon = L.icon({
  iconUrl: 'marker.png',
  iconSize: [32, 32],
  iconAnchor: [16, 32]
});

L.marker([55.75, 37.61], { icon }).addTo(map);

Принцип работы iconAnchor

  • [0, 0] — левый верхний угол изображения
  • [width/2, height] — классическая нижняя центральная точка
  • любые другие значения смещают точку привязки

Важно: именно iconAnchor, а не географические координаты определяют, какая часть изображения «прибита» к карте.


Смещение всплывающих окон (popupAnchor)

Попапы привязываются относительно маркера, а не координат карты:

const icon = L.icon({
  iconUrl: 'marker.png',
  iconSize: [32, 32],
  iconAnchor: [16, 32],
  popupAnchor: [0, -32]
});

Значения popupAnchor определяют смещение относительно iconAnchor.

Типичные сценарии:

  • popup над маркером: [0, -height]
  • popup сбоку: [width / 2, 0]

Пиксельное позиционирование и внутренний слой

Leaflet использует слой Pane, внутри которого каждый маркер размещается как DOM-элемент с абсолютным позиционированием.

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

  1. пересчёт координат через проекцию
  2. преобразование lat/lng → pixel
  3. установка CSS transform: translate(...)

Пример упрощённой логики:

const point = map.latLngToLayerPoint([lat, lng]);
markerEl.style.transform = `translate(${point.x}px, ${point.y}px)`;

Это объясняет высокую производительность: браузер оптимизирует только трансформации, а не полную переразметку DOM.


Центрирование карты относительно маркера

Часто требуется переместить карту так, чтобы маркер оказался в центре или в заданной области:

map.setView([55.75, 37.61], 13);

или более гибко:

map.panTo([55.75, 37.61]);

Разница:

  • setView — меняет и центр, и масштаб
  • panTo — изменяет только позицию

При этом маркер автоматически пересчитывает своё положение, так как зависит от текущего viewport.


Работа с drag и динамическим позиционированием

Маркер может быть перетаскиваемым:

const marker = L.marker([55.75, 37.61], {
  draggable: true
}).addTo(map);

marker.on('drag', (e) => {
  const pos = e.target.getLatLng();
});

Во время drag:

  • DOM-элемент перемещается без изменения карты
  • координаты обновляются только при завершении или в событии drag

Это важное отличие: движение маркера не обязательно означает изменение карты.


Позиционирование при изменении масштаба

Каждый zoom вызывает перерасчёт всех точек:

  • координаты переводятся через проекцию CRS
  • изменяется масштаб tile-layer
  • маркеры пересчитывают pixel-position
map.on('zoomend', () => {
  console.log('zoom changed');
});

Маркер при этом не меняет lat/lng, меняется только визуальное положение.


Пользовательские иконки и точная настройка геометрии

При работе с нестандартными иконками (стрелки, треугольники, SVG) важно правильно задать якорь:

const icon = L.divIcon({
  html: '<div class="arrow-marker"></div>',
  iconSize: [20, 20],
  iconAnchor: [10, 10]
});

Ошибки в iconAnchor приводят к смещению:

  • маркер «прыгает» относительно координаты
  • всплывающие окна выглядят асимметрично
  • кластеры визуально рассинхронизируются

Координатные преобразования и CRS

В основе позиционирования лежит проекция карты (CRS — Coordinate Reference System).

По умолчанию используется:

  • EPSG:3857 (Web Mercator)

Функции преобразования:

map.project([lat, lng], zoom);
map.unproject(point, zoom);

Именно через них определяется точное положение маркера в пикселях.


Смещение при изменении контейнера карты

Если контейнер карты изменяет размеры или положение в DOM, Leaflet автоматически пересчитывает позиции:

map.invalidateSize();

Без этого вызова маркеры могут:

  • остаться в старых координатах пикселей
  • визуально «съехать»
  • потерять синхронизацию с тайлами

Привязка маркеров к динамическим данным

При обновлении координат из внешнего источника:

setInterval(() => {
  marker.setLatLng([
    55.75 + Math.random() * 0.01,
    37.61 + Math.random() * 0.01
  ]);
}, 1000);

Позиция обновляется без пересоздания объекта, что снижает нагрузку на DOM и ускоряет рендеринг.


Особенности многослойного позиционирования

Маркер может находиться в разных pane:

const marker = L.marker([55.75, 37.61], {
  pane: 'overlayPane'
});

Каждый pane имеет свой z-index и правила трансформации, влияющие на визуальное наложение объектов.


Синхронизация с событиями карты

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

  • move
  • zoom
  • resize
  • viewreset

Пример реакции на движение карты:

map.on('move', () => {
  // маркеры автоматически перерасчитываются
});

Хотя явного обновления не требуется, события позволяют синхронизировать внешние слои и кастомные оверлеи.


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

Внутренний цикл работы маркера можно описать так:

  1. хранение lat/lng
  2. пересчёт через CRS
  3. перевод в pixel coordinates
  4. применение CSS transform
  5. перерисовка слоя

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