Source code for cartopy.vector_transform

# Copyright Crown and Cartopy Contributors
#
# This file is part of Cartopy and is released under the BSD 3-clause license.
# See LICENSE in the root of the repository for full licensing details.
"""
Generic functionality to support Cartopy vector transforms.

"""

import numpy as np


try:
    from scipy.interpolate import griddata
except ImportError as e:
    raise ImportError("Regridding vectors requires scipy.") from e


def _interpolate_to_grid(nx, ny, x, y, *scalars, **kwargs):
    """
    Interpolate two vector components and zero or more scalar fields,
    which can be irregular, to a regular grid.

    Parameters
    ----------
    nx
        Number of points at which to interpolate in x direction.
    ny
        Number of points at which to interpolate in y direction.
    x
        Array of source points in x direction.
    y
        Array of source points in y direction.

    Other Parameters
    ----------------
    scalars
        Zero or more scalar fields to regrid along with the vector
        components.
    target_extent
        The extent in the target CRS that the grid should occupy, in the
        form ``(x-lower, x-upper, y-lower, y-upper)``. Defaults to cover
        the full extent of the vector field.

    """
    target_extent = kwargs.get('target_extent', None)
    if target_extent is None:
        target_extent = (x.min(), x.max(), y.min(), y.max())
    x0, x1, y0, y1 = target_extent
    xr = x1 - x0
    yr = y1 - y0
    points = np.column_stack([(x.ravel() - x0) / xr, (y.ravel() - y0) / yr])
    x_grid, y_grid = np.meshgrid(np.linspace(0, 1, nx),
                                 np.linspace(0, 1, ny))
    s_grid_tuple = tuple()
    for s in scalars:
        s_grid_tuple += (griddata(points, s.ravel(), (x_grid, y_grid),
                                  method='linear'),)
    return (x_grid * xr + x0, y_grid * yr + y0) + s_grid_tuple


[docs] def vector_scalar_to_grid(src_crs, target_proj, regrid_shape, x, y, u, v, *scalars, **kwargs): """ Transform and interpolate a vector field to a regular grid in the target projection. Parameters ---------- src_crs The :class:`~cartopy.crs.CRS` that represents the coordinate system the vectors are defined in. target_proj The :class:`~cartopy.crs.Projection` that represents the projection the vectors are to be transformed to. regrid_shape The regular grid dimensions. If a single integer then the grid will have that number of points in the x and y directions. A 2-tuple of integers specify the size of the regular grid in the x and y directions respectively. x, y The x and y coordinates, in the source CRS coordinates, where the vector components are located. u, v The grid eastward and grid northward components of the vector field respectively. Their shapes must match. Other Parameters ---------------- scalars Zero or more scalar fields to regrid along with the vector components. Each scalar field must have the same shape as the vector components. target_extent The extent in the target CRS that the grid should occupy, in the form ``(x-lower, x-upper, y-lower, y-upper)``. Defaults to cover the full extent of the vector field. Returns ------- x_grid, y_grid The x and y coordinates of the regular grid points as 2-dimensional arrays. u_grid, v_grid The eastward and northward components of the vector field on the regular grid. scalars_grid The scalar fields on the regular grid. The number of returned scalar fields is the same as the number that were passed in. """ x = np.asanyarray(x) y = np.asanyarray(y) u = np.asanyarray(u) v = np.asanyarray(v) if u.shape != v.shape: raise ValueError('u and v must be the same shape') if x.shape != u.shape: x, y = np.meshgrid(x, y) if not (x.shape == y.shape == u.shape): raise ValueError('x and y coordinates are not compatible ' 'with the shape of the vector components') if scalars: np_like_scalars = () for s in scalars: s = np.asanyarray(s) np_like_scalars = np_like_scalars + (s,) if s.shape != u.shape: raise ValueError('scalar fields must have the same ' 'shape as the vector components') scalars = np_like_scalars try: nx, ny = regrid_shape except TypeError: nx = ny = regrid_shape if target_proj == src_crs: # Just immediately regrid, interpolate and return return _interpolate_to_grid(nx, ny, x, y, u, v, *scalars, **kwargs) # We need to transform the vectors from the source to target frame # Convert coordinates to the target projection. proj_xyz = target_proj.transform_points(src_crs, x, y) targetx, targety = proj_xyz[..., 0], proj_xyz[..., 1] # Create the grid in the target frame gridx, gridy = _interpolate_to_grid(nx, ny, targetx, targety, **kwargs) # Bring the x/y target grid coordinates back into the source frame src_xyz = src_crs.transform_points(target_proj, gridx, gridy) # Mask the invalid points that were outside the domain src_xyz = np.ma.array(src_xyz, mask=~np.isfinite(src_xyz)) sourcex, sourcey = src_xyz[..., 0], src_xyz[..., 1] # Now interpolate in the source frame x0, x1 = sourcex.min(), sourcex.max() y0, y1 = sourcey.min(), sourcey.max() xr = x1 - x0 yr = y1 - y0 # We also need to transform the original source points to the source # projection to account for original points outside the wrapped domain xyz = src_crs.transform_points(src_crs, x, y) x, y = xyz[..., 0], xyz[..., 1] points = np.column_stack([(x.ravel() - x0) / xr, (y.ravel() - y0) / yr]) newx = (sourcex - x0) / xr newy = (sourcey - y0) / yr s_grid_tuple = tuple() for s in (u, v) + scalars: s_grid_tuple += (griddata(points, s.ravel(), (newx, newy), method='linear'),) u, v = s_grid_tuple[0], s_grid_tuple[1] # Finally, transform the vectors (in the source frame) to the target CRS. u, v = target_proj.transform_vectors(src_crs, sourcex, sourcey, u, v) return (gridx, gridy, u, v) + s_grid_tuple[2:]