How to Sync GeoLibre Layers Between the Store and MapLibre Using MapController.syncLayers

GeoLibre maintains map layer state in a centralized Zustand store and reconciles it with MapLibre through MapController.syncLayers, which computes minimal diffs via the helper logic in packages/map/src/layer-sync.ts to update sources, layers, and styling efficiently.

GeoLibre is an open-source geospatial framework that separates application state from rendering logic. The @geolibre/core package manages layer definitions—visibility, opacity, style, and ordering—in a reactive Zustand store, while the @geolibre/map package handles MapLibre rendering. When these states diverge, the MapController.syncLayers method bridges the gap, ensuring the visual map stays synchronized with the underlying data model.

The Architecture of GeoLibre Layer Synchronization

The Zustand Store as Source of Truth

The Zustand store defined in packages/core/src/store.ts acts as the single source of truth for all layer metadata. This store maintains arrays of layer objects containing properties like visible, opacity, zIndex, and style definitions. When any layer property changes—whether from user interaction in the UI or programmatic updates—the store emits a subscription event that triggers the synchronization pipeline.

MapController and the syncLayers Entry Point

The MapController class exposed by the @geolibre/map package provides the primary entry point for synchronization. Its syncLayers method accepts the current layer list from the store and delegates the complex diff-and-apply work to specialized helper modules. This design keeps the controller thin and testable while delegating specific implementation details to domain-specific sync handlers.

The Eight-Step Synchronization Process

When MapController.syncLayers is invoked, it executes a precise pipeline to minimize DOM manipulation and maintain rendering performance:

  1. Store-driven change detection – The Zustand store detects mutations in the layers slice and notifies subscribers.

  2. MapController.syncLayers invocation – The controller receives the new layer array and initiates the sync cycle.

  3. Diffing in layer-sync.ts – The helper in packages/map/src/layer-sync.ts compares the current MapLibre map state against the desired store state, building three action queues:

    • Add – Sources and layers that exist only in the store
    • Update – Existing objects whose properties (style, visibility, order) changed
    • Remove – MapLibre objects no longer present in the store
  4. Source creation – For vector data, the controller creates GeoJSON sources or DuckDB-WASM spatial sources for local files. For raster data, it instantiates raster sources using URLs or local MBTiles protocols.

  5. Layer creation – Corresponding MapLibre layers (fill, line, symbol, raster, etc.) are added with styles derived from the store's style definitions.

  6. Ordering – Layers are re-ordered to match the store's zIndex values, ensuring correct stacking of vector, raster, and UI overlay layers.

  7. Visibility and opacity updates – The controller updates layout.visibility and paint properties to reflect the store's visible flag and opacity settings.

  8. Event hooks – Post-sync hooks fire for the layer-control UI and plugins that react to new source availability.

Implementation Details in packages/map/src/layer-sync.ts

The Three Action Queues

The core diffing logic resides in packages/map/src/layer-sync.ts. This module analyzes the delta between the existing MapLibre map style and the target state defined in the store. It categorizes required changes into Add, Update, and Remove operations, then executes them in a specific sequence to avoid reference errors—removing obsolete layers before adding new ones, for instance.

Specialized Layer Type Handling

Beyond standard MapLibre layers, GeoLibre supports specialized rendering engines through dedicated sync modules. The packages/map/src/cesium-layer-sync.ts file handles synchronization for Cesium-based layers, while the plugin architecture in packages/plugins/src/plugins/*-layer-sync.ts (such as web-service-sync.ts) allows third-party extensions to register custom sync handlers that integrate into the same syncLayers pipeline.

Practical Code Examples

Adding GeoJSON Layers via Store Updates

When you add a layer through the store, syncLayers runs automatically behind the scenes:

import { useStore } from '@geolibre/core';
import { geoJsonLayerFromFile } from '@geolibre/processing';

const store = useStore();

async function addSampleLayer(file: File) {
  const layer = await geoJsonLayerFromFile(file);
  store.addLayer(layer);
  // MapController automatically syncs due to store subscription
}

Manually Triggering MapController.syncLayers

For testing or deterministic timing, trigger synchronization explicitly:

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

function forceSync() {
  const layers = store.getState().layers;
  mapController.syncLayers(layers);
}

Bulk Style Updates with Custom Sync

After modifying multiple layer properties, ensure the map reflects changes immediately:

function updateAllOpacity(opacity: number) {
  store.updateAllLayers({ opacity });
  mapController.syncLayers(store.getState().layers);
}

Summary

  • GeoLibre uses a Zustand store in packages/core/src/store.ts as the single source of truth for layer state.
  • MapController.syncLayers orchestrates the synchronization between store state and MapLibre rendering.
  • The diffing algorithm in packages/map/src/layer-sync.ts optimizes performance by computing minimal sets of Add, Update, and Remove actions.
  • Source creation handles diverse data types including GeoJSON, DuckDB-WASM, and raster/MBTiles protocols.
  • Layer ordering respects the zIndex property from the store to maintain correct visual stacking.
  • Specialized sync handlers exist for Cesium (cesium-layer-sync.ts) and plugin architectures.

Frequently Asked Questions

What triggers MapController.syncLayers automatically?

The method subscribes to the Zustand store's layers slice. Whenever you call store actions like addLayer, removeLayer, or updateLayer, the subscription callback executes syncLayers with the new state, ensuring the map updates reactively without manual intervention.

How does the diffing algorithm in layer-sync.ts handle complex updates?

The algorithm in packages/map/src/layer-sync.ts compares layer IDs and their style fingerprints between the store and the current MapLibre style specification. It batches Add operations for new layers, Update operations for modified properties like opacity or visibility, and Remove operations for deleted layers, applying them in a dependency-safe order to prevent dangling source references.

Can I extend syncLayers for custom visualization engines?

Yes. GeoLibre's architecture allows you to register additional sync handlers alongside the default MapLibre implementation. You can create modules following the pattern in packages/map/src/cesium-layer-sync.ts or packages/plugins/src/plugins/*-layer-sync.ts to handle rendering engines like Deck.gl or specialized WebGL visualizations, then invoke them within the syncLayers pipeline.

Where is the layer synchronization behavior tested?

The test suite in tests/layer-control-style-sync.test.ts validates the synchronization logic, ensuring that store mutations correctly propagate to MapLibre instances and that the diffing algorithm produces the expected map style transformations under various edge cases.

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 →