This commit is contained in:
gili8420 2026-08-14 00:01:49 +09:00
commit b43955e0d9
162 changed files with 15425 additions and 0 deletions

70
tests/unit/wind.test.ts Normal file
View 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 northsouth 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
});
});