Работа с иконками

Принцип работы иконок в стиле карты

В Mapbox GL JS иконки не существуют как отдельные DOM-элементы. Все графические элементы рендерятся внутри WebGL-контекста и являются частью стиля карты. Иконки подключаются через систему спрайтов или загружаются как отдельные изображения, после чего используются в слоях типа symbol.

Ключевая особенность подхода заключается в том, что иконка — это ресурс стиля, а не UI-объект. Она попадает в общий атлас (sprite) или регистрируется вручную через API карты, после чего становится доступной по имени.


Спрайты и система именованных изображений

Стандартный способ хранения иконок в Mapbox GL JS — sprite sheet. Спрайт представляет собой два файла:

  • PNG (или WebP) с набором иконок
  • JSON с метаданными (координаты и размеры каждой иконки)

Стиль карты указывает базовый URL:

{
  "sprite": "mapbox://sprites/your-style-id"
}

Каждая иконка получает уникальное имя, например:

  • restaurant-15
  • airport-11
  • marker-blue

Далее эти имена используются в слоях:

map.addLayer({
  id: 'points',
  type: 'symbol',
  source: 'places',
  layout: {
    'icon-image': 'restaurant-15'
  }
});

Добавление кастомных иконок через addImage

Помимо спрайтов, Mapbox GL JS позволяет динамически добавлять изображения в стиль карты.

map.on('load', () => {
  map.loadImage('/icons/custom-marker.png', (error, image) => {
    if (error) throw error;

    map.addImage('custom-marker', image);
  });
});

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

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

Важный момент: имя изображения должно быть уникальным в пределах стиля. Повторная регистрация без удаления приведёт к ошибке.


Управление жизненным циклом иконок

Иконки могут добавляться и удаляться динамически:

if (map.hasImage('custom-marker')) {
  map.removeImage('custom-marker');
}

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

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

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


Использование SDF-иконок и стилизация через слой

SVG-подобная гибкость достигается через SDF (Signed Distance Field). Такие иконки позволяют менять цвет и прозрачность без повторной загрузки изображения.

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

map.addImage('pin-sdf', image, { sdf: true });

После этого иконка становится стилизуемой через icon-color:

layout: {
  'icon-image': 'pin-sdf'
},
paint: {
  'icon-color': '#ff0000'
}

SDF особенно полезен для:

  • категорийных маркеров
  • динамической перекраски точек
  • адаптации под темы (dark/light mode)

Масштабирование и адаптация под зум

Иконки часто должны изменять размер в зависимости от масштаба карты. Для этого используется свойство icon-size.

Простейший вариант:

'icon-size': 1.2

Более гибкий подход — выражения:

'icon-size': [
  'interpolate',
  ['linear'],
  ['zoom'],
  5, 0.5,
  10, 1,
  15, 1.5
]

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


Позиционирование и привязка иконок

Иконка не просто рисуется в точке координат — она имеет точку привязки.

Основные параметры:

layout: {
  'icon-anchor': 'bottom',
  'icon-offset': [0, -10]
}

icon-anchor задаёт точку якоря:

  • center
  • top
  • bottom
  • left
  • right
  • комбинации вроде top-left

icon-offset позволяет тонко смещать позицию относительно якоря.

Это критично при работе с:

  • маркерами с тенью
  • кастомными пинами
  • плотной кластеризацией объектов

Контроль наложения и поведение при коллизиях

При большом количестве объектов иконки начинают конкурировать за пространство. Mapbox GL JS предоставляет инструменты управления коллизиями:

layout: {
  'icon-allow-overlap': false,
  'icon-ignore-placement': false
}

Если включить icon-allow-overlap, иконки могут перекрывать друг друга. В противном случае движок будет скрывать менее приоритетные элементы.

Также важно свойство:

'icon-optional': true

Оно позволяет отображать текст даже без иконки, если она не прошла проверку коллизий.


Комбинация иконок и текста

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

layout: {
  'icon-image': 'restaurant-15',
  'text-field': ['get', 'name'],
  'text-offset': [0, 1.2],
  'text-anchor': 'top'
}

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


Динамическая подмена иконок через выражения

Mapbox GL JS позволяет изменять иконки в зависимости от данных:

'icon-image': [
  'match',
  ['get', 'type'],
  'restaurant', 'restaurant-15',
  'airport', 'airport-15',
  'default-marker'
]

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


Производительность и оптимизация ресурсов

Иконки напрямую влияют на производительность WebGL-рендеринга. Основные принципы оптимизации:

  • уменьшение количества уникальных изображений
  • использование sprite вместо множества отдельных файлов
  • применение SDF для снижения вариативности ресурсов
  • избегание частого вызова addImage в рантайме

Особенно критично количество draw calls: каждый уникальный набор изображений увеличивает нагрузку на GPU.


Работа с загрузкой изображений

Метод loadImage асинхронен и требует обработки ошибок:

map.loadImage(url, (error, image) => {
  if (error) return;

  map.addImage('icon-name', image);
});

При работе с внешними источниками важно учитывать:

  • CORS-политику
  • кеширование браузера
  • задержки сети при первом рендере

Иконки в стиле и редактирование через Mapbox Studio

Визуальные стили часто формируются заранее в редакторе стиля, где иконки добавляются в sprite. Однако даже после публикации стиля они могут быть переопределены на клиенте через API.

Это создаёт двухуровневую систему:

  • статические иконки (в стиле)
  • динамические иконки (runtime через JS)

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


Поведение иконок при трансформациях карты

При вращении и наклоне карты иконки могут сохранять ориентацию или поворачиваться вместе с картой:

'icon-rotation-alignment': 'map'

или

'icon-rotation-alignment': 'viewport'

Первый вариант фиксирует ориентацию относительно карты, второй — относительно экрана.

Это особенно важно для:

  • навигационных указателей
  • направленных маркеров маршрута
  • 3D-режимов наклона

Типичные архитектурные ошибки при работе с иконками

Часто встречаются следующие проблемы:

  • загрузка иконок вне load события карты, что приводит к ошибкам стиля
  • дублирование имен изображений в addImage
  • использование слишком больших PNG без оптимизации
  • отсутствие SDF при необходимости динамической перекраски
  • перегрузка слоя десятками уникальных иконок вместо категорийного подхода

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


Внутреннее устройство icon-image в слое symbol

Параметр icon-image не хранит само изображение. Он лишь ссылается на ключ в sprite или registry изображений карты. При рендере WebGL получает уже подготовленный атлас текстур, где каждая иконка — это UV-координаты внутри общей текстуры.

Такой подход обеспечивает:

  • минимальное количество текстурных переключений
  • стабильную производительность при большом количестве объектов
  • возможность батчинга отрисовки