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 GeoLibreAppAPI methods
  • Bidirectional sync: Store subscriptions update plugin UI; plugin events write store mutations
  • Schema conformance: metadata.sourceKind enables 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 useAppStore imports and a subscription-based reactivity model
  • The plugin receives a GeoLibreAppAPI during 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.sourceKind enables 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →