How Pyodide Enables Browser-Based GeoPandas/Shapely Vector Processing

Pyodide compiles CPython—including heavy-weight geospatial libraries like GeoPandas and Shapely—to WebAssembly, allowing GeoLibre to execute complex vector operations entirely inside the browser without requiring a server.

GeoLibre supports three distinct vector processing backends: a local Node-based client, a FastAPI side-car, and Python via Pyodide. By implementing browser-based GeoPandas/Shapely vector processing, the application ships the same Python source code used by the server directly to the client, enabling fully offline workflows while maintaining identical results across environments.

Single Source of Truth for Vector Operations

The core geometry logic lives in a framework-agnostic Python module that serves both the server side-car and the browser engine. In backend/geolibre_server/geolibre_server/vector_ops.py, the run_vector_tool function implements all vector operations using standard GeoPandas and Shapely APIs. This file acts as the single source of truth, ensuring that buffering, intersection, and reprojection calculations produce identical results regardless of execution environment.

To make this module available in the browser, a custom Vite plugin handles build-time code sharing. The plugin at vite-plugins/copy-vector-ops.ts copies vector_ops.py into the web bundle as apps/geolibre-desktop/src/lib/pyodide/vector_ops.generated.py. This generated file ships with the frontend assets and loads into the Pyodide runtime at execution time.

Runtime Execution Architecture

Web Worker Initialization

When a vector tool is first invoked, GeoLibre spawns a dedicated Web Worker via public/pyodide/pyodide-worker.js. This worker bootstraps the Pyodide runtime by loading the CPython interpreter from a configurable CDN URL (controlled by VITE_PYODIDE_INDEX_URL in apps/geolibre-desktop/src/lib/pyodide/pyodide-config.ts). The runtime then installs the full GeoPandas stack—a download measuring tens of megabytes that is cached by the browser for subsequent sessions.

After initialization, the worker receives the vector_ops.generated.py source code as a string and executes run_vector_tool against JSON-serialized requests. Because the worker runs in an isolated thread, heavy geometric computations never block the main UI.

Async API and Progress Handling

The main thread communicates with the worker through postMessage and onmessage events, exposed via a high-level TypeScript API in apps/geolibre-desktop/src/lib/pyodide/pyodide-vector-loader.ts. The runVectorToolInPyodide function returns a Promise resolving to a GeoJSON FeatureCollection and an array of log messages:

import { runVectorToolInPyodide, onPyodideProgress } from '@/lib/pyodide/pyodide-vector-loader';

// Subscribe to loading phases (e.g., "Downloading Python runtime", "Loading GeoPandas")
const unsubscribe = onPyodideProgress((phase) => console.log('Pyodide:', phase));

const request = {
  tool_id: 'buffer',
  geojson: myFeatureCollection,
  parameters: { distance: 10 },
};

try {
  const result = await runVectorToolInPyodide(request);
  console.log('Buffered GeoJSON:', result.geojson);
} finally {
  unsubscribe();
}

Progress events flow through the onPyodideProgress callback (implemented around lines 38-46 of the loader), enabling the UI to display loading indicators while the WASM runtime initializes.

Error Handling and Timeouts

Production deployments require guards against network failures and runaway computations. The loader implements timeout logic (lines 16-22 and 71-85) that automatically terminates the worker if the Pyodide runtime fails to download within a specified window or if a vector operation hangs. Upon timeout, the worker is destroyed and the system retries on the next tool invocation, preventing zombie processes from consuming browser resources.

Direct Worker Communication

For advanced use cases requiring custom initialization, developers can instantiate the worker directly and manage the Pyodide lifecycle:

const worker = new Worker('/pyodide/pyodide-worker.js');

worker.postMessage({
  type: 'init',
  indexURL: 'https://cdn.jsdelivr.net/pyodide/v0.27.0/full/',
  vectorOpsSource: `...` // Contents of vector_ops.generated.py
});

worker.onmessage = (e) => {
  if (e.data.type === 'ready') console.log('Pyodide ready');
  if (e.data.type === 'result') console.log('Result:', e.data.geojson);
};

This lower-level interface bypasses the TypeScript loader's convenience methods while maintaining the same JSON request/response boundary defined in vector_ops.py.

Summary

  • Shared Python Core: backend/geolibre_server/geolibre_server/vector_ops.py provides identical GeoPandas/Shapely logic to both server and browser environments.
  • Build Integration: A Vite plugin copies Python source files into the frontend bundle at compile time.
  • WebAssembly Execution: Pyodide runs CPython in a Web Worker, executing vector tools without server round-trips.
  • Robust Lifecycle Management: Timeouts and progress callbacks in pyodide-vector-loader.ts ensure reliable UX during multi-megabyte runtime downloads.

Frequently Asked Questions

What is Pyodide and how does it run Python in the browser?

Pyodide is a port of CPython to WebAssembly (WASM) that compiles the Python interpreter and its C extensions into a binary format executable by modern browsers. In GeoLibre, Pyodide loads the full GeoPandas and Shapely stack—including compiled dependencies like GEOS—allowing complex vector geoprocessing to run client-side at near-native speeds without installation or server infrastructure.

How does GeoLibre ensure consistency between server-side and browser-based vector processing?

GeoLibre uses a single Python module (vector_ops.py) as the source of truth for all vector operations. A Vite build plugin copies this file into the web bundle as vector_ops.generated.py. Whether executed by the FastAPI side-car or the Pyodide Web Worker, the same run_vector_tool function processes the geometry, guaranteeing identical results across deployment modes.

What happens if the Pyodide runtime fails to load or times out?

The pyodide-vector-loader.ts module implements timeout guards (approximately 30 seconds by default) that monitor the worker initialization and execution phases. If the Pyodide CDN is unreachable or a computation exceeds the time limit, the loader terminates the worker thread and cleans up resources. Subsequent vector tool calls spawn a fresh worker instance, allowing the application to recover without page reloads.

How large is the Pyodide download and how is it cached?

The complete Pyodide runtime with GeoPandas and its dependencies totals tens of megabytes. GeoLibre configures the worker to load from a CDN (defaulting to JSDelivr) specified by VITE_PYODIDE_INDEX_URL. Browsers cache these WASM binaries and Python packages after the first load, making subsequent vector operations instantaneous. For air-gapped deployments, developers can self-host the Pyodide index and point the configuration to an internal mirror.

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 →