Экспорт видео и анимаций

Kepler.gl как надстройка над deck.gl и Mapbox GL предоставляет интерактивную визуализацию геоданных, однако в реальных проектах часто требуется вывести результат за пределы браузера: сохранить кадр, зафиксировать анимацию или сформировать видео для отчётов и презентаций. Экспорт в этом контексте опирается не на единый встроенный механизм, а на несколько уровней: снимки состояния карты, рендеринг временных анимаций и захват canvas-потока.


Архитектура визуального рендеринга как основа экспорта

Перед тем как рассматривать экспорт, важно понимать структуру визуализации:

  • Kepler.gl использует deck.gl layers для отрисовки объектов
  • Mapbox GL отвечает за базовую карту (тайлы, стили, фон)
  • Вся сцена рендерится в WebGL canvas
  • Состояние визуализации описывается через mapState, visState, uiState

Ключевой момент: любой экспорт — это либо фиксация состояния приложения, либо последовательная фиксация кадров при изменении состояния.


Экспорт статических изображений (snapshot)

Самый базовый уровень экспорта — сохранение текущего кадра карты.

Получение canvas из Kepler.gl

Внутри приложения Kepler.gl рендер происходит в canvas Mapbox GL:

const mapCanvas = document.querySelector('.mapboxgl-canvas');

Далее используется стандартный механизм браузера:

const image = mapCanvas.toDataURL('image/png');

Результат — base64 PNG, который можно:

  • сохранить как файл
  • отправить на сервер
  • вставить в отчёт

Альтернативный способ через Kepler state

Kepler.gl позволяет работать со snapshot состояния:

const config = {
  version: 'v1',
  config: {
    visState: store.getState().visState,
    mapState: store.getState().mapState
  }
};

Далее состояние может быть восстановлено и заново отрендерено для генерации повторяемого изображения.


Экспорт серии кадров для анимации

Анимации в Kepler.gl чаще всего основаны на временных данных (timeFilter), где слой обновляется по шкале времени.

Механизм временной анимации

Ключевые элементы:

  • filter типа timeRangeFilter
  • поле временной шкалы (timestamp)
  • параметры animationWindow, speed

Пример состояния фильтра:

{
  id: 'time-filter',
  dataId: 'trips',
  name: 'timestamp',
  type: 'timeRange',
  value: [start, end],
  animationWindow: 'free',
  speed: 1
}

Генерация кадров через программное переключение времени

Для экспорта анимации выполняется итерация по временной шкале:

const frames = [];

for (let t = start; t <= end; t += step) {
  dispatch(updateFilter('trips', {
    value: [t, t + windowSize]
  }));

  const canvas = document.querySelector('.mapboxgl-canvas');
  frames.push(canvas.toDataURL('image/png'));
}

Так формируется последовательность изображений, которая затем может быть собрана в видео.


Сборка видео через MediaRecorder API

Браузерный API позволяет записывать canvas-поток напрямую.

Подключение потока

const canvas = document.querySelector('.mapboxgl-canvas');
const stream = canvas.captureStream(30);

Параметр 30 задаёт FPS записи.


Запись видео

const recorder = new MediaRecorder(stream, {
  mimeType: 'video/webm; codecs=vp9'
});

const chunks = [];

recorder.ondataavaila ble = e => {
  if (e.data.size > 0) chunks.push(e.data);
};

recorder.ons top = () => {
  const blob = new Blob(chunks, { type: 'video/webm' });
  const url = URL.createObjectURL(blob);
};

Синхронизация с анимацией Kepler.gl

Ключевая сложность — синхронное обновление времени и записи.

recorder.start();

let t = start;

const interval = setInterval(() => {
  dispatch(updateFilter('trips', { value: [t, t + 1000] }));
  t += step;

  if (t > end) {
    clearInterval(interval);
    recorder.stop();
  }
}, 100);

Экспорт через deck.gl Animation Loop

Kepler.gl использует deck.gl, который поддерживает более низкоуровневый контроль рендера.

Использование onAfterRender

deckInstance.setProps({
  onAfterRender: () => {
    const canvas = deckInstance.canvas;
  }
});

Это позволяет:

  • фиксировать каждый кадр
  • синхронизировать экспорт с рендер-циклом
  • избегать рассинхронизации UI и canvas

Серверный экспорт через Puppeteer

Для масштабируемого экспорта применяется headless Chromium.

Запуск страницы Kepler.gl

const browser = await puppeteer.launch();
const page = await browser.newPage();

await page.goto('http://localhost:3000');

Программное управление анимацией

await page.evaluate(() => {
  window.startAnimation();
});

Захват кадров

for (let i = 0; i < 100; i++) {
  const frame = await page.screenshot({ encoding: 'binary' });
  fs.writeFileSync(`frame_${i}.png`, frame);
}

Далее кадры объединяются в видео через ffmpeg:

ffmpeg -framerate 30 -i frame_%d.png -c:v libx264 output.mp4

Экспорт сложных анимаций с несколькими слоями

Kepler.gl поддерживает многослойные сцены:

  • Trips Layer (движение объектов)
  • Arc Layer (связи)
  • Scatterplot Layer (точки)
  • Hexagon Layer (агрегации)

При экспорте важно учитывать:

  • синхронизацию всех слоёв по времени
  • стабильность порядка рендеринга
  • фиксированный viewport (latitude, longitude, zoom)

Фиксация viewport для повторяемости

mapState: {
  latitude: 40.7,
  longitude: -73.9,
  zoom: 12,
  bearing: 0,
  pitch: 45
}

Любое отклонение viewport приводит к несовпадению кадров между рендерами.


Оптимизация экспорта

При работе с большими наборами данных возникают проблемы:

  • падение FPS при сложных слоях
  • нестабильность WebGL контекста
  • перегрузка памяти при хранении кадров

Практики оптимизации:

Использование более низкого разрешения canvas:

canvas.width = 1280;
canvas.height = 720;

Уменьшение частоты кадров:

stream = canvas.captureStream(15);

Отключение лишних слоёв во время записи:

visible: false

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

В некоторых сценариях используется полный отказ от браузерного рендера.

Подход:

  • вычисление позиций объектов заранее
  • генерация GeoJSON для каждого временного шага
  • рендеринг через node-canvas + deck.gl

Это позволяет:

  • детерминированно воспроизводить анимацию
  • ускорить генерацию видео
  • избегать ограничений GPU браузера

Формирование итоговых медиафайлов

После получения кадров применяются стандартные инструменты:

  • ffmpeg для видео
  • gifski для GIF-анимаций
  • imagemagick для оптимизации последовательностей

Пример сборки GIF:

ffmpeg -i frame_%d.png -vf fps=10,scale=800:-1 output.gif

Управление качеством визуального результата

Качество экспорта определяется не только разрешением, но и:

  • стабильностью тайлов Mapbox
  • кэшированием данных
  • точностью временной интерполяции
  • консистентностью WebGL контекста

Особенно критична интерполяция в TripsLayer, где траектории между точками должны быть сглажены одинаковым образом при каждом рендере.


Повторяемость рендера и детерминизм

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

  • фиксированный seed генераторов
  • отключение динамических обновлений UI
  • предзагрузка всех тайлов
  • синхронное управление временем

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