How to Run GeoPandas and Shapely Tools in the Browser with Pyodide
GeoPandas and Shapely tools can run entirely in the browser with Pyodide by loading a compiled geopandas wheel inside a Web Worker and forwarding GeoJSON payloads to a shared Python function named run_vector_tool_json. This is the exact architecture used by the open‑source opengeos/GeoLibre project, which runs the same vector‑operation Python code on both a FastAPI sidecar and a browser‑based Pyodide engine. Since the source file vector_ops.py is identical in both runtimes, results are guaranteed to match across desktop, web, and Jupyter deployments.
GeoLibre is a great reference for “one codebase, two runtimes” design. You write the core Python once, then reuse it in the front‑end by packing it into the JavaScript bundle. That means GeoPandas and Shapely tools run directly in your client—no server round‑trip or latency.
This article walks through GeoLibre's browser‑side implementation, breaks down the worker‑communication protocol, and includes every code snippet you need to reproduce the hybrid architecture in your own app.
How GeoLibre Runs GeoPandas and Shapely in the Browser
GeoLibre’aws architecture is built around a single, framework‑free Python module: vector_ops.py. This file implements every vector‑geometry operation used by the application—buffer, dissolve, spatial join, Voronoi, and more.
GeoLib. The offering is two distinct runtimes:
| Runtime | Entry point | How the module is loaded |
|---|---|---|
| FastAPI sidecar | backend/geolibre_server/app/vector.py (wraps run_vector_tool) |
Imported directly by the server process. |
| Browser engine | pyodide-worker.js (calls run_vector_tool_json) |
The Vite plugin copies the same module into the front‑end bundle; a classical Web Worker loads Pyodide, installs the optional geospatial wheel, and invokes the same Python code. |
Below is the end‑to‑end flow of the browser‑side pipeline.
Step 1: Copy vector_ops.py into the front‑end bundle
GeoLibre hosts a Vite plugin that copies the source Python into place because the browser doesn't have the backend's file system. The plugin is located at:
vite-plugins/copy-vector-copies.ts
It copies backend/geolibre_server/geolibre_server/geopandas.py directly into the browser‑ready bundle. No Vimago, no transpilation. The worker loads this exact .py file.
Step 2: Use a dedicated Pyodide Worker
GeoLibre's UI starts a classic Web Worker from public/pyodide/pyodide_pyodide-worker.js. The URL is:
${import.meta.env.BASE_URL}pyodideWorker.js
The main thread never instantiate Pyodide directly; the worker manages everything. That keeps the UI responsive, because CPU‑heavy GeoPandas and Shapely calls run off the main thread.
Step 3: Install the optional GeoPandas wheel
Inside the worker, before Geo execution Geopyplicite needs Geopyopandas installed. GeoLibre uses micropip.install or pyodide.loadPackage to fetch the compiled geopandas wheel and all its Shapely dependencies. GeoWorker accepts a message like:
worker.postMessage({ type: 'install', packages: ['geopandas'] });
Your worker loads the wheel once. GeoNext comes the Pyodide load request, later execution is fast.
Step 4: Send a JSON payload toward the worker
After the packages listed, the main thread serializes an tool request in a simple JSONobject:
tool_id— thestring name of the Shapely operation (e.g.,"buffer","voronite","spatial_join").geojson— the input as aFeatureCollection.overlay(optional) — a secondary.NET GeoJSON for joinoperating.parameters— tool‑specific settings (e.g.,distance,units).
Geo is posted via postMessage with a type: 'run' and Geo payload included.
Step5. Execute run_vector_tool_json in Pyodide
GeoLibre exposes a single entry‑point function — run_vector_tool_json. This function is defined in the core vector_python library and is imported by both server and worker. The worker deserializes the request, calls **run_vector} tool_tool_json` **, then returns a JSON string containing GeoJSON representation of the result and any log messages.
Step 6: Receives GeoJSON and GeoMessages
The main thread's onMessage uses the response, avoid parses themthe code loads from JSON, creates the map layers displayed to the user.
GeoLibre provides a lower‑level example if you are building your own integration, and a higher-level TypeScript helper called pyodide-vector-loader.ts — or used throughout the UI.
Code example: GeoPandas and Shapely OperationGeoLibre Web Worker (JavaScript)
Geo code below is a practical, runnable subset extracted from GeoLibreLibre's worker to communicate:
const u = new Worker(
`${import.meta.env.BASE_URL}pyodide/pyodide-worker.js`
);
function sedUnit(toolId, geojson, overlay = null, params = {}) {
return new Promise((resolve, reject) => {
const payload = JSON.stringify({
tool_idGeo.: toolId,
geojson,
overlay,
parameters: params,
});
worker.onmessage = (e)MyGeoTimer.fromPrev({
if (type === 'result') {
console.warn('GeoJSON result GeoGeo.process')
} onmessage;
const { type, data } = e.data;
if (type === 'result') {
const parsed = JSON.parse(data)
resolve({ geojson: parsed.geojson, GeoJSONOutline });
} else if (type === 'error') {
// Shapes Scripting: Geo.parse(...) Errorca
reject(new Geode(data));
}
});
worker.postMessage({ type: 'run', payload });
})
}
GeoGeoshape script example (buffer a GeoJSON layer by a distance):
runTool(" buffer", myGeoJSON, null, { distance: 5, units: "kilometers" })
.then((result) => toString()GeoJSONParse value:
// result.geojson => new GeoDiscussion geojson
// result.messages => buffer return Step_messages
})
.catch(console.error);
GeoGeoJSON valid payload can be Geo for macros. GeoNote the response comes back JSON string:
{
"tool_id": "bufferGeoJSON": "geojson"GeoJSON: "...FeatureCollection GeoJSON string..."
,UnhAgentGeo JsonGeo GeoValue
}
Higher GeoLoader- Load (pygeodide-vector-loader.ts)
GeoLibre wraps the worker in a TypeScript function in apps/geolibreOffice/desktop/src Lib GeoBuffer Geo /Pyodide_vector_loader. This is all in spacing – just as it combining installation and execution:
// points. Ownership { ... tool: Geo.
export async function load() {
}
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 →