Initial Commit
This commit is contained in:
69
packages/geospatial/logistics/ports/src/distance/distance.md
Normal file
69
packages/geospatial/logistics/ports/src/distance/distance.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# distance
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Wraps `@suaveplan/geo`'s `greatCircleDistance` against a sphere whose
|
||||
radius matches the IUGG mean Earth radius (6 371 km), bound once as the
|
||||
exported `EARTH_SPHERE` ellipsoid. The package's lane-distance,
|
||||
lane-progress, ETA, and nearest-port modules all consume these wrappers
|
||||
so the entire package agrees on a single radius and the rounding policy
|
||||
is centralised.
|
||||
|
||||
## Features
|
||||
|
||||
- `distanceNm(a, b)` — great-circle distance in nautical miles, rounded
|
||||
to two decimals.
|
||||
- `distanceKm(a, b)` — same in kilometres, rounded to two decimals.
|
||||
- `distanceMetresRaw(a, b)` — unrounded metres, useful for accumulating
|
||||
segment sums without compounding rounding error.
|
||||
- Symmetric: `distanceNm(a, b) === distanceNm(b, a)` to floating
|
||||
precision.
|
||||
- Non-negative for any valid coordinate pair.
|
||||
- Coordinate inputs use `@suaveplan/geo`'s `SphericalCoord` shape so the
|
||||
wrappers are interoperable with the entire geospatial stack.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { distanceNm } from "@suaveplan/ports";
|
||||
|
||||
const nm = distanceNm(
|
||||
{ latitude: 1.26, longitude: 103.82 },
|
||||
{ latitude: 51.9, longitude: 4.49 }
|
||||
);
|
||||
console.log(nm); // ~5800 nautical miles
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { distanceMetresRaw } from "@suaveplan/ports";
|
||||
|
||||
function totalMetres(coords: { latitude: number; longitude: number }[]): number {
|
||||
let total = 0;
|
||||
for (let i = 0; i < coords.length - 1; i++) {
|
||||
total += distanceMetresRaw(coords[i], coords[i + 1]);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `distanceNm(a, b)` → `number`.
|
||||
- `distanceKm(a, b)` → `number`.
|
||||
- `distanceMetresRaw(a, b)` → `number` (unrounded).
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
All three wrappers route through the single `EARTH_SPHERE` constant so
|
||||
the cost of ellipsoid construction is paid once at module load. Rounded
|
||||
helpers multiply by 100, round, then divide — this matches the
|
||||
main-branch rounding convention so cross-version diffs remain
|
||||
byte-for-byte stable on the rounded values. `distanceMetresRaw` is
|
||||
deliberately exposed for callers that need full precision (segment
|
||||
accumulation in lane-distance, deviation-ratio computation in ETA).
|
||||
@@ -0,0 +1,44 @@
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { distanceKm, distanceMetresRaw, distanceNm } from "./distance.js";
|
||||
|
||||
const SINGAPORE = { latitude: 1.2644, longitude: 103.8198 };
|
||||
const ROTTERDAM = { latitude: 51.9, longitude: 4.49 };
|
||||
|
||||
describe("distance helpers", () => {
|
||||
it("distanceNm zero between identical coords", { timeout: 5000 }, () => {
|
||||
expect(distanceNm(SINGAPORE, SINGAPORE)).toBe(0);
|
||||
});
|
||||
|
||||
it("distanceKm zero between identical coords", { timeout: 5000 }, () => {
|
||||
expect(distanceKm(SINGAPORE, SINGAPORE)).toBe(0);
|
||||
});
|
||||
|
||||
it("distanceMetresRaw produces unrounded metres", { timeout: 5000 }, () => {
|
||||
const raw = distanceMetresRaw(SINGAPORE, ROTTERDAM);
|
||||
expect(raw).toBeGreaterThan(10_000_000);
|
||||
expect(raw).toBeLessThan(12_000_000);
|
||||
});
|
||||
|
||||
it("Singapore to Rotterdam great-circle is ~5800 nm", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const nm = distanceNm(SINGAPORE, ROTTERDAM);
|
||||
expect(nm).toBeGreaterThan(5600);
|
||||
expect(nm).toBeLessThan(6000);
|
||||
});
|
||||
|
||||
it("Singapore to Rotterdam great-circle is ~10800 km", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const km = distanceKm(SINGAPORE, ROTTERDAM);
|
||||
expect(km).toBeGreaterThan(10500);
|
||||
expect(km).toBeLessThan(11200);
|
||||
});
|
||||
|
||||
it("nm * 1.852 ≈ km within 1% tolerance", { timeout: 5000 }, () => {
|
||||
const nm = distanceNm(SINGAPORE, ROTTERDAM);
|
||||
const km = distanceKm(SINGAPORE, ROTTERDAM);
|
||||
const expected = nm * 1.852;
|
||||
expect(Math.abs(km - expected) / km).toBeLessThan(0.01);
|
||||
});
|
||||
});
|
||||
31
packages/geospatial/logistics/ports/src/distance/distance.ts
Normal file
31
packages/geospatial/logistics/ports/src/distance/distance.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { SphericalCoord } from "@suaveplan/geo";
|
||||
import { greatCircleDistance } from "@suaveplan/geo";
|
||||
import { EARTH_SPHERE } from "../constants/index.js";
|
||||
|
||||
/**
|
||||
* Great-circle distance between two coordinates in nautical miles. Uses
|
||||
* the IUGG mean-radius sphere bound in `EARTH_SPHERE`. The result is
|
||||
* rounded to two decimal places so equality comparisons remain stable
|
||||
* across platforms.
|
||||
*/
|
||||
export function distanceNm(a: SphericalCoord, b: SphericalCoord): number {
|
||||
const nm = greatCircleDistance(a, b, EARTH_SPHERE, "nauticalMile");
|
||||
return Math.round(nm * 100) / 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Great-circle distance between two coordinates in kilometres. Mirrors
|
||||
* `distanceNm` rounding behaviour for the kilometre unit.
|
||||
*/
|
||||
export function distanceKm(a: SphericalCoord, b: SphericalCoord): number {
|
||||
const km = greatCircleDistance(a, b, EARTH_SPHERE, "kilometre");
|
||||
return Math.round(km * 100) / 100;
|
||||
}
|
||||
|
||||
/** Unrounded raw great-circle distance in metres (for internal use). */
|
||||
export function distanceMetresRaw(
|
||||
a: SphericalCoord,
|
||||
b: SphericalCoord
|
||||
): number {
|
||||
return greatCircleDistance(a, b, EARTH_SPHERE, "metre");
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { distanceKm, distanceMetresRaw, distanceNm } from "./distance.js";
|
||||
Reference in New Issue
Block a user