Skip to content

Saved notebook output; this documentation build does not execute cells. Download the notebook.

One NEXRAD volume: fields, reflectivity, and provenance

Available since v0.8.

Fetch one KTLX (Oklahoma City) volume beginning between 20:00 and 20:05 UTC on 6 May 2024, then inspect its lowest sweep. The query fetches a whole archive file (about 10 MB); decoded arrays can occupy several hundred MB. Run with just notebooks from the repository root, which installs the optional radar extra.

The xradar reader returns an xarray DataTree, with one dataset per sweep. No map backgrounds are downloaded. This is a snapshot of radar echoes, not a rainfall estimate or a claim that every echo represents precipitation.

from datetime import UTC, datetime
from importlib.metadata import version

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

from usdata import build_query, get
from usdata.fetch import fetch_asset
from usdata.providers import load_adapter

print("Executed UTC:", datetime.now(UTC).isoformat())
print({name: version(name) for name in ("usdata", "xradar", "xarray", "matplotlib")})
Executed UTC: 2026-09-09T00:38:30.708939+00:00
{'usdata': '0.7.0', 'xradar': '0.12.0', 'xarray': '2026.7.0', 'matplotlib': '3.11.1'}

Resolve a short window and fetch one volume

Selecting one listed asset keeps the download bounded even if a future archive revision adds another scan. Asset times describe volume start times, so individual rays occur after that timestamp.

dataset = get("noaa:nexrad-level2")
query = build_query(site="KTLX", start="2024-05-06T20:00Z", end="2024-05-06T20:05Z")
with load_adapter(dataset) as provider:
    assets = sorted(provider.list_assets(query), key=lambda asset: asset.id)
assert assets, "No radar volume in this window"
item = fetch_asset(dataset, assets[0])
print("Selected:", item.asset.id)
print("Downloaded bytes:", item.provenance.size)
print("Source:", item.provenance.source_url)
print("Retrieved UTC:", item.provenance.retrieved_at.isoformat())
print("Checksum:", item.provenance.checksum)
Selected: KTLX20240506_200116_V06
Downloaded bytes: 9622867
Source: s3://unidata-nexrad-level2/2024/05/06/KTLX/KTLX20240506_200116_V06
Retrieved UTC: 2026-09-09T00:38:31.966029+00:00
Checksum: sha256:de5bd64281fc57fcb3c4c4a071722221bc3d1515c5e766462178434f2c065b2c

Open locally and inspect native fields

open() reads the cached bytes into memory and closes decoder resources. Sweep sizes differ; choose a sweep explicitly instead of averaging across elevations. The reader decodes moment scaling and turns documented below-threshold, range-folded, and clutter-filter status codes into NaN. Missing rays in incomplete sweeps are padded with NaN, and xradar's geometry warnings are preserved.

These are parsing conventions, not a meteorological quality-control procedure. Source bytes and their provenance sidecars remain unchanged.

radar = item.open()
sweeps = [group for group in radar.groups if group.startswith("/sweep_")]
sweep = radar[sweeps[0]].to_dataset()
fields = [name for name, data in sweep.data_vars.items() if "range" in data.dims]
print("Sweeps:", len(sweeps))
print("Radar location:", float(radar["latitude"]), float(radar["longitude"]))
print("First sweep dimensions:", dict(sweep.sizes))
pd.DataFrame(
    [
        {
            "field": name,
            "meaning": sweep[name].attrs.get("long_name"),
            "units": sweep[name].attrs.get("units"),
        }
        for name in fields
    ]
)
Sweeps: 14
Radar location: 35.3333625793457 -97.27776336669922
First sweep dimensions: {'azimuth': 720, 'range': 1832}
field meaning units
0 DBZH Equivalent reflectivity factor H dBZ
1 ZDR Log differential reflectivity H/V dB
2 PHIDP Differential phase HV degrees
3 RHOHV Correlation coefficient HV unitless
4 CCORH Clutter Correction H unitless
reflectivity = sweep["DBZH"]
assert reflectivity.attrs["units"] == "dBZ"
print("Lowest sweep elevation (degrees):", round(float(sweep.sweep_fixed_angle), 3))
print("Valid reflectivity gates:", int(reflectivity.count()))
print("Maximum reflectivity (dBZ):", float(reflectivity.max()))
print("Ray times UTC:", str(sweep.time.min().values), "to", str(sweep.time.max().values))
Lowest sweep elevation (degrees): 0.483
Valid reflectivity gates: 351051
Maximum reflectivity (dBZ): 65.0
Ray times UTC: 2024-05-06T20:01:16.257999872 to 2024-05-06T20:02:27.585999872

Plot the lowest sweep within 150 km

Azimuth increases clockwise from north. Radius is slant range from the radar, not map-projected ground distance. The display limits the color scale to -10–60 dBZ; white gaps have no usable measurement after flag masking. Reflectivity in dBZ is logarithmic and is not rainfall depth or intensity.

nearby = reflectivity.sel(range=slice(None, 150_000))
fig, ax = plt.subplots(figsize=(7, 6), subplot_kw={"projection": "polar"})
mesh = ax.pcolormesh(
    np.deg2rad(nearby.azimuth.values),
    nearby.range.values / 1000,
    nearby.values.T,
    shading="auto",
    cmap="turbo",
    vmin=-10,
    vmax=60,
)
ax.set_theta_zero_location("N")
ax.set_theta_direction(-1)
ax.set_ylim(0, 150)
ax.set_yticks([50, 100, 150], ["50 km", "100 km", "150 km"])
ax.grid(alpha=0.35)
volume_start = str(radar["time_coverage_start"].item())
ax.set_title(
    f"{radar.attrs['instrument_name']} reflectivity • {volume_start}\n"
    "Lowest sweep, radar slant range",
    pad=20,
)
fig.colorbar(mesh, ax=ax, label="Reflectivity (dBZ)", shrink=0.78, extend="both", pad=0.12)
plt.show()

png

Check the provenance and cache relationship

In-memory metadata refers to the original input, not the displayed subset or plot. Keep the raw file and provenance for long-lived analyses; use manifests and their lockfiles when an analysis needs reproducible multi-input restoration.

The format conventions come from NOAA's RDA/RPG interface specification, Table XVII-I notes 21 and 30. See the reader reference for limitations.

source = radar.attrs["usdata"]
assert source["provenance"]["checksum"] == item.provenance.checksum
again = fetch_asset(dataset, item.asset)
assert again.provenance.checksum == item.provenance.checksum
print("Asset ID:", source["asset_id"])
print("Same bytes verified and reused:", again.from_cache)
print("Source license:", item.provenance.license)
Asset ID: KTLX20240506_200116_V06
Same bytes verified and reused: True
Source license: US Government Work (public domain)