70 lines
2.1 KiB
TypeScript
70 lines
2.1 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
||
import { decodeWindField, createWindInterpolator } from '$domain';
|
||
import type { WindField, WindHeader } from '$domain';
|
||
|
||
/**
|
||
* A 2×2 grid that reproduces the predictor's real conventions: longitudes in the
|
||
* 0..360 frame (358 → 0, i.e. -2° → 0°) and a north→south scan (la1 > la2) with
|
||
* a *positive* dy. Getting either wrong misplaces every vector.
|
||
*/
|
||
const header: WindHeader = {
|
||
parameterUnit: 'm.s-1',
|
||
parameterNumberName: 'wind',
|
||
nx: 2,
|
||
ny: 2,
|
||
lo1: 358,
|
||
la1: 10,
|
||
lo2: 0,
|
||
la2: 8,
|
||
dx: 2,
|
||
dy: 2,
|
||
refTime: '2026-01-01T00:00:00Z'
|
||
};
|
||
|
||
// Row-major: [ (10,-2) (10,0) ] then [ (8,-2) (8,0) ]
|
||
const field: WindField = [
|
||
{ header, data: [10, 0, 0, 0] }, // U (eastward)
|
||
{ header, data: [0, 10, 0, 0] } // V (northward)
|
||
];
|
||
|
||
describe('decodeWindField', () => {
|
||
it('places vectors using the grid extent, not raw dx/dy', () => {
|
||
const v = decodeWindField(field);
|
||
expect(v).toHaveLength(4);
|
||
// First point: la1/lo1 wrapped from 358 into (-180, 180].
|
||
expect(v[0].lat).toBe(10);
|
||
expect(v[0].lng).toBe(-2);
|
||
// Second column steps east across the 0/360 seam.
|
||
expect(v[1].lng).toBe(0);
|
||
// Second row scans southward despite dy being positive.
|
||
expect(v[2].lat).toBe(8);
|
||
});
|
||
|
||
it('derives speed and the bearing the wind blows TO', () => {
|
||
const v = decodeWindField(field);
|
||
expect(v[0].speed).toBeCloseTo(10, 6);
|
||
expect(v[0].bearing).toBeCloseTo(90, 6); // pure U → eastward
|
||
expect(v[1].bearing).toBeCloseTo(0, 6); // pure V → northward
|
||
});
|
||
});
|
||
|
||
describe('createWindInterpolator', () => {
|
||
it('returns the exact grid value at a node', () => {
|
||
const at = createWindInterpolator(field);
|
||
expect(at(-2, 10)).toEqual([10, 0]);
|
||
expect(at(0, 10)).toEqual([0, 10]);
|
||
});
|
||
|
||
it('blends between nodes', () => {
|
||
const at = createWindInterpolator(field);
|
||
const mid = at(-1, 10)!;
|
||
expect(mid[0]).toBeCloseTo(5, 6);
|
||
expect(mid[1]).toBeCloseTo(5, 6);
|
||
});
|
||
|
||
it('returns null outside the grid', () => {
|
||
const at = createWindInterpolator(field);
|
||
expect(at(-2, 20)).toBeNull(); // north of la1
|
||
expect(at(-2, 0)).toBeNull(); // south of la2
|
||
});
|
||
});
|