# How the CesiumJS 3D Globe Integrates with the 2D MapLibre Map

> Learn how GeoLibre seamlessly integrates the CesiumJS 3D globe with the MapLibre 2D map. Discover synchronization techniques for layers and camera views using Zustand and CesiumLayerSync.

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

---

**GeoLibre synchronizes its 2D MapLibre map and 3D CesiumJS globe through a centralized Zustand store, using `CesiumLayerSync` to reconcile layers and pure-function camera converters to maintain bidirectional view consistency.**

GeoLibre is an open-source geospatial framework maintained by opengeos that renders both a 2D MapLibre map and an optional 3D CesiumJS globe within the same application. The CesiumJS 3D globe integrates with the 2D MapLibre map through a store-driven architecture that treats both views as parallel renderers of shared state. This design ensures that layer additions, style changes, and camera movements remain perfectly synchronized across dimensions.

## Store-Driven Architecture for Dual-View Synchronization

The integration relies on a single source of truth defined in **`@geolibre/core`**. The Zustand store holds the canonical list of layers (`GeoLibreLayer[]`) and the current view state (`MapViewState`), including center coordinates, zoom level, bearing, and pitch.

Both the 2D and 3D views subscribe to this store but render it differently:

- **MapLibre Sync** – `MapController.syncLayers` (in [`packages/map/src/map-controller.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/map-controller.ts)) reconciles the store's layer array onto MapLibre sources and layers.
- **Cesium Sync** – `CesiumLayerSync` (in [`packages/map/src/cesium-layer-sync.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/cesium-layer-sync.ts)) performs the same reconciliation for the Cesium globe, creating `ImageryLayer`, `GeoJsonDataSource`, and `Cesium3DTileset` objects.

When a user adds, edits, or removes a layer, the store updates trigger both sync handlers simultaneously, keeping the 2D and 3D views consistent.

## Layer Synchronization with CesiumLayerSync

The `CesiumLayerSync` class handles the creation, rebuilding, styling, and ordering of Cesium objects that correspond to supported layer types: raster/XYZ/WMS/WMTS, GeoJSON, and 3D Tiles.

During each sync cycle, the class performs three checks via helper functions:

- **`isCesiumSupportedLayerType`** – Determines if the layer type can be rendered on the globe.
- **`needsRebuild`** – Detects if the source or style has changed, requiring destruction and recreation of the Cesium object.
- **`applyAppearance` / `applyGeoJsonStyle`** – Updates visibility, opacity, and vector styling without rebuilding the entire layer.

This granular approach minimizes performance overhead when only visual properties change.

## Camera Coordination Between MapLibre and Cesium

View synchronization requires translating between MapLibre's Web-Mercator zoom model and Cesium's camera model (range, heading, pitch). The [`packages/map/src/cesium-camera.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/cesium-camera.ts) module exports pure functions that perform these conversions using ground resolution math, enabling unit testing without importing Cesium into the test build graph.

**`applyMapViewToCamera`** converts MapLibre's `MapViewState` (zoom + nadir-referenced pitch) into Cesium camera parameters (range + horizon-referenced pitch). This ensures the ground resolution matches across both canvases, even if their pixel heights differ.

**`readMapViewFromCamera`** performs the inverse operation, reading the Cesium camera's current position and orientation to produce a `MapViewState` object that can be written back to the store.

**`isSameView`** prevents feedback loops by checking if the new camera-derived view differs significantly from the current store state before triggering an update.

## Implementation Examples

### Initializing the Cesium Synchronization

```typescript
import { Cesium } from "cesium";
import { CesiumLayerSync } from "@geolibre/map";
import { Viewer } from "cesium";

// Assume a Cesium Viewer has been created elsewhere
const viewer: Viewer = new Viewer("cesiumContainer");

// Create the sync helper – inject the Cesium namespace and the viewer
const cesiumSync = new CesiumLayerSync(Cesium, viewer);

```

### Syncing Layers on Store Updates

```typescript
import { useStore } from "@geolibre/core";

// Subscribe to the store's layer list
useStore.subscribe(
  (state) => state.layers,
  (layers) => {
    // Reconcile the globe with the current layer array
    cesiumSync.sync(layers);
  },
);

```

### Applying MapLibre View to Cesium Camera

```typescript
import { applyMapViewToCamera } from "@geolibre/map/cesium-camera";
import { useStore } from "@geolibre/core";

// When the view changes (e.g., user zooms), update the globe camera
useStore.subscribe(
  (state) => state.mapView,
  (view) => {
    applyMapViewToCamera(Cesium, viewer, view);
  },
);

```

### Reading Cesium Camera Back to Store

```typescript
import { readMapViewFromCamera, isSameView } from "@geolibre/map/cesium-camera";
import { setMapView } from "@geolibre/core";

// Listen to Cesium's moveEnd event
viewer.camera.moveEnd.addEventListener(() => {
  const newView = readMapViewFromCamera(Cesium, viewer);
  const currentView = useStore.getState().mapView;

  // Avoid feedback loops by checking for near-identical views
  if (!isSameView(currentView, newView)) {
    setMapView(newView);
  }
});

```

## Summary

- **Shared State**: Both views read from a single Zustand store in `@geolibre/core` containing layers and `MapViewState`.
- **Layer Sync**: `CesiumLayerSync` in [`packages/map/src/cesium-layer-sync.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/cesium-layer-sync.ts) mirrors store layers onto Cesium objects, handling raster, GeoJSON, and 3D Tiles.
- **Camera Math**: Pure functions in [`packages/map/src/cesium-camera.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/cesium-camera.ts) convert between MapLibre zoom/pitch and Cesium range/pitch to maintain identical ground resolution.
- **Bidirectional Flow**: Map interactions update the store, which updates Cesium; Cesium camera moves read back to the store via `readMapViewFromCamera`, updating MapLibre.
- **Performance**: Granular checks like `needsRebuild` and `isSameView` prevent unnecessary recreation of layers and camera feedback loops.

## Frequently Asked Questions

### Which layer types does the CesiumJS globe support?

The integration supports raster/XYZ tiles, WMS/WMTS services, GeoJSON vectors, and 3D Tiles tilesets. The `isCesiumSupportedLayerType` function in [`packages/map/src/cesium-layer-sync.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/cesium-layer-sync.ts) filters the store's layer list to only these compatible types before creating corresponding Cesium `ImageryLayer`, `GeoJsonDataSource`, or `Cesium3DTileset` instances.

### How does GeoLibre prevent infinite loops between the 2D and 3D views?

The system uses the `isSameView` utility from [`packages/map/src/cesium-camera.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/cesium-camera.ts) to compare the current store state against newly derived camera positions. When the Cesium camera stops moving, `readMapViewFromCamera` generates a candidate view, but the store only updates if `isSameView` returns false, breaking potential feedback cycles before they begin.

### Why are the camera conversion functions implemented as pure functions?

The camera conversion logic in [`packages/map/src/cesium-camera.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/cesium-camera.ts) uses pure functions (without side effects or Cesium dependencies) to calculate ground resolution, range-to-zoom ratios, and pitch conversions. This design allows the core synchronization math to be unit-tested in isolation without pulling the heavy Cesium library into the test build graph, improving CI performance and reliability.

### Can the 3D globe be used without the 2D MapLibre map?

Yes. Because the architecture is store-driven, the CesiumJS globe operates as an independent consumer of the centralized state. If the MapLibre map component is not mounted, `CesiumLayerSync` continues to function by reading directly from the Zustand store, making the 3D view viable as a standalone renderer for applications that require only a globe visualization.