How addGeoJsonLayer and GeoLibreLayer Records Flow Through the GeoLibre Application

The addGeoJsonLayer method creates a GeoLibreLayer record in the Zustand store, which triggers MapController.syncLayers to reconcile the state with MapLibre sources and style layers, rendering the GeoJSON while enabling undo/redo and cross-component reactivity.

GeoLibre is an open-source geospatial visualization framework that treats every visual element as a structured data record. When you call addGeoJsonLayer, the application executes a precise eight-step flow that transforms raw GeoJSON into a rendered map layer while maintaining synchronization across the UI, history system, and plugin API.

The Flow from Store to Map

The architecture separates state management from rendering. The Zustand store (useAppStore) owns the truth, while the MapController subscribes to changes and translates records into MapLibre primitives.

Step 1: Invoking the Store Method

When a user imports a vector file or a plugin programmatically adds data, the entry point is addGeoJsonLayer defined in packages/core/src/store.ts.

// Called from UI components or plugins
const layerId = useAppStore.getState().addGeoJsonLayer(
  'Countries', 
  geojsonData, 
  '/data/countries.geojson'
);

The method accepts a display name, the raw GeoJSON object, and an optional sourcePath string for provenance tracking.

Step 2: Creating the GeoLibreLayer Record

Inside the store, addGeoJsonLayer constructs a complete GeoLibreLayer object with a unique UUID and computed styling.

const id = uuidv4();
const layer: GeoLibreLayer = {
  id,
  name,
  type: 'geojson',
  source: { type: 'geojson' },
  visible: true,
  opacity: 1,
  style: initialLayerStyle({ 
    geojson, 
    layers: get().layers, 
    overrides: { /* ... */ } 
  }),
  metadata: {},
  geojson,
  sourcePath,
};

The record encapsulates the raw geometry, visualization defaults, and an optional file path reference.

Step 3: Updating Application State

The store’s internal addLayer reducer inserts the record into state.layers and marks the project as dirty. Because Zustand emits change events to all subscribers, the MapController (instantiated by the MapCanvas component) receives the updated layer list immediately.

Step 4: MapController Synchronization

The MapController class in packages/map/src/map-controller.ts listens to store changes and invokes syncLayers(layers), iterating bottom-to-top to preserve z-order.

syncLayers(layers) {
  for (let i = layers.length - 1; i >= 0; i--) {
    const layer = layers[i];
    syncLayer(this.map, layer, this.getBeforeStyleLayerId(layers, i));
  }
}

Step 5: Rendering Path Selection

The syncLayer function in packages/map/src/layer-sync.ts acts as a router, applying three distinct rendering strategies based on the layer’s properties:

  • Deck.gl overlay: If the layer’s style enables 3-D elevation and the GeoJSON contains Z coordinates, the layer is removed from MapLibre and handed to the Deck.gl overlay for extruded rendering.
  • Vector tiles: If the GeoJSON exceeds a size threshold benefiting from tiled rendering, syncGeoJsonVtLayer converts the data to MVT tiles on the fly.
  • Standard GeoJSON: Otherwise, syncGeoJsonLayer creates a native MapLibre source and style layer.

Step 6: MapLibre Integration

For standard layers, syncGeoJsonLayer executes the MapLibre API calls:

// Create the source
map.addSource(layer.id, {
  type: 'geojson',
  data: layer.geojson
});

// Add the style layer with proper z-ordering
map.addLayer(styleLayer, beforeId);

The map now renders the data according to the style definition stored in the GeoLibreLayer record.

Step 7: Sub-system Reactions

Because the store is the single source of truth, multiple subsystems react to the new record without direct coupling:

  • Layer control panel: Reads state.layers to populate the sidebar tree.
  • Attribute table: Queries the store to access layer.geojson.features for tabular display.
  • Feature picker: Uses the layer ID to identify intersected features.
  • Story-map logic: References layer.metadata for narrative sequencing.

Step 8: Undo and Redo Support

The addition is recorded as a single atomic action in the ZUndo history manager. The history captures the new layer ID and its full definition, enabling perfect restoration of the application state during undo/redo operations without orphaning MapLibre sources.

Implementation Examples

The following patterns demonstrate how to interact with the flow programmatically:

Adding a layer from a UI event:

import { useAppStore } from '@geolibre/core';

async function onFileUpload(file: File) {
  const geojson = await file.text().then(JSON.parse);
  const layerId = useAppStore.getState().addGeoJsonLayer(
    file.name, 
    geojson, 
    file.path
  );
  console.log('Created layer:', layerId);
}

Conditional rendering logic inside syncLayer:

if (layer.type === 'geojson' && layer.geojson) {
  if (layer.style.elevation && hasZCoordinates(layer.geojson)) {
    // Route to Deck.gl for 3D extrusion
    syncDeckOverlayLayer(map, layer);
  } else if (shouldUseTiledRendering(layer.geojson)) {
    syncGeoJsonVtLayer(map, layer, beforeId);
  } else {
    syncGeoJsonLayer(map, layer, beforeId);
  }
}

Key Source Files

Understanding the flow requires familiarity with these specific modules:

Summary

  • addGeoJsonLayer in packages/core/src/store.ts is the sole entry point for adding vector data, creating a complete GeoLibreLayer record with UUID, style, and provenance.
  • The Zustand store broadcasts changes to the MapController, which runs syncLayers to reconcile the layer list.
  • syncLayer in packages/map/src/layer-sync.ts routes GeoJSON layers to standard MapLibre sources, tiled vector tiles, or Deck.gl overlays based on geometry complexity and styling requirements.
  • All UI components, including the layer panel and attribute table, derive their state from the central store, ensuring consistency across the application.
  • ZUndo integration provides atomic undo/redo support for layer operations by tracking the full layer definition in the history stack.

Frequently Asked Questions

What is the difference between addGeoJsonLayer and addLayer?

addGeoJsonLayer is a high-level convenience method that constructs the full GeoLibreLayer object, generates a UUID, and computes initial styles before calling the lower-level addLayer reducer. While plugins and UI code typically use addGeoJsonLayer, internal store logic uses addLayer to insert pre-constructed records during undo/redo or project loading operations.

How does GeoLibre handle large GeoJSON files?

During synchronization, syncLayer checks the GeoJSON size and complexity. If the data exceeds thresholds defined in the tiling logic, the record is routed to syncGeoJsonVtLayer, which dynamically generates vector tiles using geojson-vt. This prevents MapLibre from parsing massive geometries into memory at once, maintaining 60fps pan and zoom performance.

Can external plugins invoke addGeoJsonLayer?

Yes. The method is exposed through the plugin API defined in packages/plugins/src/types.ts. Plugins receive a typed reference to useAppStore.getState(), allowing them to call addGeoJsonLayer programmatically. The store automatically handles the subsequent synchronization, rendering, and history recording without requiring plugins to interact with MapLibre directly.

How does the undo system track layer additions?

Because addGeoJsonLayer is a single Zustand action, the ZUndo middleware captures the diff in the layers array. When undoing, the history manager restores the previous state array, which triggers syncLayers to remove the MapLibre source and style layer. When redoing, the action replays, recreating the GeoLibreLayer record and re-adding the sources to the map in their original order.

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 →