# CesiumJS Integration in GeoLibre: 3D Globe and MapLibre Camera Synchronization

> Discover how GeoLibre integrates CesiumJS for 3D globe views. Learn how it synchronizes cameras with MapLibre using pure math and a Zustand store for seamless visualization.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: deep-dive
- Published: 2026-08-03

---

**GeoLibre integrates CesiumJS for 3D visualization by wrapping it in a React component that lazily loads the library, converts MapLibre camera states to Cesium parameters using pure math utilities, and maintains bidirectional synchronization through a Zustand store.**

GeoLibre, an open-source geospatial framework maintained by opengeos, combines 2D MapLibre maps with a full 3D CesiumJS globe through a modular React architecture. This integration enables seamless switching between map views while keeping camera positions and layer visibility synchronized across both rendering engines.

## Lazy Loading and Environment Preparation

To avoid impacting 2D MapLibre startup performance, GeoLibre loads CesiumJS only when the globe pane mounts. The `CesiumCanvas` component in [`packages/map/src/CesiumCanvas.tsx`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/CesiumCanvas.tsx) uses a dynamic import (`import("cesium")`) inside a `useEffect` hook to defer loading until runtime【/cache/repos/github.com/opengeos/GeoLibre/main/packages/map/src/CesiumCanvas.tsx#L44-L58】.

Before instantiating the viewer, the component prepares the runtime environment:

- Sets `window.CESIUM_BASE_URL` to point to the public `/cesium` folder containing Cesium assets
- Injects the Cesium widget stylesheet programmatically to ensure styles load correctly with the deferred bundle

This approach keeps the initial bundle size small while ensuring all Cesium Web Workers and assets resolve correctly when the 3D view activates.

## Viewer Creation and Initialization

Once the environment is ready, `CesiumCanvas` creates a single `Cesium.Viewer` instance inside a `useEffect` with an empty dependency array. The viewer is stored in a React ref (`viewerRef`) for persistent access across renders【/cache/repos/github.com/opengeos/GeoLibre/main/packages/map/src/CesiumCanvas.tsx#L50-L78】.

The initialization disables most default UI widgets to maintain a clean interface consistent with GeoLibre's design:

```tsx
const viewer = new Cesium.Viewer(containerRef.current, {
  animation: false,
  baseLayerPicker: false,
  fullscreenButton: false,
  vrButton: false,
  geocoder: false,
  homeButton: false,
  infoBox: false,
  sceneModePicker: false,
  selectionIndicator: false,
  timeline: false,
  navigationHelpButton: false,
});

```

The viewer receives an optional Ion access token via `getCesiumIonToken()` from [`packages/core/src/runtime-env.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/runtime-env.ts), falling back to keyless OpenStreetMap imagery when no token is provided【/cache/repos/github.com/opengeos/GeoLibre/main/packages/core/src/runtime-env.ts#L138】.

## Layer Synchronization Architecture

GeoLibre reconciles its layer state onto the Cesium globe through the `CesiumLayerSync` class defined in [`packages/map/src/cesium-layer-sync.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/cesium-layer-sync.ts). This utility translates the store's `GeoLibreLayer[]` array into native Cesium primitives, handling:

- **GeoJSON** sources as Cesium GeoJSON data sources
- **XYZ/WMS/WMTS** raster tiles as Cesium imagery layers
- **3D Tiles** tilesets for photogrammetry and city models

The synchronization runs immediately after viewer creation and updates reactively whenever the layer configuration changes【/cache/repos/github.com/opengeos/GeoLibre/main/packages/map/src/cesium-layer-sync.ts#L4-L12】【/cache/repos/github.com/opengeos/GeoLibre/main/packages/map/src/CesiumCanvas.tsx#L98-L104】.

## Camera Conversion Mathematics

All coordinate transformations between MapLibre's `MapViewState` (center, zoom, bearing, pitch) and Cesium's camera (range, heading, pitch) reside in [`packages/map/src/cesium-camera.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/cesium-camera.ts). These pure functions perform calculations without importing Cesium at runtime, making them fully unit-testable【/cache/repos/github.com/opengeos/GeoLibre/main/packages/map/src/cesium-camera.ts#L4-L15】【/cache/repos/github.com/opengeos/GeoLibre/main/packages/map/src/cesium-camera.ts#L85-L104】.

Key conversion utilities include:

- **`applyMapViewToCamera`** – Converts MapLibre center/zoom/pitch/bearing to Cesium camera position and orientation
- **`readMapViewFromCamera`** – Extracts MapLibre-compatible view state from a Cesium camera instance
- **`mapLibrePitchToCesiumDeg`** – Normalizes pitch angles between the two coordinate systems
- **`zoomToRange`** – Translates MapLibre zoom levels to Cesium camera range in meters

```typescript
import { applyMapViewToCamera, readMapViewFromCamera } from "@geolibre/map/cesium-camera";
import type { Viewer } from "cesium";

// Apply MapLibre view to Cesium
function syncToGlobe(Cesium: typeof import("cesium"), viewer: Viewer, view: MapViewState) {
  applyMapViewToCamera(Cesium, viewer, view);
}

// Read Cesium camera back to MapLibre format
function captureView(Cesium: typeof import("cesium"), viewer: Viewer): MapViewState {
  return readMapViewFromCamera(Cesium, viewer);
}

```

## Bidirectional Camera Synchronization

GeoLibre maintains a single source of truth for camera state in its Zustand store while keeping both MapLibre and Cesium views in sync through a bidirectional flow.

### Store to Cesium

The `CesiumCanvas` component subscribes to the global `mapView` or pane-specific `secondaryMapViews` from the Zustand store via `useAppStore`. When the view changes and the globe is ready, the `applyView` function calls `applyMapViewToCamera` to update the Cesium camera position programmatically【/cache/repos/github.com/opengeos/GeoLibre/main/packages/map/src/CesiumCanvas.tsx#L94-L112】.

### Cesium to Store

To capture user interactions like pan, zoom, or rotate on the globe, the component listens to the viewer's `camera.moveEnd` event. The handler reads the current camera back into a `MapViewState` using `readMapViewFromCamera`, then compares it against the last programmatic view stored in `lastAppliedRef`. If the view differs and passes the `isSameView` validation, the store updates via `setMapView` or `setSecondaryMapView` with a flag indicating whether the move was user-driven (`userMovedRef`)【/cache/repos/github.com/opengeos/GeoLibre/main/packages/map/src/CesiumCanvas.tsx#L49-L78】.

This pattern prevents infinite update loops while ensuring both 2D and 3D views remain synchronized regardless of which canvas the user interacts with.

## Ion Token Configuration

The Cesium Ion token can be injected at build time through the `VITE_CESIUM_TOKEN` environment variable or overridden at runtime through the `getCesiumIonToken` helper. This flexibility allows deployments to switch between Ion imagery/terrain and fallback OpenStreetMap layers without rebuilding the application【/cache/repos/github.com/opengeos/GeoLibre/main/packages/core/src/runtime-env.ts#L138】.

```tsx
import { CesiumCanvas } from "@geolibre/map";

function GlobePane() {
  // Runtime token injection
  const cesiumToken = process.env.CESIUM_TOKEN;
  return <CesiumCanvas viewId="global-globe" ionToken={cesiumToken} />;
}

```

## Summary

- **Lazy loading** via dynamic `import("cesium")` ensures the 3D globe does not impact initial 2D map load times
- **Environment setup** in [`CesiumCanvas.tsx`](https://github.com/opengeos/GeoLibre/blob/main/CesiumCanvas.tsx) configures `window.CESIUM_BASE_URL` and injects required stylesheets
- **Layer synchronization** through `CesiumLayerSync` reconciles GeoJSON, raster tiles, and 3D Tiles between MapLibre and Cesium
- **Pure math utilities** in [`cesium-camera.ts`](https://github.com/opengeos/GeoLibre/blob/main/cesium-camera.ts) convert camera states without Cesium runtime dependencies, enabling comprehensive unit testing
- **Bidirectional sync** uses Zustand store subscriptions and `camera.moveEnd` events to maintain consistent views across both engines
- **Runtime configuration** via `getCesiumIonToken` supports both Ion and open imagery sources without rebuilds

## Frequently Asked Questions

### How does GeoLibre prevent CesiumJS from slowing down the initial page load?

GeoLibre uses dynamic imports to load CesiumJS only when the `CesiumCanvas` component mounts. By calling `import("cesium")` inside a `useEffect` hook rather than at the top of the file, the library code splits into a separate chunk that browsers fetch only when users open the 3D globe view【/cache/repos/github.com/opengeos/GeoLibre/main/packages/map/src/CesiumCanvas.tsx#L44-L58】.

### What layer types synchronize between MapLibre and the Cesium globe?

The `CesiumLayerSync` utility handles GeoJSON vector data, XYZ/WMS/WMTS raster tile layers, and Cesium 3D Tiles. It reads the same `GeoLibreLayer[]` configuration used by MapLibre and translates each layer type into its Cesium equivalent, ensuring visibility and styling remain consistent when switching between 2D and 3D modes【/cache/repos/github.com/opengeos/GeoLibre/main/packages/map/src/cesium-layer-sync.ts#L4-L12】.

### How does the camera synchronization avoid infinite loops between MapLibre and Cesium?

The implementation uses a reference (`lastAppliedRef`) to track the last programmatically applied view state. When Cesium fires the `camera.moveEnd` event, the handler compares the new camera position against this reference. Only views that differ from the last applied state and pass the `isSameView` validation trigger a store update, preventing circular updates when programmatically setting the camera from MapLibre state changes【/cache/repos/github.com/opengeos/GeoLibre/main/packages/map/src/CesiumCanvas.tsx#L49-L78】.

### Can GeoLibre work without a Cesium Ion token?

Yes. While GeoLibre supports Cesium Ion tokens via the `VITE_CESIUM_TOKEN` environment variable or runtime `getCesiumIonToken` configuration, it falls back to keyless OpenStreetMap imagery when no token is provided. This ensures the 3D globe remains functional in open-source deployments without requiring Ion subscriptions【/cache/repos/github.com/opengeos/GeoLibre/main/packages/core/src/runtime-env.ts#L138】.