158 lines
5 KiB
Go
158 lines
5 KiB
Go
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
|
|
}
|
|
}
|