package dataset import "fmt" // Dataset shape constants. // Shape: (65, 47, 3, 361, 720) = (hour, pressure_level, variable, latitude, longitude) // This matches the reference predictor exactly. const ( NumHours = 65 // 0, 3, 6, ..., 192 NumLevels = 47 // pressure levels NumVariables = 3 // height, wind_u, wind_v NumLatitudes = 361 // -90.0 to +90.0 in 0.5 degree steps NumLongitudes = 720 // 0.0 to 359.5 in 0.5 degree steps HourStep = 3 // hours between forecast time steps MaxHour = 192 // maximum forecast hour Resolution = 0.5 // grid resolution in degrees LatStart = -90.0 // first latitude in the dataset LonStart = 0.0 // first longitude in the dataset // Variable indices within the dataset. VarHeight = 0 VarWindU = 1 VarWindV = 2 ElementSize = 4 // float32 = 4 bytes ) // DatasetSize is the total size of the dataset file in bytes. // 65 * 47 * 3 * 361 * 720 * 4 = 9,528,667,200 const DatasetSize int64 = int64(NumHours) * int64(NumLevels) * int64(NumVariables) * int64(NumLatitudes) * int64(NumLongitudes) * int64(ElementSize) // LevelSet identifies which GRIB file set a pressure level belongs to. type LevelSet int const ( LevelSetA LevelSet = iota // pgrb2 (primary) LevelSetB // pgrb2b (secondary) ) // Pressures contains the 47 pressure levels in hPa, sorted descending. // Index 0 = 1000 hPa (near surface), Index 46 = 1 hPa (high 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, } // pressureIndex maps pressure in hPa to its index in the Pressures array. var pressureIndex map[int]int // pressureLevelSet maps pressure in hPa to its GRIB file set. 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 } } // PressuresPgrb2 contains levels found in the primary pgrb2 file (26 levels). 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 contains levels found in the secondary pgrb2b file (21 levels). var PressuresPgrb2b = []int{ 1, 2, 3, 5, 7, 125, 175, 225, 275, 325, 375, 425, 475, 525, 575, 625, 675, 725, 775, 825, 875, } // PressureIndex returns the dataset index for a given pressure level in hPa. // Returns -1 if the level is not found. func PressureIndex(hPa int) int { idx, ok := pressureIndex[hPa] if !ok { return -1 } return idx } // PressureLevelSet returns which GRIB file set a pressure level belongs to. func PressureLevelSet(hPa int) (LevelSet, bool) { ls, ok := pressureLevelSet[hPa] return ls, ok } // HourIndex returns the dataset time index for a forecast hour. // Returns -1 if the hour is invalid (not a multiple of HourStep or out of range). func HourIndex(hour int) int { if hour < 0 || hour > MaxHour || hour%HourStep != 0 { return -1 } return hour / HourStep } // Hours returns all forecast hours as a slice: [0, 3, 6, ..., 192]. func Hours() []int { out := make([]int, 0, NumHours) for h := 0; h <= MaxHour; h += HourStep { out = append(out, h) } return out } // S3 URL configuration for NOAA GFS data. 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) } // GribFileName returns the local filename for a primary GRIB file. func GribFileName(runHour, forecastStep int) string { return fmt.Sprintf("gfs.t%02dz.pgrb2.0p50.f%03d", runHour, forecastStep) } // GribFileNameB returns the local filename for a secondary GRIB file. func GribFileNameB(runHour, forecastStep int) string { return fmt.Sprintf("gfs.t%02dz.pgrb2b.0p50.f%03d", runHour, forecastStep) } // VariableIndex returns the dataset variable index for a GRIB parameter. // Returns -1 if the parameter is not recognized. func VariableIndex(parameterCategory, parameterNumber int) int { switch { case parameterCategory == 3 && parameterNumber == 5: return VarHeight // Geopotential Height case parameterCategory == 2 && parameterNumber == 2: return VarWindU // U-component of wind case parameterCategory == 2 && parameterNumber == 3: return VarWindV // V-component of wind default: return -1 } }