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

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

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

Основой позиционирования выступает пара координат longitude/latitude. При создании маркера используется объект LngLat, который фиксирует точку на географической плоскости:

import maplibregl from 'maplibre-gl';

const marker = new maplibregl.Marker()
  .setLngLat([37.6173, 55.7558])
  .addTo(map);

Внутри MapLibre GL JS происходит преобразование координат в координаты canvas с учётом текущего состояния камеры. Любое изменение центра карты или масштаба автоматически инициирует перерасчёт позиции.

Принцип экранной проекции

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

  • географические координаты переводятся в мировую систему координат (world coordinates)
  • применяется матрица камеры (camera matrix)
  • результат проецируется в экранные пиксели

При каждом кадре рендеринга выполняется пересчёт:

  • map.transform.coordinateToPoint(lngLat)
  • учёт zoom уровня
  • учёт pitch и bearing

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

Смещение маркера (offset)

Маркер может быть смещён относительно географической точки. Это используется для визуального поднятия маркера над точкой или корректировки якоря:

const marker = new maplibregl.Marker({
  offset: [0, -20]
})
  .setLngLat([37.6173, 55.7558])
  .addTo(map);

Смещение применяется уже после проекции координат в экранное пространство. Значения задаются в пикселях и не зависят от масштаба карты.

Якорь (anchor) и точка привязки

Параметр anchor определяет, какая часть маркера совпадает с координатой:

  • center
  • top
  • bottom
  • left
  • right
  • комбинации углов

Пример:

const marker = new maplibregl.Marker({
  anchor: 'bottom'
})
  .setLngLat([37.6173, 55.7558])
  .addTo(map);

При использовании anchor система автоматически корректирует смещение DOM-элемента, чтобы заданная точка элемента совпадала с географической координатой.

DOM-маркеры и кастомные элементы

Маркер может быть основан на произвольном HTML-элементе. В этом случае позиционирование осуществляется через CSS transform:

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

const marker = new maplibregl.Marker({
  element: el,
  anchor: 'center'
})
  .setLngLat([37.6173, 55.7558])
  .addTo(map);

MapLibre GL JS управляет стилем:

  • transform: translate(...) translate(-50%, -50%)
  • position: absolute
  • обновление при каждом render событии карты

DOM-маркеры дороже по производительности, так как требуют синхронизации с layout браузера.

Обновление позиции маркера

Позиция может изменяться динамически без пересоздания объекта:

marker.setLngLat([30.3141, 59.9386]);

При этом система не создаёт новый DOM-узел, а пересчитывает только трансформацию. Это критично для анимаций и трекинга объектов.

Привязка к движению карты

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

  • move
  • zoom
  • rotate
  • pitch

Маркер остаётся географически закреплённым и визуально перемещается за счёт трансформации сцены.

Пример логики обновления:

map.on('move', () => {
  // внутренний вызов render pipeline
});

Влияние zoom на плотность отображения

При изменении масштаба координаты остаются фиксированными, однако расстояния между маркерами в пикселях изменяются нелинейно. Это связано с экспоненциальной природой WebMercator-проекции:

  • увеличение zoom в 1 шаг = удвоение детализации
  • расстояния между объектами растягиваются или сжимаются

Поворот карты (bearing) и ориентация маркеров

При вращении карты маркеры сохраняют ориентацию относительно экрана, если не задан параметр rotationAlignment.

Доступные режимы:

  • map — маркер поворачивается вместе с картой
  • viewport — маркер всегда ориентирован вверх
const marker = new maplibregl.Marker({
  rotationAlignment: 'viewport'
});

Это влияет на UX в навигационных сценариях, где важна стабильная ориентация иконок.

Наклон карты (pitch) и перспективное смещение

При использовании наклона карты возникает перспективное искажение. Позиция маркера вычисляется в 3D-пространстве:

  • координаты → world position (x, y, z)
  • применение матрицы перспективы
  • проекция на экран

Маркер визуально смещается в зависимости от высоты точки и угла обзора.

Высотная компенсация и Z-index поведение

В случаях, когда используется рельеф или 3D-terrain, маркеры могут учитывать высоту:

  • маркер “приклеивается” к поверхности земли
  • учитывается elevation слоя terrain

Также применяется порядок отрисовки:

  • маркеры ближе к камере перекрывают дальние
  • DOM-маркеры используют z-index слоя контейнера

Обновление позиции при анимации

При анимации карты (flyTo, easeTo) позиционирование маркеров обновляется на каждом кадре интерполяции:

map.flyTo({
  center: [37.6173, 55.7558],
  zoom: 10
});

Маркер не анимируется отдельно, а следует за изменением матрицы камеры.

Производительность при большом количестве маркеров

При работе с большим количеством точек важны следующие ограничения:

  • DOM-маркеры увеличивают нагрузку на layout engine
  • каждый маркер участвует в перерасчёте transform
  • рекомендуется минимизация сложных HTML-структур

Альтернативой является использование layer-based rendering (symbol layer), где точки рендерятся через WebGL без DOM.

Альтернативный подход: слой символов вместо Marker API

Для высокой плотности данных используется источник GeoJSON и слой:

map.addSource('points', {
  type: 'geojson',
  data: geojsonData
});

map.addLayer({
  id: 'points-layer',
  type: 'symbol',
  source: 'points',
  layout: {
    'icon-image': 'marker-15',
    'icon-size': 1
  }
});

В этом случае позиционирование происходит полностью на GPU, без DOM-узлов, с использованием тех же матричных преобразований камеры.