Skip to main content

Read NOAA GFS WaveWatch III forecast data from NOMADS.

Project description

noaa-gfs-wave

Read NOAA GFS WaveWatch III forecasts without the plumbing.

Install

pip install noaa-gfs-wave

System dependency — ecCodes

cfgrib (used to parse GRIB2 files) requires the ecCodes C library:

# macOS
brew install eccodes

# Debian/Ubuntu
apt-get install libeccodes-dev

On recent cfgrib versions the wheel bundles a binary eccodeslib, so the system package may not be strictly required — but install it if you see errors.

Quickstart

from noaa_gfs_wave import NoaaGribFile, latest_available_cycle

# From the latest published cycle (detects automatically)
grib = NoaaGribFile.latest(forecast_hour=3)
grid = grib.read()
point = grid.at(lat=-9.1, lon=-35.2)
print(point.combined.significant_height_meters)
print(point.wind10m.speed_meters_per_second, point.wind10m.speed_knots)
print(point.power_kilowatts_per_meter)  # kW per meter of wave crest

# Explicit cycle
from datetime import datetime, UTC
ref_time = datetime(2026, 3, 9, 0, 0, 0, tzinfo=UTC)
grib = NoaaGribFile(reference_time=ref_time, cycle=6, forecast_hour=3, cache_dir="./cache")
grid = grib.read()

# From a local file (offline / testing)
grib = NoaaGribFile.from_local("path/to/file.grib2")
grid = grib.read()
point = grid.at(lat=-9.1, lon=-35.2)

# xarray escape hatch
ds = grib.open_dataset()
print(ds.swh)
ds.close()

Custom base URL (mirrors, testing)

By default, all downloads hit NOAA NOMADS. To point at an internal mirror or a test server (useful when NOMADS is rate-limiting or unavailable), pass base_url to either GribAddress or NoaaGribFile.

from noaa_gfs_wave import GribAddress, NoaaGribFile, NOAA_NOMADS_BASE_URL
from datetime import datetime, UTC

ref_time = datetime(2026, 3, 9, 0, 0, 0, tzinfo=UTC)

# Default — hits NOAA NOMADS
address = GribAddress(reference_time=ref_time, cycle=12, forecast_hour=3)
url = address.remote_url()  # https://nomads.ncep.noaa.gov/...

# Custom mirror via GribAddress
address = GribAddress(
    reference_time=ref_time,
    cycle=12,
    forecast_hour=3,
    base_url="https://my-mirror.example/gfs",
)

# Same knob on NoaaGribFile
grib = NoaaGribFile(
    reference_time=ref_time,
    cycle=12,
    forecast_hour=3,
    base_url="https://my-mirror.example/gfs",
)

NOAA_NOMADS_BASE_URL is the canonical base URL string and can be imported directly if you need to construct URLs manually.

API Reference

Name Type Description
NoaaGribFile class Lazy download + cached GRIB2 access
WaveGrid class In-memory xarray-backed grid with typed accessors
WW3PointForecast pydantic model Full forecast for a single grid point
WW3PointMeta pydantic model Timing and location metadata
Wind10m pydantic model 10 m wind conditions
CombinedSea pydantic model Combined (total) sea state
DominantSystem pydantic model Highest-energy wave system
WindSea pydantic model Locally generated wind-driven waves
SwellPartition pydantic model One of three swell partitions
latest_available_cycle function Returns (datetime, int) for the latest published cycle
NOAA_CYCLES constant [0, 6, 12, 18]
NoaaGfsWaveError exception Base exception
GribDownloadError exception Network or HTTP error during download
GribNotPublishedError exception HTTP 404 — cycle not yet on NOMADS
GribCorruptError exception cfgrib parse failure

Concepts

GFS cycle publication lag

The GFS wave model runs at 0z, 6z, 12z, and 18z UTC. Each cycle takes ~9 hours to appear on NOMADS. latest_available_cycle() accounts for this lag automatically.

Longitude convention (0..360)

The NOAA WW3 grid uses longitudes in 0..360 (not -180..180). WaveGrid.at() accepts both conventions — negative values are normalized automatically. WaveGrid.longitudes returns the raw 0..360 array.

NaN over land

WW3 uses NaN to mark land grid points. WaveGrid.at() on a land point returns a WW3PointForecast where all numeric fields are None. Use is_land() to check this without inspecting individual fields:

point = grid.at(lat=-7.0, lon=-35.0)
if point.is_land():
    ...

Nearest-neighbor extraction

WaveGrid.at(lat, lon) uses np.argmin on both axes — no bilinear interpolation. This avoids smearing land NaN values into ocean points near coastlines.

Caching

Files are stored flat in cache_dir with the naming convention:

{cache_dir}/{YYYYMMDD}_{CC}_{FFF}.grib2

cfgrib generates an index sidecar (*.idx) automatically. Include *.idx in cleanup if you evict GRIB files. The library does not manage cache eviction.

Error Handling

Situation Exception
HTTP 404 — cycle not yet published GribNotPublishedError
5xx, network error, timeout GribDownloadError
cfgrib can't parse the file GribCorruptError
cache_dir unwritable OSError (unchanged)

Downloaded files are verified for completeness (Content-Length match) and GRIB2 framing (GRIB head, 7777 tail) before being committed to the cache. A mismatch raises GribCorruptError and the .partial file is discarded so the cache stays clean.

Limitations

  • Wave model only (no GFS atmospheric, HRRR, NAM, RTOFS)
  • No async API
  • Local filesystem cache only (no S3 / R2)
  • Nearest-neighbor extraction only (no interpolation)
  • No automatic retry — callers own retry logic

Development

The library is built with hatchling; dev and CI use uv for fast, reproducible installs.

git clone https://github.com/fbenevides/noaa-gfs-wave
cd noaa-gfs-wave
uv sync --all-extras
uv run pytest
uv run ruff check .

End-users who aren't on uv can still install with pip:

pip install noaa-gfs-wave

License

MIT — see LICENSE.

Attribution

GFS wave data is provided by NOAA and is in the public domain. This library is not affiliated with NOAA.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

noaa_gfs_wave-0.1.2.tar.gz (150.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

noaa_gfs_wave-0.1.2-py3-none-any.whl (14.9 kB view details)

Uploaded Python 3

File details

Details for the file noaa_gfs_wave-0.1.2.tar.gz.

File metadata

  • Download URL: noaa_gfs_wave-0.1.2.tar.gz
  • Upload date:
  • Size: 150.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for noaa_gfs_wave-0.1.2.tar.gz
Algorithm Hash digest
SHA256 0c8d3b8ac24928ffb527a545258b714f7e468c01c4d0ee7efee8d291e3d52ddd
MD5 8347fac4c2a0d3c71f658c063bfec268
BLAKE2b-256 bdb3d45583739f5d97b57e2cd1789402fb9ad7862fbfd54c04e5d34a5237171f

See more details on using hashes here.

Provenance

The following attestation bundles were made for noaa_gfs_wave-0.1.2.tar.gz:

Publisher: release.yml on fbenevides/noaa-gfs-wave

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file noaa_gfs_wave-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: noaa_gfs_wave-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 14.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for noaa_gfs_wave-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 adcb189d3a9e99722ca845081c6bc6544a43d2fe0449469e95ee9ca04f719bc3
MD5 10db9d31375a89ae88bba14295a4625f
BLAKE2b-256 4860e5bf6284dd7ef4bf2688db96feefabdb1d497b48c79273921f2526eb2a95

See more details on using hashes here.

Provenance

The following attestation bundles were made for noaa_gfs_wave-0.1.2-py3-none-any.whl:

Publisher: release.yml on fbenevides/noaa-gfs-wave

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page