def regrid_area_weighted_rectilinear_src_and_grid(src_cube, grid_cube, mdtol=0): """ Return a new cube with data values calculated using the area weighted mean of data values from src_grid regridded onto the horizontal grid of grid_cube. This function requires that the horizontal grids of both cubes are rectilinear (i.e. expressed in terms of two orthogonal 1D coordinates) and that these grids are in the same coordinate system. This function also requires that the coordinates describing the horizontal grids all have bounds. .. note:: Elements in data array of the returned cube that lie either partially or entirely outside of the horizontal extent of the src_cube will be masked irrespective of the value of mdtol. Args: * src_cube: An instance of :class:`iris.cube.Cube` that supplies the data, metadata and coordinates. * grid_cube: An instance of :class:`iris.cube.Cube` that supplies the desired horizontal grid definition. Kwargs: * mdtol: Tolerance of missing data. The value returned in each element of the returned cube's data array will be masked if the fraction of masked data in the overlapping cells of the source cube exceeds mdtol. This fraction is calculated based on the area of masked cells within each target cell. mdtol=0 means no missing data is tolerated while mdtol=1 will mean the resulting element will be masked if and only if all the overlapping cells of the source cube are masked. Defaults to 0. Returns: A new :class:`iris.cube.Cube` instance. """ # Get the 1d monotonic (or scalar) src and grid coordinates. src_x, src_y = _get_xy_coords(src_cube) grid_x, grid_y = _get_xy_coords(grid_cube) # Condition 1: All x and y coordinates must have contiguous bounds to # define areas. if not src_x.is_contiguous() or not src_y.is_contiguous() or \ not grid_x.is_contiguous() or not grid_y.is_contiguous(): raise ValueError("The horizontal grid coordinates of both the source " "and grid cubes must have contiguous bounds.") # Condition 2: Everything must have the same coordinate system. src_cs = src_x.coord_system grid_cs = grid_x.coord_system if src_cs != grid_cs: raise ValueError("The horizontal grid coordinates of both the source " "and grid cubes must have the same coordinate " "system.") # Condition 3: cannot create vector coords from scalars. src_x_dims = src_cube.coord_dims(src_x) src_x_dim = None if src_x_dims: src_x_dim = src_x_dims[0] src_y_dims = src_cube.coord_dims(src_y) src_y_dim = None if src_y_dims: src_y_dim = src_y_dims[0] if src_x_dim is None and grid_x.shape[0] != 1 or \ src_y_dim is None and grid_y.shape[0] != 1: raise ValueError('The horizontal grid coordinates of source cube ' 'includes scalar coordinates, but the new grid does ' 'not. The new grid must not require additional data ' 'dimensions to be created.') # Determine whether to calculate flat or spherical areas. # Don't only rely on coord system as it may be None. spherical = (isinstance(src_cs, (iris.coord_systems.GeogCS, iris.coord_systems.RotatedGeogCS)) or src_x.units == 'degrees' or src_x.units == 'radians') # Get src and grid bounds in the same units. x_units = iris.unit.Unit('radians') if spherical else src_x.units y_units = iris.unit.Unit('radians') if spherical else src_y.units # Operate in highest precision. src_dtype = np.promote_types(src_x.bounds.dtype, src_y.bounds.dtype) grid_dtype = np.promote_types(grid_x.bounds.dtype, grid_y.bounds.dtype) dtype = np.promote_types(src_dtype, grid_dtype) src_x_bounds = _get_bounds_in_units(src_x, x_units, dtype) src_y_bounds = _get_bounds_in_units(src_y, y_units, dtype) grid_x_bounds = _get_bounds_in_units(grid_x, x_units, dtype) grid_y_bounds = _get_bounds_in_units(grid_y, y_units, dtype) # Determine whether target grid bounds are decreasing. This must # be determined prior to wrap_lons being called. grid_x_decreasing = grid_x_bounds[-1, 0] < grid_x_bounds[0, 0] grid_y_decreasing = grid_y_bounds[-1, 0] < grid_y_bounds[0, 0] # Wrapping of longitudes. if spherical: base = np.min(src_x_bounds) modulus = x_units.modulus # Only wrap if necessary to avoid introducing floating # point errors. if np.min(grid_x_bounds) < base or \ np.max(grid_x_bounds) > (base + modulus): grid_x_bounds = iris.analysis.cartography.wrap_lons(grid_x_bounds, base, modulus) # Determine whether the src_x coord has periodic boundary conditions. circular = getattr(src_x, 'circular', False) # Use simple cartesian area function or one that takes into # account the curved surface if coord system is spherical. if spherical: area_func = _spherical_area else: area_func = _cartesian_area # Calculate new data array for regridded cube. new_data = _regrid_area_weighted_array(src_cube.data, src_x_dim, src_y_dim, src_x_bounds, src_y_bounds, grid_x_bounds, grid_y_bounds, grid_x_decreasing, grid_y_decreasing, area_func, circular, mdtol) # Wrap up the data as a Cube. # Create 2d meshgrids as required by _create_cube func. meshgrid_x, meshgrid_y = np.meshgrid(grid_x.points, grid_y.points) regrid_callback = RectilinearRegridder._regrid_bilinear_array new_cube = RectilinearRegridder._create_cube(new_data, src_cube, src_x_dim, src_y_dim, src_x, src_y, grid_x, grid_y, meshgrid_x, meshgrid_y, regrid_callback) # Slice out any length 1 dimensions. indices = [slice(None, None)] * new_data.ndim if src_x_dim is not None and new_cube.shape[src_x_dim] == 1: indices[src_x_dim] = 0 if src_y_dim is not None and new_cube.shape[src_y_dim] == 1: indices[src_y_dim] = 0 if 0 in indices: new_cube = new_cube[tuple(indices)] return new_cube
def regrid_bilinear_rectilinear_src_and_grid(src, grid, extrapolation_mode='mask'): """ Return a new Cube that is the result of regridding the source Cube onto the grid of the grid Cube using bilinear interpolation. Both the source and grid Cubes must be defined on rectilinear grids. Auxiliary coordinates which span the grid dimensions are ignored, except where they provide a reference surface for an :class:`iris.aux_factory.AuxCoordFactory`. Args: * src: The source :class:`iris.cube.Cube` providing the data. * grid: The :class:`iris.cube.Cube` which defines the new grid. Kwargs: * extrapolation_mode: Must be one of the following strings: * 'linear' - The extrapolation points will be calculated by extending the gradient of the closest two points. * 'nan' - The extrapolation points will be be set to NaN. * 'error' - A ValueError exception will be raised, notifying an attempt to extrapolate. * 'mask' - The extrapolation points will always be masked, even if the source data is not a MaskedArray. * 'nanmask' - If the source data is a MaskedArray the extrapolation points will be masked. Otherwise they will be set to NaN. The default mode of extrapolation is 'mask'. Returns: The :class:`iris.cube.Cube` resulting from regridding the source data onto the grid defined by the grid Cube. """ # Validity checks if not isinstance(src, iris.cube.Cube): raise TypeError("'src' must be a Cube") if not isinstance(grid, iris.cube.Cube): raise TypeError("'grid' must be a Cube") src_x_coord, src_y_coord = get_xy_dim_coords(src) grid_x_coord, grid_y_coord = get_xy_dim_coords(grid) src_cs = src_x_coord.coord_system grid_cs = grid_x_coord.coord_system if src_cs is None and grid_cs is None: if not (src_x_coord.is_compatible(grid_x_coord) and src_y_coord.is_compatible(grid_y_coord)): raise ValueError("The rectilinear grid coordinates of the 'src' " "and 'grid' Cubes have no coordinate system but " "they do not have matching coordinate metadata.") elif src_cs is None or grid_cs is None: raise ValueError("The rectilinear grid coordinates of the 'src' and " "'grid' Cubes must either both have coordinate " "systems or both have no coordinate system but with " "matching coordinate metadata.") def _check_units(coord): if coord.coord_system is None: # No restriction on units. pass elif isinstance(coord.coord_system, (iris.coord_systems.GeogCS, iris.coord_systems.RotatedGeogCS)): # Units for lat-lon or rotated pole must be 'degrees'. Note # that 'degrees_east' etc. are equal to 'degrees'. if coord.units != 'degrees': msg = "Unsupported units for coordinate system. " \ "Expected 'degrees' got {!r}.".format(coord.units) raise ValueError(msg) else: # Units for other coord systems must be equal to metres. if coord.units != 'm': msg = "Unsupported units for coordinate system. " \ "Expected 'metres' got {!r}.".format(coord.units) raise ValueError(msg) for coord in (src_x_coord, src_y_coord, grid_x_coord, grid_y_coord): _check_units(coord) if extrapolation_mode not in EXTRAPOLATION_MODES: raise ValueError('Invalid extrapolation mode.') # Convert the grid to a 2D sample grid in the src CRS. sample_grid = RectilinearRegridder._sample_grid(src_cs, grid_x_coord, grid_y_coord) sample_grid_x, sample_grid_y = sample_grid # Compute the interpolated data values. x_dim = src.coord_dims(src_x_coord)[0] y_dim = src.coord_dims(src_y_coord)[0] data = RectilinearRegridder._regrid_bilinear_array(src.data, x_dim, y_dim, src_x_coord, src_y_coord, sample_grid_x, sample_grid_y, extrapolation_mode) # Wrap up the data as a Cube. regrid_callback = partial(RectilinearRegridder._regrid_bilinear_array, extrapolation_mode='nan') result = RectilinearRegridder._create_cube(data, src, x_dim, y_dim, src_x_coord, src_y_coord, grid_x_coord, grid_y_coord, sample_grid_x, sample_grid_y, regrid_callback) return result