package gfs import ( "math" "path/filepath" "testing" "time" ) // testVariant is a tiny cube (2 hours × 3 levels × 3 lat × 4 lng) used to // exercise the sampler without allocating a multi-gigabyte real dataset. func testVariant() *Variant { return &Variant{ ID: "gfs-test", ResToken: "test", Resolution: 90, // 180/90+1 = 3 lats, 360/90 = 4 lngs HourStep: 3, MaxHour: 3, // 2 hours Pressures: []int{1000, 500, 100}, PressuresPgrb2: []int{1000, 500, 100}, PressuresPgrb2b: []int{}, } } func TestWindSampler(t *testing.T) { v := testVariant() path := filepath.Join(t.TempDir(), "cube.bin") f, err := Create(path, v) if err != nil { t.Fatalf("Create: %v", err) } // HGT increases with level so the altitude bisection has a gradient; // U and V are constant so interpolation must return them exactly. for h := range v.NumHours() { for lvl := range v.NumLevels() { for la := range v.NumLatitudes() { for ln := range v.NumLongitudes() { f.SetVal(h, lvl, VarHeight, la, ln, float32(lvl*1000)) f.SetVal(h, lvl, VarWindU, la, ln, 7) f.SetVal(h, lvl, VarWindV, la, ln, 3) } } } } f.Flush() f.Close() epoch := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) rf, err := Open(path, v, epoch) if err != nil { t.Fatalf("Open: %v", err) } defer rf.Close() w := NewWind(rf) // Query at the dataset epoch, equator, lng 45, altitude 500m (between // level 0 @ 0m and level 1 @ 1000m). s, err := w.Wind(float64(epoch.Unix()), 0, 45, 500) if err != nil { t.Fatalf("Wind: %v", err) } if math.Abs(s.U-7) > 1e-5 || math.Abs(s.V-3) > 1e-5 { t.Errorf("constant wind not recovered: got U=%v V=%v, want 7,3", s.U, s.V) } if s.AboveModel { t.Errorf("AboveModel should be false at altitude within model range") } }