예제 #1
0
파일: regrid.py 프로젝트: pp-mo/iris
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
예제 #2
0
파일: regrid.py 프로젝트: pp-mo/iris
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