_(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 ```ts 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 ```ts 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.