How Bidirectional Camera Sync Works Between MapLibre and Cesium in GeoLibre

GeoLibre implements bidirectional camera synchronization through a wrapper class that translates between MapLibre's camera options and Cesium's view parameters, using a locking mechanism to prevent feedback loops during two-way updates.

GeoLibre bridges 2D and 3D geospatial visualization by keeping MapLibre and Cesium cameras in perfect alignment. The @geolibre/map package contains the core synchronization logic that ensures user interactions in either the 2D map or 3D globe instantly reflect in the other view.

Core Architecture of Bidirectional Camera Sync

The Cesium Camera Wrapper (cesium-camera.ts)

Located at packages/map/src/cesium-camera.ts, the CesiumCamera class creates a thin abstraction around Cesium's native Camera instance. It exposes onCameraChange callbacks that fire when Cesium's moveEnd event triggers, emitting normalized camera objects matching MapLibre's CameraOptions shape.

The applyCameraOptions method accepts MapLibre camera definitions—containing center, zoom, bearing, and pitch—and translates these into Cesium's setView parameters using destination and orientation objects. This translation layer ensures that geographic view states remain consistent across the 2D and 3D rendering engines.

Layer-Level Synchronization (cesium-layer-sync.ts)

The packages/map/src/cesium-layer-sync.ts file implements the actual two-way binding through the setupCesiumLayerSync function. This module registers dual listeners that operate continuously during the map lifecycle:

  • Cesium → MapLibre: When Cesium fires moveEnd, the wrapper converts the camera state and dispatches the store's setView action to update MapLibre's viewport immediately.
  • MapLibre → Cesium: When MapLibre reports camera changes (pan, zoom, rotate), the sync module calls applyCameraOptions on the Cesium wrapper to reposition the 3D globe to match the 2D view.

The CesiumLayer class registers these listeners automatically when a Cesium layer is added to the map layer registry, establishing the bidirectional camera sync without additional configuration.

Preventing Feedback Loops in Two-Way Sync

To avoid infinite update cycles, GeoLibre employs a simple locking flag during synchronization operations. When either direction initiates a camera update, the code toggles a syncing boolean that causes the opposite listener to return early if triggered by the programmatic change.

This lock ensures that updating Cesium from MapLibre does not immediately trigger a MapLibre update from Cesium, and vice versa. The flag resets immediately after the view update completes, allowing subsequent user interactions to trigger normal synchronization behavior.

Implementation Examples

React Hook Integration

GeoLibre provides the useCesiumSync hook for React applications to automatically manage the camera synchronization lifecycle.

import { useCesiumSync } from '@geolibre/map';

// Inside a React component that has access to the MapLibre map instance
const map = useMap();                       // MapLibre map reference
const cesiumSync = useCesiumSync(map);      // Sets up the sync on mount

// When the component unmounts, the sync is cleaned up automatically

Manual Camera Control

For imperative control, instantiate the CesiumCamera wrapper directly and apply MapLibre camera states to the Cesium viewer.

import { CesiumCamera } from '@geolibre/map';

// Assume `cesiumViewer` is a Cesium Viewer instance
const camera = new CesiumCamera(cesiumViewer.camera);

// MapLibre camera options (e.g., obtained from the store)
const mapCamera = {
  center: [lon, lat],
  zoom: 12,
  bearing: 45,
  pitch: 30,
};

// Apply the same view to Cesium
camera.applyCameraOptions(mapCamera);

Custom Sync Logic with Lock Flags

When implementing custom synchronization outside the provided hooks, manually manage the lock flag to prevent feedback loops.

let syncing = false;

cesiumCamera.onCameraChange((cesiumOpts) => {
  if (syncing) return;
  syncing = true;
  map.setView(translateCesiumToMapLibre(cesiumOpts));
  syncing = false;
});

map.on('moveend', () => {
  if (syncing) return;
  syncing = true;
  cesiumCamera.applyCameraOptions(map.getCameraOptions());
  syncing = false;
});

Summary

  • Bidirectional camera sync in GeoLibre relies on the CesiumCamera wrapper class to translate between MapLibre and Cesium camera representations.
  • The cesium-layer-sync.ts module manages two-way listeners that update each view when the other changes, utilizing the store's setView action for MapLibre updates.
  • A locking mechanism prevents infinite feedback loops by tracking when a synchronization operation is in progress.
  • The CesiumLayer integration automatically sets up synchronization when 3D layers are added to the map registry.
  • Source files are located in packages/map/src/cesium-camera.ts and packages/map/src/cesium-layer-sync.ts.

Frequently Asked Questions

How does GeoLibre prevent infinite loops during camera sync?

GeoLibre uses a boolean lock flag that is set to true at the start of any camera update operation. When the opposite direction's listener fires, it checks this flag and returns early if an update is already in progress, preventing the cyclic updates that would otherwise occur between MapLibre and Cesium.

What events trigger the bidirectional camera synchronization?

Cesium triggers updates through its native moveEnd event, which the CesiumCamera wrapper listens to before emitting normalized camera options. MapLibre triggers updates through standard map interaction events (pan, zoom, rotate) that dispatch camera changes to the store, which the layer sync module monitors to update Cesium via applyCameraOptions.

Can I use the Cesium camera wrapper independently of MapLibre?

Yes, the CesiumCamera class in packages/map/src/cesium-camera.ts can be instantiated with any Cesium Viewer camera instance. It provides standalone methods like applyCameraOptions to programmatically control the 3D view using MapLibre-style camera parameters, regardless of whether bidirectional sync is active.

Where are the camera synchronization tests located?

GeoLibre includes unit tests for the camera wrapper in tests/cesium-camera.test.ts and integration tests for the full synchronization logic in tests/cesium-layer-sync.test.ts. These files verify correct translation of camera parameters and proper behavior of the bidirectional update mechanism according to the opengeos/GeoLibre source code.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →