При работе с CesiumJS часто возникает необходимость обрабатывать
частые события, не перегружая основной поток вычислений и не вызывая
избыточные перерисовки сцены. Особенно это заметно при работе с камерой,
обработчиками мыши, событиями Scene.postRender, изменениями
позиции курсора и динамическими обновлениями сущностей. В таких
сценариях применяются техники контроля частоты вызова функций —
throttling и debouncing.
CesiumJS активно использует событийную модель. Камера может изменять
положение десятки раз в секунду, курсор генерирует поток
mousemove, а рендер-цикл сцены работает на высокой частоте
(обычно 60 FPS).
Без ограничения частоты вызовов обработчиков возникают следующие проблемы:
Entity,
Primitive);CesiumJS особенно чувствителен к этим проблемам при работе с:
ScreenSpaceEventHandlerScene.camera.changedScene.postRenderClock.onTickEntity.positionThrottling (троттлинг) ограничивает выполнение функции так, чтобы она вызывалась не чаще одного раза за заданный интервал времени.
Идея заключается в равномерном распределении вызовов во времени.
function throttle(fn, limit) {
let inThrottle = false;
let lastArgs = null;
return function (...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
if (lastArgs) {
fn.apply(this, lastArgs);
lastArgs = null;
}
}, limit);
} else {
lastArgs = args;
}
};
}
Одна из частых задач — обновление UI при изменении положения камеры.
const viewer = new Cesium.Viewer("cesiumContainer");
function updateCameraInfo() {
const camera = viewer.camera;
const cartographic = Cesium.Cartographic.fromCartesian(camera.position);
const lon = Cesium.Math.toDegrees(cartographic.longitude);
const lat = Cesium.Math.toDegrees(cartographic.latitude);
console.log("Camera:", lat, lon);
}
viewer.camera.changed.addEventListener(
throttle(updateCameraInfo, 200)
);
Без throttling событие camera.changed может вызываться
десятки раз за секунду, что приводит к лишним вычислениям преобразования
координат.
const handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
function handleMouseMove(movement) {
const cartesian = viewer.camera.pickEllipsoid(
movement.endPosition
);
if (!cartesian) return;
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
const lon = Cesium.Math.toDegrees(cartographic.longitude);
const lat = Cesium.Math.toDegrees(cartographic.latitude);
console.log(lat, lon);
}
handler.setInputAction(
throttle(handleMouseMove, 100),
Cesium.ScreenSpaceEventType.MOUSE_MOVE
);
Debouncing (дебаунсинг) откладывает выполнение функции до тех пор, пока поток вызовов не прекратится на заданное время.
Это противоположная стратегия по сравнению с throttling: вместо равномерных вызовов выполняется только финальный результат.
function debounce(fn, delay) {
let timer = null;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
Типичный случай — поиск по карте или геокодинг. Пользователь вводит текст, и нет смысла отправлять запрос на каждый символ.
function searchLocation(query) {
console.log("Поиск:", query);
// имитация запроса к API
}
const debouncedSearch = debounce(searchLocation, 400);
document.getElementById("searchInput").addEventListener(
"input",
(e) => debouncedSearch(e.target.value)
);
Иногда требуется выполнить действие только после завершения движения камеры: например, загрузить тайлы, пересчитать сложную геометрию или обновить слой данных.
function onCameraStopped() {
console.log("Камера стабилизировалась");
const center = viewer.camera.pickEllipsoid(
new Cesium.Cartesian2(
viewer.canvas.width / 2,
viewer.canvas.height / 2
)
);
if (!center) return;
const cartographic = Cesium.Cartographic.fromCartesian(center);
const lon = Cesium.Math.toDegrees(cartographic.longitude);
const lat = Cesium.Math.toDegrees(cartographic.latitude);
console.log("Центр карты:", lat, lon);
}
viewer.camera.changed.addEventListener(
debounce(onCameraStopped, 300)
);
Throttling обеспечивает регулярные обновления, даже при непрерывном потоке событий. Debouncing выполняет действие только после паузы.
Throttling подходит для:
Debouncing подходит для:
CesiumJS предоставляет низкоуровневое событие
Scene.postRender, которое вызывается каждый кадр. Без
контроля частоты оно легко становится источником перегрузки.
function heavyComputation() {
console.log("Обновление аналитики сцены");
}
const throttledCompute = throttle(heavyComputation, 500);
viewer.scene.postRender.addEventListener(() => {
throttledCompute();
});
Изменение высоты камеры часто используется для переключения уровней детализации данных.
function updateLOD() {
const height = viewer.camera.positionCartographic.height;
if (height > 1000000) {
console.log("Низкий уровень детализации");
} else {
console.log("Высокий уровень детализации");
}
}
viewer.camera.changed.addEventListener(
debounce(updateLOD, 250)
);
В реальных проектах CesiumJS часто требуется сочетание обоих подходов.
const throttledMouse = throttle((pos) => {
console.log("Промежуточные координаты:", pos);
}, 100);
const debouncedMouseEnd = debounce((pos) => {
console.log("Финальные координаты:", pos);
}, 300);
const handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
handler.setInputAction((movement) => {
const cartesian = viewer.camera.pickEllipsoid(movement.endPosition);
if (!cartesian) return;
throttledMouse(cartesian);
debouncedMouseEnd(cartesian);
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
Частые проблемы, возникающие при неправильной реализации:
this при работе с методами Cesium
объектов;destroy() у
ScreenSpaceEventHandler;В крупных приложениях CesiumJS функции throttling и debouncing обычно выносятся в отдельный слой утилит:
export const throttle = (fn, limit) => {
let inThrottle = false;
return function (...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
};
export const debounce = (fn, delay) => {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
};
Такой подход обеспечивает единообразие поведения во всех модулях: от слоёв визуализации до сервисов обработки координат и пользовательского ввода.
При работе с большим количеством Entities,
Billboards, Polylines и динамическими
источниками данных:
В сценах с тысячами объектов это напрямую влияет на стабильность FPS и отзывчивость интерфейса.