# How GeoLibre Performs Client-Side Vector Analysis with Turf.js and DuckDB-WASM Spatial

> Discover how GeoLibre achieves fast client-side vector analysis with Turf.js for GeoJSON and DuckDB-WASM Spatial for large datasets and complex spatial SQL queries in your browser.

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

---

**GeoLibre performs client-side vector analysis using a dual-backend system in the `@geolibre/processing` workspace: Turf.js handles lightweight geometry operations directly on GeoJSON, while DuckDB-WASM Spatial scales to large datasets and complex spatial SQL queries entirely in the browser.**

GeoLibre's approach to **client-side vector analysis** combines two powerful JavaScript engines to keep geospatial processing fast, private, and offline-capable. The processing system lives in the `@geolibre/processing` workspace and operates on layers stored in the application's **Zustand store**. Depending on data volume and operation complexity, it automatically selects **Turf.js** for quick geometry tasks or **DuckDB-WASM Spatial** for heavy-duty spatial SQL.

## Turf.js for Lightweight Geometry Operations

For most geometry-centric operations—**buffer**, **centroid**, **union**, **simplify**, and similar—GeoLibre calls **Turf.js** directly on the selected GeoJSON feature collection.

The pattern follows three steps in [`packages/processing/src/vector-tools.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/processing/src/vector-tools.ts) (lines 1-20 and 84-95):

1. Fetch the input layer from context
2. Execute the Turf helper with user parameters
3. Filter null geometries and add results as a new layer

```typescript
import buffer from "@turf/buffer";
import { featureCollection } from "@turf/helpers";

export const bufferTool: ProcessingAlgorithm = {
  // ...
  run: (ctx) => {
    const fc = requireFeatures(ctx);               // get the GeoJSON layer
    const distance = numberParam(ctx, "distance", 1);
    const units = (ctx.parameters.units as string) || "kilometers";

    // Turf does the heavy lifting on the client thread
    const buffered = buffer(fc, distance, { units });
    const features = (buffered?.features ?? []).filter(f => Boolean(f?.geometry));

    ctx.log(`Buffered ${features.length} feature(s) by ${distance} ${units}`);
    ctx.addResultLayer?.("Buffer", featureCollection(features));
  },
};

```

All Turf-based tools in [`vector-tools.ts`](https://github.com/opengeos/GeoLibre/blob/main/vector-tools.ts) follow this consistent pattern. Because these operations run entirely in JavaScript on the **main thread**, they complete instantly for modest datasets—typically a few thousand features or fewer.

## DuckDB-WASM Spatial for Large Datasets and Complex Queries

When data volume grows or operations require **spatial joins**, **topology analysis**, or SQL-style aggregations, GeoLibre switches to **DuckDB-WASM** with the **Spatial extension**. This keeps processing client-side while leveraging a columnar SQL engine.

### How DuckDB-WASM Integration Works

**[`topology-tools.ts`](https://github.com/opengeos/GeoLibre/blob/main/topology-tools.ts)** (line 140) and **[`duckdb-processing.ts`](https://github.com/opengeos/GeoLibre/blob/main/duckdb-processing.ts)** implement a four-step pipeline:

1. **Ensure the spatial extension is loaded** — `await duckdb.ensureExtensions(["spatial"])`
2. **Register GeoJSON as a DuckDB table** — `duckdb.registerGeoJson(tagFeatureIndexes(fc))`
3. **Execute spatial SQL queries** — Standard PostGIS-compatible functions like `ST_Union`, `ST_Intersects`
4. **Convert results back to GeoJSON** — Rows become a `FeatureCollection` for the map

```typescript
const duckdb = requireDuckDb(ctx);
await duckdb.ensureExtensions(["spatial"]);
const registered = await duckdb.registerGeoJson(tagFeatureIndexes(fc));
const rows = await duckdb.query(`
  SELECT ST_Union(geometry) AS geometry
  FROM ${registered.table}
`);

```

Because DuckDB runs as **WebAssembly in a separate thread**, the pipeline stays fully **client-side**—no network requests, no server round-trips. The WASM runtime handles heavy computation without blocking the UI.

## How the Dual-Backend System Chooses Engines

Each processing algorithm declares `supportsSidecar: true`. At runtime, **[`runner.ts`](https://github.com/opengeos/GeoLibre/blob/main/runner.ts)** orchestrates backend selection:

- **Check for Python side-car** — If a FastAPI service is available, it can handle the operation
- **Default to Turf.js** — For geometry-only tasks on reasonably sized data
- **Activate DuckDB-WASM** — For spatial joins, large datasets, or SQL-required operations

This design delivers two critical benefits:

- **Responsive UI** — Small operations use lightweight Turf.js
- **Scalable analysis** — Complex work stays in the browser via DuckDB-WASM

## Running Client-Side Analysis in GeoLibre

Use these patterns in custom plugins or the browser console:

**Buffer with Turf.js:**

```typescript
await runProcessingAlgorithm("buffer", { 
  layer: "myLayer", 
  distance: 2, 
  units: "kilometers" 
});

```

**Spatial join with DuckDB-WASM:**

```typescript
await runProcessingAlgorithm("spatialJoin", {
  layerA: "roads",
  layerB: "parcels",
  predicate: "ST_Intersects"
});

```

## Key Source Files in GeoLibre

| File | Purpose |
|------|---------|
| [`packages/processing/src/vector-tools.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/processing/src/vector-tools.ts) | Turf-based algorithms: buffer, centroid, union, simplify |
| [`packages/processing/src/topology-tools.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/processing/src/topology-tools.ts) | DuckDB-WASM Spatial for complex topology and joins |
| [`apps/geolibre-desktop/src/lib/duckdb-processing.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/duckdb-processing.ts) | DuckDB-WASM wrapper: `ensureExtensions`, `registerGeoJson`, `query` |
| [`packages/plugins/src/plugins/maplibre-duckdb.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/maplibre-duckdb.ts) | MapLibre UI integration for DuckDB debugging |
| [`packages/processing/src/runner.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/processing/src/runner.ts) | Backend orchestration: Turf vs DuckDB vs Python side-car |

## Summary

- **Turf.js** handles fast, geometry-only operations on small-to-medium GeoJSON datasets in [`vector-tools.ts`](https://github.com/opengeos/GeoLibre/blob/main/vector-tools.ts)
- **DuckDB-WASM Spatial** scales to large data and complex SQL queries via [`topology-tools.ts`](https://github.com/opengeos/GeoLibre/blob/main/topology-tools.ts) and [`duckdb-processing.ts`](https://github.com/opengeos/GeoLibre/blob/main/duckdb-processing.ts)
- **Automatic backend selection** in [`runner.ts`](https://github.com/opengeos/GeoLibre/blob/main/runner.ts) optimizes for responsiveness without sacrificing capability
- **Fully client-side execution** preserves privacy and works offline, with no server dependencies required

## Frequently Asked Questions

### What file formats does GeoLibre's client-side analysis support?

GeoLibre processes data internally as **GeoJSON FeatureCollections**. The DuckDB-WASM pipeline registers these as temporary tables using `registerGeoJson()`, enabling SQL queries on any source that can be converted to GeoJSON—including Shapefile, GeoPackage, and FlatGeobuf via the application's import converters.

### When does GeoLibre switch from Turf.js to DuckDB-WASM?

The switch happens automatically based on **algorithm requirements** rather than dataset size. Operations requiring spatial joins, set-based aggregations like `ST_Union` across many features, or SQL predicates use DuckDB-WASM. Simple per-feature geometry transforms use Turf.js. Future versions may add dynamic size thresholds.

### Can I use DuckDB-WASM Spatial queries directly without the processing API?

Yes. The underlying DuckDB instance is accessible through the processing context's `duckdb` capability. Advanced users can call `duckdb.query()` directly with raw spatial SQL after ensuring the spatial extension is loaded, though the processing algorithm wrapper handles result conversion and layer management automatically.

### Does GeoLibre's client-side analysis work in mobile browsers?

Yes. Both **Turf.js** and **DuckDB-WASM** run in any modern browser with WebAssembly support. Performance depends on device memory—DuckDB-WASM excels on desktop but handles moderate datasets on mobile. The WASM runs in a separate thread via Web Workers where available, keeping the map responsive.