How 1000+ Whitebox Geoprocessing Tools Run in the Browser Using WebAssembly
GeoLibre executes Whitebox geoprocessing tools entirely client-side by lazy-loading a ~5 MB gzipped WebAssembly module into a WASI runtime, mounting an in-memory filesystem for data exchange, and processing outputs through specialized converters for GeoJSON, Cloud-Optimized GeoTIFFs, and zipped Shapefile bundles.
The GeoLibre project brings the full WhiteboxTools geoprocessing engine—over 1000 algorithms for raster, vector, and LiDAR analysis—directly into the browser through a WebAssembly (WASM) implementation. Unlike traditional geoprocessing architectures that require a Python sidecar or remote server, the @geolibre/processing package enables complete client-side execution by porting the Rust-based WhiteboxTools core to WASM and wrapping it in a WebAssembly System Interface (WASI) runtime.
The WebAssembly Execution Pipeline
The browser-side geoprocessing workflow mirrors the Python sidecar API but replaces remote HTTP calls with in-process WASI execution, maintaining identical request/response shapes through RunWhiteboxToolRequest and WhiteboxJob objects.
Lazy-Loading the WASI Runtime
To optimize initial page load performance, the heavy computational module is not bundled with the main application. Instead, packages/processing/src/wasm-client.ts exposes the loadToolsModule() function that dynamically imports the geolibre-wasm/tools module only when the user initiates local execution.
// packages/processing/src/wasm-client.ts
function loadToolsModule(): Promise<ToolsModule> { … }
This module weighs approximately 5 MB gzipped and contains the compiled WhiteboxTools algorithms. The lazy-loading strategy ensures that users only download the WASM binary when they explicitly select the Run locally (WASM) option.
Preparing the In-Memory Filesystem
Before tool execution, the system constructs a temporary virtual filesystem at /work within the WASI environment. The runWhiteboxToolWasm function (specifically lines 90–130) maps toolbox parameters to filesystem entries using the paramKind helper, which distinguishes between vector, raster, and file input/output roles.
Input data preparation follows these rules:
- Vector data is JSON-encoded and written to
*.geojsonfiles. - Raster and LiDAR files are written as raw bytes to
*.tif,*.las, or generic*.datfiles. - Remote inputs specified via URL are fetched using
fetchBytesbefore being mounted.
Building Command-Line Arguments
Each tool parameter translates to a command-line flag. File parameters become --name=/work/file paths, while scalar and boolean parameters are passed as --name=value arguments. This convention maintains compatibility with the original WhiteboxTools CLI interface while operating inside the browser sandbox.
Executing the Tool
The actual execution occurs through the WASI runtime's runTool function:
const { exitCode, stdout, files } = await runTool(request.tool_id, { args, input });
This call blocks until the Rust-implemented algorithm completes, returning the exit code, log output, and a map of output files written to /work.
Handling Input and Output Data Formats
Post-processing logic in runWhiteboxToolWasm (lines 60–90, 120–135, and 160–180) converts the raw filesystem outputs into browser-friendly formats suitable for rendering or download.
Vector Output Processing
For vector geoprocessing results, the system supports multiple output formats:
- GeoJSON (default): Parsed directly into a standard
FeatureCollectionobject for immediate rendering. - Geoparquet, FlatGeobuf, and Shapefile: Returned as raw
Uint8Arraybytes for download.
When exporting to Shapefile, the helper zipShapefileSidecars bundles the required component files (.shp, .shx, .dbf, plus optional .prj and .cpg) into a single zip archive, warning users if core members are missing.
Raster Output Conversion
Raster outputs are always produced as GeoTIFFs. The helper ensureWhiteboxRasterCog in packages/processing/src/cog-convert.ts re-encodes these as Cloud-Optimized GeoTIFFs (COG) via convertGeoTiffToCog, ensuring efficient visualization and partial reads over HTTP when the result is served from a remote source.
Tool Discovery and Catalog Management
The WASM binary ships with a complete manifest of available tools. The function listWhiteboxWasmTools (lines 28–32 in wasm-client.ts) enumerates all tool IDs embedded in the binary:
import { listWhiteboxWasmTools } from "@geolibre/processing";
const ids = await listWhiteboxWasmTools(); // e.g. ["fill_depressions", "slope", …]
console.log("WASM tools:", ids);
The UI constructs its toolbox catalog by merging this WASM-only manifest with the Python sidecar catalog using mergeWasmToolManifests, ensuring users see a unified interface regardless of the execution backend. Tool registration logic resides in packages/processing/src/registry.ts, while packages/processing/src/wasm-convert.worker.ts provides a Web Worker wrapper to offload WASM execution from the main thread.
Practical Implementation Example
The following TypeScript example demonstrates running the Fill Depressions tool entirely in the browser:
import { runWhiteboxToolWasm } from "@geolibre/processing";
/* Example: fill depressions */
const request = {
tool_id: "fill_depressions",
vector_output_format: "geojson",
parameters: { z_factor: "2" },
layer_inputs: {
dem: {
name: "dem",
kind: "raster_in",
bytes: await fetchBytes("https://example.com/dem.tif"),
},
},
tool: {
id: "fill_depressions",
params: [
{ name: "dem", io_role: "raster_in", required: true },
{ name: "output", io_role: "raster_out", required: false },
],
},
};
const job = await runWhiteboxToolWasm(request);
if (job.status === "succeeded") {
// `job.outputs` contains the raster COG (Uint8Array) under the key “output”
const cog = job.outputs["output"] as Uint8Array;
// render or download...
}
To download a Shapefile result as a zipped bundle:
if (job.outputs["output"] instanceof Uint8Array) {
const zipBytes = job.outputs["output"]; // already zipped shapefile bundle
const blob = new Blob([zipBytes], { type: "application/zip" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "output.zip";
a.click();
}
Summary
- GeoLibre's
@geolibre/processingpackage executes 1000+ WhiteboxTools algorithms client-side by compiling the Rust codebase to WebAssembly and running it in a WASI environment. - Lazy loading in
wasm-client.tsdefers the ~5 MB WASM module download until the user explicitly requests local execution, preserving initial load performance. - Virtual filesystem management at
/workhandles data exchange between JavaScript and the WASM runtime, supporting GeoJSON, raw raster bytes, and LiDAR formats. - Post-processing helpers like
ensureWhiteboxRasterCogandzipShapefileSidecarsconvert raw tool outputs into standards-compliant Cloud-Optimized GeoTIFFs and bundled Shapefiles. - Tool discovery via
listWhiteboxWasmToolsenables dynamic UI generation, whilewasm-convert.worker.tsoffloads computation to a background thread for responsive user experiences.
Frequently Asked Questions
How does GeoLibre handle large raster files in the browser?
GeoLibre processes raster data as raw byte arrays within the WASM heap, converting outputs to Cloud-Optimized GeoTIFFs (COG) using convertGeoTiffToCog in packages/processing/src/cog-convert.ts. The COG format enables efficient partial reads and streaming visualization without loading the entire dataset into memory, making it feasible to handle large rasters client-side.
What vector output formats are supported when running tools in WebAssembly?
The WASM runner supports GeoJSON (parsed immediately into FeatureCollections), Geoparquet, FlatGeobuf, and Shapefile exports. For Shapefile outputs, the zipShapefileSidecars helper automatically bundles all required sidecar files (.shp, .shx, .dbf, .prj, .cpg) into a single zip archive for download, according to the logic in runWhiteboxToolWasm.
Is the WebAssembly module loaded immediately when the GeoLibre page loads?
No. The module implementing packages/processing/src/wasm-client.ts uses dynamic imports via loadToolsModule() to fetch the ~5 MB gzipped geolibre-wasm/tools binary only when the user clicks Run locally (WASM). This lazy-loading strategy prevents bloating the initial JavaScript bundle and preserves application startup time.
How does the browser-based WASM implementation compare to the Python sidecar?
Both implementations share identical API contracts using RunWhiteboxToolRequest and WhiteboxJob types defined in packages/processing/src/sidecar-client.ts. The WASM version replaces HTTP calls to a Python backend with in-process WASI execution via runTool, eliminating server dependencies and network latency while maintaining the same parameter schemas and output formats.
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 →