Generic radar data archiving library — archive xarray DataTree volumes as Parquet
Project description
RadDB — generic radar data archiving & analysis
RadDB archives xarray DataTree radar volumes as compact Parquet files and gives you a small, fluent object API to load, filter, crop, cross-section and plot them. It is network-agnostic: any DataTree with the standard xradar coordinate layout (MeteoSwiss, NEXRAD, …) can be archived and analysed — no pyart required.
MeteoSwiss/METRANET-specific ingestion (pyart + radar_api) lives in the private
raddb.mch subpackage and is kept out of the generic core.
Storage model
A radar is stored as a static LUT (per-gate geometry, generated once) plus one
POL parquet per volume (the dynamic moments), 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.
Installation
pip install -e . # core
pip install -e ".[viz]" # + cartopy / pyproj / shapely for maps
Core runtime deps: numpy, pandas, polars, geopandas, xarray, pyarrow, dask, fsspec, s3fs, matplotlib.
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=2056) # 2056 = CH1903+/LV95
# --- archive -----------------------------------------------------------------
# From saved DataTree files on disk (.zarr / .nc); the LUT is auto-generated:
db.archive(datatree_dir="/data/MCH_datatree") # radar inferred per file
# ...or archive in-memory DataTrees directly:
# db.archive(datatree=dt, radar="A")
# db.archive(datatree=[dt1, dt2], radar="A")
# db.archive(datatree={"A": [dt1], "D": [dt2]}) # multi-radar
# --- open --------------------------------------------------------------------
rdf = db.open(time_period=("2024-08-26", "2024-08-27"), radars="L")
print(rdf) # rich 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()
# --- filter / convert --------------------------------------------------------
strong = rdf.filter({"var": "DBZH", "logic": ">", "threshold": 20})
strong = rdf.filter([{"var": "DBZH", "logic": ">", "threshold": 20},
{"var": "RHOHV", "logic": ">", "threshold": 0.9}]) # AND
pdf = rdf.to_pandas(with_geometry=True) # pandas + gate coordinates
gdf = rdf.to_geopandas() # GeoDataFrame (with CRS)
dt = rdf.to_datatree() # xarray DataTree (for plotting)
# --- area of interest --------------------------------------------------------
box = rdf.crop_by_bbox(extent=[2.60e6, 2.62e6, 1.11e6, 1.13e6]) # or bounds=(xmin,ymin,xmax,ymax)
poly = rdf.crop_by_polygone("catchment.geojson")
disc = rdf.crop_around_point((2.61e6, 1.12e6), distance=20_000) # metres
# rdf.interactive_crop() # draw an AOI on a Jupyter map (needs ipyleaflet)
# --- cross-section -----------------------------------------------------------
cs = rdf.extract_cross_section(p1=(2.60e6, 1.12e6), p2=(2.63e6, 1.12e6))
# --- plot --------------------------------------------------------------------
rdf.plot_ppi(sweep=1, variable="DBZH", save="ppi.png")
cs.plot_cross_section(variable="DBZH", save="xsec.png")
rdf.plot_rhi(azimuth=270, variable="DBZH")
# fluent: open -> filter -> crop -> plot
rdf.filter({"var": "DBZH", "logic": ">", "threshold": 20}) \
.crop_by_bbox(extent=rdf.extent()) \
.plot_ppi(variable="DBZH", save="strong.png")
filter / crs argument shapes: filters are {"var", "logic", "threshold"}
dicts (logic ∈ ==,!=,>,>=,<,<=); crs is an EPSG int (e.g. 2056), a
CRS-coercible object, or None.
What is on disk? (archive-bound)
db.inventory() # radars, volume counts, time ranges, size
db.inventory(detailed=True) # + LUT info, stored moments, day-by-day counts
db.inventory(datatree_dir="/data/MCH_datatree") # DataTree files not archived yet
LUT accessors (archive-bound)
db.list_radars() # radars present in the archive
db.get_lut("L") # the static LUT (pandas)
db.get_radar_info("L") # site location / sweep geometry
db.add_lut_projection("L", epsg=2056)
Module structure
raddb/
├── __init__.py # public API surface
├── main.py # the RadDB class (this API)
├── 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/cross-section), interactive.py
└── mch/ # private MeteoSwiss/METRANET ingestion (pyart) — not in the public wheel
Sample-data scripts live under scripts/ (make_sample_mch_datatrees.py,
download_nexrad_datatree.py).
Notes
- Projected coordinates /
crs. Generating a LUT with a projection (e.g.crs=2056) 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).
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 raddb-0.0.1.tar.gz.
File metadata
- Download URL: raddb-0.0.1.tar.gz
- Upload date:
- Size: 100.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0f149527d5c21b60b17fbc18468271d1971c3ef2cb8628814008f629e688388c
|
|
| MD5 |
d2ab1184f0a0c850f3702a6fed2dca38
|
|
| BLAKE2b-256 |
b0841f0239a3edcbb475b48dfb8eb6d6648c5b8078a77b95e03fd6127d937cf2
|
File details
Details for the file raddb-0.0.1-py3-none-any.whl.
File metadata
- Download URL: raddb-0.0.1-py3-none-any.whl
- Upload date:
- Size: 91.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cbe31b8e826cf2c4934c5ceb0e73ec0e22ceac09ee8023738685a8f9f748f0fd
|
|
| MD5 |
cb2705ce8b8c3d4ca76f732a47572910
|
|
| BLAKE2b-256 |
3be57d423d47bc674a23dc3a3b719445ab1c714f909825af0b7d07625b0ec0fc
|