HTML5 Canvas предоставляет механизм программной генерации графики непосредственно в браузере. В сочетании с MapLibre GL JS это позволяет создавать динамические иконки, маркеры, тепловые карты, схемы, пиктограммы, визуализации данных и любые другие растровые изображения без необходимости хранить готовые файлы PNG или SVG.
Canvas особенно полезен в ситуациях, когда внешний вид объектов зависит от данных, полученных во время работы приложения. Вместо хранения сотен вариантов изображений можно формировать их программно непосредственно перед отображением на карте.
MapLibre использует изображения в различных сценариях:
Любое изображение перед использованием должно быть зарегистрировано в
стиле карты через метод addImage().
Общая схема выглядит следующим образом:
canvas -> ImageData -> map.addImage() -> слой карты
Canvas можно создать двумя способами:
<canvas id="iconCanvas" width="64" height="64"></canvas>
Получение контекста:
const canvas = document.getElementById('iconCanvas');
const ctx = canvas.getContext('2d');
Чаще используется именно этот вариант.
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);
MapLibre работает не с самим Canvas, а с массивом пикселей.
Для преобразования используется метод:
const imageData = ctx.getImageData(
0,
0,
canvas.width,
canvas.height
);
Результат содержит:
{
data: Uint8ClampedArray,
width: 64,
height: 64
}
Этот объект можно передать напрямую в карту.
После загрузки карты:
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
После регистрации иконка может использоваться в слое символов.
Источник данных:
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 — возможность формировать изображения математически.
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
);
Получается иконка с текстовым обозначением.
Подобный подход часто применяется для:
Без 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';
}
}
Далее создаётся изображение нужного цвета и используется для отображения объекта.
Подобный механизм часто применяется для мониторинга:
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 может объединять несколько источников графики.
Загрузка изображения:
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
}
Преимущества подхода:
Создание нового Canvas для каждого объекта может приводить к лишним затратам памяти.
Эффективнее использовать кэширование.
Пример:
const iconCache = new Map();
Проверка наличия:
if (iconCache.has(color)) {
return iconCache.get(color);
}
После генерации:
iconCache.set(
color,
generatedImage
);
Такой подход значительно ускоряет отображение больших наборов данных.
Если изображение уже зарегистрировано:
map.updateImage(
'circle-icon',
imageData
);
Это позволяет изменять внешний вид объектов без пересоздания слоя.
Типичные сценарии:
Canvas можно использовать как источник кадров анимации.
Пример периодического обновления:
setInterval(() => {
drawNextFrame();
const imageData =
ctx.getImageData(
0,
0,
64,
64
);
map.updateImage(
'animated-icon',
imageData
);
}, 50);
Таким образом создаются:
MapLibre поддерживает специальный интерфейс изображений, способный самостоятельно перерисовываться.
Пример структуры:
const animatedImage = {
width: 64,
height: 64,
data: new Uint8Array(
64 * 64 * 4
),
onAdd() {
},
render() {
return true;
}
};
Регистрация:
map.addImage(
'animated',
animatedImage
);
Метод render() вызывается регулярно, что позволяет
реализовывать сложную анимацию без ручных вызовов
updateImage().
Наиболее затратными операциями являются:
getImageData();Для оптимизации рекомендуется:
Цвет маркера зависит от скорости или состояния транспортного средства.
Canvas формирует индикаторы тревог, предупреждений и статусов оборудования.
Изображения отображают текущие показатели датчиков непосредственно на карте.
Количество элементов отображается внутри динамически генерируемых значков.
Программно создаются пиктограммы осадков, температуры, ветра и других параметров.
Внешний вид объектов изменяется в режиме реального времени в зависимости от телеметрии.
Canvas генерирует специализированные условные обозначения без использования внешних графических ресурсов.
Благодаря сочетанию Canvas и MapLibre GL JS становится возможным создание полностью динамической графической системы, в которой изображения формируются непосредственно на стороне клиента и мгновенно адаптируются к изменяющимся данным карты.