# How GeoLibre Implements Deck.gl Integration for Advanced WebGL Map Visualizations

> Discover how GeoLibre integrates deck.gl for advanced WebGL map visualizations. Experience high-performance scatter plots, 3D tiles, and heatmaps with synchronized views.

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

---

**GeoLibre implements deck.gl integration through a singleton `MapboxOverlay` that interleaves WebGL-accelerated visualizations with MapLibre GL JS, enabling high-performance scatter plots, 3D tiles, and heatmaps while maintaining synchronized view states and deterministic draw ordering.**

The open-source GeoLibre project (opengeos/GeoLibre) combines MapLibre GL JS for raster and vector basemaps with deck.gl for advanced data visualization. This deck.gl integration architecture deliberately uses a single shared instance to render all specialized layers—from hexagon heatmaps to 3D scatter plots—ensuring minimal resource overhead and correct z-ordering relative to base map features.

## Architecture: The Singleton Shared Overlay

### The MapboxOverlay Instance

At the core of GeoLibre's deck.gl integration is the **shared overlay pattern** implemented in [`packages/plugins/src/plugins/shared-deck-overlay.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/shared-deck-overlay.ts). Rather than creating multiple deck.gl instances that compete for WebGL resources, the system maintains one `MapboxOverlay` attached to `map.__deck`.

When the first `deckgl-viz` layer activates, the overlay lazily resolves deck.gl modules via `app.getDeckGL()` and constructs a single `MapboxOverlay` instance. All subsequent producers—including the deck-viz plugin, Google 3D-Tiles integration, and COG raster overlays—contribute layers through `setSharedDeckLayers(producerId, layers)`.

### Layer Ordering Strategy

The overlay merges layer contributions according to the `SOURCE_DRAW_ORDER` constant defined at line 35 of [`shared-deck-overlay.ts`](https://github.com/opengeos/GeoLibre/blob/main/shared-deck-overlay.ts):

```typescript
const SOURCE_DRAW_ORDER = [
  "raster",
  "google-3d-tiles", 
  "deckviz",
  "route-anim"
];

```

This ordering ensures raster basemaps render first, followed by 3D tilesets, then deck.gl visualizations, maintaining correct depth buffering and visual hierarchy between WebGL contexts.

## Layer Routing and Store Integration

### The Layer Sync Router

The **layer synchronization system** in [`packages/map/src/layer-sync.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/layer-sync.ts) inspects each layer's `type` and `metadata` properties. When encountering the `deckgl-viz` source kind, it bypasses native MapLibre layer creation and instead forwards the deck.gl layer object to the shared overlay.

### Store Schema and Type Safety

Layer definitions reside in the Zustand store under the `deckgl-viz` source kind, defined in [`packages/plugins/src/plugins/deckgl-viz/store-layer.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/deckgl-viz/store-layer.ts). This separation isolates UI state from rendering logic, allowing the store to hold complex deck.gl layer configurations while the overlay handles WebGL execution.

## Activating Deck.gl Visualizations

### Plugin Activation Lifecycle

The deck-viz plugin entry point in [`packages/plugins/src/plugins/maplibre-deckgl-viz.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/maplibre-deckgl-viz.ts) exports `activateDeckViz` and `deactivateDeckViz` functions. Activation registers a UI dialog that collects layer parameters and dispatches deck.gl layer objects to the store via `store.layers.setSharedDeckLayers`.

The overlay wrapper in [`packages/plugins/src/plugins/deckgl-viz/overlay.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/deckgl-viz/overlay.ts) manages the lifecycle of the `MapboxOverlay`, handling error boundaries and cleanup when producers detach. This ensures that removing one deck.gl layer from the UI doesn't destroy the shared context needed by other visualizations.

## Third-Party Plugin Extensibility

### API Surface and Module Resolution

GeoLibre exposes deck.gl functionality to external plugins through the `GeoLibreAppAPI` interface defined in [`packages/plugins/src/types.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/types.ts) (lines 288-304). The method `getDeckGL()` returns resolved deck.gl modules (`@deck.gl/core`, `@deck.gl/layers`, etc.), allowing plugins to instantiate layers without bundling their own deck.gl copy.

This design prevents the "duplicate luma.gl" runtime error (see lines 587-591 in [`types.ts`](https://github.com/opengeos/GeoLibre/blob/main/types.ts)) that occurs when multiple deck.gl instances compete for the same WebGL context. Plugins contribute layers using `setSharedDeckLayers` with a unique producer identifier:

```typescript
// Example plugin registration
export async function register(api: GeoLibreAppAPI) {
  const deckGL = await api.getDeckGL();
  
  const layer = new deckGL.layers.ScatterplotLayer({
    id: 'plugin-scatter',
    data: [{ position: [-122.45, 37.78] }],
    getPosition: d => d.position,
    getRadius: 100,
    getFillColor: [255, 0, 0]
  });
  
  await api.setSharedDeckLayers('my-plugin-id', [layer]);
}

```

## View State Synchronization and Constraints

The `@deck.gl/mapbox` `MapboxOverlay` automatically synchronizes with MapLibre's view state, listening to `move`, `zoom`, `bearing`, and `pitch` events. This ensures that deck.gl visualizations remain geographically aligned with the basemap during user interactions.

However, the integration enforces **Mercator projection only**. As implemented in [`packages/plugins/src/types.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/types.ts) (lines 586-608), the shared overlay configures deck.gl with `"mercator"` view state because deck.gl's tiled rendering does not support MapLibre's Globe projection mode. Attempting to use deck.gl layers while the map is in Globe view will result in rendering artifacts.

## Implementation Examples

### Adding a ScatterplotLayer

To add a deck.gl visualization from within the GeoLibre ecosystem, resolve the shared deck instance and contribute layers to the overlay:

```typescript
import { useGeoLibreApp } from '@geolibre/core';
import { ScatterplotLayer } from '@deck.gl/layers';

export function AddScatterplot() {
  const app = useGeoLibreApp();

  const addLayer = async () => {
    const deck = await app.getDeckGL();
    
    const layer = new deck.layers.ScatterplotLayer({
      id: 'my-scatter',
      data: [{ position: [-122.45, 37.78], weight: 10 }],
      getPosition: d => d.position,
      getRadius: d => 100,
      getFillColor: [255, 0, 0, 180],
    });

    await setSharedDeckLayers('my-plugin', [layer as never]);
  };

  return <button onClick={addLayer}>Add Scatterplot</button>;
}

```

### Creating Hexagon Aggregation Layers

For aggregation layers like hexagon heatmaps, access the specific submodule through the resolved API:

```typescript
import { HexagonLayer } from '@deck.gl/aggregation-layers';

const deckGL = await api.getDeckGL();

const hexLayer = new deckGL.aggregationLayers.HexagonLayer({
  id: 'hex-heat',
  data: 'https://example.com/points.geojson',
  getPosition: d => d.geometry.coordinates,
  radius: 2000,
  elevationScale: 50,
});

await api.setSharedDeckLayers('hex-plugin', [hexLayer as never]);

```

## Summary

- **Singleton Pattern**: GeoLibre uses one shared `MapboxOverlay` instance in [`shared-deck-overlay.ts`](https://github.com/opengeos/GeoLibre/blob/main/shared-deck-overlay.ts) to prevent WebGL context duplication and manage resource allocation across all deck.gl visualizations.

- **Producer Architecture**: Multiple features contribute layers via `setSharedDeckLayers` with unique producer IDs, while `SOURCE_DRAW_ORDER` maintains correct z-indexing between raster, 3D tiles, and deck.gl layers.

- **Store Integration**: The [`layer-sync.ts`](https://github.com/opengeos/GeoLibre/blob/main/layer-sync.ts) router directs `deckgl-viz` layers to the shared overlay rather than creating native MapLibre layers, enabling complex WebGL rendering while preserving the layer management UI.

- **API Exposure**: `GeoLibreAppAPI.getDeckGL()` provides third-party plugins access to the host's deck.gl modules, eliminating bundle duplication and version conflicts.

- **Projection Constraints**: deck.gl integration requires Mercator projection and automatically synchronizes view state with MapLibre, but does not support Globe view mode.

## Frequently Asked Questions

### How does GeoLibre prevent duplicate deck.gl instances when multiple plugins request visualization capabilities?

GeoLibre implements a singleton `MapboxOverlay` pattern where the first activation of a deck.gl feature lazily initializes `map.__deck` via [`shared-deck-overlay.ts`](https://github.com/opengeos/GeoLibre/blob/main/shared-deck-overlay.ts). All subsequent layer requests—whether from the core deck-viz plugin, Google 3D-Tiles, or external plugins—contribute to this single instance through `setSharedDeckLayers`. This design prevents the "duplicate luma.gl" error that occurs when separate deck.gl bundles compete for WebGL contexts.

### What map projections are supported when using deck.gl layers in GeoLibre?

The deck.gl integration in GeoLibre enforces **Mercator projection only**. As implemented in [`packages/plugins/src/types.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/types.ts) (lines 586-608), the shared overlay configures deck.gl with a `"mercator"` view state because deck.gl's tiled rendering architecture does not support MapLibre's Globe projection. Users must switch to Mercator view before activating deck.gl visualizations to ensure proper geographic alignment.

### How can third-party plugins add custom deck.gl layers without bundling the library?

External plugins access the host's deck.gl modules through `GeoLibreAppAPI.getDeckGL()`, which returns resolved references to `@deck.gl/core`, `@deck.gl/layers`, and other submodules. Plugins can then instantiate any deck.gl layer class and contribute it via `api.setSharedDeckLayers('unique-producer-id', [layer])`. This approach eliminates the need for plugins to include deck.gl in their own bundle, reducing download size and ensuring version compatibility with the host application.

### Why are deck.gl layers rendered through a shared overlay instead of as native MapLibre layers?

Native MapLibre layers are optimized for vector tiles and raster sources but lack the WebGL compute capabilities required for massive dataset visualization (millions of points, 3D extrusions, GPU aggregation). The shared `MapboxOverlay` interleaves deck.gl's rendering context with MapLibre's canvas, enabling high-performance scatter plots, heatmaps, and 3D tiles while maintaining synchronized camera state and correct draw order via the `SOURCE_DRAW_ORDER` constant in [`shared-deck-overlay.ts`](https://github.com/opengeos/GeoLibre/blob/main/shared-deck-overlay.ts).