How GeoLibre Handles Layer Synchronization Between Store and MapLibre GL JS
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, 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, 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:
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:
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:
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.syncLayersinpackages/map/src/map-controller.ts, which orchestrates the complete update cycle. - The
syncLayerdispatcher inpackages/map/src/layer-sync.tsroutes 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
appliedBridgeStatefor paint caching andexternalNativeBaseFiltersfor 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →