feat: v0.22.0 — ISS-022/025/027 hover route trace on job list, on-board rows and relocation panel
This commit is contained in:
+37
-1
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { StateSnapshot } from './game/state';
|
||||
import { planesGeoJson, routesGeoJson, greatCircleArc, ROUTE_ARC_SEGMENTS, type AirportCoord } from './mapData';
|
||||
import { planesGeoJson, routesGeoJson, hoverRouteGeoJson, greatCircleArc, ROUTE_ARC_SEGMENTS, type AirportCoord } from './mapData';
|
||||
import { haversineKm } from './geo';
|
||||
|
||||
const AIRPORTS = new Map<string, AirportCoord>([
|
||||
@@ -140,6 +140,42 @@ describe('routesGeoJson (v0.12.0 — one line per flying plane\'s current leg)',
|
||||
});
|
||||
});
|
||||
|
||||
describe('hoverRouteGeoJson (ISS-022/025/027 v0.22.0 — hover route trace)', () => {
|
||||
it('returns an empty collection for the no-hover "" sentinel', () => {
|
||||
expect(hoverRouteGeoJson('', '', AIRPORTS)).toEqual({ type: 'FeatureCollection', features: [] });
|
||||
});
|
||||
|
||||
it('draws one great-circle line between two known airports with exact endpoints', () => {
|
||||
const fc = hoverRouteGeoJson('EDDF', 'EHAM', AIRPORTS);
|
||||
expect(fc.features).toHaveLength(1);
|
||||
expect(fc.features[0].properties).toEqual({ planeId: '' }); // pure overlay — no owning plane
|
||||
const coords = fc.features[0].geometry.coordinates;
|
||||
expect(coords.length).toBe(ROUTE_ARC_SEGMENTS + 1); // sampled arc, not a 2-point chord
|
||||
expect(coords[0]).toEqual([8.56, 50.03]); // EDDF [lon, lat] — exact endpoint
|
||||
expect(coords[coords.length - 1]).toEqual([4.76, 52.31]); // EHAM — exact endpoint
|
||||
});
|
||||
|
||||
it('draws the same trace reversed (endpoints swapped, arc mirrored)', () => {
|
||||
const ab = hoverRouteGeoJson('EDDF', 'EHAM', AIRPORTS).features[0].geometry.coordinates;
|
||||
const ba = hoverRouteGeoJson('EHAM', 'EDDF', AIRPORTS).features[0].geometry.coordinates;
|
||||
expect(ba.length).toBe(ab.length);
|
||||
// The slerp is symmetric up to floating-point rounding (1 − t ≠ (N−t)/N bit-exactly), so
|
||||
// compare with a tolerance: each reversed point sits on the forward arc within ~1e-9 degrees.
|
||||
for (let i = 0; i < ab.length; i++) {
|
||||
expect(Math.abs(ba[i][0] - ab[ab.length - 1 - i][0])).toBeLessThan(1e-9);
|
||||
expect(Math.abs(ba[i][1] - ab[ab.length - 1 - i][1])).toBeLessThan(1e-9);
|
||||
}
|
||||
});
|
||||
|
||||
it('returns an empty collection when the from airport is missing from the dataset', () => {
|
||||
expect(hoverRouteGeoJson('ZZZZ', 'EDDV', AIRPORTS)).toEqual({ type: 'FeatureCollection', features: [] });
|
||||
});
|
||||
|
||||
it('returns an empty collection when the to airport is missing from the dataset', () => {
|
||||
expect(hoverRouteGeoJson('EDDF', 'ZZZZ', AIRPORTS)).toEqual({ type: 'FeatureCollection', features: [] });
|
||||
});
|
||||
});
|
||||
|
||||
describe('greatCircleArc (v0.12.2 — curved route traces)', () => {
|
||||
it('samples >2 points with the endpoints exactly at the airport coords', () => {
|
||||
const arc = greatCircleArc(AIRPORTS.get('EDDF')!, AIRPORTS.get('EHAM')!);
|
||||
|
||||
@@ -85,6 +85,33 @@ export function greatCircleArc(from: AirportCoord, to: AirportCoord): [number, n
|
||||
return coords;
|
||||
}
|
||||
|
||||
// ISS-022/025/027 (v0.22.0): the hover route trace — one great-circle LineString between two
|
||||
// airports, drawn top-most while a panel row hovers (job-list rows, on-board cargo rows, the
|
||||
// Relocate section). Empty collection when either endpoint is missing from the dataset — never
|
||||
// guessed; the '' sentinel (no hovered target) resolves to nothing and yields the same.
|
||||
export function hoverRouteGeoJson(
|
||||
fromIcao: string,
|
||||
toIcao: string,
|
||||
airportsByIcao: ReadonlyMap<string, AirportCoord>,
|
||||
): FeatureCollection<MapLineFeature> {
|
||||
const from = airportsByIcao.get(fromIcao);
|
||||
const to = airportsByIcao.get(toIcao);
|
||||
if (!from || !to) return EMPTY_LINES;
|
||||
return {
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
{
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'LineString',
|
||||
coordinates: greatCircleArc(from, to),
|
||||
},
|
||||
properties: { planeId: '' }, // no owning plane — a pure hover overlay
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// One LineString per FLYING plane — its current leg from locationIcao to dest_icao.
|
||||
// Grounded planes draw nothing; a flying plane missing either endpoint from the
|
||||
// dataset is skipped, never guessed. The line is sampled along the great circle
|
||||
|
||||
Reference in New Issue
Block a user