Печать карт

При подготовке картографического интерфейса к печати в веб-приложениях на базе MapLibre GL JS ключевой задачей становится преобразование интерактивного WebGL-рендера в статичное, воспроизводимое изображение, пригодное для печатного вывода. В отличие от DOM-ориентированных интерфейсов, карта в MapLibre рендерится через WebGL-canvas, что накладывает ограничения на стандартные механизмы печати браузера.

Базовая проблема заключается в том, что window.print() фиксирует текущее состояние страницы, но не гарантирует корректный захват WebGL-контента, асинхронно загружаемых тайлов и динамических слоёв. Поэтому подготовка карты к печати требует отдельного режима отображения, в котором контролируется размер, стиль, загрузка ресурсов и отключение интерактивных элементов.

Основные задачи подготовки:

  • фиксация текущего viewport и масштаба;
  • ожидание полной загрузки тайлов;
  • отключение анимаций и переходов;
  • настройка печатного CSS-режима;
  • управление DPI и разрешением canvas.

Стабилизация состояния карты

Перед печатью необходимо гарантировать, что карта полностью загружена и не находится в процессе рендеринга. В 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.


Отключение интерактивности перед печатью

Печатная версия карты не должна содержать элементов управления, анимаций и интерактивных эффектов. Типичный набор отключаемых компонентов:

  • zoom controls;
  • attribution control;
  • popup окна;
  • hover эффекты;
  • transitionDuration;
  • вращение и наклон.

Пример подготовки:

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

CSS-режим печати

Для корректного вывода важно использовать @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, что критично для сохранения геометрической точности.


Захват изображения карты через canvas

Альтернативный подход к печати — извлечение изображения напрямую из 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();
};

Этот метод фиксирует текущее состояние карты, но не подходит для векторного вывода высокого качества.


Повышение разрешения (DPI scaling)

Для печати требуется более высокая плотность пикселей, чем в браузере. Решение — масштабирование 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();
  });
}

Также применяется принудительное удержание карты в состоянии покоя перед печатью.


Печать через скрытый print-layer

Практика создания отдельного слоя для печати позволяет полностью отделить экранный интерфейс от печатного:

<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, что позволяет:

  • экспортировать геометрию;
  • рендерить через SVG;
  • формировать PDF через внешние библиотеки.

Пример извлечения 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();
}

Важно учитывать поведение браузеров:

  • Chrome может пересоздавать canvas;
  • Firefox иногда теряет WebGL-контекст;
  • Safari ограничивает масштабирование canvas.

Поэтому печатный режим должен быть максимально статичным.


Скрытие артефактов WebGL

При печати часто проявляются визуальные артефакты:

  • разрывы тайлов;
  • мерцание;
  • неполные tile borders;
  • aliasing линий.

Для уменьшения эффекта применяется:

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

Подготовка к многостраничной печати

При необходимости печати больших территорий используется разбиение на тайлы фиксированного размера:

  • вычисление bounding box;
  • деление на сетку;
  • рендер каждой ячейки отдельно;
  • последующая сборка PDF.

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