HRRR Precipitation Forecast Download¶
This notebook demonstrates how to download and process HRRR (High-Resolution Rapid Refresh) precipitation forecast data using the PrecipHrrr class in ras-commander.
HRRR is NOAA's operational numerical weather prediction model with: - 3km horizontal resolution over CONUS - Hourly forecast cycles (00z through 23z) - 18-hour forecast horizon for standard cycles - 48-hour forecast horizon for extended cycles (00z, 06z, 12z, 18z) - Data source: NOAA NOMADS server (publicly available, no account required)
HRRR is particularly valuable for: - Short-range flood forecasting - Real-time operational HEC-RAS model forcing - Ensemble precipitation inputs for hydraulic models
Setup and Imports¶
# =============================================================================
# DEVELOPMENT MODE TOGGLE
# =============================================================================
# Set USE_LOCAL_SOURCE based on your setup:
# True = Use local source code (for developers editing ras-commander)
# False = Use pip-installed package (for users)
# =============================================================================
USE_LOCAL_SOURCE = True # <-- TOGGLE THIS
# -----------------------------------------------------------------------------
if USE_LOCAL_SOURCE:
import sys
from pathlib import Path
local_path = str(Path.cwd().parent) # Parent of examples/ = repo root
if local_path not in sys.path:
sys.path.insert(0, local_path) # Insert at position 0 = highest priority
print(f"LOCAL SOURCE MODE: Loading from {local_path}/ras_commander")
else:
print("PIP PACKAGE MODE: Loading installed ras-commander")
# Verify which version loaded
import ras_commander
print(f"Loaded: {ras_commander.__file__}")
LOCAL SOURCE MODE: Loading from <workspace>/ras_commander
Loaded: <workspace>\ras_commander\__init__.py
from pathlib import Path
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from ras_commander.precip import PrecipHrrr
print("Imports complete.")
Imports complete.
What You'll Learn¶
- How to query HRRR data availability on NOAA NOMADS
- How to download the latest HRRR forecast cycle automatically
- How to download a specific date and cycle
- How to extract precipitation data from GRIB2 files to xarray
- How to compute basin-average precipitation time series
- HRRR cycle timing and forecast horizon reference
HRRR Overview¶
The High-Resolution Rapid Refresh (HRRR) model runs every hour at 3km resolution. Understanding cycle timing and data latency is important for operational use:
| Cycle | Type | Forecast Horizon | Typical Availability |
|---|---|---|---|
| 00z, 06z, 12z, 18z | Extended | 48 hours | ~2 hours after cycle |
| All other hours | Standard | 18 hours | ~2 hours after cycle |
File format: GRIB2 (.grib2)
Key precipitation variable: APCP (Accumulated Precipitation, kg/m²)
Data server: NOAA NOMADS
Retention: NOMADS retains approximately 48 hours of recent HRRR output. For archived HRRR data, use the NOAA HRRR archive on AWS.
# Get HRRR system information
info = PrecipHrrr.get_info()
print("HRRR System Information:")
print(f" Data source: {info.get('base_url', 'NOAA NOMADS')}")
print(f" Resolution: {info.get('spatial_resolution', '3km')}")
print(f" Standard forecast: {info.get('forecast_horizon_standard', '18 hours')}")
print(f" Extended forecast: {info.get('forecast_horizon_extended', '48 hours')}")
print(f" Update frequency: {info.get('update_frequency', 'Hourly')}")
print(f" CONUS bounds: {info.get('bounds', 'Full CONUS')}")
HRRR System Information:
Data source: https://nomads.ncep.noaa.gov/pub/data/nccf/com/hrrr/prod
Resolution: 3 km
Standard forecast: 18 hours
Extended forecast: 48 hours (00z, 06z, 12z, 18z cycles)
Update frequency: Hourly (every cycle)
CONUS bounds: (-134.1, 21.1, -60.9, 52.6)
Example 1: Single Forecast Cycle¶
In this example we use get_latest_forecast() to automatically find and download the most recent available HRRR cycle. This is the recommended approach for operational workflows.
Check Availability¶
Before downloading, verify that the desired HRRR cycle is available on NOMADS. This is useful for operational monitoring and retry logic.
# Check if the latest HRRR cycle is available
try:
available = PrecipHrrr.check_availability()
print(f"Latest HRRR cycle available: {available}")
# Check a specific date/cycle
yesterday = datetime.now() - timedelta(days=1)
available_12z = PrecipHrrr.check_availability(
date=yesterday.strftime("%Y-%m-%d"),
cycle=12
)
print(f"Yesterday's 12z cycle available: {available_12z}")
print(f" (Checked date: {yesterday.strftime('%Y-%m-%d')})")
except Exception as e:
print(f"Availability check requires internet: {e}")
print("\nExpected output:")
print(" Latest HRRR cycle available: True")
print(" Yesterday's 12z cycle available: True")
Latest HRRR cycle available: True
Yesterday's 12z cycle available: False
(Checked date: 2026-06-10)
Download Latest HRRR Forecast¶
get_latest_forecast() searches backwards through recent cycles (controlled by max_lookback_hours) to find the most recently published HRRR data. This handles the ~2 hour latency between cycle time and data availability.
# Download latest HRRR cycle (18 hours ahead)
output_dir = Path("example_data/hrrr_single")
output_dir.mkdir(parents=True, exist_ok=True)
try:
grib_files = PrecipHrrr.get_latest_forecast(
output_dir=output_dir,
hours=18,
max_lookback_hours=6 # Look back up to 6 hours for available data
)
print(f"Downloaded {len(grib_files)} GRIB2 files")
print(f"\nFirst 5 files:")
for f in grib_files[:5]:
size_mb = f.stat().st_size / (1024 * 1024)
print(f" {f.name} ({size_mb:.0f} MB)")
except Exception as e:
print(f"Download requires internet access to NOAA NOMADS: {e}")
print("\nExpected output:")
print(" Downloaded 18 GRIB2 files")
print(" hrrr.t12z.wrfsubhf01.grib2 (~100-150 MB)")
print(" hrrr.t12z.wrfsubhf02.grib2 (~100-150 MB)")
print(" ...")
grib_files = []
Downloaded 18 GRIB2 files
First 5 files:
hrrr.t16z.wrfsubhf01.grib2 (204 MB)
hrrr.t16z.wrfsubhf02.grib2 (208 MB)
hrrr.t16z.wrfsubhf03.grib2 (208 MB)
hrrr.t16z.wrfsubhf04.grib2 (213 MB)
hrrr.t16z.wrfsubhf05.grib2 (217 MB)
Extract Precipitation Data¶
extract_precipitation() reads the GRIB2 files and returns an xarray.Dataset containing the precipitation variable(s). An optional bounds parameter (west, south, east, north) clips the data to a region of interest, which significantly reduces memory usage.
if grib_files:
try:
# Extract precipitation for a specific region (Bald Eagle Creek, PA)
bounds = (-77.9, 40.8, -77.3, 41.1) # west, south, east, north
precip_ds = PrecipHrrr.extract_precipitation(
grib_files=grib_files,
bounds=bounds
)
print("Extracted precipitation dataset:")
print(f" Variables: {list(precip_ds.data_vars)}")
print(f" Dimensions: {dict(precip_ds.dims)}")
time_dim = precip_ds.dims.get('time', len(grib_files))
print(f" Time steps: {time_dim}")
print(f" Dataset:\n{precip_ds}")
except Exception as e:
print(f"Extraction requires cfgrib/xarray: {e}")
print("\nInstall with: pip install cfgrib eccodes")
else:
print("Skipping extraction (no GRIB files downloaded)")
print("\nExpected output:")
print(" Variables: ['APCP']")
print(" Dimensions: {'time': 18, 'latitude': 36, 'longitude': 72}")
print(" APCP units: kg m-2 (equivalent to mm)")
Extracted precipitation dataset:
Variables: ['tp']
Dimensions: {'step': 72, 'y': 15, 'x': 18}
Time steps: 18
Dataset:
<xarray.Dataset> Size: 83kB
Dimensions: (step: 72, y: 15, x: 18)
Coordinates:
* step (step) timedelta64[ns] 576B 00:15:00 00:30:00 ... 18:00:00
valid_time (step) datetime64[ns] 576B 2026-06-11T16:15:00 ... 2026-06-12...
latitude (y, x) float64 2kB 40.81 40.81 40.8 40.8 ... 41.1 41.09 41.08
longitude (y, x) float64 2kB 282.1 282.1 282.1 282.2 ... 282.7 282.7 282.8
time datetime64[ns] 8B 2026-06-11T16:00:00
surface float64 8B 0.0
Dimensions without coordinates: y, x
Data variables:
tp (step, y, x) float32 78kB nan nan 0.0 nan ... nan 0.0 nan nan
Attributes:
GRIB_edition: 2
GRIB_centre: kwbc
GRIB_centreDescription: US National Weather Service - NCEP
GRIB_subCentre: 0
Conventions: CF-1.7
institution: US National Weather Service - NCEP
history: 2026-06-11T14:05 GRIB to CDM+CF via cfgrib-0.9.1...
Basin-Average Precipitation¶
get_basin_average() spatially averages the gridded HRRR precipitation over a watershed geometry and returns a pandas.DataFrame with hourly precipitation and cumulative totals. The geometry can be any Shapely geometry (polygon, multipolygon, etc.).
The returned DataFrame has columns:
- forecast_hour - Hours from cycle start (1 through N)
- precip_mm - Incremental precipitation in millimeters
- precip_inches - Incremental precipitation in inches
- cumulative_mm - Cumulative precipitation in millimeters
- cumulative_inches - Cumulative precipitation in inches
if grib_files:
try:
from shapely.geometry import box
basin_geom = box(-77.9, 40.8, -77.3, 41.1)
avg_precip = PrecipHrrr.get_basin_average(
grib_files=grib_files,
geometry=basin_geom
)
print("Basin-average precipitation time series:")
print(avg_precip.to_string(index=False))
print(f"\nTotal forecast precipitation: {avg_precip['cumulative_inches'].iloc[-1]:.3f} inches")
print(f"Peak hourly precipitation: {avg_precip['precip_inches'].max():.3f} inches/hr")
except Exception as e:
print(f"Basin average calculation failed: {e}")
else:
print("Skipping basin average (no GRIB files)")
print("\nExpected output (DataFrame columns):")
print(" forecast_hour | precip_mm | precip_inches | cumulative_mm | cumulative_inches")
print(" 1 | 0.5 | 0.02 | 0.5 | 0.02")
print(" 2 | 1.2 | 0.05 | 1.7 | 0.07")
print(" ...")
Basin-average precipitation time series:
forecast_hour precip_mm precip_inches cumulative_mm cumulative_inches
1 0.000000 0.000000 0.000000 0.000000
2 0.000000 0.000000 0.000000 0.000000
3 0.000000 0.000000 0.000000 0.000000
4 0.000000 0.000000 0.000000 0.000000
5 0.000047 0.000002 0.000047 0.000002
6 0.000087 0.000003 0.000133 0.000005
7 0.000067 0.000003 0.000200 0.000008
8 0.000000 0.000000 0.000200 0.000008
9 0.000000 0.000000 0.000200 0.000008
10 0.000000 0.000000 0.000200 0.000008
11 0.000807 0.000032 0.001007 0.000040
12 0.004867 0.000192 0.005873 0.000231
13 0.006033 0.000238 0.011907 0.000469
14 0.017613 0.000693 0.029520 0.001162
15 0.044653 0.001758 0.074173 0.002920
16 0.007947 0.000313 0.082120 0.003233
17 0.000353 0.000014 0.082473 0.003247
18 0.103373 0.004070 0.185847 0.007317
19 0.565213 0.022252 0.751060 0.029569
20 0.062913 0.002477 0.813973 0.032046
21 0.047487 0.001870 0.861460 0.033916
22 0.159520 0.006280 1.020980 0.040196
23 0.545967 0.021495 1.566947 0.061691
24 0.896680 0.035302 2.463627 0.096993
25 1.847753 0.072746 4.311380 0.169739
26 2.476386 0.097496 6.787767 0.267235
27 1.306480 0.051436 8.094247 0.318671
28 0.220060 0.008664 8.314307 0.327335
29 0.049787 0.001960 8.364093 0.329295
30 0.002053 0.000081 8.366147 0.329376
31 0.000053 0.000002 8.366200 0.329378
32 0.000000 0.000000 8.366200 0.329378
33 0.000000 0.000000 8.366200 0.329378
34 0.000000 0.000000 8.366200 0.329378
35 0.000000 0.000000 8.366200 0.329378
36 0.000000 0.000000 8.366200 0.329378
37 0.000000 0.000000 8.366200 0.329378
38 0.000000 0.000000 8.366200 0.329378
39 0.000000 0.000000 8.366200 0.329378
40 0.000000 0.000000 8.366200 0.329378
41 0.000000 0.000000 8.366200 0.329378
42 0.000000 0.000000 8.366200 0.329378
43 0.000000 0.000000 8.366200 0.329378
44 0.000000 0.000000 8.366200 0.329378
45 0.000000 0.000000 8.366200 0.329378
46 0.000000 0.000000 8.366200 0.329378
47 0.000000 0.000000 8.366200 0.329378
48 0.000000 0.000000 8.366200 0.329378
49 0.000000 0.000000 8.366200 0.329378
50 0.000000 0.000000 8.366200 0.329378
51 0.000000 0.000000 8.366200 0.329378
52 0.000000 0.000000 8.366200 0.329378
53 0.000000 0.000000 8.366200 0.329378
54 0.000000 0.000000 8.366200 0.329378
55 0.000000 0.000000 8.366200 0.329378
56 0.000000 0.000000 8.366200 0.329378
57 0.000000 0.000000 8.366200 0.329378
58 0.000000 0.000000 8.366200 0.329378
59 0.000000 0.000000 8.366200 0.329378
60 0.000000 0.000000 8.366200 0.329378
61 0.000000 0.000000 8.366200 0.329378
62 0.000000 0.000000 8.366200 0.329378
63 0.000000 0.000000 8.366200 0.329378
64 0.000000 0.000000 8.366200 0.329378
65 0.001813 0.000071 8.368013 0.329449
66 0.062613 0.002465 8.430627 0.331914
67 0.300387 0.011826 8.731013 0.343741
68 1.033293 0.040681 9.764307 0.384422
69 1.607067 0.063270 11.371373 0.447692
70 1.344487 0.052933 12.715860 0.500624
71 0.565260 0.022254 13.281120 0.522879
72 0.067000 0.002638 13.348120 0.525517
Total forecast precipitation: 0.526 inches
Peak hourly precipitation: 0.097 inches/hr
Example 2: Specific Date/Cycle Download¶
When you need a specific historical cycle (within NOMADS retention window, typically ~48 hours), use download_forecast() with explicit date and cycle parameters. This is useful for:
- Comparing model forecasts to observations after an event
- Reproducible analysis with a known dataset
- Operational post-processing pipelines
# Download a specific date and cycle
output_dir_specific = Path("example_data/hrrr_specific")
output_dir_specific.mkdir(parents=True, exist_ok=True)
# Use yesterday's 00z cycle with 18-hour forecast
yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
try:
grib_files_specific = PrecipHrrr.download_forecast(
output_dir=output_dir_specific,
date=yesterday,
cycle=0, # 00z cycle
hours=18,
overwrite=False # Skip if already downloaded
)
print(f"Downloaded {len(grib_files_specific)} files for {yesterday} 00z")
if grib_files_specific:
total_mb = sum(f.stat().st_size for f in grib_files_specific) / (1024 * 1024)
print(f"Total download size: {total_mb:.1f} MB")
except Exception as e:
print(f"Download requires internet: {e}")
print(f"\nWould download 18 GRIB2 files for {yesterday} 00z cycle")
print("Typical file size: ~100-150 MB per GRIB2 file (full CONUS)")
print("Total download: ~2-3 GB for full 18-hour cycle")
Downloaded 18 files for 2026-06-10 00z
Total download size: 3651.3 MB
HRRR Cycle Timing Reference¶
Understanding HRRR cycle timing is critical for operational use. The following cell provides a reference table for all 24 daily cycles.
# HRRR cycles and forecast horizons (all 24 hourly cycles)
print("HRRR Forecast Cycles:")
print("=" * 65)
print(f"{'Cycle':<10} {'Type':<12} {'Forecast Hours':<20} {'Approx Availability (UTC)'}")
print("-" * 65)
EXTENDED_CYCLES = [0, 6, 12, 18]
for cycle in range(0, 24):
if cycle in EXTENDED_CYCLES:
cycle_type = "Extended"
hours = "48 hours"
else:
cycle_type = "Standard"
hours = "18 hours"
avail_hour = (cycle + 2) % 24
availability = f"{avail_hour:02d}:00 - {avail_hour:02d}:30 UTC"
print(f"{cycle:02d}z {cycle_type:<12} {hours:<20} {availability}")
print()
print("Notes:")
print(" - HRRR runs every hour (24 cycles per day, 00z through 23z)")
print(" - Data typically available ~2 hours after cycle start time")
print(" - Example: 12z cycle data available ~14:00 UTC")
print(" - Extended cycles (00/06/12/18z) produce 48-hour forecasts")
print(" - All other cycles produce 18-hour forecasts")
print(" - NOMADS retains ~48 hours of recent cycles")
print(" - For older data: https://storage.googleapis.com/high-resolution-rapid-refresh/")
print()
print("File size reference (full CONUS wrfsubhf):")
print(" ~100-150 MB per GRIB2 file (single forecast hour)")
print(" ~2-3 GB for complete 18-hour standard cycle")
print(" ~5-7 GB for complete 48-hour extended cycle")
print(" Spatial subsetting via 'bounds' parameter reduces size significantly")
HRRR Forecast Cycles:
=================================================================
Cycle Type Forecast Hours Approx Availability (UTC)
-----------------------------------------------------------------
00z Extended 48 hours 02:00 - 02:30 UTC
01z Standard 18 hours 03:00 - 03:30 UTC
02z Standard 18 hours 04:00 - 04:30 UTC
03z Standard 18 hours 05:00 - 05:30 UTC
04z Standard 18 hours 06:00 - 06:30 UTC
05z Standard 18 hours 07:00 - 07:30 UTC
06z Extended 48 hours 08:00 - 08:30 UTC
07z Standard 18 hours 09:00 - 09:30 UTC
08z Standard 18 hours 10:00 - 10:30 UTC
09z Standard 18 hours 11:00 - 11:30 UTC
10z Standard 18 hours 12:00 - 12:30 UTC
11z Standard 18 hours 13:00 - 13:30 UTC
12z Extended 48 hours 14:00 - 14:30 UTC
13z Standard 18 hours 15:00 - 15:30 UTC
14z Standard 18 hours 16:00 - 16:30 UTC
15z Standard 18 hours 17:00 - 17:30 UTC
16z Standard 18 hours 18:00 - 18:30 UTC
17z Standard 18 hours 19:00 - 19:30 UTC
18z Extended 48 hours 20:00 - 20:30 UTC
19z Standard 18 hours 21:00 - 21:30 UTC
20z Standard 18 hours 22:00 - 22:30 UTC
21z Standard 18 hours 23:00 - 23:30 UTC
22z Standard 18 hours 00:00 - 00:30 UTC
23z Standard 18 hours 01:00 - 01:30 UTC
Notes:
- HRRR runs every hour (24 cycles per day, 00z through 23z)
- Data typically available ~2 hours after cycle start time
- Example: 12z cycle data available ~14:00 UTC
- Extended cycles (00/06/12/18z) produce 48-hour forecasts
- All other cycles produce 18-hour forecasts
- NOMADS retains ~48 hours of recent cycles
- For older data: https://storage.googleapis.com/high-resolution-rapid-refresh/
File size reference (full CONUS wrfsubhf):
~100-150 MB per GRIB2 file (single forecast hour)
~2-3 GB for complete 18-hour standard cycle
~5-7 GB for complete 48-hour extended cycle
Spatial subsetting via 'bounds' parameter reduces size significantly
Key Takeaways¶
HRRR Data Characteristics: - 3km resolution, hourly cycles, publicly available from NOAA NOMADS - Standard cycles produce 18-hour forecasts; extended cycles (00/06/12/18z) produce 48-hour forecasts - NOMADS retains approximately 48 hours of recent output; use the HRRR archive on AWS for older data
Workflow Summary:
1. Use check_availability() to verify data is on NOMADS before downloading
2. Use get_latest_forecast() for operational workflows that always need current data
3. Use download_forecast(date=..., cycle=...) when you need a specific historical cycle
4. Use extract_precipitation(bounds=...) to clip to your region of interest (reduces memory)
5. Use get_basin_average() to compute watershed-averaged precipitation for HEC-RAS boundary conditions
Common Pitfalls:
- HRRR precipitation (APCP) is accumulated from the start of the cycle — convert to incremental depth before summing
- The overwrite=False default prevents redundant downloads in operational pipelines
- For production use, consider caching GRIB2 files locally; full CONUS files are ~100-150 MB each (~2-3 GB per 18-hour cycle)
- cfgrib and eccodes are required for extract_precipitation(): pip install cfgrib eccodes
Integration with HEC-RAS:
- Use get_basin_average() output as upstream boundary condition hyetographs
- Combine with RasUnsteady.set_precipitation_hyetograph() to write directly to unsteady flow files
- For spatially distributed rainfall, export extract_precipitation() output to DSS using RasDss
Cleanup¶
import shutil
for d in ["example_data/hrrr_single", "example_data/hrrr_specific"]:
p = Path(d)
if p.exists():
shutil.rmtree(p)
print(f"Cleaned up: {d}")
else:
print(f"Not found (nothing to clean): {d}")
print("Done!")
Cleaned up: example_data/hrrr_single
Cleaned up: example_data/hrrr_specific
Done!