При подготовке картографического интерфейса к печати в веб-приложениях на базе MapLibre GL JS ключевой задачей становится преобразование интерактивного WebGL-рендера в статичное, воспроизводимое изображение, пригодное для печатного вывода. В отличие от DOM-ориентированных интерфейсов, карта в MapLibre рендерится через WebGL-canvas, что накладывает ограничения на стандартные механизмы печати браузера.
Базовая проблема заключается в том, что window.print()
фиксирует текущее состояние страницы, но не гарантирует корректный
захват WebGL-контента, асинхронно загружаемых тайлов и динамических
слоёв. Поэтому подготовка карты к печати требует отдельного режима
отображения, в котором контролируется размер, стиль, загрузка ресурсов и
отключение интерактивных элементов.
Основные задачи подготовки:
Перед печатью необходимо гарантировать, что карта полностью загружена
и не находится в процессе рендеринга. В MapLibre GL JS это
контролируется через события idle, render,
data и load.
Типовой подход — ожидание события idle, которое
сигнализирует об отсутствии активных изменений:
map.once('idle', () => {
// карта полностью стабилизирована
});
Дополнительно проверяется загрузка тайлов:
function waitForMapReady(map) {
return new Promise(resolve => {
if (map.loaded()) {
resolve();
return;
}
map.once('idle', () => resolve());
});
}
В сложных случаях (много слоёв, векторные тайлы, растровые источники)
применяется двойная проверка: map.loaded() и
idle.
Печатная версия карты не должна содержать элементов управления, анимаций и интерактивных эффектов. Типичный набор отключаемых компонентов:
Пример подготовки:
map.dragPan.disable();
map.scrollZoom.disable();
map.boxZoom.disable();
map.keyboard.disable();
map.doubleClickZoom.disable();
map.touchZoomRotate.disable();
Для визуальных переходов:
map.setPitch(0);
map.setBearing(0);
И отключение анимации при изменении стиля:
map.setLayoutProperty('layer-id', 'visibility', 'visible');
Для корректного вывода важно использовать @media print,
скрывая интерфейс и фиксируя размеры контейнера:
@media print {
body {
margin: 0;
}
.map-container {
width: 210mm;
height: 297mm;
}
.maplibregl-control-container {
display: none;
}
.sidebar, .toolbar {
display: none;
}
}
Особое внимание уделяется контейнеру карты: если его размер не совпадает с печатным листом, происходит пересчёт viewport и потеря точности масштаба.
Перед вызовом печати необходимо синхронизировать размеры canvas с печатным форматом:
function resizeForPrint(map) {
const container = map.getContainer();
container.style.width = '210mm';
container.style.height = '297mm';
map.resize();
}
Метод map.resize() в MapLibre GL JS пересчитывает WebGL
viewport, что критично для сохранения геометрической точности.
Альтернативный подход к печати — извлечение изображения напрямую из WebGL canvas. Это позволяет обойти ограничения браузерной печати.
function exportMapImage(map) {
const canvas = map.getCanvas();
return canvas.toDataURL('image/png');
}
Для последующего вывода:
const img = new Image();
img.src = exportMapImage(map);
img.onl oad = () => {
const w = window.open('');
w.document.write(img.outerHTML);
w.print();
};
Этот метод фиксирует текущее состояние карты, но не подходит для векторного вывода высокого качества.
Для печати требуется более высокая плотность пикселей, чем в
браузере. Решение — масштабирование canvas через
devicePixelRatio:
function setHighDPI(map, ratio = 2) {
const canvas = map.getCanvas();
const width = canvas.clientWidth;
const height = canvas.clientHeight;
canvas.width = width * ratio;
canvas.height = height * ratio;
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
map.resize();
}
Повышение DPI особенно важно при печати карт с тонкими линиями (границы регионов, маршруты, изолинии).
Печатная карта редко представляет собой «чистый» viewport. Обычно добавляются дополнительные элементы:
Добавление легенды:
function addLegend(container) {
const legend = document.createElement('div');
legend.className = 'print-legend';
legend.innerHTML = `
<div>Условные обозначения</div>
<div><span class="line"></span> Маршруты</div>
<div><span class="box"></span> Зоны покрытия</div>
`;
container.appendChild(legend);
}
Одной из ключевых сложностей является асинхронная природа тайлов. При печати часть слоёв может не успеть загрузиться, особенно при медленном соединении.
Решение — контроль render событий:
function waitForTiles(map) {
return new Promise(resolve => {
let ready = false;
const check = () => {
if (map.areTilesLoaded()) {
ready = true;
map.off('render', check);
resolve();
}
};
map.on('render', check);
check();
});
}
Также применяется принудительное удержание карты в состоянии покоя перед печатью.
Практика создания отдельного слоя для печати позволяет полностью отделить экранный интерфейс от печатного:
<div class="map-screen"></div>
<div class="map-print"></div>
В печатном режиме отображается только map-print, где
карта рендерится в фиксированном размере без UI.
@media print {
.map-screen {
display: none;
}
.map-print {
display: block;
}
}
Для высококачественной печати используется векторный экспорт слоёв. В MapLibre GL JS векторные данные доступны через style layers и GeoJSON sources, что позволяет:
Пример извлечения GeoJSON:
const features = map.queryRenderedFeatures();
const geojson = {
type: "FeatureCollection",
features
};
Далее данные передаются в SVG/Canvas генератор.
Несмотря на ограничения, window.print() остаётся базовым
механизмом:
async function printMap(map) {
await waitForMapReady(map);
resizeForPrint(map);
window.print();
}
Важно учитывать поведение браузеров:
Поэтому печатный режим должен быть максимально статичным.
При печати часто проявляются визуальные артефакты:
Для уменьшения эффекта применяется:
map.setPaintProperty('line-layer', 'line-antialias', true);
map.setPaintProperty('fill-layer', 'fill-antialias', true);
Также важно фиксировать pitch и bearing в
ноль, если не требуется 3D-печать.
Печатная карта должна сохранять корректный масштаб, соответствующий легенде. При изменении размера контейнера без фиксации zoom происходит смещение масштаба.
Рекомендуется фиксировать состояние:
const state = {
center: map.getCenter(),
zoom: map.getZoom(),
bearing: map.getBearing(),
pitch: map.getPitch()
};
И восстанавливать его перед печатью, исключая любые авто-адаптации.
При сложных стилях (много слоёв, символика, шрифты) карта может не успевать отрисоваться до вызова печати. Используются оптимизации:
Пример упрощения:
map.setLayoutProperty('labels', 'visibility', 'none');
При необходимости печати больших территорий используется разбиение на тайлы фиксированного размера:
Такой подход особенно актуален для кадастровых и инженерных карт.