Skip to content

Commit d09271c

Browse files
committed
Add cpr-validation plots
1 parent 4f9c803 commit d09271c

2 files changed

Lines changed: 124 additions & 2 deletions

File tree

cloudnetpy/plotting/plot_meta.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@ class PlotMeta(NamedTuple):
238238
"ze_sat": PlotMeta(
239239
plot_range=(-40, 15),
240240
),
241+
"echo_cpr": PlotMeta(
242+
plot_range=(-40, 15),
243+
),
241244
"vm_sat": PlotMeta(
242245
cmap="RdBu_r",
243246
plot_range=(-4, 4),
@@ -246,6 +249,10 @@ class PlotMeta(NamedTuple):
246249
cmap="RdBu_r",
247250
plot_range=(-4, 4),
248251
),
252+
"v_cpr": PlotMeta(
253+
cmap="RdBu_r",
254+
plot_range=(-4, 4),
255+
),
249256
"vm_sat_noise": PlotMeta(
250257
cmap="RdBu_r",
251258
plot_range=(-4, 4),

cloudnetpy/plotting/plotting.py

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from os import PathLike
88
from typing import Any
99

10+
import matplotlib.dates as mdates
1011
import matplotlib.pyplot as plt
1112
import netCDF4
1213
import numpy as np
@@ -181,8 +182,13 @@ def _get_valid_variables_and_indices(
181182
return valid_variables, variable_indices
182183

183184
def _get_time(self) -> ndarray:
185+
variable_names = [f.name for f in self.variables]
184186
if self.file_type == "cpr-simulation":
185187
x_data = self.file.variables["along_track_sat"][:] * con.M_TO_KM
188+
elif self.file_type == "cpr-validation" and (
189+
"echo_cpr" in variable_names or "v_cpr" in variable_names
190+
):
191+
x_data = self.file.variables["time_cpr"][:]
186192
else:
187193
x_data = self.file.variables["time"][:]
188194
return x_data
@@ -244,10 +250,24 @@ def __init__(
244250
self.file_type = file_type
245251
self.plot_meta = self._read_plot_meta()
246252

247-
def set_xax(self) -> None:
253+
def set_xax(self, figure_data: FigureData) -> None:
248254
if self.file_type == "cpr-simulation":
249255
self.ax.set_xlim(0, EARTHCARE_MAX_X) # km
250256
return
257+
if self.file_type == "cpr-validation":
258+
if self.variable.name in ("ze_sat", "echo_cpr", "vm_sat_folded", "v_cpr"):
259+
time = figure_data.time
260+
self.ax.set_xlim(min(time), max(time))
261+
self.ax.set_xlabel("Time (UTC)", fontsize=13)
262+
else:
263+
self.ax.tick_params(axis="both", which="major", labelsize=11)
264+
if self.variable.name in ("echo_cpr", "v_cpr"):
265+
self.ax.xaxis_date()
266+
date_fmt = mdates.DateFormatter("%H:%M:%S")
267+
self.ax.xaxis.set_major_formatter(date_fmt)
268+
self.ax.xaxis.set_major_locator(mdates.AutoDateLocator())
269+
self.ax.tick_params(axis="both", which="major", labelsize=11)
270+
return
251271
resolution = 4
252272
x_tick_labels = [
253273
f"{int(i):02d}:00"
@@ -314,6 +334,8 @@ def add_sources(self, figure_data: FigureData) -> None:
314334
)
315335

316336
def set_xlabel(self) -> None:
337+
if self.file_type == "cpr-validation":
338+
return
317339
label = (
318340
"Distance along track (km)"
319341
if self.file_type == "cpr-simulation"
@@ -493,6 +515,94 @@ def _read_flagged_data(self, figure_data: FigureData) -> ndarray:
493515

494516

495517
class Plot2D(Plot):
518+
def plot_ec_scene(self, figure_data: FigureData) -> None:
519+
variables = figure_data.file.variables
520+
lat = variables["latitude_msi"][:]
521+
lon = variables["longitude_msi"][:]
522+
data = variables["cloud_top_height"][:] / 1000
523+
valid_ind = ~data.mask
524+
525+
# Gridded cloud top height
526+
if np.sum(valid_ind) > 10:
527+
nbins = 200
528+
lon_bins = np.linspace(lon.min(), lon.max(), nbins)
529+
lat_bins = np.linspace(lat.min(), lat.max(), nbins)
530+
lon = lon[valid_ind]
531+
lat = lat[valid_ind]
532+
data = data[valid_ind].data
533+
grid, _, _ = np.histogram2d(
534+
lon, lat, bins=[lon_bins, lat_bins], weights=data
535+
)
536+
counts, _, _ = np.histogram2d(lon, lat, bins=[lon_bins, lat_bins])
537+
with np.errstate(divide="ignore", invalid="ignore"):
538+
grid_mean = np.where(counts > 0, grid / counts, np.nan)
539+
vmin = np.nanpercentile(grid_mean, 2)
540+
vmax = np.nanpercentile(grid_mean, 98)
541+
im = self._ax.pcolorfast(
542+
lon_bins,
543+
lat_bins,
544+
grid_mean.T,
545+
cmap="Blues_r",
546+
vmin=vmin,
547+
vmax=vmax,
548+
)
549+
cbar = self._init_colorbar(im)
550+
cbar.set_label("km", fontsize=13)
551+
552+
# CPR ground track
553+
lat_cpr = variables["latitude_cpr"][:]
554+
lon_cpr = variables["longitude_cpr"][:]
555+
self._ax.plot(
556+
lon_cpr[::4],
557+
lat_cpr[::4],
558+
markeredgecolor="grey",
559+
markerfacecolor="lightgreen",
560+
linewidth=0,
561+
marker=".",
562+
markersize=10,
563+
label="CPR ground track",
564+
)
565+
# Ground station
566+
site_lat = np.mean(variables["latitude"][:])
567+
site_lon = np.mean(variables["longitude"][:])
568+
self._ax.plot(
569+
site_lon,
570+
site_lat,
571+
marker="+",
572+
color="red",
573+
markersize=10,
574+
label="Ground station",
575+
)
576+
577+
# Zoom to region
578+
lat_range = 1
579+
lon_range = lat_range / np.cos(np.deg2rad(site_lat))
580+
self._ax.set_xlim(site_lon - lon_range, site_lon + lon_range)
581+
self._ax.set_ylim(site_lat - lat_range, site_lat + lat_range)
582+
583+
# Scale bar
584+
scale_km = 10
585+
km_per_deg_lon = 111.32 * np.cos(np.deg2rad(site_lat))
586+
deg_lon_10km = scale_km / km_per_deg_lon
587+
x0 = site_lon - lon_range * 0.9
588+
y0 = site_lat - lat_range * 0.9
589+
self._ax.plot(
590+
[x0, x0 + deg_lon_10km], [y0, y0], color="k", lw=2, solid_capstyle="butt"
591+
)
592+
self._ax.text(
593+
x0 + deg_lon_10km / 2,
594+
y0 + lat_range * 0.02,
595+
f"{scale_km} km",
596+
ha="center",
597+
va="bottom",
598+
fontsize=10,
599+
)
600+
601+
legend = self._ax.legend(loc="upper right")
602+
legend.get_frame().set_edgecolor("white")
603+
self._ax.set_xlabel("Longitude°", fontsize=13)
604+
self._ax.set_ylabel("Latitude°", fontsize=13)
605+
496606
def plot(self, figure_data: FigureData) -> None:
497607
self._convert_units()
498608
if self._plot_meta.mask_zeros:
@@ -1006,6 +1116,11 @@ def generate_figure(
10061116

10071117
if variable.name in ("tb", "irt") and ind is not None:
10081118
Plot1D(subplot).plot_tb(figure_data, ind)
1119+
elif (
1120+
figure_data.file_type == "cpr-validation"
1121+
and variable.name == "cloud_top_height"
1122+
):
1123+
Plot2D(subplot).plot_ec_scene(figure_data)
10091124
elif variable.ndim == 1:
10101125
Plot1D(subplot).plot(figure_data)
10111126
elif variable.name in ("number_concentration", "fall_velocity"):
@@ -1015,7 +1130,7 @@ def generate_figure(
10151130
Plot2D(subplot).plot(figure_data)
10161131
subplot.set_yax(y_limits=(0, figure_data.options.max_y))
10171132

1018-
subplot.set_xax()
1133+
subplot.set_xax(figure_data)
10191134

10201135
if options.title:
10211136
subplot.add_title(ind)

0 commit comments

Comments
 (0)