Skip to content

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

Python
# =============================================================================
# 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__}")
Text Only
LOCAL SOURCE MODE: Loading from <workspace>/ras_commander


Loaded: <workspace>\ras_commander\__init__.py
Python
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.")
Text Only
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.

Python
# 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')}")
Text Only
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.

Python
# 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")
Text Only
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.

Python
# 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 = []
Text Only
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.

Python
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)")
Text Only
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

Python
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("  ...")
Text Only
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

Python
# 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")
Text Only
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.

Python
# 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")
Text Only
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

Python
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!")
Text Only
Cleaned up: example_data/hrrr_single


Cleaned up: example_data/hrrr_specific
Done!