77from os import PathLike
88from typing import Any
99
10+ import matplotlib .dates as mdates
1011import matplotlib .pyplot as plt
1112import netCDF4
1213import 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
495517class 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