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

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

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

Основные сценарии использования изображений:

  • иконки объектов на карте;
  • пользовательские маркеры;
  • изображения в слоях symbol;
  • шаблонные пиктограммы категорий объектов;
  • динамически создаваемые изображения;
  • спрайты стилей;
  • изображения высокого разрешения для Retina-дисплеев.

Архитектура хранения изображений

Перед использованием изображения внутри стиля оно должно быть зарегистрировано в экземпляре карты.

Общая схема работы выглядит следующим образом:

  1. Загрузка изображения.
  2. Добавление изображения в карту через API.
  3. Создание источника данных.
  4. Создание слоя типа symbol.
  5. Использование имени зарегистрированного изображения в свойстве 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

Метод 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 Загруженный объект изображения

Поддерживаются форматы:

  • PNG;
  • JPG;
  • JPEG;
  • WebP;
  • другие форматы, поддерживаемые браузером.

Регистрация изображения через addImage

После загрузки изображение должно быть добавлено в карту.

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

Наиболее распространённый сценарий — отображение пиктограмм в слоях типа 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 определяет используемое изображение.


Выражения в icon-image

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'

Преимущества спрайтов:

  • меньше HTTP-запросов;
  • быстрее загрузка;
  • лучше производительность;
  • централизованное управление ресурсами.

Генерация изображений через Canvas

Изображения необязательно хранить в файлах.

Можно создавать их программно.

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() изображение может изменяться.

Применения:

  • мигающие маркеры;
  • индикаторы движения;
  • визуализация датчиков;
  • отображение состояния оборудования.

Поддержка Retina-дисплеев

Высокая плотность пикселей требует специальных изображений.

Обычная иконка:

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);
    }

});

Типичные причины ошибок:

  • неверный URL;
  • отсутствие файла;
  • ограничения CORS;
  • повреждённое изображение;
  • неподдерживаемый формат.

Организация набора иконок

Для крупных проектов рекомендуется единая система именования.

Пример:

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
    }
});

В результате:

  • каждая категория получает собственную пиктограмму;
  • размеры автоматически меняются при масштабировании;
  • объекты отображаются без скрытия друг друга;
  • вся визуализация выполняется средствами графического движка MapLibre GL JS.