How Built-In Plugins Like maplibre‑3d‑tiles Integrate with the GeoLibre Store
Built-in plugins in GeoLibre integrate with the store through a Zustand-based useAppStore, receiving a GeoLibreAppAPI during activation that enables reading state, subscribing to changes, and dispatching layer mutations while MapLibre controls and Deck.gl overlays remain synchronized.
GeoLibre's plugin architecture depends on a centralized Zustand store (@geolibre/core) that serves as the single source of truth for layers, UI state, and map view. The maplibre‑3d‑tiles plugin demonstrates how built-in plugins register, interact with this store, and maintain bidirectional synchronization between user controls and application state.
How the Plugin Registration Flow Works
Plugins reside in packages/plugins/src/plugins/ and activate during app initialization. The registration process creates the bridge between plugin code and the core store.
Plugin Manager Initialization
In apps/geolibre-desktop/src/hooks/usePlugins.ts, the desktop app instantiates a PluginManager and registers built-in plugins:
// apps/geolibre-desktop/src/hooks/usePlugins.ts
import { maplibre3dTilesPlugin } from "@geolibre/plugins";
const manager = new PluginManager();
manager.registerAll([
maplibreLayerControlPlugin,
maplibre3dTilesPlugin, // ← built-in 3D-Tiles plugin
// ...
]);
Each plugin receives manager.activate(id, appAPI), passing a GeoLibreAppAPI object that exposes safe, high-level methods: addMapControl, addTileLayer, registerExternalNativeLayer, getDeckGL, and others.
The Plugin Interface Contract
All plugins implement the GeoLibrePlugin interface defined in packages/plugins/src/types.ts:
export interface GeoLibrePlugin {
id: string;
name: string;
version: string;
activate(app: GeoLibreAppAPI): void | Promise<void>;
deactivate(app: GeoLibreAppAPI): void | Promise<void>;
}
The activate method is where plugins establish store subscriptions, create UI controls, and register event listeners.
Store Integration Patterns in maplibre‑3d‑tiles
The 3D-Tiles plugin (packages/plugins/src/plugins/maplibre-3d-tiles.ts) implements three core interaction patterns with useAppStore.
Reading Current State with getState()
Plugins access the store snapshot directly for initial reads:
import { useAppStore } from "@geolibre/core";
// Inside activation or event handlers
const currentLayers = useAppStore.getState().layers;
const mapView = useAppStore.getState().mapView;
This pattern enables plugins to hydrate their UI from existing state—for example, restoring 3D-Tiles layers when a project file loads.
Subscribing to State Changes
The plugin registers a Zustand subscription to react to layer mutations from any source (other plugins, the layer panel, undo/redo, etc.):
threeDTilesStoreUnsubscribe ??= useAppStore.subscribe((state, previous) => {
if (state.layers !== previous.layers) {
updateGooglePhotorealisticTilesPanelList(control);
}
// Synchronize visibility, opacity, removals
syncLayerVisibility(state.layers);
syncLayerOpacity(state.layers);
});
The subscription callback receives both current and previous state, allowing fine-grained change detection. When layers change, the plugin updates its control UI and reconciles MapLibre/Deck.gl layer states.
Writing Mutations Through Store Helpers
User interactions in the plugin's control panel dispatch actions to the store:
// Adding a new 3D-Tiles layer
useAppStore.getState().addLayer({
id: crypto.randomUUID(),
name: "Google Photorealistic 3D Tiles",
type: "3d-tiles",
source: { type: "3d-tiles", url, altitudeOffset: 0 },
visible: true,
opacity: 1,
metadata: {
sourceKind: "3d-tiles-url",
externalNativeLayer: true,
nativeLayerIds: [],
},
});
// Updating visibility from checkbox toggle
useAppStore.getState().updateLayer(layerId, { visible: isChecked });
// Removing a layer
useAppStore.getState().removeLayer(layerId);
All mutations flow through the store, ensuring that layer panel, style editor, time slider, and persistence layer receive identical updates.
MapLibre Control Integration
The plugin creates a ThreeDTilesControl from the maplibre-gl-3d-tiles library and mounts it through the app API:
const threeDTilesControl = new ThreeDTilesControl({ /* options */ });
app.addMapControl(threeDTilesControl, "top-left");
Control event listeners bridge UI actions to store mutations:
threeDTilesControl.on("statechange", (event) => {
const { layerId, visible, opacity, url } = event.detail;
if (visible !== undefined) {
useAppStore.getState().updateLayer(layerId, { visible });
}
if (opacity !== undefined) {
useAppStore.getState().updateLayer(layerId, { opacity });
}
// URL changes trigger layer replacement
});
On deactivation, the plugin cleans up:
app.removeMapControl(threeDTilesControl);
threeDTilesStoreUnsubscribe?.();
Deck.gl Overlay for Google Photorealistic Tiles
Google Photorealistic 3D-Tiles require Deck.gl rendering instead of MapLibre's native 3D-Tiles support. The plugin obtains the shared Deck.gl instance:
const deck = await app.getDeckGL();
It then registers layers in the shared overlay system:
setSharedDeckLayers("google-3d-tiles", [
new Tile3DLayer({
id: "google-photorealistic",
data: url,
loadOptions: { fetch: { headers: { "X-GOOG-API-KEY": key } } },
// ...
}),
]);
The overlay is ref-counted—the plugin acquires a Mercator projection lock when active, forcing the map into a compatible coordinate system. When the plugin deactivates or layers are removed, it releases the lock:
releaseSharedDeckLayers("google-3d-tiles");
Layer Persistence and Restoration
The plugin enables project file round-trips through store-schema conformance. Layer objects include metadata.sourceKind for identification:
const THREE_D_TILES_SOURCE_KIND = "3d-tiles-url";
// In layer definition
metadata: {
sourceKind: THREE_D_TILES_SOURCE_KIND,
externalNativeLayer: true,
nativeLayerIds: [], // Populated by registerExternalNativeLayer
}
On app start or project load, restoreThreeDTilesLayers(app) scans the store:
function restoreThreeDTilesLayers(app: GeoLibreAppAPI) {
const layers = useAppStore
.getState()
.layers.filter(l => l.metadata?.sourceKind === THREE_D_TILES_SOURCE_KIND);
for (const layer of layers) {
recreateControlForLayer(layer);
syncToMapLibreOrDeckGL(layer);
}
}
Because the canonical data lives in the store, restoration requires no external state—the plugin rehydrates UI and rendering layers from GeoLibreLayer objects.
Complete Layer Addition Example
Here's the full pattern for adding a 3D-Tiles layer from plugin code:
import { useAppStore } from "@geolibre/core";
import type { GeoLibreLayer, GeoLibreAppAPI } from "@geolibre/plugins";
const DEFAULT_LAYER_STYLE = {
color: [255, 255, 255],
opacity: 1.0,
};
function addThreeDTilesLayer(
app: GeoLibreAppAPI,
url: string,
name: string
): string {
const id = crypto.randomUUID();
const layer: GeoLibreLayer = {
id,
name,
type: "3d-tiles",
source: {
sourceId: id,
type: "3d-tiles",
url,
altitudeOffset: 0,
},
visible: true,
opacity: 1,
style: { ...DEFAULT_LAYER_STYLE },
metadata: {
sourceKind: "3d-tiles-url",
externalNativeLayer: true,
nativeLayerIds: [],
},
sourcePath: url,
};
// Single source of truth update
useAppStore.getState().addLayer(layer);
// Register with app API for native layer tracking
app.registerExternalNativeLayer(id, {
// native layer configuration
});
return id;
}
Key Design Principles
- Store as source of truth: No plugin maintains parallel layer state; all read from
useAppStore - API abstraction: Plugins never call MapLibre or Deck.gl directly—only through
GeoLibreAppAPImethods - Bidirectional sync: Store subscriptions update plugin UI; plugin events write store mutations
- Schema conformance:
metadata.sourceKindenables generic restoration without plugin-specific serialization
Key Files
| File | Role |
|---|---|
packages/plugins/src/plugins/maplibre-3d-tiles.ts |
Full 3D-Tiles plugin implementation with store subscriptions, control handling, Deck.gl integration |
apps/geolibre-desktop/src/hooks/usePlugins.ts |
Plugin registration and GeoLibreAppAPI instantiation |
packages/core/src/store.ts |
Zustand store definition (useAppStore) |
packages/plugins/src/types.ts |
GeoLibrePlugin and GeoLibreAppAPI type definitions |
apps/geolibre-desktop/src/components/panels/LayerPanel.tsx |
Store consumer example—UI that reads layer state |
Summary
- maplibre‑3d‑tiles integrates with the GeoLibre store through direct
useAppStoreimports and a subscription-based reactivity model - The plugin receives a
GeoLibreAppAPIduring activation, providing safe abstractions over MapLibre and Deck.gl - Read pattern:
useAppStore.getState()for snapshot access - Listen pattern:
useAppStore.subscribe()for reactive UI updates - Write pattern: Store helpers (
addLayer,updateLayer,removeLayer) dispatched from control event handlers metadata.sourceKindenables persistence and restoration without custom serialization logic- Separation of concerns keeps plugin code focused on UI translation while the store maintains canonical application state
Frequently Asked Questions
What is the GeoLibreAppAPI and why do plugins use it?
GeoLibreAppAPI is an abstraction layer that exposes controlled methods for plugins to interact with the map and UI. Instead of calling MapLibre or Deck.gl directly—which could cause state synchronization bugs—plugins use methods like addMapControl, getDeckGL, and registerExternalNativeLayer. This ensures all modifications flow through or are tracked by the central store, maintaining consistency across the application.
How does the 3D-Tiles plugin handle Google Photorealistic Tiles differently?
Google Photorealistic 3D-Tiles are rendered through Deck.gl rather than MapLibre's native 3D-Tiles renderer. The plugin calls app.getDeckGL() to access a shared overlay instance, then uses setSharedDeckLayers() to register Tile3DLayer instances. The overlay system manages reference counting and projection locking, automatically switching the map to Mercator projection when these layers are active.
Can external plugins access the store the same way as built-in plugins?
Yes. Any plugin—built-in or external—can import { useAppStore } from "@geolibre/core" and use the same getState(), subscribe(), and helper methods. The GeoLibreAppAPI interface is stabilized in packages/plugins/src/types.ts, and the plugin registration in apps/geolibre-desktop/src/hooks/usePlugins.ts treats built-in and external plugins identically during activation.
How does layer state survive when a project is saved and reopened?
Layer objects stored in useAppStore conform to the GeoLibreLayer schema, which includes metadata.sourceKind identifiers. When a project loads, the 3D-Tiles plugin's restoreThreeDTilesLayers() function filters layers by this kind and reconstructs the control UI and rendering layers from the stored properties. No plugin-specific serialization is required—the generic layer library handles persistence, and plugins rehydrate from canonical store data.
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 →