В Kepler.gl состояние отображения карты управляется через объект viewport, который описывает текущую камеру: координаты центра, масштаб, наклон, азимут и другие параметры визуализации. Этот объект является центральным связующим звеном между пользовательскими действиями, состоянием приложения и рендерингом WebGL-карты.
Viewport в Kepler.gl не является статической структурой. Он постоянно обновляется в ответ на взаимодействия пользователя (панорамирование, зум, вращение) и внешние изменения состояния (программное управление, синхронизация с Redux, переключение датасетов или слоёв).
Базовый viewport в Kepler.gl обычно включает следующие поля:
Каждое из этих значений влияет на матрицу трансформации, которая передаётся в WebGL-рендерер через Deck.gl.
Kepler.gl использует модель однонаправленного потока данных через Redux. Любое изменение viewport происходит через action:
Ключевой action:
updateMapViewport({
latitude,
longitude,
zoom,
bearing,
pitch,
width,
height
});
Этот вызов является основным способом синхронизации визуального состояния карты с приложением.
При работе с Kepler.gl часто возникает необходимость синхронизировать viewport между несколькими источниками:
Основная сложность заключается в том, что viewport обновляется часто и непрерывно, особенно при перетаскивании карты. Это создаёт риск:
Наиболее стабильный подход — считать viewport производным состоянием:
UI → action → store → map
В этом случае любое внешнее изменение viewport проходит через Redux, а карта лишь отображает текущее состояние.
Преимущества:
Недостаток:
Более сложная схема:
map ↔ store ↔ UI
Здесь изменения могут происходить как из карты, так и из внешнего UI. Это требует строгого контроля, чтобы избежать бесконечных циклов обновления.
Ключевая проблема при синхронизации viewport — повторный dispatch одного и того же состояния.
Типичный сценарий:
Для предотвращения используется сравнение:
Пример защитной логики:
const isViewportEqual = (v1, v2) =>
v1.latitude === v2.latitude &&
v1.longitude === v2.longitude &&
v1.zoom === v2.zoom &&
v1.bearing === v2.bearing &&
v1.pitch === v2.pitch;
Viewport может обновляться десятки раз в секунду. Без оптимизации это приводит к перегрузке Redux и React.
Используются техники:
Ограничение частоты dispatch:
import throttle from 'lodash.throttle';
const handleViewportChange = throttle((viewport) => {
dispatch(updateMapViewport(viewport));
}, 50);
Обновления синхронизируются с кадром рендеринга:
let pending = null;
function scheduleUpdate(vp) {
pending = vp;
requestAnimationFrame(() => {
dispatch(updateMapViewport(pending));
});
}
Viewport часто требуется синхронизировать с URL, чтобы обеспечить:
Пример сериализации:
?lat=53.9&lng=27.56&zoom=10&bearing=0&pitch=0
При изменении viewport:
Решение:
Kepler.gl может использоваться в сценариях, где отображается несколько карт одновременно. В этом случае viewport может быть:
Все карты получают одинаковый viewport:
store.subscribe(() => {
const vp = store.getState().map.viewport;
mapA.setViewport(vp);
mapB.setViewport(vp);
});
Синхронизируются только:
Но не синхронизируются:
Это используется для сравнительных визуализаций.
Viewport напрямую влияет на:
При изменении zoom может происходить:
Таким образом, viewport — это не только камера, но и триггер вычислений.
Kepler.gl построен поверх Deck.gl, где viewport транслируется в параметры камеры:
Deck.gl использует viewport для расчёта:
Любое изменение viewport фактически приводит к пересборке матрицы камеры.
Для сложных приложений viewport часто перехватывается middleware:
Пример middleware:
const viewportLogger = store => next => action => {
if (action.type === 'UPDATE_MAP_VIEWPORT') {
console.log('Viewport changed', action.payload);
}
return next(action);
};
Viewport может обновляться не только от пользователя:
В таких случаях важно:
При одновременном управлении:
возникает конфликт control authority.
Решение:
В зрелых приложениях viewport рассматривается как:
Его корректная синхронизация требует сочетания:
Viewport в Kepler.gl становится не просто параметром камеры, а ядром всей интерактивной геовизуализации.