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)
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)
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 _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)
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'])
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'])
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'])
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}
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