# How Bidirectional Camera Sync Works Between MapLibre and Cesium in GeoLibre

> Discover how GeoLibre achieves bidirectional camera sync between MapLibre and Cesium. Learn about the wrapper class and locking mechanism for seamless two-way updates.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: how-to-guide
- Published: 2026-08-01

---

**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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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.

```tsx
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.

```ts
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.

```ts
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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/cesium-camera.ts) and [`packages/map/src/cesium-layer-sync.ts`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/tests/cesium-camera.test.ts) and integration tests for the full synchronization logic in [`tests/cesium-layer-sync.test.ts`](https://github.com/opengeos/GeoLibre/blob/main/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.