GeoLibre Repository File System Exploration: Complete Architecture and Code Guide

GeoLibre is a modular, npm workspaces-based monorepo that unifies a React/MapLibre frontend, Tauri desktop client, FastAPI Python sidecar, and Jupyter anywidget into a single geospatial platform with shared state management.

The opengeos/GeoLibre repository implements a full-stack geospatial architecture designed for extensibility. Its file system organization reflects strict separation between UI applications, reusable packages, and background workers—while maintaining tight integration through a central Zustand store. This guide walks through the repository structure, key components, and practical patterns for working with GeoLibre's codebase.


Workspace Structure and Root Configuration

GeoLibre uses npm workspaces to partition the codebase into three distinct zones. The root [package.json](https://github.com/opengeos/GeoLibre/blob/main/package.json) defines these boundaries:

{
  "name": "geolibre",
  "version": "0.0.0-dev",
  "private": true,
  "type": "module",
  "workspaces": [
    "apps/*",
    "packages/*",
    "workers/*"
  ],
  "scripts": {
    "dev": "npm run -w apps/geolibre-desktop dev",
    "tauri:dev": "npm run -w apps/geolibre-desktop dev",
    "build": "npm run -w apps/geolibre-desktop build",
    "lite:build": "npm run -w apps/geolibre-desktop build --lite"
  }
}

This configuration enables:

  • Unified dependency installation – Run npm install once at the root
  • Cross-workspace imports – Packages reference each other via @geolibre/* scope
  • Targeted scripts – -w flag runs commands in specific workspaces

The type: "module" declaration enforces ES modules throughout, eliminating CommonJS friction in modern tooling.


Apps, Packages, and Workers Deep-Dive

Apps Directory (apps/*)

The apps/ workspace contains deployable user interfaces:

Application Purpose Key Path
geolibre-desktop Tauri-based desktop client apps/geolibre-desktop/src/App.tsx
geolibre-web Browser-deployable build (shared core) apps/geolibre-web/src/main.tsx

The desktop and web apps share identical React component trees. The critical difference lies in native integrations—file system access, shell execution, and the Python sidecar launcher—which are gated through Tauri's command API.


Packages Directory (packages/*)

Reusable libraries form the architectural backbone:

@geolibre/core – State Management

The central Zustand store holds the canonical project state. Located in packages/core/src/store.ts, it manages:

  • Project schema (.geolibre.json structure)
  • Layer definitions and visibility
  • Plugin state and registration
  • UI settings (theme, layout, active tools)
  • Undo/redo history stack

Critical rule: All MapLibre mutations must flow through store actions. Direct map manipulation bypasses the synchronization layer and breaks undo/redo consistency.

@geolibre/map – Rendering Engine

This package bridges the store to MapLibre-GL JS. The MapController.syncLayers method (in packages/map/src/MapCanvas.tsx) observes store changes and translates them into MapLibre source/layer operations. It also composites Deck.gl overlays for 3D data—point clouds, terrain meshes, and particle systems.

@geolibre/ui – Component Primitives

Built on shadcn/ui patterns, this workspace exports accessible React components: menus, dialogs, toolbars, and the layer tree. These primitives are theme-aware and respect the store's UI settings.

@geolibre/processing – Client-Side Computation

Vector operations, raster conversions, and format translations execute here. Heavy algorithms delegate to geolibre-wasm, a Rust-compiled WebAssembly module. The TypeScript bindings in packages/processing/src/wasm-convert.ts expose synchronous and async interfaces to WASM functions.

@geolibre/plugins – Extensibility System

Built-in plugins reside in packages/plugins/src/plugins/. Each plugin exports a manifest satisfying the GeoLibrePlugin interface:

interface GeoLibrePlugin {
  id: string;
  name: string;
  icon?: string;
  run: (store: GeoLibreStore) => Promise<void> | void;
}

Registration happens in apps/geolibre-desktop/src/hooks/usePlugins.ts:

// apps/geolibre-desktop/src/hooks/usePlugins.ts
import { terrainPlugin } from "@geolibre/plugins";
import { measurementPlugin } from "@geolibre/plugins";
// Additional imports...

export const builtInPlugins = [
  terrainPlugin,
  measurementPlugin,
  // ...remaining plugins
];

External plugins load dynamically via ZIP files containing a plugin.json manifest and bundled assets.


Workers Directory (workers/*)

Background processing units handle:

  • Viewer worker – Decodes specialized formats (e.g., proprietary sensor data) off-main-thread
  • Processing worker – Executes long-running WASM computations without blocking UI

Workers communicate with the main thread through postMessage and structured cloning, with state updates funneled back into the central store.


Python Sidecar Architecture

The backend/ directory houses a FastAPI service providing heavyweight geospatial operations:

Component Path Responsibility
FastAPI app backend/geolibre_server/app/main.py HTTP entry point, route definitions
Whitebox integration backend/geolibre_server/tools/whitebox.py Raster/terrain analysis tools
GDAL bindings backend/geolibre_server/tools/gdal_ops.py Format conversion, reprojection
GeoPandas pipeline backend/geolibre_server/tools/vector_ops.py Advanced vector processing

The desktop app launches this sidecar as a subprocess on demand. The web build proxies requests to /sidecar, enabling identical API usage across deployment targets.


Python Anywidget Package

The python/ workspace packages the compiled GeoLibre UI as a Jupyter anywidget:


# python/src/geolibre/__init__.py (conceptual structure)

import anywidget
import traitlets

class GeoLibreWidget(anywidget.AnyWidget):
    _esm = "index.js"  # Bundled from npm build

    _css = "styles.css"
    
    # Project state synchronized bi-directionally

    project = traitlets.Dict().tag(sync=True)
    active_layer = traitlets.Unicode().tag(sync=True)

Running npm run build:embed compiles the frontend and embeds it into the Python wheel, enabling pip install geolibre for notebook environments.


Data Flow: From File to Map

Understanding the ingestion pipeline clarifies how components interact:

  1. Source ingestion – Native file dialog (Tauri) or remote URL triggers loading
  2. Format detection – Extension/MIME type mapped to appropriate reader
  3. Client-side conversion – DuckDB-WASM Spatial executes ST_Read for Parquet/GeoJSON/Shapefile; fallback to shpjs for exotic formats
  4. Store insertion – New GeoLibreLayer record created with generated ID, source configuration, and default styling
  5. Map synchronization – MapController diff creates/updates MapLibre sources and layers
  6. Rendering – MapLibre draws base layers; Deck.gl composites overlays

This unidirectional flow—UI → Action → Store → Map—guarantees deterministic state and enables features like time-travel debugging through the undo stack.


Development Commands Reference

Command Target Workspace Effect
npm run dev apps/geolibre-desktop Vite dev server on localhost:5173
npm run tauri:dev apps/geolibre-desktop Desktop window with HMR
npm run build apps/geolibre-desktop Production web bundle
npm run ci All Build, lint, test, coverage gates
npm run test:frontend Multiple Node-based unit tests
npm run test:backend backend/geolibre_server Pytest suite
npm run test:e2e Built app Playwright smoke tests

Regenerating Generated Artifacts

Dependency bumps—especially for geolibre-wasm—require cache regeneration:


# Update Whitebox tool catalog after WASM version change

node scripts/gen-whitebox-menu-catalog.mjs

This script inspects the WASM binary's exported functions and generates the processing menu JSON consumed by the UI.


Code Examples for Common Tasks

Adding a GeoJSON Layer Programmatically

import { useStore } from "@geolibre/core";
import { addGeoJsonLayer } from "@geolibre/map";

async function ingestRemoteGeoJSON(url: string) {
  const response = await fetch(url);
  const geojson = await response.json();
  
  const store = useStore.getState();
  const layerId = `layer-${crypto.randomUUID()}`;
  
  // Insert into canonical store
  store.addLayer({
    id: layerId,
    type: "geojson",
    source: { data: geojson },
    paint: {
      "fill-color": "#3a86ff",
      "fill-opacity": 0.5,
      "fill-outline-color": "#1a1a2e"
    },
    layout: { visibility: "visible" }
  });
  
  // Trigger MapLibre synchronization
  addGeoJsonLayer(layerId, geojson);
}

Creating a Minimal Plugin

// packages/plugins/src/plugins/bufferTool.ts
import { GeoLibrePlugin } from "@geolibre/core";

export const bufferTool: GeoLibrePlugin = {
  id: "buffer-analysis",
  name: "Buffer Analysis",
  icon: "⭕",
  
  run: async (store) => {
    const selectedLayer = store.getSelectedLayer();
    if (!selectedLayer || selectedLayer.type !== "geojson") {
      store.notify("Select a vector layer first");
      return;
    }
    
    // Execute turf.js buffer or delegate to WASM
    const buffered = await store.processing.buffer(selectedLayer, 1000); // meters
    
    store.addLayer({
      id: `${selectedLayer.id}-buffered`,
      type: "geojson",
      source: { data: buffered },
      paint: { "fill-color": "#ff006e", "fill-opacity": 0.3 }
    });
  }
};

Register in apps/geolibre-desktop/src/hooks/usePlugins.ts:

import { bufferTool } from "@geolibre/plugins";

export const builtInPlugins = [
  bufferTool,
  // ...existing plugins
];

Calling Backend Raster Processing

interface RasterConversionParams {
  inputPath: string;
  outputFormat: "PMTiles" | "COG" | "Mbtiles";
  maxZoom?: number;
  compression?: "deflate" | "zstd";
}

async function convertRaster(params: RasterConversionParams): Promise<string> {
  const response = await fetch("/sidecar/conversion/raster", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(params)
  });
  
  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Conversion failed: ${error.detail}`);
  }
  
  const { outputPath, fileSize } = await response.json();
  console.log(`Generated ${outputPath} (${fileSize} bytes)`);
  return outputPath;
}

Testing and Coverage Requirements

GeoLibre enforces strict quality gates:

Suite Line Coverage Branch Coverage Function Coverage
Frontend ≥ 78% ≥ 78% ≥ 63%
Backend ≥ 55% — —

Test files colocate with features: tests/vector-layer-sync.test.ts validates store-to-map synchronization. Import structure matters—testing leaf modules prevents denominator inflation that artificially depresses coverage percentages.


Key Files Reference

File Purpose Direct Link
package.json Workspace definitions, root scripts View source
apps/geolibre-desktop/src/hooks/usePlugins.ts Plugin registration hub View source
packages/core/src/store.ts Zustand store implementation View source
packages/map/src/MapCanvas.tsx MapLibre React host component View source
packages/processing/src/wasm-convert.ts WASM bindings for heavy computation View source
backend/geolibre_server/app/main.py FastAPI sidecar entry point View source
python/src/geolibre/__init__.py Jupyter anywidget entry View source
scripts/gen-whitebox-menu-catalog.mjs WASM catalog generator View source
CLAUDE.md Contributor guidance and architecture notes View source

Summary

  • Monorepo structure – Three workspace zones (apps, packages, workers) unified by npm workspaces and @geolibre/* scope
  • Centralized state – Zustand store in @geolibre/core enforces unidirectional data flow and enables undo/redo
  • Rendering stack – MapLibre-GL JS base with Deck.gl overlays, synchronized through MapController
  • Extensibility – Plugin system with built-in registration in usePlugins.ts and dynamic external loading
  • Polyglot backend – FastAPI sidecar for Python geospatial heavy lifting, transparently proxied for web builds
  • Jupyter integration – Anywidget package embeds the full UI in notebooks via build:embed
  • Strict quality gates – Coverage floors and colocated tests maintain reliability across workspaces

Frequently Asked Questions

How do I add a new workspace to the GeoLibre repository?

Create the directory under apps/, packages/, or workers/ and include a package.json with name prefixed by @geolibre/. Add the workspace path to the root package.json workspaces array, then run npm install to establish symlinks. The new package can immediately import sibling workspaces using scoped imports.

Why does GeoLibre prohibit direct MapLibre manipulation?

Direct map mutations bypass the Zustand store, breaking undo/redo consistency and cross-component synchronization. All rendering changes must flow through store actions so that MapController can diff and apply minimal updates, while other UI components (layer tree, property panels) receive reactive updates through the same source of truth.

What triggers the gen-whitebox-menu-catalog.mjs script?

Regenerate this catalog after any geolibre-wasm version bump or when Whitebox tool signatures change. The script introspects the WASM binary's exported functions and rebuilds the JSON menu structure consumed by the processing UI. Run it manually or configure your CI to detect WASM changes and execute automatically.

Can I use GeoLibre components outside the monorepo?

The @geolibre/* packages are designed for internal consumption, but you can vendor specific packages by copying the source and adjusting import paths. For Jupyter environments, install the published geolibre Python wheel which bundles the complete frontend. External React usage would require building the packages and publishing to a private registry, as the repository does not currently ship to npm public.

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 →