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 labelsemiMajorAxisMeters: The Web-Mercator radius used for tilinginverseFlattening: 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:
- Inserts the raster basemap into the project's layer list
- Writes
ellipsoidIdto project preferences atstate.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
ellipsoidIdabstraction packages/core/src/ellipsoids.tsmaintains the source of truth for celestial body parameters and exposes singleton management functionsapplyPlanetaryBasemapinpackages/core/src/store.tsatomically 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →