How Cesium Layer Sync Works for GeoJSON, XYZ, and 3D Tiles in GeoLibre

GeoLibre's CesiumLayerSync class reconciles the reactive layer store with a Cesium Viewer by diffing GeoLibreLayer objects and generating, updating, or destroying the corresponding ImageryLayer, GeoJsonDataSource, and Cesium3DTileset instances.

GeoLibre bridges 2D and 3D mapping through a synchronization layer that lives in packages/map/src/cesium-layer-sync.ts. The CesiumLayerSync class implements a reconciliation strategy similar to MapController.syncLayers, ensuring that changes to raster sources, vector data, and photorealistic meshes are reflected on the Cesium globe without full re-renders. This article breaks down the internal mechanics of how the library detects changes, manages Cesium object lifecycles, and optimizes performance for mixed 2D/3D workflows.

The CesiumLayerSync Class

The core implementation resides in packages/map/src/cesium-layer-sync.ts and exports the CesiumLayerSync class. This class maintains an internal registry of LayerEntry objects mapped by layer ID, allowing it to compare previous and current states of the application's layer store. When the sync(layers) method receives an updated array of GeoLibreLayer objects, it calculates the minimal set of operations needed to bring the Cesium Viewer into alignment with the store.

The class handles three primary responsibilities: validating which layers Cesium can render, determining whether existing objects can be restyled in place or must be rebuilt, and managing the z-order of imagery layers to match the store's sequence.

Supported Layer Types

Not all GeoLibre layer types translate to Cesium primitives. The helper isCesiumSupportedLayerType implements a whitelist check that restricts synchronization to the following mappings:

  • XYZ / Raster / WMS / WMTS → ImageryLayer via UrlTemplateImageryProvider or WebMapServiceImageryProvider
  • GeoJSON → GeoJsonDataSource with clamp-to-ground styling
  • 3D Tiles → Cesium3DTileset for streaming photorealistic or mesh data

Before creating any objects, the isSupported function verifies that required data exists—such as a valid tile URL for XYZ sources, a FeatureCollection for GeoJSON, or a tileset.json endpoint for 3D Tiles.

The LayerEntry Data Structure

Each synchronized layer receives a corresponding LayerEntry stored in a Map<string, LayerEntry> keyed by the layer's id. As defined in packages/map/src/cesium-layer-sync.ts, this interface tracks:

  • kind: A normalized discriminator ("imagery", "geojson", or "3dtiles") derived by the entryKind helper
  • layer: The last seen layer configuration object for diffing purposes
  • handle: The actual Cesium instance (ImageryLayer, DataSource, or Cesium3DTileset)
  • State flags: Cancellation tokens and cached opacity values to prevent redundant style calculations

This registry enables precise, granular updates rather than clearing and rebuilding the entire scene on every store change.

Detecting When to Rebuild vs. Restyle

The needsRebuild(prev, next) method implements the diffing logic that determines whether a layer requires expensive reconstruction or can receive a cheap property update. Rebuilds are triggered when:

  • The type field changes (e.g., switching from XYZ to GeoJSON)
  • Geometry or source URLs change (new tile endpoints, updated 3D Tiles URLs, or modified GeoJSON data)
  • Style-related properties change for GeoJSON layers (fill color, stroke width, or marker color)

Crucially, opacity and visibility changes do not trigger rebuilds. These are applied later via applyAppearance methods, allowing smooth UI interactions like opacity sliders to perform at 60fps without destroying and recreating WebGL resources.

The Synchronization Flow

The sync(layers) method executes a three-phase reconciliation process:

Removing Stale Layers

The first pass iterates over the existing LayerEntry registry. Any layer ID no longer present in the incoming layers array is destroyed via destroyEntry, which properly removes the object from the Cesium Viewer and cancels any pending asynchronous operations.

Creating and Updating Entries

For each layer in the new list, the sync logic checks:

  1. If the layer type is unsupported → destroy any existing entry
  2. If no entry exists → invoke createEntry (which routes to createImagery, createGeoJson, or createTileset)
  3. If needsRebuild returns true → destroy the old entry and create a fresh one
  4. Otherwise → call applyAppearance to update visibility and opacity

During this phase, any newly created imagery layer sets an internal imageryRebuilt flag to true, signaling that reordering logic may need to run.

Reordering Imagery Layers

Because Cesium's addImageryProvider always appends to the top of the stack, GeoLibre performs a separate reordering pass. This only executes if imageryRebuilt is true or if the layer order has actually changed, avoiding O(n²) sorting operations during opacity drags. The algorithm iterates through the store's layer order and raises each imagery layer to its correct index using Cesium's raise methods.

Creating Cesium Objects by Type

Imagery Layers (XYZ, WMS, WMTS)

The createImagery method handles raster sources via Cesium.ImageryLayer:

  • WMS layers: Instantiate WebMapServiceImageryProvider using the GetMap parameters stored in the layer object
  • XYZ/Raster/WMTS: Use UrlTemplateImageryProvider with the tile URL template and min/max zoom constraints

The provider is added to viewer.imageryLayers, then applyAppearance sets the show and alpha properties.

GeoJSON Data Sources

The async createGeoJson method loads data via Cesium.GeoJsonDataSource.load:

  1. Pass style options (fill color, stroke color, stroke width, marker color) directly to the loader
  2. Enable clampToGround for proper terrain draping
  3. Add to viewer.dataSources
  4. Apply appearance, including opacity handling through applyGeoJsonStyle

Style updates recompute fill and stroke colors using the layer's opacity and fillOpacity properties. An appliedAlpha guard prevents recalculating identical color values.

3D Tilesets

The async createTileset method handles streaming 3D content:

  1. Resolve request headers (including Google Photorealistic API keys) via resolveThreeDTilesRequestHeaders
  2. Construct a Cesium.Resource if custom headers are required
  3. Instantiate via Cesium3DTileset.fromUrl with the resource and URL
  4. Apply an optional altitude offset via applyTilesetAltitude, which translates the tileset's model matrix
  5. Set visibility and opacity via applyAppearance

Managing Appearance and Opacity

Each layer type implements optimized appearance handling:

  • Imagery: Direct manipulation of imageryLayer.show and imageryLayer.alpha
  • GeoJSON: Visibility toggles the DataSource show property, while opacity requires re-running applyGeoJsonStyle to blend fill and stroke colors with the alpha channel
  • 3D Tiles: Simple show/hide on the Cesium3DTileset instance; opacity is applied to the tileset's style if supported

These updates occur outside the rebuild cycle, ensuring that UI-driven property changes remain responsive.

Implementation Example

The following TypeScript example demonstrates initializing the sync helper and applying a mixed layer stack:

import { CesiumLayerSync } from "@geolibre/map";
import { Viewer } from "cesium";
import type { GeoLibreLayer } from "@geolibre/core";

// Initialize Cesium Viewer
const viewer = new Viewer(document.getElementById("cesiumContainer")!);

// Create the sync instance
const cesiumSync = new CesiumLayerSync(Cesium, viewer);

// Typical store update handler
function onStoreChange(layers: GeoLibreLayer[]) {
  cesiumSync.sync(layers);
}

// Example layer configurations
const layers: GeoLibreLayer[] = [
  // XYZ satellite imagery
  {
    id: "satellite",
    type: "xyz",
    source: { 
      tiles: ["https://server/tile/{z}/{x}/{y}.png"], 
      maxzoom: 19 
    },
    visible: true,
    opacity: 0.9,
  },
  // GeoJSON road network
  {
    id: "roads",
    type: "geojson",
    geojson: { 
      type: "FeatureCollection", 
      features: [] 
    },
    style: { 
      strokeColor: "#ff0000", 
      strokeWidth: 3, 
      fillOpacity: 0.0 
    },
    visible: true,
    opacity: 1.0,
  },
  // 3D city model
  {
    id: "city-3d",
    type: "3d-tiles",
    source: { 
      url: "https://example.com/tileset/tileset.json", 
      altitudeOffset: 20 
    },
    visible: true,
    opacity: 1.0,
  },
];

// Synchronize with the globe
cesiumSync.sync(layers);

Summary

  • CesiumLayerSync in packages/map/src/cesium-layer-sync.ts manages the lifecycle of Cesium objects based on store state changes
  • Supported types include XYZ (ImageryLayer), GeoJSON (GeoJsonDataSource), and 3D Tiles (Cesium3DTileset), validated via isCesiumSupportedLayerType
  • Rebuild detection through needsRebuild separates expensive reconstruction from cheap property updates, keeping opacity changes performant
  • LayerEntry registry tracks Cesium handles and previous state to enable precise diffing and cleanup
  • Imagery reordering runs as a separate optimization pass to maintain z-order without O(n²) penalties during continuous updates

Frequently Asked Questions

What triggers a full rebuild of a Cesium layer instead of a style update?

A rebuild occurs when needsRebuild detects changes to the layer type, source URLs (tile endpoints or GeoJSON data), or GeoJSON-specific style properties like fill and stroke colors. Simple visibility toggles or opacity adjustments are applied in place without destroying the underlying Cesium object.

How does GeoLibre handle the ordering of multiple raster layers on the Cesium globe?

Because Cesium appends new imagery to the top of the stack by default, GeoLibre tracks whether any imagery layers were rebuilt during the sync cycle. If so, it executes a secondary pass that raises each imagery layer to its correct index according to the store's layer order, ensuring the visual hierarchy matches the application's state.

How is opacity implemented differently for GeoJSON compared to raster imagery?

Raster imagery uses Cesium's native alpha property directly on the ImageryLayer. GeoJSON opacity requires recalculating color values through applyGeoJsonStyle, which blends the layer's opacity and fillOpacity settings with the configured stroke and fill colors, then reapplies them to the data source entities.

Can 3D Tiles layers include authentication headers or API keys?

Yes. The createTileset method calls resolveThreeDTilesRequestHeaders to assemble authentication headers (such as Google Photorealistic 3D Tiles API keys). These headers are bundled into a Cesium.Resource object and passed to Cesium3DTileset.fromUrl, ensuring secure access to protected tile endpoints.

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 →