# How GeoLibre Handles Planetary Basemaps with Per-Project Ellipsoid Settings for Non-Earth Mapping

> Discover how GeoLibre manages planetary basemaps with per-project ellipsoid settings for non-Earth mapping. Learn about its ellipsoid registry and synchronized store for accurate celestial body measurements.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: deep-dive
- Published: 2026-08-15

---

**GeoLibre separates the concept of a "map body" (Earth, Moon, Mars, etc.) from visual basemap layers through an ellipsoid registry, planetary basemap interface, and store synchronization that keeps measurements accurate across celestial bodies.**

Whether you're building a lunar exploration dashboard or a Mars terrain analyzer, mapping libraries typically assume Earth-based coordinates. GeoLibre breaks this assumption with a sophisticated **per-project ellipsoid system** that enables accurate non-Earth mapping. This architecture, implemented in the `opengeos/GeoLibre` repository, allows planetary basemaps to automatically configure the correct celestial body parameters while maintaining Web Mercator compatibility for rendering.

## The Three-Component Architecture

GeoLibre's planetary support rests on three tightly coupled subsystems: an **ellipsoid registry**, a **planetary basemap definition**, and **store-to-map synchronization**. Each handles a distinct responsibility while sharing state through a lightweight singleton pattern.

### Ellipsoid Registry and Active Singleton

All supported celestial bodies live in [`packages/core/src/ellipsoids.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/ellipsoids.ts). Each ellipsoid entry specifies:

- `id`: Machine identifier (e.g., `"mars"`, `"moon"`)
- `name`: Human-readable label
- `semiMajorAxisMeters`: The Web-Mercator radius used for tiling
- `inverseFlattening`: Optional oblateness parameter

The Earth ellipsoid definition appears at lines 44–50:

```ts
// From packages/core/src/ellipsoids.ts
export const EARTH_ELLIPSOID: Ellipsoid = {
  id: "earth",
  name: "Earth (WGS 84)",
  semiMajorAxisMeters: 6378137,
  inverseFlattening: 298.257223563,
};

```

The module exports three critical functions (lines 45–68):

| Function | Purpose |
|----------|---------|
| `setActiveEllipsoidId(id)` | Switches the active body globally |
| `getActiveEllipsoid()` | Returns full ellipsoid metadata |
| `getActiveMeanRadiusMeters()` | Provides radius for haversine calculations |

These functions manage a **module-level singleton** that measurement code references throughout the codebase. When the active ellipsoid changes, all distance and area calculations automatically adopt the new body's parameters.

### Planetary Basemap Interface

Planetary basemaps are raster sources that visualize non-Earth bodies while **retaining Web Mercator tiling** for MapLibre compatibility. The `PlanetaryBasemap` interface (lines 80–100) extends a standard raster source with one critical addition:

```ts
// From packages/core/src/ellipsoids.ts
export interface PlanetaryBasemap extends RasterSource {
  styleUrl: string;
  tiles: string[];
  tileSize?: number;
  scheme?: "xyz" | "tms";
  ellipsoidId: string;  // ← Links basemap to celestial body
  attribution?: string;
}

```

The `ellipsoidId` property binds each basemap to its corresponding entry in the ellipsoid registry. A Mars elevation basemap would declare `ellipsoidId: "mars"`, ensuring GeoLibre knows which radius to apply for measurements.

### Store and Map-Controller Synchronization

The connection between visual and mathematical models happens in [`packages/core/src/store.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/store.ts). The `applyPlanetaryBasemap` action (around line 400) performs two atomic operations:

1. **Inserts the raster basemap** into the project's layer list
2. **Writes `ellipsoidId` to project preferences** at `state.preferences.map.ellipsoidId`

```ts
// Conceptual flow from packages/core/src/store.ts
applyPlanetaryBasemap(basemap: PlanetaryBasemap) {
  // 1. Update visual layers
  this.setBasemap(basemap);
  
  // 2. Sync ellipsoid preference
  this.setPreferences({
    map: {
      ...this.preferences.map,
      ellipsoidId: basemap.ellipsoidId  // ← Critical link
    }
  });
}

```

The map controller in [`packages/map/src/map-controller.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/map-controller.ts) subscribes to these preference changes (see subscription around line 1010) and updates **scale bars, measurement readouts, and coordinate displays** accordingly.

## Behavioral Scenarios

Understanding how these components interact requires examining specific user flows:

| Scenario | System Response | Implementation Location |
|----------|---------------|------------------------|
| **Select planetary basemap** | `applyPlanetaryBasemap` sets `ellipsoidId` from basemap; singleton updates automatically | [`packages/core/src/store.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/store.ts) @L400 |
| **Manually change ellipsoid** | Store updates preference directly; singleton refreshes; basemap unchanged | [`packages/core/src/ellipsoids.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/ellipsoids.ts) @L52–55 |
| **Undo/redo basemap change** | Ellipsoid re-derives from restored basemap; auto-switches if body differs | [`packages/core/src/store.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/store.ts) @L2350 |
| **Calculate distance/area** | `getActiveMeanRadiusMeters()` fetches current ellipsoid for haversine formula | [`packages/core/src/ellipsoids.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/ellipsoids.ts) @L60–68 |

This design prioritizes **automatic correctness** while preserving **manual override capability**. A Mars basemap triggers Mars measurements by default, but users can force Earth calculations if needed for comparative analysis.

## Practical Implementation Examples

### Switch to a Planetary Basemap

```ts
import { useAppStore, getPlanetaryBasemapById } from "@geolibre/core";

const marsBasemap = getPlanetaryBasemapById("mars-colour-mola-elevation")!;
useAppStore.getState().applyPlanetaryBasemap(marsBasemap);

// Result: state.preferences.map.ellipsoidId === "mars"
// All measurements now use 3,396,190m radius

```

The `applyPlanetaryBasemap` call handles both visual and mathematical context automatically.

### Override Ellipsoid Independently

```ts
import { setActiveEllipsoidId } from "@geolibre/core";

// Keep Earth basemap, but calculate as if Moon
setActiveEllipsoidId("moon");

useAppStore.getState().setPreferences({
  map: {
    ...useAppStore.getState().preferences.map,
    ellipsoidId: "moon"
  }
});

// Measurements use lunar radius; basemap remains unchanged

```

This pattern supports **comparative analysis workflows** where users need consistent calculations across varied visual contexts.

### Display Current Body Information

```ts
import { getActiveEllipsoid } from "@geolibre/core";

const ellipsoid = getActiveEllipsoid();

console.log(
  `Current body: ${ellipsoid.name} ` +
  `(radius ${ellipsoid.semiMajorAxisMeters.toLocaleString()} m)`
);
// Output: "Current body: Mars (radius 3,396,190 m)"

```

Use this for **status indicators** in planetary exploration interfaces.

## Source File Reference

| File | Responsibility |
|------|--------------|
| [`packages/core/src/ellipsoids.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/ellipsoids.ts) | Ellipsoid definitions, singleton management, `PlanetaryBasemap` interface |
| [`packages/core/src/store.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/store.ts) | `applyPlanetaryBasemap` action, preference synchronization, undo/redo handling |
| [`packages/map/src/map-controller.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/map-controller.ts) | UI subscription to ellipsoid changes, scale bar updates |
| [`tests/planetary-basemap.test.ts`](https://github.com/opengeos/GeoLibre/blob/main/tests/planetary-basemap.test.ts) | Verification of basemap-to-ellipsoid synchronization and undo integrity |

## Summary

- **GeoLibre's planetary basemap system** decouples visual rendering from mathematical modeling through the `ellipsoidId` abstraction
- **[`packages/core/src/ellipsoids.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/ellipsoids.ts)** maintains the source of truth for celestial body parameters and exposes singleton management functions
- **`applyPlanetaryBasemap`** in [`packages/core/src/store.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/store.ts) atomically links basemap selection to ellipsoid configuration
- **Per-project ellipsoid settings** persist in `state.preferences.map.ellipsoidId`, enabling undo/redo-aware body switching
- **Measurements automatically adapt** via `getActiveMeanRadiusMeters()`, ensuring distance and area accuracy for any supported body

## Frequently Asked Questions

### What celestial bodies does GeoLibre support out of the box?

The [`packages/core/src/ellipsoids.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/ellipsoids.ts) registry includes Earth, Moon, and Mars definitions by default. Each entry provides the semi-major axis and flattening parameters needed for accurate calculations. Users can extend support by adding custom ellipsoid definitions to this registry and referencing them via `ellipsoidId` in planetary basemap configurations.

### Why do planetary basemaps still use Web Mercator tiling?

Web Mercator provides **hardware-accelerated rendering compatibility** with MapLibre and other standard libraries. GeoLibre preserves this tiling scheme while substituting the appropriate body radius for measurements. This approach avoids custom projection engineering while delivering mathematically correct planetary distances.

### Can I use different ellipsoids for different map views in the same project?

No—the ellipsoid singleton is **global per project** as implemented in [`packages/core/src/ellipsoids.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/ellipsoids.ts). For multi-body comparison, create separate GeoLibre project instances or manually switch ellipsoids via `setActiveEllipsoidId()` when changing views. The test suite in [`tests/planetary-basemap.test.ts`](https://github.com/opengeos/GeoLibre/blob/main/tests/planetary-basemap.test.ts) verifies that these switches propagate correctly through the measurement pipeline.

### How does the undo system handle ellipsoid changes?

The store's undo/redo implementation (around line 2350 in [`packages/core/src/store.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/store.ts)) re-derives the ellipsoid from the restored basemap's `ellipsoidId`. If the restored basemap belongs to a different celestial body than the current state, the ellipsoid updates automatically. If the basemap remains on the same body, any manual ellipsoid override persists across undo operations.