How GeoLibre's Python Pyodide Engine Shares Code with the FastAPI Sidecar for Consistent Vector Operations

GeoLibre ensures identical vector-processing results across browser and server environments by maintaining a single shared Python package (geolibre) that both Pyodide and the FastAPI sidecar import from the same source files.

The open-source GeoLibre project solves a critical challenge in geospatial web applications: running Python vector operations both in the browser and in a native backend without code duplication or result discrepancies. By packaging vector-processing logic once and deploying it to two distinct runtimes, the project eliminates maintenance overhead and guarantees mathematical consistency for geometry operations like buffering, intersection, and union.

Shared Python Package Architecture

GeoLibre's solution centers on a unified codebase located in python/src/geolibre/. This directory contains pure-Python implementations of all vector operations, designed to run under standard CPython (for the sidecar) and Pyodide's WebAssembly-compiled Python (for the browser).

Key Design Principles

  • Single source of truth: All geometry functions live in one location, eliminating version drift between client and server
  • Pure-Python compatibility: The geolibre package avoids platform-specific C extensions that would break WebAssembly compilation
  • Identical APIs: Both runtimes expose the same function signatures and return formats

The core vector operations are defined in python/src/geolibre/vector.py, implementing methods like buffer(), intersect(), and union() using geopandas and shapely primitives.

FastAPI Sidecar Integration

The native backend service at backend/geolibre_server/geolibre_server/vector_ops.py imports directly from the shared package:


# backend/geolibre_server/geolibre_server/vector_ops.py

from geolibre.vector import buffer, intersect, union

router = APIRouter()

@router.post("/buffer")
def buffer_route(payload: BufferPayload):
    """FastAPI endpoint that mirrors the Pyodide geolibre.vector.buffer call."""
    return {"geojson": buffer(payload.geojson, payload.distance)}

This approach provides several advantages:

  • Full native performance: The sidecar leverages GEOS and PROJ at full speed through standard wheels
  • HTTP accessibility: JavaScript clients can call vector operations when Pyodide is unavailable or impractical
  • Fallback capability: The UI transparently routes requests to the sidecar when Pyodide lacks required functionality

Pyodide Bundle and Runtime Loading

The browser-side implementation follows a multi-stage build and load process to ensure code parity with the server.

Build-Time Wheel Generation

During the desktop application build, the geolibre package is compiled to a WebAssembly-compatible wheel using pyodide-build scripts. The resulting artifact (geolibre-<version>-py3-none-any.whl) is copied into the Electron/Tauri assets at apps/geolibre-desktop/dist/.

Runtime Loading Mechanism

The JavaScript loader at apps/geolibre-desktop/src/lib/pyodide/pyodide-vector-loader.ts manages Pyodide initialization:

// apps/geolibre-desktop/src/lib/pyodide/pyodide-vector-loader.ts
const loadVectorEngine = async (pyodide: any) => {
  // Load the pre-built geolibre wheel into the Pyodide filesystem
  await pyodide.loadPackage(["geolibre"]);
  // Import the identical module used by the FastAPI sidecar
  const vector = pyodide.pyimport("geolibre.vector");
  return vector;
};

const bufferSelected = async (geojson, distance) => {
  const vector = await loadVectorEngine(pyodide);
  // Executes the same function exposed at /vector/buffer
  const result = vector.buffer(geojson, distance);
  return JSON.parse(result);
};

The Pyodide configuration at apps/geolibre-desktop/src/lib/pyodide/pyodide-config.ts declares geolibre as a required package, ensuring inclusion in browser builds.

Shared Implementation Example

The actual buffer operation—used identically by both environments—is implemented in python/src/geolibre/vector.py:


# python/src/geolibre/vector.py

import geopandas as gpd
from shapely.geometry import shape

def buffer(geojson: dict, distance: float) -> dict:
    """Shared buffer implementation used by both Pyodide and sidecar."""
    gdf = gpd.GeoDataFrame.from_features([geojson])
    gdf["geometry"] = gdf.geometry.buffer(distance)
    return gdf.__geo_interface__

When this function is modified—whether to fix a bug, improve performance, or add a parameter—the change propagates automatically to both runtimes upon package rebuild.

Fallback Strategy for Consistent User Experience

GeoLibre implements transparent fallback logic to handle cases where Pyodide cannot execute a particular operation. The UI queries sidecar status via /vector/status and routes requests accordingly:

  • Pyodide path: Preferred for latency-critical operations and offline functionality
  • Sidecar path: Used when native dependencies are required or when Pyodide initialization fails

Because both paths execute the same underlying implementation from geolibre/vector.py, users receive mathematically identical results regardless of which runtime handles the request.

Summary

  • Unified codebase: The geolibre package at python/src/geolibre/ provides a single source of truth for all vector operations
  • Dual runtime deployment: Identical code runs in WebAssembly (Pyodide) and native CPython (FastAPI sidecar)
  • Build-time compilation: pyodide-build generates browser-compatible wheels from the same source files used by the server
  • Runtime loading: pyodide-vector-loader.ts initializes the Pyodide environment with the shared package
  • Automatic consistency: Bug fixes and feature additions apply simultaneously to both execution contexts
  • Graceful degradation: The UI falls back to HTTP-based sidecar calls when Pyodide is unsuitable

Frequently Asked Questions

How does GeoLibre prevent version skew between Pyodide and sidecar implementations?

Both environments import from the identical source tree at python/src/geolibre/. The build process generates the Pyodide wheel and installs the native package from the same commit, ensuring function signatures, algorithms, and default parameters remain synchronized.

What happens when a vector operation requires native C extensions unavailable in WebAssembly?

The UI detects such limitations through capability checks or import failures, then transparently routes the request to the FastAPI sidecar via the /vector/* endpoints. The sidecar executes the same function with full native library access and returns the result over HTTP.

Can users run GeoLibre entirely without the sidecar?

Yes, provided all required vector operations are supported by the Pyodide build. The pyodide-config.ts file controls which Python dependencies are bundled; pure-Python packages like geopandas and shapely function correctly in WebAssembly. Operations requiring compiled extensions (some PROJ transformations, for example) necessitate the sidecar.

Where is the entry point that exposes geolibre functions to Pyodide's JavaScript bridge?

The file python/src/geolibre/_server.py serves this purpose, exposing the same functions imported by vector_ops.py in a format accessible to pyodide.pyimport().

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 →