How to Handle Coordinate Reference Systems and Projections in GeoLibre

GeoLibre automatically detects source CRS types, parses EPSG codes via utility functions in crs-utils.ts and crs-catalog.ts, and reprojects non-WGS84 data to EPSG:4326 using DuckDB-WASM to ensure compatibility with MapLibre's geographic coordinate system.

GeoLibre is an open-source geospatial application that aggregates data from GeoJSON, Shapefiles, raster tiles, and OGC API collections. Because these sources often use different coordinate reference systems (CRS), the codebase includes a robust normalization layer that detects, parses, and transforms coordinates before rendering.

Detecting Geographic vs. Projected CRS

The first step in CRS handling is determining whether incoming data uses geographic coordinates (WGS 84) or requires reprojection.

The isGeographicCrs Function

In apps/geolibre-desktop/src/lib/crs-utils.ts, the isGeographicCrs function identifies whether a CRS string represents WGS 84 (EPSG:4326) or the equivalent CRS84 identifier:

export function isGeographicCrs(crs: string | undefined): boolean {
  const value = (crs ?? "").replace(/\s+/g, "").toUpperCase();
  if (!value) return true;                     // blank → WGS84
  return value.includes("CRS84") || /EPSG:+4326\b/.test(value);
}

This function returns true for blank strings (interpreted as WGS 84), CRS84 references, or explicit EPSG:4326 codes. Any other EPSG code—such as EPSG:32643 for a UTM zone—is flagged as projected and triggers the reprojection pipeline.

Handling Legacy GeoJSON CRS Metadata

Older GeoJSON files may contain a top-level crs member encoding projected coordinates that predate the modern specification.

Extracting Legacy CRS Information

The projectedGeoJsonCrs function (also in crs-utils.ts) extracts and normalizes the CRS from legacy GeoJSON metadata:

export function projectedGeoJsonCrs(value: unknown): string | null {
  const name = (value as { crs?: { properties?: { name?: unknown } } })?.crs?.properties?.name;
  if (typeof name !== "string" || isGeographicCrs(name)) return null;
  const match = name.toUpperCase().match(/EPSG:+(\d+)/);
  return match ? `EPSG:${match[1]}` : null;
}

When this function returns a non-null value, the caller invokes reprojectFeatureCollectionToWgs84 (via DuckDB-WASM’s ST_Transform) to convert coordinates from the source projected units (e.g., meters) to geographic degrees before adding the layer to the map.

EPSG Code Parsing and Normalization

User inputs and external APIs provide EPSG codes in varying formats—ranging from simple numbers to full URN strings.

The parseEpsgCode Utility

Located in apps/geolibre-desktop/src/lib/crs-catalog.ts, the parseEpsgCode function standardizes these variations into clean integer codes:

export function parseEpsgCode(value: string | null | undefined): number | null {
  if (value === null || value === undefined) return null;
  const match = String(value)
    .trim()
    .toUpperCase()
    .replace(/^(?:URN:OGC:DEF:CRS:)?EPSG\s*:*\s*/, "")
    .match(/^(\d{4,6})$/);
  if (!match) return null;
  const code = Number(match[1]);
  return Number.isInteger(code) ? code : null;
}

This parser tolerates whitespace, optional EPSG: prefixes, and full URN identifiers like urn:ogc:def:crs:EPSG::4326.

The CRS Catalog for Processing Tools

Processing algorithms such as reproject_vector and assign_projection_vector require a curated list of valid coordinate systems.

The CRS_CATALOG Constant

In crs-catalog.ts, the CRS_CATALOG array provides a comprehensive registry of geographic and projected systems:

export const CRS_CATALOG: readonly CrsEntry[] = [
  ...GEOGRAPHIC_CRS,
  ...NAMED_PROJECTED_CRS,
  ...utmZones("WGS 84", 32600, "N", range(1, 60)),
  ...utmZones("WGS 84", 32700, "S", range(1, 60)),
  ...utmZones("NAD83", 26900, "N", range(1, 23)),
  ...utmZones("ETRS89", 25800, "N", range(28, 37)),
  ...mgaZones("GDA94", 28300, range(48, 58)),
  ...mgaZones("GDA2020", 7800, range(46, 59)),
  ...japanPlaneZones(),
  ...chinaGaussKrugerZones(),
];

The catalog includes worldwide geographic systems, UTM and MGA zones, and national grid definitions. Helper functions such as crsEntryForCode, searchCrsCatalog, and formatCrsLabel enable the UI to present searchable pickers and display friendly labels like "WGS 84 / UTM zone 17N (EPSG:32617)".

OGC API Vector Tiles CRS Handling

When consuming OGC API vector-tile collections, GeoLibre must validate that spatial extents are suitable for map fitting operations.

Validating Geographic Extents

The isLonLatCrs function in apps/geolibre-desktop/src/lib/ogc-vector-tiles.ts checks whether a collection’s CRS field indicates geographic coordinates:

function isLonLatCrs(crs: unknown): boolean {
  if (crs === undefined || crs === null) return true; // default CRS84
  return typeof crs === "string" && /CRS84|4326/i.test(crs);
}

The unionCollectionBounds function aggregates bounding boxes only from collections with confirmed geographic CRS, ignoring projected extents that cannot be used directly for "zoom-to-layer" operations.

Raster CRS Metadata

Raster layers expose CRS information through a normalized metadata structure. In apps/geolibre-desktop/src/lib/raster-info.ts, the RasterInfo interface includes:

export interface RasterInfo {
  crs: string;               // e.g. "EPSG:32643"
  crsCitation?: string;     // optional human citation
  // …
}

When a raster’s CRS is projected, the same DuckDB-WASM reprojection pipeline aligns the data with the map’s geographic base layer.

Practical Implementation Examples

Example 1: Adding a GeoJSON Layer with a Legacy CRS

import { projectedGeoJsonCrs, isGeographicCrs } from "./crs-utils";
import { reprojectFeatureCollectionToWgs84 } from "./duckdb-reproject";

async function addGeoJsonLayer(fc: unknown) {
  // 1️⃣ Detect if a legacy `crs` member forces reprojection
  const sourceCrs = projectedGeoJsonCrs(fc);
  const geojson = fc as GeoJSON.FeatureCollection;

  // 2️⃣ If needed, re‑project the features to WGS84
  const normalized = sourceCrs
    ? await reprojectFeatureCollectionToWgs84(geojson, sourceCrs)
    : geojson;

  // 3️⃣ Add the layer (MapLibre expects geographic coordinates)
  map.addSource("uploaded", { type: "geojson", data: normalized });
}

Example 2: Populating the CRS Picker in a Processing Dialog

import {
  CRS_CATALOG,
  searchCrsCatalog,
  formatCrsLabel,
  parseEpsgCode,
} from "./crs-catalog";

// Load all entries for the initial list
const allOptions = CRS_CATALOG.map(formatCrsLabel);

// Filter on user input (e.g. from a text field)
function filterCrs(query: string) {
  const results = searchCrsCatalog(query);
  return results.map(formatCrsLabel);
}

// When the user types an EPSG string, resolve the numeric code
function resolveTyped(value: string) {
  const code = parseEpsgCode(value);
  return code !== null ? `EPSG:${code}` : null;
}

Example 3: Determining Whether an OGC Collection Can Be Zoomed To

import { unionCollectionBounds } from "./ogc-vector-tiles";

async function zoomToOgcLayer(tilesUrl: string) {
  const bounds = await fetchOgcCollectionsBounds(tilesUrl);
  if (bounds) {
    const [w, s, e, n] = bounds;
    map.fitBounds([[w, s], [e, n]], { padding: 20 });
  } else {
    console.warn("No geographic bounds available – cannot auto‑zoom.");
  }
}

Summary

  • Detection: The isGeographicCrs function in crs-utils.ts identifies whether data is already in WGS 84 or requires transformation.
  • Legacy Support: The projectedGeoJsonCrs parser handles deprecated GeoJSON crs members by extracting EPSG codes for reprojection.
  • Normalization: The parseEpsgCode utility in crs-catalog.ts standardizes varied EPSG string formats into clean integer codes.
  • Catalog: The CRS_CATALOG provides a curated registry of geographic and projected systems for processing tools and UI pickers.
  • Reprojection: Non-geographic data is transformed to EPSG:4326 via DuckDB-WASM before rendering in MapLibre.

Frequently Asked Questions

What coordinate reference system does GeoLibre use for rendering?

GeoLibre renders all data in WGS 84 (EPSG:4326) geographic coordinates, also known as CRS84, which is the standard expected by MapLibre. Any data in a projected CRS—such as UTM zones or national grids—is reprojected before display.

How does GeoLibre handle legacy GeoJSON files that include a crs member?

According to the source code in crs-utils.ts, GeoLibre uses the projectedGeoJsonCrs function to extract the EPSG code from legacy GeoJSON metadata. If the CRS is projected, the system invokes reprojectFeatureCollectionToWgs84 via DuckDB-WASM to transform the coordinates to geographic degrees before adding them to the map.

What projection engine does GeoLibre use for coordinate transformations?

GeoLibre leverages DuckDB-WASM with spatial extensions for reprojection. Specifically, it uses the ST_Transform function to convert coordinates from source projected systems (e.g., meters in a UTM zone) to the geographic WGS 84 system required by the rendering engine.

How can I check if my raster data is compatible with GeoLibre without reprojection?

Check the crs property in the RasterInfo interface from raster-info.ts. If the value is EPSG:4326 or the string is empty (interpreted as WGS 84), the data is compatible. Any other EPSG code indicates the raster requires reprojection through the DuckDB-WASM pipeline to align with the map's geographic base.

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 →