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,
syncGeoJsonVtLayerconverts the data to MVT tiles on the fly. - Standard GeoJSON: Otherwise,
syncGeoJsonLayercreates 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.layersto populate the sidebar tree. - Attribute table: Queries the store to access
layer.geojson.featuresfor tabular display. - Feature picker: Uses the layer ID to identify intersected features.
- Story-map logic: References
layer.metadatafor 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:
packages/core/src/store.ts– Defines theaddGeoJsonLayermethod and theGeoLibreLayerTypeScript interface.packages/map/src/map-controller.ts– Contains thesyncLayersmethod that orchestrates layer reconciliation between the store and MapLibre.packages/map/src/layer-sync.ts– ImplementssyncLayer,syncGeoJsonLayer, andsyncGeoJsonVtLayer, handling the routing logic for different rendering paths.packages/plugins/src/types.ts– Exposes theaddGeoJsonLayersignature to the plugin API, ensuring type safety for third-party extensions.
Summary
addGeoJsonLayerinpackages/core/src/store.tsis the sole entry point for adding vector data, creating a completeGeoLibreLayerrecord with UUID, style, and provenance.- The Zustand store broadcasts changes to the
MapController, which runssyncLayersto reconcile the layer list. syncLayerinpackages/map/src/layer-sync.tsroutes 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →