Canvas для генерации изображений

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

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


Роль изображений в MapLibre GL JS

MapLibre использует изображения в различных сценариях:

  • иконки объектов слоя Symbol;
  • пользовательские маркеры;
  • пиктограммы кластеров;
  • изображения для паттернов заливки;
  • динамические обозначения состояний объектов;
  • анимационные элементы интерфейса карты.

Любое изображение перед использованием должно быть зарегистрировано в стиле карты через метод addImage().

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

canvas -> ImageData -> map.addImage() -> слой карты

Создание Canvas

Canvas можно создать двумя способами:

Через HTML

<canvas id="iconCanvas" width="64" height="64"></canvas>

Получение контекста:

const canvas = document.getElementById('iconCanvas');
const ctx = canvas.getContext('2d');

Полностью через JavaScript

Чаще используется именно этот вариант.

const canvas = document.createElement('canvas');

canvas.width = 64;
canvas.height = 64;

const ctx = canvas.getContext('2d');

После создания становится доступен объект рисования:

ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 64, 64);

Получение ImageData

MapLibre работает не с самим Canvas, а с массивом пикселей.

Для преобразования используется метод:

const imageData = ctx.getImageData(
    0,
    0,
    canvas.width,
    canvas.height
);

Результат содержит:

{
    data: Uint8ClampedArray,
    width: 64,
    height: 64
}

Этот объект можно передать напрямую в карту.


Добавление Canvas-изображения на карту

После загрузки карты:

map.on('load', () => {

    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, 28, 0, Math.PI * 2);
    ctx.fill();

    const imageData = ctx.getImageData(
        0,
        0,
        64,
        64
    );

    map.addImage('circle-icon', imageData);

});

Теперь изображение зарегистрировано под именем:

circle-icon

Использование изображения в Symbol Layer

После регистрации иконка может использоваться в слое символов.

Источник данных:

map.addSource('places', {
    type: 'geojson',
    data: {
        type: 'FeatureCollection',
        features: [
            {
                type: 'Feature',
                geometry: {
                    type: 'Point',
                    coordinates: [37.62, 55.75]
                }
            }
        ]
    }
});

Создание слоя:

map.addLayer({
    id: 'places-layer',
    type: 'symbol',
    source: 'places',
    layout: {
        'icon-image': 'circle-icon',
        'icon-size': 1
    }
});

Карта будет отображать объект с изображением, сгенерированным через Canvas.


Рисование фигур на Canvas

Одно из преимуществ Canvas — возможность формировать изображения математически.

Круг

ctx.beginPath();

ctx.arc(
    32,
    32,
    20,
    0,
    Math.PI * 2
);

ctx.fillStyle = '#3498db';
ctx.fill();

Прямоугольник

ctx.fillStyle = '#2ecc71';

ctx.fillRect(
    8,
    8,
    48,
    48
);

Линия

ctx.beginPath();

ctx.moveTo(10, 10);
ctx.lineTo(54, 54);

ctx.lineWidth = 4;
ctx.strokeStyle = '#000';

ctx.stroke();

Создание пользовательских пиктограмм

Canvas позволяет генерировать собственные иконки без графических редакторов.

Пример маркера в виде булавки:

ctx.fillStyle = '#e74c3c';

ctx.beginPath();

ctx.moveTo(32, 60);

ctx.quadraticCurveTo(
    0,
    25,
    32,
    4
);

ctx.quadraticCurveTo(
    64,
    25,
    32,
    60
);

ctx.fill();

Такие изображения можно создавать динамически для различных категорий объектов.


Добавление текста на изображение

Canvas поддерживает отрисовку текста.

ctx.fillStyle = 'white';

ctx.font = 'bold 24px Arial';

ctx.textAlign = 'center';
ctx.textBaseline = 'middle';

ctx.fillText(
    'A',
    32,
    32
);

Получается иконка с текстовым обозначением.

Подобный подход часто применяется для:

  • номеров маршрутов;
  • обозначения групп объектов;
  • категорий POI;
  • сокращённых названий.

Генерация цветовых вариантов

Без Canvas пришлось бы хранить множество файлов:

red.png
green.png
blue.png
yellow.png
...

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,
        28,
        0,
        Math.PI * 2
    );

    ctx.fill();

    return ctx.getImageData(
        0,
        0,
        64,
        64
    );
}

Регистрация:

map.addImage(
    'green-icon',
    createIcon('#2ecc71')
);

map.addImage(
    'blue-icon',
    createIcon('#3498db')
);

Генерация иконок по данным

Визуализация может зависеть от свойств объектов.

Например:

const status = feature.properties.status;

Формирование изображения:

function getColor(status) {

    switch (status) {

        case 'online':
            return '#2ecc71';

        case 'offline':
            return '#e74c3c';

        default:
            return '#95a5a6';
    }
}

Далее создаётся изображение нужного цвета и используется для отображения объекта.

Подобный механизм часто применяется для мониторинга:

  • транспорта;
  • датчиков;
  • серверов;
  • IoT-устройств;
  • производственных систем.

Использование градиентов

Canvas поддерживает линейные и радиальные градиенты.

Линейный градиент

const gradient =
    ctx.createLinearGradient(
        0,
        0,
        64,
        64
    );

gradient.addColorStop(
    0,
    '#3498db'
);

gradient.addColorStop(
    1,
    '#8e44ad'
);

ctx.fillStyle = gradient;

ctx.fillRect(
    0,
    0,
    64,
    64
);

Радиальный градиент

const gradient =
    ctx.createRadialGradient(
        32,
        32,
        5,
        32,
        32,
        30
    );

gradient.addColorStop(
    0,
    '#ffffff'
);

gradient.addColorStop(
    1,
    '#2980b9'
);

ctx.fillStyle = gradient;

ctx.fillRect(
    0,
    0,
    64,
    64
);

Градиенты позволяют создавать более выразительные иконки без внешних графических файлов.


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

Canvas может объединять несколько источников графики.

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

const image = new Image();

image.onl oad = () => {

    ctx.drawImage(
        image,
        0,
        0,
        64,
        64
    );

};

image.src = 'icon.png';

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

Например:

ctx.fillStyle = 'red';

ctx.beginPath();

ctx.arc(
    50,
    14,
    10,
    0,
    Math.PI * 2
);

ctx.fill();

Получается бейдж уведомления поверх основной иконки.


Создание кластерных иконок

Canvas особенно полезен при работе с кластерами.

Пример отображения количества объектов:

ctx.beginPath();

ctx.arc(
    32,
    32,
    28,
    0,
    Math.PI * 2
);

ctx.fillStyle = '#f39c12';
ctx.fill();

Добавление текста:

ctx.fillStyle = '#fff';

ctx.font = 'bold 20px sans-serif';

ctx.textAlign = 'center';

ctx.fillText(
    String(count),
    32,
    39
);

В результате каждый кластер может иметь уникальное изображение.


Использование прозрачности

Canvas поддерживает альфа-канал.

Прозрачная заливка:

ctx.fillStyle =
    'rgba(52,152,219,0.5)';

ctx.fillRect(
    0,
    0,
    64,
    64
);

Прозрачность применяется для:

  • зон покрытия;
  • тепловых эффектов;
  • визуальных подсказок;
  • временных состояний объектов.

Масштабирование изображений

Размер Canvas определяет фактическое разрешение изображения.

Например:

canvas.width = 128;
canvas.height = 128;

Отображение на карте:

layout: {
    'icon-image': 'my-icon',
    'icon-size': 0.5
}

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

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

Повторное использование Canvas

Создание нового Canvas для каждого объекта может приводить к лишним затратам памяти.

Эффективнее использовать кэширование.

Пример:

const iconCache = new Map();

Проверка наличия:

if (iconCache.has(color)) {
    return iconCache.get(color);
}

После генерации:

iconCache.set(
    color,
    generatedImage
);

Такой подход значительно ускоряет отображение больших наборов данных.


Обновление изображения через updateImage

Если изображение уже зарегистрировано:

map.updateImage(
    'circle-icon',
    imageData
);

Это позволяет изменять внешний вид объектов без пересоздания слоя.

Типичные сценарии:

  • изменение состояния оборудования;
  • обновление индикаторов;
  • отображение новых показателей;
  • анимация.

Анимированные изображения

Canvas можно использовать как источник кадров анимации.

Пример периодического обновления:

setInterval(() => {

    drawNextFrame();

    const imageData =
        ctx.getImageData(
            0,
            0,
            64,
            64
        );

    map.updateImage(
        'animated-icon',
        imageData
    );

}, 50);

Таким образом создаются:

  • мигающие маркеры;
  • индикаторы активности;
  • вращающиеся значки;
  • динамические схемы.

Использование объекта StyleImageInterface

MapLibre поддерживает специальный интерфейс изображений, способный самостоятельно перерисовываться.

Пример структуры:

const animatedImage = {

    width: 64,
    height: 64,

    data: new Uint8Array(
        64 * 64 * 4
    ),

    onAdd() {

    },

    render() {

        return true;
    }
};

Регистрация:

map.addImage(
    'animated',
    animatedImage
);

Метод render() вызывается регулярно, что позволяет реализовывать сложную анимацию без ручных вызовов updateImage().


Производительность при работе с Canvas

Наиболее затратными операциями являются:

  • частое создание новых Canvas;
  • многократные вызовы getImageData();
  • генерация больших изображений;
  • обновление сотен иконок одновременно.

Для оптимизации рекомендуется:

  • повторно использовать Canvas;
  • кэшировать результаты генерации;
  • уменьшать размеры изображений;
  • обновлять только изменившиеся иконки;
  • избегать лишних операций чтения пикселей.

Практические сценарии применения

Мониторинг транспорта

Цвет маркера зависит от скорости или состояния транспортного средства.

Системы диспетчеризации

Canvas формирует индикаторы тревог, предупреждений и статусов оборудования.

Геоинформационные панели

Изображения отображают текущие показатели датчиков непосредственно на карте.

Кластеризация объектов

Количество элементов отображается внутри динамически генерируемых значков.

Визуализация погодных данных

Программно создаются пиктограммы осадков, температуры, ветра и других параметров.

Карты промышленного мониторинга

Внешний вид объектов изменяется в режиме реального времени в зависимости от телеметрии.

Интерактивные схемы

Canvas генерирует специализированные условные обозначения без использования внешних графических ресурсов.

Благодаря сочетанию Canvas и MapLibre GL JS становится возможным создание полностью динамической графической системы, в которой изображения формируются непосредственно на стороне клиента и мгновенно адаптируются к изменяющимся данным карты.