package gfs import ( "time" "predictor-refactored/internal/numerics" "predictor-refactored/internal/weather" ) // Wind is a WindField backed by a GFS dataset file. type Wind struct { file *File } // NewWind returns a Wind backed by file. func NewWind(file *File) *Wind { return &Wind{file: file} } // Epoch returns the forecast run time of the underlying file. func (w *Wind) Epoch() time.Time { return w.file.Epoch } // Source returns the source identifier "noaa-gfs-0p50". func (w *Wind) Source() string { return "noaa-gfs-0p50" } // Close releases the underlying file's resources. func (w *Wind) Close() error { return w.file.Close() } // Grid axes for the GFS 0.5-degree dataset. var ( hourAxis = numerics.Axis{ Left: 0, Step: float64(HourStep), N: NumHours, Name: "hour", } latAxis = numerics.Axis{ Left: LatStart, Step: Resolution, N: NumLatitudes, Name: "lat", } lngAxis = numerics.Axis{ Left: LonStart, Step: Resolution, N: NumLongitudes, Wrap: true, Name: "lng", } ) // Wind samples the field at the given UNIX time, geographic coordinate, and // altitude. Vertical interpolation matches Tawhiri: locate the two pressure // levels whose interpolated geopotential heights bracket alt, then linearly // interpolate U and V between them. func (w *Wind) Wind(t, lat, lng, alt float64) (weather.Sample, error) { hours := (t - float64(w.file.Epoch.Unix())) / 3600.0 bh, err := hourAxis.Locate(hours) if err != nil { return weather.Sample{}, err } bla, err := latAxis.Locate(lat) if err != nil { return weather.Sample{}, err } bln, err := lngAxis.Locate(lng) if err != nil { return weather.Sample{}, err } bs := [3]numerics.Bracket{bh, bla, bln} height := func(level int) func(i, j, k int) float64 { return func(i, j, k int) float64 { return float64(w.file.Val(i, level, VarHeight, j, k)) } } levelIdx := numerics.Bisect(0, NumLevels-2, alt, func(level int) float64 { return numerics.EvalTrilinear(bs, height(level)) }) lowerHGT := numerics.EvalTrilinear(bs, height(levelIdx)) upperHGT := numerics.EvalTrilinear(bs, height(levelIdx+1)) var altFrac float64 if lowerHGT != upperHGT { altFrac = (upperHGT - alt) / (upperHGT - lowerHGT) } else { altFrac = 0.5 } component := func(level, variable int) float64 { return numerics.EvalTrilinear(bs, func(i, j, k int) float64 { return float64(w.file.Val(i, level, variable, j, k)) }) } lowerU := component(levelIdx, VarWindU) upperU := component(levelIdx+1, VarWindU) lowerV := component(levelIdx, VarWindV) upperV := component(levelIdx+1, VarWindV) return weather.Sample{ U: lowerU*altFrac + upperU*(1-altFrac), V: lowerV*altFrac + upperV*(1-altFrac), AboveModel: altFrac < 0, }, nil }