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 installonce at the root - Cross-workspace imports – Packages reference each other via
@geolibre/*scope - Targeted scripts –
-wflag 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.jsonstructure) - 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:
- Source ingestion – Native file dialog (Tauri) or remote URL triggers loading
- Format detection – Extension/MIME type mapped to appropriate reader
- Client-side conversion – DuckDB-WASM Spatial executes
ST_Readfor Parquet/GeoJSON/Shapefile; fallback toshpjsfor exotic formats - Store insertion – New
GeoLibreLayerrecord created with generated ID, source configuration, and default styling - Map synchronization –
MapControllerdiff creates/updates MapLibre sources and layers - 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/coreenforces 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.tsand 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →