Архитектура библиотеки изначально ориентирована на потоковую работу с
геоданными, где наборы точек, траекторий и агрегированных слоёв могут
изменяться без полного пересоздания визуализации. В основе лежит связка
React + Redux, где вся сцена описывается через единое состояние
приложения (keplerGl state), а любые обновления проходят
через экшены, которые модифицируют слои, датасеты и параметры
визуализации.
Система обновления данных строится вокруг принципа immutable state. Это означает, что любое изменение данных не мутирует существующий объект, а заменяет его новым состоянием.
Ключевые сущности:
datasets — наборы геоданных (GeoJSON, CSV, JSON)visState — визуальное представление (слои, фильтры,
взаимодействия)mapState — камера, зум, позиция картыuiState — интерфейсные параметрыЛюбое обновление данных проходит через Redux action:
addDataToMap — добавление или замена датасетаsetFilter — обновление фильтровupdateMap — изменение состояния картыlayerConfigChange — изменение визуализации слоёвСамый прямолинейный способ обновления — полная замена dataset через
addDataToMap.
dispatch({
type: 'ADD_DATA_TO_MAP',
payload: {
datasets: {
info: {
label: 'live-data',
id: 'live_data'
},
data: newGeoJson
},
options: {
centerMap: false,
readOnly: false
},
config: {}
}
});
В этом сценарии Kepler перерисовывает слои, пересчитывает агрегаты и обновляет фильтры.
Особенность: при частом вызове происходит полная переработка визуального состояния, что может стать узким местом при потоковых данных.
Для потоковых систем важнее частичные обновления без полной пересборки визуализации.
Подход:
const upd atedData = {
...existingData,
features: [
...existingData.features,
...newData.features
]
};
Далее обновление:
dispatch(addDataToMap({
datasets: {
info: {
id: 'live_data',
label: 'live-data'
},
data: upd atedData
}
}));
Важно: такой подход подходит только для небольших потоков или батчей.
Реальные сценарии обновления данных в реальном времени чаще используют WebSocket.
Типичная схема:
const socket = new WebSocket('wss://stream.example.com');
socket.onmess age = (event) => {
const incoming = JSON.parse(event.data);
store.dispatch(updateLiveDataset(incoming));
};
Далее Redux middleware преобразует поток в обновление Kepler:
function liveDataMiddleware(store) {
return next => action => {
if (action.type === 'LIVE_DATA_RECEIVED') {
const state = store.getState();
const current = state.keplerGl.map1.visState.datasets.live;
const merged = mergeData(current, action.payload);
store.dispatch(addDataToMap({
datasets: {
info: { id: 'live', label: 'live stream' },
data: merged
}
}));
}
return next(action);
};
}
Потоковые данные могут приходить с высокой частотой, что создаёт нагрузку на рендеринг.
Используются техники:
Сглаживание частоты обновлений:
const debouncedUpdate = debounce((data) => {
dispatch(addDataToMap(data));
}, 200);
Накопление событий:
let buffer = [];
socket.onmess age = (event) => {
buffer.push(JSON.parse(event.data));
if (buffer.length > 50) {
dispatch(updateBatch(buffer));
buffer = [];
}
};
Ограничение FPS обновлений:
const throttled = throttle((data) => {
dispatch(addDataToMap(data));
}, 1000);
Kepler.gl позволяет менять визуализацию без полной перезагрузки данных.
Используется layerConfigChange:
dispatch({
type: 'LAYER_CONFIG_CHANGE',
payload: {
layerId: 'points_layer',
config: {
colorField: 'speed',
colorScale: 'quantile'
}
}
});
Это важно для real-time аналитики, где структура данных стабильна, но смысл визуализации меняется динамически.
Фильтры часто используются как реакция на поток данных.
dispatch({
type: 'SET_FILTER',
payload: {
id: 'time_filter',
value: [startTime, endTime]
}
});
При потоковой обработке фильтр может автоматически сдвигаться:
function shiftTimeFilter(newTimestamp) {
dispatch(setFilter({
id: 'time_filter',
value: [newTimestamp - 3600, newTimestamp]
}));
}
Для треков объектов используется слой Trips.
При обновлении маршрутов важно сохранять временную структуру:
const tripsData = {
type: 'FeatureCollection',
features: updatedTrips
};
Обновление:
dispatch(addDataToMap({
datasets: {
info: { id: 'trips', label: 'vehicle movement' },
data: tripsData
}
}));
Анимация синхронизируется с временным фильтром, поэтому поток должен содержать корректные timestamp.
В высоконагруженных системах используется дифф:
function applyDiff(oldData, diff) {
const map = new Map();
oldData.features.forEach(f => map.se t(f.id, f));
diff.upserts.forEach(f => map.se t(f.id, f));
diff.deletes.forEach(id => map.delete(id));
return {
...oldData,
features: Array.from(map.values())
};
}
Такой подход снижает нагрузку на перерасчёт слоёв.
При обновлении данных важно не терять состояние камеры.
Kepler.gl разделяет:
Поэтому обновление данных не должно сбрасывать
mapState:
dispatch(addDataToMap({
datasets: newData,
options: {
centerMap: false
}
}));
Основные узкие места:
Методы оптимизации:
Часто Kepler.gl используется не для сырого потока, а для уже агрегированных данных.
Сервер выполняет:
Клиент получает уже подготовленные чанки:
{
"type": "aggregated",
"resolution": "5s",
"features": [...]
}
Это значительно снижает нагрузку на браузер.
При нескольких источниках (например, GPS + события + телеметрия) используется нормализация:
Слияние происходит перед dispatch:
const unified = normalizeStreams([gps, telemetry, events]);
dispatch(addDataToMap(unified));
Для массивных потоков (>100k объектов) применяется стратегия:
Kepler.gl использует WebGL, но CPU-bound операции всё равно критичны.
Типичная схема:
useEffect(() => {
socket.connect();
return () => {
socket.disconnect();
};
}, []);
При real-time обновлениях важно избегать:
Для этого используется:
interactionConfigСтабильность идентификаторов является ключевым условием корректного обновления.
Поток данных → нормализация → батчинг → Redux action → Kepler state → WebGL рендер
Любое отклонение от этой цепочки приводит либо к избыточной нагрузке, либо к потере синхронизации визуализации.