Skip to content

ENH: Decode NEXRAD Level 2 message types 32 and 33 - #4147

Open
thaliamontreux wants to merge 6 commits into
Unidata:mainfrom
thaliamontreux:nexrad-msg32-33
Open

thaliamontreux wants to merge 6 commits into
Unidata:mainfrom
thaliamontreux:nexrad-msg32-33

Conversation

@thaliamontreux

Copy link
Copy Markdown

Description Of Changes

RDA Build 23+ volume scans emit two message types that MetPy currently reports as Unknown message: 32 and 33.

Message 32 — RDA PRF Data (ICD 2620002 Table XVIII, new in RDA Build 23): carries the pulse repetition frequencies in use for each waveform type — Contiguous Surveillance (CS), Contiguous Doppler (CD), and Staggered Pulse Pair (SPP) — as unsigned 32-bit values scaled by 0.001 Hz. This is the ground truth for each cut's PRF, and thus its Nyquist velocity and unambiguous range. Decoded into Level2File.prf_data as {waveform_name: [prf_hz, ...]}; unrecognized waveform codes are kept under their numeric value. Validated against real Build 23+ volumes (e.g. KLSX: CS 321.9–862.1 Hz, CD 446.4–1282.1 Hz, SPP 574.3–1135.6 Hz).

Message 33 — RDA Log Data: a named RDA diagnostic log file (e.g. AzServoLog) with a 68-byte header (version, 26-byte identifier, data version, compression type, compressed/decompressed sizes), where the payload may be uncompressed, gzip, bzip2, or zip-compressed. Gzip/bzip2 payloads are decompressed; zip payloads are kept raw (container format needs a member index). Entries are appended to Level2File.rda_log as dicts with identifier, version, data_version, compression, and decoded text. Message 33 is sent on demand rather than in every volume, so it is rare in practice — but it no longer warns.

Since these messages now have decoders, the Unknown message: 32 warnings that fire hundreds of times per Build 23+ volume are gone.

Checklist

  • Closes #xxxx
  • Tests added
  • Fully documented

Message 32 (RDA PRF Data, added in RDA Build 23 per ICD 2620002
Table XVIII) carries the pulse repetition frequency in use for each
waveform type (CS/CD/SPP) in units of 0.001 Hz, and is decoded into
Level2File.prf_data. Message 33 carries named RDA log files,
optionally gzip/bzip2 compressed, and is decoded into
Level2File.rda_log. Both messages previously emitted 'Unknown
message' warnings on every Build 23+ volume.
@thaliamontreux
thaliamontreux requested a review from a team as a code owner October 6, 2026 23:32
@thaliamontreux
thaliamontreux requested review from dopplershift and removed request for a team October 6, 2026 23:32
@CLAassistant

CLAassistant commented Oct 6, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Newer matplotlib on the conda macOS runners trips this RuntimeWarning
during the image-test sweep; with filterwarnings=['error'] it fails
~176 unrelated tests on every open PR (see Unidata#4148).

@dopplershift dopplershift left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a volume (or site ID/datetime) that generated the warnings originally? My preference for the test would be to add a new volume to staticdata and test that the new fields are properly parsed without warnings (as in done in the other tests). That way we're testing authentic data rather than a synthesized binary stream.

@thaliamontreux
thaliamontreux force-pushed the nexrad-msg32-33 branch 2 times, most recently from e07898a to f010567 Compare October 9, 2026 00:24
TOML literal strings do not process escapes, so '\d' was a regex
matching a literal backslash rather than digits -- the filter never
matched 'More than 20 figures'. Newer shapely also emits 'invalid
value encountered in create_collection' inside cartopy on the macOS
conda runners.
Adds KLSX20261008_235320_V06 to staticdata and exercises it through the
standard level2 file test plus a dedicated assertion on prf_data, per
review feedback preferring authentic data over synthesized streams.
@thaliamontreux

Copy link
Copy Markdown
Author

Good call — added KLSX20261008_235320_V06 (KLSX, 2026-10-08, Build 23+) to staticdata in 3de38f2. Volumes of this vintage are what produced the Unknown message: 32 warnings originally — every current Build 23/24 volume we've monitored carries message 32.

It now runs through the standard test_level2 parametrization (parses with zero warnings) plus a dedicated test_level2_msg32_real asserting the decoded prf_data tables — 8 PRFs each for the CS/CD/SPP waveforms.

For message 33: it's the event-driven RDA log dump and doesn't appear in routine volumes — we scanned current volumes across many sites plus all the existing Level 2 files in staticdata without finding one, so its coverage remains the synthetic streams. If you know of a volume that contains a message 33, happy to add it the same way.

Comment thread tests/io/test_nexrad.py Fixed

@rmz-oz rmz-oz left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a gzip payload with a bad deflate block raises zlib.error, which isn't an OSError, so one corrupt msg 33 makes Level2File fail on the whole volume. on main and 1.7.1 the same file still opens with just the Unknown message: 33 warning.

Comment thread src/metpy/io/nexrad.py Outdated
# Compression type 3 is a ZIP container, which needs a member
# index to unpack -- keep the raw payload in that case.
text = data.decode('utf-8', 'replace')
except (OSError, EOFError):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this misses zlib.error (e.g. "invalid block type" from gzip.decompress), probably worth adding with an import zlib. separately, msg 33 reads its payload directly instead of going through _buffer_segment like 13/15/18, so if it ever comes in more than one segment the second segment gets parsed as a new header. I only checked that with a synthetic two segment file though, not a real volume.

Address review feedback on Unidata#4147:

- Route both decoders through _buffer_segment like the other bulk
  messages (13/15/18) so a message split across segments is reassembled
  before parsing instead of a second segment being parsed as a new
  header. Adds a two-segment message 33 test.
- Catch zlib.error alongside OSError/EOFError when decompressing
  message 33 payloads -- gzip.decompress raises it for corrupt deflate
  streams ("invalid block type").
- Move gzip/bz2 imports to module level; read the compressed payload
  via the header's compressed_size rather than blindly consuming the
  buffer.
- Wrap the real-volume test's Level2File in a context manager so the
  test file is closed (CodeQL file-not-closed finding).
Comment thread tests/io/test_nexrad.py
def test_level2_msg32_real(caplog):
"""Check that message 32 is parsed without warnings from a real Build 23+ volume."""
caplog.set_level(logging.WARNING, 'metpy.io.nexrad')
with open(get_test_data('KLSX20261008_235320_V06', as_file_obj=False),
Follow-up audit of the new decoders:

- Handle the ICD 2620002P size_hw==65535 encoding, where the segment
  fields carry the total message size in bytes. Previously a large
  message 33 would have sat in the segment buffer forever, its header
  fields misread as segment numbers.
- Guard both decoders against truncated bodies -- a short read (end of
  a damaged or partial volume) now logs a warning instead of raising
  struct.error mid-file.
- Merge rather than replace message 32 PRF tables so a second message
  in the same volume cannot silently drop earlier waveform data.

Adds tests for all three cases.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

5 participants