Recast Systems Recast Systems Radar Tools
Notes
Code
Recast Radar Tools / Field guide

Recast Radar
Field Guide

Read radar volumes, inspect fields, and convert files with Recast Radar Tools. This guide covers the Python package, Rust library, and command line, with examples for NEXRAD, ODIM_H5, CfRadial, DORADE, and JMA data.

open a Level II volume
import recast_radar

tree = recast_radar.open("KTLX20240315_000217_V06")   # xarray.DataTree
dbz = tree["sweep_0"]["DBZH"]                         # dBZ, NaN where no echo
print(float(dbz.max()), "dBZ")

radar = recast_radar.to_pyart("KTLX20240315_000217_V06")  # a pyart Radar
01

Install

The Python package ships prebuilt for Windows, macOS and Linux, so you do not need Rust. The Rust library needs Rust 1.94 or later.

Python 3.10+

Python package

One install gives you the Python API and the recast-radar command. Wheels cover Windows x64, Linux x86-64 and macOS (Apple silicon and Intel) for CPython 3.10 and later.

python -m pip install recast-radar
python -m recast_radar --help

Requires Python 3.10+. numpy and xarray 2024.10+ install with it. For Py-ART integration use pip install "recast-radar[pyart]".

Command line

Command-line program

The Python package installs the recast-radar command. python -m recast_radar runs the same program.

recast-radar --help
recast-radar info my_radar_file
Rust 1.94+

Rust crate

The Rust crates are not on crates.io yet. The Rust examples in this guide show the library's API; features select optional modules.

Default features include the supported readers and core algorithms. Add render, net, write, track, or full.

02

Radar basics

The diagrams below show beam height and velocity aliasing. For file-reading examples, go to Read a volume. To learn radar hands-on with a real scan, from the scan pattern down to the bytes in the file, open How Radar Sees.

In a typical PPI scan, a weather radar rotates at one elevation angle, then tilts to the next. Each rotation is a sweep; a sequence of sweeps forms a volume. A sweep contains rays at successive pointing directions, and each ray contains gates at successive ranges. Scan duration, angular sampling, and gate spacing depend on the radar and scan strategy. RHI and sector scans cover different paths.

Beam geometry

How high is the beam?

Beam height increases with range as the Earth curves beneath it. This diagram uses the standard 4/3 Earth-radius approximation and a 0.95° beam width. Heights are relative to radar altitude; terrain is not included.

0height, m
0ground distance, km
0beam width, m
Velocity aliasing

Why velocity folds

Radial velocities outside the Nyquist interval wrap into it. With a Nyquist velocity of 23.8 m/s, +30 m/s is reported as −17.6 m/s. Dealiasing estimates the number of wraps from other observations; the measured value alone is ambiguous.

0radar reports, m/s
0times folded
0input velocity, m/s

Radar measurements are stored as fields, also called moments. Common field names and units are listed below; availability depends on the source file.

DBZHdBZ

Reflectivity factor at horizontal polarization, expressed in dBZ. Echoes can come from precipitation or non-weather targets.

VRADHm/s

Radial velocity: the component of target motion along the beam. Negative is toward the radar; positive is away.

WRADHm/s

Spectrum width: the spread of radial velocities within a sample volume. Shear, turbulence, and signal quality can affect it.

ZDRdB

Differential reflectivity: the ratio of horizontal to vertical reflectivity, in dB. Large, flattened raindrops generally produce positive values.

RHOHVunitless

Correlation between horizontal and vertical returns. Lower values can indicate mixed particles, non-weather echoes, or poor signal quality.

PHIDPdegrees

Differential phase: the phase difference between horizontal and vertical returns. Its propagation component accumulates along the beam.

KDPdeg/km

Specific differential phase: a measure of how propagation phase changes with range. Used in rainfall estimation; it is not a rain rate.

VRADDHm/s

Dealiased velocity, added by the library when you unfold VRADH.

Radar background: NWS dual-polarization products and spectrum width.

DetailsBeam geometry and sweep elevation

Beam height is h = sqrt(r² + (k·a)² + 2·r·k·a·sinθ) − k·a with a = 6371 km and k = 4/3 (Doviak and Zrnić). trace_refracted_beam and RefractivityProfile replace the 4/3 assumption with a supplied refractivity profile, and propagation_regime classifies it as subrefractive, standard, superrefractive or ducting.

Use volume.tilt_elevation_deg(i) for the elevation the antenna actually scanned at. It can differ from fixed_angle_deg, the cut angle the scan strategy asked for, and the column products pick their base sweep by it.

03

Read a volume

Pass a file in a supported format. The reader detects the format from its contents and unwraps gzip or single-file ZIP compression.

read a supported format
import recast_radar

# NEXRAD, ODIM_H5, CfRadial 1 or 2, DORADE, JMA: the format is detected.
tree = recast_radar.open("bejab.pvol.hdf")          # xarray.DataTree, FM301 layout

tree["/"].attrs["instrument_name"]
tree["sweep_0"]["DBZH"].sel(azimuth=slice(90, 100)).load()

# Or keep the volume in Rust and look at its metadata without loading values.
volume = recast_radar.read("bejab.pvol.hdf")
volume.source_format, volume.nsweeps, volume.field_names
for sweep in volume.sweeps:
    print(sweep["fixed_angle"], sweep["nrays"], sweep["gate_spacing"], sweep["fields"])
BasicsChoosing a Python representation
  • open() returns an xarray DataTree. Start here if you use xarray, numpy or matplotlib. Packed field values are converted to physical values on access; file parsing happens when you open the file.
  • read() returns a Volume kept in Rust. Use it to check metadata, merge files, split scan cycles or write another format without converting any values in Python.
  • to_pyart() returns a pyart.core.Radar. Use it with Py-ART's plotting and algorithms. Field values and masks are checked against Py-ART on the project's test files.
Detailsxradar conventions and compatibility

The tree uses FM301 with xradar 0.12's naming conventions. Check the differences below when replacing xradar.io.open_*_datatree(). Useful options: first_dim="time" keeps rays in acquisition order, decode=False keeps the packed integers, flavor="wmo" uses FM301-2022 names, and passthrough="all" keeps source attributes xradar drops.

Deliberate differences from xradar: NEXRAD fields are NaN where there is no echo (xradar reads −33 dBZ), each NEXRAD sweep has one range at its finest gate spacing, and FM301 items like sweep_group_name and per-ray nyquist_velocity are always present. The engine also registers with xarray: xr.open_datatree(path, engine="recast_radar").

04

Data model

Supported volume readers use a shared model based on WMO FM301 (CfRadial 2). Source formats still differ in their fields, geometry, and available metadata.

VolumeA collection of sweepsvolume.sweeps

Site location, times, scan strategy (for example, a NEXRAD VCP), radar parameters, calibration, and source format.

SweepOne scan pathsweep.fields

Mode (PPI, RHI), fixed angle, the range coordinate and per-ray variables such as Nyquist velocity.

RayOne pointing directionsweep.rays.azimuth_deg[i]

Time, azimuth and elevation of each ray, stored as parallel arrays on the sweep.

GateOne range samplefield.gate(ray, gate)

A physical value, or a reason there is none.

The Rust model distinguishes valid measurements from undetect, range-folded, and missing gates. Conversion to floating-point arrays can collapse these states to NaN:

GateMeansIn Python
Gate::Value(v)A measurement in physical units (dBZ, m/s, ...)the number
Gate::UndetectBelow the source's detection threshold; this does not establish that the sampled air is clearNaN
Gate::RangeFoldedFlagged as range ambiguous by the sourceNaN, or flag 1 in <FIELD>_flags
Gate::MissingNo usable value is availableNaN
physical_values.rs, shortened
tree = recast_radar.open("KTLX20240315_000217_V06", range_folded_variable=True)
sweep = tree["sweep_0"]
dbz = sweep["DBZH"]                 # float32 dBZ, NaN for every non-value
folded = sweep["DBZH_flags"] == 1   # where range folding hid the echo

raw = recast_radar.open("KTLX20240315_000217_V06", decode=False)
raw["sweep_0"]["DBZH"].encoding     # the packing: scale_factor, add_offset, _FillValue
DetailsPacked values and native gate spacing

A field keeps the source's own encoding: 8- or 16-bit codes with a scale and offset (NEXRAD, ODIM, most CfRadial) or floats. Keeping packed values avoids allocating a floating-point copy of each field during decoding. You convert what you need: gate() and value() for single gates, to_physical() for a whole field, and lut8 for 8-bit codes through a lookup table.

A field can have coarser gates than its sweep (NEXRAD's 1 km legacy reflectivity on a 250 m range). field.native_geometry(&sweep.range) gives its first gate and spacing, for use by the renderer and algorithms. Values the model has no typed slot for are kept in other, extra_vars and variable_attrs.

05

Cookbook

Examples for common tasks, including native processing and rendering from Python. Some snippets assume an existing volume, sweep, or field; replace the sample paths with your files. Output blocks marked as full examples come from the project's longer test examples.

Get data

Download the newest NEXRAD volume

AWSLevel IIfeature net

The public NEXRAD archive on AWS uses the unidata-nexrad-level2 bucket. This example downloads the latest available volume and reuses a local copy on subsequent calls.

fetch.realtime("KTLX") assembles the volume being scanned right now from the real-time chunks, and fetch.level3 gets Level III products.

from datetime import datetime
from recast_radar import fetch
import recast_radar

paths = fetch.level2("KTLX", count=1, dest="data")        # newest volume
tree = recast_radar.open(paths[0])

# A moment in history (times are UTC):
fetch.level2("KTLX", datetime(2024, 3, 15, 0, 5), dest="data", count=2)
data, info = fetch.realtime("KTLX")                        # the scan in progress
Science

Locate peak reflectivity

Any formatGeometry

The Python snippet finds peak reflectivity in sweep 0. The Rust snippet converts a selected ray and gate to ground range and beam height. Sweep 0 is not necessarily the lowest elevation in a volume.

The sample output places the beam centre 1.7 km above sea level at 94 km range. Beam height depends on range, elevation, radar altitude, and the refraction model.

tree = recast_radar.open("KTLX20240315_000217_V06")
dbz = tree["sweep_0"]["DBZH"].load()

peak = dbz.where(dbz == dbz.max(), drop=True)
print(f"{float(dbz.max()):.1f} dBZ at azimuth",
      peak.azimuth.values, "range", peak.range.values / 1000, "km")
Science

Dealias radial velocity

Dopplerfeature correct

Apply region-based dealiasing to sweeps containing radial velocity. The result is a new field, VRADDH, on the same rays and gates, kept beside the raw velocity.

Choose dealias_method="region", "pyart", or "volume". The volume method accepts optional previous and environment inputs and reports whether they were used. Its confidence field ranges from 0 (no opinion) to 255.

use recast_radar_tools::model::Quantity;
use recast_radar_tools::{correct, nexrad};

let mut volume = nexrad::read_volume_from_path("KTLX20240315_000217_V06")?;
for sweep in &mut volume.sweeps {
    let Some(raw) = sweep.find(Quantity::RadialVelocity) else { continue };
    let dealiased = correct::dealias_velocity(sweep, raw);   // VRADDH
    sweep.add_field(dealiased)?;
}
Output of the full example (first lines)
 0.48 deg: Nyquist 23.8 m/s, 1833 gates unfolded
 0.88 deg: Nyquist 23.8 m/s, 2459 gates unfolded
 0.48 deg: Nyquist 23.8 m/s, 1878 gates unfolded
Science

Composite reflectivity, echo tops and VIL

Column productsfeature map

Column products combine measurements across elevation angles. Composite reflectivity, echo tops, and vertically integrated liquid (VIL) are returned on the base sweep's geometry and can be added to that sweep for rendering.

The default echo-top threshold comes from Rust: 18.3 dBZ. Set threshold_dbz in Python or --threshold-dbz in the CLI to change it. products() / recast-radar products lists the available products.

use recast_radar_tools::{io, map};

let volume = io::read_supported_volume_bytes(&std::fs::read(path)?)?;
let base = map::column_base_sweep(&volume).ok_or("no tilt has reflectivity")?;
let cref = map::composite_reflectivity(&volume).ok_or("no composite")?;
let tops = map::echo_top(&volume, map::ECHO_TOP_THRESHOLD_DBZ).ok_or("no tops")?;
let vil  = map::vil(&volume).ok_or("no VIL")?;
Output of the full example on KTLX20240315_000217_V06
column base: sweep 4 at 0.41 deg
CREF: maximum 72.5 dBZ at azimuth 135.8 deg, 95.6 km
ET: maximum 19366.5 m at azimuth 97.7 deg, 362.9 km
VIL: maximum 75.3 kg m-2 at azimuth 167.7 deg, 236.1 km
Images & files

Render a sweep to PNG

feature renderGR .pal palettes

The default output is a 1024×1024 RGBA image with a transparent background and the radar at the centre. For packed fields, the renderer maps codes through a palette without expanding the full field to floats.

For maps, ViewportRasterOptions places the radar at a pixel with a km-per-pixel scale, and a ViewportFieldCache reuses field data across frames.

recast-radar render FILE -o dbz.png                    # first sweep with reflectivity
recast-radar render FILE --sweep 3 --field ZDR -o zdr.png
recast-radar render FILE --all-sweeps --field DBZH -o ./frames/
recast-radar render FILE --palette BR.pal --size 2048 -o dbz.png
Images & files

Convert between formats

Level IICfRadial 1FM301ODIM_H5

Convert supported inputs to NEXRAD Level II, CfRadial 1, CfRadial 2 / FM301, or ODIM_H5. The output format may not represent all of the source fields or metadata; review the writer's report.

The Level II writer reports omitted fields and unsupported values. Use strict=True in Python or --strict on the command line to reject omissions; see Troubleshooting for other writer errors.

recast_radar.convert("dkrom.pvol.h5", "dkrom.ar2v", "level2")

volume = recast_radar.read("dkrom.pvol.h5")
volume.write("dkrom.cf1.nc", "cfradial1")
volume.write("dkrom.fm301.nc", "fm301")
data = volume.to_bytes("odim")                 # bytes in memory
recast_radar.writers()                         # which writers this build has
Images & files

Publish a GR2Analyst polling feed

GRLevelXdir.list

Write volumes into a polling directory that GR2Analyst can read: config.cfg, grlevel2.cfg, one folder per site and a dir.list of the newest files. Then serve it over HTTP.

The publisher writes each file and dir.list under a temporary name, then renames it after the write completes.

recast-radar publish bejab.pvol.h5 --dir ./polling --site EBJB --keep 30
recast-radar serve ./polling --bind 0.0.0.0:8080     # point GR2Analyst here
Get data

Fetch international radar data

14 providersODIMJMA

SMHI, NCI Australia, DMI, GeoSphere Austria, FMI, SHMU, DWD, CHMI, ARPA Piemonte and Lombardia, JMA, KAIA Estonia, Meteo Romania and EUMETNET ORD. The fetcher merges frames distributed across several files.

The SMHI, NCI, and ORD integrations support archived queries with date= and when=. Use the provider listing to check available sources.

from recast_radar import fetch

fetch.intl_providers()                           # dmi, fmi, smhi, dwd, ord, jma, ...
site = fetch.intl_sites("dmi")[0]["id"]          # "06036" (Sindal)
volumes = fetch.intl("dmi", site)                # decoded, split frames merged
fetch.intl_frames("ord", "bejab", date="2026-09-24")   # a day of archived frames
Other tools

Use Py-ART and xarray

Py-ARTxradarxarray engine

Use Recast to decode files for Py-ART and xarray workflows. to_pyart accepts a path, bytes, a Volume or a DataTree, including one from xradar.

field_names="config" gives Py-ART's default names (reflectivity, velocity, ...), which its algorithms look for.

import xarray as xr, pyart, recast_radar

radar = recast_radar.to_pyart("KTLX20240315_000217_V06")
gatefilter = pyart.filters.GateFilter(radar)

tree = xr.open_datatree("KTLX20240315_000217_V06", engine="recast_radar")
ds = xr.open_dataset("KTLX20240315_000217_V06", engine="recast_radar", group="sweep_0")

# Save with xarray: use the WMO flavor, which netCDF can hold.
recast_radar.open(path, flavor="wmo", first_dim="time").to_netcdf("out.nc")
Science

Split a file into scan cycles

JMAmerge

The Level II writer expects one scan cycle per volume. The JMA tar in this example contains two cycles; split them before writing separate Level II files.

merge joins per-moment or per-sweep files of one scan (ODIM, DWD) and pairs sweeps only when their first rays are within 60 s.

itok = recast_radar.read(
    "Z__C_RJTD_20260924210000_RDR_JMAGPV_N5_grib2.RS47937.tar", station="ITOK")
for n, cycle in enumerate(recast_radar.split_scan_cycles(itok), start=1):
    cycle.write(f"ITOK_{n}.ar2v", "level2")

merged = recast_radar.merge([recast_radar.read(p) for p in parts])
Other tools

Read Level III storm tracks

Level IIINST

Graphic and tabular Level III products (storm tracking, hail, mesocyclone, TVS) contain structured product data rather than a volume of gate measurements. Use dump to inspect packets, storm identifiers, and positions as dictionaries.

nst = recast_radar.dump("KDVN_SDUS33_NSTDVN_202008101804")
for layer in nst["level3"]["symbology"]["layers"]:
    for packet in layer:
        for storm in packet["packet"].get("StormIds", []):
            print(storm["id"], storm["i"] / 4, storm["j"] / 4)   # km east, north
Get data

Check a folder of files

QC

Validate files in a directory for model consistency, site location, ray coordinates, and plausible field values. Results are reported as OK, WARN, or FAIL.

recast-radar validate -r ./archive          # --json for a report, --strict to fail on warnings
Output
OK    KTLX20240315_000217.trim.V06  (NEXRAD Level II: KTLX 2024-03-15T00:02:17Z 2 sweep(s))
WARN  KLIX20050829_130035.trim.V06  (NEXRAD Level II: KLIX 2005-08-29T13:00:12Z 2 sweep(s))
      warning: has no location
FAIL  config.cfg
      error: does not decode: not a recognised radar file (...)
3 file(s): 1 ok, 1 with warnings, 1 failed
Science

Compute sweep products

Derived fieldsPython & CLI

Use the Rust retrievals for KDP, attenuation corrections, rain-rate estimates, textures, gradients, and quality diagnostics. Specify the radar band for products that require it.

Inputs remain unchanged. Results report missing source fields, existing fields kept, and actual output names. Use strict=True / --strict to reject unavailable products.

Science

Create a vertical section or RHI panel

SectionsPython & CLI

Cross sections sample a path through a volume. RHI panels sample an existing vertical scan. Python returns an xarray DataArray with height and distance coordinates in metres; heights are relative to radar altitude.

Endpoints are kilometres east and north of the radar. For the CLI, save these options: {"field":"DBZH","start_km":[0,0],"end_km":[100,-100],"width":512,"height":256,"top_m":12000}. The output contains coordinates, shape, units, and values; missing values are JSON null.

Science

Grid one or more radars

GridsPython & CLI

Map gates onto a Cartesian grid with the existing Rust weighting and geometry. Python returns an xarray Dataset with coordinates, field units, projection metadata when available, and radius of influence.

CLI options: {"fields":["DBZH"],"shape":[10,101,101],"limits_m":[[0,9000],[-100000,100000],[-100000,100000]]}. Both interfaces accept origin, weighting, and radius_m.

06

Command line

Use recast-radar <command> --help for command options. Prefix the examples in the table with recast-radar.

CommandWhat it doesExample arguments
infoSummary: site, times, scan strategy, a table of sweepsinfo --merge a.h5 b.h5
dumpEvery decoded value, as text or JSONdump --json --data --sweep 0 --field DBZH
renderOne field of one sweep to PNGrender FILE --field velocity --dealias
validateDecode and check files or foldersvalidate -r ./archive --strict
benchTime decodes of your own filesbench --threads 1 -n 5 FILE
fetchAWS Level II, chunks, Level III, international, polling servers, site listfetch level2 KTLX -n 3
convertWrite Level II, CfRadial 1, ODIM_H5 or FM301convert FILE --to fm301 -o out.nc
publishAdd volumes to a GR2Analyst polling directorypublish FILE --dir ./polling
serveServe a directory over HTTP (GET and HEAD, no TLS)serve ./polling --bind 0.0.0.0:8080
productsList products available to the Python and CLI processing APIproducts
processCompute products and write an augmented volumeprocess FILE --products CREF,ET,VIL -o products.nc
sectionCreate a vertical section or native RHI panelsection FILE --options section-options.json -o section.json
gridGrid one or more radar volumesgrid FILE --options grid-options.json -o grid.json
DetailsExit status and Level II writer options

Exit codes: 0 success, 1 a file failed, 2 bad usage, 3 the feature is not in this build. Inputs over 1 GiB are refused.

--quantization precise|compatible|standard: precise (default) never codes a value more coarsely than its source, compatible keeps NEXRAD's word sizes that xradar 0.12 reads, standard writes NOAA's codings where they hold every value. No policy clips. Also --nyquist, --unambiguous-range, --position LAT,LON,HEIGHT, --position-from FILE, --sweeps 0,2,5-9, --sweeps-in-time-order, --drop-negative-range-gates, --level2-compression none, --strict and --threads N.

07

Formats

Detected from the bytes. gzip and single-file ZIP wrappers are removed first.

FormatReadWriteCovers
NEXRAD Level IIyesyesArchive II back to 1991 (ARCHIVE2, AR2V), Message 1 and 31, uncompressed, gzip, bzip2, LDM records, files cut short; every metadata message
Level II real-time chunksyesyesThe unidata-nexrad-level2-chunks layout: S, I and E chunks
NEXRAD and TDWR Level IIIyesno95 product codes: radial, raster, generic, graphic and tabular products, status and text messages
ODIM_H5yesyesPolar volumes and scans, v2.2 to 2.4, any HDF5 superblock; Cartesian grids through the Rust API
CfRadial 1yesyesClassic netCDF and netCDF-4
CfRadial 2 / FM301yesyesnetCDF-4, one group per sweep
DORADEyesnoSweep files, airborne scans, mobile-radar ZIP archives (DOW, COW, RaXPol)
JMA radar GRIB2yesnoNICT RDR_JMAGPV tars, every station, or pick one
GR2Analyst polling directoryyesyesconfig.cfg, grlevel2.cfg, dir.list
PNG–yesAny field, radar-centred or map viewport; GR .pal palettes
DetailsUnsupported formats and encodings

Sigmet/IRIS RAW, Rainbow 5, Universal Format, GAMIC HDF5, Furuno, NCAR MDV, CSU-CHILL, RAPIC, CINRAD, MRMS GRIB2 and Level 1 I/Q. CDF-5 netCDF, and HDF5 filters other than deflate, shuffle and Fletcher-32 (szip, LZF, n-bit, scale-offset).

08

Benchmarks

Time to decode a whole file into memory from Python, in milliseconds: every field of every sweep, file read included. Each reader is pinned to one CPU core with one thread. Lower is better; the multiplier is how long the other reader takes relative to recast-radar.

Into xarray and NumPy

recast-radar open(path).load(), which returns an xarray DataTree with every field as floats, against xradar's open_*_datatree(path).load(), MetPy's Level2File / Level3File and wradlib's readers.

Filerecast-radarxradarMetPywradlib
Level II, KTLX 2024 (bzip2 records)7443,686 5.0×1,573 2.1×–
Level II, KTLX 2013 (gzip file)313fails731 2.3×–
Level III, TLX N0B16–18 ≈–
ODIM_H5 volume, DMI Rømø (dual-pol)76259 3.4×–60*
CfRadial 1, NCAR S-Pol1343,204 24×–222 1.7×

Into a Py-ART Radar

recast-radar to_pyart(path) against Py-ART's own readers (read_nexrad_archive, read_nexrad_level3, aux_io.read_odim_h5, read_cfradial). Both return the same Py-ART Radar object with masked float fields.

Filerecast-radarPy-ART
Level II, KTLX 2024 (bzip2 records)8061,716 2.1×
Level II, KTLX 2013 (gzip file)387634 1.6×
Level III, TLX N0B1624 1.4×
ODIM_H5 volume, DMI Rømø (dual-pol)71167 2.3×
CfRadial 1, NCAR S-Pol115157 1.4×

≈ means the two readers' round timings overlap, so neither is faster. * wradlib's ODIM reader returns the stored bytes (uint8) without converting them to physical values, so it does less work than the other readers. xradar 0.12 raises a TypeError on the gzip-compressed Level II file.

These timings use one core. By default recast-radar also spreads the decode over several cores, which helps most on large Level II files; the other readers run on one thread either way.

How this was measured. 29 September 2026. Windows 11, AMD Ryzen 9 9950X3D, reader pinned to one core, idle machine. recast-radar 0.1.0 (release build), xradar 0.12.0, Py-ART 2.3.0, MetPy 1.7.1, wradlib 2.9.6, Python 3.13.7. Each reader runs in its own process: one warm-up decode, then 5 timed decodes, repeated over 5 rounds in rotating order. Each cell is the median of the round medians. The script and the public input files are in bench/decode_bench.py; run python decode_bench.py fetch, then python decode_bench.py run.
09

Implementation notes

Error handling, metadata storage, and runtime configuration.

Input validation and fuzzing

Project crates forbid unsafe, and library code avoids unwrap and expect. Parsers return errors for malformed inputs and are exercised by fuzz tests.

Decoder limits

Decoders limit decompressed size, ray counts, gates per ray, sweeps, and archive members. Inputs that exceed a limit return an error.

Additional metadata

Values without a typed slot stay in other, extra_vars and variable_attrs. Level II metadata messages and per-radial values are retained alongside the volume.

Function naming

read_* returns a Volume, decode_* returns a format's own structures, looks_like_* sniffs without decoding.

Thread pool

Decoders and algorithms use rayon's global pool. Set RAYON_NUM_THREADS, or run inside your own pool's install.

WebAssembly

Every crate builds for wasm32-unknown-unknown except network access. Use the byte entry points there, such as read_supported_volume_bytes.

DetailsFeatures, enums and errors

Features: the defaults (io, correct, filters, retrieve, map) read every format and run the algorithms. Add write, render, net, track, scattering, serde, or full. Only net makes network requests or compiles C.

Public enums and errors are #[non_exhaustive], so a match needs a wildcard arm. The storage types (FieldData, Coding, LinearTransform, ...) are deliberately closed so a writer or renderer must handle every one. Each crate has its own thiserror error enum, and all of them work with ? and Box<dyn Error>.

10

Troubleshooting

Common errors and the options to check.

You seeIt meansDo this
DecodeErrorNot a radar file this library reads, or it is damaged. Also raised for Level III products with no data arrayRun recast-radar validate FILE; use dump for graphic and tabular Level III
UnrepresentableErrorThe output format cannot hold something in the volumeRead the message: it names the keyword that helps, like position= or sweeps=
Level II refuses a volumeNo site position, more than 32 sweeps, an RHI, or sweeps from two scan cyclesposition=, sweeps=, split_scan_cycles
WriteWarningA field was left out, or coded more coarsely than the sourcePass strict=True to make it an error instead
UnavailableErrorThis build has no writer, publisher or network supportUse a full build; check recast_radar.writers()
FetchErrorA download or listing failedCheck the provider, site, time, and network response. The SMHI, NCI, and ORD integrations support archived queries