# How addGeoJsonLayer and GeoLibreLayer Records Flow Through the GeoLibre Application

> Understand how addGeoJsonLayer and GeoLibreLayer records flow in the GeoLibre app. See how Zustand store, MapController, and MapLibre sync to render GeoJSON with undo/redo support.

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

---

**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`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/store.ts)**.

```typescript
// 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.

```typescript
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`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/map-controller.ts)** listens to store changes and invokes `syncLayers(layers)`, iterating bottom-to-top to preserve z-order.

```typescript
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`](https://github.com/opengeos/GeoLibre/blob/main/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:

```typescript
// 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:**

```typescript
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:**

```typescript
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`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/store.ts)** – Defines the `addGeoJsonLayer` method and the `GeoLibreLayer` TypeScript interface.
- **[`packages/map/src/map-controller.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/map-controller.ts)** – Contains the `syncLayers` method that orchestrates layer reconciliation between the store and MapLibre.
- **[`packages/map/src/layer-sync.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/layer-sync.ts)** – Implements `syncLayer`, `syncGeoJsonLayer`, and `syncGeoJsonVtLayer`, handling the routing logic for different rendering paths.
- **[`packages/plugins/src/types.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/types.ts)** – Exposes the `addGeoJsonLayer` signature to the plugin API, ensuring type safety for third-party extensions.

## Summary

- **`addGeoJsonLayer`** in [`packages/core/src/store.ts`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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.