How to Configure MapLibre Layer Synchronization with deck.gl Raster Overlays in GeoLibre
GeoLibre uses a single shared interleaved deck.gl overlay to synchronize MapLibre GL JS base maps with deck.gl raster overlays, ensuring all layers coexist without overwriting each other while maintaining consistent view state.
GeoLibre is an open-source geospatial framework that combines MapLibre GL JS for base map rendering with deck.gl for advanced 3-D and raster visualizations. Configuring MapLibre layer synchronization with deck.gl raster overlays requires a specific architectural pattern implemented in packages/plugins/src/plugins/shared-deck-overlay.ts to prevent layer conflicts and ensure proper draw order.
Understanding the Shared Overlay Architecture
The core challenge is that deck.gl only allows one interleaved MapboxOverlay per map instance (accessed via map.__deck). Adding a second overlay replaces the existing layer list, causing previously added layers to disappear. GeoLibre solves this by funneling every producer—raster controls, Google Photorealistic 3D Tiles, and vector visualizations—through a single shared overlay.
The system maintains a strict source draw order defined in the SOURCE_DRAW_ORDER constant (line 35 of shared-deck-overlay.ts). Layers render from bottom to top as follows: raster layers, Google 3D tiles, deck.gl visualizations, and finally route animations. This mirrors standard GIS stacking principles and guarantees raster backgrounds remain beneath 3-D structures.
Core Implementation in shared-deck-overlay.ts
Layer Aggregation and Ordering
The overlay aggregates layers from multiple sources using the setSharedDeckLayers(source, layers) function (lines 44-48). Each producer registers its layers under a specific SharedDeckSource key such as "raster", "google-3d-tiles", or "deckviz". The aggregatedLayers() function (lines 73-79) merges these arrays according to SOURCE_DRAW_ORDER before passing them to the overlay.
Lazy Mounting and Retry Logic
The overlay initializes lazily via renderSharedDeckOverlay() (lines 82-99). It only mounts when at least one layer exists, and includes a retry mechanism (scheduleMountRetry, lines 108-126) that attempts reconnection for approximately two seconds if the map isn't ready, preventing race conditions during startup.
GPU Device Sharing
deck.gl's Luma GPU device is exposed to other producers through onSharedDeckDevice (lines 59-70). This callback immediately invokes listeners if the device exists and re-invokes on every map re-bind, enabling efficient texture allocation for raster controls without creating duplicate GPU contexts.
Step-by-Step Implementation Guide
Initialize the Shared Overlay
Before adding any raster layers, ensure the shared overlay exists. This function is idempotent and serializes concurrent calls via ensureInFlight.
import { ensureSharedDeckOverlay } from "./shared-deck-overlay";
// app is the GeoLibreAppAPI passed to every plugin
await ensureSharedDeckOverlay(app);
Register Raster Layers
Create your deck.gl raster layer and register it under the "raster" source key. This places it at the bottom of the layer stack.
import type { Layer } from "@deck.gl/core";
import { setSharedDeckLayers, SharedDeckSource } from "./shared-deck-overlay";
const rasterLayer: Layer = {
type: "TileLayer",
data: "https://my.bucket/tiles/{z}/{x}/{y}.tif",
// Additional configuration (opacity, bounds, etc.)
};
setSharedDeckLayers("raster" as SharedDeckSource, [rasterLayer]);
Update or Remove Layers Dynamically
Replace existing raster layers or clear them completely without affecting other deck.gl content.
// Update with new data
setSharedDeckLayers("raster" as SharedDeckSource, [newRasterLayer]);
// Remove all raster layers
setSharedDeckLayers("raster" as SharedDeckSource, []);
Access the Shared GPU Device
For custom texture creation or advanced GPU operations, subscribe to the shared Luma device.
import { onSharedDeckDevice } from "./shared-deck-overlay";
const unsubscribe = onSharedDeckDevice((device) => {
// device is the LumaGPUDevice for texture allocation
console.log("Shared deck device ready:", device);
});
// Cleanup when component unmounts
unsubscribe();
Complete Plugin Example
Below is a minimal plugin structure that implements a raster overlay using the shared deck.gl system.
import type { GeoLibrePlugin } from "../types";
import { ensureSharedDeckOverlay, setSharedDeckLayers } from "./shared-deck-overlay";
export const RASTER_PLUGIN_ID = "custom-raster-plugin";
export const customRasterPlugin: GeoLibrePlugin = {
id: RASTER_PLUGIN_ID,
name: "Custom Raster Overlay",
version: "1.0.0",
activeByDefault: false,
activate: async (app) => {
await ensureSharedDeckOverlay(app);
const layer = {
type: "TileLayer",
data: "https://example.com/cog/{z}/{x}/{y}.tif",
opacity: 0.8
};
setSharedDeckLayers("raster", [layer]);
},
deactivate: () => {
// Clean up to prevent memory leaks
setSharedDeckLayers("raster", []);
},
};
Summary
- Single shared overlay: GeoLibre uses one interleaved
MapboxOverlayinstance inshared-deck-overlay.tsto prevent layer overwriting and ensure all producers share the same GPU context. - Defined draw order: The
SOURCE_DRAW_ORDERconstant guarantees raster layers render beneath 3-D tiles and vector visualizations. - Layer aggregation: Use
setSharedDeckLayers()with source keys ("raster","deckviz", etc.) and letaggregatedLayers()handle the merge. - Lazy initialization: The overlay mounts only when needed with automatic retry logic for map readiness.
- GPU efficiency: Access the shared Luma device via
onSharedDeckDevice()for texture operations without context duplication.
Frequently Asked Questions
Why can't I create multiple deck.gl overlays on the same MapLibre map?
deck.gl's MapboxOverlay is designed to interleave with the base map using a single instance attached to map.__deck. Creating a second overlay replaces the first one's layer list, causing previously added layers to disappear. GeoLibre's shared overlay architecture prevents this by aggregating all layers through one central interface.
How do I control the stacking order of raster overlays relative to 3-D tiles?
Register your raster layers with the "raster" source key. The aggregatedLayers() function in shared-deck-overlay.ts (lines 73-79) automatically orders layers according to the SOURCE_DRAW_ORDER array: raster at the bottom, followed by Google 3D tiles, then deck.gl visualizations. To change the order, modify the constant in the source file.
What happens if the map isn't ready when I try to add layers?
The renderSharedDeckOverlay() function includes a retry mechanism (scheduleMountRetry, lines 108-126) that attempts to mount the overlay for approximately two seconds if addMapControl fails. This handles race conditions during map initialization without throwing errors.
Can I access deck.gl's GPU device for custom texture operations?
Yes. Use the onSharedDeckDevice() callback (lines 59-70) to receive the Luma GPU device instance. The callback fires immediately if the device exists and re-fires on every map re-bind, ensuring your texture allocations stay synchronized with the active deck.gl context.
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 →