NODEDC_DESIGN_GUIDELINE/scripts/map-spiral.test.mjs

90 lines
3.7 KiB
JavaScript

import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
import ts from "typescript";
const source = await readFile(new URL("../apps/catalog/src/mapSpiralMath.ts", import.meta.url), "utf8");
const { outputText } = ts.transpileModule(source, {
compilerOptions: {
module: ts.ModuleKind.ES2022,
target: ts.ScriptTarget.ES2022,
},
fileName: "mapSpiralMath.ts",
});
const spiralMath = await import(`data:text/javascript;base64,${Buffer.from(outputText).toString("base64")}`);
const {
MAX_SPIRAL_RADIUS_METERS,
directGeodesicDestination,
normalizeRadians,
spiralAngleAtArcLength,
spiralArcLengthAtAngle,
spiralRadiusAtAngle,
spiralSurfaceFrame,
} = spiralMath;
const twoPi = Math.PI * 2;
test("one complete spiral turn increases radius by exactly one pitch", () => {
assert.equal(spiralRadiusAtAngle(twoPi, 10_000), 10_000);
});
test("arc-length inversion keeps configured surface distance across the route", () => {
for (const distance of [0, 1, 20, 1_000, 25_000, 1_000_000]) {
const angle = spiralAngleAtArcLength(distance, 10_000);
const restored = spiralArcLengthAtAngle(angle, 10_000);
assert.ok(Math.abs(restored - distance) <= Math.max(1e-6, distance * 1e-10), `${distance}${restored}`);
}
});
test("equal elapsed time produces equal traveled surface distance", () => {
const speed = 250;
const pitch = 10_000;
const atOneSecond = spiralAngleAtArcLength(speed, pitch);
const atTenSeconds = spiralAngleAtArcLength(speed * 10, pitch);
assert.ok(atTenSeconds > atOneSecond);
assert.ok(spiralRadiusAtAngle(atTenSeconds, pitch) > spiralRadiusAtAngle(atOneSecond, pitch));
assert.ok(Math.abs(spiralArcLengthAtAngle(atTenSeconds, pitch) - speed * 10) < 1e-6);
});
test("WGS84 direct destination remains finite across dateline and high latitude", () => {
const destination = directGeodesicDestination({
longitude: 179.9 * Math.PI / 180,
latitude: 84 * Math.PI / 180,
}, Math.PI / 3, 500_000);
assert.ok(Number.isFinite(destination.longitude));
assert.ok(Number.isFinite(destination.latitude));
assert.ok(destination.longitude >= -Math.PI && destination.longitude <= Math.PI);
assert.ok(destination.latitude >= -Math.PI / 2 && destination.latitude <= Math.PI / 2);
});
test("eastbound WGS84 destination at equator matches the ellipsoid circumference", () => {
const destination = directGeodesicDestination({ longitude: 0, latitude: 0 }, Math.PI / 2, 100_000);
assert.ok(Math.abs(destination.latitude) < 1e-12);
assert.ok(Math.abs(destination.longitude - 100_000 / 6_378_137) < 1e-10);
});
test("spiral frame starts at the current point and follows the current heading", () => {
const origin = { longitude: 37.6173 * Math.PI / 180, latitude: 55.7558 * Math.PI / 180 };
const heading = 0.7;
const frame = spiralSurfaceFrame(origin, heading, 0, 10_000);
assert.ok(Math.abs(normalizeRadians(frame.longitude - origin.longitude)) < 1e-12);
assert.ok(Math.abs(frame.latitude - origin.latitude) < 1e-12);
assert.equal(frame.radiusMeters, 0);
assert.ok(Math.abs(normalizeRadians(frame.tangentHeading - heading)) < 1e-12);
});
test("route stops at the documented regional WGS84 safety boundary", () => {
const pitch = 100_000;
const angle = (MAX_SPIRAL_RADIUS_METERS + pitch) * twoPi / pitch;
const distance = spiralArcLengthAtAngle(angle, pitch);
assert.throws(
() => spiralSurfaceFrame({ longitude: 0, latitude: 0 }, 0, distance, pitch),
/spiral_extent_limit/,
);
});
test("invalid animation inputs fail closed", () => {
assert.throws(() => spiralAngleAtArcLength(100, 0), /spiral_pitch_invalid/);
assert.throws(() => directGeodesicDestination({ longitude: 0, latitude: Number.NaN }, 0, 10), /spiral_origin_invalid/);
});