Canonical NetCDF Forecast Schema v2
EarthSHAB v2.0 standardizes on a single netCDF forecast format: what the Copernicus Climate Data Store (CDS) API returns post-September 2024 for ERA5 pressure-level reanalysis. Any new reader, downloader, or converter must produce files that match this spec.
Note
Adopting the CDS post-September-2024 layout as canonical means current ERA5
downloads load with no conversion, and GFS is the only source that needs a
converter (saveNETCDF.py for live forecasts, saveNETCDF_archive.py for
historical cycles from the AWS GFS archive — both emit identical v2 output).
For converting between older/alternate forecast layouts (e.g. pre-September-2024 ERA5 or other variable/dimension naming), see the ERA5-Utils toolkit.
Reference files (the bundled SHAB14-V flight):
src/EarthSHAB/forecasts/SHAB14V_ERA5_20220822_20220823.nc (ERA5) and
src/EarthSHAB/forecasts/gfs_0p25_20220822_12.nc (GFS, migrated from v1 to v2 canonical format).
1. Global attributes
Attribute |
Required |
Value |
|---|---|---|
|
yes |
|
|
yes |
|
|
yes |
One line of provenance (timestamp + tool that wrote the file) |
|
optional |
Present on direct Copernicus downloads ( |
Warning
The reader’s format-detection uses Conventions + variable names. Any file
lacking Conventions == "CF-1.7" AND containing the old GFS variables
(ugrdprs, vgrdprs, hgtprs) is treated as a v1 file and refused with a
migration message (see migration-v2.md).
Automatic Netcdf Forecast Sourcing
Forecast.source (the field used for plot labels and evaluation grouping) is
resolved from institution:
contains
"NOAA","GFS", or"NCEP"→"GFS"contains
"ECMWF"or"ERA5"→"ERA5"missing/empty → fall back to filename pattern (basename contains
"gfs"→"GFS"; contains"era5"→"ERA5"; otherwise"unknown")
saveNETCDF.py, saveNETCDF_archive.py, and migrate_v1.py all write the appropriate institution
value. Files migrated before this convention was introduced carry an empty
institution and rely on the filename fallback.
Storage convention
Every v2 file is a bounded subset. No full-world arrays with
mask-based subsetting; the shape of u/v/z/t IS the data extent. Missing
samples inside the subset (e.g., at high pressure levels above the model top)
are represented as _FillValue / NaN and resolved by the reader’s
fill_missing_data 1-D interpolation. The reader does not perform any
mask-based outer-bounding-box detection.
This is enforced upstream: saveNETCDF.py downloads via NOAA’s GRIB-filter
subregion, saveNETCDF_archive.py crops each archive step to the configured
bounding box, migrate_v1.py strips masked padding from v1 GFS archives, and
raw Copernicus CDS ERA5 downloads are already subsets.
2. Dimensions
Four dimensions, in this exact order on every data variable:
Dimension |
Type |
Notes |
|---|---|---|
|
int64 |
UTC instants. Renamed from v1 |
|
float64 |
Isobaric levels in hPa. Renamed from v1 |
|
float64 |
Degrees north. |
|
float64 |
Degrees east. |
Dimension ORDER on data vars: (valid_time, pressure_level, latitude, longitude).
3. Coordinate variables
3.1 valid_time (1D, dim valid_time)
Attribute |
Value |
|---|---|
|
|
|
|
|
|
|
|
dtype |
|
stored order |
strictly ascending |
3.2 pressure_level (1D, dim pressure_level)
Attribute |
Value |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
dtype |
|
stored order |
DESCENDING in hPa (e.g., |
Real CDS files publish 37 standard pressure levels: [1000, 975, 950, 925, 900, 875, 850, 825, 800, 775, 750, 700, 650, 600, 550, 500, 450, 400, 350, 300, 250, 225, 200, 175, 150, 125, 100, 70, 50, 30, 20, 10, 7, 5, 3, 2, 1]. Subsets are allowed; ordering must still be descending hPa.
3.3 latitude (1D, dim latitude)
Attribute |
Value |
|---|---|
|
|
|
|
|
|
|
|
|
|
dtype |
|
stored order |
DESCENDING (e.g., |
grid spacing |
uniform; 0.25° for ERA5; 0.25° for GFS at 0p25 resolution |
3.4 longitude (1D, dim longitude)
Attribute |
Value |
|---|---|
|
|
|
|
|
|
|
|
dtype |
|
stored order |
ASCENDING (e.g., |
convention |
|
Note
GFS downloads natively use 0-360; saveNETCDF.py must convert to -180-180 before writing.
4. Scalar / auxiliary coordinates
Present on real CDS files; the canonical schema TOLERATES them but the reader treats them as informational only.
Variable |
Shape |
Purpose |
|---|---|---|
|
scalar (int64) |
Ensemble member ID; always |
|
|
ECMWF experiment version ( |
5. Data variables
Four required data variables. All have:
dtype:
float32dims:
(valid_time, pressure_level, latitude, longitude)_FillValue:NaN(float32)coordinatesattribute listing applicable scalars:"number valid_time isobaricInhPa latitude longitude expver"is the CDS form. The reader accepts any value (or missing).
Var |
|
|
|
Notes |
|---|---|---|---|---|
|
|
|
|
Eastward (positive = wind blowing TO the east) |
|
|
|
|
Northward (positive = wind blowing TO the north) |
|
|
|
|
NOT altitude in meters. Divide by |
|
|
|
|
Optional in v1 ERA5, required in v2 |
Important
z is geopotential (m²/s²), not geopotential height (m). The reader does
the /g conversion when interpolating against an altitude query.
6. Index axis summary
For a query at (t_query, alt_query_m, lat_query, lon_query) the reader:
finds nearest indices:
valid_time(ascending),latitude(descending),longitude(ascending; query in -180-180);builds the altitude column
z[t_idx, :, lat_idx, lon_idx] / g;interpolates
u,vover altitude (perwind_interpolation), then overvalid_timebetween the two enclosing time steps.
7. Differences from v1 (“processed” ERA5 format the legacy ERA5.py expected)
Item |
v1 (legacy ERA5.py) |
v2 (canonical) |
|---|---|---|
Time dim/var name |
|
|
Level dim/var name |
|
|
Time units |
varies |
|
Time calendar |
varies |
|
Pressure ordering |
ascending hPa |
descending hPa (level idx 0 = 1000 hPa) |
|
already-divided-by-g (meters) |
geopotential (m²/s²); reader divides |
|
optional |
required |
CF Conventions attr |
absent or older |
|
8. Differences from v1 GFS format (saveNETCDF.py output)
Item |
v1 GFS ( |
v2 (canonical) |
|---|---|---|
Variable names |
|
|
Dimension names |
|
|
Time encoding |
|
|
Altitude variable |
|
|
Longitude convention |
|
|
Latitude order |
ascending |
descending |
Level ( |
descending hPa |
same ( |
|
absent |
|
saveNETCDF.py (current) and saveNETCDF_archive.py (historical, AWS archive) both do all the conversion at download time; pre-v2 archived files are migrated by the migrate_v1 CLI.
See also
migration-v2.md — detecting v1 files, the converter, and rollback.
9. Reference
The canonical example files are the bundled SHAB14-V v2 forecasts:
src/EarthSHAB/forecasts/SHAB14V_ERA5_20220822_20220823.nc # ERA5
src/EarthSHAB/forecasts/gfs_0p25_20220822_12.nc # GFS (migrated)
Inspect either directly with:
import netCDF4
ds = netCDF4.Dataset('src/EarthSHAB/forecasts/SHAB14V_ERA5_20220822_20220823.nc')
print(ds)