# How to Run Python GeoPandas and Shapely in the Browser with Pyodide: No Server Required

> Run Python GeoPandas and Shapely in your browser with Pyodide. GeoLibre offers full vector processing capabilities directly client-side, no server needed. Experience identical results to native processing.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: how-to-guide
- Published: 2026-08-04

---

**GeoLibre enables full GeoPandas and Shapely vector processing directly in the browser using Pyodide, eliminating any server dependency while maintaining identical results to the native FastAPI side-car.**

Running Python geospatial libraries in the browser has historically required a back-end server. The **opengeos/GeoLibre** project breaks this constraint by embedding Pyodide—a WebAssembly-based Python runtime—directly into its web build. This article explains the complete architecture, from shared Python modules to Web Worker execution, so you can implement the same pattern in your own applications.

## Shared Python Code: One Source of Truth

The foundation of GeoLibre's browser-side processing is **vector_ops.py**, a pure-Python module containing all core geometry functions. Located at [`backend/geolibre_server/geolibre_server/vector_ops.py`](https://github.com/opengeos/GeoLibre/blob/main/backend/geolibre_server/geolibre_server/vector_ops.py), this file serves both the native FastAPI side-car and the Pyodide browser engine.

```python

# backend/geolibre_server/geolibre_server/vector_ops.py

# Core functions for buffer, intersection, union, dissolve, etc.

# Used identically by server and browser builds

```

By maintaining a single implementation, GeoLibre guarantees that vector operations produce identical results regardless of execution environment.

## Build-Time Integration: Vite Copy Plugin

To make the Python module available in the browser, a custom Vite plugin copies [`vector_ops.py`](https://github.com/opengeos/GeoLibre/blob/main/vector_ops.py) into the frontend bundle during build:

```typescript
// apps/geolibre-desktop/vite-plugins/copy-vector-ops.ts
// Copies vector_ops.py to src/lib/pyodide/vector_ops.generated.py

```

This transformation happens automatically, ensuring the browser always has the latest shared logic without manual file synchronization.

## Pyodide Runtime Configuration

The Pyodide runtime URL is configurable via environment variable, defaulting to the public jsDelivr CDN:

```typescript
// apps/geolibre-desktop/src/lib/pyodide/pyodide-config.ts
const PYODIDE_VERSION = "0.25.0"; // or specified version
const indexURL = import.meta.env.VITE_PYODIDE_INDEX_URL 
  ?? `https://cdn.jsdelivr.net/pyodide/v${PYODIDE_VERSION}/full/`;

```

**Key configuration points:**

- **First load cost:** The Pyodide WASM payload downloads once (~tens of MB)
- **Subsequent operations:** Reuse the warmed-up interpreter for near-instant execution
- **Self-hosting option:** Point `VITE_PYODIDE_INDEX_URL` to your own mirror for air-gapped environments

## Web Worker Execution Architecture

A dedicated Web Worker at [`public/pyodide/pyodide-worker.js`](https://github.com/opengeos/GeoLibre/blob/main/public/pyodide/pyodide-worker.js) manages the Pyodide lifecycle:

```javascript
// apps/geolibre-desktop/src/lib/pyodide/pyodide-worker.js
// Loads Pyodide, installs geopandas, imports vector_ops.generated.py
// Executes tools over JSON-serialized message passing

```

The worker pattern prevents UI blocking during heavy geometry operations. It loads Pyodide from the configured CDN, installs the `geopandas` package (which includes Shapely), then imports the copied [`vector_ops.generated.py`](https://github.com/opengeos/GeoLibre/blob/main/vector_ops.generated.py) module.

## UI Integration: Automatic Engine Selection

The **VectorToolsDialog** component automatically selects the appropriate engine based on build configuration:

```tsx
// apps/geolibre-desktop/src/components/processing/VectorToolsDialog.tsx
// IS_MAS_BUILD true → automatically uses Pyodide engine
// Otherwise → connects to FastAPI side-car

```

When Pyodide is active, the **pyodide-vector-loader** handles parameter marshaling, progress streaming, and result conversion:

```typescript
// apps/geolibre-desktop/src/lib/pyodide/pyodide-vector-loader.ts
interface VectorToolParams {
  tool: string;
  layerId: string;
  params: Record<string, any>;
  onProgress?: (percent: number) => void;
}

export async function runVectorToolInPyodide(
  params: VectorToolParams
): Promise<GeoJSON.FeatureCollection> {
  // Forwards to worker, awaits result, streams progress
}

```

## Complete Usage Examples

### From the Application UI

```tsx
import { runVectorToolInPyodide } from "./pyodide/pyodide-vector-loader";

async function bufferLayer(layerId: string, distance: number) {
  const result = await runVectorToolInPyodide({
    tool: "buffer",
    layerId,
    params: { distance, resolution: 16 },
    onProgress: (p) => console.log(`Progress: ${p}%`),
  });
  // result is GeoJSON FeatureCollection ready for map display
  addGeoJsonLayer(result);
}

```

### Direct Pyodide in Custom Console

```javascript
const pyodide = await loadPyodide({
  indexURL: "https://cdn.jsdelivr.net/pyodide/v0.25.0/full/",
});

await pyodide.loadPackage(["geopandas", "shapely"]);

const geojson = pyodide.runPython(`
import geopandas as gpd
from shapely.geometry import Point

# Create sample data

gdf = gpd.GeoDataFrame(
    {"name": ["A", "B"]},
    geometry=[Point(0, 0), Point(1, 1)],
    crs="EPSG:4326"
)

# Reproject and buffer

gdf_utm = gdf.to_crs("EPSG:32633")
buffered = gdf_utm.buffer(1000)  # 1km buffer in meters

# Return as GeoJSON

gdf_utm.set_geometry(buffered).to_json()
`);

const featureCollection = JSON.parse(geojson);

```

## File Reference Table

| Component | Path | Purpose |
|-----------|------|---------|
| Shared geometry logic | [`backend/geolibre_server/geolibre_server/vector_ops.py`](https://github.com/opengeos/GeoLibre/blob/main/backend/geolibre_server/geolibre_server/vector_ops.py) | Single source of truth for all vector operations |
| Build-time copy plugin | [`apps/geolibre-desktop/vite-plugins/copy-vector-ops.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/vite-plugins/copy-vector-ops.ts) | Copies Python module into frontend bundle |
| Runtime configuration | [`apps/geolibre-desktop/src/lib/pyodide/pyodide-config.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/pyodide/pyodide-config.ts) | CDN URL and version management |
| Web Worker | [`apps/geolibre-desktop/public/pyodide/pyodide-worker.js`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/public/pyodide/pyodide-worker.js) | Pyodide lifecycle and package installation |
| UI bridge | [`apps/geolibre-desktop/src/lib/pyodide/pyodide-vector-loader.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/pyodide/pyodide-vector-loader.ts) | Parameter passing and progress streaming |
| Engine selection | [`apps/geolibre-desktop/src/components/processing/VectorToolsDialog.tsx`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/components/processing/VectorToolsDialog.tsx) | Automatic Pyodide/side-car routing |

## Performance Considerations

- **Initial load:** 10-30 MB WASM download depending on Pyodide version and packages
- **Package caching:** `geopandas` and dependencies download once, cached by browser
- **Memory limits:** Constrained by browser tab (typically 2-4 GB for modern browsers)
- **Suitable workloads:** Vector operations on datasets up to ~100k features depending on complexity

For larger datasets, the FastAPI side-car remains available as a fallback option.

## Summary

- **Single Python module:** [`vector_ops.py`](https://github.com/opengeos/GeoLibre/blob/main/vector_ops.py) ensures identical results across server and browser
- **Build-time copying:** Vite plugin integrates Python code into the frontend bundle
- **Configurable runtime:** Environment variable controls Pyodide CDN or mirror URL
- **Worker isolation:** Web Worker prevents UI blocking during geometry operations
- **Automatic fallback:** UI components transparently select browser or server engine
- **Zero server dependency:** Full GeoPandas/Shapely capabilities available offline after initial load

## Frequently Asked Questions

### What file size should users expect when loading Pyodide for the first time?

The initial download is approximately 10-30 MB depending on the Pyodide version and which packages are loaded. GeoLibre loads `geopandas` which includes Shapely and its dependencies. This payload caches in the browser, making subsequent visits nearly instant.

### How does GeoLibre guarantee the same results between browser and server processing?

Both environments execute the identical Python code from [`backend/geolibre_server/geolibre_server/vector_ops.py`](https://github.com/opengeos/GeoLibre/blob/main/backend/geolibre_server/geolibre_server/vector_ops.py). A Vite plugin copies this file into the frontend at build time, ensuring the browser and FastAPI side-car always run the same implementation.

### Can Pyodide handle the same dataset sizes as a native Python environment?

Browser memory constraints limit Pyodide to smaller datasets than a dedicated server—typically tens to hundreds of thousands of features depending on geometry complexity. GeoLibre provides automatic fallback to the FastAPI side-car when server processing is available.

### Is internet connectivity required after the initial Pyodide load?

Only if self-hosting is not configured. By default, GeoLibde uses the jsDelivr CDN. Setting `VITE_PYODIDE_INDEX_URL` to a local mirror enables fully offline operation after the first download.