Spaceborne-Ground Radar Calibration (Application)#
In this tutorial, we demonstrate how to exploit GPM-API along with other radar software to match reflectivities measurements of spaceborne (SR) and ground (GR) radars and assess the GR calibration bias.
As example, we analyze a coincident GPM DPR and NEXRAD overpass over San Diego, USA, during the landfall of Tropical Storm Hilary in 2023. The NEXRAD KNKX radar data appears to be significantly miscalibrated and we will assess the calibration bias.
NEXRAD data of interest are download from the AWS bucket using radar_api and read into xarray using xradar. GPM data are download using GPM-API and the volume_matching routine is used to match the SR and GR data.
Note that the GPM-API’s volume_matching routine can automatically download and open the coincident GPM overpass data if the SR dataset is not provided to the function.
Please read the Spaceborne-Ground Radar Matching Tutorial for a step-by-step guide through the process of obtaining spatially and temporally coincident radar samples.
Relevant software#
To run this tutorial, it is necessary to install additional libraries which are not automatically required by GPM-API. The libraries are xradar, wradlib, geopandas, radar_api, fsspec, s3fs,
folium. You can install them with terminal command conda install -c conda-forge radar_api xradar wradlib geopandas fsspec s3fs folium
If you have such libraries installed, you can start importing the required packages:
[ ]:
import warnings
warnings.filterwarnings("ignore")
from functools import reduce
import cartopy.crs as ccrs
import fsspec
import numpy as np
import radar_api
import xradar as xd
from IPython.display import display
from gpm.gv import (
calibration_summary,
compare_maps,
reflectivity_scatterplots,
volume_matching,
)
np.set_printoptions(suppress=True)
Read GR data#
In this tutorial, we use a coincident GPM DPR/NEXRAD overpass over San Diego (USA) during the landfall of Tropical Storm Hilary in 2023. Let’s load manually the GR scanning volume of interest from the AWS S3 bucket.
[ ]:
filepath = "noaa-nexrad-level2/2023/08/20/KNKX/KNKX20230820_221341_V06"
file = fsspec.open_local(
f"simplecache::s3://{filepath}",
s3={"anon": True},
filecache={"cache_storage": "."},
)
dt_gr = xd.io.open_nexradlevel2_datatree(file)
As alternative, you can search for GR data files using radar_api and then either download or directly open the file using the xradar wrapper within radar_api.
[ ]:
start_time = "2023-08-20 22:13:40"
end_time = "2023-08-20 22:20:00"
filepaths = radar_api.find_files(
network="NEXRAD",
radar="KNKX",
start_time=start_time,
end_time=end_time,
verbose=False,
)
print(filepaths)
filepath = filepaths[1]
print(filepath)
['s3://noaa-nexrad-level2/2023/08/20/KNKX/KNKX20230820_220808_V06', 's3://noaa-nexrad-level2/2023/08/20/KNKX/KNKX20230820_221341_V06', 's3://noaa-nexrad-level2/2023/08/20/KNKX/KNKX20230820_221914_V06']
s3://noaa-nexrad-level2/2023/08/20/KNKX/KNKX20230820_221341_V06
[ ]:
dt_gr = radar_api.open_datatree(filepath, network="NEXRAD")
Now, let’s select a GR sweep to match with GPM DPR
[ ]:
sweep_idx = 0
sweep_group = f"sweep_{sweep_idx}" # GR sweep (elevation to be used)
ds_gr = dt_gr[sweep_group].to_dataset().compute()
and display the horizontally polarized reflectivity field:
[ ]:
ds_gr["DBZH"].where(ds_gr["DBZH"] > -10).xradar_dev.plot_map()
<cartopy.mpl.geocollection.GeoQuadMesh at 0x7f9c28eed250>
Run SR/GR volume matching#
Here we start defining the required settings for the SR/GR volume matching procedure:
The GR
radar_bandcontrols to which frequency SR Ku-band reflectivities will be converted. Valid values areX,C,S,Ku.The
beamwidth_grrefers to the angular beam width of the GR.The minimum reflectivity thresholds
z_min_threshold_srandz_min_threshold_grare used to mask out SR and GR gates belows such thresholds.
[ ]:
radar_band = "S"
beamwidth_gr = 1
z_min_threshold_gr = 0
z_min_threshold_sr = 10
The SR/GR volume matching routine typically takes around 40-50 seconds to complete. It returns a geopandas.DataFrame with the matched aggregated reflectivities and the associated statistics.
[ ]:
gdf_match = volume_matching(
ds_gr=ds_gr,
z_variable_gr="DBZH",
radar_band=radar_band,
beamwidth_gr=beamwidth_gr,
z_min_threshold_gr=z_min_threshold_gr,
z_min_threshold_sr=z_min_threshold_sr,
min_gr_range=0,
max_gr_range=150_000,
# gr_sensitivity_thresholds=None,
# sr_sensitivity_thresholds=None,
download_sr=True, # require internet connection !
display_quicklook=True,
)
# Note: The circle radius in the quicklook represents the 15 km and max_gr_range distances.
All the available GPM 2A-DPR product files are already on disk.
Integrity checking of GPM files has completed.
All the available GPM 1B-Ku product files are already on disk.
Integrity checking of GPM files has completed.
SR/GR Matching Elapsed time: 0:01:16.207764 .
[ ]:
display(gdf_match)
| GR_Z_mean | GR_Z_std | GR_Z_max | GR_Z_min | GR_Z_range | GR_Z_cov | GR_time | GR_gate_volume_sum | GR_fraction_covered_area | GR_counts | ... | sweep_fixed_angle | along_track | cross_track | VolumeRatio | VolumeDiff | time_difference | GR_z_lower_bound | GR_z_upper_bound | SR_z_lower_bound | SR_z_upper_bound | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 15.100328 | 2.405542 | 19.5 | 8.5 | 11.0 | 0.159304 | 2023-08-20 22:13:43.619000064 | 1.324878e+09 | 0.523921 | 50.0 | ... | 0.483398 | 98 | 42 | 44.679571 | 5.787012e+10 | 97 | 1535.964343 | 4204.783950 | 1654.094116 | 4209.583984 |
| 1 | 13.289946 | 2.324646 | 18.5 | 6.5 | 12.0 | 0.174918 | 2023-08-20 22:13:43.513999872 | 1.314559e+09 | 0.792669 | 74.0 | ... | 0.483398 | 98 | 43 | 45.585145 | 5.860982e+10 | 97 | 1515.361516 | 4199.699750 | 1558.925415 | 4227.430664 |
| 2 | 16.295664 | 3.504062 | 23.0 | 6.0 | 17.0 | 0.215030 | 2023-08-20 22:13:43.414000128 | 1.303967e+09 | 0.966467 | 92.0 | ... | 0.483398 | 98 | 44 | 46.532491 | 5.937285e+10 | 97 | 1495.031559 | 4194.460201 | 1605.041138 | 4143.421875 |
| 3 | 19.697226 | 4.036701 | 26.0 | 5.0 | 21.0 | 0.204938 | 2023-08-20 22:13:43.315000064 | 1.291230e+09 | 1.000000 | 98.0 | ... | 0.483398 | 98 | 45 | 45.354210 | 5.727147e+10 | 97 | 1475.893415 | 4181.632853 | 1647.520386 | 4056.250488 |
| 4 | 19.018411 | 3.410486 | 24.5 | 9.5 | 15.0 | 0.179326 | 2023-08-20 22:13:43.212000000 | 1.280514e+09 | 1.000000 | 97.0 | ... | 0.483398 | 98 | 46 | 44.031053 | 5.510187e+10 | 97 | 1468.543682 | 4156.803848 | 1811.275757 | 4090.862549 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1038 | 17.190318 | 3.090927 | 22.5 | 7.0 | 15.5 | 0.179806 | 2023-08-20 22:13:50.860999936 | 1.312649e+09 | 0.846275 | 74.0 | ... | 0.483398 | 155 | 39 | 12.468087 | 1.505358e+10 | 64 | 1509.901099 | 4198.756713 | 3403.228027 | 4139.753906 |
| 1039 | 16.530711 | 3.806119 | 22.5 | 4.0 | 18.5 | 0.230245 | 2023-08-20 22:13:50.963000064 | 1.281359e+09 | 1.000000 | 94.0 | ... | 0.483398 | 155 | 40 | 6.443431 | 6.974989e+09 | 64 | 1468.120593 | 4157.226937 | 3783.374756 | 4150.641113 |
| 1040 | 5.893002 | 1.561805 | 9.0 | 4.0 | 5.0 | 0.265027 | 2023-08-20 22:13:51.091000064 | 1.262083e+09 | 1.000000 | 95.0 | ... | 0.483398 | 155 | 41 | 6.614890 | 7.086455e+09 | 65 | 1427.293448 | 4095.952596 | 3685.078369 | 4051.276123 |
| 1045 | 3.531114 | 0.499994 | 4.0 | 3.0 | 1.0 | 0.141597 | 2023-08-20 22:13:51.857999872 | 1.058070e+09 | 1.000000 | 103.0 | ... | 0.483398 | 155 | 48 | 28.967377 | 2.959143e+10 | 65 | 1301.113630 | 3756.150436 | 2516.499268 | 3706.372314 |
| 1046 | 5.938911 | 2.127853 | 7.5 | 3.0 | 4.5 | 0.358290 | 2023-08-20 22:13:51.116000000 | 1.325865e+09 | 0.742812 | 68.0 | ... | 0.483398 | 156 | 41 | 10.494452 | 1.258836e+10 | 65 | 1516.193419 | 4205.278647 | 3574.987549 | 4185.318359 |
1016 rows × 230 columns
Analyse SR/GR database#
Now let’s first define the variables names of the SR and GR reflectivities:
[ ]:
sr_z_column = f"SR_zFactorFinal_{radar_band}_mean"
gr_z_column = "GR_Z_mean"
Compare spatial reflectivity fields#
Here we start comparing the spatial reflectivity fields without applying restrictive filtering criteria:
[ ]:
fig = compare_maps(
gdf_match,
sr_column=sr_z_column,
gr_column=gr_z_column,
sr_label="SR Reflectivity (dBz)",
gr_label="GR Reflectivity (dBz)",
cmap="Spectral_r",
unified_color_scale=True,
vmin=15,
# vmax=40
)
fig.tight_layout()
If you wish to create a cartopy map, specify the 'projection' in the subplot_kwargs argument:
[ ]:
ccrs_gr_aeqd = ccrs.AzimuthalEquidistant(
central_longitude=ds_gr["longitude"].item(),
central_latitude=ds_gr["latitude"].item(),
)
subplot_kwargs = {}
subplot_kwargs["projection"] = ccrs_gr_aeqd
fig = compare_maps(
gdf_match,
sr_column=sr_z_column,
gr_column=gr_z_column,
sr_label="SR Reflectivity (dBz)",
gr_label="GR Reflectivity (dBz)",
cmap="Spectral_r",
unified_color_scale=True,
vmin=15,
# vmax=40
subplot_kwargs=subplot_kwargs,
)
fig.tight_layout()
Here we show how to explore interactively the reflectivity fields using Folium:
[ ]:
gdf_match.explore(column="GR_Z_mean", legend=True, cmap="Spectral_r", vmin=0, vmax=40)