How to Run GeoPandas/Shapely Tools in the Browser via Pyodide

GeoLibre executes GeoPandas and Shapely directly in the browser by loading a Pyodide Web Worker that runs the same pure-Python vector logic used by the server-side sidecar, ensuring identical geometry processing without network latency.

The GeoLibre project provides a third vector engine that brings server-grade geospatial processing client-side using Pyodide—a WebAssembly port of CPython. By sharing the underlying Python implementation between the browser and server environments, you can run complex GeoPandas/Shapely operations locally while maintaining a single source of truth for your vector tools.

How the Pyodide Vector Engine Works

GeoLibre’s browser-based engine relies on a multi-layer architecture that bridges TypeScript frontend code with a Python runtime running inside a Web Worker.

Shared Python Logic

The core geometry functions reside in backend/geolibre_server/geolibre_server/vector_ops.py. This file contains pure-Python implementations of vector operations using GeoPandas and Shapely, designed to run anywhere CPython is available. Both the server-side sidecar and the browser-based Pyodide engine import this module, guaranteeing that buffer operations, intersections, and unions produce identical coordinates regardless of execution environment.

Build-Time Integration

A custom Vite plugin located at apps/geolibre-desktop/vite-plugins/copy-vector-ops.ts automatically copies vector_ops.py into the frontend bundle as src/lib/pyodide/vector_ops.generated.py during the build process. This ensures the browser always executes the same code version deployed to the server, eliminating drift between client and server logic.

Web Worker Runtime

The browser loads public/pyodide/pyodide-worker.js, which initializes the Pyodide environment inside a dedicated Web Worker thread. This script fetches the Pyodide distribution from a CDN (or self-hosted mirror), installs the geopandas package, and exposes the run_vector_tool function that accepts JSON-serialized GeoJSON and returns processed results.

Configuring the Pyodide Environment

Before executing Python code, you must configure the runtime source and initialize the console.

CDN vs. Self-Hosted

Configuration is managed in apps/geolibre-desktop/src/lib/pyodide/pyodide-config.ts. By default, the system loads Pyodide from the public jsDelivr CDN at https://cdn.jsdelivr.net/pyodide/v${PYODIDE_VERSION}/full/. For offline deployments or security-hardened environments, set the VITE_PYODIDE_INDEX_URL environment variable to point to a local mirror (e.g., https://your-host/pyodide/v0.27.7/full/).

Runtime Initialization

The apps/geolibre-desktop/src/lib/pyodide/pyodide-console.ts module handles bootstrapping. It creates the Pyodide runtime, registers a JavaScript module named _geolibre_js that exposes helper functions for DOM and network access, and executes the console’s Python bootstrap code to warm up the interpreter for subsequent calls.

Running Vector Tools in the Browser

Once configured, you can invoke GeoPandas operations from your TypeScript application using the provided loader functions.

Initializing the Console

Import the initialization function to prepare the Pyodide environment. This typically runs once when the application starts:

import { initConsoleRuntime } from '../lib/pyodide/pyodide-console';

// Warm up the interpreter (downloads ~tens of MB on first run)
await initConsoleRuntime();

Executing GeoPandas Operations

Use runVectorToolInPyodide from apps/geolibre-desktop/src/lib/pyodide/pyodide-vector-loader.ts to send requests to the worker. This function serializes your GeoJSON input, invokes run_vector_tool inside the Pyodide environment, and streams progress updates back to the UI:

import { runVectorToolInPyodide } from '../lib/pyodide/pyodide-vector-loader';

async function runBufferTool(geojson: string, distance: number) {
  const result = await runVectorToolInPyodide({
    toolId: 'buffer',
    layerId: 'my-layer',
    inputGeoJSON: geojson,
    params: { distance },
    onProgress: (msg) => console.log(msg), // UI progress hook
  });
  return result;
}

Inside the worker, the following Python executes in vector_ops.generated.py:

from geopandas import GeoDataFrame
from shapely.geometry import shape

def run_vector_tool(tool_id: str, geojson_str: str, **params):
    gdf = GeoDataFrame.from_features(shape(geojson_str))
    if tool_id == 'buffer':
        gdf['geometry'] = gdf.buffer(params['distance'])
    # Additional tool implementations...

    return gdf.__geo_interface__

UI Integration and User Experience

GeoLibre’s frontend provides a seamless interface for selecting the execution engine and handling runtime downloads.

Engine Selection

The VectorToolsDialog component in apps/geolibre-desktop/src/components/processing/VectorToolsDialog.tsx presents users with three engine options: client (JavaScript), sidecar (server Python), or pyodide (browser Python). When users select Pyodide, the dialog invokes runVectorToolInPyodide() instead of making HTTP requests to the backend.

First-Run Download Behavior

The UI displays a notification during the initial Pyodide execution, informing users that the Python runtime (approximately tens of megabytes) is downloading. Subsequent operations reuse the warmed-up runtime, providing near-instant execution for additional vector tools during the same session.

Summary

  • Shared logic: The vector_ops.py module provides a single source of truth for geometry operations used by both server-side and browser-based engines.
  • Build integration: The Vite plugin copy-vector-ops.ts ensures the frontend bundle contains the latest Python code.
  • Web Worker execution: Pyodide runs inside pyodide-worker.js, keeping the main thread responsive during heavy geospatial computations.
  • Flexible hosting: Configure VITE_PYODIDE_INDEX_URL to use public CDNs or private mirrors for the Pyodide distribution.
  • Identical results: Because both environments execute the same GeoPandas/Shapely code, buffer distances and intersection areas remain consistent across client and server.

Frequently Asked Questions

How does GeoLibre ensure results match between browser and server?

Both environments import the same vector_ops.py file. The Vite build process copies this file into the frontend as vector_ops.generated.py, ensuring the Pyodide engine and the sidecar execute identical logic when calling run_vector_tool.

What is the performance cost of running GeoPandas in the browser?

The initial load requires downloading the Pyodide runtime and packages (tens of megabytes), which may take several seconds on slower connections. Once cached, vector operations execute at near-native speed inside the Web Worker, though very large datasets may still benefit from the server-side sidecar engine due to memory constraints in the browser.

Can I deploy GeoLibre with an offline Pyodide mirror?

Yes. Set the VITE_PYODIDE_INDEX_URL environment variable to point to a self-hosted directory containing the Pyodide distribution files. The configuration in pyodide-config.ts will use this URL instead of the public jsDelivr CDN, enabling offline or air-gapped deployments.

Which vector tools are available in the Pyodide engine?

Any tool defined in the shared vector_ops.py catalog is available, including buffering, intersection, union, and dissolving operations. The VectorToolsDialog filters the available tools based on the selected engine, but the underlying Python implementation remains consistent across all three execution modes.

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 →