Exemple #1
0
def _label(cube, mode, result=None, ndims=2, coords=None):
    """Puts labels on the current plot using the given cube."""
    
    plt.title(_title(cube, with_units=False))

    if result is not None:
        draw_edges = mode == iris.coords.POINT_MODE
        bar = plt.colorbar(result, orientation='horizontal',
                           drawedges=draw_edges)
        has_known_units = not (cube.units.is_unknown() or cube.units.is_no_unit())
        if has_known_units and cube.units != iris.unit.Unit('1'):
            # Use shortest unit representation for anything other than time
            if (not cube.units.is_time() and not cube.units.is_time_reference() and
                len(cube.units.symbol) < len(str(cube.units))):
                bar.set_label(cube.units.symbol)
            else:
                bar.set_label(cube.units)
        # Remove the tick which is put on the colorbar by default.
        bar.ax.tick_params(length=0)
    
    if coords is None:
        plot_defn = iplt._get_plot_defn(cube, mode, ndims)
    else:
        plot_defn = iplt._get_plot_defn_custom_coords_picked(cube, coords, mode, ndims=ndims)
    
    if ndims == 2:
        if not iplt._can_draw_map(plot_defn.coords):
            plt.ylabel(_title(plot_defn.coords[0], with_units=True))
            plt.xlabel(_title(plot_defn.coords[1], with_units=True))
    elif ndims == 1:
        plt.xlabel(_title(plot_defn.coords[0], with_units=True))
        plt.ylabel(_title(cube, with_units=True))
    else:
        raise ValueError('Unexpected number of dimensions (%s) given to _label.' % ndims)
Exemple #2
0
def _label(cube, mode, result=None, ndims=2, coords=None):
    """Puts labels on the current plot using the given cube."""
    
    plt.title(_title(cube, with_units=False))

    if result is not None:
        draw_edges = mode == iris.coords.POINT_MODE
        bar = plt.colorbar(result, orientation='horizontal', drawedges=draw_edges)
        bar.set_label(cube.units)
        # Remove the tick which is put on the colorbar by default.
        bar.ax.tick_params(length=0)
    
    if coords is None:
        plot_defn = iplt._get_plot_defn(cube, mode, ndims)
    else:
        plot_defn = iplt._get_plot_defn_custom_coords_picked(cube, coords, mode, ndims=ndims)
    
    if ndims == 2:
        if not iplt._can_draw_map(plot_defn.coords):
            plt.ylabel(_title(plot_defn.coords[0], with_units=True))
            plt.xlabel(_title(plot_defn.coords[1], with_units=True))
    elif ndims == 1:
        plt.xlabel(_title(plot_defn.coords[0], with_units=True))
        plt.ylabel(_title(cube, with_units=True))
    else:
        raise ValueError('Unexpected number of dimensions (%s) given to _label.' % ndims)
Exemple #3
0
    def _cube_manipulation(cube, x, y):
        """
        Optionally transpose the data and make a mesh-grid, taking into account auxilliary coordinates
        A lot of this logic closely follows that used in iris.qplt.

        :param Cube cube: An iris Cube
        :param NDarray x: a numpy array of x points
        :param NDarray y: a numpy array of y points
        :return: data, x, and y and as numpy arrays
        """
        import iris.plot as iplt
        import iris
        from cartopy.util import add_cyclic_point
        import numpy as np

        no_of_dims = len(cube.shape)
        data = cube.data

        plot_defn = iplt._get_plot_defn(cube, iris.coords.POINT_MODE, ndims=no_of_dims)
        if plot_defn.transpose:
            data = data.T
            x = x.T
            y = y.T

        # Check for auxiliary coordinates.
        aux_coords = False
        for coord in cube[0].coords(dim_coords=False):
            aux_coords = True
        if no_of_dims == 2:
            # If we have found some auxiliary coordinates in the data and the shape of x data or y data is the same as
            # data assume we have a hybrid coordinate (which is two dimensional b nature. Perhaps need a more robust
            # method for detecting this.
            if aux_coords and (data.shape == x.shape or data.shape == y.shape):
                # Work out which set of data needs expanding to match the coordinates of the others. Note there can only
                # ever be one hybrid coordinate axis.
                if y.shape == data.shape:
                    if y[:, 0].shape == x.shape:
                        x, _y = np.meshgrid(x, y[0, :])
                    elif y[0, :].shape == x.shape:
                        x, _y = np.meshgrid(x, y[:, 0])
                elif x.shape == data.shape:
                    if x[:, 0].shape == y.shape:
                        y, _x = np.meshgrid(y, x[0, :])
                    elif x[0, :].shape == y.shape:
                        y, _x = np.meshgrid(y, x[:, 0])
            else:
                if len(x) == data.shape[-1]:
                    try:
                        data, x = add_cyclic_point(data, x)
                    except ValueError as e:
                        logging.warn('Unable to add cyclic data point for x-axis. Error was: ' + e.args[0])
                    x, y = np.meshgrid(x, y)
                elif len(y) == data.shape[-1]:
                    try:
                        data, y = add_cyclic_point(data, y)
                    except ValueError as e:
                        logging.warn('Unable to add cyclic data point for y-axis. Error was: ' + e.args[0])
                    y, x = np.meshgrid(y, x)
        return data, x, y
Exemple #4
0
def _label(cube, mode, result=None, ndims=2, coords=None, axes=None):
    """Puts labels on the current plot using the given cube."""

    if axes is None:
        axes = plt.gca()

    axes.set_title(_title(cube, with_units=False))

    if result is not None:
        draw_edges = mode == iris.coords.POINT_MODE
        bar = plt.colorbar(result,
                           orientation='horizontal',
                           drawedges=draw_edges)
        has_known_units = not (cube.units.is_unknown()
                               or cube.units.is_no_unit())
        if has_known_units and cube.units != cf_units.Unit('1'):
            # Use shortest unit representation for anything other than time
            if _use_symbol(cube.units):
                bar.set_label(cube.units.symbol)
            else:
                bar.set_label(cube.units)
        # Remove the tick which is put on the colorbar by default.
        bar.ax.tick_params(length=0)

    if coords is None:
        plot_defn = iplt._get_plot_defn(cube, mode, ndims)
    else:
        plot_defn = iplt._get_plot_defn_custom_coords_picked(cube,
                                                             coords,
                                                             mode,
                                                             ndims=ndims)

    if ndims == 2:
        if not iplt._can_draw_map(plot_defn.coords):
            axes.set_ylabel(_title(plot_defn.coords[0], with_units=True))
            axes.set_xlabel(_title(plot_defn.coords[1], with_units=True))
    elif ndims == 1:
        axes.set_xlabel(_title(plot_defn.coords[0], with_units=True))
        axes.set_ylabel(_title(cube, with_units=True))
    else:
        msg = 'Unexpected number of dimensions ({}) given to ' \
              '_label.'.format(ndims)
        raise ValueError(msg)
Exemple #5
0
 def test_2d_coords(self):
     cube = simple_2d_w_multidim_coords()
     defn = iplt._get_plot_defn(cube, iris.coords.BOUND_MODE)
     self.assertEqual([coord.name() for coord in defn.coords],
                      ['bar', 'foo'])
Exemple #6
0
 def test_axis_order_yx(self):
     cube_yx = simple_2d()
     cube_yx.transpose()
     defn = iplt._get_plot_defn(cube_yx, iris.coords.POINT_MODE)
     self.assertEqual([coord.name() for coord in defn.coords],
                      ['foo', 'bar'])
Exemple #7
0
 def test_axis_order_xy(self):
     cube_xy = simple_2d()
     defn = iplt._get_plot_defn(cube_xy, iris.coords.POINT_MODE)
     self.assertEqual([coord.name() for coord in defn.coords],
                      ['bar', 'foo'])
Exemple #8
0
def unpack_data_object(data_object, x_variable, y_variable, x_wrap_start):
    """
    :param data_object    A cube or an UngriddedData object
    :return: A dictionary containing x, y and data as numpy arrays
    """
    from iris.cube import Cube
    import iris.plot as iplt
    import iris
    import logging
    from mpl_toolkits.basemap import addcyclic

    no_of_dims = len(data_object.shape)

    data = data_object.data  # ndarray

    x = get_coord(data_object, x_variable, data)
    if hasattr(x, 'points'):
        x = x.points
    try:
        coord = data_object.coord(name=x_variable)
        x_axis_name = guess_coord_axis(coord)
    except CoordinateNotFoundError:
        x_axis_name = None

    y = get_coord(data_object, y_variable, data)
    if hasattr(y, 'points'):
        y = y.points
    # Must use special function to check equality of array here, so NaNs are returned as equal and False is returned if
    # arrays have a different shape
    if array_equal_including_nan(y, data) or array_equal_including_nan(y, x):
        y = None

    if array_equal_including_nan(x, data):
        data = y
        y = None

    if isinstance(data_object, Cube):
        plot_defn = iplt._get_plot_defn(data_object, iris.coords.POINT_MODE, ndims=no_of_dims)
        if plot_defn.transpose:
            data = data.T
            x = x.T
            y = y.T

        # Check for auxiliary coordinates.
        aux_coords = False
        for coord in data_object[0].coords(dim_coords=False):
            aux_coords = True

        if no_of_dims == 2:
            # If we have found some auxiliary coordinates in the data and the shape of x data or y data is the same as
            # data assume we have a hybrid coordinate (which is two dimensional b nature. Perhaps need a more robust
            # method for detecting this.
            if aux_coords and (data.shape == x.shape or data.shape == y.shape):
                # Work out which set of data needs expanding to match the coordinates of the others. Note there can only
                # ever be one hybrid coordinate axis.
                if y.shape == data.shape:
                    if y[:, 0].shape == x.shape:
                        x, _y = np.meshgrid(x, y[0, :])
                    elif y[0, :].shape == x.shape:
                        x, _y = np.meshgrid(x, y[:, 0])
                elif x.shape == data.shape:
                    if x[:, 0].shape == y.shape:
                        y, _x = np.meshgrid(y, x[0, :])
                    elif x[0, :].shape == y.shape:
                        y, _x = np.meshgrid(y, x[:, 0])
            else:
                try:
                    data, x = addcyclic(data, x)
                    x, y = np.meshgrid(x, y)
                except:
                    data, y = addcyclic(data, y)
                    y, x = np.meshgrid(y, x)

    if x_axis_name == 'X' and x_wrap_start is not None:
        # x = iris.analysis.cartography.wrap_lons(x, x_wrap_start, 360)
        if isnan(x_wrap_start):
            raise InvalidCommandLineOptionError('Overall range for longitude axis must be within 0 - 360 degrees.')
        x = fix_longitude_range(x, x_wrap_start)

    logging.debug("Shape of x: " + str(x.shape))
    if y is not None:
        logging.debug("Shape of y: " + str(y.shape))
    logging.debug("Shape of data: " + str(data.shape))

    return {"data": data, "x": x, "y": y}
Exemple #9
0
    def _cube_manipulation(cube, x, y):
        """
        Optionally transpose the data and make a mesh-grid, taking into account auxilliary coordinates
        A lot of this logic closely follows that used in iris.qplt.

        :param Cube cube: An iris Cube
        :param NDarray x: a numpy array of x points
        :param NDarray y: a numpy array of y points
        :return: data, x, and y and as numpy arrays
        """
        import iris.plot as iplt
        import iris
        from cartopy.util import add_cyclic_point
        import numpy as np

        no_of_dims = len(cube.shape)
        data = cube.data

        plot_defn = iplt._get_plot_defn(cube,
                                        iris.coords.POINT_MODE,
                                        ndims=no_of_dims)
        if plot_defn.transpose:
            data = data.T
            x = x.T
            y = y.T

        # Check for auxiliary coordinates.
        aux_coords = False
        for coord in cube[0].coords(dim_coords=False):
            aux_coords = True
        if no_of_dims == 2:
            # If we have found some auxiliary coordinates in the data and the shape of x data or y data is the same as
            # data assume we have a hybrid coordinate (which is two dimensional b nature. Perhaps need a more robust
            # method for detecting this.
            if aux_coords and (data.shape == x.shape or data.shape == y.shape):
                # Work out which set of data needs expanding to match the coordinates of the others. Note there can only
                # ever be one hybrid coordinate axis.
                if y.shape == data.shape:
                    if y[:, 0].shape == x.shape:
                        x, _y = np.meshgrid(x, y[0, :])
                    elif y[0, :].shape == x.shape:
                        x, _y = np.meshgrid(x, y[:, 0])
                elif x.shape == data.shape:
                    if x[:, 0].shape == y.shape:
                        y, _x = np.meshgrid(y, x[0, :])
                    elif x[0, :].shape == y.shape:
                        y, _x = np.meshgrid(y, x[:, 0])
            else:
                if len(x) == data.shape[-1]:
                    try:
                        data, x = add_cyclic_point(data, x)
                    except ValueError as e:
                        logging.warn(
                            'Unable to add cyclic data point for x-axis. Error was: '
                            + e.args[0])
                    x, y = np.meshgrid(x, y)
                elif len(y) == data.shape[-1]:
                    try:
                        data, y = add_cyclic_point(data, y)
                    except ValueError as e:
                        logging.warn(
                            'Unable to add cyclic data point for y-axis. Error was: '
                            + e.args[0])
                    y, x = np.meshgrid(y, x)
        return data, x, y
def plot_cube_cross_section(cube,
                            waypoints,
                            short_name,
                            ini_dict,
                            bl_depth=None,
                            titleprefix=None,
                            plotdir=None,
                            filesuffix='',
                            tight=False,
                            verbose=1):
    """
    Plot vertical cross section of a cube along a given set of waypoints.

    Defaults (where parameters are set to None or 'default') are set to the
    defaults in :class:`field_layer.FieldLayer` or

    :class:`field_plot.FieldPlot`.

    :param cube: Iris cube which should contain X,Y and Z coordinates, plus
                 'short_name' and 'label' attributes.

    :param waypoints: List of dictionaries, whose keys are 'latitude' and
                      'longitude' and whose values are the lat/lon points that
                      should be included in the cross-section.

    :param short_name: string to match to short_name attribute in cubes.

    :param ini_dict: Dictionary of values used to define appearance of plot.

    :param bl_depth: Iris cube containing boundary layer depth. This should
                     contain X and Y coordinates, plus 'short_name' and 'label'
                     attributes. If given, the boundary layer height will also
                     be plotted on the cross-section plot.

    :param titleprefix: String to prefix to default title (time of plot).

    :param plotdir: Output directory for plot to be saved to.

    :param filesuffix: String to add to end of filename for specific naming
                       purposes. By default adds nothing.

    :param tight: If set to True, will adjust figure size to
                  be tight to bounding box of figure and minimise
                  whitespace. Note this will change size of output file
                  and may not be consistent amongst different plots
                  (particularly if levels on colorbar are different).

    :param verbose: Level of print output:

                    * 0 = No extra printing
                    * 1 = Standard level (recommended)
                    * 2 = Extra level for debugging

    :returns: Iris cube containing the section that was plotted. This will
              have additional coordinate 'i_sample_points' instead of X and Y
              coordinates.

    Load example cubes:

    >>> import config
    >>> import adaq_functions
    >>> sample_datadir = config.SAMPLE_DATADIR + 'aqum_output/oper/3d/'
    >>> tcube = iris.load_cube(sample_datadir +
    ... 'prodm_op_aqum_20170701_18.006.pp', 'air_temperature')
    >>> ini_dict = inifile.get_inidict(defaultfilename=
    ... 'adaqcode/adaq_vertical_plotting.ini') # doctest: +ELLIPSIS
    Reading inifile .../adaqcode/adaq_vertical_plotting.ini

    >>> tcube.attributes['short_name'] = 'T'
    >>> tcube.attributes['label'] = 'aqum'
    >>> tcube.coord('time').points = tcube.coord('time').bounds[:, 1]
    >>> print(tcube.summary(True)) # doctest: +NORMALIZE_WHITESPACE
    air_temperature / (K) (time: 2; model_level_number: 63; grid_latitude:
    182; grid_longitude: 146)
    >>> blcube = iris.load_cube(sample_datadir +
    ... 'prods_op_aqum_20170701_18.000.pp',
    ... 'atmosphere_boundary_layer_thickness')
    >>> blcube.attributes['short_name'] = 'bl_depth'
    >>> blcube.attributes['label'] = 'aqum'

    Set up dictionary of way points and then plot cross section

    >>> waypoints = [{'latitude':51.1, 'longitude':-0.6},
    ... {'latitude':51.7, 'longitude':0.2},
    ... {'latitude':52.0, 'longitude':-1.0}]
    >>> plotdir = config.CODE_DIR + "/adaqdocs/" + "figures/adaq_plotting"
    >>> section = plot_cube_cross_section(tcube, waypoints, 'T', ini_dict,
    ... bl_depth=blcube, plotdir=plotdir) # doctest: +ELLIPSIS
    Saved figure  .../CrossSection_aqum_T_201707020300.png
    Saved figure  .../CrossSection_aqum_T_201707020600.png

    .. image:: ../adaqdocs/figures/adaq_plotting/
               CrossSection_aqum_T_201707020600.png
       :scale: 75%

    >>> print(section.summary(True)) # doctest: +NORMALIZE_WHITESPACE
    air_temperature / (K) (time: 2; level_height: 26; i_sample_point: 30)

    """

    #Get some variables from ini_dict if possible.
    max_height = ini_dict.get('max_height', None)
    if max_height is not None:
        max_height = float(max_height)

    #If levels not already set, get levels from ini_dict if possible
    #(and corresponding number of levels)
    levels_dict = ini_dict.get('levels_dict', {})
    if short_name in levels_dict:
        levels = levels_dict[short_name]
    else:
        levels = ini_dict.get('levels_list', None)
    if levels is not None:
        levels = [float(v) for v in levels]
    if levels is not None:
        nlevels = len(levels)
    else:
        nlevels = ini_dict.get('nlevels', 10)

    cmap = ini_dict.get('cmap', 'YlGnBu')
    cbar_label = ini_dict.get('cbar_label', 'default')
    cbar = ini_dict.get('cbar', True)
    cbar_num_fmt = ini_dict.get('cbar_num_fmt', None)
    line_colours_list = ini_dict.get('line_colours_list', COLOURS)
    line_colour = line_colours_list[0]
    cbar_orientation = ini_dict.get('cbar_orientation', 'vertical')

    # Extract a section along the waypoints
    section = cube_functions.extract_section(cube, waypoints)

    #Plot against a sensible vertical coordinate
    if section.coords('model_level_number') and section.coords('level_height'):
        section.remove_coord('model_level_number')
        iris.util.promote_aux_coord_to_dim_coord(section, 'level_height')

    zcoordname, = cube_functions.guess_coord_names(section, ['Z'])
    if zcoordname is None:
        print('Not plotting cross section for ' +
              cube.attributes['short_name'] + ' from ' +
              cube.attributes['label'] + ' (no vertical coordinate)')
        return None
    scoordname = 'i_sample_point'

    #Limit section to maximum required height
    if max_height is not None:
        if section.coords('level_height'):
            section = section.extract(
                iris.Constraint(level_height=lambda c: c <= max_height))
        else:
            raise UserWarning('Cannot limit to max_height as level_height' +
                              'coordinate does not exist')
    if bl_depth:
        bl_depth_section = cube_functions.extract_section(bl_depth, waypoints)

    #---
    #Set up field layer
    flr = field_layer.FieldLayer(section)
    flr.set_layerstyle(plottype='pcolormesh',
                       colorscale='linear',
                       levels=levels,
                       nlevels=nlevels,
                       mask=True,
                       cmap=cmap)

    flr.cbar = cbar
    flr.cbar_orientation = cbar_orientation
    flr.cbar_label = cbar_label
    flr.cbar_num_fmt = cbar_num_fmt

    # Set colour for boundary layer line plots
    line_colour = line_colour if line_colour else 'black'

    #---
    #Loop over fields within layer (sample point & Z coords)
    for layer_slice in flr.layer_slice([scoordname, zcoordname]):
        fplt = field_plot.FieldPlot(ini_dict)
        #Don't plot fields which are entirely nan data
        if isinstance(layer_slice.cube.data, np.ma.MaskedArray):
            if np.sum(np.isfinite(layer_slice.cube.data.data)) == 0:
                #All nan data
                warnings.warn('All nan data, no gridded field plot created')
                continue
        else:
            #Not a masked array
            if np.sum(np.isfinite(layer_slice.cube.data)) == 0:
                #All nan data
                warnings.warn('All nan data, no gridded field plot created')
                continue
        fplt.add_layer(layer_slice)

        if titleprefix is None:
            titleprefix = ('Cross section for ' +
                           cube.name().replace('_', ' ') + '\n')
        fplt.titleprefix = titleprefix
        # Set a specific figsize for this type of plot:
        fplt.plot(figsize=[15.0, 6.0])

        # Get the coordinate that was actually used on the vertical axis.
        vert_axis_coord = iplt._get_plot_defn(flr.cube,
                                              iris.coords.BOUND_MODE,
                                              ndims=2).coords[0]

        plt.gca().set_ylabel(vert_axis_coord.name().replace('_', ' ') + ' (' +
                             units_str(str(vert_axis_coord.units)) + ')')
        if vert_axis_coord.has_bounds():
            plt.gca().set_ylim([0, vert_axis_coord.bounds[-1, 1]])
        plt.gca().set_xlabel('Section point')
        plt.gca().set_xlim([-0.5, 29.5])  # n_sample_points=30.

        # Also plot BL depth at the same time
        if bl_depth:
            time = layer_slice.cube.coord('time').units.num2date(
                layer_slice.cube.coord('time').points[0])
            bl_depth_slice = bl_depth_section.extract(
                iris.Constraint(time=time))
            if bl_depth_slice:  # found matching time
                # Plot
                iplt.plot(bl_depth_slice,
                          label='Boundary Layer Height',
                          color=line_colour,
                          linestyle='--')
                # Add label
                plt.gca().legend()

        fplt.save_fig(plotdir=plotdir,
                      fileprefix='CrossSection_',
                      filesuffix=filesuffix,
                      tight=tight,
                      verbose=verbose)

    return section