Работа со спрайтами

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

Спрайт в контексте MapLibre GL JS представляет собой набор изображений, упакованных в один графический файл (обычно PNG) и сопровождаемый JSON-описанием, содержащим координаты и размеры каждого элемента. Такой подход минимизирует количество HTTP-запросов и ускоряет рендеринг.

Типичная структура спрайта состоит из двух файлов:

  • sprite.png — атлас изображений
  • sprite.json — метаданные размещения

Фрагмент JSON-структуры:

{
  "airport": {
    "x": 0,
    "y": 0,
    "width": 32,
    "height": 32,
    "pixelRatio": 1
  },
  "rail": {
    "x": 32,
    "y": 0,
    "width": 32,
    "height": 32,
    "pixelRatio": 1
  }
}

Каждый ключ соответствует имени иконки, используемой в стиле карты через свойство icon-image.

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

Спрайт определяется на уровне стиля через параметр sprite. Это базовый URL без расширения, так как MapLibre автоматически запрашивает .png и .json.

{
  "version": 8,
  "sprite": "https://example.com/sprites/sprite",
  "sources": {},
  "layers": []
}

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

  • https://example.com/sprites/sprite.json
  • https://example.com/sprites/sprite.png

Использование спрайтов в слоях

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

{
  "id": "poi-layer",
  "type": "symbol",
  "source": "points",
  "layout": {
    "icon-image": "airport",
    "icon-size": 1
  }
}

Имя "airport" должно совпадать с ключом в sprite.json.

Масштабирование и retina-спрайты

Для устройств с высокой плотностью пикселей используются спрайты с коэффициентом @2x или pixelRatio: 2. MapLibre автоматически выбирает нужную версию в зависимости от devicePixelRatio.

Пример структуры:

  • sprite.png
  • sprite@2x.png
  • sprite.json
  • sprite@2x.json

В JSON каждая запись может содержать:

"airport": {
  "x": 0,
  "y": 0,
  "width": 32,
  "height": 32,
  "pixelRatio": 2
}

Генерация спрайтов

Спрайты обычно создаются сборщиками, которые объединяют набор SVG или PNG файлов в единый атлас.

Логика генерации включает:

  • упаковку изображений в плотный grid или bin-packing алгоритм
  • создание JSON с координатами
  • оптимизацию размеров
  • генерацию retina-версии

Важно учитывать, что порядок упаковки влияет на итоговый размер файла.

Работа со спрайтами в runtime

Помимо статического sprite.json, MapLibre GL JS позволяет добавлять изображения динамически через API:

map.on('load', () => {
  map.addImage('custom-marker', imageBitmap, {
    pixelRatio: 2
  });
});

После добавления изображение становится доступным как icon-image.

Удаление:

map.removeImage('custom-marker');

Проверка наличия:

map.hasImage('custom-marker');

Различие между спрайтом и addImage

Спрайт и addImage решают разные задачи:

  • спрайт — централизованный набор иконок для стиля
  • addImage — динамическая регистрация изображений в рантайме

Спрайт лучше подходит для:

  • фиксированных наборов иконок
  • масштабируемых дизайнов
  • кэширования

addImage используется для:

  • пользовательских маркеров
  • динамически загружаемых данных
  • runtime-генерации иконок

Использование SDF-иконок

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

Пример:

{
  "layout": {
    "icon-image": "airport",
    "icon-sdf": true
  },
  "paint": {
    "icon-color": "#ff0000",
    "icon-halo-color": "#000000",
    "icon-halo-width": 2
  }
}

SDF особенно эффективен для унифицированных иконок, требующих стилизации через CSS-подобные параметры.

Кэширование и производительность

Спрайты являются критически важным элементом оптимизации:

  • один HTTP-запрос вместо десятков
  • уменьшение задержек при загрузке карты
  • эффективное использование CDN
  • предсказуемое время рендеринга

При использовании CDN рекомендуется задавать долгий cache-control, так как изменение спрайта требует обновления хэша или версии URL.

Ограничения и особенности

Работа со спрайтами имеет ряд технических ограничений:

  • максимальный размер текстуры зависит от GPU (часто 2048×2048 или 4096×4096)
  • большое количество мелких иконок ухудшает packing efficiency
  • изменения спрайта после загрузки стиля требуют перезагрузки стиля
  • конфликт имён приводит к переопределению изображений

Динамическая замена спрайта

Хотя прямое обновление sprite URL возможно, оно требует переинициализации стиля:

map.setStyle({
  ...map.getStyle(),
  sprite: "https://example.com/new-sprite"
});

Это приводит к повторной загрузке ресурсов и пересборке слоёв.

Организация именования

Система именования элементов спрайта влияет на читаемость стилей:

  • poi_hospital
  • poi_school
  • transport_bus
  • transport_train

Иерархическая структура помогает группировать иконки логически, особенно в больших проектах.

Использование спрайтов в сложных стилях

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

{
  "icon-image": [
    "match",
    ["get", "type"],
    "hospital", "poi_hospital",
    "school", "poi_school",
    "default_marker"
  ]
}

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

Оптимизация размера спрайта

Практические подходы:

  • объединение схожих иконок в один набор
  • удаление неиспользуемых изображений
  • использование SVG на этапе сборки с последующей растеризацией
  • применение компрессии PNG (lossless optimization)

Чрезмерно большие спрайты увеличивают время парсинга JSON и загрузки текстуры.

Совместимость и спецификация

MapLibre GL JS следует спецификации Mapbox Style Specification, где sprite является стандартным полем стиля. Это обеспечивает совместимость с большинством инструментов генерации картографических стилей и библиотек визуализации.

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