# How to Create Custom Map Projections with Planetary Basemaps in GeoLibre

> Learn how to create custom map projections with planetary basemaps in GeoLibre. This guide explains how to convert equirectangular planetary data into Web-Mercator tiles, extending reprojection logic for your needs.

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

---

**GeoLibre converts equirectangular planetary data into Web-Mercator tiles using a worker-based reprojection pipeline that resamples raster rows onto Mercator latitude grids, enabling you to add custom projections by extending the dataset registry and reprojection logic.**

GeoLibre is an open-source mapping framework that renders planetary basemaps for Mars, the Moon, and other celestial bodies by transforming raster data into Web-Mercator projections compatible with MapLibre GL. Understanding how to create custom map projections with planetary basemaps allows you to integrate specialized datasets—from Venus hyperspectral imagery to custom sinusoidal projections—into your geospatial applications.

## Understanding the Reprojection Pipeline

GeoLibre handles two distinct types of planetary basemap sources. **Mars and Moon basemaps** are served as pre-projected Web-Mercator tiles directly from OpenPlanetaryMap. All other celestial bodies—including Mercury, Venus, the Galilean moons, Titan, Pluto, and Charon—are available only as **equirectangular (EPSG:4326) WMS layers** that require server-side reprojection.

The reprojection pipeline centers on the Cloudflare worker endpoint `/wms/<dataset>/<z>/<x>/<y>.png` defined in [`workers/tiles/src/index.ts`](https://github.com/opengeos/GeoLibre/blob/main/workers/tiles/src/index.ts). When a tile request arrives, the worker fetches the original WMS image and passes it through the reprojection module before returning a Web-Mercator-compatible PNG with `Access-Control-Allow-Origin: *` headers.

## Core Reprojection Logic

The transformation from equirectangular to Mercator happens in [`workers/tiles/src/reproject.ts`](https://github.com/opengeos/GeoLibre/blob/main/workers/tiles/src/reproject.ts) through three key functions:

- **`tileGeoBounds`** computes the geographic bounds (west, south, east, north) of a Web-Mercator tile at specific zoom levels.
- **`wmsBboxFor`** constructs the WMS BBOX query parameter string from those geographic bounds.
- **`remapRowsToMercator`** performs the actual resampling by mapping rows from the equirectangular source image onto the Mercator latitude grid row-by-row.

The `remapRowsToMercator` function isolates latitude conversion through the `mercatorLatDeg` helper. For standard equirectangular sources, this performs a simple 1:1 column mapping with latitude interpolation, making the pipeline efficient for planetary basemaps that span large longitude ranges.

## Handling Projection Locks

When overlaying third-party datasets that require Mercator projection (such as USGS-style raster layers), GeoLibre uses projection lock utilities from [`packages/plugins/src/plugins/map-projection-utils.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/map-projection-utils.ts). The **`ensureMercatorProjection`** function forces the MapLibre map into Mercator mode while the overlay is active.

The system tracks projection locks through `acquireMercatorProjectionLock` and `releaseMercatorProjectionLock`. A lazy "idle" guard automatically restores the previous projection when all locks are released, ensuring the UI remains consistent when switching between planetary basemaps and standard Earth projections.

## Step-by-Step: Adding a Custom Projection

Follow these steps to integrate a new planetary projection into GeoLibre:

### 1. Define the Source Dataset

Add your planetary dataset to the appropriate registry in [`workers/tiles/src/index.ts`](https://github.com/opengeos/GeoLibre/blob/main/workers/tiles/src/index.ts). For pre-projected Web-Mercator tiles, extend the `DATASETS` record. For equirectangular WMS sources, extend `WMS_DATASETS`:

```typescript
const WMS_DATASETS: Record<string, WmsDataset> = {
  // existing entries …
  "venus-hyperspectral": {
    map: "/maps/venus/venus_hyperspectral.map",
    layer: "HYPERSPECTRAL",
  },
};

```

The dataset key must be alphanumeric and the upstream URL or map/layer pair must be allow-listed in the worker configuration.

### 2. Update the Basemap Catalog

Register the new basemap in the offline catalog at [`apps/geolibre-desktop/src/lib/offline-basemaps.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/offline-basemaps.ts) and the planetary-basemap catalog used by the UI. Include the dataset ID and specify the correct ellipsoid:

```typescript
export const PLANETARY_BASEMAPS = [
  // existing basemaps …
  {
    id: "venus-hyperspectral",
    name: "Venus Hyperspectral",
    datasetId: "venus-hyperspectral",
    styleUrl: "https://tiles.geolibre.app/wms/venus-hyperspectral",
    ellipsoidId: "venus", // matches the Venus ellipsoid definition
  },
];

```

### 3. Implement Custom Projection Logic

If your source uses a non-linear projection (such as sinusoidal or Mollweide), extend [`workers/tiles/src/reproject.ts`](https://github.com/opengeos/GeoLibre/blob/main/workers/tiles/src/reproject.ts). Replace the latitude-only resampling in `remapRowsToMercator` with a full forward/inverse projection algorithm using `proj4` or custom formulas:

```typescript
// Example: Adding a custom latitude conversion
function customLatDeg(y: number, height: number): number {
  // Your custom projection logic here
  return convertedLatitude;
}

// Modify remapRowsToMercator to use customLatDeg instead of mercatorLatDeg
// for specific dataset IDs

```

The existing architecture isolates the conversion logic, allowing you to add companion functions like `customLatDeg` without disrupting the core row-remapping structure.

### 4. Expose Client-Side Helpers

If UI components need to switch projections directly, add a method in [`packages/plugins/src/plugins/map-projection-utils.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/map-projection-utils.ts):

```typescript
export function useVenusHyperspectralOverlay(app: MercatorProjectionApp) {
  const key = "venus-hyperspectral";
  acquireMercatorProjectionLock(key, app);
  // Mount Deck.gl layer or other overlay components
  // When removing:
  // releaseMercatorProjectionLock(key, app);
}

```

This ensures that components calling `setMapProjection` with custom types properly acquire Mercator locks when needed.

## Testing Custom Projections

Verify your custom projection implementation through targeted testing:

Unit tests for reprojection logic live in [`tests/map-projection-utils.test.ts`](https://github.com/opengeos/GeoLibre/blob/main/tests/map-projection-utils.test.ts) and [`tests/planetary-basemap.test.ts`](https://github.com/opengeos/GeoLibre/blob/main/tests/planetary-basemap.test.ts). Create analogous tests for new datasets:

```typescript
// tests/wms-reproject.test.ts
import { tileGeoBounds, wmsBboxFor, remapRowsToMercator } from "../workers/tiles/src/reproject";

it("reprojects venus‑hyperspectral tile correctly", async () => {
  const tile = { z: 3, x: 4, y: 2 };
  const bounds = tileGeoBounds(tile);
  const bbox = wmsBboxFor(bounds);
  // fetch mock WMS image (size 256×256) …
  const src = new Uint8Array(256 * 256 * 4); // filled with test data
  const dst = remapRowsToMercator(src, 256, tile, bounds);
  // verify that the top row of `dst` matches the latitude at latN, etc.
  expect(dst.subarray(0, 4)).toEqual(/* expected RGBA */);
});

```

Run the full frontend test suite with `npm run test:frontend` to ensure that Mercator lock logic remains compatible with your new overlay.

## Summary

- **GeoLibre** supports planetary basemaps through a dual-path system: pre-projected Web-Mercator tiles for Mars/Moon and on-the-fly reprojection for equirectangular WMS sources.
- The **reprojection pipeline** in [`workers/tiles/src/reproject.ts`](https://github.com/opengeos/GeoLibre/blob/main/workers/tiles/src/reproject.ts) uses `tileGeoBounds`, `wmsBboxFor`, and `remapRowsToMercator` to transform equirectangular raster data into Mercator tiles.
- **Projection locks** in [`map-projection-utils.ts`](https://github.com/opengeos/GeoLibre/blob/main/map-projection-utils.ts) ensure that Deck.gl overlays requiring Mercator projection can temporarily override the map's current projection state.
- Adding custom projections requires registering datasets in [`index.ts`](https://github.com/opengeos/GeoLibre/blob/main/index.ts), updating the basemap catalog, potentially extending [`reproject.ts`](https://github.com/opengeos/GeoLibre/blob/main/reproject.ts) for non-linear projections, and implementing client-side lock utilities.
- Comprehensive testing in [`tests/planetary-basemap.test.ts`](https://github.com/opengeos/GeoLibre/blob/main/tests/planetary-basemap.test.ts) ensures that new projections maintain compatibility with the existing UI and reprojection pipeline.

## Frequently Asked Questions

### How does GeoLibre handle different planetary coordinate systems?

GeoLibre treats planetary bodies as ellipsoids defined in the basemap catalog. When reprojecting from equirectangular sources, the system uses the standard spherical Mercator formula adapted to the body's radius, but the key distinction is the **source projection** handling in `remapRowsToMercator` rather than the target coordinate system, which remains Web-Mercator for MapLibre GL compatibility.

### Can I use projections other than equirectangular for planetary data?

Yes, but you must extend the reprojection logic in [`workers/tiles/src/reproject.ts`](https://github.com/opengeos/GeoLibre/blob/main/workers/tiles/src/reproject.ts). The current implementation assumes linear longitude relationships. For projections like sinusoidal or stereographic, you need to replace the simple row-remapping in `remapRowsToMercator` with full forward and inverse projection calculations, potentially using libraries like `proj4` to handle the mathematical transformations.

### What is the performance impact of real-time reprojection?

The reprojection pipeline is optimized for **column-wise 1:1 mapping**, making it efficient for equirectangular sources. The `remapRowsToMercator` function processes tiles row-by-row without complex warping, ensuring that WMS layers for planetary bodies load with minimal latency. However, more complex projections requiring per-pixel transformation will incur higher computational costs.

### Why do some overlays require Mercator projection locks?

Deck.gl and similar visualization libraries often expect data in Web-Mercator projection to align correctly with the base map. The `ensureMercatorProjection` utility in [`map-projection-utils.ts`](https://github.com/opengeos/GeoLibre/blob/main/map-projection-utils.ts) forces the MapLibre instance into Mercator mode while these overlays are active, preventing projection mismatches that would cause data misalignment when viewing planetary basemaps alongside standard Earth layers.