Печать карт

CesiumJS работает поверх WebGL и формирует изображение сцены в реальном времени, поэтому печать карты требует промежуточного этапа — преобразования текущего состояния Scene в статическое изображение. Основная задача сводится к корректному захвату canvas, синхронизации слоёв тайлов и подготовки результата к выводу на печать без потери качества.

В CesiumJS визуализация рендерится в элемент <canvas>, доступный через viewer.scene.canvas. Именно этот элемент используется как источник итогового изображения.

Базовый способ получения снимка заключается в использовании метода:

const canvas = viewer.scene.canvas;
const image = canvas.toDataURL("image/png");

Однако прямой вызов toDataURL без предварительного рендера может привести к захвату неполной сцены, поскольку Cesium выполняет отложенную отрисовку кадров.

Корректный подход включает принудительный рендер:

viewer.render();
const canvas = viewer.scene.canvas;
const image = canvas.toDataURL("image/png");

Для сцен с динамическими слоями дополнительно учитывается готовность тайлов:

viewer.scene.globe.tileLoadProgressEvent.addEventListener(function (remaining) {
    if (remaining === 0) {
        viewer.render();
        const image = viewer.scene.canvas.toDataURL("image/png");
    }
});

Это гарантирует, что все подложки ImageryLayer полностью загружены.

Экспорт в изображение высокого разрешения

Стандартный canvas ограничен размером экрана, что приводит к низкому качеству при печати. Для получения печатного качества используется увеличение виртуального разрешения через масштабирование canvas.

Подход основан на создании временного canvas:

const originalCanvas = viewer.scene.canvas;

const scale = 2;
const exportCanvas = document.createElement("canvas");
exportCanvas.width = originalCanvas.width * scale;
exportCanvas.height = originalCanvas.height * scale;

const ctx = exportCanvas.getContext("2d");
ctx.scale(scale, scale);
ctx.drawImage(originalCanvas, 0, 0);

const image = exportCanvas.toDataURL("image/png");

Для более качественного результата применяют коэффициент 3–4, однако нагрузка на память растёт экспоненциально.

Печать через окно браузера

После получения изображения формируется отдельное окно с минимальной HTML-разметкой:

const printWindow = window.open("", "_blank");

printWindow.document.write(`
<html>
<head>
<style>
body { margin: 0; }
img { width: 100%; }
</style>
</head>
<body>
<img src="${image}">
</body>
</html>
`);

printWindow.document.close();
printWindow.focus();
printWindow.print();

Такой подход обеспечивает контроль над масштабированием и предотвращает вмешательство стилей основной страницы.

Стабилизация сцены перед захватом

CesiumJS продолжает обновлять кадры даже при отсутствии пользовательского взаимодействия. Для получения стабильного снимка необходимо зафиксировать камеру и отключить анимации:

viewer.clock.shouldAnimate = false;
viewer.scene.requestRenderMode = true;
viewer.scene.maximumRenderTimeChange = 0;

Дополнительно полезно отключать постобработку:

viewer.scene.postProcessStages.enabled = false;

Это снижает вероятность артефактов при печати.

Учёт тайлов и ImageryLayer

Карты Cesium формируются из тайлов, загружаемых асинхронно. При печати важно дождаться завершения всех запросов.

Используется отслеживание состояния загрузки:

function waitForTiles(viewer) {
    return new Promise((resolve) => {
        const check = () => {
            if (viewer.scene.globe.tilesLoaded) {
                resolve();
            } else {
                requestAnimationFrame(check);
            }
        };
        check();
    });
}

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

Работа с 3D-геометрией при печати

3D-объекты в CesiumJS рендерятся с учётом перспективы, что создаёт искажения при печати. Для плоского представления карты используется переход в ортографическую проекцию:

viewer.scene.camera.setView({
    destination: Cesium.Cartesian3.fromDegrees(0, 0, 20000000)
});

Либо временное переключение на режим, минимизирующий перспективные эффекты.

Также учитывается освещение:

viewer.scene.light = new Cesium.DirectionalLight({
    direction: viewer.scene.camera.directionWC
});

Это обеспечивает стабильные тени при фиксированном положении камеры.

Подготовка композиции перед печатью

Перед созданием снимка часто требуется скрыть элементы интерфейса:

viewer.animation.container.style.display = "none";
viewer.timeline.container.style.display = "none";
viewer.geocoder.container.style.display = "none";

Также отключаются декоративные элементы сцены:

viewer.scene.skyAtmosphere.show = false;
viewer.scene.skyBox.show = false;
viewer.scene.fog.enabled = false;

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

Экспорт через более точный рендеринг кадра

Для повышения качества применяется рендеринг нескольких кадров подряд с последующим захватом:

for (let i = 0; i < 3; i++) {
    viewer.render();
}

const canvas = viewer.scene.canvas;
const image = canvas.toDataURL("image/png");

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

Масштабирование под печатные форматы

Для соответствия стандартам печати (A4, A3) вычисляется DPI:

const dpi = 300;
const inchesWidth = 8.27;
const pixels = dpi * inchesWidth;

После этого canvas масштабируется до целевого разрешения, сохраняя пропорции сцены.

Обход ограничений WebGL при экспорте

WebGL-рендер может блокировать чтение пикселей при использовании cross-origin тайлов без CORS-заголовков. В CesiumJS это проявляется в виде «tainted canvas».

Решение заключается в настройке источников изображений:

const viewer = new Cesium.Viewer("cesiumContainer", {
    imageryProvider: new Cesium.OpenStreetMapImageryProvider({
        url: "https://a.tile.openstreetmap.org/"
    }),
    requestRenderMode: true
});

При необходимости сервер тайлов должен возвращать заголовок:

Access-Control-Allow-Origin: *

Постобработка изображения перед печатью

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

const ctx = exportCanvas.getContext("2d");
const imageData = ctx.getImageData(0, 0, exportCanvas.width, exportCanvas.height);

for (let i = 0; i < imageData.data.length; i += 4) {
    imageData.data[i] *= 1.05;
    imageData.data[i + 1] *= 1.05;
    imageData.data[i + 2] *= 1.05;
}

ctx.putImageData(imageData, 0, 0);

Такая коррекция повышает читаемость картографических слоёв при печати.

Генерация PDF-документа из сцены

Помимо изображения, сцена может быть встроена в PDF через библиотеку jsPDF:

const { jsPDF } = window.jspdf;

const pdf = new jsPDF("landscape", "px", [exportCanvas.width, exportCanvas.height]);
pdf.addImage(image, "PNG", 0, 0, exportCanvas.width, exportCanvas.height);
pdf.save("map.pdf");

Этот подход используется для стандартизированных отчётов с картографическим содержимым.

Управление детализацией перед печатью

Для уменьшения артефактов при экспорте уровень детализации можно зафиксировать:

viewer.scene.globe.maximumScreenSpaceError = 1;

Уменьшение этого параметра увеличивает качество геометрии, что особенно важно при крупномасштабной печати.

Синхронизация анимации и кадров

При наличии движущихся объектов необходимо остановить симуляцию времени:

viewer.clock.multiplier = 0;
viewer.clock.currentTime = viewer.clock.currentTime;
viewer.clock.shouldAnimate = false;

Это исключает смазывание динамических сущностей на итоговом изображении.

Контроль конечного цвета и гаммы

CesiumJS использует линейное освещение и гамма-коррекцию, что может отличаться от печатного представления. Для корректировки применяются постпроцессинговые этапы:

viewer.scene.gamma = 1.2;
viewer.scene.highDynamicRange = false;

Балансировка этих параметров влияет на читаемость контуров и текстур в печати.