How to Perform CRS-Aware Reprojection with DuckDB-WASM in GeoLibre
GeoLibre performs browser-based CRS-aware reprojection using the DuckDB-WASM Spatial extension (PROJ) through the reprojectFeatureCollectionToWgs84 helper, which detects source CRS metadata, executes SQL transforms via ST_Transform, and returns EPSG:4326-compliant GeoJSON.
GeoLibre is an open-source desktop GIS application that processes geospatial data entirely in the browser using DuckDB-WASM. When loading vector data that carries a non-WGS84 coordinate reference system (CRS), the library automatically reprojects geometries to EPSG:4326 using PROJ-backed spatial functions. This article explains the internal mechanics and implementation patterns for CRS-aware reprojection with DuckDB-WASM in GeoLibre based on the actual source code.
How CRS Detection and Reprojection Works
The reprojection workflow centers on the reprojectFeatureCollectionToWgs84 function in apps/geolibre-desktop/src/lib/duckdb-vector-loader.ts. This utility orchestrates CRS detection, database initialization, and SQL-based transformation to WGS84.
Detecting the Source CRS from GeoJSON
GeoLibre first inspects input GeoJSON for legacy CRS metadata. The sourceCrsFromGeoJson function (lines 904-913) parses any top-level crs member and returns an AUTHORITY:CODE string such as EPSG:3857. If the data is already geographic (EPSG:4326 or CRS84), the function returns null to skip unnecessary reprojection.
Initializing the DuckDB-WASM Instance
Before executing transformations, getDatabase (lines 58-71) memoizes a shared AsyncDuckDB instance. This helper guarantees that the DuckDB Spatial extension is installed and ready, ensuring PROJ capabilities are available in the WASM environment before any reprojection query runs.
Registering GeoJSON as In-Memory Files
To process GeoJSON through DuckDB's SQL engine, the collection is stringified and registered via db.registerFileText. The helper stripAutoFidColumn removes synthetic OGC_FID columns that would cause duplicate-column errors during the re-read operation (lines 545-557).
Executing ST_Transform for Reprojection
The core reprojection logic uses DuckDB's spatial SQL functions. The query SELECT * FROM ST_Read(<sourceFile>) materializes features as GeoJSON. When a source CRS is detected, the geometryGeoJsonSql helper injects ST_Transform into the SELECT clause, projecting geometries to EPSG:4326 before returning the result (lines 936-978). The function finally strips the obsolete crs member and returns the clean FeatureCollection.
Implementing CRS Reprojection in Your Code
You can trigger reprojection manually or rely on automatic handling during file loading. Both approaches use the same underlying DuckDB-WASM infrastructure.
Reprojecting GeoJSON with Legacy CRS Metadata
Use reprojectFeatureCollectionToWgs84 when working with GeoJSON that contains a crs member:
import { reprojectFeatureCollectionToWgs84 } from "./duckdb-vector-loader";
async function demoReprojectGeoJson(fc: GeoJSON.FeatureCollection) {
// Parses `crs` member, runs DuckDB-WASM → PROJ, returns EPSG:4326
const reprojected = await reprojectFeatureCollectionToWgs84(fc);
console.log("Reprojected FeatureCollection:", reprojected);
}
Loading Vector Files with Automatic Reprojection
For Shapefiles, GeoPackages, or other vector formats, loadDuckDbVectorFile handles CRS detection and reprojection automatically:
import { loadDuckDbVectorFile, type DuckDbVectorFile } from "./duckdb-vector-loader";
async function demoLoadVectorFile(file: DuckDbVectorFile) {
// Reads via ST_Read, discovers CRS, calls reprojectFeatureCollectionToWgs84 internally
const collection = await loadDuckDbVectorFile(file, {
// Optional: force source CRS if file lacks metadata
// overrideSourceCrs: "EPSG:3857",
});
console.log("Loaded & reprojected collection:", collection);
}
Explicitly Specifying a Source CRS
When input data lacks CRS metadata, provide the source CRS as the second argument:
async function demoExplicitCrs(fc: GeoJSON.FeatureCollection) {
const sourceCrs = "EPSG:27700"; // British National Grid
const reprojected = await reprojectFeatureCollectionToWgs84(fc, sourceCrs);
console.log(reprojected);
}
Key Source Files for CRS Reprojection
Understanding the module structure helps when extending or debugging reprojection behavior:
apps/geolibre-desktop/src/lib/duckdb-vector-loader.ts– Core implementation containingreprojectFeatureCollectionToWgs84,sourceCrsFromGeoJson, and database helpers.apps/geolibre-desktop/src/lib/crs-utils.ts– Utilities includingisGeographicCrsfor identifying WGS84-type coordinate systems.apps/geolibre-desktop/src/lib/duckdb-geometry.ts– Generates thegeometryGeoJsonSqlfragment used forST_Transforminjection.apps/geolibre-desktop/src/lib/spatial-extension-config.ts– Configures custom paths for the DuckDB Spatial extension viaVITE_DUCKDB_SPATIAL_EXTENSION_PATH.apps/geolibre-desktop/src/lib/duckdb-wasm-bundles.ts– Manages DuckDB-WASM bundle selection and worker creation.
Summary
- CRS-aware reprojection with DuckDB-WASM in GeoLibre relies on the DuckDB Spatial extension (PROJ) running entirely in the browser.
- The
reprojectFeatureCollectionToWgs84function induckdb-vector-loader.tsorchestrates the detection, transformation, and cleanup workflow. - Source CRS detection parses legacy GeoJSON
crsmembers viasourceCrsFromGeoJsonor accepts explicit overrides as function arguments. - Data is registered as in-memory files using
registerFileText, then processed withST_ReadandST_TransformSQL functions to ensure EPSG:4326 output. - All vector loading through
loadDuckDbVectorFileautomatically invokes this reprojection pipeline when non-WGS84 data is detected.
Frequently Asked Questions
What is the primary function for reprojection in GeoLibre?
The reprojectFeatureCollectionToWgs84 function in apps/geolibre-desktop/src/lib/duckdb-vector-loader.ts serves as the main entry point. It detects the source CRS, initializes the DuckDB-WASM database with the spatial extension, and executes SQL queries with ST_Transform to convert geometries to EPSG:4326.
How does GeoLibre handle legacy GeoJSON CRS metadata?
GeoLibre uses the sourceCrsFromGeoJson helper (lines 904-913) to parse the deprecated crs member found in legacy GeoJSON specifications. If present, it extracts the AUTHORITY:CODE string (e.g., EPSG:3857); if absent or already geographic, it returns null to skip unnecessary transformation.
Can I specify a custom source CRS if the file lacks metadata?
Yes. When calling reprojectFeatureCollectionToWgs84, provide the source CRS as the second argument. Similarly, loadDuckDbVectorFile accepts an overrideSourceCrs option in its configuration object to force a specific projection when file metadata is missing or incorrect.
What spatial extension powers the reprojection?
The reprojection capabilities are provided by the DuckDB Spatial extension, which bundles PROJ for coordinate transformations. According to the GeoLibre source code, the getDatabase helper ensures this extension is loaded into the DuckDB-WASM instance before executing any spatial queries, enabling full client-side CRS transformations without server dependencies.
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 →