# How GeoLibre Handles Layer Synchronization Between Store and MapLibre GL JS

> Discover how GeoLibre synchronizes map layers between your store and MapLibre GL JS. Learn about the deterministic process managed by MapController and layer-sync for seamless updates.

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

---

**GeoLibre keeps the visual state of the map in lock-step with the application store through a deterministic synchronization process managed by the MapController and layer-sync module.**

GeoLibre is an open-source geospatial framework that maintains strict consistency between its reactive application state and the MapLibre GL JS rendering engine. The layer synchronization system ensures that every change in the store—whether adding layers, toggling visibility, or adjusting opacity—immediately reflects on the map through a coordinated dispatch pipeline.

## The Synchronization Entry Point: MapController.syncLayers

In [`packages/map/src/map-controller.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/map-controller.ts), the `syncLayers` method serves as the primary entry point for all layer updates. Called by the UI whenever the array of `GeoLibreLayer` objects changes, this method orchestrates the complete synchronization workflow.

The process begins by removing any layers that no longer exist in the store. It then iterates **bottom-up** through the new layer list, invoking `syncLayer` for each entry while passing a computed `beforeId` parameter. This guarantees correct Z-ordering by ensuring anchor layers exist before dependent layers are inserted (lines 1074–1100).

## Layer Classification and Dispatch

Located in [`packages/map/src/layer-sync.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/layer-sync.ts), the `syncLayer` function acts as a dispatcher that categorizes layers into three distinct types before routing them to specialized handlers (lines 80–86).

### External Native Layers

These include plugin-registered sources such as PMTiles, Wayback raster, basemap-control tiles, web-service tiles, generic external rasters, and external GeoJSON. The system identifies these via `isExternalNativeLayer` checks.

### Placeholder Layers

UI-only layers that don't require MapLibre rendering resources. These are filtered out early in the process via `isPlaceholderLayer`.

### Built-in Layer Types

Core geospatial primitives including `geojson`, `raster`, `vector-tiles`, `mbtiles`, `video`, and `image` layers. The dispatcher routes these to type-specific synchronization functions based on the `layer.type` field.

## Synchronizing External Native Layers

The `syncExternalNativeLayer` function manages complex plugin-provided layers through several coordinated steps:

**Source Registration**. Ensures MapLibre sources exist for PMTiles, Wayback imagery, or generic raster endpoints.

**Native Layer Creation**. Builds MapLibre style layers (`fill`, `line`, `circle`, `raster`) using `ensureLayer`. Layer IDs derive from store records or plugin-provided native IDs.

**Visibility and Opacity Management**. The `setNativeLayerVisibility` function applies the store's `visible` flag to each native layer. Paint properties forward via `setExternalNativeLayerPaint` unless the control owns paint management.

**Zoom-Range Management**. When users override default zoom ranges, the controller tracks these in `managedZoomRangeLayerIds` and updates ranges during every sync through `setLayerZoomRange`.

**Per-Feature Filters**. Time-slider windows, embed-API filters, and rule-based visibility filters combine with existing native filters using `applyExternalNativeFeatureFilters`. The system caches the last applied filter to prevent redundant `setFilter` calls.

**Ordering**. After preparation, `moveLayer` re-orders native layers according to the computed `beforeId`. For deck-gl raster layers, ordering forwards through the registered `externalDeckLayerOrderHandler`.

## Built-in Layer Handling

For standard layer types, GeoLibre provides specialized synchronization paths:

**GeoJSON Layers**. The system chooses between `syncGeoJsonVtLayer` (for vector-tile rendering) and `syncGeoJsonLayer` (for plain GeoJSON). When 3-D Z rendering is active, the MapLibre representation removes to prevent double-drawing, delegating instead to the deck-gl overlay.

**Raster and Tile Layers**. Functions including `syncRasterTileLayer`, `syncVectorTileLayer`, `syncMbtilesLayer`, `syncVideoLayer`, and `syncImageLayer` create appropriate MapLibre sources and style layers. Each mirrors the store's `style` configuration including opacity, visibility, and zoom constraints.

## Performance Optimizations and State Caching

GeoLibre implements sophisticated caching mechanisms to prevent expensive WebGL repaints:

**External Native Paint Bridge**. The `appliedBridgeState` map stores the last opacity and visibility values sent to plugin-provided paint bridges. This eliminates redundant calls that would trigger unnecessary redraws.

**Filter State Caching**. The `externalNativeBaseFilters` WeakMap holds the original filter for each native layer plus the JSON of the last combined filter. This enables cheap change detection without recomputing complex filter expressions on every sync cycle.

## Why Bottom-Up Processing Matters

The synchronization iterates through layers from bottom-to-top because of MapLibre's insertion requirements. While layers store bottom-to-top logically, MapLibre requires each new layer to be placed **beneath** the first style layer of the layer above it.

Walking the array backwards guarantees that the anchor layer (`beforeId`) already exists when the current layer inserts. This eliminates ordering glitches during initial synchronization, as noted in the comment within `syncLayers`.

## Practical Implementation Examples

Basic store synchronization:

```typescript
import { useAppStore } from '@geolibre/core';
import { MapController } from './map-controller';

const controller = new MapController();
controller.init(mapDiv, { styleUrl: basemapUrl });

function updateMap(layers: GeoLibreLayer[]) {
  // Called whenever the store's layer array changes
  controller.syncLayers(layers);
}

```

Registering a custom external-native layer from a plugin:

```typescript
import { registerExternalNativeLayer } from '@geolibre/plugins';
import { setExternalNativePaintBridge } from '@geolibre/core';

const myLayerId = 'my-plugin-layer';

registerExternalNativeLayer({
  id: myLayerId,
  nativeLayerIds: ['my-plugin-fill', 'my-plugin-line'],
  source: { type: 'vector', url: 'pmtiles://my-archive' },
  metadata: { externalNativeLayer: true, sourceKind: 'pmtiles-url' },
});

setExternalNativePaintBridge(myLayerId, {
  setOpacity: (opacity) => myPlugin.setOpacity(opacity),
  setVisibility: (visible) => myPlugin.setVisibility(visible),
});

```

Applying per-feature filters manually:

```typescript
import { applyExternalNativeFeatureFilters } from './layer-sync';
import type maplibregl from 'maplibre-gl';

function applyTimeFilter(
  map: maplibregl.Map,
  layerId: string,
  filter: maplibregl.FilterSpecification,
) {
  const nativeId = `${layerId}-fill`;
  const layer = /* retrieve the store layer object */;
  layer.timeFilter = filter;
  applyExternalNativeFeatureFilters(map, nativeId, layer);
}

```

## Summary

- GeoLibre maintains layer synchronization through `MapController.syncLayers` in [`packages/map/src/map-controller.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/map-controller.ts), which orchestrates the complete update cycle.
- The `syncLayer` dispatcher in [`packages/map/src/layer-sync.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/layer-sync.ts) routes layers to specialized handlers based on type: external native, placeholder, or built-in.
- External native layers support complex plugin integrations with dedicated handling for sources, paint properties, zoom ranges, and feature filters.
- Built-in layers (`geojson`, `raster`, `vector-tiles`, etc.) use type-specific synchronization functions that mirror store state to MapLibre internals.
- Performance optimizations include `appliedBridgeState` for paint caching and `externalNativeBaseFilters` for efficient filter change detection.
- Bottom-up iteration ensures correct Z-ordering by guaranteeing anchor layers exist before dependent layer insertion.

## Frequently Asked Questions

### What triggers layer synchronization in GeoLibre?

Layer synchronization triggers whenever the `GeoLibreLayer` array changes in the application store. The UI calls `controller.syncLayers(layers)`, which diffs the current state against the new layer list and updates MapLibre accordingly.

### How does GeoLibre handle Z-ordering for overlapping layers?

GeoLibre computes a `beforeId` parameter for each layer during the bottom-up pass through `syncLayers`. This ensures layers insert beneath the correct anchor layer, maintaining the exact stack order defined in the store without rendering glitches.

### Can plugins integrate custom layer types with GeoLibre's synchronization?

Yes. Plugins register external-native layers via `registerExternalNativeLayer`, providing native layer IDs and source definitions. They can optionally implement a paint bridge through `setExternalNativePaintBridge` to receive opacity and visibility updates while maintaining control over rendering internals.

### How does GeoLibre optimize performance during rapid layer updates?

The framework caches paint state in `appliedBridgeState` and filter state in `externalNativeBaseFilters` (a WeakMap). These caches prevent redundant WebGL calls and filter recomputations, ensuring smooth performance even when time-slider or animation events trigger frequent store updates.