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_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

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:

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 →