Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
# @suaveplan/geo - Abstract spherical and ellipsoidal geometry — pure math with no planet-specific constants.
## Summary
Abstract spherical and ellipsoidal geometry — pure math with no planet-specific constants. Every operation that needs a body's shape takes an explicit `Ellipsoid` parameter.
<!-- begin:auto-docs -->
## Documentation tree
- [angular](./src/angular/angular.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [angular](./src/angular/angular.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [bearing](./src/bearing/bearing.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [bearing](./src/bearing/bearing.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [bounds](./src/bounds/bounds.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [bounds](./src/bounds/bounds.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [distance](./src/distance/distance.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [distance](./src/distance/distance.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [ellipsoid](./src/ellipsoid/ellipsoid.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [ellipsoid](./src/ellipsoid/ellipsoid.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [observability](./src/observability/observability.md) — OpenTelemetry tracer wrapper for `@suaveplan/geo`.
- [observability](./src/observability/observability.md) — OpenTelemetry tracer wrapper for `@suaveplan/geo`.
- [polygon](./src/polygon/polygon.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [polygon](./src/polygon/polygon.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [transforms](./src/transforms/transforms.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [transforms](./src/transforms/transforms.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [schemas](./src/types/schemas.md) — The `schemas` module exports Zod schemas that validate every public geometric type defined in `@suaveplan/geo`. The sch…
- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [units](./src/units/units.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [units](./src/units/units.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
<!-- end:auto-docs --> - [angular](./src/angular/angular.md) — <!-- begin:auto-docs --> _(no submodules)_- [bearing](./src/bearing/bearing.md) — <!-- begin:auto-docs --> _(no submodules)_- [bearing](./src/bearing/bearing.md) — <!-- begin:auto-docs --> _(no submodules)_- [bounds](./src/bounds/bounds.md) — <!-- begin:auto-docs --> _(no submodules)_- [bounds](./src/bounds/bounds.md) — <!-- begin:auto-docs --> _(no submodules)_- [distance](./src/distance/distance.md) — <!-- begin:auto-docs --> _(no submodules)_- [distance](./src/distance/distance.md) — <!-- begin:auto-docs --> _(no submodules)_- [ellipsoid](./src/ellipsoid/ellipsoid.md) — <!-- begin:auto-docs --> _(no submodules)_- [ellipsoid](./src/ellipsoid/ellipsoid.md) — <!-- begin:auto-docs --> _(no submodules)_- [polygon](./src/polygon/polygon.md) — <!-- begin:auto-docs --> _(no submodules)_- [polygon](./src/polygon/polygon.md) — <!-- begin:auto-docs --> _(no submodules)_- [transforms](./src/transforms/transforms.md) — <!-- begin:auto-docs --> _(no submodules)_- [transforms](./src/transforms/transforms.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/types/schemas.md) — The `schemas` module exports Zod schemas that validate every public geometric type defined in `@suaveplan/geo`. The sch…
- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [units](./src/units/units.md) — <!-- begin:auto-docs --> _(no submodules)_- [units](./src/units/units.md) — <!-- begin:auto-docs --> _(no submodules)_- [observability](./src/observability.md) — OpenTelemetry tracer wrapper for `@suaveplan/geo`.
- [angular](./src/angular/angular.md) — <!-- begin:auto-docs --> _(no submodules)_- [bearing](./src/bearing/bearing.md) — <!-- begin:auto-docs --> _(no submodules)_- [bearing](./src/bearing/bearing.md) — <!-- begin:auto-docs --> _(no submodules)_- [bounds](./src/bounds/bounds.md) — <!-- begin:auto-docs --> _(no submodules)_- [bounds](./src/bounds/bounds.md) — <!-- begin:auto-docs --> _(no submodules)_- [distance](./src/distance/distance.md) — <!-- begin:auto-docs --> _(no submodules)_- [distance](./src/distance/distance.md) — <!-- begin:auto-docs --> _(no submodules)_- [ellipsoid](./src/ellipsoid/ellipsoid.md) — <!-- begin:auto-docs --> _(no submodules)_- [ellipsoid](./src/ellipsoid/ellipsoid.md) — <!-- begin:auto-docs --> _(no submodules)_- [polygon](./src/polygon/polygon.md) — <!-- begin:auto-docs --> _(no submodules)_- [polygon](./src/polygon/polygon.md) — <!-- begin:auto-docs --> _(no submodules)_- [transforms](./src/transforms/transforms.md) — <!-- begin:auto-docs --> _(no submodules)_- [transforms](./src/transforms/transforms.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/types/schemas.md) — The `schemas` module exports Zod schemas that validate every public geometric type defined in `@suaveplan/geo`. The sch…
- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [units](./src/units/units.md) — <!-- begin:auto-docs --> _(no submodules)_- [units](./src/units/units.md) — <!-- begin:auto-docs --> _(no submodules)_- [observability](./src/observability.md) — OpenTelemetry tracer wrapper for `@suaveplan/geo`.
- [angular](./src/angular/angular.md) — <!-- begin:auto-docs --> _(no submodules)_- [bearing](./src/bearing/bearing.md) — <!-- begin:auto-docs --> _(no submodules)_- [bearing](./src/bearing/bearing.md) — <!-- begin:auto-docs --> _(no submodules)_- [bounds](./src/bounds/bounds.md) — <!-- begin:auto-docs --> _(no submodules)_- [bounds](./src/bounds/bounds.md) — <!-- begin:auto-docs --> _(no submodules)_- [distance](./src/distance/distance.md) — <!-- begin:auto-docs --> _(no submodules)_- [distance](./src/distance/distance.md) — <!-- begin:auto-docs --> _(no submodules)_- [ellipsoid](./src/ellipsoid/ellipsoid.md) — <!-- begin:auto-docs --> _(no submodules)_- [ellipsoid](./src/ellipsoid/ellipsoid.md) — <!-- begin:auto-docs --> _(no submodules)_- [polygon](./src/polygon/polygon.md) — <!-- begin:auto-docs --> _(no submodules)_- [polygon](./src/polygon/polygon.md) — <!-- begin:auto-docs --> _(no submodules)_- [transforms](./src/transforms/transforms.md) — <!-- begin:auto-docs --> _(no submodules)_- [transforms](./src/transforms/transforms.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/types/schemas.md) — The `schemas` module exports Zod schemas that validate every public geometric type defined in `@suaveplan/geo`. The sch…
- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [units](./src/units/units.md) — <!-- begin:auto-docs --> _(no submodules)_- [units](./src/units/units.md) — <!-- begin:auto-docs --> _(no submodules)_- [observability](./src/observability.md) — OpenTelemetry tracer wrapper for `@suaveplan/geo`.
- [angular](./src/angular/angular.md) — <!-- begin:auto-docs --> _(no submodules)_- [bearing](./src/bearing/bearing.md) — <!-- begin:auto-docs --> _(no submodules)_ - [bearing](./src/bearing/bearing.md) — <!-- begin:auto-docs --> _(no submodules)_- [bounds](./src/bounds/bounds.md) — <!-- begin:auto-docs --> _(no submodules)_ - [bounds](./src/bounds/bounds.md) — <!-- begin:auto-docs --> _(no submodules)_- [distance](./src/distance/distance.md) — <!-- begin:auto-docs --> _(no submodules)_ - [distance](./src/distance/distance.md) — <!-- begin:auto-docs --> _(no submodules)_- [ellipsoid](./src/ellipsoid/ellipsoid.md) — <!-- begin:auto-docs --> _(no submodules)_ - [ellipsoid](./src/ellipsoid/ellipsoid.md) — <!-- begin:auto-docs --> _(no submodules)_- [polygon](./src/polygon/polygon.md) — <!-- begin:auto-docs --> _(no submodules)_ - [polygon](./src/polygon/polygon.md) — <!-- begin:auto-docs --> _(no submodules)_- [transforms](./src/transforms/transforms.md) — <!-- begin:auto-docs --> _(no submodules)_ - [transforms](./src/transforms/transforms.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_ - [schemas](./src/types/schemas.md) — The `schemas` module exports Zod schemas that validate every public geometric type defined in `@suaveplan/geo`. The sch…
- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [units](./src/units/units.md) — <!-- begin:auto-docs --> _(no submodules)_ - [units](./src/units/units.md) — <!-- begin:auto-docs --> _(no submodules)_- [observability](./src/observability.md) — OpenTelemetry tracer wrapper for `@suaveplan/geo`.
## Install
```bash
bun add @suaveplan/geo
```
## Quick start
```ts
import { defineEllipsoid, greatCircleDistance, bearing, destination } from "@suaveplan/geo";
const wgs84 = defineEllipsoid(6_378_137, 6_356_752.314245);
const london = { latitude: 51.5074, longitude: -0.1278 };
const paris = { latitude: 48.8566, longitude: 2.3522 };
console.log(greatCircleDistance(london, paris, wgs84, "kilometre")); // ~344
console.log(bearing(london, paris)); // ~154°
```
## Modules
- [`src/ellipsoid/ellipsoid.md`](src/ellipsoid/ellipsoid.md) — `Ellipsoid`, `defineEllipsoid`
- [`src/transforms/transforms.md`](src/transforms/transforms.md) — `sphericalToCartesian`, `cartesianToSpherical`, `toRadians`, `toDegrees`
- [`src/distance/distance.md`](src/distance/distance.md) — `greatCircleDistance`, `vincentyDistance`
- [`src/bearing/bearing.md`](src/bearing/bearing.md) — `bearing`, `destination`, `midpoint`
- [`src/angular/angular.md`](src/angular/angular.md) — `angularSeparation`
- [`src/bounds/bounds.md`](src/bounds/bounds.md) — `BoundingBox`, `createBoundingBox`, `containsPoint`, `expandBox`, `mergeBoxes`, `intersects`
- [`src/polygon/polygon.md`](src/polygon/polygon.md) — `pointInPolygon`, `polygonArea`, `polygonPerimeter`, `simplifyPolygon`
- [`src/units/units.md`](src/units/units.md) — `DistanceUnit`, `convertDistance`
- [`src/types/types.md`](src/types/types.md) — Zod schemas for all public types

View File

@@ -0,0 +1,79 @@
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The `angular` module computes the angular separation between two points
expressed as `SphericalCoord` values, returning the central angle subtended
on a unit sphere. The result is a pure spherical angle in radians,
independent of any planetary radius or ellipsoid, which makes it the right
primitive for astronomy (angular distance between celestial objects),
antenna and dish pointing, instrument field-of-view checks, and any
calculation where you want to multiply by a chosen radius later rather than
bake one in upfront. Building on this primitive keeps the higher-level
distance kernels (`greatCircleDistance`, `vincentyDistance`) free to apply
their own ellipsoid-aware radii.
## Features
- `angularSeparation(a, b)` — central angle in radians using the spherical
law of cosines.
- Inputs are degrees (matching the rest of `@suaveplan/geo`); the kernel
converts to radians internally.
- Result is clamped to `[0, π]` by guarding the `acos` argument against
floating-point drift past `±1`.
## Usage
### Basic Example
```ts
import { angularSeparation } from "@suaveplan/geo";
const angle = angularSeparation(
{ latitude: 0, longitude: 0 },
{ latitude: 0, longitude: 90 }
);
console.log(angle); // ~1.5708 radians (90°)
```
### Advanced Example
Combine `angularSeparation` with `toDegrees` and a chosen radius to compute
arc-length on bodies other than Earth — for example, on the Moon's mean
radius — without instantiating an ellipsoid:
```ts
import { angularSeparation, toDegrees } from "@suaveplan/geo";
const MOON_RADIUS_KM = 1737.4;
const sep = angularSeparation(
{ latitude: 0, longitude: 23.47 }, // Apollo 11 site
{ latitude: -3.01, longitude: -23.42 } // Apollo 12 site
);
const arcKm = sep * MOON_RADIUS_KM;
console.log(`${toDegrees(sep).toFixed(2)}° / ${arcKm.toFixed(1)} km`);
```
## API Reference
### `angularSeparation(a: SphericalCoord, b: SphericalCoord): number`
Returns the spherical angle in radians between `a` and `b`. The argument to
`Math.acos` is clamped to `[-1, 1]` so identical inputs deterministically
return `0` and antipodal inputs return `π` even when intermediate
floating-point arithmetic produces values like `1.0000000000000002`.
## Implementation Notes
The implementation uses the spherical law of cosines:
`acos(sin φ₁ sin φ₂ + cos φ₁ cos φ₂ cos Δλ)`. For separations below ~1 km
on Earth the haversine formula is more numerically stable, but for the
astronomy and pointing use cases this primitive targets — separations of
many degrees — the clamped cosine formulation is sufficient and faster.
Consumers that need sub-metre Earth-surface precision should reach for
`greatCircleDistance` (haversine) or `vincentyDistance` (ellipsoidal)
instead.

View File

@@ -0,0 +1,55 @@
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The `bearing` module computes initial bearings, destination points, and midpoints on an ellipsoid. These are the fundamental navigation primitives for routing, waypoint generation, and boundary interpolation.
## Features
- `bearing(from, to)` — initial compass bearing in degrees (0–360)
- `destination(start, bearingDeg, distanceM, ellipsoid)` — computes the point reached after travelling a given distance on a given bearing
- `midpoint(a, b)` — geographic midpoint between two coordinates
## Usage
### Basic Example
```ts
import { bearing } from "@suaveplan/geo";
const brg = bearing(
{ latitude: 51.5, longitude: -0.1 },
{ latitude: 48.9, longitude: 2.3 }
);
```
### Advanced Example
```ts
import { defineEllipsoid, destination, bearing } from "@suaveplan/geo";
const wgs84 = defineEllipsoid(6_378_137, 6_356_752.314245);
const start = { latitude: 51.5, longitude: -0.1 };
const end = destination(start, 90, 100_000, wgs84);
const back = bearing(end, start); // ~270
```
## API Reference
### `bearing(from, to): number`
Returns initial bearing in degrees clockwise from north.
### `destination(start, bearingDeg, distanceM, ellipsoid): SphericalCoord`
Returns the coordinate reached after travelling `distanceM` metres on `bearingDeg`.
### `midpoint(a, b): SphericalCoord`
Returns the geographic midpoint.
## Implementation Notes
Destination uses the spherical law of cosines with the ellipsoid's semi-major axis as radius. For high-precision geodesic destinations, consumers should iterate `bearing` + `vincentyDistance`.

View File

@@ -0,0 +1,66 @@
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The `bounds` module provides axis-aligned bounding boxes for spherical coordinates. Boxes support containment tests, expansion, merging, and intersection — the primitives needed for spatial indexing, map viewport clipping, and collision detection.
## Features
- `createBoundingBox` — construct from an array of coordinates
- `containsPoint` — point-in-box test
- `expandBox` — grow a box to include a new point
- `mergeBoxes` — combine two boxes
- `intersects` — box-box overlap test
## Usage
### Basic Example
```ts
import { createBoundingBox, containsPoint } from "@suaveplan/geo";
const box = createBoundingBox([
{ latitude: 0, longitude: 0 },
{ latitude: 10, longitude: 10 },
]);
console.log(containsPoint(box, { latitude: 5, longitude: 5 })); // true
```
### Advanced Example
```ts
import { createBoundingBox, mergeBoxes, intersects } from "@suaveplan/geo";
const a = createBoundingBox([{ latitude: 0, longitude: 0 }, { latitude: 10, longitude: 10 }]);
const b = createBoundingBox([{ latitude: 5, longitude: 5 }, { latitude: 15, longitude: 15 }]);
console.log(intersects(a, b)); // true
const merged = mergeBoxes(a, b);
```
## API Reference
### `createBoundingBox(coords): BoundingBox | null`
Returns `null` for an empty array.
### `containsPoint(box, point): boolean`
Inclusive on all boundaries.
### `expandBox(box, point): BoundingBox`
Returns a new box; does not mutate the input.
### `mergeBoxes(a, b): BoundingBox`
Returns the smallest box containing both inputs.
### `intersects(a, b): boolean`
Returns `true` if the boxes share any area.
## Implementation Notes
Bounding boxes use simple min/max on latitude and longitude. They do not handle the antimeridian (`±180°` longitude wrap). Consumers crossing the antimeridian should split geometries or use a custom coordinate system.

View File

@@ -0,0 +1,100 @@
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The `distance` module computes geodesic distances between two
`SphericalCoord` values on a configurable `Ellipsoid`. Two algorithms are
exported so callers can pick the speed/accuracy tradeoff that fits the use
case: a fast haversine great-circle approximation that treats the
ellipsoid's semi-major axis as a sphere radius, and the iterative Vincenty
inverse formula which solves on the actual ellipsoid and is accurate to
millimetres for any pair of points that aren't nearly antipodal. Both
functions return their result in a caller-chosen `DistanceUnit`, delegating
the conversion to `convertDistance` so the caller never has to remember
the metre→mile or metre→nautical-mile factor.
## Features
- `greatCircleDistance(a, b, ellipsoid, unit?)` — O(1) haversine; ideal
when the ellipsoid is approximately spherical or when ~0.5% accuracy is
acceptable.
- `vincentyDistance(a, b, ellipsoid, unit?)` — iterative inverse Vincenty
with up to 100 iterations and a `1e-12` convergence tolerance; converges
to millimetre precision on WGS-84.
- Both functions accept any `DistanceUnit` (`"metre"`, `"kilometre"`,
`"mile"`, `"nauticalMile"`) for the return value, defaulting to `"metre"`.
- Both functions handle identical inputs deterministically: distance `0`,
no NaN.
## Usage
### Basic Example
```ts
import { defineEllipsoid, greatCircleDistance } from "@suaveplan/geo";
const wgs84 = defineEllipsoid(6_378_137, 6_356_752.314245);
const km = greatCircleDistance(
{ latitude: 51.5, longitude: -0.1 }, // London
{ latitude: 48.9, longitude: 2.3 }, // Paris
wgs84,
"kilometre"
);
console.log(km.toFixed(1)); // ~344.0
```
### Advanced Example
Compare the two algorithms to characterise the haversine error against
Vincenty for a particular use case:
```ts
import {
defineEllipsoid,
greatCircleDistance,
vincentyDistance,
} from "@suaveplan/geo";
const wgs84 = defineEllipsoid(6_378_137, 6_356_752.314245);
const a = { latitude: 35.6762, longitude: 139.6503 }; // Tokyo
const b = { latitude: -33.8688, longitude: 151.2093 }; // Sydney
const haversineMetres = greatCircleDistance(a, b, wgs84, "metre");
const vincentyMetres = vincentyDistance(a, b, wgs84, "metre");
const errorPpm =
((haversineMetres - vincentyMetres) / vincentyMetres) * 1_000_000;
console.log(`haversine: ${haversineMetres.toFixed(0)} m`);
console.log(`vincenty: ${vincentyMetres.toFixed(0)} m`);
console.log(`error: ${errorPpm.toFixed(1)} ppm`);
```
## API Reference
### `greatCircleDistance(a, b, ellipsoid, unit?): number`
Haversine great-circle distance using `ellipsoid.semiMajorAxis` as the
sphere radius. `unit` defaults to `"metre"`. Treats both points as
spherical, so the answer is a great-circle arc, not an ellipsoidal
geodesic.
### `vincentyDistance(a, b, ellipsoid, unit?): number`
Vincenty's inverse formula. Iterates up to 100 times with a `1e-12`
convergence tolerance on `λ`. `unit` defaults to `"metre"`. Returns `0`
immediately for coincident points (when `sinSigma === 0`).
## Implementation Notes
Vincenty does not converge for nearly antipodal points; the 100-iteration
ceiling will simply return the last computed value rather than throw. In
production code that may receive arbitrary input, validate the input
geometry first or fall back to `greatCircleDistance` when convergence is
suspect. For all realistic Earth-surface use cases — same-continent or
even trans-oceanic — the algorithm converges in well under a dozen
iterations. The kernels read only `semiMajorAxis`, `semiMinorAxis`, and
`flattening` from the ellipsoid, so any ellipsoid produced by
`defineEllipsoid` is compatible.

View File

@@ -0,0 +1,51 @@
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The `ellipsoid` module defines the `Ellipsoid` type and the `defineEllipsoid` factory used throughout the `@suaveplan/geo` family. An ellipsoid is the geometric shape that approximates planets and moons, characterized by a semi-major axis (equatorial radius) and a semi-minor axis (polar radius). This module derives flattening and eccentricity automatically so consumers never hand-roll the same arithmetic.
## Features
- Immutable `Ellipsoid` interface with derived properties
- `defineEllipsoid(a, b)` factory that computes flattening and eccentricity
- Pure functions — no side effects, no planet-specific constants
## Usage
### Basic Example
```ts
import { defineEllipsoid } from "@suaveplan/geo/ellipsoid/ellipsoid";
const wgs84 = defineEllipsoid(6_378_137, 6_356_752.314245);
console.log(wgs84.flattening); // ~0.00335
```
### Advanced Example
```ts
import { defineEllipsoid } from "@suaveplan/geo/ellipsoid/ellipsoid";
const mars = defineEllipsoid(3_396_200, 3_376_200);
const sphere = defineEllipsoid(6_371_000, 6_371_000);
console.log(sphere.flattening); // 0
```
## API Reference
### `interface Ellipsoid`
- `semiMajorAxis: number` — equatorial radius in metres
- `semiMinorAxis: number` — polar radius in metres
- `flattening: number` — derived `(a − b) / a`
- `eccentricity: number` — derived from flattening
### `defineEllipsoid(semiMajorAxis, semiMinorAxis): Ellipsoid`
Creates an `Ellipsoid` from the given axes and derives flattening and eccentricity.
## Implementation Notes
Eccentricity is computed as `sqrt(2f − f²)` where `f` is flattening. For a perfect sphere (`a === b`), both flattening and eccentricity are exactly zero.

View File

@@ -0,0 +1,17 @@
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
OpenTelemetry tracer wrapper for `@suaveplan/geo`.
## Usage
```ts
import { withSpan } from "@suaveplan/geo";
const result = withSpan("compute", () => compute());
```

View File

@@ -0,0 +1,63 @@
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The `polygon` module provides point-in-polygon, area, perimeter, and simplification for spherical polygons. These are essential for geofencing, territory mapping, and route buffer calculations.
## Features
- `pointInPolygon` — ray-casting algorithm for spherical coordinates
- `polygonArea` — spherical excess area on a given ellipsoid
- `polygonPerimeter` — sum of geodesic edge lengths
- `simplifyPolygon` — Douglas-Peucker with great-circle perpendicular distance
## Usage
### Basic Example
```ts
import { pointInPolygon } from "@suaveplan/geo";
const square = [
{ latitude: 0, longitude: 0 },
{ latitude: 0, longitude: 10 },
{ latitude: 10, longitude: 10 },
{ latitude: 10, longitude: 0 },
];
console.log(pointInPolygon({ latitude: 5, longitude: 5 }, square)); // true
```
### Advanced Example
```ts
import { defineEllipsoid, polygonArea, simplifyPolygon } from "@suaveplan/geo";
const wgs84 = defineEllipsoid(6_378_137, 6_356_752.314245);
const complex = [/* many points */];
const simple = simplifyPolygon(complex, 100, wgs84);
const area = polygonArea(simple, wgs84);
```
## API Reference
### `pointInPolygon(point, polygon): boolean`
Ray-casting (even-odd rule). Does not handle polygons that cross the antimeridian.
### `polygonArea(polygon, ellipsoid): number`
Returns area in square metres. Returns `0` for fewer than 3 vertices.
### `polygonPerimeter(polygon, ellipsoid): number`
Returns perimeter in metres using `greatCircleDistance` for each edge.
### `simplifyPolygon(polygon, toleranceMetres, ellipsoid): SphericalCoord[]`
Douglas-Peucker simplification. Returns the original polygon if it has 2 or fewer points.
## Implementation Notes
`polygonArea` uses the spherical excess formula with the ellipsoid's semi-major axis as radius. `simplifyPolygon` computes perpendicular distance using great-circle distances on the supplied ellipsoid rather than planar Euclidean distance, preserving accuracy for large polygons.

View File

@@ -0,0 +1,59 @@
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The `transforms` module converts between spherical coordinates (latitude, longitude, altitude) and 3-D Cartesian vectors on a given ellipsoid. It also provides radian/degree helpers used by every other module in `@suaveplan/geo`.
## Features
- `sphericalToCartesian` — converts lat/lon/alt to ECEF (Earth-Centered Earth-Fixed) Cartesian
- `cartesianToSpherical` — inverse conversion using Bowring's iterative method
- `toRadians` / `toDegrees` — conversion helpers
- `SphericalCoord`, `Vec3`, `Mat3x3` types
## Usage
### Basic Example
```ts
import { defineEllipsoid, sphericalToCartesian } from "@suaveplan/geo";
const wgs84 = defineEllipsoid(6_378_137, 6_356_752.314245);
const vec = sphericalToCartesian({ latitude: 51.5, longitude: -0.1, altitude: 0 }, wgs84);
```
### Advanced Example
```ts
import { defineEllipsoid, sphericalToCartesian, cartesianToSpherical } from "@suaveplan/geo";
const wgs84 = defineEllipsoid(6_378_137, 6_356_752.314245);
const coord = { latitude: 51.5, longitude: -0.1, altitude: 100 };
const vec = sphericalToCartesian(coord, wgs84);
const back = cartesianToSpherical(vec, wgs84);
console.log(back.latitude); // ~51.5
```
## API Reference
### `sphericalToCartesian(coord, ellipsoid): Vec3`
Converts a `SphericalCoord` to a Cartesian `Vec3` on the given `Ellipsoid`.
### `cartesianToSpherical(vec, ellipsoid): SphericalCoord`
Converts a `Vec3` back to a `SphericalCoord` on the given `Ellipsoid`.
### `toRadians(degrees): number`
Converts degrees to radians.
### `toDegrees(radians): number`
Converts radians to degrees.
## Implementation Notes
Cartesian-to-spherical uses Bowring's iterative formula which converges rapidly for all latitudes including the poles. The prime vertical radius of curvature `N` is computed from the eccentricity squared.

View File

@@ -0,0 +1,102 @@
## Purpose
The `schemas` module exports Zod schemas that validate every public geometric
type defined in `@suaveplan/geo`. The schemas are used at API boundaries —
HTTP handlers, message-bus envelopes, persistence reads — wherever runtime
input must be coerced and rejected before it reaches the geometry kernels.
Pairing the schemas with the typed interfaces in `types.ts` lets consumers
infer the same TypeScript shape via `z.infer` without re-declaring a parallel
type, eliminating drift between compile-time and runtime contracts.
## Features
- `EllipsoidSchema` — validates `semiMajorAxis`/`semiMinorAxis` are positive
numbers and `flattening`/`eccentricity` are finite numbers.
- `SphericalCoordSchema` — clamps `latitude` to `[-90, 90]`, `longitude` to
`[-180, 180]`, and accepts an optional `altitude` number.
- `Vec3Schema` — three finite-number Cartesian components.
- `Mat3x3Schema` — nine finite-number row-major rotation-matrix entries.
- `BoundingBoxSchema` — both latitude bounds in `[-90, 90]` and both
longitude bounds in `[-180, 180]`.
- `DistanceUnitSchema` — enum over `"metre" | "kilometre" | "mile" |
"nauticalMile"`.
## Usage
### Basic Example
```ts
import { SphericalCoordSchema } from "@suaveplan/geo";
const coord = SphericalCoordSchema.parse({
latitude: 51.5,
longitude: -0.1,
});
```
### Advanced Example
```ts
import {
BoundingBoxSchema,
DistanceUnitSchema,
EllipsoidSchema,
} from "@suaveplan/geo";
import type { z } from "zod";
type ParsedEllipsoid = z.infer<typeof EllipsoidSchema>;
function loadConfig(raw: unknown): {
ellipsoid: ParsedEllipsoid;
box: z.infer<typeof BoundingBoxSchema>;
unit: z.infer<typeof DistanceUnitSchema>;
} {
const obj = raw as Record<string, unknown>;
return {
ellipsoid: EllipsoidSchema.parse(obj.ellipsoid),
box: BoundingBoxSchema.parse(obj.box),
unit: DistanceUnitSchema.parse(obj.unit),
};
}
```
## API Reference
### `EllipsoidSchema`
`z.object({ semiMajorAxis: z.number().positive(), semiMinorAxis:
z.number().positive(), flattening: z.number(), eccentricity: z.number() })`.
Throws `ZodError` on negative or non-finite axes.
### `SphericalCoordSchema`
`z.object({ latitude: z.number().min(-90).max(90), longitude:
z.number().min(-180).max(180), altitude: z.number().optional() })`.
### `Vec3Schema`
`z.object({ x, y, z })` — three `z.number()` fields.
### `Mat3x3Schema`
`z.object({ m00..m22 })` — nine `z.number()` fields in row-major order.
### `BoundingBoxSchema`
`z.object({ minLatitude, maxLatitude, minLongitude, maxLongitude })` with
each value clamped to spherical ranges. Note that the schema does NOT verify
`minLatitude <= maxLatitude`; that ordering check is the caller's job.
### `DistanceUnitSchema`
`z.enum(["metre", "kilometre", "mile", "nauticalMile"])`.
## Implementation Notes
The schemas are deliberately permissive on cross-field invariants — for
example, `BoundingBoxSchema` does not verify that `min` ≤ `max`, and
`EllipsoidSchema` does not verify that `semiMinorAxis ≤ semiMajorAxis`.
Cross-field invariants belong with the constructors (`createBoundingBox`,
`defineEllipsoid`), which are tested for those invariants separately. The
schemas exist to reject malformed wire data, not to substitute for the
domain constructors.

View File

@@ -0,0 +1,114 @@
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The `types` module declares the shared TypeScript interfaces and enums that
every other module in `@suaveplan/geo` consumes — `Ellipsoid`,
`SphericalCoord`, `Vec3`, `Mat3x3`, `BoundingBox`, and the `DistanceUnit`
string-literal union. Centralising the shapes here keeps the rest of the
package free of duplicate declarations and ensures that any change to a
core geometric structure has exactly one source of truth. The module is
runtime-free: it emits zero JavaScript and is safe to import from any
tier (universal, browser, or server).
## Features
- `Ellipsoid` — readonly `semiMajorAxis`/`semiMinorAxis`/`flattening`/
`eccentricity`; produced by `defineEllipsoid`.
- `SphericalCoord` — `latitude`, `longitude`, optional `altitude` (all
degrees / metres).
- `Vec3` — `x`, `y`, `z` Cartesian components.
- `Mat3x3` — nine row-major rotation-matrix entries (`m00`..`m22`).
- `BoundingBox` — `minLatitude`, `maxLatitude`, `minLongitude`,
`maxLongitude`.
- `DistanceUnit` — `"metre" | "kilometre" | "mile" | "nauticalMile"`.
## Usage
### Basic Example
```ts
import type { Ellipsoid, SphericalCoord } from "@suaveplan/geo";
function describe(point: SphericalCoord, ellipsoid: Ellipsoid): string {
return `(${point.latitude}, ${point.longitude}) on ${ellipsoid.semiMajorAxis}m sphere`;
}
```
### Advanced Example
Compose the types into a domain-specific record without re-declaring any
field shapes:
```ts
import type {
BoundingBox,
DistanceUnit,
SphericalCoord,
} from "@suaveplan/geo";
interface Region {
name: string;
centre: SphericalCoord;
bbox: BoundingBox;
measurementUnit: DistanceUnit;
}
const london: Region = {
name: "Greater London",
centre: { latitude: 51.5074, longitude: -0.1278 },
bbox: {
minLatitude: 51.28,
maxLatitude: 51.69,
minLongitude: -0.51,
maxLongitude: 0.33,
},
measurementUnit: "kilometre",
};
```
## API Reference
### `Ellipsoid`
`{ readonly semiMajorAxis: number; readonly semiMinorAxis: number;
readonly flattening: number; readonly eccentricity: number }`. All values
are in metres except the dimensionless `flattening` and `eccentricity`.
### `SphericalCoord`
`{ latitude: number; longitude: number; altitude?: number }`. `latitude`
and `longitude` are degrees; `altitude` (when present) is metres above
the ellipsoid surface.
### `Vec3`
`{ x: number; y: number; z: number }` — Cartesian components, typically in
ECEF metres.
### `Mat3x3`
Nine numeric fields `m00`..`m22` in row-major order. The rotation
operators in the `transforms` module produce and consume this shape.
### `BoundingBox`
`{ minLatitude: number; maxLatitude: number; minLongitude: number;
maxLongitude: number }`. Caller is responsible for ensuring `min ≤ max`;
`createBoundingBox` enforces this invariant.
### `DistanceUnit`
`"metre" | "kilometre" | "mile" | "nauticalMile"` — accepted everywhere a
unit is required. Conversion factors live in `units.ts`.
## Implementation Notes
The interfaces are intentionally structural — there are no nominal brands
— so values produced by other libraries that match the shape are accepted
without conversion. Runtime validation lives in the sibling `schemas.ts`
module via Zod, which mirrors these shapes one-for-one. When you change a
type here, update the matching schema in the same commit; the package's
test suite includes round-trip checks that catch drift between the two.

View File

@@ -0,0 +1,95 @@
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The `units` module converts scalar distance values between metres,
kilometres, statute miles, and nautical miles. It is the single conversion
hub used by every higher-level kernel in `@suaveplan/geo` — every distance
function returns its result by piping the kernel-internal metre value
through `convertDistance` to the caller's chosen unit, so consumers never
have to remember a conversion factor or copy-paste one. Exposing the
function publicly also lets callers normalise mixed-unit external inputs
(e.g. an aviation feed that mixes nautical miles and metres) into a single
working unit before invoking other geometry primitives.
## Features
- `convertDistance(value, from, to)` — bidirectional conversion across all
supported units in O(1).
- `DistanceUnit` string-literal union — `"metre" | "kilometre" | "mile" |
"nauticalMile"`.
- Identity short-circuit: when `from === to` the input is returned as-is
without any floating-point round-trip.
- Conversion factors are exact per international standards (no
hand-rounded constants).
## Usage
### Basic Example
```ts
import { convertDistance } from "@suaveplan/geo";
const km = convertDistance(1000, "metre", "kilometre"); // 1
const miles = convertDistance(10_000, "metre", "mile"); // 6.2137...
```
### Advanced Example
Build a unit-aware helper that accepts any input unit and emits a
canonicalised metre value for downstream geometry kernels:
```ts
import {
convertDistance,
type DistanceUnit,
greatCircleDistance,
defineEllipsoid,
} from "@suaveplan/geo";
interface Reading {
value: number;
unit: DistanceUnit;
}
function toMetres(r: Reading): number {
return convertDistance(r.value, r.unit, "metre");
}
const wgs84 = defineEllipsoid(6_378_137, 6_356_752.314245);
const allowance = toMetres({ value: 5, unit: "nauticalMile" }); // 9260
const distance = greatCircleDistance(
{ latitude: 51.5, longitude: -0.1 },
{ latitude: 48.9, longitude: 2.3 },
wgs84,
"metre"
);
const withinRange = distance <= allowance;
```
## API Reference
### `convertDistance(value: number, from: DistanceUnit, to: DistanceUnit): number`
Returns `value` expressed in `to`. When `from === to` the function returns
`value` unchanged — no multiplication and no division, which keeps the
identity case bit-exact. Otherwise it computes
`value * factor[from] / factor[to]` where the factor table is
`{ metre: 1, kilometre: 1000, mile: 1609.344, nauticalMile: 1852 }`.
### `type DistanceUnit`
`"metre" | "kilometre" | "mile" | "nauticalMile"`. Re-exported from the
package root.
## Implementation Notes
Conversion factors are exact under the modern international definitions:
1 statute mile = 1609.344 m and 1 nautical mile = 1852 m. The function
performs at most one multiply and one divide, so the output is within
one ULP of the mathematical result and round-trip
(`x → from → to → from`) is stable. No rounding is applied internally;
callers that need a fixed display precision should round at presentation
time.