예제 #1
0
    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)
예제 #2
0
    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)
예제 #3
0
    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)
예제 #4
0
    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)
예제 #5
0
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)
예제 #6
0
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()
예제 #7
0
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')
예제 #8
0
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)
예제 #9
0
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
예제 #10
0
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)
예제 #11
0
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)