В веб-картографии изображения используются значительно шире, чем обычные маркеры. Они применяются для отображения пользовательских значков объектов, пиктограмм категорий, символов транспорта, логотипов организаций, изображений направлений движения, а также различных элементов интерфейса карты.
MapLibre GL JS позволяет загружать изображения во время работы приложения и добавлять их в стиль карты без необходимости заранее описывать их в файле стиля. Такой подход особенно важен для приложений, работающих с динамическими данными, когда набор используемых изображений заранее неизвестен.
Основными инструментами являются методы:
loadImage()addImage()updateImage()removeImage()hasImage()listImages()После регистрации изображения оно становится доступным внутри
выражений стиля и может использоваться в слоях типа
symbol.
Процесс состоит из нескольких этапов:
icon-image.Схема выглядит следующим образом:
map.loadImage('/images/restaurant.png', (error, image) => {
if (error) throw error;
map.addImage('restaurant-icon', image);
map.addLayer({
id: 'restaurants',
type: 'symbol',
source: 'places',
layout: {
'icon-image': 'restaurant-icon'
}
});
});
После выполнения кода изображение становится частью стиля карты и может использоваться любым количеством слоёв.
Метод loadImage() предназначен для загрузки изображения
по URL.
Сигнатура:
map.loadImage(url, callback);
Параметры:
| Параметр | Описание |
|---|---|
url |
Путь к изображению |
callback |
Функция обратного вызова |
Пример:
map.loadImage(
'https://example.com/icons/hotel.png',
(error, image) => {
if (error) {
console.error(error);
return;
}
map.addImage('hotel-icon', image);
}
);
Если загрузка завершилась успешно, объект изображения передаётся во второй аргумент callback-функции.
После загрузки изображение необходимо зарегистрировать в карте.
Простейший вариант:
map.addImage('hotel-icon', image);
Здесь:
hotel-icon — внутренний идентификатор;image — объект изображения.После регистрации идентификатор становится доступным в стилях.
Пример использования:
layout: {
'icon-image': 'hotel-icon'
}
Попытка повторно зарегистрировать изображение с тем же именем приводит к ошибке.
Для проверки используется метод hasImage().
if (!map.hasImage('hotel-icon')) {
map.addImage('hotel-icon', image);
}
Подобная проверка особенно полезна при работе с большим количеством динамически создаваемых слоёв.
Наиболее распространённый сценарий — отображение изображений вместо стандартных маркеров.
Источник данных:
map.addSource('places', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [37.6176, 55.7558]
}
}
]
}
});
Слой:
map.addLayer({
id: 'places-layer',
type: 'symbol',
source: 'places',
layout: {
'icon-image': 'hotel-icon',
'icon-size': 0.8
}
});
Каждый объект будет отображён с использованием загруженной пиктограммы.
Размер иконки регулируется свойством icon-size.
layout: {
'icon-image': 'hotel-icon',
'icon-size': 1.5
}
Уменьшение:
'icon-size': 0.5
Увеличение:
'icon-size': 2
Размер изменяется относительно исходных размеров изображения.
Одно из ключевых преимуществ MapLibre заключается в возможности выбирать изображение на основе данных объекта.
GeoJSON:
{
type: 'Feature',
properties: {
icon: 'restaurant-icon'
},
geometry: {
type: 'Point',
coordinates: [37.62, 55.75]
}
}
Слой:
map.addLayer({
id: 'places',
type: 'symbol',
source: 'places',
layout: {
'icon-image': ['get', 'icon']
}
});
В результате каждый объект может использовать собственную пиктограмму.
Часто требуется набор иконок для различных типов объектов.
const icons = [
{
id: 'hotel',
url: '/icons/hotel.png'
},
{
id: 'restaurant',
url: '/icons/restaurant.png'
},
{
id: 'museum',
url: '/icons/museum.png'
}
];
Загрузка:
icons.forEach(icon => {
map.loadImage(icon.url, (error, image) => {
if (error) return;
map.addImage(icon.id, image);
});
});
Использование:
layout: {
'icon-image': ['get', 'category']
}
Если значение поля category совпадает с идентификатором
изображения, нужная иконка будет выбрана автоматически.
Для сложной логики выбора изображения применяется выражение
match.
layout: {
'icon-image': [
'match',
['get', 'type'],
'hotel',
'hotel-icon',
'restaurant',
'restaurant-icon',
'museum',
'museum-icon',
'default-icon'
]
}
Преимущества такого подхода:
Иногда требуется заменить изображение без удаления слоя.
Для этого используется метод updateImage().
map.updateImage(
'vehicle-icon',
newImage
);
Пример применения:
После обновления все объекты, использующие данное изображение, автоматически перерисовываются.
Для освобождения ресурсов используется метод
removeImage().
map.removeImage('vehicle-icon');
Перед удалением желательно убедиться, что изображение больше не используется.
if (map.hasImage('vehicle-icon')) {
map.removeImage('vehicle-icon');
}
Удалённое изображение становится недоступным для слоёв.
Для диагностики и отладки предусмотрен метод
listImages().
const images = map.listImages();
console.log(images);
Результат:
[
'hotel-icon',
'restaurant-icon',
'museum-icon'
]
Метод помогает контролировать содержимое внутреннего реестра изображений.
Источник изображения не обязательно должен быть файлом.
MapLibre позволяет использовать содержимое элемента Canvas.
Создание холста:
const canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#ff0000';
ctx.beginPath();
ctx.arc(32, 32, 30, 0, Math.PI * 2);
ctx.fill();
Добавление изображения:
map.addImage(
'circle-icon',
{
width: 64,
height: 64,
data: ctx.getImageData(
0,
0,
64,
64
).data
}
);
Такой механизм позволяет генерировать пиктограммы программно.
Canvas часто используется для создания динамических значков.
Например, кругов разного цвета:
function createIcon(color) {
const canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
const ctx = canvas.getContext('2d');
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(32, 32, 30, 0, Math.PI * 2);
ctx.fill();
return {
width: 64,
height: 64,
data: ctx.getImageData(
0,
0,
64,
64
).data
};
}
Регистрация:
map.addImage(
'red-marker',
createIcon('#ff0000')
);
map.addImage(
'green-marker',
createIcon('#00ff00')
);
Преимущество подхода заключается в отсутствии необходимости хранить множество файлов изображений.
Иногда карта пытается отобразить изображение, которое ещё не зарегистрировано.
Для таких случаев существует событие
styleimagemissing.
map.on(
'styleimagemissing',
e => {
console.log(
'Не найдено изображение:',
e.id
);
}
);
Событие позволяет создавать изображения непосредственно в момент запроса.
Наиболее мощный сценарий использования styleimagemissing
— генерация иконок по требованию.
map.on(
'styleimagemissing',
e => {
const canvas =
document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
const ctx =
canvas.getContext('2d');
ctx.fillStyle = '#0099ff';
ctx.fillRect(
0,
0,
64,
64
);
map.addImage(
e.id,
{
width: 64,
height: 64,
data: ctx.getImageData(
0,
0,
64,
64
).data
}
);
}
);
Если слой запросит неизвестное изображение, оно будет создано автоматически.
Для экранов Retina рекомендуется использовать изображения большего разрешения.
Например:
icon.png 32×32
icon@2x.png 64×64
Регистрация:
map.addImage(
'icon',
image,
{
pixelRatio: 2
}
);
MapLibre корректно рассчитает масштаб и обеспечит более чёткое отображение на устройствах с высокой плотностью пикселей.
SDF (Signed Distance Field) позволяет изменять цвет изображения средствами стиля.
Добавление:
map.addImage(
'marker',
image,
{
sdf: true
}
);
После регистрации становятся доступны дополнительные свойства.
Например:
paint: {
'icon-color': '#ff0000'
}
Изменение цвета:
paint: {
'icon-color': '#00ff00'
}
Один SDF-файл может использоваться для отображения множества цветовых вариантов.
В крупных приложениях количество динамически загружаемых изображений может исчисляться сотнями или тысячами.
Практические рекомендации:
removeImage();hasImage();listImages().Грамотное управление изображениями снижает потребление памяти, ускоряет загрузку карты и уменьшает количество сетевых запросов.
Полный цикл работы обычно выглядит следующим образом:
hasImage().loadImage().addImage().icon-image.updateImage() при изменении
состояния.removeImage() после
удаления данных.Такой подход позволяет строить масштабируемые картографические приложения, в которых набор отображаемых изображений формируется динамически и полностью зависит от поступающих данных.