Python library for NOAA GFS forecasts and GODAS/ERSST ocean data
Project description
noawclg
Download, analyse and visualise NOAA atmospheric and ocean data in Python.
noawclg gives you a clean Python API over two major NOAA data streams —
GFS weather forecasts and GODAS/ERSST ocean analyses — returning
xarray.Dataset objects ready for analysis and plotting, with no API key required.
Table of contents
- Features
- Installation
- GFS weather forecasts
- Ocean data — GODAS & ERSST
- ENSO diagnostics
- Plotting
- API reference
- Module overview
- Contributing
Features
| GFS forecasts | 3-hourly, 0–384 h, surface + multi-level, GRIB2 via NOMADS |
| GODAS ocean | pottmp · salt · ucur · vcur · sshg · 40 depth levels · 1980–present |
| ERSST v5 | SST back to 1854 · long climatologies via OPeNDAP |
| ENSO diagnostics | ONI · Niño indices · D20 thermocline · WWV · phase classification |
| 26 plot functions | Synoptic maps · globe · ENSO time series · thermocline sections · wind rose |
| No API key | All data via public OPeNDAP / NOMADS endpoints, lazy loading |
Installation
pip install noawclg # core (GFS + ocean data)
pip install "noawclg[plots]" # + matplotlib, cartopy, metpy, windrose, seaborn
GFS GRIB2 support requires the
eccodesC library.
On Ubuntu/Debian:sudo apt install libeccodes-dev
On macOS:brew install eccodes
On conda:conda install -c conda-forge eccodes
Development install:
git clone https://github.com/reinanbr/noawclg
cd noawclg
pip install -e ".[dev]"
pytest
GFS weather forecasts
Quick download with load
from noawclg import load, auto_date
date, cycle = auto_date(lag_days=1) # latest complete GFS run
ds = load(
date=date,
cycle=cycle,
lat=-3.7, # point of interest (Fortaleza, Brazil)
lon=-38.5,
region={
"toplat": 5, "bottomlat": -15,
"leftlon": -50, "rightlon": -30,
},
hours=list(range(0, 121, 3)), # forecast hours 0–120 h every 3 h
)
print(ds)
The returned xr.Dataset contains:
| Variable | Description | Units |
|---|---|---|
t2m |
2-m temperature | K |
d2m |
2-m dew point | K |
prmsl |
Mean sea-level pressure | Pa |
u10 / v10 |
10-m wind components | m/s |
gust |
Surface wind gust | m/s |
prate |
Precipitation rate | kg/m²/s |
r2 |
2-m relative humidity | % |
tcc |
Total cloud cover | 0–1 |
cape |
CAPE | J/kg |
auto_date — pick the latest available GFS cycle
from noawclg import auto_date
date, cycle = auto_date(lag_days=1)
# date → "05/06/2026" (DD/MM/YYYY)
# cycle → "12" (00 / 06 / 12 / 18)
lag_days=1 returns yesterday (safe; today's runs may still be publishing).
lag_days=0 returns today's most recently available cycle.
GFSDatasetManager — full control
from noawclg import GFSDatasetManager
mgr = GFSDatasetManager(
date="05/06/2026",
cycle="12",
variables=["t2m", "u10", "v10", "prmsl"],
region={"toplat": 10, "bottomlat": -20, "leftlon": -55, "rightlon": -25},
output_dir="gfs_cache/",
)
mgr.download_hours(list(range(0, 49, 3)))
ds = mgr.build_multi_dataset(list(range(0, 49, 3)))
# Save / load
mgr.save_netcdf(ds, "forecast.nc")
ds2 = mgr.load_netcdf("forecast.nc")
mgr.save_zarr(ds, "forecast.zarr")
ds3 = mgr.load_zarr("forecast.zarr")
get_noaa_data — query by place name
from noawclg import get_noaa_data
gfs = get_noaa_data(
date="05/06/2026",
cycle="12",
place="Recife PE", # geocoded automatically
hours=list(range(0, 73, 3)),
variables=["t2m", "prmsl", "prate"],
)
# Access extracted time series
ts = gfs.time_series("t2m") # pd.Series indexed by forecast time
pt = gfs.point_query("t2m", hour=24) # scalar value at hour 24
Pre-defined hour sequences
from noawclg import HOURS_5DAYS_1H, HOURS_10DAYS_3H, HOURS_16DAYS_3H
# Download 5-day hourly forecast
ds = load(date=date, cycle=cycle, lat=0, lon=-40, hours=HOURS_5DAYS_1H)
| Constant | Hours | Step |
|---|---|---|
HOURS_5DAYS_1H |
0–120 | 1 h |
HOURS_10DAYS_3H |
0–240 | 3 h |
HOURS_16DAYS_3H |
0–384 | 3 h |
HOURS_16DAYS |
0–384 | 6 h |
Ocean data — GODAS & ERSST
All ocean data is served via OPeNDAP — no files are downloaded, access is lazy.
open_godas — single year, single variable
from noawclg import open_godas
ds = open_godas(
year=2024,
variable="pottmp", # "pottmp" | "salt" | "ucur" | "vcur" | "sshg"
depth_m=200.0, # nearest depth level; None = all 40 levels
region={
"lat_min": -10, "lat_max": 10,
"lon_min": 120, "lon_max": 290,
},
)
print(ds["pottmp"]) # °C, (time=12, lat, lon)
Available GODAS variables (GODAS_VARS):
| Key | Description | Input units | Output units |
|---|---|---|---|
pottmp |
Potential temperature | K | °C |
salt |
Salinity | kg/kg | PSU |
ucur |
U-current (eastward) | m/s | m/s |
vcur |
V-current (northward) | m/s | m/s |
sshg |
Sea surface height / geoid | m | m |
40 depth levels: 5, 15, 25, … 205, 215, 225, 238, 262, 303, 366, 459, 584, 747, 949, 1193, 1479, 1807, 2174, 2579, 3016, 3483, 3972, 4478 m.
get_godas — multi-year concatenation
from noawclg import get_godas
# All 2020–2024 temperature at 200 m
da = get_godas(2020, 2024, variable="pottmp", depth_m=200.0)
print(da) # DataArray (time=60, lat, lon)
Typed convenience wrappers
from noawclg import get_ocean_temp, get_salinity, get_currents, get_ssh
t200 = get_ocean_temp(2024, depth_m=200) # °C
t5 = get_ocean_temp(2024, depth_m=5) # surface temperature
sal = get_salinity(2024, depth_m=5) # PSU
curr = get_currents(2024, depth_m=5) # Dataset: ucur, vcur, speed
print(curr["speed"].mean().item(), "m/s")
ssh = get_ssh(2024) # m, (time=12, lat, lon)
ssh5 = get_ssh(2020, 2024) # 5 years concatenated
open_ersst — NOAA ERSST v5 (SST since 1854)
from noawclg import open_ersst
# Niño 3.4 box, 1950–2024
sst = open_ersst(
year_start=1950,
year_end=2024,
region={
"lat_min": -5, "lat_max": 5,
"lon_min": 190, "lon_max": 240,
},
)
print(sst["sst"]) # °C, (time=900, lat, lon)
ERSST uses a decreasing latitude axis (88 → −88); open_ersst handles this automatically.
get_sst_series — monthly Niño-box SST time series
from noawclg import get_sst_series
# From GODAS (1980+)
sst_godas = get_sst_series(2000, 2024, box="3.4", source="godas")
# From ERSST (1854+, longer climatology)
sst_ersst = get_sst_series(1950, 2024, box="3.4", source="ersst")
Niño boxes (NINO_BOXES, longitude 0–360):
| Key | Lat | Lon | Used for |
|---|---|---|---|
"1+2" |
10°S–0° | 270–280°E | Near-coastal SST |
"3" |
5°S–5°N | 210–270°E | Central/eastern Pacific |
"3.4" |
5°S–5°N | 190–240°E | ONI / official ENSO index |
"4" |
5°S–5°N | 160–210°E | Western Pacific |
ENSO diagnostics
ONI and phase classification
from noawclg import get_nino_anomaly, get_oni, classify_enso
# SST anomaly relative to 1991–2020 climatology
anom = get_nino_anomaly(2000, 2024, box="3.4", source="ersst",
clim_start=1991, clim_end=2020)
# Oceanic Niño Index (3-month running mean of anomaly)
oni = get_oni(2000, 2024, source="ersst")
# Phase classification (CPC ONI rule: ≥5 consecutive seasons)
phase = classify_enso(oni)
# pd.Series with values: "El Niño" | "La Niña" | "Neutral"
print(oni.tail(6))
print(phase.tail(6))
Complete ENSO summary table
from noawclg import enso_summary
df = enso_summary(2015, 2024)
print(df.tail(12))
sst_nino34 anom_nino34 oni phase
month
2023-12-01 27.45 0.85 1.41 El Niño
2024-01-01 27.21 0.84 1.23 El Niño
2024-02-01 27.13 0.72 0.98 El Niño
...
Columns: sst_nino34 (°C), anom_nino34 (°C), oni (°C), phase.
Thermocline depth D20
The depth of the 20 °C isotherm is the primary dynamical ENSO precursor. When the thermocline deepens in the eastern Pacific, warm water accumulates → El Niño.
from noawclg import get_thermocline_depth
d20 = get_thermocline_depth(2024, region={
"lat_min": -30, "lat_max": 30,
"lon_min": 120, "lon_max": 290,
})
print(d20) # DataArray (time=12, lat, lon) in metres
Warm Water Volume (WWV)
WWV measures water warmer than 20 °C in the equatorial Pacific (5°S–5°N, 120°E–80°W) above 300 m. A large positive WWV anomaly typically precedes El Niño by 6–9 months.
from noawclg import get_warm_water_volume
wwv = get_warm_water_volume(2020, 2024)
wwv.plot(title="Equatorial Pacific Warm Water Volume 2020–2024")
Plotting
Install extras: pip install "noawclg[plots]"
All plot functions accept an xarray.DataArray or Dataset and return a
matplotlib.Figure. Pass save_path="file.png" to save automatically.
GFS plots
from plots import (
plot_synoptic_map,
plot_wind_speed_map,
plot_cloud_map,
plot_cape_map,
plot_precip_map,
plot_timeseries,
plot_temp_dewpoint,
plot_wind_timeseries,
plot_humidity_cloud,
plot_cumulative_precip,
plot_wind_rose,
plot_hodograph,
plot_vertical_profiles,
plot_500hpa_map,
plot_hovmoller,
plot_precip_heatmap,
plot_spread_matrix,
plot_diurnal_distribution,
plot_dashboard,
)
# Synoptic map — T2m filled + MSLP isobars + 10 m wind barbs (requires cartopy)
fig = plot_synoptic_map(ds, hour=24, title="GFS 24 h forecast", save_path="synoptic.png")
# Time-series at a point
fig = plot_timeseries(ds, city_name="Fortaleza CE", save_path="ts.png")
# Wind rose
fig = plot_wind_rose(ds, city_name="Fortaleza CE", save_path="rose.png")
# Upper-air hodograph
fig = plot_hodograph(ds_upper, lat=-3.7, lon=-38.5, save_path="hodo.png")
# Vertical temperature + humidity profiles
fig = plot_vertical_profiles(ds_upper, lat=-3.7, lon=-38.5, save_path="profiles.png")
# 500 hPa geopotential + jet stream (requires cartopy)
fig = plot_500hpa_map(ds_upper, hour=24, save_path="500hpa.png")
# Hovmöller — longitude vs time at fixed latitude
fig = plot_hovmoller(ds, mode="lon", lat=-3.7, variable="prate", save_path="hovmoller.png")
# 4-panel dashboard
fig = plot_dashboard(ds, hour=24, city_name="Fortaleza CE", save_path="dashboard.png")
Ocean / ENSO plots
from plots import (
plot_enso_index,
plot_ocean_temp_map,
plot_thermocline_section,
plot_ssh_map,
plot_ocean_currents,
plot_globe,
)
# ONI time series with El Niño / La Niña shading
from noawclg import get_oni, classify_enso
oni = get_oni(2000, 2024)
phase = classify_enso(oni)
fig = plot_enso_index(oni, phase, title="ONI 2000–2024", save_path="oni.png")
# Ocean temperature flat map with Niño-box overlays
from noawclg import get_ocean_temp
t200 = get_ocean_temp(2024, depth_m=200)
fig = plot_ocean_temp_map(
t200.mean("time"),
title="Mean Ocean Temperature at 200 m — 2024",
nino_boxes=True, # draws Niño 1+2 / 3 / 3.4 / 4 rectangles
cmap="RdYlBu_r",
save_path="t200_map.png",
)
# Thermocline cross-section (depth–longitude, equatorial band)
from noawclg import get_godas
pottmp = get_godas(2024, variable="pottmp", region={
"lat_min": -5, "lat_max": 5, "lon_min": 120, "lon_max": 290,
})
fig = plot_thermocline_section(pottmp, lat=0.0, isotherm=20.0, save_path="thermo.png")
# SSH anomaly map
from noawclg import get_ssh
ssh = get_ssh(2024)
fig = plot_ssh_map(ssh.isel(time=0), title="SSH — January 2024", save_path="ssh.png")
# Ocean currents — speed fill + quiver arrows
from noawclg import get_currents
curr = get_currents(2024, depth_m=5)
fig = plot_ocean_currents(
curr["ucur"].isel(time=0),
curr["vcur"].isel(time=0),
title="Surface Ocean Currents — January 2024",
save_path="currents.png",
)
# Globe — any 2-D field on an Orthographic projection (requires cartopy)
fig = plot_globe(
t200.mean("time"),
title="Ocean Temperature at 200 m — Pacific view",
cmap="RdYlBu_r",
central_longitude=-150, # Pacific-centred
central_latitude=0,
save_path="globe.png",
)
# Globe — SSH with symmetric colour scale
fig = plot_globe(
ssh.mean("time"),
cmap="RdBu_r",
central_longitude=-150,
symmetric=True, # force vmin = -vmax
save_path="globe_ssh.png",
)
Colourmap reference
| Field | Default colourmap |
|---|---|
| Temperature | cmocean.cm.thermal / RdYlBu_r |
| Salinity | cmocean.cm.haline |
| SSH / anomaly | RdBu_r |
| Current speed | plasma |
| Precipitation | Blues |
| CAPE | YlOrRd |
| Wind speed | viridis |
API reference
noawclg.load
load(date, cycle, lat, lon, region=None, hours=None, variables=None, **kwargs)
→ xr.Dataset
One-liner wrapper around GFSDatasetManager. Returns an xr.Dataset with all
requested variables merged.
noawclg.auto_date
auto_date(lag_days=1) → tuple[str, str]
# Returns (date_str, cycle_str) e.g. ("05/06/2026", "12")
noawclg.GFSDatasetManager
| Method | Description |
|---|---|
download_hours(hours) |
Download GRIB2 files for given forecast hours |
build_dataset(hour, variables) |
Build xr.Dataset for a single hour |
build_multi_dataset(hours, variables) |
Build merged dataset for all hours |
save_netcdf(ds, filename) |
Save to NetCDF4 |
load_netcdf(filename) |
Load from NetCDF4 |
save_zarr(ds, path) |
Save to Zarr |
load_zarr(path) |
Load from Zarr |
noawclg.get_noaa_data
get_noaa_data(date, cycle, place=None, lat=None, lon=None,
hours=None, variables=None, region=None, **kwargs)
High-level interface. place is geocoded with geopy (e.g. "São Paulo SP").
Ocean functions
| Function | Description | Returns |
|---|---|---|
open_godas(year, variable, depth_m, region) |
Single-year GODAS via OPeNDAP | xr.Dataset |
get_godas(y0, y1, variable, depth_m, region) |
Multi-year GODAS | xr.DataArray |
get_ocean_temp(y0, y1, depth_m, region) |
Potential temperature (°C) | xr.DataArray |
get_salinity(y0, y1, depth_m, region) |
Salinity (PSU) | xr.DataArray |
get_currents(y0, y1, depth_m, region) |
U/V/speed (m/s) | xr.Dataset |
get_ssh(y0, y1, region) |
Sea Surface Height (m) | xr.DataArray |
open_ersst(y0, y1, region) |
ERSST v5 SST via OPeNDAP | xr.Dataset |
get_sst_series(y0, y1, box, source) |
Monthly Niño-box SST | pd.Series |
get_nino_anomaly(y0, y1, box, source, clim_start, clim_end) |
SST anomaly | pd.Series |
get_oni(y0, y1, source) |
Oceanic Niño Index | pd.Series |
classify_enso(oni) |
El Niño / La Niña / Neutral | pd.Series[str] |
get_thermocline_depth(y0, y1, region) |
D20 isotherm depth (m) | xr.DataArray |
get_warm_water_volume(y0, y1, max_depth) |
WWV index | pd.Series |
enso_summary(y0, y1) |
SST + anomaly + ONI + phase | pd.DataFrame |
All year_end parameters default to year_start (single year). All region
dicts use keys lat_min, lat_max, lon_min, lon_max (longitude 0–360).
BoundingBox
from noawclg import BoundingBox
bb = BoundingBox(toplat=15, bottomlat=-15, leftlon=-80, rightlon=-30)
Module overview
noawclg/
├── catalog.py — GFS variable catalogue and hour sequences (VARIABLES, HOURS_*)
├── coords.py — BoundingBox, auto_date, coordinate helpers
├── gfs_dataset.py — GFSDatasetManager (download, build, cache)
├── http.py — low-level GRIB2 download via NOMADS grib-filter
├── load.py — noawclg.load() one-liner wrapper
├── main.py — noawclg.main legacy entry-point
├── ocean.py — GODAS / ERSST: temperature, salinity, currents,
│ SSH, ENSO indices, WWV, D20, ONI classification
├── persistence.py — NetCDF4 / Zarr save and load
├── query.py — get_noaa_data() high-level interface
└── view.py — dataset inspection helpers
plots.py — 26 plot functions (GFS + ocean/ENSO)
enso_forecast.py — real-data ENSO analysis and probability model
Contributing
git clone https://github.com/reinanbr/noawclg
cd noawclg
pip install -e ".[dev]"
pytest tests/ -m "not integration"
Issues and pull requests are welcome on GitHub.
License
GPLv3 — see LICENSE.
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 noawclg-2.3.0.tar.gz.
File metadata
- Download URL: noawclg-2.3.0.tar.gz
- Upload date:
- Size: 63.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2cbb7d88d7429da2bd03cc1d75997c31e99c01e5134e5c8182aa3d48c42b7324
|
|
| MD5 |
5257e13b6dd80045c44c4cb46ef80bce
|
|
| BLAKE2b-256 |
12497fa24e3783d592f8c03f454ba18606a3e16424b944b5358f0704a84f0247
|
Provenance
The following attestation bundles were made for noawclg-2.3.0.tar.gz:
Publisher:
ci.yml on reinanbr/noawclg
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
noawclg-2.3.0.tar.gz -
Subject digest:
2cbb7d88d7429da2bd03cc1d75997c31e99c01e5134e5c8182aa3d48c42b7324 - Sigstore transparency entry: 1737617492
- Sigstore integration time:
-
Permalink:
reinanbr/noawclg@1e87b9136abd95d3b8e5d11b62d639ccb74cd3f0 -
Branch / Tag:
refs/tags/v2.3.0 - Owner: https://github.com/reinanbr
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@1e87b9136abd95d3b8e5d11b62d639ccb74cd3f0 -
Trigger Event:
push
-
Statement type:
File details
Details for the file noawclg-2.3.0-py3-none-any.whl.
File metadata
- Download URL: noawclg-2.3.0-py3-none-any.whl
- Upload date:
- Size: 46.2 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 |
1d06e4e6229dab5f06c6a32a17429e0e7c084fcd3f2e837e5260be84e2566725
|
|
| MD5 |
96575461623a2b550791e9c75004bc1d
|
|
| BLAKE2b-256 |
a327d0d116e8a0126e7582ff830219874d54d928a2379f40cc66f74f08861d19
|
Provenance
The following attestation bundles were made for noawclg-2.3.0-py3-none-any.whl:
Publisher:
ci.yml on reinanbr/noawclg
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
noawclg-2.3.0-py3-none-any.whl -
Subject digest:
1d06e4e6229dab5f06c6a32a17429e0e7c084fcd3f2e837e5260be84e2566725 - Sigstore transparency entry: 1737617506
- Sigstore integration time:
-
Permalink:
reinanbr/noawclg@1e87b9136abd95d3b8e5d11b62d639ccb74cd3f0 -
Branch / Tag:
refs/tags/v2.3.0 - Owner: https://github.com/reinanbr
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@1e87b9136abd95d3b8e5d11b62d639ccb74cd3f0 -
Trigger Event:
push
-
Statement type: