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

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. 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:

// 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:

// 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. 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
// 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 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 @L400
Manually change ellipsoid Store updates preference directly; singleton refreshes; basemap unchanged 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 @L2350
Calculate distance/area getActiveMeanRadiusMeters() fetches current ellipsoid for haversine formula 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

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

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

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 Ellipsoid definitions, singleton management, PlanetaryBasemap interface
packages/core/src/store.ts applyPlanetaryBasemap action, preference synchronization, undo/redo handling
packages/map/src/map-controller.ts UI subscription to ellipsoid changes, scale bar updates
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 maintains the source of truth for celestial body parameters and exposes singleton management functions
  • applyPlanetaryBasemap in 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 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. 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 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) 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.

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 →