NWP | Herbie | Download NWP model output (grib2) | MPAS | WRF

Herbie

Herbie is a python package that downloads recent and archived numerical weather prediction (NWP) model output from different cloud archive sources. NWP data is distributed in GRIB2 format which Herbie reads using xarray+cfgrib. Herbie also provides some extra features to help visualize and extract data.

Herbie helps you discover, download, and read data from:

  • High Resolution Rapid Refresh (HRRR) | HRRR-Alaska
  • Rapid Refresh (RAP)
  • Global Forecast System (GFS)
  • Global Ensemble Forecast System (GEFS)
  • ECMWF Open Data Forecasts (IFS and AIFS)
  • Navy Global Environmental Model (NAVGEM)
  • North American Mesoscale Model (NAM)
  • National Blend of Models (NBM)
  • Rapid Refresh Forecast System (RRFS) prototype
  • Real-Time/Un-Restricted Mesoscale Analysis (RTMA/URMA)
  • Hurricane Analysis And Forecast System (HAFS)
  • High Resolution Deterministic Prediction System (HRDPS)
  • Climate Forecast System (CFS)

and more! Check out the gallery.

Installation

1
conda install -c conda-forge herbie-data

Capabilities

  • Search for model output from different data sources.
  • Download full GRIB2 files.
  • Download subset GRIB2 files (by grib field).
  • Read data with xarray.
  • Read index file with Pandas.
  • Extra features (herbie xarray accessors)
    • Extract data at a point
    • Get Cartopy coordinate references system
    • Plot data with Cartopy (very early development).

Examples

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from herbie import Herbie

# Herbie object for the HRRR model 6-hr surface forecast product
H = Herbie(
'2021-01-01 12:00',
model='hrrr',
product='sfc',
fxx=6
)

# Look at file contents
H.inventory()

# Download the full GRIB2 file
H.download()

# Download a subset, like all fields at 500 mb
H.download(":500 mb")

# Read subset with xarray, like 2-m temperature.
H.xarray("TMP:2 m")

IFS/AIFS

IFS data is only available at 0.4 degree prior to February 1,2024. After that date, the IFS is available at 0.25 degreeresolution.

ECMWF provides data for two different models

  • model="ifs" ECMWF Integrated Forecast System
  • model="aifs" ECMWF Artificial Intelligence Integrated Forecast System
prioriy Data source Archive Duration
"ecmwf" ECMWF Open Data Server last 4 days
"azure" Microsoft Azure 2022-01-21 to present
"aws" Amazon Web Services 2023-01-18 to present
product= Product Description Available model runs
"oper" operational high-resolution forecast, atmospheric fields 00z, 12z,
"wave" wave forecasts 00z, 12z,
"scda" short cut-off high-resolution forecast, atmospheric fields (also known a high-frequency products)”, 06z, 18z
"scwv" short cut-off high-resolution forecast, ocean wave fields (also known a high-frequency products)”, 06z, 18z
"enfo" ensemble forecast, atmospheric fields 00z, 06z, 12z, 18z
"waef" ensemble forecast, ocean wave fields, 00z, 06z, 12z, 18z
"mmsf" multi-model seasonal forecasts fields from the ECMWF model only. ?
  • ./download_grib.sh 2024120400 3 384 1>log.04 2>&1 &
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!/bin/bash

set -o pipefail
export SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

source /home/wpsze/micromamba/etc/profile.d/micromamba.sh
micromamba activate herbie-data

export HERBIE_SAVE_DIR="/home/wpsze/cpas/IFS/herbie/"

# Check if all required arguments are provided
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then
echo "Usage: $0 <yyyymmddhh> <time_interval> <hour_periods>"
echo "Example: $0 2025101000 6 48"
echo ""
echo " <yyyymmddhh> : IFS run date and hour (e.g., 2025101000)"
echo " <time_interval>: Interval between fxx hours (e.g., 6 for 0,6,12...)"
echo " <hour_periods> : Maximum forecast hour (e.g., 48)"
exit 1
fi

DATETIME_INPUT=$1
TIME_INTERVAL=$2 # e.g., 6
HOUR_PERIODS=$3 # e.g., 48
yyyymm=${DATETIME_INPUT:0:6}
yyyymmdd=${DATETIME_INPUT:0:8}

# --- Configuration ---
PYTHON_SCRIPT="download_ifs_herbie.py"

# Generate the comma-separated list of forecast hours (FXX_RANGE)
# 'seq' generates the sequence: start at 0, step by TIME_INTERVAL, up to HOUR_PERIODS.
# '-s ,' specifies the comma separator.
FXX_RANGE=$(seq -s ',' 0 "${TIME_INTERVAL}" "${HOUR_PERIODS}")

echo "Running IFS GRIB Download..."
echo "Requested Run Time (yyyymmddhh): ${DATETIME_INPUT}"
echo "Time Interval: ${TIME_INTERVAL} hours"
echo "Max Period: ${HOUR_PERIODS} hours"
echo "Generated Forecast Periods (fxx): ${FXX_RANGE}"
echo "--------------------------------------------------------"

# Execute the Python script, passing the datetime and generated fxx range
# Ensure you have 'herbie' and 'python3' installed in your environment.
python3 "${PYTHON_SCRIPT}" --datetime "${DATETIME_INPUT}" --fxx "${FXX_RANGE}"

# Check the exit status of the python script
if [ $? -eq 0 ]; then
echo "--------------------------------------------------------"
echo "Download process completed successfully."
else
echo "--------------------------------------------------------"
echo "Download process finished, but some errors occurred (check logs above)."
fi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/bin/python
# -*- coding: utf-8 -*-

import argparse
import sys
from herbie import Herbie
from datetime import datetime

def download_ifs_data(datetime_str: str, fxx_periods: str):
"""
Downloads IFS GRIB files using Herbie for a specified run time and list of forecast hours.

Args:
datetime_str (str): Run datetime in YYYYMMDDHH format (e.g., '2025101000').
fxx_periods (str): Comma-separated string of forecast hours (fxx) (e.g., '0,6,12,24').
"""
if len(datetime_str) != 10:
print(f"Error: Datetime must be in YYYYMMDDHH format (10 characters). Received: {datetime_str}", file=sys.stderr)
sys.exit(1)

try:
# Convert YYYYMMDDHH to the required Herbie format (YYYY-MM-DD HH:00:00)
run_date_str = f"{datetime_str[:4]}-{datetime_str[4:6]}-{datetime_str[6:8]} {datetime_str[8:10]}:00:00"

# Parse the comma-separated fxx periods into a list of integers
fxx_list = [int(fxx.strip()) for fxx in fxx_periods.split(',')]

except ValueError as e:
print(f"Error parsing input: {e}", file=sys.stderr)
sys.exit(1)

print(f"--- Starting Download Process ---")
print(f"IFS Run Time (Init): {run_date_str}")
print(f"Forecast Hours (fxx): {fxx_list}")
print("---------------------------------")

# Herbie Setup: model="ifs", product="oper"
# We create a Herbie object for each fxx period to ensure the correct valid time is queried.

for fxx in fxx_list:
print(f"\n[INFO] Attempting download for fxx={fxx}...")

try:
# Initialize Herbie for the specific run and forecast hour
H = Herbie(
run_date_str,
model="ifs",
product="oper",
fxx=fxx,
# Example of specifying a search string for faster downloads
# If left unspecified, it will download all GRIB messages found
# searchString=':(U|V|T|Z|G)GRD:10 m above ground|500 hPa:',
)

# --- Optional: Inventory Check ---
# print(f"[DEBUG] Inventory for fxx={fxx}:")
# print(H.inventory())

# --- Download ---
H.download()
print(f"[SUCCESS] Download complete for fxx={fxx}.")

except Exception as e:
print(f"[ERROR] Failed to download fxx={fxx}. Reason: {e}", file=sys.stderr)
# Continue to the next forecast hour if one fails
continue


if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Download IFS GRIB files using Herbie, iterating over forecast periods."
)
parser.add_argument(
"--datetime",
type=str,
required=True,
help="Run datetime in YYYYMMDDHH format (e.g., 2025101000)."
)
parser.add_argument(
"--fxx",
type=str,
default="0,6,12,18,24,36",
help="Comma-separated list of forecast hours (fxx) to download (e.g., 0,6,12,24). Default is 0,6,12,18,24,36."
)

args = parser.parse_args()
download_ifs_data(args.datetime, args.fxx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash
for filename in "herbie/ifs"/*/*"oper-fc.grib2"; do
yyyymm="${filename:11:6}"
yyyymmdd="${filename:11:8}"
hh="${filename:28:2}"
echo ${filename}, ${yyyymm}, ${yyyymmdd}, ${hh}
mkdir -p /home/wpsze/IFS/${yyyymm}/${yyyymmdd}/
ln -s /home/wpsze/IFS/${filename} /home/wpsze/IFS/${yyyymm}/${yyyymmdd}/
done

for filename in "herbie/ifs"/*/*"scda-fc.grib2"; do
yyyymm="${filename:11:6}"
yyyymmdd="${filename:11:8}"
hh="${filename:28:2}"
echo ${filename}, ${yyyymm}, ${yyyymmdd}, ${hh}
mkdir -p /home/wpsze/IFS/${yyyymm}/${yyyymmdd}/
ln -s /home/wpsze/IFS/${filename} /home/wpsze/IFS/${yyyymm}/${yyyymmdd}/${yyyymmdd}${hh}0000-0h-oper-fc.grib2
done

#-- search all symlink and delete if need
# find ./20* -type l -ls
# find ./20* -type l -ls -delete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
Use regular expression to search for lines in the index file.
Here are some examples you can use for the ecCodes-style `search`

Look at the ECMWF GRIB Parameter Database
https://apps.ecmwf.int/codes/grib/param-db


product=`oper` or `enfo`
======================== ==============================================
search= GRIB messages that will be downloaded
======================== ==============================================
":2t:" 2-m temperature
":2d:" 2-m dew point temperature
":10u:" 10-m u wind vector
":10v:" 10-m v wind vector
":10[uv]: 10-m u and 10-m v wind
":[tuvr]:" Temp, u/v wind, RH (all levels)
":500:" All variables on the 500 hPa level
":gh:500" Geopotential height only at 500 hPa
":gh:" Geopotential height (all pressure levels)
":t:" Temperature (all pressure levels)
":q:" Specific Humidity (all pressure levels)
":r:" Relative humidity (all pressure levels)
":v:" v wind vector (all pressure levels)
":u:" u wind vector (all pressure levels)
":w:" Vertical velocity (Pascals per second)
":lsm:" Land-sea mask
":ttr:" Top net long-wave (thermal) radiation
":ssrd:" Surface short-wave (solar) radiation downwards
":ssr:" Surface net short-wave (solar) radiation
":strd:" Surface long-wave (thermal) radiation downwards
":str:" Surface net long-wave (thermal) radiation
":swvl1:" Volumetric soil water layer 1 (depth 0 meters)
":swvl2:" Volumetric soil water layer 2 (depth 7 meters)
":swvl3:" Volumetric soil water layer 3 (depth 28 meters)
":swvl4:" Volumetric soil water layer 4 (depth 100 meters)
":skt:" Skin (surface) temperature
":d:" Divergence (all levels)
":st:" Soil temperature
":stl2:" Soil temperature level 2 (depth 7 meters)
":tp:" Total precipitation
":ro:" Run-off
":asn:" Snow albedo
":msl:" Mean sea level pressure
":sp:" Surface pressure
":cape:" CAPE
":tcwv:" Total column vertically integrated water vapor
":vo:" Relative vorticity

product=`wave` or `waef`
======================== ==============================================
search= GRIB messages that will be downloaded
======================== ==============================================
":swh:" Significant height of wind waves + swell
":mwp:" Mean wave period
":mwd:" Mean wave direction
":pp1d:" Peak wave period
":mp2:" Mean zero-crossing wave period

If you need help with regular expression, search the web or look at
this cheatsheet: https://www.petefreitag.com/cheatsheets/regex/.

Here is an example: https://regex101.com/r/niNjwp/1

Problem

  • The grib files from ecmwf.ini and from aws are different. It will make different/debug on the ungrib step and init_atmosphere_model step.
    • Not all different.

It is because,

IFS:2024-04-26_00.nc IFS:2025-03-26_00.nc

GFS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from herbie import Herbie
from herbie.toolbox import EasyMap, pc, ccrs
from herbie import paint

import matplotlib.pyplot as plt

H = Herbie("2021-07-11", model="gfs", product="pgrb2.0p25")

# Show all available sources
H.SOURCES

# Show all available products
H.PRODUCTS

# Download the full GRIB2 file
H.download()

# Plot t2m
ds = H.xarray(":TMP:2 m above")

ax = EasyMap(crs=ds.herbie.crs, figsize=[10, 8]).ax
p = ax.pcolormesh(
ds.longitude,
ds.latitude,
ds.t2m - 273.15,
transform=pc,
**paint.NWSTemperature.kwargs2,
)
plt.colorbar(
p, ax=ax, orientation="horizontal", pad=0.05, **paint.NWSTemperature.cbar_kwargs2
)

ax.set_title(ds.t2m.GRIB_name, loc="right")
ax.set_title(f"{ds.model.upper()}: {H.product_description}", loc="left")

GFS wave data

1
2
3
4
5
6
7
8
9
from herbie import Herbie
from herbie.toolbox import EasyMap, pc, ccrs
from herbie import paint

import matplotlib.pyplot as plt

H = Herbie("2021-07-11", model="gfs_wave")

H.inventory()

GEFS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
from herbie import Herbie
from herbie.toolbox import EasyMap, pc
from herbie import paint

import matplotlib.pyplot as plt

H = Herbie(
"2023-01-04 12:00",
model="gefs",
product="atmos.5",
member="mean",
)

H

# Show all the available products (from the model template file)
H.PRODUCTS

df = H.inventory()
df

df.variable.unique()

H.inventory("HGT")

ds = H.xarray("PWAT")
ds

ds2 = H.xarray("HGT:500 mb")
ds2

ax = EasyMap("50m", crs=ds.herbie.crs, figsize=[10, 10]).STATES().BORDERS().ax
p = ax.pcolormesh(
ds.longitude,
ds.latitude,
ds.pwat,
transform=pc,
cmap=paint.NWSPrecipitation.cmap,
vmin=0,
vmax=80,
)
plt.colorbar(
p,
ax=ax,
orientation="horizontal",
pad=0.01,
shrink=0.8,
label=f"{ds.pwat.GRIB_name}({ds.pwat.GRIB_units})",
)

# Add 500 hPa Geopotential height
ax.contour(
ds2.longitude,
ds2.latitude,
ds2.gh,
colors="k",
linewidths=0.5,
levels=range(0, 10000, 120),
)

ax.set_title(
f"{ds.model.upper()} ensemble mean\nValid: {ds.valid_time.dt.strftime('%H:%M UTC %d %b %Y').item()}",
loc="left",
)
ax.set_title(ds.pwat.GRIB_name, loc="right")

# Get a specific member
H5 = Herbie(
"2022-01-01",
model="gefs",
product="atmos.5",
member=5,
)
H5.xarray("TMP:2 m")

## If a user wants all the members in
## a single Dataframe, I'll let the user
## concat it themselves.

NWP | Herbie | Download NWP model output (grib2) | MPAS | WRF
https://waipangsze.github.io/2025/04/10/NWP-Herbie-Download-NWP-model-output-MPAS-WRF/
Author
wpsze
Posted on
April 10, 2025
Updated on
October 20, 2025
Licensed under