141 lines
4.1 KiB
Go
141 lines
4.1 KiB
Go
package gfs
|
||
|
||
import "fmt"
|
||
|
||
// Dataset shape: (hour, pressure_level, variable, latitude, longitude).
|
||
// Matches the cube layout used by the reference Tawhiri implementation.
|
||
const (
|
||
NumHours = 65 // 0, 3, 6, ..., 192 hours forecast
|
||
NumLevels = 47 // pressure levels
|
||
NumVariables = 3 // geopotential height, U-wind, V-wind
|
||
NumLatitudes = 361 // -90.0 to +90.0 inclusive in 0.5° steps
|
||
NumLongitudes = 720 // 0.0 to 359.5 in 0.5° steps
|
||
|
||
HourStep = 3
|
||
MaxHour = 192
|
||
Resolution = 0.5
|
||
LatStart = -90.0
|
||
LonStart = 0.0
|
||
|
||
VarHeight = 0
|
||
VarWindU = 1
|
||
VarWindV = 2
|
||
|
||
ElementSize = 4 // float32
|
||
|
||
// DatasetSize is the canonical file size: every grid cell × element size.
|
||
DatasetSize int64 = int64(NumHours) * int64(NumLevels) * int64(NumVariables) *
|
||
int64(NumLatitudes) * int64(NumLongitudes) * int64(ElementSize)
|
||
)
|
||
|
||
// LevelSet identifies which GRIB file (primary/secondary) carries a level.
|
||
type LevelSet int
|
||
|
||
const (
|
||
LevelSetA LevelSet = iota // pgrb2 — primary file
|
||
LevelSetB // pgrb2b — secondary file
|
||
)
|
||
|
||
// Pressures lists the 47 pressure levels (hPa) in dataset index order,
|
||
// descending from surface to top of atmosphere.
|
||
var Pressures = [NumLevels]int{
|
||
1000, 975, 950, 925, 900, 875, 850, 825, 800, 775,
|
||
750, 725, 700, 675, 650, 625, 600, 575, 550, 525,
|
||
500, 475, 450, 425, 400, 375, 350, 325, 300, 275,
|
||
250, 225, 200, 175, 150, 125, 100, 70, 50, 30,
|
||
20, 10, 7, 5, 3, 2, 1,
|
||
}
|
||
|
||
// PressuresPgrb2 lists the levels carried by the primary GRIB file.
|
||
var PressuresPgrb2 = []int{
|
||
10, 20, 30, 50, 70, 100, 150, 200, 250, 300, 350, 400,
|
||
450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 925,
|
||
950, 975, 1000,
|
||
}
|
||
|
||
// PressuresPgrb2b lists the levels carried by the secondary GRIB file.
|
||
var PressuresPgrb2b = []int{
|
||
1, 2, 3, 5, 7, 125, 175, 225, 275, 325, 375, 425,
|
||
475, 525, 575, 625, 675, 725, 775, 825, 875,
|
||
}
|
||
|
||
var pressureIndex map[int]int
|
||
var pressureLevelSet map[int]LevelSet
|
||
|
||
func init() {
|
||
pressureIndex = make(map[int]int, NumLevels)
|
||
for i, p := range Pressures {
|
||
pressureIndex[p] = i
|
||
}
|
||
pressureLevelSet = make(map[int]LevelSet, NumLevels)
|
||
for _, p := range PressuresPgrb2 {
|
||
pressureLevelSet[p] = LevelSetA
|
||
}
|
||
for _, p := range PressuresPgrb2b {
|
||
pressureLevelSet[p] = LevelSetB
|
||
}
|
||
}
|
||
|
||
// PressureIndex returns the dataset index for a pressure level in hPa,
|
||
// or -1 when the level is unknown.
|
||
func PressureIndex(hPa int) int {
|
||
idx, ok := pressureIndex[hPa]
|
||
if !ok {
|
||
return -1
|
||
}
|
||
return idx
|
||
}
|
||
|
||
// PressureLevelSet returns the GRIB file set carrying a pressure level.
|
||
func PressureLevelSet(hPa int) (LevelSet, bool) {
|
||
ls, ok := pressureLevelSet[hPa]
|
||
return ls, ok
|
||
}
|
||
|
||
// HourIndex returns the dataset time index for a forecast hour, or -1 when
|
||
// the hour is outside the range or not a multiple of HourStep.
|
||
func HourIndex(hour int) int {
|
||
if hour < 0 || hour > MaxHour || hour%HourStep != 0 {
|
||
return -1
|
||
}
|
||
return hour / HourStep
|
||
}
|
||
|
||
// Hours returns the full list of forecast hours, [0, 3, 6, ..., MaxHour].
|
||
func Hours() []int {
|
||
out := make([]int, 0, NumHours)
|
||
for h := 0; h <= MaxHour; h += HourStep {
|
||
out = append(out, h)
|
||
}
|
||
return out
|
||
}
|
||
|
||
// VariableIndex maps a GRIB (category, number) pair to a dataset variable
|
||
// index, returning -1 for parameters this dataset does not store.
|
||
func VariableIndex(parameterCategory, parameterNumber int) int {
|
||
switch {
|
||
case parameterCategory == 3 && parameterNumber == 5:
|
||
return VarHeight
|
||
case parameterCategory == 2 && parameterNumber == 2:
|
||
return VarWindU
|
||
case parameterCategory == 2 && parameterNumber == 3:
|
||
return VarWindV
|
||
default:
|
||
return -1
|
||
}
|
||
}
|
||
|
||
// S3 URL configuration for NOAA GFS data on the public S3 mirror.
|
||
const S3BaseURL = "https://noaa-gfs-bdp-pds.s3.amazonaws.com"
|
||
|
||
// GribURL returns the S3 URL for a primary (pgrb2) GRIB file.
|
||
func GribURL(date string, runHour, forecastStep int) string {
|
||
return fmt.Sprintf("%s/gfs.%s/%02d/atmos/gfs.t%02dz.pgrb2.0p50.f%03d",
|
||
S3BaseURL, date, runHour, runHour, forecastStep)
|
||
}
|
||
|
||
// GribURLB returns the S3 URL for a secondary (pgrb2b) GRIB file.
|
||
func GribURLB(date string, runHour, forecastStep int) string {
|
||
return fmt.Sprintf("%s/gfs.%s/%02d/atmos/gfs.t%02dz.pgrb2b.0p50.f%03d",
|
||
S3BaseURL, date, runHour, runHour, forecastStep)
|
||
}
|