Kepler.gl как надстройка над deck.gl и Mapbox GL предоставляет интерактивную визуализацию геоданных, однако в реальных проектах часто требуется вывести результат за пределы браузера: сохранить кадр, зафиксировать анимацию или сформировать видео для отчётов и презентаций. Экспорт в этом контексте опирается не на единый встроенный механизм, а на несколько уровней: снимки состояния карты, рендеринг временных анимаций и захват canvas-потока.
Перед тем как рассматривать экспорт, важно понимать структуру визуализации:
mapState,
visState, uiStateКлючевой момент: любой экспорт — это либо фиксация состояния приложения, либо последовательная фиксация кадров при изменении состояния.
Самый базовый уровень экспорта — сохранение текущего кадра карты.
Внутри приложения Kepler.gl рендер происходит в canvas Mapbox GL:
const mapCanvas = document.querySelector('.mapboxgl-canvas');
Далее используется стандартный механизм браузера:
const image = mapCanvas.toDataURL('image/png');
Результат — base64 PNG, который можно:
Kepler.gl позволяет работать со snapshot состояния:
const config = {
version: 'v1',
config: {
visState: store.getState().visState,
mapState: store.getState().mapState
}
};
Далее состояние может быть восстановлено и заново отрендерено для генерации повторяемого изображения.
Анимации в Kepler.gl чаще всего основаны на временных данных
(timeFilter), где слой обновляется по шкале времени.
Ключевые элементы:
filter типа timeRangeFiltertimestamp)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'));
}
Так формируется последовательность изображений, которая затем может быть собрана в видео.
Браузерный 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);
};
Ключевая сложность — синхронное обновление времени и записи.
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);
Kepler.gl использует deck.gl, который поддерживает более низкоуровневый контроль рендера.
deckInstance.setProps({
onAfterRender: () => {
const canvas = deckInstance.canvas;
}
});
Это позволяет:
Для масштабируемого экспорта применяется headless Chromium.
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 поддерживает многослойные сцены:
При экспорте важно учитывать:
latitude,
longitude, zoom)mapState: {
latitude: 40.7,
longitude: -73.9,
zoom: 12,
bearing: 0,
pitch: 45
}
Любое отклонение viewport приводит к несовпадению кадров между рендерами.
При работе с большими наборами данных возникают проблемы:
Использование более низкого разрешения canvas:
canvas.width = 1280;
canvas.height = 720;
Уменьшение частоты кадров:
stream = canvas.captureStream(15);
Отключение лишних слоёв во время записи:
visible: false
В некоторых сценариях используется полный отказ от браузерного рендера.
Подход:
Это позволяет:
После получения кадров применяются стандартные инструменты:
Пример сборки GIF:
ffmpeg -i frame_%d.png -vf fps=10,scale=800:-1 output.gif
Качество экспорта определяется не только разрешением, но и:
Особенно критична интерполяция в TripsLayer, где
траектории между точками должны быть сглажены одинаковым образом при
каждом рендере.
Для промышленного экспорта важно обеспечить одинаковый результат при повторном запуске:
Без этих условий видеоряд может отличаться между сессиями даже при одинаковых данных.