# GeoLibre Performance Considerations: Browser & Desktop Optimization Guide

> Optimize GeoLibre performance by managing WebAssembly memory, avoiding main-thread blocking, and leveraging the Python side-car for demanding spatial tasks.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: performance
- Published: 2026-08-16

---

**GeoLibre performance depends on managing WebAssembly memory limits, minimizing main-thread blocking during vector conversion, and judiciously using the optional Python side-car for heavy spatial operations.**

GeoLibre is a modular, client-side-first GIS platform where most processing happens in the browser or a lightweight Python side-car. Understanding the architectural layers—from DuckDB-WASM ingestion to MapLibre GL rendering—helps you maintain responsive UIs and avoid memory blowouts. This guide breaks down the key performance considerations based on the actual source code implementation in **opengeos/GeoLibre**.

## Data Ingestion & Conversion Bottlenecks

### File Loading Memory Limits

GeoLibre ingests vector formats (Shapefile, GeoParquet, KMZ) and raster tiles primarily through **DuckDB-WASM Spatial**, with `shpjs` as a fallback. The critical constraint is `MAX_REMOTE_FILE_BYTES` in [`packages/plugins/src/plugins/remote-file-formats.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/remote-file-formats.ts), which enforces a ~2 GiB ceiling to prevent WebAssembly memory exhaustion.

When DuckDB loads a file, it pulls the entire dataset into a WASM memory buffer. Exceeding this limit causes UI hangs or outright crashes with no graceful degradation.

```ts
// From packages/plugins/src/plugins/remote-file-formats.ts
const MAX_REMOTE_FILE_BYTES = 2 * 1024 * 1024 * 1024; // 2 GiB hard limit

```

**Mitigation strategies:**
- Pre-filter large datasets server-side before ingestion
- Use pagination for feature collections
- Consider PMTiles for streaming vector tiles instead of full file loads

### Vector-to-GeoJSON Conversion Blocking

The `ST_Read` function in DuckDB triggers O(N) processing where N equals feature count. In [`packages/plugins/src/plugins/maplibre-raster.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/maplibre-raster.ts), the codebase implements a **deadline loop** using `performance.now()` to yield control and prevent complete main-thread blockage:

```ts
// Simplified pattern from maplibre-raster.ts
while (performance.now() - start < DEADLINE_MS) {
  processChunk(features);
  if (performance.now() - start >= DEADLINE_MS) {
    await yieldControl(); // Prevent UI freeze
  }
}

```

### WASM Processing Tools

Whitebox and GeoLibre-WASM algorithms defined in [`packages/processing/src/wasm-convert.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/processing/src/wasm-convert.ts) provide near-native speed but remain single-threaded within the browser's constraints. Intensive geometry operations—buffers, dissolves, unions—can saturate one CPU core with no true parallelism.

The `MAX_VECTOR_BYTES` constant in this file sets a secondary size gate for WASM-side operations:

```ts
// packages/processing/src/wasm-convert.ts
export const MAX_VECTOR_BYTES = 512 * 1024 * 1024; // 512 MB for WASM tools

```

## Rendering & Map Synchronization

### MapLibre GL JS Tuning

The core renderer in [`packages/map/src/map-controller.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/map-controller.ts) manages vector tiles, raster sources, and custom layers. High-zoom tile requests create GPU pressure that compounds with large raster overlays (GeoTIFF, COG).

**Recommended configurations:**

```ts
import { RasterSource } from 'maplibre-gl';

// Cap resolution to control memory
const raster = new RasterSource('optimized-raster', {
  url: 'https://example.com/tiles/{z}/{x}/{y}.png',
  maxzoom: 12,        // Prevent excessive tile generation
  tileSize: 256,      // Standard size; 512 increases quality at cost
});
map.addSource('my-raster', raster);

```

### Deck.gl Overlay Optimization

3-D visualizations in [`packages/plugins/src/plugins/deckgl-viz/overlay.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/deckgl-viz/overlay.ts) use WebGL with `performance.now()`-driven animation timing. Over-draw from unbounded point counts saturates the GPU.

Implement explicit limits:

```ts
import DeckGL from '@deck.gl/react';
import { ScatterplotLayer } from '@deck.gl/layers';

const MAX_POINTS = 500_000; // Hard cap for responsive rendering

const layer = new ScatterplotLayer({
  id: 'point-cloud',
  data: largePointArray.slice(0, MAX_POINTS),
  cullEnabled: true,           // Frustum culling
  getPosition: d => d.coordinates,
  pickable: true,
});

```

### State Management Batch Updates

The Zustand store in [`packages/core/src/store.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/store.ts) drives UI synchronization. Frequent granular updates trigger cascading re-renders. Debounce slider-driven style changes and batch layer visibility toggles.

## Python Side-Car Performance

The optional FastAPI side-car ([`backend/geolibre_server/app/main.py`](https://github.com/opengeos/GeoLibre/blob/main/backend/geolibre_server/app/main.py)) offloads operations too heavy for browser execution—GeoPandas/Shapely vector operations and rasterio-based raster I/O.

| Aspect | Performance Impact | Mitigation |
|--------|-------------------|------------|
| Cold start latency | 2–5 seconds on first invocation | Keep side-car running during intensive sessions |
| Data transfer | HTTP serialization overhead for large geometries | Use `/vector/stream` endpoints for chunked transfer |
| Concurrency | Single FastAPI process by default | Launch with `uvicorn --workers 4` |
| Dependency drift | Runtime errors masquerading as slowdowns | Verify `backend/geolibre_server/uv.lock` freshness |

**Streaming endpoint example:**

```python

# backend/geolibre_server/app/main.py pattern

@app.get("/vector/stream")
async def stream_features():
    for chunk in large_geodataframe.iterfeatures(batched=True):
        yield MessagePack.encode(chunk)

```

## Built-In Performance Measurement

GeoLibre exposes timing utilities in [`apps/geolibre-desktop/src/lib/diagnostics.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/diagnostics.ts). Wrap critical sections:

```ts
// Custom instrumentation pattern
export function timeIt<T>(label: string, fn: () => Promise<T>): Promise<T> {
  const start = performance.now();
  return fn().finally(() => {
    console.info(`${label}: ${Math.round(performance.now() - start)} ms`);
  });
}

// Usage
await timeIt('GeoParquet conversion', async () => {
  await duckdb.query(`SELECT * FROM ST_Read('large.parquet')`);
});

```

The [`tour-recorder.ts`](https://github.com/opengeos/GeoLibre/blob/main/tour-recorder.ts) file demonstrates cross-context timing that works in both browser and Tauri desktop builds.

## Critical Memory Constants

| Constant | Location | Default | Purpose |
|----------|----------|---------|---------|
| `MAX_REMOTE_FILE_BYTES` | [`remote-file-formats.ts`](https://github.com/opengeos/GeoLibre/blob/main/remote-file-formats.ts) | 2 GiB | DuckDB-WASM download ceiling |
| `MAX_VECTOR_BYTES` | [`wasm-convert.ts`](https://github.com/opengeos/GeoLibre/blob/main/wasm-convert.ts) | 512 MB | WASM tool input limit |
| `MAX_VECTOR_PMTILES_ZOOM` | [`wasm-convert.ts`](https://github.com/opengeos/GeoLibre/blob/main/wasm-convert.ts) | 18 | PMTiles generation zoom cap |

These values must stay synchronized with upstream `maplibre-gl-vector` updates. Silent failures occur when the UI marks a layer "available" but the underlying engine rejects it.

## Browser vs. Desktop (Tauri) Tradeoffs

| Capability | Browser Build | Tauri Desktop Build |
|------------|-------------|---------------------|
| File system access | CORS-restricted fetch | Native via Rust bridge |
| Large raster reads | Chunked HTTP | Direct MBTile streaming |
| Resource timing buffer | `performance.setResourceTimingBufferSize` limit | Extended buffer |
| Startup overhead | Lower | Higher (native runtime) |

For desktop deployments, prefer [`apps/geolibre-desktop/src/lib/native-http.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/native-http.ts) paths over fetch-based loading for local raster datasets.

## Summary

- **Respect size gates**: `MAX_REMOTE_FILE_BYTES` and `MAX_VECTOR_BYTES` prevent WASM memory crashes
- **Yield the main thread**: Use deadline-loop patterns for large conversions
- **Cap rendering load**: Limit zoom levels, tile counts, and Deck.gl point densities
- **Keep the side-car warm**: Amortize FastAPI startup cost during heavy sessions
- **Measure everything**: Leverage `performance.now()` wrappers in [`diagnostics.ts`](https://github.com/opengeos/GeoLibre/blob/main/diagnostics.ts)

## Frequently Asked Questions

### What causes GeoLibre to hang during file loading?

DuckDB-WASM loads entire files into browser memory before parsing. Files approaching the 2 GiB `MAX_REMOTE_FILE_BYTES` limit cause unresponsive UIs or crashes. Pre-filter datasets or use PMTiles for streaming access.

### How do I optimize MapLibre rendering for large raster datasets?

Set `maxzoom` between 10–12 to limit tile generation, use 256×256 `tileSize` rather than 512×512, and prefer COG (Cloud Optimized GeoTIFF) sources that support HTTP range requests for partial reads.

### When should I use the Python side-car instead of browser processing?

Invoke the FastAPI side-car for operations exceeding 512 MB of vector data, complex spatial joins, or raster formats unsupported in WASM (specific proprietary encodings). Profile with `uvicorn --log-level debug` to identify bottlenecks.

### Why does my Deck.gl visualization freeze the map?

Unbounded point counts cause GPU over-draw. Implement `pointCountLimit` slicing, enable `cullEnabled` for frustum culling, and consider aggregate layers (heatmap, hexbin) for dense distributions rather than raw point rendering.