def regrid(array, source_x_coords, source_y_coords, source_proj, target_proj, target_x_points, target_y_points, mask_extrapolated=False): """ Regrid the data array from the source projection to the target projection. Parameters ---------- array The :class:`numpy.ndarray` of data to be regridded to the target projection. source_x_coords A 2-dimensional source projection :class:`numpy.ndarray` of x-direction sample points. source_y_coords A 2-dimensional source projection :class:`numpy.ndarray` of y-direction sample points. source_proj The source :class:`~cartopy.crs.Projection` instance. target_proj The target :class:`~cartopy.crs.Projection` instance. target_x_points A 2-dimensional target projection :class:`numpy.ndarray` of x-direction sample points. target_y_points A 2-dimensional target projection :class:`numpy.ndarray` of y-direction sample points. mask_extrapolated: optional Assume that the source coordinate is rectilinear and so mask the resulting target grid values which lie outside the source grid domain. Defaults to False. Returns ------- new_array The data array regridded in the target projection. """ # Stack our original xyz array, this will also wrap coords when necessary xyz = source_proj.transform_points(source_proj, source_x_coords.flatten(), source_y_coords.flatten()) # Transform the target points into the source projection target_xyz = source_proj.transform_points(target_proj, target_x_points.flatten(), target_y_points.flatten()) if _is_pykdtree: kdtree = pykdtree.kdtree.KDTree(xyz) # Use sqr_dists=True because we don't care about distances, # and it saves a sqrt. _, indices = kdtree.query(target_xyz, k=1, sqr_dists=True) else: # Versions of scipy >= v0.16 added the balanced_tree argument, # which caused the KDTree to hang with this input. kdtree = scipy.spatial.cKDTree(xyz, balanced_tree=False) _, indices = kdtree.query(target_xyz, k=1) mask = indices >= len(xyz) indices[mask] = 0 desired_ny, desired_nx = target_x_points.shape # Squash the first two dims of the source array into one temp_array = array.reshape((-1,) + array.shape[2:]) if np.any(mask): new_array = np.ma.array(temp_array[indices]) new_array[mask] = np.ma.masked else: new_array = temp_array[indices] new_array.shape = (desired_ny, desired_nx) + (array.shape[2:]) # Do double transform to clip points that do not map back and forth # to the same point to within a fixed fractional offset. # NOTE: This only needs to be done for (pseudo-)cylindrical projections, # or any others which have the concept of wrapping back_to_target_xyz = target_proj.transform_points(source_proj, target_xyz[:, 0], target_xyz[:, 1]) back_to_target_x = back_to_target_xyz[:, 0].reshape(desired_ny, desired_nx) back_to_target_y = back_to_target_xyz[:, 1].reshape(desired_ny, desired_nx) FRACTIONAL_OFFSET_THRESHOLD = 0.1 # data has moved by 10% of the map x_extent = np.abs(target_proj.x_limits[1] - target_proj.x_limits[0]) y_extent = np.abs(target_proj.y_limits[1] - target_proj.y_limits[0]) non_self_inverse_points = (((np.abs(target_x_points - back_to_target_x) / x_extent) > FRACTIONAL_OFFSET_THRESHOLD) | ((np.abs(target_y_points - back_to_target_y) / y_extent) > FRACTIONAL_OFFSET_THRESHOLD)) if np.any(non_self_inverse_points): if not np.ma.isMaskedArray(new_array): new_array = np.ma.array(new_array, mask=False) new_array[non_self_inverse_points] = np.ma.masked # Transform the target points to the source projection and mask any points # that fall outside the original source domain. if mask_extrapolated: target_in_source_x = target_xyz[:, 0].reshape(desired_ny, desired_nx) target_in_source_y = target_xyz[:, 1].reshape(desired_ny, desired_nx) bounds = _determine_bounds(source_x_coords, source_y_coords, source_proj) outside_source_domain = ((target_in_source_y >= bounds['y'][1]) | (target_in_source_y <= bounds['y'][0])) tmp_inside = np.zeros_like(outside_source_domain) for bound_x in bounds['x']: tmp_inside = tmp_inside | ((target_in_source_x <= bound_x[1]) & (target_in_source_x >= bound_x[0])) outside_source_domain = outside_source_domain | ~tmp_inside if np.any(outside_source_domain): if not np.ma.isMaskedArray(new_array): new_array = np.ma.array(new_array, mask=False) new_array[outside_source_domain] = np.ma.masked return new_array
def regrid(array, source_x_coords, source_y_coords, source_cs, target_proj, target_x_points, target_y_points, mask_extrapolated=False): """ Regrid the data array from the source projection to the target projection. Parameters ---------- array The :class:`numpy.ndarray` of data to be regridded to the target projection. source_x_coords A 2-dimensional source projection :class:`numpy.ndarray` of x-direction sample points. source_y_coords A 2-dimensional source projection :class:`numpy.ndarray` of y-direction sample points. source_cs The source :class:`~cartopy.crs.Projection` instance. target_cs The target :class:`~cartopy.crs.Projection` instance. target_x_points A 2-dimensional target projection :class:`numpy.ndarray` of x-direction sample points. target_y_points A 2-dimensional target projection :class:`numpy.ndarray` of y-direction sample points. mask_extrapolated: optional Assume that the source coordinate is rectilinear and so mask the resulting target grid values which lie outside the source grid domain. Defaults to False. Returns ------- new_array The data array regridded in the target projection. """ # n.b. source_cs is actually a projection (the coord system of the # source coordinates), but not necessarily the native projection of # the source array (i.e. you can provide a warped image with lat lon # coordinates). # XXX NB. target_x and target_y must currently be rectangular (i.e. # be a 2d np array) geo_cent = source_cs.as_geocentric() xyz = geo_cent.transform_points(source_cs, source_x_coords.flatten(), source_y_coords.flatten()) target_xyz = geo_cent.transform_points(target_proj, target_x_points.flatten(), target_y_points.flatten()) if _is_pykdtree: kdtree = pykdtree.kdtree.KDTree(xyz) # Use sqr_dists=True because we don't care about distances, # and it saves a sqrt. _, indices = kdtree.query(target_xyz, k=1, sqr_dists=True) else: # Versions of scipy >= v0.16 added the balanced_tree argument, # which caused the KDTree to hang with this input. try: kdtree = scipy.spatial.cKDTree(xyz, balanced_tree=False) except TypeError: kdtree = scipy.spatial.cKDTree(xyz) _, indices = kdtree.query(target_xyz, k=1) mask = indices >= len(xyz) indices[mask] = 0 desired_ny, desired_nx = target_x_points.shape # Squash the first two dims of the source array into one temp_array = array.reshape((-1, ) + array.shape[2:]) if np.any(mask): new_array = np.ma.array(temp_array[indices]) new_array[mask] = np.ma.masked else: new_array = temp_array[indices] new_array.shape = (desired_ny, desired_nx) + (array.shape[2:]) # Do double transform to clip points that do not map back and forth # to the same point to within a fixed fractional offset. # XXX THIS ONLY NEEDS TO BE DONE FOR (PSEUDO-)CYLINDRICAL PROJECTIONS # (OR ANY OTHERS WHICH HAVE THE CONCEPT OF WRAPPING) source_desired_xyz = source_cs.transform_points(target_proj, target_x_points.flatten(), target_y_points.flatten()) back_to_target_xyz = target_proj.transform_points(source_cs, source_desired_xyz[:, 0], source_desired_xyz[:, 1]) back_to_target_x = back_to_target_xyz[:, 0].reshape(desired_ny, desired_nx) back_to_target_y = back_to_target_xyz[:, 1].reshape(desired_ny, desired_nx) FRACTIONAL_OFFSET_THRESHOLD = 0.1 # data has moved by 10% of the map x_extent = np.abs(target_proj.x_limits[1] - target_proj.x_limits[0]) y_extent = np.abs(target_proj.y_limits[1] - target_proj.y_limits[0]) non_self_inverse_points = (((np.abs(target_x_points - back_to_target_x) / x_extent) > FRACTIONAL_OFFSET_THRESHOLD) | ((np.abs(target_y_points - back_to_target_y) / y_extent) > FRACTIONAL_OFFSET_THRESHOLD)) if np.any(non_self_inverse_points): if not np.ma.isMaskedArray(new_array): new_array = np.ma.array(new_array, mask=False) new_array[non_self_inverse_points] = np.ma.masked # Transform the target points to the source projection and mask any points # that fall outside the original source domain. if mask_extrapolated: target_in_source_xyz = source_cs.transform_points( target_proj, target_x_points, target_y_points) target_in_source_x = target_in_source_xyz[..., 0] target_in_source_y = target_in_source_xyz[..., 1] bounds = _determine_bounds(source_x_coords, source_y_coords, source_cs) outside_source_domain = ((target_in_source_y >= bounds['y'][1]) | (target_in_source_y <= bounds['y'][0])) tmp_inside = np.zeros_like(outside_source_domain) for bound_x in bounds['x']: tmp_inside = tmp_inside | ((target_in_source_x <= bound_x[1]) & (target_in_source_x >= bound_x[0])) outside_source_domain = outside_source_domain | ~tmp_inside if np.any(outside_source_domain): if not np.ma.isMaskedArray(new_array): new_array = np.ma.array(new_array, mask=False) new_array[outside_source_domain] = np.ma.masked return new_array