Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Convert CfRadial1 NetCDF to CfRadial2 Zarr with xradar

Access S-Pol PRECIP Radar Data through the NCAR GDEX and Convert CfRadial1 to CfRadial2

Required Packages

The following packages are needed for this example. Please make sure they are installed before moving forward.

  • xarray

  • xradar

  • h5netcdf>=1.8.0

  • netCDF4

  • zarr

  • numpy

  • matplotlib

  • cmweather

Why CfRadial1 to CfRadial2

Radar data are often distributed in the CfRadial1 format: one flat NetCDF file per volume, where all sweeps are stacked along the ray (time) dimension. The newer CfRadial2 data model (based on the WMO FM 301 standard) stores each sweep in its own group, together with dedicated groups for radar parameters, calibration and georeferencing. That hierarchical structure maps naturally onto an xarray.DataTree, and onto cloud-optimized formats such as Zarr.

In this notebook we will:

  1. Open a CfRadial1 NetCDF file from GDEX.

  2. Convert it to a CfRadial2 DataTree with xradar’s .xradar.to_cf2() method (see the xradar Transform notebook).

  3. Write the CfRadial2 DataTree to a local Zarr store and to a NetCDF4 file.

  4. Do a round trip, CfRadial2 → CfRadial1, and check that the radar data are unchanged compared with the original NetCDF file (see the xradar CfRadial1 to CfRadial2 Data Model Transformation notebook).

1. Open CfRadial1

The example file is available on GDEX both through the GDEX POSIX path (on NCAR HPC systems such as Derecho/Casper) and through an HTTPS data URL. In this example we use the GDEX POSIX path. If you are not on an NCAR system, set use_posix = False to read the same file over HTTPS.

We open the CfRadial1 file as a plain xarray.Dataset. Note that import xradar is required to make the .xradar accessor available.

Loading...

The CfRadial1 file is flat: all sweeps share the time dimension, and because n_gates_vary = "true" the radar moments (e.g. DBZ) are 1-D arrays along n_points.

n_gates_vary : true
sweeps       : 11
rays (time)  : 2866
n_points     : 5729134
DBZ dims     : ('n_points',)

2. Convert CfRadial1 to CfRadial2

The .xradar.to_cf2() method (an alias of .xradar.to_cfradial2() / .xradar.to_cfradial2_datatree()) converts the flat CfRadial1 Dataset into a CfRadial2 DataTree, following the xradar Transform notebook. The ragged n_points fields are unpacked onto a regular (azimuth, range) grid per sweep, with NaN padding where a ray has fewer gates.

The method returns a new object; it does not modify ds_cf1 in place.

Loading...
/                         dims={'sweep': 11, 'frequency': 1}
/radar_parameters         dims={'frequency': 1}
/radar_calibration        dims={'frequency': 1}
/georeferencing_correction dims={'frequency': 1}
/sweep_0                  dims={'elevation': 252, 'range': 1999, 'frequency': 1}
/sweep_1                  dims={'elevation': 259, 'range': 1999, 'frequency': 1}
/sweep_2                  dims={'elevation': 259, 'range': 1999, 'frequency': 1}
/sweep_3                  dims={'elevation': 255, 'range': 1999, 'frequency': 1}
/sweep_4                  dims={'elevation': 256, 'range': 1999, 'frequency': 1}
/sweep_5                  dims={'elevation': 260, 'range': 1999, 'frequency': 1}
/sweep_6                  dims={'elevation': 266, 'range': 1999, 'frequency': 1}
/sweep_7                  dims={'elevation': 265, 'range': 1999, 'frequency': 1}
/sweep_8                  dims={'elevation': 266, 'range': 1999, 'frequency': 1}
/sweep_9                  dims={'elevation': 265, 'range': 1999, 'frequency': 1}
/sweep_10                 dims={'elevation': 263, 'range': 1999, 'frequency': 1}

A quick look at the data

Before writing anything out, we plot the reflectivity of the first RHI sweep. georeference() adds Cartesian x, y, z coordinates (in meters, relative to the radar), and for an RHI we plot height against the horizontal distance from the radar.

<Figure size 1000x400 with 2 Axes>

Write the CfRadial2 DataTree to a local Zarr store

Some variables decoded from the CfRadial1 character arrays (e.g. time_coverage_start, instrument_name) are fixed-length string arrays. These are not reliably supported by Zarr, so we convert them to variable-length (object) strings before writing. Only the string variables are copied; all other arrays are shared by reference.

/glade/u/home/chiaweih/test_xradar/.venv/lib/python3.11/site-packages/zarr/api/asynchronous.py:247: ZarrUserWarning: Consolidated metadata is currently not part in the Zarr format 3 specification. It may not be supported by other zarr implementations and may change in the future.
  warnings.warn(
/glade/u/home/chiaweih/test_xradar/.venv/lib/python3.11/site-packages/zarr/api/asynchronous.py:247: ZarrUserWarning: Consolidated metadata is currently not part in the Zarr format 3 specification. It may not be supported by other zarr implementations and may change in the future.
  warnings.warn(
/glade/u/home/chiaweih/test_xradar/.venv/lib/python3.11/site-packages/zarr/api/asynchronous.py:247: ZarrUserWarning: Consolidated metadata is currently not part in the Zarr format 3 specification. It may not be supported by other zarr implementations and may change in the future.
  warnings.warn(
/glade/u/home/chiaweih/test_xradar/.venv/lib/python3.11/site-packages/zarr/api/asynchronous.py:247: ZarrUserWarning: Consolidated metadata is currently not part in the Zarr format 3 specification. It may not be supported by other zarr implementations and may change in the future.
  warnings.warn(
/glade/u/home/chiaweih/test_xradar/.venv/lib/python3.11/site-packages/zarr/api/asynchronous.py:247: ZarrUserWarning: Consolidated metadata is currently not part in the Zarr format 3 specification. It may not be supported by other zarr implementations and may change in the future.
  warnings.warn(
/glade/u/home/chiaweih/test_xradar/.venv/lib/python3.11/site-packages/zarr/api/asynchronous.py:247: ZarrUserWarning: Consolidated metadata is currently not part in the Zarr format 3 specification. It may not be supported by other zarr implementations and may change in the future.
  warnings.warn(
/glade/u/home/chiaweih/test_xradar/.venv/lib/python3.11/site-packages/zarr/api/asynchronous.py:247: ZarrUserWarning: Consolidated metadata is currently not part in the Zarr format 3 specification. It may not be supported by other zarr implementations and may change in the future.
  warnings.warn(
/glade/u/home/chiaweih/test_xradar/.venv/lib/python3.11/site-packages/zarr/api/asynchronous.py:247: ZarrUserWarning: Consolidated metadata is currently not part in the Zarr format 3 specification. It may not be supported by other zarr implementations and may change in the future.
  warnings.warn(
/glade/u/home/chiaweih/test_xradar/.venv/lib/python3.11/site-packages/zarr/api/asynchronous.py:247: ZarrUserWarning: Consolidated metadata is currently not part in the Zarr format 3 specification. It may not be supported by other zarr implementations and may change in the future.
  warnings.warn(
/glade/u/home/chiaweih/test_xradar/.venv/lib/python3.11/site-packages/zarr/api/asynchronous.py:247: ZarrUserWarning: Consolidated metadata is currently not part in the Zarr format 3 specification. It may not be supported by other zarr implementations and may change in the future.
  warnings.warn(
/glade/u/home/chiaweih/test_xradar/.venv/lib/python3.11/site-packages/zarr/api/asynchronous.py:247: ZarrUserWarning: Consolidated metadata is currently not part in the Zarr format 3 specification. It may not be supported by other zarr implementations and may change in the future.
  warnings.warn(
CPU times: user 7.91 s, sys: 2.16 s, total: 10.1 s
Wall time: 10.2 s
/glade/u/home/chiaweih/test_xradar/.venv/lib/python3.11/site-packages/zarr/api/asynchronous.py:247: ZarrUserWarning: Consolidated metadata is currently not part in the Zarr format 3 specification. It may not be supported by other zarr implementations and may change in the future.
  warnings.warn(
<xarray.backends.zarr.ZarrStore at 0x7f3d141b8ee0>

Reopen the Zarr store lazily as a DataTree. The groups are the same CfRadial2 groups we wrote.

Loading...

Write the same CfRadial2 DataTree to NetCDF4

The CfRadial2 model is not tied to Zarr. The same DataTree can be written to a grouped NetCDF4 file with xradar’s xd.io.to_cfradial2() exporter (or directly with dtree.to_netcdf()).

CPU times: user 2.53 s, sys: 398 ms, total: 2.93 s
Wall time: 2.5 s
Same group structure: True
altitude_agl              data equal: True
instrument_type           data equal: True
platform_type             data equal: True
primary_axis              data equal: True
status_str                data equal: True
sweep_fixed_angle         data equal: True
sweep_group_name          data equal: True
time_coverage_end         data equal: True
time_coverage_start       data equal: True
volume_number             data equal: True
georeferencing_correction data equal: True
radar_calibration         data equal: True
radar_parameters          data equal: True
sweep_0                   data equal: True
sweep_1                   data equal: True
sweep_10                  data equal: True
sweep_2                   data equal: True
sweep_3                   data equal: True
sweep_4                   data equal: True
sweep_5                   data equal: True
sweep_6                   data equal: True
sweep_7                   data equal: True
sweep_8                   data equal: True
sweep_9                   data equal: True
CfRadial1 NetCDF :   65.17 MB
CfRadial2 Zarr   :   62.26 MB
CfRadial2 NetCDF4:  597.48 MB

Round trip: CfRadial2 → CfRadial1, compared with the original file

To check that nothing is lost, we convert the CfRadial2 tree read back from the Zarr store into a CfRadial1 Dataset with .xradar.to_cfradial1_dataset() (alias .xradar.to_cfradial1()). This is the inverse of the transformation described in the xradar CfRadial1 to CfRadial2 Data Model Transformation notebook.

The full round trip is therefore: CfRadial1 NetCDF → CfRadial2 DataTree → Zarr → CfRadial2 DataTree → CfRadial1 Dataset.

Loading...

xradar always exports the padded CfRadial1 layout: fields are (time, range) arrays with NaN beyond each ray’s last gate, and n_gates_vary is set to "false". The original file uses the ragged n_points layout, so the two datasets cannot be compared with a single identical() call. We compare them in three steps instead:

  1. which variables exist on each side,

  2. the per-ray metadata (time, azimuth, elevation, ...),

  3. the radar moments, after unpacking the original ragged arrays onto the padded grid.

Only in original  : ['grid_mapping', 'r_calib_base_dbz_1km_hc', 'r_calib_base_dbz_1km_hx', 'r_calib_base_dbz_1km_vc', 'r_calib_base_dbz_1km_vx', 'radar_rx_bandwidth', 'ray_angle_res', 'ray_n_gates', 'ray_start_index', 'rays_are_indexed', 'status_xml', 'target_scan_rate']
Only in round trip: ['r_calib_base_1km_hc', 'r_calib_base_1km_hx', 'r_calib_base_1km_vc', 'r_calib_base_1km_vx', 'radar_receiver_bandwidth', 'status_str']
antenna_transition           identical
azimuth                      identical
elevation                    identical
measured_transmit_power_h    identical
measured_transmit_power_v    identical
n_samples                    identical
nyquist_velocity             identical
prt                          identical
prt_ratio                    identical
pulse_width                  identical
r_calib_index                identical
ray_gate_spacing             identical
ray_start_range              identical
scan_rate                    identical
time                         identical
unambiguous_range            identical

For the radar moments we rebuild a boolean mask of the valid gates from ray_n_gates. Selecting the padded round-trip array with that mask returns the gates ray by ray, in exactly the order they are stored in the original n_points array, so the two can be compared directly. We also check that the padding outside the mask is all NaN.

RATE_ZH        identical    padding all NaN: True
RATE_HYBRID    identical    padding all NaN: True
PID            identical    padding all NaN: True
TEMP_FOR_PID   identical    padding all NaN: True
DBZ            identical    padding all NaN: True
DBZ_F          identical    padding all NaN: True
VEL            identical    padding all NaN: True
VEL_F          identical    padding all NaN: True
WIDTH          identical    padding all NaN: True
WIDTH_F        identical    padding all NaN: True
NCP            identical    padding all NaN: True
NCP_F          identical    padding all NaN: True
ZDR_F          identical    padding all NaN: True
PHIDP_F        identical    padding all NaN: True
KDP_F          identical    padding all NaN: True
RHOHV_F        identical    padding all NaN: True
RHOHV_NNC_F    identical    padding all NaN: True
SNRHC          identical    padding all NaN: True
SNRHC_F        identical    padding all NaN: True
SNRVC          identical    padding all NaN: True
SNRVC_F        identical    padding all NaN: True
DBMHC          identical    padding all NaN: True
DBMHC_F        identical    padding all NaN: True
DBMVC          identical    padding all NaN: True
DBMVC_F        identical    padding all NaN: True
CMD_FLAG       identical    padding all NaN: True

All radar moments identical after the round trip: True
Conventions:
    original  : 'CF-1.7'
    round trip: 'Cf/Radial'
Sub_conventions:
    original  : 'CF-Radial instrument_parameters radar_parameters radar_calibration'
    round trip: '<missing>'
created:
    original  : '2024/02/20 20:39:37.094'
    round trip: '<missing>'
driver:
    original  : 'RadxConvert(NCAR)'
    round trip: '<missing>'
end_datetime:
    original  : '2022-05-25T01:57:09Z'
    round trip: '<missing>'
end_time:
    original  : '2022-05-25 01:57:09.985'
    round trip: '<missing>'
history:
    original  : ''
    round trip: 'xradar v0.12.1.dev36+g00aba7834 CfRadial1 export'
n_gates_vary:
    original  : 'true'
    round trip: '<missing>'
original_format:
    original  : 'CFRADIAL'
    round trip: '<missing>'
start_datetime:
    original  : '2022-05-25T01:54:43Z'
    round trip: '<missing>'
start_time:
    original  : '2022-05-25 01:54:43.665'
    round trip: '<missing>'
time_coverage_end:
    original  : '2022-05-25T01:57:09Z'
    round trip: '<missing>'
time_coverage_start:
    original  : '2022-05-25T01:54:43Z'
    round trip: '<missing>'
version:
    original  : 'CF-Radial-1.4'
    round trip: '1.2'