RadDB: Radar Database
| Deployment | |
| Activity | |
| Python Versions | |
| Supported Systems | |
| Project Status | |
| Build Status | |
| Linting | |
| Code Coverage | |
| Code Quality | |
| License |
| Citation | | Documentation
RadDB archives xarray DataTree radar volumes as compact Parquet files and gives you a small, fluent interface to load, filter, crop, extract cross-section and plot them. It is network-agnostic: any DataTree with the standard xradar coordinate layout (NEXRAD, ODIM, IRIS, …) can be archived and analysed.
Storage model
A radar is stored as a static LUT (per-gate geometry, generated once) plus one
POL parquet per volume (the variables), linked by an integer gate_id:
{archive_dir}/{radar}/LUT/{radar}_LUT.parquet # gate_id, lat/lon/alt, x_<epsg>/y_<epsg>, sweep, …
{archive_dir}/{radar}/{YYYY}/{MM}/{DD}/{radar}_{YYYYMMDD}_{HHMMSS}_POL.parquet
No-echo gates are dropped at archive time (default DBZH > 0), so the archive
stays small: a 12-sweep WSR-88D volume of 8,791,200 polar gates becomes 8.2 MB.
Installation
pip install raddb
pip install "raddb[viz]" # interactive Jupyter map and cartopy basemaps
Core runtime dependencies: numpy, pandas, polars, geopandas, shapely, pyproj, xarray, pyyaml, pyarrow, matplotlib, netcdf4, zarr.
Quick start
RadDB is a single, dual-role class:
- archive-bound —
db = RadDB(archive_dir, crs=…); use it toarchive()andopen(). - data-carrying — the object returned by
open()(call itrdf). It holds the data as a polars DataFrame (rdf.data) and exposes the query / convert / crop / cross-section / plot methods. Each of those returns a newRadDB, so calls chain fluently.
import raddb
db = raddb.RadDB(archive_dir="/data/raddb", crs=32614)
A projected CRS is mandatory to write an archive and never needed to read one.
There is no default: the wrong projection is silently wrong.
raddb.lut.suggest_crs(longitude, latitude) tells you which one to pass.
Archive
# From saved DataTree files on disk (.zarr / .nc); the LUT is auto-generated:
db.archive(datatree_dir="/data/NEXRAD_datatree") # radar inferred per file
# ...or archive in-memory DataTrees directly:
db.archive(datatree=dt, radar="KTLX")
db.archive(datatree=[dt1, dt2], radar="KTLX")
db.archive(datatree={"KTLX": [dt1], "KMLB": [dt2]}) # multi-radar
Pass filter= to decide which gates ever reach the disk — the main control on
archive size:
db.archive(
datatree=dt, radar="KTLX", filter={"var": "DBZH", "logic": ">", "threshold": 20}
)
Open
rdf = db.open(time_period=("2024-06-12", "2024-06-13"), radars="KTLX")
print(rdf) # summary: gates, radars, time range, columns
len(rdf), rdf.columns(), rdf.radars()
rdf.start_time(), rdf.end_time()
rdf.extent() # [xmin, xmax, ymin, ymax] in `crs`
rdf.geographic_extent() # [lon_min, lon_max, lat_min, lat_max]
rdf.crs(), rdf.geographic_crs()
columns= and filters= are pushed down into the scan, so only the rows you
asked for are ever materialised.
Filter and convert
filtered_rdf = rdf.filter({"var": "DBZH", "logic": ">", "threshold": 20})
filtered_rdf = rdf.filter(
[
{"var": "DBZH", "logic": ">", "threshold": 20},
{"var": "RHOHV", "logic": ">", "threshold": 0.9},
]
)
df = rdf.to_pandas(with_geometry=True) # pandas + gate coordinates
gdf = rdf.to_geopandas() # GeoDataFrame (with CRS)
dt = rdf.to_datatree() # back to xarray
Filters are {"var", "logic", "threshold"} dicts, where logic is one of
==, !=, >, >=, <, <=. crs is an EPSG int (e.g. 32614), a
CRS object, or None.
Crop to an area of interest
box = rdf.crop_by_bbox(extent=[636_504, 676_504, 3_891_333, 3_931_333])
poly = rdf.crop_by_polygone("catchment.geojson")
disc = rdf.crop_around_point((656_504, 3_911_333), distance=20_000) # metres
# rdf.interactive_crop() # draw an AOI on a Jupyter map
Cut a cross-section
cs = rdf.extract_cross_section(p1=(626_504, 3_911_333), p2=(686_504, 3_911_333))
Plot
rdf.plot_ppi(sweep=1, variable="DBZH", save="ppi.png")
rdf.plot_rhi(azimuth=270, variable="DBZH")
rdf.plot_cappi(altitude=3000, variable="DBZH")
cs.plot_cross_section(variable="DBZH", save="xsec.png")
Each plot draws into one Axes and returns the matplotlib artist, so you compose
panels by passing ax=.
Chain them
rdf.filter({"var": "DBZH", "logic": ">", "threshold": 20}).crop_by_bbox(
extent=rdf.extent()
).plot_ppi(variable="DBZH", save="ppi_plot_example.png")
What is on disk? (archive-bound)
db.inventory() # radars, volume counts, time ranges, size
db.inventory(detailed=True) # + LUT info, stored variables, day-by-day counts
db.inventory(datatree_dir="/data/NEXRAD_datatree") # DataTree files not archived yet
LUT accessors (archive-bound)
db.list_radars() # radars present in the archive
db.get_lut("KTLX") # the static LUT (polars)
db.get_radar_info("KTLX") # site location / sweep geometry
db.add_lut_projection("KTLX", epsg=32614)
Module structure
raddb/
├── __init__.py
├── main.py # the RadDB class
├── io_core.py # DataTree <-> DataFrame <-> Parquet + archive backends
├── lut.py # LUT generation / geo projection
├── aoi.py # AOI / crop / cross-section geometry
├── discovery.py # find_datatree_files + filename-time parsing
├── helper.py # filters, radar-name normalisation, timers
└── viz/ # plot.py (PPI/RHI/CAPPI/cross-section), interactive.py
Notes
- Projected coordinates /
crs. Generating a LUT with a projection (e.g.crs=32614) and the projected accessors (extent,to_geopandas) usepyproj, which needs the PROJ database. APROJ_DATA/PROJ_LIBinherited from another environment (a conda base env, a system PROJ) points at a proj.db of the wrong PROJ version and makes every projection fail with "no database context specified" —import raddbdetects that and repoints PROJ at the running interpreter's ownshare/proj(seeraddb/_proj.py;raddb.PROJ_DATAreports what it changed,Nonewhen nothing had to be). - Zarr / NetCDF. Saved volumes are read back with
raddb.open_any_datatree(engine auto-detected); either format works.
License
See LICENSE (MIT).
Metadata
Release files for raddb 0.0.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| raddb-0.0.3.tar.gz | 139.5 kB | Without provenance | Details for raddb-0.0.3.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| raddb-0.0.3-py3-none-any.whl | Python 3 | none | any | Details for raddb-0.0.3-py3-none-any.whl |
Total release size: 267.2 kB
Release files / raddb-0.0.3.tar.gz
| Download URL | raddb-0.0.3.tar.gz |
|---|---|
| Size | 139.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c67b2cf705fb34ca50e64d945113c9fdd7f2c30e6f77b11710c7ca782e4ac045
|
|
BLAKE2b-256 checksum How to use checksums |
f837a93fbd26b3a22fd09b0a1878ceec7e66ae77c8f992fac764668e25ea0de0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / raddb-0.0.3-py3-none-any.whl
| Download URL | raddb-0.0.3-py3-none-any.whl |
|---|---|
| Size | 127.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1ec3de9096812c133785ffd8d83618eb9ff5ed03afc7bf0a3256d0f879effa3c
|
|
BLAKE2b-256 checksum How to use checksums |
b486dbce7e992812d43d4ff7b27b1ab610a55d1ec7e232df20498e5335157321
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Security for raddb 0.0.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.