Загрузка изображений

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

Архитектура работы с изображениями построена вокруг трёх ключевых сущностей: стиль (style), спрайты (sprite) и динамически добавляемые изображения через API карты. Каждая из этих сущностей решает отдельную задачу и имеет собственные механизмы кэширования и обновления.


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

Спрайт в контексте MapLibre GL JS представляет собой объединённый атлас изображений и соответствующий JSON-индекс. Он подключается через свойства стиля:

{
  "sprite": "https://example.com/sprite"
}

При этом фактически загружаются два файла:

  • sprite.png — изображение-атлас
  • sprite.json — описание координат каждого элемента

Каждая иконка в слое symbol layer ссылается на имя в sprite.json, а не на отдельный файл.

Структура sprite.json

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

Ключевые поля:

  • x, y — координаты в атласе
  • width, height — размеры изображения
  • pixelRatio — коэффициент плотности экрана

Использование спрайтов снижает количество HTTP-запросов и ускоряет рендеринг символов на карте.


Динамическая загрузка изображений через API карты

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

loadImage и addImage

Основной поток загрузки пользовательского изображения выглядит так:

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

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

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

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

Особенности поведения addImage

При добавлении изображения важно учитывать:

  • имя изображения должно быть уникальным в рамках стиля
  • повторный вызов addImage с тем же именем требует удаления или переопределения
  • изображение хранится в памяти WebGL-контекста

Обновление и переопределение изображений

MapLibre GL JS позволяет динамически заменять изображения, что используется при изменении состояния интерфейса (например, активный/неактивный маркер).

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

map.addImage('custom-marker', newImage);

Такой подход позволяет реализовывать:

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

Использование изображений из URL и Blob

Загрузка изображений может происходить не только из статических файлов, но и из динамических источников.

Загрузка через Image объект

const img = new Image();
img.onl oad = () => {
  map.addImage('dynamic-icon', img);
};
img.src = 'https://example.com/icon.png';

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

fetch('/icon.png')
  .then(res => res.blob())
  .then(blob => {
    const url = URL.createObjectURL(blob);

    const img = new Image();
    img.onl oad = () => {
      map.addImage('blob-icon', img);
      URL.revokeObjectURL(url);
    };
    img.src = url;
  });

Blob-подход часто применяется при обработке изображений на лету или после трансформаций (например, canvas-рендеринг).


Работа с параметрами изображения

Метод addImage поддерживает дополнительные параметры, влияющие на поведение рендеринга:

map.addImage('icon', image, {
  pixelRatio: 2,
  sdf: false
});

pixelRatio

Позволяет задать плотность пикселей. Используется для:

  • Retina-дисплеев
  • сглаживания иконок
  • корректного масштабирования

sdf (Signed Distance Field)

Если sdf: true, изображение может быть перекрашено через свойства слоя:

paint: {
  'icon-color': '#ff0000'
}

Это особенно полезно для однотонных иконок.


Использование изображений в symbol layers

Изображения применяются преимущественно в слоях типа symbol:

map.addLayer({
  id: 'cities',
  type: 'symbol',
  source: 'cities',
  layout: {
    'icon-image': 'city-icon',
    'icon-size': 1.2,
    'icon-allow-overlap': true
  }
});

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

Возможен выбор изображения на основе свойства данных:

layout: {
  'icon-image': ['get', 'iconName']
}

Каждый объект источника должен содержать поле iconName, соответствующее зарегистрированному изображению.


Кэширование изображений и жизненный цикл

После добавления изображение становится частью style-ресурсов и хранится в WebGL памяти. Важные особенности:

  • изображения не перезагружаются при каждом рендере
  • style reset может очищать изображения
  • при смене стиля требуется повторная регистрация addImage

Типичный сценарий восстановления:

map.on('styledata', () => {
  map.addImage('custom-marker', image);
});

Асинхронная загрузка и обработка ошибок

Загрузка изображений является асинхронной операцией и требует обработки ошибок:

map.loadImage('/icon.png', (error, image) => {
  if (error) {
    return;
  }

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

Ошибки могут возникать из-за:

  • CORS-ограничений
  • отсутствующего ресурса
  • неподдерживаемого формата изображения

Оптимизация изображений для WebGL

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

  • предпочтительно использовать PNG для прозрачности
  • избегать чрезмерно больших текстур (ограничения WebGL)
  • использовать спрайты для массовых наборов иконок
  • применять SDF для масштабируемых символов

WebGL имеет ограничения по максимальному размеру текстуры, которые зависят от устройства (обычно 2048–8192 пикселей).


Переключение изображений в зависимости от масштаба

Изображения могут меняться в зависимости от zoom-уровня:

layout: {
  'icon-image': [
    'step',
    ['zoom'],
    'small-icon',
    10,
    'large-icon'
  ]
}

Это позволяет:

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

Комбинирование изображений и слоёв

Изображения редко используются изолированно. Они комбинируются с:

  • text layers (подписи)
  • circle layers (фоновые маркеры)
  • fill-extrusion (3D объекты с текстурами)

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

map.addLayer({
  id: 'poi',
  type: 'symbol',
  source: 'poi',
  layout: {
    'icon-image': 'poi-icon',
    'text-field': ['get', 'name'],
    'text-offset': [0, 1.2]
  }
});

Управление памятью изображений

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

  • удаление неиспользуемых изображений через removeImage
  • ограничение количества уникальных имён
  • повторное использование одинаковых текстур
map.removeImage('temporary-icon');

Удаление освобождает GPU-память и предотвращает деградацию производительности при длительной работе приложения.