Files
skills/docs/geospatial/geo/geo-planet/src/planet-surface/planet-surface.md
2026-05-29 08:20:56 +01:00

4.7 KiB

(no submodules)

Purpose

The planet-surface module provides the PlanetSurface class, an oblate-ellipsoid wrapper around a PlanetConfig that exposes the geodetic operations every multi-planet system needs: great-circle distance, initial bearing, terminal-point projection from a heading and distance, and point-in-polygon tests. It is the single bridge between body-specific configuration (Earth, Mars, Titan, …) and the body-agnostic geometry primitives in @suaveplan/geo.

By deriving the ellipsoid once per PlanetSurface instance from the radius and polarRadius fields of PlanetConfig, callers never have to thread an ellipsoid through every distance call, and they cannot accidentally mix Earth-flattening into a Mars computation.

Features

  • PlanetSurface class — owns the planet config and the cached Ellipsoid
  • distance(a, b, unit?) — geodesic great-circle distance with selectable distance unit
  • bearing(from, to) — initial forward azimuth in degrees from north, clockwise
  • destination(start, bearingDeg, distanceM) — terminal-point projection by direct-geodesic solution
  • pointInPolygon(point, polygon) — spherical-coordinate ray-cast / winding test for arbitrary closed rings
  • Body-agnostic — works for any planet whose oblateness is well-modelled by an ellipsoid of revolution

Usage

Basic Example

import { PlanetSurface } from "@suaveplan/geo-planet";
import type { PlanetConfig } from "@suaveplan/geo-planet";

const earth: PlanetConfig = {
    name: "Earth",
    mass: 5.972e24,
    radius: 6_378_137,
    polarRadius: 6_356_752,
    surfaceGravity: 9.81,
    rotationPeriod: 86_164,
    axialTilt: 23.44,
    atmosphere: null,
    satellites: [],
};

const surface = new PlanetSurface(earth);
const km = surface.distance(
    { latitude: 51.5, longitude: -0.13 }, // London
    { latitude: 40.71, longitude: -74.0 }, // New York
    "kilometre",
);

Advanced Example

import { PlanetSurface } from "@suaveplan/geo-planet";
import type { PlanetConfig } from "@suaveplan/geo-planet";

const mars: PlanetConfig = {
    name: "Mars",
    mass: 6.39e23,
    radius: 3_396_200,
    polarRadius: 3_376_200,
    surfaceGravity: 3.72,
    rotationPeriod: 88_620,
    axialTilt: 25.2,
    atmosphere: null,
    satellites: [],
};

const surface = new PlanetSurface(mars);

// Project a 100 km traverse from Jezero crater on a 45° heading.
const start = { latitude: 18.44, longitude: 77.45 };
const end = surface.destination(start, 45, 100_000);

// Heading toward Olympus Mons.
const heading = surface.bearing(start, { latitude: 18.65, longitude: -133.8 });

// Region containment for science-target zones.
const aoi = [
    { latitude: 18.0, longitude: 77.0 },
    { latitude: 19.0, longitude: 77.0 },
    { latitude: 19.0, longitude: 78.0 },
    { latitude: 18.0, longitude: 78.0 },
];
const inside = surface.pointInPolygon(end, aoi);

API Reference

class PlanetSurface

constructor(planet: PlanetConfig)

Stores the planet on surface.planet and constructs an Ellipsoid via defineEllipsoid(planet.radius, planet.polarRadius) once per instance.

distance(a, b, unit?: DistanceUnit): number

Returns the great-circle distance between two SphericalCoord points along the cached ellipsoid. unit defaults to "metre"; pass "kilometre", "mile", etc. as supported by @suaveplan/geo.

bearing(from, to): number

Returns the initial forward azimuth in degrees clockwise from north. Note: the spherical bearing in @suaveplan/geo does not depend on the ellipsoid.

destination(start, bearingDeg, distanceM): SphericalCoord

Solves the direct geodesic problem on the cached ellipsoid: given a start point, an initial bearing, and a distance in metres, returns the terminal point.

pointInPolygon(point, polygon: SphericalCoord[]): boolean

Tests containment of point inside the closed polygon ring; uses spherical-coordinate point-in-polygon math from @suaveplan/geo.

Implementation Notes

The ellipsoid is built once at construction and then reused on every method call, which keeps every distance / destination call allocation-free in steady state. Bearing intentionally does not pass the ellipsoid because the underlying bearing from @suaveplan/geo is a pure spherical primitive — for the precision needed in mission-planning UIs, the difference between spherical and geodetic bearing is below display granularity, and the geometric simplicity is worth more than the marginal accuracy gain. Storing planet as a public readonly property lets consumers retain the original config (for atmosphere, satellites, textures, etc.) without round-tripping through a separate registry.