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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0c8d3b8ac24928ffb527a545258b714f7e468c01c4d0ee7efee8d291e3d52ddd
|
|
| MD5 |
8347fac4c2a0d3c71f658c063bfec268
|
|
| BLAKE2b-256 |
bdb3d45583739f5d97b57e2cd1789402fb9ad7862fbfd54c04e5d34a5237171f
|
Provenance
The following attestation bundles were made for noaa_gfs_wave-0.1.2.tar.gz:
Publisher:
release.yml on fbenevides/noaa-gfs-wave
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
noaa_gfs_wave-0.1.2.tar.gz -
Subject digest:
0c8d3b8ac24928ffb527a545258b714f7e468c01c4d0ee7efee8d291e3d52ddd - Sigstore transparency entry: 1341965831
- Sigstore integration time:
-
Permalink:
fbenevides/noaa-gfs-wave@62e0241179b1f2192abc5235f0f21fbe7e384651 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/fbenevides
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@62e0241179b1f2192abc5235f0f21fbe7e384651 -
Trigger Event:
push
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
adcb189d3a9e99722ca845081c6bc6544a43d2fe0449469e95ee9ca04f719bc3
|
|
| MD5 |
10db9d31375a89ae88bba14295a4625f
|
|
| BLAKE2b-256 |
4860e5bf6284dd7ef4bf2688db96feefabdb1d497b48c79273921f2526eb2a95
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
noaa_gfs_wave-0.1.2-py3-none-any.whl -
Subject digest:
adcb189d3a9e99722ca845081c6bc6544a43d2fe0449469e95ee9ca04f719bc3 - Sigstore transparency entry: 1341965837
- Sigstore integration time:
-
Permalink:
fbenevides/noaa-gfs-wave@62e0241179b1f2192abc5235f0f21fbe7e384651 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/fbenevides
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@62e0241179b1f2192abc5235f0f21fbe7e384651 -
Trigger Event:
push
-
Statement type: