В MapLibre GL JS визуализация карты строится на WebGL-контексте, где итоговый кадр всегда доступен через HTMLCanvasElement, связанный с экземпляром карты. Основной точкой доступа выступает метод получения canvas:
const canvas = map.getCanvas();
Полученный canvas содержит текущий отрендеренный кадр карты, включая стили, тайлы, слои и все визуальные эффекты, сформированные на момент последнего рендера.
Ключевое свойство: изображение не является статическим артефактом, а представляет собой результат WebGL-пайплайна, поэтому экспорт всегда зависит от состояния рендеринга.
Самый прямой способ получения изображения — преобразование canvas в DataURL:
const canvas = map.getCanvas();
const dataURL = canvas.toDataURL('image/png');
Полученный dataURL можно использовать для:
<img>Пример скачивания:
function downloadMapImage(map) {
const canvas = map.getCanvas();
const url = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.href = url;
link.download = 'map.png';
link.click();
}
Ограничение этого подхода связано с тем, что результат фиксируется в текущем размере canvas, без контроля качества и без возможности асинхронного ожидания завершения рендера.
Метод toBlob предпочтительнее toDataURL,
так как не создаёт громоздкую base64-строку:
map.getCanvas().toBlob((blob) => {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'map.png';
link.click();
URL.revokeObjectURL(url);
}, 'image/png');
Преимущества:
Экспорт изображения напрямую после вызова изменения карты приводит к неполному результату. MapLibre GL JS рендерит асинхронно, поэтому необходимо дождаться завершения кадра.
Используются события:
idle — карта полностью завершила рендерrender — происходит отрисовкаload — завершена загрузка стиляПример корректного ожидания:
map.once('idle', () => {
const canvas = map.getCanvas();
const url = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.href = url;
link.download = 'map.png';
link.click();
});
Событие idle является наиболее надёжным индикатором
готовности изображения.
WebGL по умолчанию очищает буфер после отрисовки кадра, что может привести к пустому изображению при экспорте. Для корректного сохранения изображения используется параметр инициализации карты:
const map = new maplibregl.Map({
container: 'map',
style: 'https://demotiles.maplibre.org/style.json',
preserveDrawingBuffer: true
});
Этот флаг заставляет WebGL сохранять содержимое буфера между кадрами, что делает возможным стабильный экспорт.
Недостатки:
Для получения изображения с повышенной детализацией используется масштабирование canvas через pixel ratio.
function exportHighRes(map, scale = 2) {
const canvas = map.getCanvas();
const width = canvas.width;
const height = canvas.height;
const exportCanvas = document.createElement('canvas');
exportCanvas.width = width * scale;
exportCanvas.height = height * scale;
const ctx = exportCanvas.getContext('2d');
ctx.drawImage(canvas, 0, 0, exportCanvas.width, exportCanvas.height);
exportCanvas.toBlob((blob) => {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'map@2x.png';
link.click();
URL.revokeObjectURL(url);
});
}
Этот метод не увеличивает реальную детализацию данных карты, но улучшает плотность пикселей для печати и экранов Retina.
При динамическом изменении стиля или слоёв требуется принудительно дождаться стабилизации рендера:
function exportAfterUpdate(map) {
map.triggerRepaint();
map.once('render', () => {
map.once('idle', () => {
const canvas = map.getCanvas();
const url = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.href = url;
link.download = 'map.png';
link.click();
});
});
}
Важно учитывать, что render не гарантирует завершённость
данных, тогда как idle обеспечивает завершение всех
асинхронных операций.
При экспорте карты часто возникает ошибка безопасности canvas:
Tainted canvas cannot be exported
Причина — загрузка ресурсов (тайлы, спрайты, шрифты) с серверов без корректных CORS-заголовков.
Требования:
Access-Control-Allow-Originconst map = new maplibregl.Map({
container: 'map',
style: style,
crossOrigin: 'anonymous'
});
Если хотя бы один ресурс не поддерживает CORS, экспорт становится невозможным.
Иногда требуется экспортировать карту в размере, отличном от отображаемого на экране.
Подход заключается в временном изменении размера контейнера:
function exportWithSize(map, width, height) {
const container = map.getContainer();
container.style.width = width + 'px';
container.style.height = height + 'px';
map.resize();
map.once('idle', () => {
const canvas = map.getCanvas();
const url = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.href = url;
link.download = 'map.png';
link.click();
});
}
После экспорта обычно требуется вернуть исходные размеры и снова
вызвать resize().
В высоконагруженных системах экспорт выполняется через перенос рендера в OffscreenCanvas (при поддержке окружения):
const offscreen = new OffscreenCanvas(1024, 768);
const gl = offscreen.getContext('webgl');
Однако MapLibre GL JS напрямую не управляет OffscreenCanvas, поэтому такой подход требует кастомной интеграции WebGL-контекста и не является стандартным сценарием.
При необходимости экспорта больших областей карты применяется разбиение на тайлы с последующей склейкой:
map.setCenterПример логики:
const images = [];
async function captureStep(center) {
map.setCenter(center);
await new Promise(resolve => map.once('idle', resolve));
const canvas = map.getCanvas();
images.push(canvas.toDataURL());
}
Дальнейшая сборка выполняется через отдельный canvas-композитинг.
Экспорт изображения напрямую связан с архитектурой рендеринга:
Это означает, что любое вмешательство в момент загрузки приводит к неполным или артефактным изображениям.
idleДля предсказуемого результата применяются фиксированные настройки:
map.setBearing(0);
map.setPitch(0);
map.setZoom(10);
После стабилизации состояния карта становится детерминированной для рендеринга изображения.