Files
gods-eye-view/src/data/trafficBounds.test.mjs
T
Ashfaq c22ad6af32 fix: handle antimeridian crossing in clampBoundsAroundCenter
east - west assumed a linear longitude axis, so a viewport crossing
the antimeridian (west=179.98, east=-179.98) produced a negative span
and unnormalized output coordinates outside [-180, 180). Wrap the span
through 360 and normalize the clamped west/east back into range.

Fixes #352.
2026-09-13 10:15:55 +05:30

138 lines
5.2 KiB
JavaScript

// src/data/trafficBounds.test.mjs
import { test } from 'node:test';
import assert from 'node:assert/strict';
import {
greatCircleKm,
deriveFetchCenter,
clampBoundsAroundCenter,
} from './trafficBounds.js';
// Downtown Austin — matches the TomTom fixture tile neighbourhood.
const NADIR = { lat: 30.2672, lon: -97.7431 };
test('greatCircleKm sanity: Austin -> ~5 km north', () => {
const d = greatCircleKm(NADIR.lat, NADIR.lon, NADIR.lat + 0.045, NADIR.lon);
assert.ok(Math.abs(d - 5.0) < 0.1, `got ${d}`);
});
test('straight-down look: hit ~= nadir -> center unchanged (uses hit)', () => {
const c = deriveFetchCenter({
nadirLat: NADIR.lat, nadirLon: NADIR.lon,
hitLat: NADIR.lat + 0.0001, hitLon: NADIR.lon - 0.0001,
maxPullKm: 12,
});
assert.equal(c.source, 'hit');
assert.ok(Math.abs(c.lat - (NADIR.lat + 0.0001)) < 1e-9);
assert.ok(Math.abs(c.lon - (NADIR.lon - 0.0001)) < 1e-9);
});
test('oblique look within 12 km: uses the hit point verbatim', () => {
const hit = { lat: NADIR.lat + 0.045, lon: NADIR.lon + 0.02 }; // ~5.3 km away
const c = deriveFetchCenter({
nadirLat: NADIR.lat, nadirLon: NADIR.lon,
hitLat: hit.lat, hitLon: hit.lon,
maxPullKm: 12,
});
assert.equal(c.source, 'hit');
assert.equal(c.lat, hit.lat);
assert.equal(c.lon, hit.lon);
});
test('horizon gaze: far hit is pulled back to 12 km along the bearing', () => {
// Hit ~100 km due east of nadir.
const hit = { lat: NADIR.lat, lon: NADIR.lon + 1.041 };
assert.ok(greatCircleKm(NADIR.lat, NADIR.lon, hit.lat, hit.lon) > 90, 'precondition: far hit');
const c = deriveFetchCenter({
nadirLat: NADIR.lat, nadirLon: NADIR.lon,
hitLat: hit.lat, hitLon: hit.lon,
maxPullKm: 12,
});
assert.equal(c.source, 'pulled');
const d = greatCircleKm(NADIR.lat, NADIR.lon, c.lat, c.lon);
assert.ok(Math.abs(d - 12) < 0.05, `pulled distance ${d} km, expected ~12`);
// Due-east bearing: latitude stays ~constant, longitude moves east but well
// short of the hit.
assert.ok(Math.abs(c.lat - NADIR.lat) < 0.01, `lat drifted: ${c.lat}`);
assert.ok(c.lon > NADIR.lon && c.lon < hit.lon, `lon not between nadir and hit: ${c.lon}`);
});
test('pull cap is honored for other maxPullKm values', () => {
const hit = { lat: NADIR.lat + 0.9, lon: NADIR.lon }; // ~100 km north
const c = deriveFetchCenter({
nadirLat: NADIR.lat, nadirLon: NADIR.lon,
hitLat: hit.lat, hitLon: hit.lon,
maxPullKm: 5,
});
assert.equal(c.source, 'pulled');
const d = greatCircleKm(NADIR.lat, NADIR.lon, c.lat, c.lon);
assert.ok(Math.abs(d - 5) < 0.05, `pulled distance ${d} km, expected ~5`);
});
test('pickEllipsoid failure (non-finite hit): falls back to the camera nadir', () => {
for (const [hitLat, hitLon] of [[NaN, NaN], [undefined, undefined], [30.3, undefined]]) {
const c = deriveFetchCenter({
nadirLat: NADIR.lat, nadirLon: NADIR.lon,
hitLat, hitLon,
maxPullKm: 12,
});
assert.equal(c.source, 'nadir');
assert.equal(c.lat, NADIR.lat);
assert.equal(c.lon, NADIR.lon);
}
});
test('span clamp: oversized bounds shrink to maxSpan centered on the given center', () => {
const bounds = { south: 29.5, north: 30.5, west: -98.5, east: -97.5 }; // 1 degree spans
const center = { lat: NADIR.lat, lon: NADIR.lon };
const clamped = clampBoundsAroundCenter(bounds, center, 0.05);
assert.ok(Math.abs((clamped.north - clamped.south) - 0.05) < 1e-12);
assert.ok(Math.abs((clamped.east - clamped.west) - 0.05) < 1e-12);
assert.ok(Math.abs((clamped.north + clamped.south) / 2 - center.lat) < 1e-12);
assert.ok(Math.abs((clamped.east + clamped.west) / 2 - center.lon) < 1e-12);
});
test('span clamp: small bounds keep their span, recentered', () => {
const bounds = { south: 30.0, north: 30.02, west: -98.0, east: -97.97 };
const center = { lat: 30.30, lon: -97.70 };
const clamped = clampBoundsAroundCenter(bounds, center, 0.05);
assert.ok(Math.abs((clamped.north - clamped.south) - 0.02) < 1e-12);
assert.ok(Math.abs((clamped.east - clamped.west) - 0.03) < 1e-12);
assert.ok(Math.abs((clamped.north + clamped.south) / 2 - 30.30) < 1e-12);
assert.ok(Math.abs((clamped.east + clamped.west) / 2 + 97.70) < 1e-12);
});
test('span clamp is idempotent on already-clamped bounds (loadRoadsForBounds re-clamp)', () => {
const center = { lat: NADIR.lat, lon: NADIR.lon };
const once = clampBoundsAroundCenter({ south: 29.5, north: 30.5, west: -98.5, east: -97.5 }, center, 0.05);
const midpoint = { lat: (once.south + once.north) / 2, lon: (once.west + once.east) / 2 };
const twice = clampBoundsAroundCenter(once, midpoint, 0.05);
assert.deepEqual(twice, once);
});
test('span clamp handles bounds crossing the antimeridian', () => {
const bounds = {
south: -0.02,
north: 0.02,
west: 179.98,
east: -179.98,
};
const center = {
lat: 0,
lon: 179.99,
};
const result = clampBoundsAroundCenter(bounds, center, 0.05);
assert.ok(result.west >= -180 && result.west < 180);
assert.ok(result.east >= -180 && result.east < 180);
// Wrapped longitude span should remain about 0.04 degrees.
const span =
result.east >= result.west
? result.east - result.west
: result.east - result.west + 360;
assert.ok(Math.abs(span - 0.04) < 1e-12);
});