Load ODIM_H5 Volume data from German Weather Service

In this example, we obtain and read the latest 30 minutes of available volumetric radar data from German Weather Service available at opendata.dwd.de. Finally we do some plotting.

This retrieves 6 timesteps of the 10 sweeps (moments DBZH and VRADH) of the DWD volume scan of a distinct radar. This amounts to 120 data files which are combined into one volumetric Cf/Radial2 like xarray powered structure.

Exports to single file Odim_H5 and Cf/Radial2 format are shown at the end of this tutorial.

Note

The following code is based on xarray, xarray-datatree and xradar. It claims multiple data files and presents them in a DataTree.

[1]:
import wradlib as wrl
import warnings

warnings.filterwarnings("ignore")
import matplotlib.pyplot as pl
import numpy as np
import xarray as xr

try:
    get_ipython().run_line_magic("matplotlib inline")
except:
    pl.ion()
from wradlib.io import open_odim_mfdataset
/home/runner/micromamba-root/envs/wradlib-notebooks/lib/python3.11/site-packages/tqdm/auto.py:22: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm
[2]:
import urllib3
import os
import io
import glob
import shutil
import datetime

Download radar volumes of latest 30 minutes from server using wetterdienst

wetterdienst is a neat package for easy retrieval of data primarily from DWD. For further information have a look at their documentation.

[3]:
from wetterdienst.provider.dwd.radar import (
    DwdRadarDataFormat,
    DwdRadarDataSubset,
    DwdRadarParameter,
    DwdRadarValues,
)
from wetterdienst.provider.dwd.radar.sites import DwdRadarSite
[4]:
elevations = range(10)

end_date = datetime.datetime.utcnow()
start_date = end_date - datetime.timedelta(minutes=30)

results_velocity = []
results_reflectivity = []

for el in elevations:
    # Horizontal Doppler Velocity
    request_velocity = DwdRadarValues(
        parameter=DwdRadarParameter.SWEEP_VOL_VELOCITY_H,
        start_date=start_date,
        end_date=end_date,
        site=DwdRadarSite.ESS,
        elevation=el,
        fmt=DwdRadarDataFormat.HDF5,
        subset=DwdRadarDataSubset.POLARIMETRIC,
    )

    # Horizontal Reflectivity
    request_reflectivity = DwdRadarValues(
        parameter=DwdRadarParameter.SWEEP_VOL_REFLECTIVITY_H,
        start_date=start_date,
        end_date=end_date,
        elevation=el,
        site=DwdRadarSite.ESS,
        fmt=DwdRadarDataFormat.HDF5,
        subset=DwdRadarDataSubset.POLARIMETRIC,
    )

    # Submit requests.
    results_velocity.append(request_velocity.query())
    results_reflectivity.append(request_reflectivity.query())
[5]:
import wetterdienst

wetterdienst.__version__
[5]:
'0.20.3'

Acquire data as memory buffer

[6]:
%%time
volume_velocity = []
for item1 in results_velocity:
    files = []
    for item2 in item1:
        files.append(item2.data)
    volume_velocity.append(files)
  2%|▎         | 6/240 [00:03<02:29,  1.56it/s]
  2%|▎         | 6/240 [00:01<01:03,  3.68it/s]
  2%|▎         | 6/240 [00:01<01:03,  3.67it/s]
  2%|▎         | 6/240 [00:02<01:26,  2.72it/s]
  2%|▎         | 6/240 [00:01<01:15,  3.10it/s]
  2%|▎         | 6/240 [00:02<01:18,  2.97it/s]
  2%|▎         | 6/240 [00:01<00:57,  4.09it/s]
  2%|▎         | 6/240 [00:01<01:00,  3.88it/s]
  2%|▎         | 6/240 [00:01<00:59,  3.95it/s]
  2%|▎         | 6/240 [00:01<01:16,  3.05it/s]
CPU times: user 4.68 s, sys: 169 ms, total: 4.85 s
Wall time: 19.8 s
[7]:
volume_velocity = [v[-6:] for v in volume_velocity]
volume_velocity = np.array(volume_velocity).T.tolist()
[8]:
%%time
volume_reflectivity = []
for item1 in results_reflectivity:
    files = []
    for item2 in item1:
        files.append(item2.data)
    volume_reflectivity.append(files)
  2%|▎         | 6/240 [00:02<01:25,  2.75it/s]
  2%|▎         | 6/240 [00:02<01:20,  2.89it/s]
  2%|▎         | 6/240 [00:02<01:20,  2.91it/s]
  2%|▎         | 6/240 [00:01<01:14,  3.13it/s]
  2%|▎         | 6/240 [00:02<01:28,  2.64it/s]
  2%|▎         | 6/240 [00:01<01:16,  3.05it/s]
  2%|▎         | 6/240 [00:02<01:23,  2.81it/s]
  2%|▎         | 6/240 [00:01<00:58,  4.00it/s]
  2%|▎         | 6/240 [00:01<01:00,  3.88it/s]
  2%|▎         | 6/240 [00:01<01:04,  3.61it/s]
CPU times: user 4.98 s, sys: 88.2 ms, total: 5.07 s
Wall time: 19.3 s

[9]:
volume_reflectivity = [v[-6:] for v in volume_reflectivity]
volume_reflectivity = np.array(volume_reflectivity).T.tolist()

Read the data into xarray powered structure

[10]:
from datatree import DataTree, open_datatree
import xradar


def concat_radar_datatree(objs, dim="volume_time"):
    root_ds = [obj["/"].ds for obj in objs]
    root = xr.concat(root_ds, dim=dim)
    dtree = DataTree(data=root, name="root")
    for grp in objs[0].groups[1:]:
        ngrps = [obj[grp[1:]].ds for obj in objs]
        ngrp = xr.concat(ngrps, dim=dim)
        DataTree(ngrp, name=grp[1:], parent=dtree)
    return dtree
[11]:
vol = wrl.io.RadarVolume()
dsl = []
reindex_angle = dict(
    tolerance=1.0, start_angle=0, stop_angle=360, angle_res=1.0, direction=1
)
for r, v in zip(volume_reflectivity, volume_velocity):
    ds0 = [
        xr.open_dataset(r0, engine="odim", group="sweep_0", reindex_angle=reindex_angle)
        for r0 in r
    ]
    ds1 = [
        xr.open_dataset(v0, engine="odim", group="sweep_0", reindex_angle=reindex_angle)
        for v0 in v
    ]
    ds = [xr.merge([r0, v0], compat="override") for r0, v0 in zip(ds0, ds1)]
    ds.insert(0, xr.open_dataset(r[0], group="/"))
    dsl.append(ds)
# this takes some private functions from xradar, take care here
trees = [
    DataTree(data=xradar.io.backends.common._assign_root(ds), name="root") for ds in dsl
]
trees = [
    xradar.io.backends.common._attach_sweep_groups(tree, ds[1:])
    for tree, ds in zip(trees, dsl)
]
vol = concat_radar_datatree(trees, dim="volume_time")
# align sweep_numbers to cover for single sweep single moment layout of DWD
for i, swp in enumerate(vol.groups[1:]):
    vol[swp]["sweep_number"] = i
[12]:
vol
[12]:
<xarray.DatasetView>
Dimensions:              (volume_time: 6)
Dimensions without coordinates: volume_time
Data variables:
    volume_number        (volume_time) int64 0 0 0 0 0 0
    platform_type        (volume_time) <U5 'fixed' 'fixed' ... 'fixed' 'fixed'
    instrument_type      (volume_time) <U5 'radar' 'radar' ... 'radar' 'radar'
    time_coverage_start  (volume_time) <U20 '2023-02-24T14:10:35Z' ... '2023-...
    time_coverage_end    (volume_time) <U20 '2023-02-24T14:14:03Z' ... '2023-...
    longitude            (volume_time) float64 6.967 6.967 6.967 ... 6.967 6.967
    altitude             (volume_time) float64 185.1 185.1 185.1 ... 185.1 185.1
    latitude             (volume_time) float64 51.41 51.41 51.41 ... 51.41 51.41
Attributes:
    Conventions:      ODIM_H5/V2_2
    version:          None
    title:            None
    institution:      None
    references:       None
    source:           None
    history:          None
    comment:          im/exported using xradar
    instrument_name:  None
[13]:
vol["sweep_9"]
[13]:
<xarray.DatasetView>
Dimensions:            (azimuth: 360, range: 240, volume_time: 6)
Coordinates:
  * azimuth            (azimuth) float64 0.5 1.5 2.5 3.5 ... 357.5 358.5 359.5
  * range              (range) float32 125.0 375.0 625.0 ... 5.962e+04 5.988e+04
    elevation          (volume_time, azimuth) float64 24.98 24.98 ... 24.98
    time               (volume_time, azimuth) datetime64[ns] 2023-02-24T14:13...
    longitude          float64 6.967
    latitude           float64 51.41
    altitude           float64 185.1
Dimensions without coordinates: volume_time
Data variables:
    DBZH               (volume_time, azimuth, range) float32 9.005 ... -64.0
    sweep_mode         (volume_time) <U20 'azimuth_surveillance' ... 'azimuth...
    sweep_number       int64 9
    prt_mode           (volume_time) <U7 'not_set' 'not_set' ... 'not_set'
    follow_mode        (volume_time) <U7 'not_set' 'not_set' ... 'not_set'
    sweep_fixed_angle  (volume_time) float64 25.0 25.0 25.0 25.0 25.0 25.0
    VRADH              (volume_time, azimuth, range) float32 -128.0 ... -128.0

Inspect structure

Root Group

[14]:
vol.root
[14]:
<xarray.DatasetView>
Dimensions:              (volume_time: 6)
Dimensions without coordinates: volume_time
Data variables:
    volume_number        (volume_time) int64 0 0 0 0 0 0
    platform_type        (volume_time) <U5 'fixed' 'fixed' ... 'fixed' 'fixed'
    instrument_type      (volume_time) <U5 'radar' 'radar' ... 'radar' 'radar'
    time_coverage_start  (volume_time) <U20 '2023-02-24T14:10:35Z' ... '2023-...
    time_coverage_end    (volume_time) <U20 '2023-02-24T14:14:03Z' ... '2023-...
    longitude            (volume_time) float64 6.967 6.967 6.967 ... 6.967 6.967
    altitude             (volume_time) float64 185.1 185.1 185.1 ... 185.1 185.1
    latitude             (volume_time) float64 51.41 51.41 51.41 ... 51.41 51.41
Attributes:
    Conventions:      ODIM_H5/V2_2
    version:          None
    title:            None
    institution:      None
    references:       None
    source:           None
    history:          None
    comment:          im/exported using xradar
    instrument_name:  None

Sweep Groups

[15]:
vol["sweep_0"]
[15]:
<xarray.DatasetView>
Dimensions:            (azimuth: 360, range: 720, volume_time: 6)
Coordinates:
  * azimuth            (azimuth) float64 0.5 1.5 2.5 3.5 ... 357.5 358.5 359.5
  * range              (range) float32 125.0 375.0 625.0 ... 1.796e+05 1.799e+05
    elevation          (volume_time, azimuth) float64 5.482 5.482 ... 5.482
    time               (volume_time, azimuth) datetime64[ns] 2023-02-24T14:10...
    longitude          float64 6.967
    latitude           float64 51.41
    altitude           float64 185.1
Dimensions without coordinates: volume_time
Data variables:
    DBZH               (volume_time, azimuth, range) float32 -64.0 ... -64.0
    sweep_mode         (volume_time) <U20 'azimuth_surveillance' ... 'azimuth...
    sweep_number       int64 0
    prt_mode           (volume_time) <U7 'not_set' 'not_set' ... 'not_set'
    follow_mode        (volume_time) <U7 'not_set' 'not_set' ... 'not_set'
    sweep_fixed_angle  (volume_time) float64 5.499 5.499 5.499 5.499 5.499 5.499
    VRADH              (volume_time, azimuth, range) float32 -128.0 ... -128.0

plot sweeps

DBZH

[16]:
vol["sweep_0"].isel(volume_time=0)
[16]:
<xarray.DatasetView>
Dimensions:            (azimuth: 360, range: 720)
Coordinates:
  * azimuth            (azimuth) float64 0.5 1.5 2.5 3.5 ... 357.5 358.5 359.5
  * range              (range) float32 125.0 375.0 625.0 ... 1.796e+05 1.799e+05
    elevation          (azimuth) float64 5.482 5.482 5.482 ... 5.482 5.482 5.482
    time               (azimuth) datetime64[ns] 2023-02-24T14:10:54.692500224...
    longitude          float64 6.967
    latitude           float64 51.41
    altitude           float64 185.1
Data variables:
    DBZH               (azimuth, range) float32 -64.0 -64.0 ... -64.0 -64.0
    sweep_mode         <U20 'azimuth_surveillance'
    sweep_number       int64 0
    prt_mode           <U7 'not_set'
    follow_mode        <U7 'not_set'
    sweep_fixed_angle  float64 5.499
    VRADH              (azimuth, range) float32 -128.0 -128.0 ... -128.0 -128.0
[17]:
fig, gs = pl.subplots(
    4, 3, figsize=(20, 30), sharex=True, sharey=True, constrained_layout=True
)

for i, grp in enumerate(vol.groups[1:]):
    swp = vol[grp].isel(volume_time=0).ds
    swp = swp.assign_coords(sweep_mode=swp.sweep_mode)
    swp.DBZH.pipe(wrl.georef.georeference_dataset).wradlib.plot(ax=gs.flat[i], fig=fig)
    ax = pl.gca()
    ax.set_title(swp.sweep_fixed_angle.values)

fig.delaxes(gs.flat[-2])
fig.delaxes(gs.flat[-1])
../../_images/notebooks_fileio_wradlib_load_DWD_opendata_volumes_24_0.png

VRADH

[18]:
fig, gs = pl.subplots(
    4, 3, figsize=(20, 30), sharex=True, sharey=True, constrained_layout=True
)

for i, grp in enumerate(vol.groups[1:]):
    swp = vol[grp].isel(volume_time=0).ds
    swp = swp.assign_coords(sweep_mode=swp.sweep_mode)
    swp.VRADH.pipe(wrl.georef.georeference_dataset).wradlib.plot(ax=gs.flat[i], fig=fig)
    ax = pl.gca()
    ax.set_title(swp.sweep_fixed_angle.values)

fig.delaxes(gs.flat[-2])
fig.delaxes(gs.flat[-1])
../../_images/notebooks_fileio_wradlib_load_DWD_opendata_volumes_26_0.png

Plot single sweep using cartopy

[19]:
vol0 = vol.isel(volume_time=0)
swp = vol0["sweep_9"].ds
# need to assign sweep_mode as coordinate
swp = swp.assign_coords(sweep_mode=swp.sweep_mode)
[20]:
import cartopy
import cartopy.crs as ccrs
import cartopy.feature as cfeature

map_trans = ccrs.AzimuthalEquidistant(
    central_latitude=vol0.root.ds.latitude.values,
    central_longitude=vol0.root.ds.longitude.values,
)
[21]:
map_proj = ccrs.AzimuthalEquidistant(
    central_latitude=vol0.root.ds.latitude.values,
    central_longitude=vol0.root.ds.longitude.values,
)

pm = swp.DBZH.pipe(wrl.georef.georeference_dataset).wradlib.plot_ppi(proj=map_proj)
ax = pl.gca()
ax.gridlines(crs=map_proj)
print(ax)
< GeoAxes: +proj=aeqd +ellps=WGS84 +lon_0=6.967111 +lat_0=51.405649 +x_0=0.0 +y_0=0.0 +no_defs +type=crs >
../../_images/notebooks_fileio_wradlib_load_DWD_opendata_volumes_30_1.png
[22]:
map_proj = ccrs.Mercator(central_longitude=vol0.root.ds.longitude.values)
fig = pl.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection=map_proj)
pm = swp.DBZH.pipe(wrl.georef.georeference_dataset).wradlib.plot_ppi(ax=ax)
ax.gridlines(draw_labels=True)
[22]:
<cartopy.mpl.gridliner.Gridliner at 0x7f62654a5550>
../../_images/notebooks_fileio_wradlib_load_DWD_opendata_volumes_31_1.png
[23]:
fig = pl.figure(figsize=(10, 8))
proj = ccrs.AzimuthalEquidistant(
    central_latitude=vol0.root.ds.latitude.values,
    central_longitude=vol0.root.ds.longitude.values,
)
ax = fig.add_subplot(111, projection=proj)
pm = swp.DBZH.pipe(wrl.georef.georeference_dataset).wradlib.plot_ppi(ax=ax)
ax.gridlines()
[23]:
<cartopy.mpl.gridliner.Gridliner at 0x7f6265047f50>
../../_images/notebooks_fileio_wradlib_load_DWD_opendata_volumes_32_1.png

Inspect radar moments

The DataArrays can be accessed by key or by attribute. Each DataArray inherits dimensions and coordinates of it’s parent dataset. There are attributes connected which are defined by Cf/Radial and/or ODIM_H5 standard.

[24]:
vol["sweep_9"].isel(volume_time=0).ds.DBZH
[24]:
<xarray.DataArray 'DBZH' (azimuth: 360, range: 240)>
array([[  9.005295 ,  10.338364 ,  -4.1173744, ..., -64.00293  ,
        -64.00293  , -64.00293  ],
       [-64.00293  ,   8.228889 ,  -3.285305 , ..., -64.00293  ,
        -64.00293  , -64.00293  ],
       [-64.00293  , -64.00293  , -64.00293  , ..., -64.00293  ,
        -64.00293  , -64.00293  ],
       ...,
       [-64.00293  , -64.00293  , -64.00293  , ..., -64.00293  ,
        -64.00293  , -64.00293  ],
       [-64.00293  , -64.00293  , -64.00293  , ..., -64.00293  ,
        -64.00293  , -64.00293  ],
       [-64.00293  ,   8.05603  , -64.00293  , ..., -64.00293  ,
        -64.00293  , -64.00293  ]], dtype=float32)
Coordinates:
  * azimuth    (azimuth) float64 0.5 1.5 2.5 3.5 4.5 ... 356.5 357.5 358.5 359.5
  * range      (range) float32 125.0 375.0 625.0 ... 5.962e+04 5.988e+04
    elevation  (azimuth) float64 24.98 24.98 24.98 24.98 ... 24.98 24.98 24.98
    time       (azimuth) datetime64[ns] 2023-02-24T14:13:54.802500096 ... 202...
    longitude  float64 6.967
    latitude   float64 51.41
    altitude   float64 185.1
Attributes:
    _Undetect:      0.0
    standard_name:  radar_equivalent_reflectivity_factor_h
    units:          dBZ
    long_name:      Equivalent reflectivity factor H
[25]:
vol["sweep_9"].isel(volume_time=0).ds.sweep_mode
[25]:
<xarray.DataArray 'sweep_mode' ()>
array('azimuth_surveillance', dtype='<U20')
Coordinates:
    longitude  float64 6.967
    latitude   float64 51.41
    altitude   float64 185.1

Plot Quasi Vertical Profile

[26]:
ts = vol["sweep_9"]
ts
[26]:
<xarray.DatasetView>
Dimensions:            (azimuth: 360, range: 240, volume_time: 6)
Coordinates:
  * azimuth            (azimuth) float64 0.5 1.5 2.5 3.5 ... 357.5 358.5 359.5
  * range              (range) float32 125.0 375.0 625.0 ... 5.962e+04 5.988e+04
    elevation          (volume_time, azimuth) float64 24.98 24.98 ... 24.98
    time               (volume_time, azimuth) datetime64[ns] 2023-02-24T14:13...
    longitude          float64 6.967
    latitude           float64 51.41
    altitude           float64 185.1
Dimensions without coordinates: volume_time
Data variables:
    DBZH               (volume_time, azimuth, range) float32 9.005 ... -64.0
    sweep_mode         (volume_time) <U20 'azimuth_surveillance' ... 'azimuth...
    sweep_number       int64 9
    prt_mode           (volume_time) <U7 'not_set' 'not_set' ... 'not_set'
    follow_mode        (volume_time) <U7 'not_set' 'not_set' ... 'not_set'
    sweep_fixed_angle  (volume_time) float64 25.0 25.0 25.0 25.0 25.0 25.0
    VRADH              (volume_time, azimuth, range) float32 -128.0 ... -128.0
[27]:
fig = pl.figure(figsize=(10, 4))
ax = fig.add_subplot(111)
ts.ds.DBZH.median("azimuth").plot(x="volume_time", vmin=-10, vmax=30, ax=ax)
ax.set_title(f"{np.datetime_as_string(ts.ds.time[0][0].values, unit='D')}")
ax.set_ylim(0, 20000)
[27]:
(0.0, 20000.0)
../../_images/notebooks_fileio_wradlib_load_DWD_opendata_volumes_38_1.png

Export to OdimH5

This exports the radar volume at given timestep including all moments into one ODIM_H5 compliant data file.

[28]:
vol0["sweep_9"]["sweep_number"]
[28]:
<xarray.DataArray 'sweep_number' ()>
array(9)
Coordinates:
    longitude  float64 6.967
    latitude   float64 51.41
    altitude   float64 185.1
[29]:
xradar.io.to_odim(vol0, "dwd_odim.h5")

Export to Cf/Radial2

This exports the radar volume at given timestep including all moments into one Cf/Radial2 compliant data file.

[30]:
xradar.io.to_cfradial2(vol0, "dwd_cfradial2.nc")

Import again and check equality

[31]:
vol1 = xradar.io.open_odim_datatree("dwd_odim.h5")
vol2 = open_datatree("dwd_cfradial2.nc")
[32]:
xr.testing.assert_equal(vol1.root.ds, vol2.root.ds)
for grp in vol1.groups[1:]:
    xr.testing.assert_equal(
        vol1[grp]
        .to_dataset()
        .reset_coords(["latitude", "longitude", "altitude"], drop=True),
        vol2[grp].to_dataset().swap_dims(time="azimuth").sortby("azimuth"),
    )