# How to Handle Coordinate Reference Systems and Projections in GeoLibre

> Learn how to handle coordinate reference systems and projections in GeoLibre. GeoLibre automatically detects CRS types and reprojects data to EPSG:4326 for seamless MapLibre integration.

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

---

**GeoLibre automatically detects source CRS types, parses EPSG codes via utility functions in [`crs-utils.ts`](https://github.com/opengeos/GeoLibre/blob/main/crs-utils.ts) and [`crs-catalog.ts`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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:

```typescript
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`](https://github.com/opengeos/GeoLibre/blob/main/crs-utils.ts)) extracts and normalizes the CRS from legacy GeoJSON metadata:

```typescript
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`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/crs-catalog.ts), the **`parseEpsgCode`** function standardizes these variations into clean integer codes:

```typescript
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`](https://github.com/opengeos/GeoLibre/blob/main/crs-catalog.ts), the **`CRS_CATALOG`** array provides a comprehensive registry of geographic and projected systems:

```typescript
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`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/ogc-vector-tiles.ts) checks whether a collection’s CRS field indicates geographic coordinates:

```typescript
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`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/raster-info.ts), the **`RasterInfo`** interface includes:

```typescript
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

```typescript
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

```typescript
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

```typescript
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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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.