How DuckDB-WASM Handles GeoParquet, Shapefiles, KML, and KMZ Files in GeoLibre
DuckDB-WASM handles GeoParquet, Shapefiles, KML, and KMZ files through a unified SQL pipeline using GDAL's ST_Read function, automatically selecting the correct driver for each format and converting geometries to GeoJSON entirely within the browser.
GeoLibre, an open-source geospatial visualization project maintained by opengeos, leverages DuckDB-WASM to bring server-grade vector data processing to the client side. Unlike traditional web mapping workflows that require backend services or format-specific JavaScript libraries, GeoLibre uses a single abstraction—GDAL's spatial SQL function ST_Read—to ingest diverse file formats. This article explains exactly how the loader works, with direct references to the source code implementation in apps/geolibre-desktop/src/lib/duckdb-vector-loader.ts.
The Unified DuckDB-WASM Loading Pipeline
All four formats follow an identical seven-step pipeline. The consistency of this approach eliminates the need for format-specific parsing code in your application.
Step 1: File Ingestion into the Virtual File System
The DuckDBVectorFile interface represents any vector file to be loaded. Raw bytes are written to DuckDB-WASM's in-memory virtual file system before SQL execution.
For multi-file formats like Shapefile, the siblingFiles array carries required companion files (.dbf, .prj, .shx).
const shpFiles: DuckDbVectorFile = {
name: "buildings.shp",
extension: "shp",
data: await fetch("/data/buildings.shp").then(r => r.arrayBuffer()),
siblingFiles: [
{ name: "buildings.dbf", extension: "dbf", data: await fetch("/data/buildings.dbf").then(r => r.arrayBuffer()) },
{ name: "buildings.prj", extension: "prj", data: await fetch("/data/buildings.prj").then(r => r.arrayBuffer()) },
],
};
Step 2: SQLite Connection Initialization
The loader obtains a temporary database connection via getDatabase()/getSqlDatabase() from duckdb-vector-loader.ts. This connection provides the execution context for GDAL's spatial extension.
Step 3: Automatic GDAL Driver Selection
The ST_Read SQL function automatically selects the appropriate GDAL driver based on file extension and content. No manual driver configuration is required.
- GeoParquet — DuckDB's native Parquet engine handles most processing, with
ST_Readproviding the spatial metadata layer - Shapefile — GDAL's ESRI Shapefile driver reads the
.shpand companion files from the virtual file system - KML — GDAL's KML driver parses the XML-based format directly
- KMZ — The same KML driver transparently decompresses the ZIP archive; no manual unzip step needed
Step 4: SQL Query Construction with ST_Read
The loader builds a parameterized query in duckdb-vector-loader.ts. The layerArgSql helper conditionally adds a layer= argument for multi-layer sources.
SELECT * FROM ST_Read('filename', keep_wkb=true [, layer='layer_name'])
The keep_wkb=true argument preserves original Well-Known Binary (WKB) geometries for potential reprojection operations later in the pipeline.
Step 5: Geometry Conversion to GeoJSON
After row retrieval, duckdb-geometry.ts processes the results. The wkbRowsToFeatureCollection function:
- Identifies the geometry column (aliased to
__geolibre_geometry_geojsonvia theGEOMETRY_JSON_COLUMNconstant) - Converts WKB bytes to GeoJSON geometry objects
- Strips GDAL's automatically added
OGC_FIDcolumn when present
// Result: standard GeoJSON FeatureCollection
{
"type": "FeatureCollection",
"features": [
{"type": "Feature", "geometry": {...}, "properties": {...}},
...
]
}
Step 6: Error Handling for Unsupported Geometries
The loader implements specific error patterns in duckdb-vector-loader.ts:
isUnsupportedSurfaceWkbError— Catches Polyhedral Surface and TIN geometry types that DuckDB-WASM cannot yet processisGenericUnsupportedWkbError— Handles other WKB parsing failures
These functions enable graceful degradation with user-facing warnings rather than hard failures.
Step 7: Large Dataset Guard
duckdb-vector-guard.ts evaluates estimated feature counts before full loading. For GeoParquet files, it uses the fast read_parquet metadata path to obtain row counts without reading the entire file. Users receive a confirmation prompt when datasets exceed configurable thresholds.
Complete Loading Examples for Each Format
GeoParquet
import { loadVectorFile } from "./duckdb-vector-loader";
const parquetFile: DuckDbVectorFile = {
name: "roads.parquet",
extension: "parquet",
data: await fetch("/data/roads.parquet").then(r => r.arrayBuffer()),
};
const featureCollection = await loadVectorFile(parquetFile);
Shapefile with Sibling Files
const shpFiles: DuckDbVectorFile = {
name: "buildings.shp",
extension: "shp",
data: await fetch("/data/buildings.shp").then(r => r.arrayBuffer()),
siblingFiles: [
{ name: "buildings.dbf", extension: "dbf", data: await fetch("/data/buildings.dbf").then(r => r.arrayBuffer()) },
{ name: "buildings.prj", extension: "prj", data: await fetch("/data/buildings.prj").then(r => r.arrayBuffer()) },
],
};
const featureCollection = await loadVectorFile(shpFiles);
KML
const kmlFile: DuckDbVectorFile = {
name: "tour.kml",
extension: "kml",
data: await fetch("/data/tour.kml").then(r => r.arrayBuffer()),
};
const featureCollection = await loadVectorFile(kmlFile);
KMZ (Compressed KML)
const kmzFile: DuckDbVectorFile = {
name: "tour.kmz",
extension: "kmz",
data: await fetch("/data/tour.kmz").then(r => r.arrayBuffer()),
};
const featureCollection = await loadVectorFile(kmzFile);
// Note: No manual decompression required
All four examples execute internal SQL equivalent to:
SELECT * FROM ST_Read('filename', keep_wkb=true);
Key Source Files and Their Roles
| File | Purpose |
|---|---|
duckdb-vector-loader.ts |
Core orchestration: file system writes, ST_Read query construction, error handling, GeoJSON assembly |
duckdb-geometry.ts |
Geometry column detection, WKB-to-GeoJSON conversion, OGC_FID cleanup |
duckdb-vector-guard.ts |
Pre-load size estimation and user confirmation for large datasets |
maplibre-vector.ts (plugin) |
Extension registration (.geojson, .parquet, .shp, .kml, .kmz) triggering the loader pipeline |
Full source: https://github.com/opengeos/GeoLibre
Performance Characteristics
- GeoParquet — Fastest path due to DuckDB's native Parquet support; column pruning and predicate pushdown reduce memory pressure
- Shapefile — Moderate speed; multiple file I/O operations in WASM file system add overhead
- KML/KMZ — Slowest due to XML parsing; KMZ adds minor decompression cost (handled internally by GDAL)
Summary
- DuckDB-WASM provides one SQL interface (
ST_Read) for four vector formats through automatic GDAL driver selection - The
DuckDBVectorFileabstraction handles both single files and multi-file archives via thesiblingFilesarray - KMZ requires no manual extraction—GDAL's KML driver decompresses transparently
- Geometry conversion preserves original WKB for reprojection, with error handling for unsupported surface types
- Large dataset guards prevent browser memory exhaustion, using fast Parquet metadata queries when available
Frequently Asked Questions
Does DuckDB-WASM require separate code paths for each file format?
No. According to the GeoLibre source code, all formats use the same loadVectorFile function in duckdb-vector-loader.ts. The ST_Read SQL function automatically detects the format and selects the appropriate GDAL driver based on file extension and content signatures.
Can DuckDB-WASM read KMZ files without manual decompression?
Yes. The GDAL KML driver used by ST_Read transparently handles ZIP-compressed KMZ archives. You pass the .kmz file directly to the loader with no unzip step required, as shown in the implementation examples from duckdb-vector-loader.ts.
What happens when a Shapefile is missing its .prj projection file?
The loader will still execute, but geometries may lack proper coordinate reference system information. The ST_Read function proceeds with the data available; downstream code in duckdb-geometry.ts handles the conversion to GeoJSON, though reprojection accuracy depends on having the complete sibling file set including .prj.
How does GeoLibre prevent browser crashes from massive vector files?
duckdb-vector-guard.ts implements a size-checking layer that estimates feature counts before full loading. For GeoParquet, it uses the efficient read_parquet metadata path to obtain row counts instantly. When thresholds are exceeded, the user receives a confirmation dialog rather than an automatic load that could exhaust memory.
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 →