import { describe, it, expect } from 'vitest'; import { parseEnsemble, ellipseRing } from '$domain'; import type { EnsembleFootprint } from '$domain'; describe('parseEnsemble', () => { it('narrows an ensemble payload', () => { const parsed = parseEnsemble({ ensemble: { members: [{ member: 0, landing: { lat: 1, lng: 2, alt: 0 } }], failed: [] } }); expect(parsed?.members).toHaveLength(1); expect(parsed?.footprint).toBeNull(); }); it('rejects a plain single-run result', () => { expect(parseEnsemble({ prediction: [] })).toBeNull(); expect(parseEnsemble(null)).toBeNull(); expect(parseEnsemble({ ensemble: {} })).toBeNull(); }); }); describe('ellipseRing', () => { const footprint: EnsembleFootprint = { count: 5, mean: { lat: 62, lng: 129 }, radius_km: 10, ellipse: { semi_major_km: 20, semi_minor_km: 10, bearing_deg: 0 } }; it('returns a closed ring centred on the mean', () => { const ring = ellipseRing(footprint, 32); expect(ring).toHaveLength(33); expect(ring[0][0]).toBeCloseTo(ring[32][0], 9); expect(ring[0][1]).toBeCloseTo(ring[32][1], 9); const lats = ring.map((p) => p[0]); expect((Math.min(...lats) + Math.max(...lats)) / 2).toBeCloseTo(62, 6); }); it('corrects longitude for latitude so the ellipse is not stretched', () => { const ring = ellipseRing(footprint, 64); const lats = ring.map((p) => p[0]); const lngs = ring.map((p) => p[1]); // 20 km north-south at bearing 0 => ~0.18° of latitude. expect(Math.max(...lats) - 62).toBeCloseTo(20 / 111.32, 3); // 10 km east-west spans more degrees at 62°N than it would at the equator. const halfSpanDeg = Math.max(...lngs) - 129; expect(halfSpanDeg).toBeGreaterThan(10 / 111.32); expect(halfSpanDeg).toBeCloseTo(10 / (111.32 * Math.cos((62 * Math.PI) / 180)), 3); }); });