def test_1d_inputs(self): ''' make sure ProjInds accepts 1D inputs for lat/lon and data This example is otherwise the same as above ''' import os import numpy as np import cartopy.crs as ccrs import cartopy.feature as cfeature import matplotlib.pyplot as plt import domutils import domutils.legs as legs import domutils.geo_tools as geo_tools #destination grid dest_lon = [[-100., -100, -100], [-90., -90, -90], [-80., -80, -80]] dest_lat = [[30, 40, 50], [30, 40, 50], [30, 40, 50]] #source data src_lon = np.array([[-101., -101], [-88., -88]]).ravel() src_lat = np.array([[30, 40], [30, 40]]).ravel() src_data = np.array([[1., 2], [3, 4]]).ravel() #instantiate object to handle geographical projection of data proj_inds = geo_tools.ProjInds(src_lon=src_lon, src_lat=src_lat, dest_lon=dest_lon, dest_lat=dest_lat, extend_x=False, extend_y=False) #geographical projection of data into axes space projected_data = proj_inds.project_data(src_data) #reference data when this works expected = [[1., 2., 2.], [3., 4., 4.], [3., 4., 4.]] ##test that sum of projected_data equals some pre-validated value self.assertListEqual(projected_data.tolist(), expected)
def test_basic_projection(self): ''' make sure ProjInds projects data correctly for a very simple case with provided source and destination grids ''' import os import numpy as np import cartopy.crs as ccrs import cartopy.feature as cfeature import matplotlib.pyplot as plt import domutils import domutils.legs as legs import domutils.geo_tools as geo_tools #destination grid dest_lon = [[-100., -100, -100], [-90., -90, -90], [-80., -80, -80]] dest_lat = [[30, 40, 50], [30, 40, 50], [30, 40, 50]] #source data src_lon = [[-101., -101], [-88., -88]] src_lat = [[30, 40], [30, 40]] src_data = [[1., 2], [3, 4]] #instantiate object to handle geographical projection of data proj_inds = geo_tools.ProjInds(src_lon=src_lon, src_lat=src_lat, dest_lon=dest_lon, dest_lat=dest_lat, extend_x=False, extend_y=False) #geographical projection of data into axes space projected_data = proj_inds.project_data(src_data) #reference data when this works expected = [[1., 2., 2.], [3., 4., 4.], [3., 4., 4.]] ##test that sum of projected_data equals some pre-validated value self.assertListEqual(projected_data.tolist(), expected)
def test_general_lam_projection(self): import os, inspect import pickle import numpy as np import matplotlib as mpl import matplotlib.pyplot as plt import cartopy.crs as ccrs import cartopy.feature as cfeature import domutils import domutils._py_tools as py_tools # In your scripts use something like : import domutils.legs as legs import domutils.geo_tools as geo_tools #recover previously prepared data domutils_dir = os.path.dirname(domutils.__file__) package_dir = os.path.dirname(domutils_dir) + '/' source_file = package_dir + '/test_data/pal_demo_data.pickle' with open(source_file, 'rb') as f: data_dict = pickle.load(f) longitudes = data_dict['longitudes'] #2D longitudes [deg] latitudes = data_dict['latitudes'] #2D latitudes [deg] ground_mask = data_dict[ 'groundMask'] #2D land fraction [0-1]; 1 = all land terrain_height = data_dict[ 'terrainHeight'] #2D terrain height of model [m ASL] #flag non-terrain (ocean and lakes) as -3333. inds = np.asarray((ground_mask.ravel() <= .01)).nonzero() if inds[0].size != 0: terrain_height.flat[inds] = -3333. #missing value missing = -9999. #pixel density of image to plot ratio = 0.8 hpix = 600. #number of horizontal pixels vpix = ratio * hpix #number of vertical pixels img_res = (int(hpix), int(vpix)) ##define Albers projection and extend of map #Obtained through trial and error for good fit of the mdel grid being plotted proj_aea = ccrs.AlbersEqualArea(central_longitude=-94., central_latitude=35., standard_parallels=(30., 40.)) map_extent = [-104.8, -75.2, 27.8, 48.5] #point density for figure mpl.rcParams['figure.dpi'] = 400 #larger characters mpl.rcParams.update({'font.size': 15}) #instantiate figure fig = plt.figure(figsize=(7.5, 6.)) #instantiate object to handle geographical projection of data proj_inds = geo_tools.ProjInds(src_lon=longitudes, src_lat=latitudes, extent=map_extent, dest_crs=proj_aea, image_res=img_res, missing=missing) #axes for this plot ax = fig.add_axes([.01, .1, .8, .8], projection=proj_aea) ax.set_extent(map_extent) # Set up colormapping object # # Two color segments for this palette red_green = [ [[227, 209, 130], [20, 89, 69]], # bottom color leg : yellow , dark green [[227, 209, 130], [140, 10, 10]] ] # top color leg : yellow , dark red map_terrain = legs.PalObj( range_arr=[0., 750, 1500.], color_arr=red_green, dark_pos=['low', 'high'], excep_val=[-3333., missing], excep_col=[[170, 200, 250], [120, 120, 120]], #blue , grey_120 over_high='extend') #geographical projection of data into axes space proj_data = proj_inds.project_data(terrain_height) #plot data & palette map_terrain.plot_data(ax=ax, data=proj_data, zorder=0, palette='right', pal_units='[meters]', pal_format='{:4.0f}') #palette options #add political boundaries ax.add_feature(cfeature.STATES.with_scale('50m'), linewidth=0.5, edgecolor='0.2', zorder=1) #plot border and mask everything outside model domain proj_inds.plot_border(ax, mask_outside=True, linewidth=.5) #uncomment to save figure output_dir = package_dir + 'test_results/' if not os.path.isdir(output_dir): os.mkdir(output_dir) image_name = output_dir + 'test_general_lam_projection.svg' plt.savefig(image_name) plt.close(fig) print('saved: ' + image_name) #compare image with saved reference #copy reference image to testdir reference_image = package_dir + '/test_data/_static/' + os.path.basename( image_name) images_are_similar = py_tools.render_similarly(image_name, reference_image) #test fails if images are not similar self.assertEqual(images_are_similar, True)
def test_no_extent_in_cartopy_projection(self): ''' make sure ProjInds works with projections requiring no extent ''' import os import numpy as np import cartopy.crs as ccrs import cartopy.feature as cfeature import matplotlib.pyplot as plt import domutils import domutils.legs as legs import domutils.geo_tools as geo_tools import domutils._py_tools as py_tools regular_lons = [[-100., -100, -100], [-90., -90, -90], [-80., -80, -80]] regular_lats = [[30, 40, 50], [30, 40, 50], [30, 40, 50]] data_vals = [[6.5, 3.5, .5], [7.5, 4.5, 1.5], [8.5, 5.5, 2.5]] missing = -9999. image_ratio = .5 rec_w = 6.4 #inches! rec_h = image_ratio * rec_w #inches! grid_w_pts = 2000. image_res = [grid_w_pts, image_ratio * grid_w_pts] #100 x 50 #cartopy projection with no extent proj_rob = ccrs.Robinson() #instantiate object to handle geographical projection of data # onto geoAxes with this specific crs ProjInds = geo_tools.ProjInds(src_lon=regular_lons, src_lat=regular_lats, dest_crs=proj_rob, image_res=image_res, extend_x=False, extend_y=False) #geographical projection of data into axes space projected_data = ProjInds.project_data(data_vals) #plot data to make sure it works #the image that is generated can be looked at to insure proper functionning of the test #it is not currently tested color_map = legs.PalObj(range_arr=[0., 9.], color_arr=[ 'brown', 'blue', 'green', 'orange', 'red', 'pink', 'purple', 'yellow', 'b_w' ], excep_val=missing, excep_col='grey_220') fig_w = 9. fig_h = image_ratio * fig_w fig = plt.figure(figsize=(fig_w, fig_h)) pos = [.1, .1, rec_w / fig_w, rec_h / fig_h] ax = fig.add_axes(pos, projection=proj_rob) x1, x2, y1, y2 = ax.get_extent() ax.outline_patch.set_linewidth(.3) projected_rgb = color_map.to_rgb(projected_data) ax.imshow(projected_rgb, interpolation='nearest', aspect='auto', extent=[x1, x2, y1, y2], origin='upper') ax.coastlines(resolution='110m', linewidth=0.3, edgecolor='0.3', zorder=10) color_map.plot_palette(data_ax=ax) domutils_dir = os.path.dirname(domutils.__file__) package_dir = os.path.dirname(domutils_dir) test_results_dir = package_dir + '/test_results/' if not os.path.isdir(test_results_dir): os.mkdir(test_results_dir) svg_name = test_results_dir + '/test_no_extent_in_cartopy_projection.svg' plt.savefig(svg_name, dpi=500) plt.close(fig) print('saved: ' + svg_name) #compare image with saved reference #copy reference image to testdir reference_image = package_dir + '/test_data/_static/' + os.path.basename( svg_name) images_are_similar = py_tools.render_similarly(svg_name, reference_image) #test fails if images are not similar self.assertEqual(images_are_similar, True)
def main(): #recover previously prepared data currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) parentdir = os.path.dirname(currentdir) #directory where this package lives source_file = parentdir + '/test_data/pal_demo_data.pickle' with open(source_file, 'rb') as f: data_dict = pickle.load(f) longitudes = data_dict['longitudes'] #2D longitudes [deg] latitudes = data_dict['latitudes'] #2D latitudes [deg] ground_mask = data_dict['groundMask'] #2D land fraction [0-1]; 1 = all land terrain_height = data_dict['terrainHeight'] #2D terrain height of model [m ASL] #flag non-terrain (ocean and lakes) as -3333. inds = np.asarray( (ground_mask.ravel() <= .01) ).nonzero() if inds[0].size != 0: terrain_height.flat[inds] = -3333. #missing value missing = -9999. #pixel density of image to plot ratio = 0.8 hpix = 600. #number of horizontal pixels E-W vpix = ratio*hpix #number of vertical pixels S-N img_res = (int(hpix),int(vpix)) ##define Albers projection and extend of map #Obtained through trial and error for good fit of the mdel grid being plotted proj_aea = ccrs.AlbersEqualArea(central_longitude=-94., central_latitude=35., standard_parallels=(30.,40.)) map_extent=[-104.8,-75.2,27.8,48.5] #point density for figure mpl.rcParams['figure.dpi'] = 400 #larger characters mpl.rcParams.update({'font.size': 15}) #instantiate figure fig = plt.figure(figsize=(7.5,6.)) #instantiate object to handle geographical projection of data proj_inds = geo_tools.ProjInds(src_lon=longitudes, src_lat=latitudes, extent=map_extent, dest_crs=proj_aea, image_res=img_res, missing=missing) #axes for this plot ax = fig.add_axes([.01,.1,.8,.8], projection=proj_aea) ax.set_extent(map_extent) # Set up colormapping object # # Two color segments for this palette red_green = [[[227,209,130],[ 20, 89, 69]], # bottom color leg : yellow , dark green [[227,209,130],[140, 10, 10]]] # top color leg : yellow , dark red map_terrain = legs.PalObj(range_arr=[0., 750, 1500.], color_arr=red_green, dark_pos=['low','high'], excep_val=[-3333. ,missing], excep_col=[[170,200,250],[120,120,120]], #blue , grey_120 over_high='extend') #geographical projection of data into axes space proj_data = proj_inds.project_data(terrain_height) #plot data & palette map_terrain.plot_data(ax=ax,data=proj_data, zorder=0, palette='right', pal_units='[meters]', pal_format='{:4.0f}') #palette options #add political boundaries ax.add_feature(cfeature.STATES.with_scale('50m'), linewidth=0.5, edgecolor='0.2',zorder=1) #plot border and mask everything outside model domain proj_inds.plot_border(ax, mask_outside=True, linewidth=.5)
def get_instantaneous(valid_date: Optional[Any] = None, data_path: Optional[str] = None, data_recipe: Optional[str] = None, desired_quantity: Optional[str] = None, median_filt: Optional[int] = None, coef_a: Optional[float] = None, coef_b: Optional[float] = None, qced: Optional[bool] = True, missing: Optional[float] = -9999., latlon: Optional[bool] = False, dest_lon: Optional[Any] = None, dest_lat: Optional[Any] = None, average: Optional[bool] = False, nearest_time: Optional[float] = None, smooth_radius: Optional[float] = None, odim_latlon_file: Optional[str] = None, verbose: Optional[int] = 0): """ Get instantaneous precipitation from various sources Provides one interface for: - support of Odim h5 composites and URP composites in the standard format - output to an arbitrary output grid - Consistent median filter on precip observations and the accompanying quality index - Various types of averaging - Find the nearest time where observations are available The file extension present in *data_recipe* determines the file type of the source data Files having no extension are assumed to be in the 'standard' file format Args: valid_date: datetime object with the validity date of the precip field data_path: path to directory where data is expected data_recipe: datetime code for constructing the file name eg: /%Y/%m/%d/qcomp_%Y%m%d%H%M.h5' the filename will be obtained with data_path + valid_date.strftime(data_recipe) desired_quantity: What quantity is desired in output dictionary *precip_rate* in [mm/h] and *reflectivity* in [dBZ] are supported median_filt: If specified, a median filter will be applied on the data being read and the associated quality index. eg. *medialFilter=3* will apply a median filter over a 3x3 boxcar If unspecified, no filtering is applied coef_a: Coefficient *a* in Z = aR^b coef_b: Coefficient *b* in Z = aR^b qced: Only for Odim H5 composites When True (default), Quality Controlled reflectivity (DBZH) will be returned. When False, raw reflectivity (TH) will be returned. missing: Value that will be assigned to missing data latlon: Return *latitudes* and *longitudes* grid of the data dest_lon: Longitudes of destination grid. If not provided data is returned on its original grid dest_lat: Latitudes of destination grid. If not provided data is returned on its original grid average: Use the averaging method to interpolate data (see geo_tools documentation), this can be slow nearest_time: If set, rewind time until a match is found to an integer number of *nearestTime* For example, with nearestTime=10, time will be rewinded to the nearest integer of 10 minutes smooth_radius: Use the smoothing radius method to interpolate data, faster (see geo_tools documentation) odim_latlon_file: file containing the latitude and longitudes of Baltrad mosaics in Odim H5 format verbose: -- Deprecated -- Set >=1 to print info on execution steps Returns: None If no file matching the desired time is found or { 'reflectivity' 2D reflectivity on destination grid (if requested) 'precip_rate' 2D reflectivity on destination grid (if requested) 'total_quality_index' Quality index of data with 1 = best and 0 = worse 'valid_date' Actual validity date of data 'latitudes' If latlon=True 'longitudes' If latlon=True } """ import os import datetime import copy import logging import numpy as np from . import read_h5_composite from . import read_fst_composite from . import exponential_zr from . import median_filter #import geo_tools from parent module import os,sys,inspect currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) parentdir = os.path.dirname(currentdir) sys.path.insert(0,parentdir) import domutils.geo_tools as geo_tools #logging logger = logging.getLogger(__name__) if verbose > 0: logger.warning('verbose keyword is deprecated, please set logging level in calling handler') #defaut time is now if valid_date is None: valid_date = datetime.datetime() logger.info('get_instantaneous, getting data for: '+str( valid_date)) #default coefficients for Z-R if coef_a is None: coef_a = 300 if coef_b is None: coef_b = 1.4 #default data path and recipe point to operational h5 outputs if data_path is None: data_path = '/space/hall2/sitestore/eccc/cmod/prod/hubs/radar/BALTRAD/Outcoming/Composites' if data_recipe is None: data_recipe = '/%Y/%m/%d/qcomp_%Y%m%d%H%M.h5' #defaut value for desired quantity if desired_quantity is None: desired_quantity = 'reflectivity' elif desired_quantity == 'reflectivity': pass elif desired_quantity == 'precip_rate': pass else: raise ValueError('Wrong value in desired_quantity ') #define settings if interpolation to a destination grid is needed interpolate=False require_precip_rate = False if dest_lon is not None and dest_lat is not None: interpolate=True latlon=True #if any kind of averaging is to be done we need precipitation rates if average or smooth_radius is not None: require_precip_rate = True # # #rewind clock if nearest time is required this_time = copy.deepcopy(valid_date) #rewind time if necessary if nearest_time is not None: try : minutes = this_time.minute except : raise ValueError('Could get minute from datetime object valid_date*') for tt in np.arange(0, np.float(nearest_time)): this_time = valid_date - datetime.timedelta(minutes=tt) minutes = this_time.minute remainder = np.mod(minutes, nearest_time) if np.isclose(remainder, 0.): break # # #make filename from recipe try : this_file_name = this_time.strftime(data_recipe) except : raise ValueError('Could not build filename from datetime object') #complete filename of data file to read data_file = data_path + this_file_name # # #read data based on extension name, ext = os.path.splitext(data_recipe) if ext == '.h5': # # #ODIM H5 format out_dict = read_h5_composite(data_file, qced=qced, latlon=latlon, latlon_file=odim_latlon_file) elif (ext == '.std' or ext == '.stnd' or ext == '.fst' or ext == '') : # # #CMC *standard* format out_dict = read_fst_composite(data_file, latlon=latlon) else: raise ValueError('Filetype: '+ext+' not yet supported') #return None if nothing was returned by reader if out_dict is None: return None # # #perform conversion if necessary if desired_quantity == 'reflectivity' : if 'reflectivity' not in out_dict: #conversion from R to dBZ try: out_dict['reflectivity'] = exponential_zr(out_dict['precip_rate'], coef_a=coef_a, coef_b=coef_b, r_to_dbz=True) except: raise ValueError('Could not convert precip rate to reflectivity') if desired_quantity == 'precip_rate' or require_precip_rate : if 'precip_rate' not in out_dict: #conversion from R to dBZ try: out_dict['precip_rate'] = exponential_zr(out_dict['reflectivity'], coef_a=coef_a, coef_b=coef_b, dbz_to_r=True) except: raise ValueError('Could not convert precip rate to reflectivity') # # #remove speckle with median filter if desired if median_filt is not None: #speckle filter will be applied from precip_rate or reflectivity #the same pixel selection is applied to quality indexes logger.info('get_instantaneous, applying median filter') if 'reflectivity' in out_dict: median_inds = median_filter.get_inds(out_dict['reflectivity'], window=median_filt) out_dict['reflectivity'] = median_filter.apply_inds(out_dict['reflectivity'], median_inds) if 'precip_rate' in out_dict: out_dict['precip_rate'] = median_filter.apply_inds(out_dict['precip_rate'], median_inds) elif 'precip_rate' in out_dict: median_inds = median_filter.get_inds(out_dict['precip_rate'], window=median_filt) filtered_pr = median_filter.apply_inds(out_dict['precip_rate'], median_inds) out_dict['precip_rate'] = filtered_pr else: raise ValueError('One of reflectivity or precip_rate must be present for filtering') #quality index should always be in dict out_dict['total_quality_index'] = median_filter.apply_inds(out_dict['total_quality_index'], median_inds) # # #perform interpolation if necessary if interpolate: logger.info('get_instantaneous, interpolating to destination grid') #projection from one grid to another proj_obj = geo_tools.ProjInds(src_lon=out_dict['longitudes'], src_lat=out_dict['latitudes'], dest_lon=dest_lon, dest_lat=dest_lat, average=average, smooth_radius=smooth_radius, missing=missing) interpolated_pr = None interpolated_ref = None if average or smooth_radius is not None: #interpolation involving some averaging interpolated_pr, interpolated_qi = proj_obj.project_data(out_dict['precip_rate'], weights=out_dict['total_quality_index'], output_avg_weights=True) #if reflectivity is present get it from interpolated precip Rate if 'reflectivity' in out_dict: interpolated_ref = exponential_zr(interpolated_pr, coef_a=coef_a, coef_b=coef_b, r_to_dbz=True) else : #interpolation by nearest neighbor interpolated_qi = proj_obj.project_data(out_dict['total_quality_index']) if 'precip_rate' in out_dict: interpolated_pr = proj_obj.project_data(out_dict['precip_rate']) if 'reflectivity' in out_dict: interpolated_ref = proj_obj.project_data(out_dict['reflectivity']) #update outdict out_dict['total_quality_index'] = interpolated_qi out_dict['latitudes'] = dest_lat out_dict['longitudes'] = dest_lon if interpolated_pr is not None: out_dict['precip_rate'] = interpolated_pr if interpolated_ref is not None: out_dict['reflectivity'] = interpolated_ref logger.info('get_instantaneous, interpolation done') return out_dict def main(): pass if __name__ == "__main__": main()
def plot_rdpr_rdqi(fst_file: str = None, this_date: Any = None, fig_dir: Optional[str] = None, fig_format: Optional[str] = 'gif', args=None): """ plot RDPR and RDQI from a rpn "standard" file Data needs to be at correct valid time If the file does not exist, the image will display "Non existing file" Args: fst_file: full path of standard file to read this_date: validity time of data to plot fig_dir: directory where figures will be written Returns: Nothing, only plot a figure """ import os from os import linesep as newline import datetime import logging import numpy as np import matplotlib as mpl import matplotlib.pyplot as plt from packaging import version import cartopy import cartopy.crs as ccrs import cartopy.feature as cfeature import domutils.legs as legs import domcmc.fst_tools as fst_tools import domutils.geo_tools as geo_tools import domutils._py_tools as dpy #logging logger = logging.getLogger(__name__) #use provided data path for cartiopy shapefiles #TODO there has got to be a better way to do this! if args is not None: if hasattr(args, 'cartopy_dir'): if args.cartopy_dir is not None: cartopy.config['pre_existing_data_dir'] = args.cartopy_dir #Read data logger.info('Reading RDPR and RDQI from: ' + fst_file) pr_dict = fst_tools.get_data(file_name=fst_file, var_name='RDPR', datev=this_date, latlon=True) qi_dict = fst_tools.get_data(file_name=fst_file, var_name='RDQI', datev=this_date) #value assigned to missing data missing = -9999. #make figure directory if it does not exist dpy.parallel_mkdir(fig_dir) #setup figure properties ratio = 0.5 fig_name_recipe = '%Y%m%d_%H%M.svg' # all sizes are inches for consistency with matplotlib rec_w = 6. # Horizontal size of a panel /2.54 for dimensions in cm rec_h = ratio * rec_w # Vertical size of a panel sp_w = .1 # horizontal space between panels sp_h = .5 # vertical space between panels pal_sp = .1 # spavce between panel and palette pal_w = .25 # width of palette tit_h = 1. # height of title xp = .04 # axes relative x position of image caption yp = 1.05 # axes relative y position of image caption dpi = 500 # density of pixel for figure #size of figure fig_w = 3. + 2 * (sp_w + rec_w + pal_w + pal_sp) fig_h = 2. * sp_h + sp_h + rec_h + tit_h #normalize all dimensions rec_w /= fig_w rec_h /= fig_h sp_w /= fig_w sp_h /= fig_h pal_sp /= fig_w pal_w /= fig_w tit_h /= fig_h # matplotlib global settings mpl.rcParams.update({'font.size': 24}) # Use this for editable text in svg mpl.rcParams['text.usetex'] = False mpl.rcParams['svg.fonttype'] = 'none' # Hi def figure mpl.rcParams['figure.dpi'] = dpi # instantiate figure fig = plt.figure(figsize=(fig_w, fig_h)) ax = fig.add_axes([0, 0, 1, 1], zorder=0) ax.axis('off') ax.annotate(this_date.strftime('%Y-%m-%d %H:%M'), size=35, xy=(0.015, 0.92), xycoords='figure fraction', bbox=dict(boxstyle="round", fc=[1, 1, 1, .9], ec=[1, 1, 1, 0])) if pr_dict is None: #data not found or not available at desired date #print warning and make empty image message = ('RDPR not available in file: ' + newline + fst_file + newline + 'at date' + newline + str(this_date)) logger.warning(message) ax = fig.add_axes([0, 0, 1, 1]) ax.axis('off') ax.annotate(message, size=18, xy=(.1, .5), xycoords='axes fraction') else: #PR found in file, proceed to plot it #setup color mapping objects: # #Precip rate map_pr = legs.PalObj(range_arr=[.1, 3., 6., 12., 25., 50., 100.], n_col=6, over_high='extend', under_low='white', excep_val=missing, excep_col='grey_200') #custom pastel color segments for QI index pastel = [ [[255, 190, 187], [230, 104, 96]], #pale/dark red [[255, 185, 255], [147, 78, 172]], #pale/dark purple [[255, 227, 215], [205, 144, 73]], #pale/dark brown [[210, 235, 255], [58, 134, 237]], #pale/dark blue [[223, 255, 232], [61, 189, 63]] ] #pale/dark green map_qi = legs.PalObj(range_arr=[0., 1.], dark_pos='high', color_arr=pastel, excep_val=[missing, 0.], excep_col=['grey_220', 'white']) #Setup geographical projection # Full HRDPS grid pole_latitude = 35.7 pole_longitude = 65.5 lat_0 = 48.8 delta_lat = 10. lon_0 = 266.00 delta_lon = 40. map_extent = [ lon_0 - delta_lon, lon_0 + delta_lon, lat_0 - delta_lat, lat_0 + delta_lat ] proj_aea = ccrs.RotatedPole(pole_latitude=pole_latitude, pole_longitude=pole_longitude) logger.info('Making projection object') proj_obj = geo_tools.ProjInds(src_lon=pr_dict['lon'], src_lat=pr_dict['lat'], extent=map_extent, dest_crs=proj_aea, image_res=(800, 400)) #plot precip rate # #position of this fig x0 = sp_w + 0. * (rec_w + sp_w) y0 = sp_h + 0. * (rec_h + sp_h) pos = [x0, y0, rec_w, rec_h] #setup axes ax = fig.add_axes(pos, projection=proj_aea) ax.set_extent(map_extent) #thinner lines if version.parse(cartopy.__version__) >= version.parse("0.18.0"): ax.spines['geo'].set_linewidth(0.3) else: ax.outline_patch.set_linewidth(0.3) #plot image caption ax.annotate('RDPR', size=30, xy=(xp, yp), xycoords='axes fraction') #geographical projection of data projected_pr = proj_obj.project_data(pr_dict['values']) #apply color mapping n plot data map_pr.plot_data(ax, projected_pr, palette='right', pal_linewidth=0.3, pal_units='[mm/h]', pal_format='{:5.1f}', equal_legs=True) #force axes to respect ratio we previously indicated... ax.set_aspect('auto') #plot geographical contours ax.add_feature(cfeature.STATES.with_scale('50m'), linewidth=0.3, edgecolor='0.3', zorder=1) #plot border proj_obj.plot_border(ax, mask_outside=True, linewidth=.3) #plot quality index # #position of this fig x0 = sp_w + 1. * (rec_w + sp_w + pal_sp + pal_w + 1.5 / fig_w) y0 = sp_h + 0. * (rec_h + sp_h) pos = [x0, y0, rec_w, rec_h] #setup axes ax = fig.add_axes(pos, projection=proj_aea) ax.set_extent(map_extent) #thinner lines if version.parse(cartopy.__version__) >= version.parse("0.18.0"): ax.spines['geo'].set_linewidth(0.3) else: ax.outline_patch.set_linewidth(0.3) #plot image caption ax.annotate('RDQI', size=30, xy=(xp, yp), xycoords='axes fraction') if qi_dict is None: #QI not available indicate it on figure message = 'RDQI not available in file: ' + newline + fst_file logger.warning.warn(message) ax.annotate(message, size=10, xy=(.0, .5), xycoords='axes fraction') else: #QI is available, plot it # #geographical projection of data projected_qi = proj_obj.project_data(qi_dict['values']) #apply color mapping n plot data map_qi.plot_data(ax, projected_qi, palette='right', pal_linewidth=0.3, pal_units='[unitless]', pal_format='{:2.1f}') #force axes to respect ratio we previously indicated... ax.set_aspect('auto') #plot geographical contours ax.add_feature(cfeature.STATES.with_scale('50m'), linewidth=0.3, edgecolor='0.3', zorder=1) #plot border proj_obj.plot_border(ax, mask_outside=True, linewidth=.3) #save figure svg_name = fig_dir + this_date.strftime(fig_name_recipe) logger.info('Saving figure:' + svg_name + ', changing typeface and converting format if needed.') plt.savefig(svg_name) plt.close(fig) dpy.lmroman(svg_name) if fig_format != 'svg': dpy.convert(svg_name, fig_format, del_orig=True, density=500, geometry='50%') #not sure what is accumulating but adding this garbage collection step #prevents jobs from aborting when a large number of files are made #gc.collect() #not sure if needed anymore... keeping commented command here just in case. logger.info('plot_rdpr_rdqi Done')
def make_panel(fig, pos, img_res, map_extent, missing, dpr_lats, dpr_lons, dpr_pr, goes_lats, goes_lons, goes_albedo, map_pr, map_goes, map_extent_small=None, include_zero=True, average_dpr=False): ''' Generic function for plotting data on an ax Data is displayed with specific projection settings ''' #cartopy crs for lat/lon (ll) and the image (Miller) proj_ll = ccrs.Geodetic() proj_mil = ccrs.Miller() #global #instantiate object to handle geographical projection of data # #Note the average=True for GPM data, high resolution DPR data # will be averaged within coarser images pixel tiles proj_inds_dpr = geo_tools.ProjInds(src_lon=dpr_lons, src_lat=dpr_lats, extent=map_extent, dest_crs=proj_mil, average=average_dpr, missing=missing, image_res=img_res) proj_inds_goes = geo_tools.ProjInds(src_lon=goes_lons, src_lat=goes_lats, extent=map_extent, dest_crs=proj_mil, image_res=img_res, missing=missing) ax = fig.add_axes(pos, projection=proj_mil) ax.set_extent(map_extent) #geographical projection of data into axes space proj_data_pr = proj_inds_dpr.project_data(dpr_pr) proj_data_goes = proj_inds_goes.project_data(goes_albedo) #get RGB values for each data types precip_rgb = map_pr.to_rgb(proj_data_pr) albedo_rgb = map_goes.to_rgb(proj_data_goes) #blend the two images by hand #image will be opaque where reflectivity > 0 if include_zero: alpha = np.where(proj_data_pr >= 0., 1., 0.) #include zero else: alpha = np.where(proj_data_pr > 0., 1., 0.) #exclude zero combined_rgb = np.zeros(albedo_rgb.shape, dtype='uint8') for zz in np.arange(3): combined_rgb[:, :, zz] = ( 1. - alpha) * albedo_rgb[:, :, zz] + alpha * precip_rgb[:, :, zz] #plot image w/ imshow x11, x22 = ax.get_xlim() #get image limits in Cartopy data coordinates y11, y22 = ax.get_ylim() dum = ax.imshow(combined_rgb, interpolation='nearest', origin='upper', extent=[x11, x22, y11, y22]) ax.set_aspect('auto') #add political boundaries ax.add_feature(cfeature.COASTLINE, linewidth=0.8, edgecolor='yellow', zorder=1) #plot extent of the small domain that will be displayed in next panel if map_extent_small is not None: bright_red = np.array([255., 0., 0.]) / 255. w = map_extent_small[0] e = map_extent_small[1] s = map_extent_small[2] n = map_extent_small[3] ax.plot([w, e, e, w, w], [s, s, n, n, s], transform=proj_ll, color=bright_red, linewidth=5)
def get_accumulation(end_date: Optional[Any] = None, duration: Optional[Any] = None, desired_quantity: Optional[str] = None, data_path: Optional[str] = None, data_recipe: Optional[str] = None, median_filt: Optional[int] = None, coef_a: Optional[float] = None, coef_b: Optional[float] = None, qced: Optional[bool] = True, missing: Optional[float] = -9999., latlon: Optional[bool] = False, dest_lon: Optional[Any] = None, dest_lat: Optional[Any] = None, average: Optional[bool] = False, nearest: Optional[float] = None, smooth_radius: Optional[float] = None, odim_latlon_file: Optional[str] = None, verbose: Optional[int] = 0): """Get accumulated precipitation from instantaneous observations This is essentially a wrapper around get_instantaneous. Data is read during the accumulation period and accumulated (in linear units of precipitation rates) taking the quality index into account. If interpolation to a different grid is desired, it is performed after the accumulation procedure. If the desired quantity *reflectivity* or *precip_rate* is desired, then the returned quantity will reflect the average precipitation rate during the accumulation period. With an endTime set to 16:00h and duration to 60 (minutes), data from: - 15:10h, 15:20h, 15:30h, 15:40h, 15:50h and 16:00h will be accumulated Args: end_date: datetime object representing the time (inclusively) at the end of the accumulation period duration: amount of time (minutes) during which precipitation should be accumulated data_path: path to directory where data is expected data_recipe: datetime code for constructing the file name eg: /%Y/%m/%d/qcomp_%Y%m%d%H%M.h5' the filename will be obtained with data_path + valid_date.strftime(data_recipe) desired_quantity: What quantity is desired in output dictionary. Supported values are: - *accumulation* Default option, the amount of water (in mm) - *avg_precip_rate* For average precipitation rate (in mm/h) during the accumulation period - *reflectivity* For the reflectivity (in dBZ) associated with the average precipitation rate median_filt: If specified, a median filter will be applied on the data being read and the associated quality index. eg. *medialFilter=3* will apply a median filter over a 3x3 boxcar If unspecified, no filtering is applied coef_a: Coefficient *a* in Z = aR^b coef_b: Coefficient *b* in Z = aR^b qced: Only for Odim H5 composites When True (default), Quality Controlled reflectivity (DBZH) will be returned. When False, raw reflectivity (TH) will be returned. missing: Value that will be assigned to missing data latlon: Return *latitudes* and *longitudes* grid of the data dest_lon: Longitudes of destination grid. If not provided data is returned on its original grid dest_lat: Latitudes of destination grid. If not provided data is returned on its original grid average: Use the averaging method to interpolate data (see geo_tools documentation), this can be slow nearest: If set, rewind time until a match is found to an integer number of *nearest* For example, with nearest=10, time will be rewinded to the nearest integer of 10 minutes smooth_radius: Use the smoothing radius method to interpolate data, faster (see geo_tools documentation) odim_latlon_file: file containing the latitude and longitudes of Baltrad mosaics in Odim H5 format verbose: -- Deprecated -- Set >=1 to print info on execution steps Returns: None If no file matching the desired time is found or { 'end_date' End time for accumulation period 'duration' Accumulation length (minutes) 'reflectivity' 2D reflectivity on destination grid (if requested) 'precip_rate' 2D reflectivity on destination grid (if requested) 'total_quality_index' Quality index of data with 1 = best and 0 = worse 'latitudes' If latlon=True 'longitudes' If latlon=True } """ import os import datetime import logging import numpy as np from . import get_instantaneous from . import exponential_zr #import geo_tools from parent module import os, sys, inspect currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) parentdir = os.path.dirname(currentdir) sys.path.insert(0,parentdir) import domutils.geo_tools as geo_tools #logging logger = logging.getLogger(__name__) if verbose > 0: logger.warning('verbose keyword is deprecated, please set logging level in calling handler') #define settings if interpolation to a destination grid is needed #for better performance, interpolation will be performed only once #data has been accumulated on its original grid interpolate=False if dest_lon is not None and dest_lat is not None: interpolate=True latlon=True #defaut time is now if end_date is None: end_date = datetime.datetime() #defaut duration is 1h if duration is None: duration = 60. #default coefficients for Z-R if coef_a is None: coef_a = 300 if coef_b is None: coef_b = 1.4 #defaut value for desired quantity if desired_quantity is None: desired_quantity = 'accumulation' elif desired_quantity == 'accumulation': pass elif desired_quantity == 'avg_precip_rate': pass elif desired_quantity == 'reflectivity': pass else: raise ValueError('Wrong value in desired_quantity ') #start filling out output dictionary out_dict = {'end_date': end_date, 'duration': duration } #time interval between radar observations name, ext = os.path.splitext(data_recipe) if ext == '.h5': radar_dt = 10. else: raise ValueError('Filetype: '+ext+' not yet supported') # # #get list of times during which observations should be accumulated m_list = np.arange(0, duration, radar_dt) date_list = [end_date - datetime.timedelta(minutes=this_min) for this_min in m_list] # # #read data logger.info('Reading in data') #for 1st time kk = 0 this_date = date_list[kk] dat_dict = get_instantaneous(desired_quantity='precip_rate', valid_date=this_date, data_path=data_path, odim_latlon_file=odim_latlon_file, data_recipe=data_recipe, median_filt=median_filt, latlon=latlon) data_shape = dat_dict['precip_rate'].shape if latlon is not None: orig_lat = dat_dict['latitudes'] orig_lon = dat_dict['longitudes'] #init accumulation arrays accum_dat = np.full( (data_shape[0], data_shape[1], len(date_list)), missing) accum_qi = np.zeros((data_shape[0], data_shape[1], len(date_list))) #save data accum_dat[:,:,kk] = dat_dict['precip_rate'] accum_qi[:,:,kk] = dat_dict['total_quality_index'] # #read rest of data for kk, this_date in enumerate(date_list): #we already did the 1st one, skip it if kk == 0 : continue #fill accumulation arrays with data for this time dat_dict = get_instantaneous(desired_quantity='precip_rate', valid_date=this_date, data_path=data_path, odim_latlon_file=odim_latlon_file, data_recipe=data_recipe, median_filt=median_filt) if dat_dict is not None: accum_dat[:,:,kk] = dat_dict['precip_rate'] accum_qi[:,:,kk] = dat_dict['total_quality_index'] # # #average of precip_rate is weighted by quality index logger.info('Computing average precip rate in accumulation period') sum_w = np.sum(accum_qi, axis=2) good_pts = np.asarray(sum_w > 0.).nonzero() # #average precip_rate weighted_sum = np.sum(accum_dat*accum_qi, axis=2) avg_pr = np.full_like(sum_w, missing) if good_pts[0].size > 0: avg_pr[good_pts] = weighted_sum[good_pts]/sum_w[good_pts] # #compute average quality index weighted_sum = np.sum(accum_qi*accum_qi, axis=2) avg_qi = np.full_like(sum_w, missing) if good_pts[0].size > 0: avg_qi[good_pts] = weighted_sum[good_pts]/sum_w[good_pts] # # #perform interpolation if necessary if interpolate: logger.info('get_accumulation, interpolating to destination grid') #projection from one grid to another proj_obj = geo_tools.ProjInds(src_lon=orig_lon, src_lat=orig_lat, dest_lon=dest_lon, dest_lat=dest_lat, average=average, smooth_radius=smooth_radius, missing=missing) if average or smooth_radius is not None: #interpolation involving some averaging interpolated_pr = proj_obj.project_data(avg_pr, weights=avg_qi) interpolated_qi = proj_obj.project_data(avg_qi, weights=avg_qi) else : #interpolation by nearest neighbor interpolated_pr = proj_obj.project_data(avg_pr) interpolated_qi = proj_obj.project_data(avg_qi) #fill out_dict out_dict['avg_precip_rate'] = interpolated_pr out_dict['total_quality_index'] = interpolated_qi out_dict['latitudes'] = dest_lat out_dict['longitudes'] = dest_lon else: #no Interpolation out_dict['avg_precip_rate'] = avg_pr out_dict['total_quality_index'] = avg_qi if latlon: out_dict['latitudes'] = orig_lat out_dict['longitudes'] = orig_lon # # #convert precip rates to other quantities if desired if desired_quantity == 'accumulation': logger.info('Computing accumulation from avg precip rate') # rate (mm/h) * duration time (h) = accumulation (mm) #number of hours of accumulation period duration_hours = duration/60. out_dict['accumulation'] = out_dict['avg_precip_rate'] * duration_hours # if desired_quantity == 'reflectivity': logger.info('get_accumulation computing reflectivity from avg precip rate') out_dict['reflectivity'] = exponential_zr(out_dict['avg_precip_rate'], coef_a=coef_a, coef_b=coef_b, r_to_dbz=True) logger.info('get_accumulation Done') return out_dict
def main(): #recover previously prepared data currentdir = os.path.dirname( os.path.abspath(inspect.getfile(inspect.currentframe()))) parentdir = os.path.dirname( currentdir) #directory where this package lives source_file = parentdir + '/test_data/pal_demo_data.pickle' with open(source_file, 'rb') as f: data_dict = pickle.load(f) longitudes = data_dict['longitudes'] #2D longitudes [deg] latitudes = data_dict['latitudes'] #2D latitudes [deg] quality_index = data_dict[ 'qualityIndex'] #2D quality index of a radar mosaic [0-1]; 1 = best quality #missing value missing = -9999. #pixel density of image to plot ratio = 0.8 hpix = 600. #number of horizontal pixels E-W vpix = ratio * hpix #number of vertical pixels S-N img_res = (int(hpix), int(vpix)) ##define Albers projection and extend of map #Obtained through trial and error for good fit of the mdel grid being plotted proj_aea = ccrs.AlbersEqualArea(central_longitude=-94., central_latitude=35., standard_parallels=(30., 40.)) map_extent = [-104.8, -75.2, 27.8, 48.5] #point density for figure mpl.rcParams['figure.dpi'] = 400 #larger characters mpl.rcParams.update({'font.size': 15}) #instantiate figure fig = plt.figure(figsize=(7.5, 6.)) #instantiate object to handle geographical projection of data proj_inds = geo_tools.ProjInds(src_lon=longitudes, src_lat=latitudes, extent=map_extent, dest_crs=proj_aea, image_res=img_res, missing=missing) #axes for this plot ax = fig.add_axes([.01, .1, .8, .8], projection=proj_aea) ax.set_extent(map_extent) # Set up colormapping object # #custom pastel color segments pastel = [ [[255, 190, 187], [230, 104, 96]], #pale/dark red [[255, 185, 255], [147, 78, 172]], #pale/dark purple [[255, 227, 215], [205, 144, 73]], #pale/dark brown [[210, 235, 255], [58, 134, 237]], #pale/dark blue [[223, 255, 232], [61, 189, 63]] ] #pale/dark green #init color mapping object map_qi = legs.PalObj(range_arr=[0., 1.], dark_pos='high', color_arr=pastel, excep_val=[missing], excep_col='grey_120') #geographical projection of data into axes space proj_data = proj_inds.project_data(quality_index) #plot data & palette map_qi.plot_data(ax=ax, data=proj_data, zorder=0, palette='right', pal_units='[unitless]', pal_format='{:2.1f}') #palette options #add political boundaries ax.add_feature(cfeature.STATES.with_scale('50m'), linewidth=0.5, edgecolor='0.2', zorder=1) #plot border and mask everything outside model domain proj_inds.plot_border(ax, mask_outside=True, linewidth=.5)
def main(): import os, inspect import copy import numpy as np import matplotlib as mpl import matplotlib.pyplot as plt import cartopy.crs as ccrs # imports from domutils import domutils.legs as legs import domutils.geo_tools as geo_tools import domutils.radar_tools as radar_tools #missing values ane the associated color missing = -9999. missing_color = 'grey_160' undetect = -3333. undetect_color = 'grey_180' #file to read currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) parentdir = os.path.dirname(currentdir) #directory where this package lives odim_file = parentdir + '/test_data/odimh5_radar_volume_scans/2019071120_24_ODIMH5_PVOL6S_VOL.qc.casbv.h5' #Read a PPI from a volume scan in the ODIM H5 format #we want the PPI with a nominal elevation of 0.4 degree #we retrieve reflectivity (dbzh) and Doppler velocity (vradh) and the Depolarization ratio (dr) #the reader computes the lat/lon of data points in the PPI for you out_dict = radar_tools.read_h5_vol(odim_file=odim_file, elevations=[0.4], quantities=['dbzh','vradh','dr'], no_data=missing, undetect=undetect, include_quality=True, latlon=True) #radar properties radar_lat = out_dict['radar_lat'] radar_lon = out_dict['radar_lon'] #PPIs # You can see the quantities available in the dict with # print(out_dict['0.4'].keys()) reflectivity = out_dict['0.4']['dbzh'] doppvelocity = out_dict['0.4']['vradh'] dr = out_dict['0.4']['dr'] tot_qi = out_dict['0.4']['quality_qi_total'] latitudes = out_dict['0.4']['latitudes'] longitudes = out_dict['0.4']['longitudes'] # #Quality controls on Doppler velocity #pixel is considered weather is DR < 12 dB weather_target = np.where( dr < -12., 1, 0) #not a weather target when DR is missing weather_target = np.where( np.isclose(dr, missing), 0, weather_target) #not a weather target when DR is undetect weather_target = np.where( np.isclose(dr, undetect), 0, weather_target) #a 3D representation of a boxcar filter see radar_tools API for docs on this function #5x5 search area in polar coordinates remapped_data_5 = radar_tools.median_filter.remap_data(weather_target, window=5, mode='wrap') #if less than 12 points (half the points in a 5x5 window) have good dr, pixel is marked as clutter is_clutter = np.where(np.sum(remapped_data_5, axis=2) <= 12, True, False) #3x3 search area in polar coordinates remapped_data_3 = radar_tools.median_filter.remap_data(weather_target, window=3, mode='wrap') #if less than 9 points (all the points in a 3x3 window) have good dr, pixel is marked as clutter is_clutter = np.where(np.sum(remapped_data_3, axis=2) < 9, True, is_clutter) # doppvelocity_qc = copy.deepcopy(doppvelocity) #DR filtering only applied to points with reflectivities < 35 dB doppvelocity_qc = np.where( np.logical_and(is_clutter , reflectivity < 35.) , undetect, doppvelocity_qc) #add filtering using total quality index doppvelocity_qc = np.where( tot_qi < 0.2 , undetect, doppvelocity_qc) #pixel density of panels in figure ratio = 1. hpix = 800. #number of horizontal pixels E-W vpix = ratio*hpix #number of vertical pixels S-N img_res = (int(hpix),int(vpix)) #cartopy Rotated Pole projection pole_latitude=90. pole_longitude=0. proj_rp = ccrs.RotatedPole(pole_latitude=pole_latitude, pole_longitude=pole_longitude) #plate carree proj_pc = ccrs.PlateCarree() #a domain ~250km around the Montreal area where the radar is lat_0 = 45.7063 delta_lat = 2.18 lon_0 = -73.85 delta_lon = 3.12 map_extent=[lon_0-delta_lon, lon_0+delta_lon, lat_0-delta_lat, lat_0+delta_lat] #a smaller domain for a closeup that will be inlaid in figure lat_0 = 46.4329666 delta_lat = 0.072666 lon_0 = -75.00555 delta_lon = 0.104 map_extent_close=[lon_0-delta_lon, lon_0+delta_lon, lat_0-delta_lat, lat_0+delta_lat] #a circular clipping mask for the closeup axes x = np.linspace(-1., 1, int(hpix)) y = np.linspace(-1., 1, int(vpix)) xx, yy = np.meshgrid(x, y, indexing='ij') clip_alpha = np.where( np.sqrt(xx**2.+yy**2.) < 1., 1., 0. ) #a circular line around the center of the closeup window radius=8. #km azimuths = np.arange(0,361.)#360 degree will be included for full circles closeup_lons, closeup_lats = geo_tools.lat_lon_range_az(lon_0, lat_0, radius, azimuths) #a line 5km long for showing scale in closeup insert azimuth = 90 #meteorological angle distance = np.linspace(0,5.,50)#360 degree will be included for full circles scale_lons, scale_lats = geo_tools.lat_lon_range_az(lon_0-0.07, lat_0-0.04, distance, azimuth) #point density for figure mpl.rcParams['figure.dpi'] = 100. #crank this up for high def images # Use this for editable text in svg (eg with Inkscape) mpl.rcParams['text.usetex'] = False mpl.rcParams['svg.fonttype'] = 'none' #larger characters mpl.rcParams.update({'font.size': 25}) # dimensions for figure panels and spaces # all sizes are inches for consistency with matplotlib fig_w = 13.5 # Width of figure fig_h = 12.5 # Height of figure rec_w = 5. # Horizontal size of a panel rec_h = ratio * rec_w # Vertical size of a panel sp_w = .5 # horizontal space between panels sp_h = .8 # vertical space between panels fig = plt.figure(figsize=(fig_w,fig_h)) xp = .02 #coords of title (axes normalized coordinates) yp = 1.02 #coords for the closeup that is overlayed x0 = .55 #x-coord of bottom left position of closeup (axes coords) y0 = .55 #y-coord of bottom left position of closeup (axes coords) dx = .4 #x-size of closeup axes (fraction of a "regular" panel) dy = .4 #y-size of closeup axes (fraction of a "regular" panel) #normalize sizes to obtain figure coordinates (0-1 both horizontally and vertically) rec_w = rec_w / fig_w rec_h = rec_h / fig_h sp_w = sp_w / fig_w sp_h = sp_h / fig_h #instantiate objects to handle geographical projection of data proj_inds = geo_tools.ProjInds(src_lon=longitudes, src_lat=latitudes, extent=map_extent, dest_crs=proj_rp, extend_x=False, extend_y=True, image_res=img_res, missing=missing) #for closeup image proj_inds_close = geo_tools.ProjInds(src_lon=longitudes, src_lat=latitudes, extent=map_extent_close, dest_crs=proj_rp, extend_x=False, extend_y=True, image_res=img_res, missing=missing) # #Reflectivity # #axes for this plot pos = [sp_w, sp_h+(sp_h+rec_h), rec_w, rec_h] ax = fig.add_axes(pos, projection=proj_rp) ax.spines['geo'].set_linewidth(0.3) ax.set_extent(map_extent) ax.set_aspect('auto') ax.annotate('Qced Reflectivity', size=30, xy=(xp, yp), xycoords='axes fraction') # colormapping object for reflectivity map_reflectivity = legs.PalObj(range_arr=[0.,60], n_col=6, over_high='extend', under_low='white', excep_val=[missing, undetect], excep_col=[missing_color, undetect_color]) #geographical projection of data into axes space proj_data = proj_inds.project_data(reflectivity) #plot data & palette map_reflectivity.plot_data(ax=ax, data=proj_data, zorder=0, palette='right', pal_units='[dBZ]', pal_format='{:3.0f}') #add political boundaries add_feature(ax) #radar circles and azimuths radar_ax_circ(ax, radar_lat, radar_lon) #circle indicating closeup area ax.plot(closeup_lons, closeup_lats, transform=proj_pc, c=(0.,0.,0.), zorder=300, linewidth=.8) #arrow pointing to closeup ax.annotate("", xy=(0.33, 0.67), xytext=(.55, .74), xycoords='axes fraction', arrowprops=dict(arrowstyle="<-")) # #Closeup of reflectivity # pos = [sp_w+x0*rec_w, sp_h+(sp_h+rec_h)+y0*rec_h, dx*rec_w, dy*rec_h] ax2 = fig.add_axes(pos, projection=proj_rp, label='reflectivity overlay') ax2.set_extent(map_extent_close) ax2.spines['geo'].set_linewidth(0.0) #no border line ax2.set_facecolor((1.,1.,1.,0.)) #transparent background #geographical projection of data into axes space proj_data = proj_inds_close.project_data(reflectivity) #RGB values for data to plot closeup_rgb = map_reflectivity.to_rgb(proj_data) #get corners of image in data space extent_data_space = ax2.get_extent() ## another way of doing the same thing is to get an object that convers axes coords to data coords ## this method is more powerfull as it will return data coords of any points in axes space #transform_data_to_axes = ax2.transData + ax2.transAxes.inverted() #transform_axes_to_data = transform_data_to_axes.inverted() #pts = ((0.,0.),(1.,1.)) #axes space coords #pt1, pt2 = transform_axes_to_data.transform(pts) #extent_data_space = [pt1[0],pt2[0],pt1[1],pt2[1]] #add alpha channel (transparency) to closeup image rgba = np.concatenate([closeup_rgb/255.,clip_alpha[...,np.newaxis]], axis=2) #plot image ax2.imshow(rgba, interpolation='nearest', origin='upper', extent=extent_data_space, zorder=100) ax2.set_aspect('auto') #circle indicating closeup area circle = ax2.plot(closeup_lons, closeup_lats, transform=proj_pc, c=(0.,0.,0.), zorder=300, linewidth=1.5) #prevent clipping of the circle we just drawn for line in ax2.lines: line.set_clip_on(False) # #Quality Controlled Doppler velocity # #axes for this plot pos = [sp_w+(sp_w+rec_w+1./fig_w), sp_h+(sp_h+rec_h), rec_w, rec_h] ax = fig.add_axes(pos, projection=proj_rp) ax.set_extent(map_extent) ax.set_aspect('auto') ax.annotate('Qced Doppler velocity', size=30, xy=(xp, yp), xycoords='axes fraction') #from https://colorbrewer2.org brown_purple=[[ 45, 0, 75], [ 84, 39,136], [128,115,172], [178,171,210], [216,218,235], [247,247,247], [254,224,182], [253,184, 99], [224,130, 20], [179, 88, 6], [127, 59, 8]] range_arr = [-48.,-40.,-30.,-20,-10.,-1.,1.,10.,20.,30.,40.,48.] map_dvel = legs.PalObj(range_arr=range_arr, color_arr = brown_purple, solid='supplied', excep_val=[missing, undetect], excep_col=[missing_color, undetect_color]) #geographical projection of data into axes space proj_data = proj_inds.project_data(doppvelocity_qc) #plot data & palette map_dvel.plot_data(ax=ax,data=proj_data, zorder=0, palette='right', pal_units='[m/s]', pal_format='{:3.0f}') #palette options #add political boundaries add_feature(ax) #radar circles and azimuths radar_ax_circ(ax, radar_lat, radar_lon) #circle indicating closeup area ax.plot(closeup_lons, closeup_lats, transform=proj_pc, c=(0.,0.,0.), zorder=300, linewidth=.8) #arrow pointing to closeup ax.annotate("", xy=(0.33, 0.67), xytext=(.55, .74), xycoords='axes fraction', arrowprops=dict(arrowstyle="<-")) # #Closeup of Doppler velocity # pos = [sp_w+1.*(sp_w+rec_w+1./fig_w)+x0*rec_w, sp_h+(sp_h+rec_h)+y0*rec_h, dx*rec_w, dy*rec_h] ax2 = fig.add_axes(pos, projection=proj_rp, label='overlay') ax2.set_extent(map_extent_close) ax2.spines['geo'].set_linewidth(0.0) #no border line ax2.set_facecolor((1.,1.,1.,0.)) #transparent background #geographical projection of data into axes space proj_data = proj_inds_close.project_data(doppvelocity_qc) #RGB values for data to plot closeup_rgb = map_dvel.to_rgb(proj_data) #get corners of image in data space extent_data_space = ax2.get_extent() #add alpha channel (transparency) to closeup image rgba = np.concatenate([closeup_rgb/255.,clip_alpha[...,np.newaxis]], axis=2) #plot image ax2.imshow(rgba, interpolation='nearest', origin='upper', extent=extent_data_space, zorder=100) ax2.set_aspect('auto') #line indicating closeup area circle = ax2.plot(closeup_lons, closeup_lats, transform=proj_pc, c=(0.,0.,0.), zorder=300, linewidth=1.5) for line in ax2.lines: line.set_clip_on(False) #Show scale in inlay ax2.plot(scale_lons, scale_lats, transform=proj_pc, c=(0.,0.,0.), zorder=300, linewidth=.8) ax2.annotate("5 km", size=18, xy=(.16, .25), xycoords='axes fraction', zorder=310) # #DR # #axes for this plot pos = [sp_w, sp_h, rec_w, rec_h] ax = fig.add_axes(pos, projection=proj_rp) ax.set_extent(map_extent) ax.set_aspect('auto') ax.annotate('Depolarization ratio', size=30, xy=(xp, yp), xycoords='axes fraction') # Set up colormapping object map_dr = legs.PalObj(range_arr=[-36.,-24.,-12., 0.], color_arr=['purple','blue','orange'], dark_pos =['high', 'high','low'], excep_val=[missing, undetect], excep_col=[missing_color, undetect_color]) #geographical projection of data into axes space proj_data = proj_inds.project_data(dr) #plot data & palette map_dr.plot_data(ax=ax,data=proj_data, zorder=0, palette='right', pal_units='[dB]', pal_format='{:3.0f}') #add political boundaries add_feature(ax) #radar circles and azimuths radar_ax_circ(ax, radar_lat, radar_lon) # #Total quality index # #axes for this plot pos = [sp_w+(sp_w+rec_w+1./fig_w), sp_h, rec_w, rec_h] ax = fig.add_axes(pos, projection=proj_rp) ax.set_extent(map_extent) ax.set_aspect('auto') ax.annotate('Total quality index', size=30, xy=(xp, yp), xycoords='axes fraction') # Set up colormapping object pastel = [ [[255,190,187],[230,104, 96]], #pale/dark red [[255,185,255],[147, 78,172]], #pale/dark purple [[255,227,215],[205,144, 73]], #pale/dark brown [[210,235,255],[ 58,134,237]], #pale/dark blue [[223,255,232],[ 61,189, 63]] ] #pale/dark green map_qi = legs.PalObj(range_arr=[0., 1.], dark_pos='high', color_arr=pastel, excep_val=[missing, undetect], excep_col=[missing_color, undetect_color]) #geographical projection of data into axes space proj_data = proj_inds.project_data(tot_qi) #plot data & palette map_qi.plot_data(ax=ax,data=proj_data, zorder=0, palette='right', pal_units='[unitless]', pal_format='{:3.1f}') #palette options #add political boundaries add_feature(ax) #radar circles and azimuths radar_ax_circ(ax, radar_lat, radar_lon)