GeoLibre Performance Considerations: Browser & Desktop Optimization Guide
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, 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.
// 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, the codebase implements a deadline loop using performance.now() to yield control and prevent complete main-thread blockage:
// 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 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:
// 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 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:
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 use WebGL with performance.now()-driven animation timing. Over-draw from unbounded point counts saturates the GPU.
Implement explicit limits:
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 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) 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:
# 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. Wrap critical sections:
// 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 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 |
2 GiB | DuckDB-WASM download ceiling |
MAX_VECTOR_BYTES |
wasm-convert.ts |
512 MB | WASM tool input limit |
MAX_VECTOR_PMTILES_ZOOM |
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 paths over fetch-based loading for local raster datasets.
Summary
- Respect size gates:
MAX_REMOTE_FILE_BYTESandMAX_VECTOR_BYTESprevent 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 indiagnostics.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.
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 →