Использование symbol layer вместо HTML

В Mapbox GL JS визуальные объекты на карте могут быть реализованы двумя принципиально разными подходами: через HTML-элементы (Marker, Popup и кастомные DOM-слои) либо через встроенные слои рендеринга WebGL. Одним из ключевых инструментов нативного отображения данных является symbol layer, который позволяет полностью отказаться от HTML-представления объектов и перенести их рендеринг в графический конвейер карты.

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

Проблемы DOM-меток:

  • высокая нагрузка на браузер при тысячах элементов;
  • частые reflow и repaint;
  • сложность синхронизации анимаций с картой;
  • ограниченная производительность при зуме и панорамировании.

Symbol layer работает иначе: все точки данных передаются в GPU через WebGL, где отрисовка происходит как часть единого слоя. Это исключает необходимость работы с DOM для каждого объекта.

Природа symbol layer

Symbol layer — это тип слоя в Mapbox GL JS, предназначенный для отображения иконок и текстовых меток на основе геометрии типа Point. Он использует заранее подготовленные спрайты или SDF-иконки, а также текстовые рендеры через шрифты, встроенные в стиль карты.

Основные возможности:

  • отображение иконок из sprite atlas;
  • рендеринг текста с использованием glyphs;
  • управление масштабируемостью и видимостью через zoom;
  • кластеризация и фильтрация на уровне слоя;
  • аппаратное ускорение через WebGL.

Базовая структура слоя

Symbol layer добавляется в стиль карты как часть слоёв:

map.addLayer({
  id: 'points',
  type: 'symbol',
  source: 'places',
  layout: {
    'icon-image': 'marker-15',
    'text-field': ['get', 'title'],
    'text-offset': [0, 1.2],
    'text-anchor': 'top'
  }
});

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

map.addSource('places', {
  type: 'geojson',
  data: {
    type: 'FeatureCollection',
    features: [
      {
        type: 'Feature',
        geometry: {
          type: 'Point',
          coordinates: [76.8897, 43.2389]
        },
        properties: {
          title: 'Объект A'
        }
      }
    ]
  }
});

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

Рендеринг и производительность

Symbol layer обрабатывается как часть WebGL сцены. Все объекты проходят через единый пайплайн:

  1. загрузка GeoJSON или vector tiles;
  2. преобразование координат в экранное пространство;
  3. батчинг символов;
  4. передача в GPU;
  5. отрисовка через shader program.

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

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

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

Иконки в symbol layer основаны на sprite sheet, который описывается в стиле карты:

map.addImage('custom-marker', imageData, {
  sdf: true
});

Использование в слое:

layout: {
  'icon-image': 'custom-marker',
  'icon-size': 1.2
}

Особенность SDF (Signed Distance Field) заключается в том, что иконки становятся масштабируемыми и могут менять цвет без перерисовки изображения.

Текстовые метки

Text rendering в symbol layer использует glyph atlas. Шрифты загружаются заранее:

map.addLayer({
  id: 'labels',
  type: 'symbol',
  source: 'places',
  layout: {
    'text-field': ['get', 'name'],
    'text-font': ['Open Sans Regular', 'Arial Unicode MS Regular'],
    'text-size': 14
  }
});

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

'text-field': ['concat', ['get', 'city'], ', ', ['get', 'country']]

Управление отображением через zoom

Одно из ключевых преимуществ symbol layer — управление видимостью без DOM-логики.

layout: {
  'icon-size': [
    'interpolate',
    ['linear'],
    ['zoom'],
    5, 0.5,
    15, 1.5
  ]
}

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

'text-opacity': [
  'step',
  ['zoom'],
  0,
  10,
  1
]

Это позволяет строить адаптивные карты без дополнительных вычислений на стороне JavaScript.

Фильтрация данных

Symbol layer поддерживает фильтры на уровне GPU-пайплайна:

filter: ['==', ['get', 'type'], 'restaurant']

Фильтрация выполняется до рендеринга, что делает её значительно быстрее, чем скрытие HTML-элементов через CSS или JS.

Кластеризация и оптимизация

При работе с большим количеством точек используется кластеризация:

map.addSource('points', {
  type: 'geojson',
  data: geojsonData,
  cluster: true,
  clusterMaxZoom: 14,
  clusterRadius: 50
});

Symbol layer затем отображает как кластеры, так и отдельные точки:

map.addLayer({
  id: 'clusters',
  type: 'symbol',
  source: 'points',
  filter: ['has', 'point_count'],
  layout: {
    'text-field': ['get', 'point_count'],
    'icon-image': 'cluster-icon'
  }
});

Ограничения symbol layer

Несмотря на высокую производительность, symbol layer имеет ряд ограничений:

  • невозможность произвольного HTML-рендеринга;
  • ограниченная интерактивность (нет событий внутри DOM);
  • зависимость от стиля карты и sprite/glyph ресурсов;
  • сложность реализации сложных UI-элементов (формы, кнопки, кастомные карточки).

В случаях, когда требуется сложная интерактивная визуализация, HTML-слои остаются единственным вариантом.

Сравнение подходов

Symbol layer обеспечивает:

  • масштабируемость до десятков тысяч объектов;
  • минимальную нагрузку на CPU;
  • синхронизацию с камерой карты без дополнительных расчётов;
  • единый рендер-пайплайн.

HTML-метки обеспечивают:

  • произвольный UI;
  • использование CSS и анимаций;
  • простую интеграцию с DOM-библиотеками.

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