Изображения являются важной частью визуализации картографических данных. В MapLibre GL JS они используются для отображения пользовательских маркеров, пиктограмм объектов, символов в слоях, шаблонных обозначений точек интереса, а также для создания сложных интерфейсных элементов поверх карты.
В отличие от традиционных HTML-маркеров, изображения, встроенные в стиль карты, обрабатываются графическим движком WebGL. Это обеспечивает высокую производительность даже при отображении тысяч объектов одновременно.
Основные сценарии использования изображений:
symbol;Перед использованием изображения внутри стиля оно должно быть зарегистрировано в экземпляре карты.
Общая схема работы выглядит следующим образом:
symbol.icon-image.map.loadImage('/images/shop.png', (error, image) => {
if (error) {
throw error;
}
map.addImage('shop-icon', image);
map.addSource('shops', {
type: 'geojson',
data: '/data/shops.geojson'
});
map.addLayer({
id: 'shops-layer',
type: 'symbol',
source: 'shops',
layout: {
'icon-image': 'shop-icon'
}
});
});
После регистрации изображение становится частью графических ресурсов карты и может использоваться в любом количестве слоёв.
Метод loadImage() предназначен для загрузки изображений
по URL.
Синтаксис:
map.loadImage(url, callback);
Пример:
map.loadImage('/assets/restaurant.png', (error, image) => {
if (error) {
console.error(error);
return;
}
map.addImage('restaurant', image);
});
Параметры:
| Параметр | Описание |
|---|---|
| url | Путь к изображению |
| error | Ошибка загрузки |
| image | Загруженный объект изображения |
Поддерживаются форматы:
После загрузки изображение должно быть добавлено в карту.
map.addImage('hospital', image);
Первый аргумент — уникальное имя изображения.
map.addImage('park', image);
map.addImage('museum', image);
map.addImage('school', image);
Позже эти идентификаторы используются в стилях.
layout: {
'icon-image': 'museum'
}
Для предотвращения ошибок удобно проверять наличие зарегистрированного изображения.
if (!map.hasImage('bus-stop')) {
map.addImage('bus-stop', image);
}
Метод:
map.hasImage(name);
Возвращает:
true
или
false
Иногда требуется доступ к уже зарегистрированному ресурсу.
const image = map.getImage('museum');
Это может использоваться при динамическом обновлении интерфейса.
Неиспользуемые изображения можно удалять из памяти.
map.removeImage('temporary-icon');
Перед удалением желательно убедиться, что изображение не используется слоями.
if (map.hasImage('temporary-icon')) {
map.removeImage('temporary-icon');
}
Наиболее распространённый сценарий — отображение пиктограмм в слоях
типа symbol.
Источник данных:
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': 'museum'
}
});
Каждый объект будет отображаться с соответствующей иконкой.
Размер изображения регулируется свойством icon-size.
layout: {
'icon-image': 'museum',
'icon-size': 0.5
}
Увеличение:
layout: {
'icon-image': 'museum',
'icon-size': 2
}
Размер задаётся как коэффициент относительно исходного изображения.
Примеры:
| Значение | Результат |
|---|---|
| 0.5 | уменьшение вдвое |
| 1 | оригинальный размер |
| 2 | увеличение в два раза |
| 3 | увеличение в три раза |
Размер иконок часто зависит от масштаба карты.
layout: {
'icon-image': 'museum',
'icon-size': [
'interpolate',
['linear'],
['zoom'],
5, 0.5,
10, 1,
15, 2
]
}
При увеличении масштаба изображения становятся больше.
Часто разные объекты должны иметь разные пиктограммы.
GeoJSON:
{
"type": "Feature",
"properties": {
"icon": "hospital"
},
"geometry": {
"type": "Point",
"coordinates": [37.6, 55.7]
}
}
Слой:
map.addLayer({
id: 'pois',
type: 'symbol',
source: 'pois-source',
layout: {
'icon-image': ['get', 'icon']
}
});
Теперь значение свойства icon определяет используемое
изображение.
MapLibre GL JS позволяет выбирать изображения через выражения.
Пример с категоризацией:
layout: {
'icon-image': [
'match',
['get', 'type'],
'restaurant', 'restaurant-icon',
'hotel', 'hotel-icon',
'museum', 'museum-icon',
'default-icon'
]
}
Система автоматически выбирает нужную пиктограмму.
Слой symbol способен одновременно отображать текст и
иконку.
map.addLayer({
id: 'cities',
type: 'symbol',
source: 'cities',
layout: {
'icon-image': 'city-icon',
'text-field': ['get', 'name']
}
});
Результат:
Настройка положения:
layout: {
'icon-image': 'city-icon',
'text-field': ['get', 'name'],
'text-offset': [0, 1.5]
}
Для изменения положения используется свойство
icon-offset.
layout: {
'icon-image': 'marker',
'icon-offset': [0, -10]
}
Пример:
layout: {
'icon-image': 'marker',
'icon-offset': [15, 0]
}
Первое значение отвечает за горизонтальное смещение, второе — за вертикальное.
Иконки могут вращаться.
layout: {
'icon-image': 'arrow',
'icon-rotate': 90
}
Динамический вариант:
layout: {
'icon-image': 'arrow',
'icon-rotate': ['get', 'bearing']
}
Полезно для:
Свойство:
'icon-rotation-alignment'
Варианты:
'map'
или
'viewport'
Пример:
layout: {
'icon-image': 'arrow',
'icon-rotation-alignment': 'map'
}
В этом режиме иконка вращается вместе с картой.
По умолчанию движок старается избегать наложения символов.
layout: {
'icon-image': 'poi'
}
Для принудительного отображения:
layout: {
'icon-image': 'poi',
'icon-allow-overlap': true
}
Для плотного размещения большого количества объектов это часто необходимо.
Свойство:
'icon-ignore-placement'
Пример:
layout: {
'icon-image': 'camera',
'icon-ignore-placement': true
}
Изображение будет отображаться независимо от соседних объектов.
Спрайт представляет собой большой атлас изображений.
Вместо загрузки десятков отдельных файлов используется один набор ресурсов.
Структура:
sprite.png
sprite.json
JSON содержит координаты каждого изображения внутри атласа.
Пример записи:
{
"museum": {
"x": 0,
"y": 0,
"width": 32,
"height": 32
}
}
После подключения стиля изображения доступны по именам:
'icon-image': 'museum'
Преимущества спрайтов:
Изображения необязательно хранить в файлах.
Можно создавать их программно.
const canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.arc(32, 32, 25, 0, Math.PI * 2);
ctx.fill();
Полученное изображение:
const imageData = ctx.getImageData(
0,
0,
64,
64
);
map.addImage('generated-circle', imageData);
После регистрации оно используется как обычная иконка.
MapLibre GL JS позволяет обновлять содержимое изображения в реальном времени.
Простейшая схема:
const image = {
width: 64,
height: 64,
data: new Uint8Array(64 * 64 * 4),
onAdd() {},
render() {
return true;
}
};
Регистрация:
map.addImage(
'animated-icon',
image,
{ pixelRatio: 2 }
);
При каждом вызове render() изображение может
изменяться.
Применения:
Высокая плотность пикселей требует специальных изображений.
Обычная иконка:
32 × 32
Retina-вариант:
64 × 64
Регистрация:
map.addImage(
'retina-icon',
image,
{
pixelRatio: 2
}
);
MapLibre корректно масштабирует изображение и обеспечивает высокую чёткость.
Если ресурс уже существует, его можно обновить.
map.updateImage(
'vehicle',
newImage
);
Такой подход применяется для:
Надёжный вариант загрузки изображения:
map.loadImage('/icons/shop.png', (error, image) => {
if (error) {
console.error(
'Ошибка загрузки изображения',
error
);
return;
}
if (!map.hasImage('shop')) {
map.addImage('shop', image);
}
});
Типичные причины ошибок:
Для крупных проектов рекомендуется единая система именования.
Пример:
poi-hospital
poi-school
poi-bank
poi-hotel
transport-bus
transport-train
transport-plane
service-parking
service-fuel
service-carwash
Преимущества такого подхода:
Регистрация изображений:
map.addImage('restaurant', restaurantImage);
map.addImage('hotel', hotelImage);
map.addImage('museum', museumImage);
Слой:
map.addLayer({
id: 'places',
type: 'symbol',
source: 'places',
layout: {
'icon-image': [
'match',
['get', 'category'],
'restaurant',
'restaurant',
'hotel',
'hotel',
'museum',
'museum',
'restaurant'
],
'icon-size': [
'interpolate',
['linear'],
['zoom'],
5, 0.5,
10, 1,
15, 1.5
],
'icon-allow-overlap': true
}
});
В результате: