В MapLibre GL JS изображения являются фундаментальным элементом визуального слоя карты: они используются для отображения иконок маркеров, символов POI, стрелок направлений, пользовательских меток, а также любых кастомных графических элементов. В отличие от растровых тайлов, изображения в стиле представляют собой отдельные ресурсы, которые загружаются один раз и затем многократно переиспользуются в слоях символов.
Архитектура работы с изображениями построена вокруг трёх ключевых сущностей: стиль (style), спрайты (sprite) и динамически добавляемые изображения через API карты. Каждая из этих сущностей решает отдельную задачу и имеет собственные механизмы кэширования и обновления.
Спрайт в контексте MapLibre GL JS представляет собой объединённый атлас изображений и соответствующий JSON-индекс. Он подключается через свойства стиля:
{
"sprite": "https://example.com/sprite"
}
При этом фактически загружаются два файла:
Каждая иконка в слое symbol layer ссылается на имя в sprite.json, а не на отдельный файл.
{
"airport": {
"x": 0,
"y": 0,
"width": 64,
"height": 64,
"pixelRatio": 1
}
}
Ключевые поля:
x, y — координаты в атласеwidth, height — размеры изображенияpixelRatio — коэффициент плотности экранаИспользование спрайтов снижает количество HTTP-запросов и ускоряет рендеринг символов на карте.
Помимо статических спрайтов, MapLibre GL JS поддерживает добавление изображений во время выполнения через методы карты.
Основной поток загрузки пользовательского изображения выглядит так:
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'
}
});
При добавлении изображения важно учитывать:
MapLibre GL JS позволяет динамически заменять изображения, что используется при изменении состояния интерфейса (например, активный/неактивный маркер).
if (map.hasImage('custom-marker')) {
map.removeImage('custom-marker');
}
map.addImage('custom-marker', newImage);
Такой подход позволяет реализовывать:
Загрузка изображений может происходить не только из статических файлов, но и из динамических источников.
const img = new Image();
img.onl oad = () => {
map.addImage('dynamic-icon', img);
};
img.src = 'https://example.com/icon.png';
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
});
Позволяет задать плотность пикселей. Используется для:
Если sdf: true, изображение может быть перекрашено через
свойства слоя:
paint: {
'icon-color': '#ff0000'
}
Это особенно полезно для однотонных иконок.
Изображения применяются преимущественно в слоях типа 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 памяти. Важные особенности:
Типичный сценарий восстановления:
map.on('styledata', () => {
map.addImage('custom-marker', image);
});
Загрузка изображений является асинхронной операцией и требует обработки ошибок:
map.loadImage('/icon.png', (error, image) => {
if (error) {
return;
}
map.addImage('icon', image);
});
Ошибки могут возникать из-за:
Производительность напрямую зависит от качества подготовки изображений:
WebGL имеет ограничения по максимальному размеру текстуры, которые зависят от устройства (обычно 2048–8192 пикселей).
Изображения могут меняться в зависимости от zoom-уровня:
layout: {
'icon-image': [
'step',
['zoom'],
'small-icon',
10,
'large-icon'
]
}
Это позволяет:
Изображения редко используются изолированно. Они комбинируются с:
Пример комбинированного слоя:
map.addLayer({
id: 'poi',
type: 'symbol',
source: 'poi',
layout: {
'icon-image': 'poi-icon',
'text-field': ['get', 'name'],
'text-offset': [0, 1.2]
}
});
При интенсивной работе с динамическими иконками важно учитывать утечки памяти:
map.removeImage('temporary-icon');
Удаление освобождает GPU-память и предотвращает деградацию производительности при длительной работе приложения.