Initial
This commit is contained in:
commit
b43955e0d9
162 changed files with 15425 additions and 0 deletions
70
tests/unit/wind.test.ts
Normal file
70
tests/unit/wind.test.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
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
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue