HRDPS π ±#
BETA Requires MetPy >=1.6
This demonstrates using data from Canadaβs High Resolution Deterministic Prediction System (HRDPS).
Data Sources
|
Data source |
Archive Duration |
|---|---|---|
|
Last 24 hours |
Model Initialization
Model cyles every hour.
Forecast Hour
For the most recent version of HRRRβ¦
|
Forecast lead time |
|---|---|
|
hourly forecasts available |
Products
|
Product Description |
|---|---|
|
Continental domain |
Variable and Level
You will need to specify the variable and level for each request.
NOTE: The organization of these files is different than other NWP products.
There are no index files provided.
Each GRIB2 file only contains one message. The variable name and level is in the fileβs name.
Herbie requires you provide a keyword argument for both
variableandlevel. Pay special attention to model description (linked above) to understand how the model data is organized. If you donβt provide input forvariableorlevel, Herbie will give you some ideas. For example,variable=TMPandlevel=AGL-2mwill give you the filename that containsTMP_AGL-2m
Note: This requires MetPy version 1.6 or greater which has the capability to parse the rotated latitude longitude map projection type (see MetPy/#3123).
[ ]:
from herbie import Herbie
import xarray as xr
import numpy as np
import matplotlib.pyplot as plt
from herbie.toolbox import EasyMap, pc
import cartopy.crs as ccrs
import cartopy.feature as feature
import pandas as pd
recent = pd.Timestamp("now").floor("6h") - pd.Timedelta("6h")
[2]:
# Some examples
H = Herbie(
recent, # Datetime
model="hrdps",
fxx=32,
product="continental",
variable="TMP",
level="AGL-2m",
)
H.grib
β
Found β model=hrdps β product=continental β 2026-Apr-15 06:00 UTC F32 β GRIB2 @ msc β IDX @ None
[2]:
'https://dd.weather.gc.ca/20260415/WXO-DD/model_hrdps/continental/2.5km/06/032/20260415T06Z_MSC_HRDPS_TMP_AGL-2m_RLatLon0.0225_PT032H.grib2'
[3]:
H = Herbie(
recent, # Datetime
model="hrdps",
fxx=12,
product="continental",
variable="HGT",
level="ISBL_0500",
)
H.grib
β
Found β model=hrdps β product=continental β 2026-Apr-15 06:00 UTC F12 β GRIB2 @ msc β IDX @ None
[3]:
'https://dd.weather.gc.ca/20260415/WXO-DD/model_hrdps/continental/2.5km/06/012/20260415T06Z_MSC_HRDPS_HGT_ISBL_0500_RLatLon0.0225_PT012H.grib2'
Get the 2-metre temperature#
[4]:
H = Herbie(
recent,
model="hrdps",
fxx=0,
product="continental",
variable="TMP",
level="AGL-2m",
)
ds = H.xarray()
ds
β
Found β model=hrdps β product=continental β 2026-Apr-15 06:00 UTC F00 β GRIB2 @ msc β IDX @ None
π¨π»βπ Created directory: [/home/meteo/kps5442/data/hrdps/20260415]
/home/kps5442/mapwall_dev/herbie-dev/src/herbie/core.py:1306: UserWarning: Will not remove GRIB file because Herbie will only remove subsetted files (not full files).
warnings.warn(
[4]:
<xarray.Dataset> Size: 66MB
Dimensions: (y: 1290, x: 2540)
Coordinates:
latitude (y, x) float64 26MB ...
longitude (y, x) float64 26MB ...
time datetime64[ns] 8B 2026-04-15T06:00:00
step timedelta64[ns] 8B 00:00:00
heightAboveGround float64 8B 2.0
valid_time datetime64[ns] 8B ...
gribfile_projection object 8B None
Dimensions without coordinates: y, x
Data variables:
t2m (y, x) float32 13MB ...
Attributes:
GRIB_edition: 2
GRIB_centre: cwao
GRIB_centreDescription: Canadian Meteorological Service - Montreal
GRIB_subCentre: 0
Conventions: CF-1.7
institution: Canadian Meteorological Service - Montreal
model: hrdps
product: continental
description: Canada's High Resolution Deterministic Predictio...
remote_grib: /home/meteo/kps5442/data/hrdps/20260415/20260415...
local_grib: /home/meteo/kps5442/data/hrdps/20260415/20260415...
search: None[5]:
ds.valid_time.dt.strftime("%Y-%m-%d %H:%M").item()
[5]:
'2026-04-15 06:00'
Plot data on Plate Carree projection#
[6]:
ax = EasyMap("50m").BORDERS().STATES(alpha=0.5).ax
p = ax.pcolormesh(ds.longitude, ds.latitude, ds.t2m, transform=pc, cmap="Spectral_r")
plt.colorbar(p, ax=ax, orientation="horizontal", pad=0.01, shrink=0.8)
ax.set_title(
f"2-m Temperature\nValid {ds.valid_time.dt.strftime('%Y-%m-%d %H:%M').item()} UTC",
loc="right",
fontsize=10,
)
ax.set_title(f"{ds.model.upper()}: {H.product_description}", loc="left")
ax.gridlines()
[6]:
<cartopy.mpl.gridliner.Gridliner at 0x783b9b0c4980>
Plot data on model grid projection - rotated latitude longitude#
Requires Metpy >=1.6.0
[7]:
ax = EasyMap("50m", crs=ds.herbie.crs).BORDERS().STATES(alpha=0.5).ax
p = ax.pcolormesh(ds.longitude, ds.latitude, ds.t2m, transform=pc, cmap="Spectral_r")
plt.colorbar(p, ax=ax, orientation="horizontal", pad=0.01, shrink=0.8)
ax.set_title(
f"2-m Temperature\nValid {ds.valid_time.dt.strftime('%Y-%m-%d %H:%M').item()} UTC",
loc="right",
fontsize=10,
)
ax.set_title(f"{ds.model.upper()}: {H.product_description}", loc="left")
ax.gridlines()
[7]:
<cartopy.mpl.gridliner.Gridliner at 0x783b9b7363c0>
Get 10-m U and 10-m V wind#
[ ]:
# loading more than one variable requires a loop, because the
# data is stored in multiple files (and a Herbie object only
# represents a single file).
store = []
for var, lev in zip(["UGRD", "VGRD"], ["AGL-10m", "AGL-10m"]):
_ds = Herbie(
recent,
model="hrdps",
fxx=0,
product="continental",
variable=var,
level=lev,
).xarray()
store.append(_ds)
ds = xr.merge(store)
ds
β
Found β model=hrdps β product=continental/2.5km β 2023-Dec-29 00:00 UTC F00 β GRIB2 @ msc β IDX @ None
/home/blaylock/GITHUB/Herbie/herbie/core.py:1065: UserWarning: Will not remove GRIB file because Herbie will only remove subsetted files (not full files).
warnings.warn(
β
Found β model=hrdps β product=continental/2.5km β 2023-Dec-29 00:00 UTC F00 β GRIB2 @ msc β IDX @ None
/home/blaylock/GITHUB/Herbie/herbie/core.py:1065: UserWarning: Will not remove GRIB file because Herbie will only remove subsetted files (not full files).
warnings.warn(
<xarray.Dataset>
Dimensions: (y: 1290, x: 2540)
Coordinates:
time datetime64[ns] 2023-12-29
step timedelta64[ns] 00:00:00
heightAboveGround float64 10.0
latitude (y, x) float64 39.63 39.63 39.64 ... 47.91 47.89 47.88
longitude (y, x) float64 -133.6 -133.6 -133.6 ... -40.73 -40.71
valid_time datetime64[ns] 2023-12-29
Dimensions without coordinates: y, x
Data variables:
u10 (y, x) float32 ...
gribfile_projection object None
v10 (y, x) float32 ...
Attributes:
GRIB_edition: 2
GRIB_centre: cwao
GRIB_centreDescription: Canadian Meteorological Service - Montreal
GRIB_subCentre: 0
Conventions: CF-1.7
institution: Canadian Meteorological Service - Montreal
model: hrdps
product: continental/2.5km
description: Canada's High Resolution Deterministic Predictio...
remote_grib: /home/blaylock/data/hrdps/20231229/20231229T00Z_...
local_grib: /home/blaylock/data/hrdps/20231229/20231229T00Z_...
search: None[9]:
# MetPy version >= 1.6 is required to parse the map projection
ds.herbie.crs
[9]:
<cartopy.crs.RotatedPole object at 0x7f9e3bbc9010>
[10]:
ax = (
EasyMap("50m", crs=ds.herbie.crs, figsize=8, linewidth=1, theme="dark")
.BORDERS()
.STATES(alpha=0.5)
.ax
)
p = ax.pcolormesh(
ds.longitude,
ds.latitude,
np.hypot(ds.u10, ds.v10), # Wind Speed
transform=pc,
)
plt.colorbar(
p, ax=ax, orientation="horizontal", pad=0.01, shrink=0.8, label="Wind speed (m/s)"
)
ax.set_title(
f"10-m Wind Speed\nValid {ds.valid_time.dt.strftime('%Y-%m-%d %H:%M').item()} UTC",
loc="center",
fontsize=10,
)
ax.set_title(f"{ds.model.upper()}", loc="left")
ax.EasyMap.INSET_GLOBE()
[10]:
<GeoAxes: >
500 hPa Humidity and Geopotential Height#
[ ]:
# loading more than one variable requires a loop, because the
# data is stored in multiple files (and a Herbie object only
# represents a single file).
store = []
for var, lev in zip(["HGT", "RH"], ["ISBL_0500", "ISBL_0500"]):
_ds = Herbie(
recent,
model="hrdps",
fxx=0,
product="continental",
variable=var,
level=lev,
).xarray()
store.append(_ds)
ds = xr.merge(store)
ds
β
Found β model=hrdps β product=continental/2.5km β 2023-Dec-29 00:00 UTC F00 β GRIB2 @ msc β IDX @ None
/home/blaylock/GITHUB/Herbie/herbie/core.py:1065: UserWarning: Will not remove GRIB file because Herbie will only remove subsetted files (not full files).
warnings.warn(
β
Found β model=hrdps β product=continental/2.5km β 2023-Dec-29 00:00 UTC F00 β GRIB2 @ msc β IDX @ None
/home/blaylock/GITHUB/Herbie/herbie/core.py:1065: UserWarning: Will not remove GRIB file because Herbie will only remove subsetted files (not full files).
warnings.warn(
<xarray.Dataset>
Dimensions: (y: 1290, x: 2540)
Coordinates:
time datetime64[ns] 2023-12-29
step timedelta64[ns] 00:00:00
isobaricInhPa float64 500.0
latitude (y, x) float64 39.63 39.63 39.64 ... 47.91 47.89 47.88
longitude (y, x) float64 -133.6 -133.6 -133.6 ... -40.73 -40.71
valid_time datetime64[ns] 2023-12-29
Dimensions without coordinates: y, x
Data variables:
gh (y, x) float32 ...
gribfile_projection object None
r (y, x) float32 ...
Attributes:
GRIB_edition: 2
GRIB_centre: cwao
GRIB_centreDescription: Canadian Meteorological Service - Montreal
GRIB_subCentre: 0
Conventions: CF-1.7
institution: Canadian Meteorological Service - Montreal
model: hrdps
product: continental/2.5km
description: Canada's High Resolution Deterministic Predictio...
remote_grib: /home/blaylock/data/hrdps/20231229/20231229T00Z_...
local_grib: /home/blaylock/data/hrdps/20231229/20231229T00Z_...
search: None[12]:
ax = (
EasyMap("50m", crs=ds.herbie.crs, figsize=8, linewidth=1, theme="dark")
.BORDERS()
.STATES(alpha=0.5)
.ax
)
# Draw Relative Humidity
p = ax.pcolormesh(
ds.longitude, ds.latitude, ds.r, transform=pc, cmap="BrBG", vmin=0, vmax=100
)
plt.colorbar(
p,
ax=ax,
orientation="horizontal",
pad=0.01,
shrink=0.8,
label="Relative Humidity (%)",
)
# Draw Geopential Height Contours
ax.contour(
ds.longitude,
ds.latitude,
ds.gh,
colors="k",
transform=pc,
levels=range(0, 6000, 40),
)
ax.set_title(
f"500 hPa RH and Geopotential height\nValid {ds.valid_time.dt.strftime('%Y-%m-%d %H:%M').item()} UTC",
loc="center",
fontsize=10,
)
ax.set_title(f"{ds.model.upper()}", loc="left")
[12]:
Text(0.0, 1.0, 'HRDPS')
HRDPS North domain (experimental)#
Not available as of testing in Apr 2026
[8]:
H = Herbie(
recent.floor("12h"), # only run every 00 and 12 UTC
model="hrdps",
fxx=0,
product="north",
variable="TMP",
level="AGL-2m",
)
ds = H.xarray()
ds
/home/kps5442/mapwall_dev/herbie-dev/src/herbie/core.py:1306: UserWarning: Will not remove GRIB file because Herbie will only remove subsetted files (not full files).
warnings.warn(
𦨠GRIB2 file not found: self.model='hrdps' self.date=Timestamp('2026-04-15 00:00:00') self.fxx=0
π Did not find β model=hrdps β product=north β 2026-Apr-15 00:00 UTC F00
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
Cell In[8], line 9
5 product="north",
6 variable="TMP",
7 level="AGL-2m",
8 )
----> 9 ds = H.xarray()
10 ds
File ~/mapwall_dev/herbie-dev/src/herbie/core.py:1338, in Herbie.xarray(self, search, searchString, backend_kwargs, remove_grib, _use_pygrib_for_crs, **download_kwargs)
1334 backend_kwargs.setdefault("errors", "raise")
1336 # Use cfgrib.open_datasets, just in case there are multiple "hypercubes"
1337 # for what we requested.
-> 1338 Hxr = cfgrib.open_datasets(
1339 local_file,
1340 backend_kwargs=backend_kwargs,
1341 decode_timedelta=True,
1342 )
1344 for ds in Hxr:
1345 # Need model attribute before using get_cf_crs
1346 ds.attrs["model"] = str(self.model)
File /home/meteo/kps5442/miniconda3/envs/herbie-dev-local/lib/python3.14/site-packages/cfgrib/xarray_store.py:119, in open_datasets(path, backend_kwargs, **kwargs)
117 backend_kwargs = backend_kwargs.copy()
118 backend_kwargs["squeeze"] = False
--> 119 datasets = open_variable_datasets(path, backend_kwargs=backend_kwargs, **kwargs)
121 type_of_level_datasets = {} # type: T.Dict[str, T.List[xr.Dataset]]
122 for ds in datasets:
File /home/meteo/kps5442/miniconda3/envs/herbie-dev-local/lib/python3.14/site-packages/cfgrib/xarray_store.py:101, in open_variable_datasets(path, backend_kwargs, **kwargs)
99 errors = backend_kwargs.get("errors", "warn")
100 stream = messages.FileStream(path, errors=errors)
--> 101 index = open_fileindex(stream, computed_keys=cfmessage.COMPUTED_KEYS, **fileindex_kwargs)
102 datasets = [] # type: T.List[xr.Dataset]
103 for param_id in sorted(index["paramId"]):
File /home/meteo/kps5442/miniconda3/envs/herbie-dev-local/lib/python3.14/site-packages/cfgrib/dataset.py:807, in open_fileindex(stream, indexpath, index_keys, ignore_keys, filter_by_keys, computed_keys)
805 index_keys = sorted(set(index_keys) | set(filter_by_keys))
806 index_keys = [key for key in index_keys if key not in ignore_keys]
--> 807 index = messages.FileIndex.from_indexpath_or_filestream(
808 stream, index_keys, indexpath=indexpath, computed_keys=computed_keys
809 )
810 return index.subindex(filter_by_keys)
File /home/meteo/kps5442/miniconda3/envs/herbie-dev-local/lib/python3.14/site-packages/cfgrib/messages.py:533, in FileIndex.from_indexpath_or_filestream(cls, filestream, index_keys, indexpath, computed_keys, log)
525 @classmethod
526 def from_indexpath_or_filestream(
527 cls, filestream, index_keys, indexpath=DEFAULT_INDEXPATH, computed_keys={}, log=LOG
(...) 530
531 # Reading and writing the index can be explicitly suppressed by passing indexpath==''.
532 if not indexpath:
--> 533 return cls.from_fieldset(filestream, index_keys, computed_keys)
535 hash = hashlib.md5(repr(index_keys).encode("utf-8")).hexdigest()
536 indexpath = indexpath.format(path=filestream.path, hash=hash, short_hash=hash[:5])
File /home/meteo/kps5442/miniconda3/envs/herbie-dev-local/lib/python3.14/site-packages/cfgrib/messages.py:379, in FieldsetIndex.from_fieldset(cls, fieldset, index_keys, computed_keys)
377 else:
378 iteritems = enumerate(fieldset)
--> 379 return cls.from_fieldset_and_iteritems(fieldset, iteritems, index_keys, computed_keys)
File /home/meteo/kps5442/miniconda3/envs/herbie-dev-local/lib/python3.14/site-packages/cfgrib/messages.py:392, in FieldsetIndex.from_fieldset_and_iteritems(cls, fieldset, iteritems, index_keys, computed_keys)
390 index_keys = list(index_keys)
391 header_values_cache = {} # type: T.Dict[T.Tuple[T.Any, type], T.Any]
--> 392 for field_id, raw_field in iteritems:
393 field = ComputedKeysAdapter(raw_field, computed_keys)
394 header_values = []
File /home/meteo/kps5442/miniconda3/envs/herbie-dev-local/lib/python3.14/site-packages/cfgrib/messages.py:292, in FileStreamItems.__iter__(self)
290 old_offset = -1
291 count = 0
--> 292 for message in self.itervalues():
293 offset = message.message_get("offset", int)
294 if offset == old_offset:
File /home/meteo/kps5442/miniconda3/envs/herbie-dev-local/lib/python3.14/site-packages/cfgrib/messages.py:268, in FileStreamItems.itervalues(self)
266 def itervalues(self) -> T.Iterator[Message]:
267 errors = self.filestream.errors
--> 268 with open(self.filestream.path, "rb") as file:
269 # enable MULTI-FIELD support on sequential reads (like when building the index)
270 with multi_enabled(file):
271 valid_message_found = False
FileNotFoundError: [Errno 2] No such file or directory: '/home/meteo/kps5442/data/hrdps/20260415/20260415T00Z_MSC_HRDPS-North_TMP_AGL-2m_RLatLon0.03_PT000H.grib2'
[14]:
# This domain is run on a polar stereographic projection
ds.herbie.crs
[14]:
<cartopy.crs.Stereographic object at 0x7f9e3bbca060>
[15]:
ax = EasyMap("50m", crs=ds.herbie.crs).BORDERS().STATES(alpha=0.5).ax
p = ax.pcolormesh(
ds.longitude, ds.latitude, ds.t2m - 273.15, transform=pc, cmap="Spectral_r"
)
plt.colorbar(
p,
ax=ax,
orientation="horizontal",
pad=0.01,
shrink=0.8,
label="2-m Temperature (C)",
)
ax.set_title(f"{ds.model.upper()}", loc="left")
ax.set_title(
f"2-m Temperature\nValid {ds.valid_time.dt.strftime('%Y-%m-%d %H:%M').item()} UTC",
loc="center",
fontsize=10,
)
ax.gridlines()
ax.EasyMap.INSET_GLOBE()
[15]:
<GeoAxes: >
[ ]: