ToGrid#

In this notebook we show the production of a reflectivity composite from 3 neighboring radars to a common cartesian grid, using the sampling volume size as a quality criterion.

import tempfile
import warnings

import cmweather
import numpy as np
import pyproj
import xarray as xr
import matplotlib.pyplot as plt

import wradlib as wrl
import wradlib_data

warnings.filterwarnings("ignore")
/home/docs/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/tqdm/auto.py:21: 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

Get radar data#

First, we import measurements from three belgian radars. This is done using xarray.open_dataset using xradar.io.backends.odim.OdimBackendEntrypoint.

filenames = ["bejab.pvol.hdf", "bewid.pvol.hdf", "behel.pvol.hdf"]
paths = {f.split(".")[0][2:]: wradlib_data.DATASETS.fetch(f"hdf5/{f}") for f in filenames}
ctree = xr.DataTree()
for radar, filename in paths.items():
    ctree[radar] = xr.open_dataset(filename, engine="odim", group="sweep_0").chunk().wrl.georef.georeference().set_coords("sweep_mode")
display(ctree)
<xarray.DataTree>
Group: /
├── Group: /jab
│       Dimensions:            (azimuth: 360, range: 598)
│       Coordinates: (12/15)
│         * azimuth            (azimuth) float32 1kB 0.5 1.5 2.5 ... 357.5 358.5 359.5
│           elevation          (azimuth) float64 3kB dask.array<chunksize=(360,), meta=np.ndarray>
│           time               (azimuth) datetime64[ns] 3kB dask.array<chunksize=(360,), meta=np.ndarray>
│         * range              (range) float32 2kB 250.0 750.0 ... 2.982e+05 2.988e+05
│           x                  (azimuth, range) float64 2MB 2.182 6.545 ... -2.605e+03
│           y                  (azimuth, range) float64 2MB 250.0 750.0 ... 2.986e+05
│           ...                 ...
│           bins               (azimuth, range) float32 861kB 250.0 750.0 ... 2.988e+05
│           sweep_mode         <U20 80B 'azimuth_surveillance'
│           longitude          float64 8B 3.064
│           latitude           float64 8B 51.19
│           altitude           float64 8B 50.0
│           crs_wkt            int64 8B 0
│       Data variables:
│           DBZH               (azimuth, range) float64 2MB dask.array<chunksize=(360, 598), meta=np.ndarray>
│           sweep_number       int64 8B ...
│           prt_mode           <U7 28B ...
│           follow_mode        <U7 28B ...
│           sweep_fixed_angle  float64 8B ...
│           nyquist_velocity   object 8B ...
│       Attributes:
│           Conventions:  ODIM_H5/V2_2
├── Group: /wid
│       Dimensions:            (azimuth: 360, range: 1000)
│       Coordinates: (12/15)
│         * azimuth            (azimuth) float32 1kB 0.5 1.5 2.5 ... 357.5 358.5 359.5
│           elevation          (azimuth) float64 3kB dask.array<chunksize=(360,), meta=np.ndarray>
│           time               (azimuth) datetime64[ns] 3kB dask.array<chunksize=(360,), meta=np.ndarray>
│         * range              (range) float32 4kB 125.0 375.0 ... 2.496e+05 2.499e+05
│           x                  (azimuth, range) float64 3MB 1.091 3.272 ... -2.179e+03
│           y                  (azimuth, range) float64 3MB 125.0 375.0 ... 2.497e+05
│           ...                 ...
│           bins               (azimuth, range) float32 1MB 125.0 375.0 ... 2.499e+05
│           sweep_mode         <U20 80B 'azimuth_surveillance'
│           longitude          float64 8B 5.506
│           latitude           float64 8B 49.91
│           altitude           float64 8B 590.0
│           crs_wkt            int64 8B 0
│       Data variables:
│           DBZH               (azimuth, range) float64 3MB dask.array<chunksize=(360, 1000), meta=np.ndarray>
│           sweep_number       int64 8B ...
│           prt_mode           <U7 28B ...
│           follow_mode        <U7 28B ...
│           sweep_fixed_angle  float64 8B ...
│           nyquist_velocity   object 8B ...
│       Attributes:
│           Conventions:  ODIM_H5/V2_2
└── Group: /hel
        Dimensions:            (azimuth: 360, range: 800)
        Coordinates: (12/15)
          * azimuth            (azimuth) float32 1kB 0.5 1.5 2.5 ... 357.5 358.5 359.5
            elevation          (azimuth) float64 3kB dask.array<chunksize=(360,), meta=np.ndarray>
            time               (azimuth) datetime64[ns] 3kB dask.array<chunksize=(360,), meta=np.ndarray>
          * range              (range) float32 3kB 125.0 375.0 ... 1.996e+05 1.999e+05
            x                  (azimuth, range) float64 2MB 1.091 3.272 ... -1.744e+03
            y                  (azimuth, range) float64 2MB 125.0 375.0 ... 1.998e+05
            ...                 ...
            bins               (azimuth, range) float32 1MB 125.0 375.0 ... 1.999e+05
            sweep_mode         <U20 80B 'azimuth_surveillance'
            longitude          float64 8B 5.406
            latitude           float64 8B 51.07
            altitude           float64 8B 140.0
            crs_wkt            int64 8B 0
        Data variables:
            DBZH               (azimuth, range) float64 2MB dask.array<chunksize=(360, 800), meta=np.ndarray>
            sweep_number       int64 8B ...
            prt_mode           <U7 28B ...
            follow_mode        <U7 28B ...
            sweep_fixed_angle  float64 8B ...
            nyquist_velocity   object 8B ...
        Attributes:
            Conventions:  ODIM_H5/V2_2

Plot Overview#

fig, axs = plt.subplots(1, 3, figsize=(18, 5), sharey=True)
ax = axs.flat[0]
ctree["jab"].ds.DBZH.wrl.vis.plot(ax=ax, vmin=0, vmax=60)
ax.set_aspect("equal")
ax.set_title("Radar Jabbeke")
ax = axs.flat[1]
ctree["wid"].ds.DBZH.wrl.vis.plot(ax=ax, vmin=0, vmax=60)
ax.set_aspect("equal")
ax.set_title("Radar Wideumont")
ax = axs.flat[2]
ctree["hel"].ds.DBZH.wrl.vis.plot(ax=ax, vmin=0, vmax=60)
ax.set_aspect("equal")
ax.set_title("Radar Helchteren")
fig.tight_layout()
../../_images/384da605de3e0353b590a219f93457e6d017863e97aaeb0ed1e0b0883b3fd3a0.png

Georeference UTM#

proj_utm = pyproj.CRS.from_epsg(32632)
kwargs = dict(crs=proj_utm)
for radar in ctree.children:
    ctree[radar].ds = ctree[radar].ds.wrl.georef.georeference(crs=proj_utm)
display(ctree)
<xarray.DataTree>
Group: /
├── Group: /jab
│       Dimensions:            (azimuth: 360, range: 598)
│       Coordinates: (12/15)
│         * azimuth            (azimuth) float32 1kB 0.5 1.5 2.5 ... 357.5 358.5 359.5
│           elevation          (azimuth) float64 3kB dask.array<chunksize=(360,), meta=np.ndarray>
│           time               (azimuth) datetime64[ns] 3kB dask.array<chunksize=(360,), meta=np.ndarray>
│         * range              (range) float32 2kB 250.0 750.0 ... 2.982e+05 2.988e+05
│           x                  (azimuth, range) float64 2MB 8.539e+04 ... 1.074e+05
│           y                  (azimuth, range) float64 2MB 5.688e+06 ... 5.986e+06
│           ...                 ...
│           bins               (azimuth, range) float32 861kB 250.0 750.0 ... 2.988e+05
│           sweep_mode         <U20 80B 'azimuth_surveillance'
│           longitude          float64 8B 3.064
│           latitude           float64 8B 51.19
│           altitude           float64 8B 50.0
│           crs_wkt            int64 8B 0
│       Data variables:
│           DBZH               (azimuth, range) float64 2MB dask.array<chunksize=(360, 598), meta=np.ndarray>
│           sweep_number       int64 8B ...
│           prt_mode           <U7 28B ...
│           follow_mode        <U7 28B ...
│           sweep_fixed_angle  float64 8B ...
│           nyquist_velocity   object 8B ...
│       Attributes:
│           Conventions:  ODIM_H5/V2_2
├── Group: /wid
│       Dimensions:            (azimuth: 360, range: 1000)
│       Coordinates: (12/15)
│         * azimuth            (azimuth) float32 1kB 0.5 1.5 2.5 ... 357.5 358.5 359.5
│           elevation          (azimuth) float64 3kB dask.array<chunksize=(360,), meta=np.ndarray>
│           time               (azimuth) datetime64[ns] 3kB dask.array<chunksize=(360,), meta=np.ndarray>
│         * range              (range) float32 4kB 125.0 375.0 ... 2.496e+05 2.499e+05
│           x                  (azimuth, range) float64 3MB 2.492e+05 ... 2.588e+05
│           y                  (azimuth, range) float64 3MB 5.535e+06 ... 5.785e+06
│           ...                 ...
│           bins               (azimuth, range) float32 1MB 125.0 375.0 ... 2.499e+05
│           sweep_mode         <U20 80B 'azimuth_surveillance'
│           longitude          float64 8B 5.506
│           latitude           float64 8B 49.91
│           altitude           float64 8B 590.0
│           crs_wkt            int64 8B 0
│       Data variables:
│           DBZH               (azimuth, range) float64 3MB dask.array<chunksize=(360, 1000), meta=np.ndarray>
│           sweep_number       int64 8B ...
│           prt_mode           <U7 28B ...
│           follow_mode        <U7 28B ...
│           sweep_fixed_angle  float64 8B ...
│           nyquist_velocity   object 8B ...
│       Attributes:
│           Conventions:  ODIM_H5/V2_2
└── Group: /hel
        Dimensions:            (azimuth: 360, range: 800)
        Coordinates: (12/15)
          * azimuth            (azimuth) float32 1kB 0.5 1.5 2.5 ... 357.5 358.5 359.5
            elevation          (azimuth) float64 3kB dask.array<chunksize=(360,), meta=np.ndarray>
            time               (azimuth) datetime64[ns] 3kB dask.array<chunksize=(360,), meta=np.ndarray>
          * range              (range) float32 3kB 125.0 375.0 ... 1.996e+05 1.999e+05
            x                  (azimuth, range) float64 2MB 2.483e+05 ... 2.564e+05
            y                  (azimuth, range) float64 2MB 5.664e+06 ... 5.863e+06
            ...                 ...
            bins               (azimuth, range) float32 1MB 125.0 375.0 ... 1.999e+05
            sweep_mode         <U20 80B 'azimuth_surveillance'
            longitude          float64 8B 5.406
            latitude           float64 8B 51.07
            altitude           float64 8B 140.0
            crs_wkt            int64 8B 0
        Data variables:
            DBZH               (azimuth, range) float64 2MB dask.array<chunksize=(360, 800), meta=np.ndarray>
            sweep_number       int64 8B ...
            prt_mode           <U7 28B ...
            follow_mode        <U7 28B ...
            sweep_fixed_angle  float64 8B ...
            nyquist_velocity   object 8B ...
        Attributes:
            Conventions:  ODIM_H5/V2_2
fig, axs = plt.subplots(1, 3, figsize=(18, 5), sharey=True)
ax = axs.flat[0]
ctree["jab"].ds.DBZH.wrl.vis.plot(ax=ax, vmin=0, vmax=60)
ax.set_aspect("equal")
ax.set_title("Radar Jabbeke")
ax = axs.flat[1]
ctree["wid"].ds.DBZH.wrl.vis.plot(ax=ax, vmin=0, vmax=60)
ax.set_aspect("equal")
ax.set_title("Radar Wideumont")
ax = axs.flat[2]
ctree["hel"].ds.DBZH.wrl.vis.plot(ax=ax, vmin=0, vmax=60)
ax.set_aspect("equal")
ax.set_title("Radar Helchteren")
fig.tight_layout()
Error in callback <function _draw_all_if_interactive at 0x7af7b5ccaca0> (for post_execute), with arguments args (),kwargs {}:
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/pyplot.py:300, in _draw_all_if_interactive()
    298 def _draw_all_if_interactive() -> None:
    299     if matplotlib.is_interactive():
--> 300         draw_all()

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/_pylab_helpers.py:133, in Gcf.draw_all(cls, force)
    131 for manager in cls.get_all_fig_managers():
    132     if force or manager.canvas.figure.stale:
--> 133         manager.canvas.draw_idle()

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/backend_bases.py:1989, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
   1987 if not self._is_idle_drawing:
   1988     with self._idle_draw_cntx():
-> 1989         self.draw(*args, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/backends/backend_agg.py:438, in FigureCanvasAgg.draw(self)
    435 # Acquire a lock on the shared font cache.
    436 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
    437       else nullcontext()):
--> 438     self.figure.draw(self.renderer)
    439     # A GUI class may be need to update a window using this draw, so
    440     # don't forget to call the superclass.
    441     super().draw()

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     92 @wraps(draw)
     93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94     result = draw(artist, renderer, *args, **kwargs)
     95     if renderer._rasterizing:
     96         renderer.stop_rasterizing()

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/figure.py:3271, in Figure.draw(self, renderer)
   3267     return
   3269 with self._render_lock:
-> 3271     artists = self._get_draw_artists(renderer)
   3272     try:
   3273         renderer.open_group('figure', gid=self.get_gid())

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/figure.py:166, in FigureBase._get_draw_artists(self, renderer)
    164 for ax in self._localaxes:
    165     locator = ax.get_axes_locator()
--> 166     ax.apply_aspect(locator(ax, renderer) if locator else None)
    168     for child in ax.get_children():
    169         if hasattr(child, 'apply_aspect'):

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axes/_base.py:2127, in _AxesBase.apply_aspect(self, position)
   2124 if not self.get_autoscalex_on():
   2125     _log.warning("Ignoring fixed x limits to fulfill fixed data aspect "
   2126                  "with adjustable data limits.")
-> 2127 self.set_xbound(x_trf.inverted().transform([x0, x1]))

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axes/_base.py:3849, in _AxesBase.set_xbound(self, lower, upper)
   3846 if upper is None:
   3847     upper = old_upper
-> 3849 self.set_xlim(sorted((lower, upper),
   3850                      reverse=bool(self.xaxis_inverted())),
   3851               auto=None)

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axes/_base.py:3970, in _AxesBase.set_xlim(self, left, right, emit, auto, xmin, xmax)
   3968         raise TypeError("Cannot pass both 'right' and 'xmax'")
   3969     right = xmax
-> 3970 return self.xaxis._set_lim(left, right, emit=emit, auto=auto)

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axis.py:1263, in Axis._set_lim(self, v0, v1, emit, auto)
   1260 name = self._get_axis_name()
   1262 self.axes._process_unit_info([(name, (v0, v1))], convert=False)
-> 1263 v0 = self.axes._validate_converted_limits(v0, self.convert_units)
   1264 v1 = self.axes._validate_converted_limits(v1, self.convert_units)
   1266 if v0 is None or v1 is None:
   1267     # Axes init calls set_xlim(0, 1) before get_xlim() can be called,
   1268     # so only grab the limits if we really need them.

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axes/_base.py:3891, in _AxesBase._validate_converted_limits(self, limit, convert)
   3888     converted_limit = converted_limit.squeeze()
   3889 if (isinstance(converted_limit, Real)
   3890         and not np.isfinite(converted_limit)):
-> 3891     raise ValueError("Axis limits cannot be NaN or Inf")
   3892 return converted_limit

ValueError: Axis limits cannot be NaN or Inf
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/backend_bases.py:2249, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
   2246     # we do this instead of `self.figure.draw_without_rendering`
   2247     # so that we can inject the orientation
   2248     with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2249         self.figure.draw(renderer)
   2250 else:
   2251     renderer = None

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     92 @wraps(draw)
     93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94     result = draw(artist, renderer, *args, **kwargs)
     95     if renderer._rasterizing:
     96         renderer.stop_rasterizing()

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/figure.py:3271, in Figure.draw(self, renderer)
   3267     return
   3269 with self._render_lock:
-> 3271     artists = self._get_draw_artists(renderer)
   3272     try:
   3273         renderer.open_group('figure', gid=self.get_gid())

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/figure.py:166, in FigureBase._get_draw_artists(self, renderer)
    164 for ax in self._localaxes:
    165     locator = ax.get_axes_locator()
--> 166     ax.apply_aspect(locator(ax, renderer) if locator else None)
    168     for child in ax.get_children():
    169         if hasattr(child, 'apply_aspect'):

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axes/_base.py:2127, in _AxesBase.apply_aspect(self, position)
   2124 if not self.get_autoscalex_on():
   2125     _log.warning("Ignoring fixed x limits to fulfill fixed data aspect "
   2126                  "with adjustable data limits.")
-> 2127 self.set_xbound(x_trf.inverted().transform([x0, x1]))

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axes/_base.py:3849, in _AxesBase.set_xbound(self, lower, upper)
   3846 if upper is None:
   3847     upper = old_upper
-> 3849 self.set_xlim(sorted((lower, upper),
   3850                      reverse=bool(self.xaxis_inverted())),
   3851               auto=None)

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axes/_base.py:3970, in _AxesBase.set_xlim(self, left, right, emit, auto, xmin, xmax)
   3968         raise TypeError("Cannot pass both 'right' and 'xmax'")
   3969     right = xmax
-> 3970 return self.xaxis._set_lim(left, right, emit=emit, auto=auto)

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axis.py:1263, in Axis._set_lim(self, v0, v1, emit, auto)
   1260 name = self._get_axis_name()
   1262 self.axes._process_unit_info([(name, (v0, v1))], convert=False)
-> 1263 v0 = self.axes._validate_converted_limits(v0, self.convert_units)
   1264 v1 = self.axes._validate_converted_limits(v1, self.convert_units)
   1266 if v0 is None or v1 is None:
   1267     # Axes init calls set_xlim(0, 1) before get_xlim() can be called,
   1268     # so only grab the limits if we really need them.

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axes/_base.py:3891, in _AxesBase._validate_converted_limits(self, limit, convert)
   3888     converted_limit = converted_limit.squeeze()
   3889 if (isinstance(converted_limit, Real)
   3890         and not np.isfinite(converted_limit)):
-> 3891     raise ValueError("Axis limits cannot be NaN or Inf")
   3892 return converted_limit

ValueError: Axis limits cannot be NaN or Inf
<Figure size 1800x500 with 6 Axes>

Calculate Quality#

for radar in ctree.children:
    rng = ctree[radar].ds.range
    azimuth = ctree[radar].ds.azimuth
    rscale = rng.diff("range").median().values
    qual = rng.wrl.qual.pulse_volume(rscale, 1.0).expand_dims(azimuth=ctree[radar].ds.azimuth)
    ctree[radar].ds = ctree[radar].ds.assign(QUAL=qual)
display(ctree)
<xarray.DataTree>
Group: /
├── Group: /jab
│       Dimensions:            (azimuth: 360, range: 598)
│       Coordinates: (12/15)
│         * azimuth            (azimuth) float32 1kB 0.5 1.5 2.5 ... 357.5 358.5 359.5
│           elevation          (azimuth) float64 3kB dask.array<chunksize=(360,), meta=np.ndarray>
│           time               (azimuth) datetime64[ns] 3kB dask.array<chunksize=(360,), meta=np.ndarray>
│         * range              (range) float32 2kB 250.0 750.0 ... 2.982e+05 2.988e+05
│           x                  (azimuth, range) float64 2MB 8.539e+04 ... 1.074e+05
│           y                  (azimuth, range) float64 2MB 5.688e+06 ... 5.986e+06
│           ...                 ...
│           bins               (azimuth, range) float32 861kB 250.0 750.0 ... 2.988e+05
│           sweep_mode         <U20 80B 'azimuth_surveillance'
│           longitude          float64 8B 3.064
│           latitude           float64 8B 51.19
│           altitude           float64 8B 50.0
│           crs_wkt            int64 8B 0
│       Data variables:
│           DBZH               (azimuth, range) float64 2MB dask.array<chunksize=(360, 598), meta=np.ndarray>
│           sweep_number       int64 8B ...
│           prt_mode           <U7 28B ...
│           follow_mode        <U7 28B ...
│           sweep_fixed_angle  float64 8B ...
│           nyquist_velocity   object 8B ...
│           QUAL               (azimuth, range) float64 2MB 7.477e+03 ... 1.068e+10
│       Attributes:
│           Conventions:  ODIM_H5/V2_2
├── Group: /wid
│       Dimensions:            (azimuth: 360, range: 1000)
│       Coordinates: (12/15)
│         * azimuth            (azimuth) float32 1kB 0.5 1.5 2.5 ... 357.5 358.5 359.5
│           elevation          (azimuth) float64 3kB dask.array<chunksize=(360,), meta=np.ndarray>
│           time               (azimuth) datetime64[ns] 3kB dask.array<chunksize=(360,), meta=np.ndarray>
│         * range              (range) float32 4kB 125.0 375.0 ... 2.496e+05 2.499e+05
│           x                  (azimuth, range) float64 3MB 2.492e+05 ... 2.588e+05
│           y                  (azimuth, range) float64 3MB 5.535e+06 ... 5.785e+06
│           ...                 ...
│           bins               (azimuth, range) float32 1MB 125.0 375.0 ... 2.499e+05
│           sweep_mode         <U20 80B 'azimuth_surveillance'
│           longitude          float64 8B 5.506
│           latitude           float64 8B 49.91
│           altitude           float64 8B 590.0
│           crs_wkt            int64 8B 0
│       Data variables:
│           DBZH               (azimuth, range) float64 3MB dask.array<chunksize=(360, 1000), meta=np.ndarray>
│           sweep_number       int64 8B ...
│           prt_mode           <U7 28B ...
│           follow_mode        <U7 28B ...
│           sweep_fixed_angle  float64 8B ...
│           nyquist_velocity   object 8B ...
│           QUAL               (azimuth, range) float64 3MB 934.6 ... 3.735e+09
│       Attributes:
│           Conventions:  ODIM_H5/V2_2
└── Group: /hel
        Dimensions:            (azimuth: 360, range: 800)
        Coordinates: (12/15)
          * azimuth            (azimuth) float32 1kB 0.5 1.5 2.5 ... 357.5 358.5 359.5
            elevation          (azimuth) float64 3kB dask.array<chunksize=(360,), meta=np.ndarray>
            time               (azimuth) datetime64[ns] 3kB dask.array<chunksize=(360,), meta=np.ndarray>
          * range              (range) float32 3kB 125.0 375.0 ... 1.996e+05 1.999e+05
            x                  (azimuth, range) float64 2MB 2.483e+05 ... 2.564e+05
            y                  (azimuth, range) float64 2MB 5.664e+06 ... 5.863e+06
            ...                 ...
            bins               (azimuth, range) float32 1MB 125.0 375.0 ... 1.999e+05
            sweep_mode         <U20 80B 'azimuth_surveillance'
            longitude          float64 8B 5.406
            latitude           float64 8B 51.07
            altitude           float64 8B 140.0
            crs_wkt            int64 8B 0
        Data variables:
            DBZH               (azimuth, range) float64 2MB dask.array<chunksize=(360, 800), meta=np.ndarray>
            sweep_number       int64 8B ...
            prt_mode           <U7 28B ...
            follow_mode        <U7 28B ...
            sweep_fixed_angle  float64 8B ...
            nyquist_velocity   object 8B ...
            QUAL               (azimuth, range) float64 2MB 934.6 8.411e+03 ... 2.39e+09
        Attributes:
            Conventions:  ODIM_H5/V2_2

Gridding#

First, we create a Datset with cartesian coordinates, which can hold our three radars. We are using wradlib.comp.CompMethods.togrid to interpolate our sweep data to the cartesian domain. As an interpolator we use wradlib.ipol.Nearest.

xmin, xmax, ymin, ymax = ctree.wrl.util.bbox()
x = np.linspace(xmin, xmax + 1000.0, 1000)
y = np.linspace(ymin, ymax + 1000.0, 1000)
cart = xr.Dataset(coords={"x": (["x"], x), "y": (["y"], y)})
display(cart)
<xarray.Dataset> Size: 16kB
Dimensions:  (x: 1000, y: 1000)
Coordinates:
  * x        (x) float64 8kB -2.143e+05 -2.136e+05 ... 4.991e+05 4.999e+05
  * y        (y) float64 8kB 5.285e+06 5.286e+06 ... 5.987e+06 5.988e+06
Data variables:
    *empty*
gtree = xr.DataTree()
for radar in ctree.children:
    gridded = ctree[radar].ds.wrl.comp.togrid(cart)
    gtree[radar] = gridded
display(gtree)
<xarray.DataTree>
Group: /
├── Group: /jab
│       Dimensions:            (y: 1000, x: 1000)
│       Coordinates:
│         * y                  (y) float64 8kB 5.285e+06 5.286e+06 ... 5.988e+06
│         * x                  (x) float64 8kB -2.143e+05 -2.136e+05 ... 4.999e+05
│           sweep_mode         <U20 80B 'azimuth_surveillance'
│           longitude          float64 8B 3.064
│           latitude           float64 8B 51.19
│           altitude           float64 8B 50.0
│           crs_wkt            int64 8B 0
│       Data variables:
│           DBZH               (y, x) float64 8MB dask.array<chunksize=(1000, 1000), meta=np.ndarray>
│           QUAL               (y, x) float64 8MB nan nan nan nan ... nan nan nan nan
│           sweep_number       int64 8B ...
│           prt_mode           <U7 28B ...
│           follow_mode        <U7 28B ...
│           sweep_fixed_angle  float64 8B ...
│           nyquist_velocity   object 8B ...
├── Group: /wid
│       Dimensions:            (y: 1000, x: 1000)
│       Coordinates:
│         * y                  (y) float64 8kB 5.285e+06 5.286e+06 ... 5.988e+06
│         * x                  (x) float64 8kB -2.143e+05 -2.136e+05 ... 4.999e+05
│           sweep_mode         <U20 80B 'azimuth_surveillance'
│           longitude          float64 8B 5.506
│           latitude           float64 8B 49.91
│           altitude           float64 8B 590.0
│           crs_wkt            int64 8B 0
│       Data variables:
│           DBZH               (y, x) float64 8MB dask.array<chunksize=(1000, 1000), meta=np.ndarray>
│           QUAL               (y, x) float64 8MB nan nan nan nan ... nan nan nan nan
│           sweep_number       int64 8B ...
│           prt_mode           <U7 28B ...
│           follow_mode        <U7 28B ...
│           sweep_fixed_angle  float64 8B ...
│           nyquist_velocity   object 8B ...
└── Group: /hel
        Dimensions:            (y: 1000, x: 1000)
        Coordinates:
          * y                  (y) float64 8kB 5.285e+06 5.286e+06 ... 5.988e+06
          * x                  (x) float64 8kB -2.143e+05 -2.136e+05 ... 4.999e+05
            sweep_mode         <U20 80B 'azimuth_surveillance'
            longitude          float64 8B 5.406
            latitude           float64 8B 51.07
            altitude           float64 8B 140.0
            crs_wkt            int64 8B 0
        Data variables:
            DBZH               (y, x) float64 8MB dask.array<chunksize=(1000, 1000), meta=np.ndarray>
            QUAL               (y, x) float64 8MB nan nan nan nan ... nan nan nan nan
            sweep_number       int64 8B ...
            prt_mode           <U7 28B ...
            follow_mode        <U7 28B ...
            sweep_fixed_angle  float64 8B ...
            nyquist_velocity   object 8B ...
fig, axs = plt.subplots(1, 3, figsize=(18, 5), sharey=True)
ax = axs.flat[0]
gtree["jab"].ds.DBZH.plot(ax=ax, vmin=0, vmax=60, cmap="HomeyerRainbow")
ax.set_aspect("equal")
ax.set_title("Radar Jabbeke")
ax = axs.flat[1]
gtree["wid"].ds.DBZH.plot(ax=ax, vmin=0, vmax=60, cmap="HomeyerRainbow")
ax.set_aspect("equal")
ax.set_title("Radar Wideumont")
ax = axs.flat[2]
gtree["hel"].ds.DBZH.plot(ax=ax, vmin=0, vmax=60, cmap="HomeyerRainbow")
ax.set_aspect("equal")
ax.set_title("Radar Helchteren")
fig.tight_layout()
Error in callback <function _draw_all_if_interactive at 0x7af7b5ccaca0> (for post_execute), with arguments args (),kwargs {}:
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/pyplot.py:300, in _draw_all_if_interactive()
    298 def _draw_all_if_interactive() -> None:
    299     if matplotlib.is_interactive():
--> 300         draw_all()

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/_pylab_helpers.py:133, in Gcf.draw_all(cls, force)
    131 for manager in cls.get_all_fig_managers():
    132     if force or manager.canvas.figure.stale:
--> 133         manager.canvas.draw_idle()

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/backend_bases.py:1989, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
   1987 if not self._is_idle_drawing:
   1988     with self._idle_draw_cntx():
-> 1989         self.draw(*args, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/backends/backend_agg.py:438, in FigureCanvasAgg.draw(self)
    435 # Acquire a lock on the shared font cache.
    436 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
    437       else nullcontext()):
--> 438     self.figure.draw(self.renderer)
    439     # A GUI class may be need to update a window using this draw, so
    440     # don't forget to call the superclass.
    441     super().draw()

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     92 @wraps(draw)
     93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94     result = draw(artist, renderer, *args, **kwargs)
     95     if renderer._rasterizing:
     96         renderer.stop_rasterizing()

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/figure.py:3282, in Figure.draw(self, renderer)
   3279             # ValueError can occur when resizing a window.
   3281     self.patch.draw(renderer)
-> 3282     mimage._draw_list_compositing_images(
   3283         renderer, self, artists, self.suppressComposite)
   3285     renderer.close_group('figure')
   3286 finally:

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/image.py:133, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    131 if not_composite or not has_images:
    132     for a in artists:
--> 133         a.draw(renderer)
    134 else:
    135     # Composite any adjacent images together
    136     image_group = []

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axes/_base.py:3314, in _AxesBase.draw(self, renderer)
   3311     for spine in self.spines.values():
   3312         artists.remove(spine)
-> 3314 self._update_title_position(renderer)
   3316 if not self.axison:
   3317     for _axis in self._axis_map.values():

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axes/_base.py:3258, in _AxesBase._update_title_position(self, renderer)
   3256 if title.get_text():
   3257     for ax in axs:
-> 3258         ax.yaxis.get_tightbbox(renderer)  # update offsetText
   3259         if ax.yaxis.offsetText.get_text():
   3260             bb = ax.yaxis.offsetText.get_tightbbox(renderer)

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axis.py:1425, in Axis.get_tightbbox(self, renderer, for_layout_only)
   1423 if renderer is None:
   1424     renderer = self.get_figure(root=True)._get_renderer()
-> 1425 ticks_to_draw = self._update_ticks()
   1427 self._update_label_position(renderer)
   1429 # go back to just this axis's tick labels

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axis.py:1344, in Axis._update_ticks(self)
   1340 # Check if major ticks should be computed.
   1341 # Skip if using NullLocator or if all visible components are off.
   1342 if (self._tick_group_visible(self._major_tick_kw)
   1343         and not isinstance(self.get_major_locator(), NullLocator)):
-> 1344     major_locs = self.get_majorticklocs()
   1345     major_labels = self.major.formatter.format_ticks(major_locs)
   1346     major_ticks = self.get_major_ticks(len(major_locs))

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axis.py:1673, in Axis.get_majorticklocs(self)
   1671 def get_majorticklocs(self):
   1672     """Return this Axis' major tick locations in data coordinates."""
-> 1673     return self.major.locator()

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/ticker.py:2299, in MaxNLocator.__call__(self)
   2297 def __call__(self):
   2298     vmin, vmax = self.axis.get_view_interval()
-> 2299     return self.tick_values(vmin, vmax)

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/ticker.py:2307, in MaxNLocator.tick_values(self, vmin, vmax)
   2304     vmin = -vmax
   2305 vmin, vmax = mtransforms._nonsingular(
   2306     vmin, vmax, expander=1e-13, tiny=1e-14)
-> 2307 locs = self._raw_ticks(vmin, vmax)
   2309 prune = self._prune
   2310 if prune == 'lower':

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/ticker.py:2237, in MaxNLocator._raw_ticks(self, vmin, vmax)
   2235 if self._nbins == 'auto':
   2236     if self.axis is not None:
-> 2237         nbins = np.clip(self.axis.get_tick_space(),
   2238                         max(1, self._min_n_ticks - 1), 9)
   2239     else:
   2240         nbins = 9

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axis.py:2984, in YAxis.get_tick_space(self)
   2982 size = self._get_tick_label_size('y') * 2
   2983 if size > 0:
-> 2984     return int(np.floor(length / size))
   2985 else:
   2986     return 2**31 - 1

ValueError: cannot convert float NaN to integer
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/backend_bases.py:2249, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
   2246     # we do this instead of `self.figure.draw_without_rendering`
   2247     # so that we can inject the orientation
   2248     with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2249         self.figure.draw(renderer)
   2250 else:
   2251     renderer = None

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     92 @wraps(draw)
     93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94     result = draw(artist, renderer, *args, **kwargs)
     95     if renderer._rasterizing:
     96         renderer.stop_rasterizing()

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/figure.py:3282, in Figure.draw(self, renderer)
   3279             # ValueError can occur when resizing a window.
   3281     self.patch.draw(renderer)
-> 3282     mimage._draw_list_compositing_images(
   3283         renderer, self, artists, self.suppressComposite)
   3285     renderer.close_group('figure')
   3286 finally:

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/image.py:133, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    131 if not_composite or not has_images:
    132     for a in artists:
--> 133         a.draw(renderer)
    134 else:
    135     # Composite any adjacent images together
    136     image_group = []

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axes/_base.py:3314, in _AxesBase.draw(self, renderer)
   3311     for spine in self.spines.values():
   3312         artists.remove(spine)
-> 3314 self._update_title_position(renderer)
   3316 if not self.axison:
   3317     for _axis in self._axis_map.values():

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axes/_base.py:3258, in _AxesBase._update_title_position(self, renderer)
   3256 if title.get_text():
   3257     for ax in axs:
-> 3258         ax.yaxis.get_tightbbox(renderer)  # update offsetText
   3259         if ax.yaxis.offsetText.get_text():
   3260             bb = ax.yaxis.offsetText.get_tightbbox(renderer)

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axis.py:1425, in Axis.get_tightbbox(self, renderer, for_layout_only)
   1423 if renderer is None:
   1424     renderer = self.get_figure(root=True)._get_renderer()
-> 1425 ticks_to_draw = self._update_ticks()
   1427 self._update_label_position(renderer)
   1429 # go back to just this axis's tick labels

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axis.py:1344, in Axis._update_ticks(self)
   1340 # Check if major ticks should be computed.
   1341 # Skip if using NullLocator or if all visible components are off.
   1342 if (self._tick_group_visible(self._major_tick_kw)
   1343         and not isinstance(self.get_major_locator(), NullLocator)):
-> 1344     major_locs = self.get_majorticklocs()
   1345     major_labels = self.major.formatter.format_ticks(major_locs)
   1346     major_ticks = self.get_major_ticks(len(major_locs))

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axis.py:1673, in Axis.get_majorticklocs(self)
   1671 def get_majorticklocs(self):
   1672     """Return this Axis' major tick locations in data coordinates."""
-> 1673     return self.major.locator()

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/ticker.py:2299, in MaxNLocator.__call__(self)
   2297 def __call__(self):
   2298     vmin, vmax = self.axis.get_view_interval()
-> 2299     return self.tick_values(vmin, vmax)

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/ticker.py:2307, in MaxNLocator.tick_values(self, vmin, vmax)
   2304     vmin = -vmax
   2305 vmin, vmax = mtransforms._nonsingular(
   2306     vmin, vmax, expander=1e-13, tiny=1e-14)
-> 2307 locs = self._raw_ticks(vmin, vmax)
   2309 prune = self._prune
   2310 if prune == 'lower':

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/ticker.py:2237, in MaxNLocator._raw_ticks(self, vmin, vmax)
   2235 if self._nbins == 'auto':
   2236     if self.axis is not None:
-> 2237         nbins = np.clip(self.axis.get_tick_space(),
   2238                         max(1, self._min_n_ticks - 1), 9)
   2239     else:
   2240         nbins = 9

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axis.py:2984, in YAxis.get_tick_space(self)
   2982 size = self._get_tick_label_size('y') * 2
   2983 if size > 0:
-> 2984     return int(np.floor(length / size))
   2985 else:
   2986     return 2**31 - 1

ValueError: cannot convert float NaN to integer
<Figure size 1800x500 with 6 Axes>
fig, axs = plt.subplots(1, 3, figsize=(18, 5), sharey=True)
ax = axs.flat[0]
gtree["jab"].ds.QUAL.plot(ax=ax)
ax.set_aspect("equal")
ax.set_title("Radar Jabbeke")
ax = axs.flat[1]
gtree["wid"].ds.QUAL.plot(ax=ax)
ax.set_aspect("equal")
ax.set_title("Radar Wideumont")
ax = axs.flat[2]
gtree["hel"].ds.QUAL.plot(ax=ax)
ax.set_aspect("equal")
ax.set_title("Radar Helchteren")
fig.tight_layout()
Error in callback <function _draw_all_if_interactive at 0x7af7b5ccaca0> (for post_execute), with arguments args (),kwargs {}:
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/pyplot.py:300, in _draw_all_if_interactive()
    298 def _draw_all_if_interactive() -> None:
    299     if matplotlib.is_interactive():
--> 300         draw_all()

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/_pylab_helpers.py:133, in Gcf.draw_all(cls, force)
    131 for manager in cls.get_all_fig_managers():
    132     if force or manager.canvas.figure.stale:
--> 133         manager.canvas.draw_idle()

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/backend_bases.py:1989, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
   1987 if not self._is_idle_drawing:
   1988     with self._idle_draw_cntx():
-> 1989         self.draw(*args, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/backends/backend_agg.py:438, in FigureCanvasAgg.draw(self)
    435 # Acquire a lock on the shared font cache.
    436 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
    437       else nullcontext()):
--> 438     self.figure.draw(self.renderer)
    439     # A GUI class may be need to update a window using this draw, so
    440     # don't forget to call the superclass.
    441     super().draw()

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     92 @wraps(draw)
     93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94     result = draw(artist, renderer, *args, **kwargs)
     95     if renderer._rasterizing:
     96         renderer.stop_rasterizing()

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/figure.py:3282, in Figure.draw(self, renderer)
   3279             # ValueError can occur when resizing a window.
   3281     self.patch.draw(renderer)
-> 3282     mimage._draw_list_compositing_images(
   3283         renderer, self, artists, self.suppressComposite)
   3285     renderer.close_group('figure')
   3286 finally:

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/image.py:133, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    131 if not_composite or not has_images:
    132     for a in artists:
--> 133         a.draw(renderer)
    134 else:
    135     # Composite any adjacent images together
    136     image_group = []

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axes/_base.py:3314, in _AxesBase.draw(self, renderer)
   3311     for spine in self.spines.values():
   3312         artists.remove(spine)
-> 3314 self._update_title_position(renderer)
   3316 if not self.axison:
   3317     for _axis in self._axis_map.values():

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axes/_base.py:3258, in _AxesBase._update_title_position(self, renderer)
   3256 if title.get_text():
   3257     for ax in axs:
-> 3258         ax.yaxis.get_tightbbox(renderer)  # update offsetText
   3259         if ax.yaxis.offsetText.get_text():
   3260             bb = ax.yaxis.offsetText.get_tightbbox(renderer)

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axis.py:1425, in Axis.get_tightbbox(self, renderer, for_layout_only)
   1423 if renderer is None:
   1424     renderer = self.get_figure(root=True)._get_renderer()
-> 1425 ticks_to_draw = self._update_ticks()
   1427 self._update_label_position(renderer)
   1429 # go back to just this axis's tick labels

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axis.py:1344, in Axis._update_ticks(self)
   1340 # Check if major ticks should be computed.
   1341 # Skip if using NullLocator or if all visible components are off.
   1342 if (self._tick_group_visible(self._major_tick_kw)
   1343         and not isinstance(self.get_major_locator(), NullLocator)):
-> 1344     major_locs = self.get_majorticklocs()
   1345     major_labels = self.major.formatter.format_ticks(major_locs)
   1346     major_ticks = self.get_major_ticks(len(major_locs))

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axis.py:1673, in Axis.get_majorticklocs(self)
   1671 def get_majorticklocs(self):
   1672     """Return this Axis' major tick locations in data coordinates."""
-> 1673     return self.major.locator()

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/ticker.py:2299, in MaxNLocator.__call__(self)
   2297 def __call__(self):
   2298     vmin, vmax = self.axis.get_view_interval()
-> 2299     return self.tick_values(vmin, vmax)

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/ticker.py:2307, in MaxNLocator.tick_values(self, vmin, vmax)
   2304     vmin = -vmax
   2305 vmin, vmax = mtransforms._nonsingular(
   2306     vmin, vmax, expander=1e-13, tiny=1e-14)
-> 2307 locs = self._raw_ticks(vmin, vmax)
   2309 prune = self._prune
   2310 if prune == 'lower':

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/ticker.py:2237, in MaxNLocator._raw_ticks(self, vmin, vmax)
   2235 if self._nbins == 'auto':
   2236     if self.axis is not None:
-> 2237         nbins = np.clip(self.axis.get_tick_space(),
   2238                         max(1, self._min_n_ticks - 1), 9)
   2239     else:
   2240         nbins = 9

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axis.py:2984, in YAxis.get_tick_space(self)
   2982 size = self._get_tick_label_size('y') * 2
   2983 if size > 0:
-> 2984     return int(np.floor(length / size))
   2985 else:
   2986     return 2**31 - 1

ValueError: cannot convert float NaN to integer
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/backend_bases.py:2249, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
   2246     # we do this instead of `self.figure.draw_without_rendering`
   2247     # so that we can inject the orientation
   2248     with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2249         self.figure.draw(renderer)
   2250 else:
   2251     renderer = None

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     92 @wraps(draw)
     93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94     result = draw(artist, renderer, *args, **kwargs)
     95     if renderer._rasterizing:
     96         renderer.stop_rasterizing()

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/figure.py:3282, in Figure.draw(self, renderer)
   3279             # ValueError can occur when resizing a window.
   3281     self.patch.draw(renderer)
-> 3282     mimage._draw_list_compositing_images(
   3283         renderer, self, artists, self.suppressComposite)
   3285     renderer.close_group('figure')
   3286 finally:

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/image.py:133, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    131 if not_composite or not has_images:
    132     for a in artists:
--> 133         a.draw(renderer)
    134 else:
    135     # Composite any adjacent images together
    136     image_group = []

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axes/_base.py:3314, in _AxesBase.draw(self, renderer)
   3311     for spine in self.spines.values():
   3312         artists.remove(spine)
-> 3314 self._update_title_position(renderer)
   3316 if not self.axison:
   3317     for _axis in self._axis_map.values():

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axes/_base.py:3258, in _AxesBase._update_title_position(self, renderer)
   3256 if title.get_text():
   3257     for ax in axs:
-> 3258         ax.yaxis.get_tightbbox(renderer)  # update offsetText
   3259         if ax.yaxis.offsetText.get_text():
   3260             bb = ax.yaxis.offsetText.get_tightbbox(renderer)

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axis.py:1425, in Axis.get_tightbbox(self, renderer, for_layout_only)
   1423 if renderer is None:
   1424     renderer = self.get_figure(root=True)._get_renderer()
-> 1425 ticks_to_draw = self._update_ticks()
   1427 self._update_label_position(renderer)
   1429 # go back to just this axis's tick labels

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axis.py:1344, in Axis._update_ticks(self)
   1340 # Check if major ticks should be computed.
   1341 # Skip if using NullLocator or if all visible components are off.
   1342 if (self._tick_group_visible(self._major_tick_kw)
   1343         and not isinstance(self.get_major_locator(), NullLocator)):
-> 1344     major_locs = self.get_majorticklocs()
   1345     major_labels = self.major.formatter.format_ticks(major_locs)
   1346     major_ticks = self.get_major_ticks(len(major_locs))

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axis.py:1673, in Axis.get_majorticklocs(self)
   1671 def get_majorticklocs(self):
   1672     """Return this Axis' major tick locations in data coordinates."""
-> 1673     return self.major.locator()

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/ticker.py:2299, in MaxNLocator.__call__(self)
   2297 def __call__(self):
   2298     vmin, vmax = self.axis.get_view_interval()
-> 2299     return self.tick_values(vmin, vmax)

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/ticker.py:2307, in MaxNLocator.tick_values(self, vmin, vmax)
   2304     vmin = -vmax
   2305 vmin, vmax = mtransforms._nonsingular(
   2306     vmin, vmax, expander=1e-13, tiny=1e-14)
-> 2307 locs = self._raw_ticks(vmin, vmax)
   2309 prune = self._prune
   2310 if prune == 'lower':

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/ticker.py:2237, in MaxNLocator._raw_ticks(self, vmin, vmax)
   2235 if self._nbins == 'auto':
   2236     if self.axis is not None:
-> 2237         nbins = np.clip(self.axis.get_tick_space(),
   2238                         max(1, self._min_n_ticks - 1), 9)
   2239     else:
   2240         nbins = 9

File ~/checkouts/readthedocs.org/user_builds/wradlib/conda/2.9.0/lib/python3.13/site-packages/matplotlib/axis.py:2984, in YAxis.get_tick_space(self)
   2982 size = self._get_tick_label_size('y') * 2
   2983 if size > 0:
-> 2984     return int(np.floor(length / size))
   2985 else:
   2986     return 2**31 - 1

ValueError: cannot convert float NaN to integer
<Figure size 1800x500 with 6 Axes>

Compositing#

Before compositing we combine the three radar grids as well as the quality grids into one Dataset, respectively.

radars = xr.DataArray(gtree.children, dims="radar")
radargrids = xr.concat([gtree[radar].ds.DBZH for radar in gtree.children], dim=radars)
# normalizing Quality between 1. and 0.
qualitygrids = xr.concat([1. - (gtree[radar].ds.QUAL / gtree[radar].ds.QUAL.max()) for radar in gtree.children], dim=radars)

display(radargrids)
display(qualitygrids)
<xarray.DataArray 'DBZH' (radar: 3, y: 1000, x: 1000)> Size: 24MB
dask.array<concatenate, shape=(3, 1000, 1000), dtype=float64, chunksize=(1, 1000, 1000), chunktype=numpy.ndarray>
Coordinates:
  * radar       (radar) <U3 36B 'jab' 'wid' 'hel'
    longitude   (radar) float64 24B 3.064 5.506 5.406
    latitude    (radar) float64 24B 51.19 49.91 51.07
    altitude    (radar) float64 24B 50.0 590.0 140.0
  * y           (y) float64 8kB 5.285e+06 5.286e+06 ... 5.987e+06 5.988e+06
  * x           (x) float64 8kB -2.143e+05 -2.136e+05 ... 4.991e+05 4.999e+05
    sweep_mode  <U20 80B 'azimuth_surveillance'
    crs_wkt     int64 8B 0
Attributes:
    _Undetect:      0.0
    units:          dBZ
    standard_name:  radar_equivalent_reflectivity_factor_h
    long_name:      Equivalent reflectivity factor H
<xarray.DataArray 'QUAL' (radar: 3, y: 1000, x: 1000)> Size: 24MB
array([[[nan, nan, nan, ..., nan, nan, nan],
        [nan, nan, nan, ..., nan, nan, nan],
        [nan, nan, nan, ..., nan, nan, nan],
        ...,
        [nan, nan, nan, ..., nan, nan, nan],
        [nan, nan, nan, ..., nan, nan, nan],
        [nan, nan, nan, ..., nan, nan, nan]],

       [[nan, nan, nan, ..., nan, nan, nan],
        [nan, nan, nan, ..., nan, nan, nan],
        [nan, nan, nan, ..., nan, nan, nan],
        ...,
        [nan, nan, nan, ..., nan, nan, nan],
        [nan, nan, nan, ..., nan, nan, nan],
        [nan, nan, nan, ..., nan, nan, nan]],

       [[nan, nan, nan, ..., nan, nan, nan],
        [nan, nan, nan, ..., nan, nan, nan],
        [nan, nan, nan, ..., nan, nan, nan],
        ...,
        [nan, nan, nan, ..., nan, nan, nan],
        [nan, nan, nan, ..., nan, nan, nan],
        [nan, nan, nan, ..., nan, nan, nan]]], shape=(3, 1000, 1000))
Coordinates:
  * radar       (radar) <U3 36B 'jab' 'wid' 'hel'
    longitude   (radar) float64 24B 3.064 5.506 5.406
    latitude    (radar) float64 24B 51.19 49.91 51.07
    altitude    (radar) float64 24B 50.0 590.0 140.0
  * y           (y) float64 8kB 5.285e+06 5.286e+06 ... 5.987e+06 5.988e+06
  * x           (x) float64 8kB -2.143e+05 -2.136e+05 ... 4.991e+05 4.999e+05
    sweep_mode  <U20 80B 'azimuth_surveillance'
    crs_wkt     int64 8B 0
Attributes:
    units:                           meters
    standard_name:                   projection_range_coordinate
    long_name:                       range_to_measurement_volume
    axis:                            radial_range_coordinate
    meters_between_gates:            500.0
    spacing_is_constant:             true
    meters_to_center_of_first_gate:  250.0

Then we finally can call wradlib.comp.CompMethods.compose_weighted to create the final output.

composite = radargrids.wrl.comp.compose_weighted(qualitygrids)
display(composite)
<xarray.DataArray 'DBZH' (y: 1000, x: 1000)> Size: 8MB
dask.array<where, shape=(1000, 1000), dtype=float64, chunksize=(1000, 1000), chunktype=numpy.ndarray>
Coordinates:
  * y           (y) float64 8kB 5.285e+06 5.286e+06 ... 5.987e+06 5.988e+06
  * x           (x) float64 8kB -2.143e+05 -2.136e+05 ... 4.991e+05 4.999e+05
    sweep_mode  <U20 80B 'azimuth_surveillance'
    crs_wkt     int64 8B 0
Attributes:
    _Undetect:      0.0
    units:          dBZ
    standard_name:  radar_equivalent_reflectivity_factor_h
    long_name:      Equivalent reflectivity factor H

Plot Result#

composite.where(composite>0.1).plot(cmap="HomeyerRainbow", vmin=0, vmax=60)
<matplotlib.collections.QuadMesh at 0x7af7a0ae8410>
../../_images/604e506fbdac58c66af898a1feeb71a425c0d5d79b3e57f8b8edeb55854375ae.png