Exemple #1
0
def test_target_aligned_pixels():
    """Issue 853 has been resolved"""
    with rasterio.open('tests/data/world.rgb.tif') as src:
        source = src.read(1)
        profile = src.profile.copy()

    dst_crs = {'init': 'epsg:3857'}

    with rasterio.Env(CHECK_WITH_INVERT_PROJ=False):
        # Calculate the ideal dimensions and transformation in the new crs
        dst_affine, dst_width, dst_height = calculate_default_transform(
            src.crs, dst_crs, src.width, src.height, *src.bounds)

        dst_affine, dst_width, dst_height = aligned_target(
            dst_affine, dst_width, dst_height, 100000.0)

        profile['height'] = dst_height
        profile['width'] = dst_width

        out = np.empty(shape=(dst_height, dst_width), dtype=np.uint8)

        reproject(source,
                  out,
                  src_transform=src.transform,
                  src_crs=src.crs,
                  dst_transform=dst_affine,
                  dst_crs=dst_crs,
                  resampling=Resampling.nearest)

        # Check that there is no black borders
        assert out[:, 0].all()
        assert out[:, -1].all()
        assert out[0, :].all()
        assert out[-1, :].all()
def create_grid(geom, dst_crs, dst_res):
    """Create a raster grid for a given area of interest.

    Parameters
    ----------
    geom : shapely geometry
        Area of interest.
    dst_crs : CRS
        Target CRS as a rasterio CRS object.
    dst_res : int or float
        Spatial resolution (in `dst_crs` units).

    Returns
    -------
    transform: Affine
        Output affine transform object.
    shape : tuple of int
        Output shape (height, width).
    bounds : tuple of float
        Output bounds.
    """
    bounds = transform_bounds(CRS.from_epsg(4326), dst_crs, *geom.bounds)
    xmin, ymin, xmax, ymax = bounds
    transform = from_origin(xmin, ymax, dst_res, dst_res)
    ncols = (xmax - xmin) / dst_res
    nrows = (ymax - ymin) / dst_res
    transform, ncols, nrows = aligned_target(transform, ncols, nrows, dst_res)
    log.info(f"Created raster grid of shape ({nrows}, {ncols}).")
    return transform, (nrows, ncols), bounds
Exemple #3
0
 def match_pixel_size(self, xres, yres):
     dst_transform, dst_width, dst_height = aligned_target(self.raster.transform, self.raster.width, self.raster.height, (xres, yres))
     with MemoryFile() as mem_file:
         aligned = mem_file.open(driver = 'GTiff', height = dst_height, width = dst_width, count = self.raster.count, dtype = self.raster.dtypes[0], crs = self.raster.crs, transform = dst_transform, nodata = self.raster.nodata)
         for band in range(1, self.raster.count + 1):
             reproject(rio.band(self.raster, band), rio.band(aligned, band))
         self.raster = aligned
Exemple #4
0
def test_target_aligned_pixels():
    """Issue 853 has been resolved"""
    with rasterio.open('tests/data/world.rgb.tif') as src:
        source = src.read(1)
        profile = src.profile.copy()

    dst_crs = {'init': 'epsg:3857'}

    with rasterio.Env(CHECK_WITH_INVERT_PROJ=False):
        # Calculate the ideal dimensions and transformation in the new crs
        dst_affine, dst_width, dst_height = calculate_default_transform(
            src.crs, dst_crs, src.width, src.height, *src.bounds)

        dst_affine, dst_width, dst_height = aligned_target(dst_affine, dst_width, dst_height, 100000.0)

        profile['height'] = dst_height
        profile['width'] = dst_width

        out = np.empty(shape=(dst_height, dst_width), dtype=np.uint8)

        reproject(
            source,
            out,
            src_transform=src.transform,
            src_crs=src.crs,
            dst_transform=dst_affine,
            dst_crs=dst_crs,
            resampling=Resampling.nearest)

        # Check that there is no black borders
        assert out[:, 0].all()
        assert out[:, -1].all()
        assert out[0, :].all()
        assert out[-1, :].all()
Exemple #5
0
def generate_raster(shapes, geobox):
    yt, xt = geobox.shape
    transform, width, height = calculate_default_transform(
        geobox.crs, geobox.crs.crs_str, xt, yt, *geobox.extent.boundingbox)
    transform, width, height = aligned_target(transform, xt, yt, resolution=(25, 25))
    target_ds = features.rasterize(shapes,
        (yt, xt), fill=0, transform=transform, all_touched=False)
    return target_ds
Exemple #6
0
def read_buffered_window(uri, metadata, px_buffer, cell, resampling):
    """
    Read a region from a source image, reprojecting in the process.

    Given the metadata for a layer (including a layout), a cell id, and a buffer
    size, read a window from the supplied image while reprojecting the source image
    to the CRS specified by the metadata.

    In concept, we specify a grid in the target CRS using the cell and resolution in
    the metadata, padding with px_buffer additional cells on all sides of the cell.
    For each cell center, we read from the source image after back-projecting the
    target location to the source image's coordinate system and applying the chosen
    resampling kernel.

    Args:
        uri (str): The source image URI; any scheme interpretable by GDAL is allowed
        metadata (``gps.Metadata``): The source layer metadata
        px_buffer (int): The number of pixels worth of padding that should be added
            to the extent to be read and added to the tile.
        cell (``gps.SpatialKey`` or ``gps.SpaceTimeKey``): The key of the target
            region
        resampling (``rasterio.warp.Resampling``): The resampling method to be used
            while reading from the source image

    Returns:
        ``gps.Tile``
    """
    if ("GDAL_DATA" not in os.environ) and '_GDAL_DATA' in vars():
        os.environ["GDAL_DATA"] = _GDAL_DATA

    layout = metadata.layout_definition
    dest_bounds = buffered_cell_extent(layout, px_buffer, cell)
    res = cell_size(metadata.layout_definition)
    dest_transform = Affine(res[0], 0, dest_bounds.xmin, 0, -res[1],
                            dest_bounds.ymax)
    dest_width = max(int(ceil((dest_bounds.xmax - dest_bounds.xmin) / res[0])),
                     1)
    dest_height = max(
        int(ceil((dest_bounds.ymax - dest_bounds.ymin) / res[1])), 1)
    dest_transform, dest_width, dest_height = rwarp.aligned_target(
        dest_transform, dest_width, dest_height, res)

    with rstr.open(uri) as src:
        tile = np.zeros((src.count, layout.tileLayout.tileCols + 2 * px_buffer,
                         layout.tileLayout.tileRows + 2 * px_buffer))
        rwarp.reproject(source=rstr.band(src, list(range(1, src.count + 1))),
                        destination=tile,
                        src_transform=src.transform,
                        src_crs=src.crs,
                        src_nodata=src.nodata,
                        dst_transform=dest_transform,
                        dst_crs=CRS.from_epsg(4326),
                        dst_nodata=src.nodata,
                        resampling=resampling,
                        num_threads=1)

    return gps.Tile.from_numpy_array(tile)
Exemple #7
0
def reproject_profile_to_new_crs(src_profile, dst_crs, resolution=None):
    reprojected_profile = src_profile.copy()
    bounds_dict = get_bounds_dict(src_profile)

    src_crs = src_profile['crs']
    dst_transform, dst_width, dst_height = calculate_default_transform(
        src_crs, dst_crs, src_profile['width'], src_profile['height'],
        **bounds_dict)

    if resolution is not None:
        dst_transform, dst_width, dst_height = aligned_target(
            dst_transform, dst_width, dst_height, resolution)
    reprojected_profile.update({
        'crs': dst_crs,
        'transform': dst_transform,
        'width': dst_width,
        'height': dst_height,
    })
    return reprojected_profile
Exemple #8
0
def reproject_profile_to_new_crs(src_profile: dict,
                                 dst_crs: str,
                                 target_resolution: Union[float, int] = None)\
                                         -> dict:
    """
    Create a new profile into a new CRS based on a dst_crs. May specify
    resolution.

    Parameters
    ----------
    src_profile : dict
        Source rasterio profile.
    dst_crs : str
        Destination CRS, as specified by rasterio.
    target_resolution : Union[float, int]
        Target resolution

    Returns
    -------
    dict:
        Rasterio profile of new CRS
    """
    reprojected_profile = src_profile.copy()
    bounds_dict = get_bounds_dict(src_profile)

    src_crs = src_profile['crs']
    w, h = src_profile['width'], src_profile['height']
    dst_trans, dst_w, dst_h = calculate_default_transform(
        src_crs, dst_crs, w, h, **bounds_dict)

    if target_resolution is not None:
        tr = target_resolution
        dst_trans, dst_w, dst_h = aligned_target(dst_trans, dst_w, dst_h, tr)
    reprojected_profile.update({
        'crs': dst_crs,
        'transform': dst_trans,
        'width': dst_w,
        'height': dst_h,
    })
    return reprojected_profile
Exemple #9
0
def align_bounds(minx, miny, maxx, maxy, res):
    """
    Aligns bounds to resolution

    Args:
        minx (float)
        miny (float)
        maxx (float)
        maxy (float)
        res (tuple)

    Returns:
        ``affine.Affine``, ``int``, ``int``
    """

    xres, yres = res

    new_height = (maxy - miny) / yres
    new_width = (maxx - minx) / xres

    new_transform = Affine(xres, 0.0, minx, 0.0, -yres, maxy)

    return aligned_target(new_transform, new_width, new_height, res)
Exemple #10
0
    def polygon_to_array(self,
                         polygon,
                         col=None,
                         data=None,
                         cellx=None,
                         celly=None,
                         band_name=None,
                         row_chunks=512,
                         col_chunks=512,
                         src_res=None,
                         fill=0,
                         default_value=1,
                         all_touched=True,
                         dtype='uint8',
                         sindex=None,
                         tap=False,
                         bounds_by='intersection'):
        """
        Converts a polygon geometry to an ``xarray.DataArray``.

        Args:
            polygon (GeoDataFrame | str): The ``geopandas.DataFrame`` or file with polygon geometry.
            col (Optional[str]): The column in ``polygon`` you want to assign values from.
                If not set, creates a binary raster.
            data (Optional[DataArray]): An ``xarray.DataArray`` to use as a reference for rasterizing.
            cellx (Optional[float]): The output cell x size.
            celly (Optional[float]): The output cell y size.
            band_name (Optional[list]): The ``xarray.DataArray`` band name.
            row_chunks (Optional[int]): The ``dask`` row chunk size.
            col_chunks (Optional[int]): The ``dask`` column chunk size.
            src_res (Optional[tuple]: A source resolution to align to.
            fill (Optional[int]): Used as fill value for all areas not covered by input geometries
                to ``rasterio.features.rasterize``.
            default_value (Optional[int]): Used as value for all geometries, if not provided in shapes
                to ``rasterio.features.rasterize``.
            all_touched (Optional[bool]): If True, all pixels touched by geometries will be burned in.
                If false, only pixels whose center is within the polygon or that are selected by Bresenham’s line
                algorithm will be burned in. The ``all_touched`` value for ``rasterio.features.rasterize``.
            dtype (Optional[rasterio | numpy data type]): The output data type for ``rasterio.features.rasterize``.
            sindex (Optional[object]): An instanced of ``geopandas.GeoDataFrame.sindex``.
            tap (Optional[bool]): Whether to target align pixels.
            bounds_by (Optional[str]): How to concatenate the output extent. Choices are ['intersection', 'union', 'reference'].

                * reference: Use the bounds of the reference image
                * intersection: Use the intersection (i.e., minimum extent) of all the image bounds
                * union: Use the union (i.e., maximum extent) of all the image bounds

        Returns:
            ``xarray.DataArray``

        Example:
            >>> import geowombat as gw
            >>> import geopandas as gpd
            >>>
            >>> df = gpd.read_file('polygons.gpkg')
            >>>
            >>> # 100x100 cell size
            >>> data = gw.polygon_to_array(df, 100.0, 100.0)
            >>>
            >>> # Align to an existing image
            >>> with gw.open('image.tif') as src:
            >>>     data = gw.polygon_to_array(df, data=src)
        """

        if not band_name:
            band_name = [1]

        if isinstance(polygon, gpd.GeoDataFrame):
            dataframe = polygon
        else:

            if os.path.isfile(polygon):
                dataframe = gpd.read_file(polygon)
            else:
                logger.exception('  The polygon file does not exist.')
                raise OSError

        ref_kwargs = {
            'bounds': None,
            'crs': None,
            'res': None,
            'tap': tap,
            'tac': None
        }

        if config['with_config'] and not isinstance(data, xr.DataArray):

            ref_kwargs = _check_config_globals(
                data.filename if isinstance(data, xr.DataArray) else None,
                bounds_by, ref_kwargs)

        if isinstance(data, xr.DataArray):

            if dataframe.crs != data.crs:

                # Transform the geometry
                dataframe = dataframe.to_crs(data.crs)

            if not sindex:

                # Get the R-tree spatial index
                sindex = dataframe.sindex

            # Get intersecting features
            int_idx = sorted(
                list(
                    sindex.intersection(
                        tuple(data.gw.geodataframe.total_bounds.flatten()))))

            if not int_idx:

                return self.dask_to_xarray(
                    data,
                    da.zeros(
                        (1, data.gw.nrows, data.gw.ncols),
                        chunks=(1, data.gw.row_chunks, data.gw.col_chunks),
                        dtype=data.dtype.name),
                    band_names=band_name)

            # Subset to the intersecting features
            dataframe = dataframe.iloc[int_idx]

            # Clip the geometry
            dataframe = gpd.clip(dataframe, data.gw.geodataframe)

            if dataframe.empty:

                return self.dask_to_xarray(
                    data,
                    da.zeros(
                        (1, data.gw.nrows, data.gw.ncols),
                        chunks=(1, data.gw.row_chunks, data.gw.col_chunks),
                        dtype=data.dtype.name),
                    band_names=band_name)

            cellx = data.gw.cellx
            celly = data.gw.celly
            row_chunks = data.gw.row_chunks
            col_chunks = data.gw.col_chunks
            src_res = None

            if ref_kwargs['bounds']:

                left, bottom, right, top = ref_kwargs['bounds']

                if 'res' in ref_kwargs and ref_kwargs['res'] is not None:

                    if isinstance(ref_kwargs['res'], tuple) or isinstance(
                            ref_kwargs['res'], list):
                        cellx, celly = ref_kwargs['res']
                    elif isinstance(ref_kwargs['res'], int) or isinstance(
                            ref_kwargs['res'], float):
                        cellx = ref_kwargs['res']
                        celly = ref_kwargs['res']
                    else:
                        logger.exception(
                            'The reference resolution must be a tuple, int, or float. Is type %s'
                            % (type(ref_kwargs['res'])))
                        raise TypeError

            else:
                left, bottom, right, top = data.gw.bounds

        else:

            if ref_kwargs['bounds']:

                left, bottom, right, top = ref_kwargs['bounds']

                if 'res' in ref_kwargs and ref_kwargs['res'] is not None:

                    if isinstance(ref_kwargs['res'], tuple) or isinstance(
                            ref_kwargs['res'], list):
                        cellx, celly = ref_kwargs['res']
                    elif isinstance(ref_kwargs['res'], int) or isinstance(
                            ref_kwargs['res'], float):
                        cellx = ref_kwargs['res']
                        celly = ref_kwargs['res']
                    else:
                        logger.exception(
                            'The reference resolution must be a tuple, int, or float. Is type %s'
                            % (type(ref_kwargs['res'])))
                        raise TypeError

            else:
                left, bottom, right, top = dataframe.total_bounds.flatten(
                ).tolist()

        dst_height = int((top - bottom) / abs(celly))
        dst_width = int((right - left) / abs(cellx))

        dst_transform = Affine(cellx, 0.0, left, 0.0, -celly, top)

        if src_res:

            dst_transform = aligned_target(dst_transform, dst_width,
                                           dst_height, src_res)[0]

            left = dst_transform[2]
            top = dst_transform[5]

            dst_transform = Affine(cellx, 0.0, left, 0.0, -celly, top)

        if col:
            shapes = (
                (geom, value)
                for geom, value in zip(dataframe.geometry, dataframe[col]))
        else:
            shapes = dataframe.geometry.values

        varray = rasterize(shapes,
                           out_shape=(dst_height, dst_width),
                           transform=dst_transform,
                           fill=fill,
                           default_value=default_value,
                           all_touched=all_touched,
                           dtype=dtype)

        cellxh = abs(cellx) / 2.0
        cellyh = abs(celly) / 2.0

        if isinstance(data, xr.DataArray):

            # Ensure the coordinates align
            xcoords = data.x.values
            ycoords = data.y.values

        else:

            xcoords = np.arange(left + cellxh,
                                left + cellxh + dst_width * abs(cellx), cellx)
            ycoords = np.arange(top - cellyh,
                                top - cellyh - dst_height * abs(celly), -celly)

        if xcoords.shape[0] > dst_width:
            xcoords = xcoords[:dst_width]

        if ycoords.shape[0] > dst_height:
            ycoords = ycoords[:dst_height]

        attrs = {
            'transform': dst_transform[:6],
            'crs': dataframe.crs,
            'res': (cellx, celly),
            'is_tiled': 1
        }

        return xr.DataArray(data=da.from_array(varray[np.newaxis, :, :],
                                               chunks=(1, row_chunks,
                                                       col_chunks)),
                            coords={
                                'band': band_name,
                                'y': ycoords,
                                'x': xcoords
                            },
                            dims=('band', 'y', 'x'),
                            attrs=attrs)
Exemple #11
0
def warp(ctx, files, output, driver, like, dst_crs, dimensions, src_bounds,
         dst_bounds, res, resampling, src_nodata, dst_nodata, threads,
         check_invert_proj, overwrite, creation_options,
         target_aligned_pixels):
    """
    Warp a raster dataset.

    If a template raster is provided using the --like option, the
    coordinate reference system, affine transform, and dimensions of
    that raster will be used for the output.  In this case --dst-crs,
    --bounds, --res, and --dimensions options are not applicable and
    an exception will be raised.

    \b
        $ rio warp input.tif output.tif --like template.tif

    The output coordinate reference system may be either a PROJ.4 or
    EPSG:nnnn string,

    \b
        --dst-crs EPSG:4326
        --dst-crs '+proj=longlat +ellps=WGS84 +datum=WGS84'

    or a JSON text-encoded PROJ.4 object.

    \b
        --dst-crs '{"proj": "utm", "zone": 18, ...}'

    If --dimensions are provided, --res and --bounds are not applicable and an
    exception will be raised.
    Resolution is calculated based on the relationship between the
    raster bounds in the target coordinate system and the dimensions,
    and may produce rectangular rather than square pixels.

    \b
        $ rio warp input.tif output.tif --dimensions 100 200 \\
        > --dst-crs EPSG:4326

    If --bounds are provided, --res is required if --dst-crs is provided
    (defaults to source raster resolution otherwise).

    \b
        $ rio warp input.tif output.tif \\
        > --bounds -78 22 -76 24 --res 0.1 --dst-crs EPSG:4326

    """
    output, files = resolve_inout(
        files=files, output=output, overwrite=overwrite)

    resampling = Resampling[resampling]  # get integer code for method

    if not len(res):
        # Click sets this as an empty tuple if not provided
        res = None
    else:
        # Expand one value to two if needed
        res = (res[0], res[0]) if len(res) == 1 else res

    if target_aligned_pixels:
        if not res:
            raise click.BadParameter(
                '--target-aligned-pixels requires a specified resolution')
        if src_bounds or dst_bounds:
            raise click.BadParameter(
                '--target-aligned-pixels cannot be used with '
                '--src-bounds or --dst-bounds')

    # Check invalid parameter combinations
    if like:
        invalid_combos = (dimensions, dst_bounds, dst_crs, res)
        if any(p for p in invalid_combos if p is not None):
            raise click.BadParameter(
                "--like cannot be used with any of --dimensions, --bounds, "
                "--dst-crs, or --res")

    elif dimensions:
        invalid_combos = (dst_bounds, res)
        if any(p for p in invalid_combos if p is not None):
            raise click.BadParameter(
                "--dimensions cannot be used with --bounds or --res")

    with ctx.obj['env']:
        setenv(CHECK_WITH_INVERT_PROJ=check_invert_proj)

        with rasterio.open(files[0]) as src:
            l, b, r, t = src.bounds
            out_kwargs = src.profile.copy()
            out_kwargs['driver'] = driver

            # Sort out the bounds options.
            if src_bounds and dst_bounds:
                raise click.BadParameter(
                    "--src-bounds and destination --bounds may not be "
                    "specified simultaneously.")

            if like:
                with rasterio.open(like) as template_ds:
                    dst_crs = template_ds.crs
                    dst_transform = template_ds.transform
                    dst_height = template_ds.height
                    dst_width = template_ds.width

            elif dst_crs is not None:
                try:
                    dst_crs = CRS.from_string(dst_crs)
                except ValueError as err:
                    raise click.BadParameter(
                        str(err), param='dst_crs', param_hint='dst_crs')

                if dimensions:
                    # Calculate resolution appropriate for dimensions
                    # in target.
                    dst_width, dst_height = dimensions
                    try:
                        xmin, ymin, xmax, ymax = transform_bounds(
                            src.crs, dst_crs, *src.bounds)
                    except CRSError as err:
                        raise click.BadParameter(
                            str(err), param='dst_crs', param_hint='dst_crs')
                    dst_transform = Affine(
                        (xmax - xmin) / float(dst_width),
                        0, xmin, 0,
                        (ymin - ymax) / float(dst_height),
                        ymax
                    )

                elif src_bounds or dst_bounds:
                    if not res:
                        raise click.BadParameter(
                            "Required when using --bounds.",
                            param='res', param_hint='res')

                    if src_bounds:
                        try:
                            xmin, ymin, xmax, ymax = transform_bounds(
                                src.crs, dst_crs, *src_bounds)
                        except CRSError as err:
                            raise click.BadParameter(
                                str(err), param='dst_crs',
                                param_hint='dst_crs')
                    else:
                        xmin, ymin, xmax, ymax = dst_bounds

                    dst_transform = Affine(res[0], 0, xmin, 0, -res[1], ymax)
                    dst_width = max(int(ceil((xmax - xmin) / res[0])), 1)
                    dst_height = max(int(ceil((ymax - ymin) / res[1])), 1)

                else:
                    try:
                        if src.transform.is_identity and src.gcps:
                            src_crs = src.gcps[1]
                            kwargs = {'gcps': src.gcps[0]}
                        else:
                            src_crs = src.crs
                            kwargs = src.bounds._asdict()
                        dst_transform, dst_width, dst_height = calcdt(
                            src_crs, dst_crs, src.width, src.height,
                            resolution=res, **kwargs)
                    except CRSError as err:
                        raise click.BadParameter(
                            str(err), param='dst_crs', param_hint='dst_crs')

            elif dimensions:
                # Same projection, different dimensions, calculate resolution.
                dst_crs = src.crs
                dst_width, dst_height = dimensions
                dst_transform = Affine(
                    (r - l) / float(dst_width),
                    0, l, 0,
                    (b - t) / float(dst_height),
                    t
                )

            elif src_bounds or dst_bounds:
                # Same projection, different dimensions and possibly
                # different resolution.
                if not res:
                    res = (src.transform.a, -src.transform.e)

                dst_crs = src.crs
                xmin, ymin, xmax, ymax = (src_bounds or dst_bounds)
                dst_transform = Affine(res[0], 0, xmin, 0, -res[1], ymax)
                dst_width = max(int(ceil((xmax - xmin) / res[0])), 1)
                dst_height = max(int(ceil((ymax - ymin) / res[1])), 1)

            elif res:
                # Same projection, different resolution.
                dst_crs = src.crs
                dst_transform = Affine(res[0], 0, l, 0, -res[1], t)
                dst_width = max(int(ceil((r - l) / res[0])), 1)
                dst_height = max(int(ceil((t - b) / res[1])), 1)

            else:
                dst_crs = src.crs
                dst_transform = src.transform
                dst_width = src.width
                dst_height = src.height

            if target_aligned_pixels:
                dst_transform, dst_width, dst_height = aligned_target(dst_transform, dst_width, dst_height, res)

            # If src_nodata is not None, update the dst metadata NODATA
            # value to src_nodata (will be overridden by dst_nodata if it is not None
            if src_nodata is not None:
                # Update the dst nodata value
                out_kwargs.update({
                    'nodata': src_nodata
                })

            # Validate a manually set destination NODATA value
            # against the input datatype.
            if dst_nodata is not None:
                if src_nodata is None and src.meta['nodata'] is None:
                    raise click.BadParameter(
                        "--src-nodata must be provided because dst-nodata is not None")
                else:
                    # Update the dst nodata value
                    out_kwargs.update({'nodata': dst_nodata})

            # When the bounds option is misused, extreme values of
            # destination width and height may result.
            if (dst_width < 0 or dst_height < 0 or
                    dst_width > MAX_OUTPUT_WIDTH or
                    dst_height > MAX_OUTPUT_HEIGHT):
                raise click.BadParameter(
                    "Invalid output dimensions: {0}.".format(
                        (dst_width, dst_height)))

            out_kwargs.update({
                'crs': dst_crs,
                'transform': dst_transform,
                'width': dst_width,
                'height': dst_height
            })

            # Adjust block size if necessary.
            if ('blockxsize' in out_kwargs and
                    dst_width < out_kwargs['blockxsize']):
                del out_kwargs['blockxsize']
            if ('blockysize' in out_kwargs and
                    dst_height < out_kwargs['blockysize']):
                del out_kwargs['blockysize']

            out_kwargs.update(**creation_options)

            with rasterio.open(output, 'w', **out_kwargs) as dst:
                reproject(
                    source=rasterio.band(src, list(range(1, src.count + 1))),
                    destination=rasterio.band(
                        dst, list(range(1, src.count + 1))),
                    src_transform=src.transform,
                    src_crs=src.crs,
                    src_nodata=src_nodata,
                    dst_transform=out_kwargs['transform'],
                    dst_crs=out_kwargs['crs'],
                    dst_nodata=dst_nodata,
                    resampling=resampling,
                    num_threads=threads)
Exemple #12
0
def warp(ctx, files, output, driver, like, dst_crs, dimensions, src_bounds,
         dst_bounds, res, resampling, src_nodata, dst_nodata, threads,
         check_invert_proj, overwrite, creation_options,
         target_aligned_pixels):
    """
    Warp a raster dataset.

    If a template raster is provided using the --like option, the
    coordinate reference system, affine transform, and dimensions of
    that raster will be used for the output.  In this case --dst-crs,
    --bounds, --res, and --dimensions options are not applicable and
    an exception will be raised.

    \b
        $ rio warp input.tif output.tif --like template.tif

    The output coordinate reference system may be either a PROJ.4 or
    EPSG:nnnn string,

    \b
        --dst-crs EPSG:4326
        --dst-crs '+proj=longlat +ellps=WGS84 +datum=WGS84'

    or a JSON text-encoded PROJ.4 object.

    \b
        --dst-crs '{"proj": "utm", "zone": 18, ...}'

    If --dimensions are provided, --res and --bounds are not applicable and an
    exception will be raised.
    Resolution is calculated based on the relationship between the
    raster bounds in the target coordinate system and the dimensions,
    and may produce rectangular rather than square pixels.

    \b
        $ rio warp input.tif output.tif --dimensions 100 200 \\
        > --dst-crs EPSG:4326

    If --bounds are provided, --res is required if --dst-crs is provided
    (defaults to source raster resolution otherwise).

    \b
        $ rio warp input.tif output.tif \\
        > --bounds -78 22 -76 24 --res 0.1 --dst-crs EPSG:4326

    """
    output, files = resolve_inout(files=files,
                                  output=output,
                                  overwrite=overwrite)

    resampling = Resampling[resampling]  # get integer code for method

    if not len(res):
        # Click sets this as an empty tuple if not provided
        res = None
    else:
        # Expand one value to two if needed
        res = (res[0], res[0]) if len(res) == 1 else res

    if target_aligned_pixels:
        if not res:
            raise click.BadParameter(
                '--target-aligned-pixels requires a specified resolution')
        if src_bounds or dst_bounds:
            raise click.BadParameter(
                '--target-aligned-pixels cannot be used with '
                '--src-bounds or --dst-bounds')

    # Check invalid parameter combinations
    if like:
        invalid_combos = (dimensions, dst_bounds, dst_crs, res)
        if any(p for p in invalid_combos if p is not None):
            raise click.BadParameter(
                "--like cannot be used with any of --dimensions, --bounds, "
                "--dst-crs, or --res")

    elif dimensions:
        invalid_combos = (dst_bounds, res)
        if any(p for p in invalid_combos if p is not None):
            raise click.BadParameter(
                "--dimensions cannot be used with --bounds or --res")

    with ctx.obj['env']:
        setenv(CHECK_WITH_INVERT_PROJ=check_invert_proj)

        with rasterio.open(files[0]) as src:
            l, b, r, t = src.bounds
            out_kwargs = src.profile
            out_kwargs.pop("driver", None)
            if driver:
                out_kwargs["driver"] = driver

            # Sort out the bounds options.
            if src_bounds and dst_bounds:
                raise click.BadParameter(
                    "--src-bounds and destination --bounds may not be "
                    "specified simultaneously.")

            if like:
                with rasterio.open(like) as template_ds:
                    dst_crs = template_ds.crs
                    dst_transform = template_ds.transform
                    dst_height = template_ds.height
                    dst_width = template_ds.width

            elif dst_crs is not None:
                try:
                    dst_crs = CRS.from_string(dst_crs)
                except ValueError as err:
                    raise click.BadParameter(str(err),
                                             param='dst_crs',
                                             param_hint='dst_crs')

                if dimensions:
                    # Calculate resolution appropriate for dimensions
                    # in target.
                    dst_width, dst_height = dimensions
                    bounds = src_bounds or src.bounds
                    try:
                        xmin, ymin, xmax, ymax = transform_bounds(
                            src.crs, dst_crs, *bounds)
                    except CRSError as err:
                        raise click.BadParameter(str(err),
                                                 param='dst_crs',
                                                 param_hint='dst_crs')
                    dst_transform = Affine(
                        (xmax - xmin) / float(dst_width), 0, xmin, 0,
                        (ymin - ymax) / float(dst_height), ymax)

                elif src_bounds or dst_bounds:
                    if not res:
                        raise click.BadParameter(
                            "Required when using --bounds.",
                            param='res',
                            param_hint='res')

                    if src_bounds:
                        try:
                            xmin, ymin, xmax, ymax = transform_bounds(
                                src.crs, dst_crs, *src_bounds)
                        except CRSError as err:
                            raise click.BadParameter(str(err),
                                                     param='dst_crs',
                                                     param_hint='dst_crs')
                    else:
                        xmin, ymin, xmax, ymax = dst_bounds

                    dst_transform = Affine(res[0], 0, xmin, 0, -res[1], ymax)
                    dst_width = max(int(ceil((xmax - xmin) / res[0])), 1)
                    dst_height = max(int(ceil((ymax - ymin) / res[1])), 1)

                else:
                    try:
                        if src.transform.is_identity and src.gcps:
                            src_crs = src.gcps[1]
                            kwargs = {'gcps': src.gcps[0]}
                        else:
                            src_crs = src.crs
                            kwargs = src.bounds._asdict()
                        dst_transform, dst_width, dst_height = calcdt(
                            src_crs,
                            dst_crs,
                            src.width,
                            src.height,
                            resolution=res,
                            **kwargs)
                    except CRSError as err:
                        raise click.BadParameter(str(err),
                                                 param='dst_crs',
                                                 param_hint='dst_crs')

            elif dimensions:
                # Same projection, different dimensions, calculate resolution.
                dst_crs = src.crs
                dst_width, dst_height = dimensions
                l, b, r, t = src_bounds or (l, b, r, t)
                dst_transform = Affine((r - l) / float(dst_width), 0, l, 0,
                                       (b - t) / float(dst_height), t)

            elif src_bounds or dst_bounds:
                # Same projection, different dimensions and possibly
                # different resolution.
                if not res:
                    res = (src.transform.a, -src.transform.e)

                dst_crs = src.crs
                xmin, ymin, xmax, ymax = (src_bounds or dst_bounds)
                dst_transform = Affine(res[0], 0, xmin, 0, -res[1], ymax)
                dst_width = max(int(ceil((xmax - xmin) / res[0])), 1)
                dst_height = max(int(ceil((ymax - ymin) / res[1])), 1)

            elif res:
                # Same projection, different resolution.
                dst_crs = src.crs
                dst_transform = Affine(res[0], 0, l, 0, -res[1], t)
                dst_width = max(int(ceil((r - l) / res[0])), 1)
                dst_height = max(int(ceil((t - b) / res[1])), 1)

            else:
                dst_crs = src.crs
                dst_transform = src.transform
                dst_width = src.width
                dst_height = src.height

            if target_aligned_pixels:
                dst_transform, dst_width, dst_height = aligned_target(
                    dst_transform, dst_width, dst_height, res)

            # If src_nodata is not None, update the dst metadata NODATA
            # value to src_nodata (will be overridden by dst_nodata if it is not None
            if src_nodata is not None:
                # Update the dst nodata value
                out_kwargs.update(nodata=src_nodata)

            # Validate a manually set destination NODATA value
            # against the input datatype.
            if dst_nodata is not None:
                if src_nodata is None and src.meta['nodata'] is None:
                    raise click.BadParameter(
                        "--src-nodata must be provided because dst-nodata is not None"
                    )
                else:
                    # Update the dst nodata value
                    out_kwargs.update(nodata=dst_nodata)

            # When the bounds option is misused, extreme values of
            # destination width and height may result.
            if (dst_width < 0 or dst_height < 0 or dst_width > MAX_OUTPUT_WIDTH
                    or dst_height > MAX_OUTPUT_HEIGHT):
                raise click.BadParameter(
                    "Invalid output dimensions: {0}.".format(
                        (dst_width, dst_height)))

            out_kwargs.update(crs=dst_crs,
                              transform=dst_transform,
                              width=dst_width,
                              height=dst_height)

            # Adjust block size if necessary.
            if "blockxsize" in out_kwargs and dst_width < int(
                    out_kwargs["blockxsize"]):
                del out_kwargs["blockxsize"]
                logger.warning(
                    "Blockxsize removed from creation options to accomodate small output width"
                )
            if "blockysize" in out_kwargs and dst_height < int(
                    out_kwargs["blockysize"]):
                del out_kwargs["blockysize"]
                logger.warning(
                    "Blockxsize removed from creation options to accomodate small output height"
                )

            out_kwargs.update(**creation_options)

            with rasterio.open(output, 'w', **out_kwargs) as dst:
                reproject(source=rasterio.band(src,
                                               list(range(1, src.count + 1))),
                          destination=rasterio.band(
                              dst, list(range(1, src.count + 1))),
                          src_transform=src.transform,
                          src_crs=src.crs,
                          src_nodata=src_nodata,
                          dst_transform=out_kwargs['transform'],
                          dst_crs=out_kwargs['crs'],
                          dst_nodata=dst_nodata,
                          resampling=resampling,
                          num_threads=threads)
Exemple #13
0
def calc_transform(src,
                   dst_crs=None,
                   resolution=None,
                   dimensions=None,
                   src_bounds=None,
                   dst_bounds=None,
                   target_aligned_pixels=False):
    """Output dimensions and transform for a reprojection.

    Parameters
    ------------
    src: rasterio.io.DatasetReader
        Data source.
    dst_crs: rasterio.crs.CRS, optional
        Target coordinate reference system.
    resolution: tuple (x resolution, y resolution) or float, optional
        Target resolution, in units of target coordinate reference
        system.
    dimensions: tuple (width, height), optional
        Output file size in pixels and lines.
    src_bounds: tuple (xmin, ymin, xmax, ymax), optional
        Georeferenced extent of output file from source bounds
        (in source georeferenced units).
    dst_bounds: tuple (xmin, ymin, xmax, ymax), optional
        Georeferenced extent of output file from destination bounds
        (in destination georeferenced units).
    target_aligned_pixels: bool, optional
        Align the output bounds based on the resolution.
        Default is `False`.

    Returns
    -------
    dst_crs: rasterio.crs.CRS
        Output crs
    transform: Affine
        Output affine transformation matrix
    width, height: int
        Output dimensions
    """
    if resolution is not None:
        if isinstance(resolution, (float, int)):
            resolution = (float(resolution), float(resolution))

    if target_aligned_pixels:
        if not resolution:
            raise ValueError(
                'target_aligned_pixels cannot be used without resolution')
        if src_bounds or dst_bounds:
            raise ValueError(
                'target_aligned_pixels cannot be used with src_bounds or dst_bounds'
            )

    elif dimensions:
        invalid_combos = (dst_bounds, resolution)
        if any(p for p in invalid_combos if p is not None):
            raise ValueError(
                'dimensions cannot be used with dst_bounds or resolution')

    if src_bounds and dst_bounds:
        raise ValueError(
            'src_bounds and dst_bounds may not be specified simultaneously')

    if dst_crs is not None:

        if dimensions:
            # Calculate resolution appropriate for dimensions
            # in target.
            dst_width, dst_height = dimensions
            bounds = src_bounds or src.bounds
            xmin, ymin, xmax, ymax = transform_bounds(src.crs, dst_crs,
                                                      *bounds)
            dst_transform = Affine((xmax - xmin) / float(dst_width), 0, xmin,
                                   0, (ymin - ymax) / float(dst_height), ymax)

        elif src_bounds or dst_bounds:
            if not resolution:
                raise ValueError(
                    'resolution is required when using src_bounds or dst_bounds'
                )

            if src_bounds:
                xmin, ymin, xmax, ymax = transform_bounds(
                    src.crs, dst_crs, *src_bounds)
            else:
                xmin, ymin, xmax, ymax = dst_bounds

            dst_transform = Affine(resolution[0], 0, xmin, 0, -resolution[1],
                                   ymax)
            dst_width = max(int(ceil((xmax - xmin) / resolution[0])), 1)
            dst_height = max(int(ceil((ymax - ymin) / resolution[1])), 1)

        else:
            if src.transform.is_identity and src.gcps:
                src_crs = src.gcps[1]
                kwargs = {'gcps': src.gcps[0]}
            else:
                src_crs = src.crs
                kwargs = src.bounds._asdict()
            dst_transform, dst_width, dst_height = calcdt(
                src_crs,
                dst_crs,
                src.width,
                src.height,
                resolution=resolution,
                **kwargs)

    elif dimensions:
        # Same projection, different dimensions, calculate resolution.
        dst_crs = src.crs
        dst_width, dst_height = dimensions
        l, b, r, t = src_bounds or src.bounds
        dst_transform = Affine((r - l) / float(dst_width), 0, l, 0,
                               (b - t) / float(dst_height), t)

    elif src_bounds or dst_bounds:
        # Same projection, different dimensions and possibly
        # different resolution.
        if not resolution:
            resolution = (src.transform.a, -src.transform.e)

        dst_crs = src.crs
        xmin, ymin, xmax, ymax = (src_bounds or dst_bounds)
        dst_transform = Affine(resolution[0], 0, xmin, 0, -resolution[1], ymax)
        dst_width = max(int(ceil((xmax - xmin) / resolution[0])), 1)
        dst_height = max(int(ceil((ymax - ymin) / resolution[1])), 1)

    elif resolution:
        # Same projection, different resolution.
        dst_crs = src.crs
        l, b, r, t = src.bounds
        dst_transform = Affine(resolution[0], 0, l, 0, -resolution[1], t)
        dst_width = max(int(ceil((r - l) / resolution[0])), 1)
        dst_height = max(int(ceil((t - b) / resolution[1])), 1)

    else:
        dst_crs = src.crs
        dst_transform = src.transform
        dst_width = src.width
        dst_height = src.height

    if target_aligned_pixels:
        dst_transform, dst_width, dst_height = aligned_target(
            dst_transform, dst_width, dst_height, resolution)

    return dst_crs, dst_transform, dst_width, dst_height
Exemple #14
0
def warp(filename,
         resampling='nearest',
         bounds=None,
         crs=None,
         res=None,
         nodata=0,
         warp_mem_limit=512,
         num_threads=1,
         tap=False):
    """
    Warps an image to a VRT object

    Args:
        filename (str): The input file name.
        resampling (Optional[str]): The resampling method. Choices are ['average', 'bilinear', 'cubic',
            'cubic_spline', 'gauss', 'lanczos', 'max', 'med', 'min', 'mode', 'nearest'].
        bounds (Optional[tuple]): The extent bounds to warp to.
        crs (Optional[object]): The CRS to warp to.
        res (Optional[tuple]): The cell resolution to warp to.
        nodata (Optional[int or float]): The 'no data' value.
        warp_mem_limit (Optional[int]): The memory limit (in MB) for the ``rasterio.vrt.WarpedVRT`` function.
        num_threads (Optional[int]): The number of warp worker threads.
        tap (Optional[bool]): Whether to target align pixels.

    Returns:
        ``rasterio.vrt.WarpedVRT``
    """

    with rio.open(filename) as src:

        if res:
            dst_res = res
        else:
            dst_res = src.res

        if crs:

            if isinstance(crs, CRS):
                dst_crs = crs
            elif isinstance(crs, int):
                dst_crs = CRS.from_epsg(crs)
            elif isinstance(crs, dict):
                dst_crs = CRS.from_dict(crs)
            elif isinstance(crs, str):

                if crs.startswith('+proj'):
                    dst_crs = CRS.from_proj4(crs)
                else:
                    dst_crs = CRS.from_string(crs)

            else:
                logger.exception('  The CRS was not understood.')

        else:
            dst_crs = src.crs

        dst_transform, dst_width, dst_height = calculate_default_transform(
            src.crs,
            dst_crs,
            src.width,
            src.height,
            left=src.bounds.left,
            bottom=src.bounds.bottom,
            right=src.bounds.right,
            top=src.bounds.top,
            resolution=dst_res)

        # Check if the data need to be subset
        if bounds and (bounds != src.bounds):

            if isinstance(bounds, str):

                if bounds.startswith('BoundingBox'):

                    bounds_str = bounds.replace('BoundingBox(', '').split(',')

                    for str_ in bounds_str:

                        if str_.strip().startswith('left='):
                            left_coord = float(
                                str_.strip().split('=')[1].replace(')', ''))
                        elif str_.strip().startswith('bottom='):
                            bottom_coord = float(
                                str_.strip().split('=')[1].replace(')', ''))
                        elif str_.strip().startswith('right='):
                            right_coord = float(
                                str_.strip().split('=')[1].replace(')', ''))
                        elif str_.strip().startswith('top='):
                            top_coord = float(
                                str_.strip().split('=')[1].replace(')', ''))

                    bounds = BoundingBox(left=left_coord,
                                         bottom=bottom_coord,
                                         right=right_coord,
                                         top=top_coord)

                else:
                    logger.exception('  The bounds were not accepted.')

            left, bottom, right, top = bounds

            # Keep the CRS but subset the data
            dst_transform, dst_width, dst_height = calculate_default_transform(
                dst_crs,
                dst_crs,
                dst_width,
                dst_height,
                left=left,
                bottom=bottom,
                right=right,
                top=top,
                resolution=dst_res)

            dst_width = int((right - left) / dst_res[0])
            dst_height = int((top - bottom) / dst_res[1])

        else:
            bounds = src.bounds

        # Do not warp if all the key metadata match the reference information
        if (src.bounds == bounds) and (src.res == dst_res) and (
                src.crs == dst_crs) and (src.width
                                         == dst_width) and (src.height
                                                            == dst_height):
            output = filename
        else:

            if tap:

                # Align the cells
                dst_transform, dst_width, dst_height = aligned_target(
                    dst_transform, dst_width, dst_height, dst_res)

            vrt_options = {
                'resampling': getattr(Resampling, resampling),
                'crs': dst_crs,
                'transform': dst_transform,
                'height': dst_height,
                'width': dst_width,
                'nodata': nodata,
                'warp_mem_limit': warp_mem_limit,
                'warp_extras': {
                    'multi': True,
                    'warp_option': 'NUM_THREADS={:d}'.format(num_threads)
                }
            }

            with WarpedVRT(src, **vrt_options) as vrt:
                output = vrt

    return output
Exemple #15
0
def warp(filename,
         resampling='nearest',
         bounds=None,
         crs=None,
         res=None,
         nodata=0,
         warp_mem_limit=512,
         num_threads=1,
         tap=False,
         tac=None):

    """
    Warps an image to a VRT object

    Args:
        filename (str): The input file name.
        resampling (Optional[str]): The resampling method. Choices are ['average', 'bilinear', 'cubic',
            'cubic_spline', 'gauss', 'lanczos', 'max', 'med', 'min', 'mode', 'nearest'].
        bounds (Optional[tuple]): The extent bounds to warp to.
        crs (Optional[``CRS`` | int | dict | str]): The CRS to warp to.
        res (Optional[tuple]): The cell resolution to warp to.
        nodata (Optional[int or float]): The 'no data' value.
        warp_mem_limit (Optional[int]): The memory limit (in MB) for the ``rasterio.vrt.WarpedVRT`` function.
        num_threads (Optional[int]): The number of warp worker threads.
        tap (Optional[bool]): Whether to target align pixels.
        tac (Optional[tuple]): Target aligned raster coordinates (x, y).

    Returns:
        ``rasterio.vrt.WarpedVRT``
    """

    if crs:
        dst_crs = check_crs(crs)
    else:
        dst_crs = check_file_crs(filename)

    src_crs = check_file_crs(filename)

    with rio.open(filename) as src:

        src_info = get_file_info(src)

        if res:
            dst_res = check_res(res)
        else:
            dst_res = src_info.src_res

        # Check if the data need to be subset
        if (bounds is None) or (tuple(bounds) == tuple(src_info.src_bounds)):

            if crs:

                left_coord, bottom_coord, right_coord, top_coord = transform_bounds(src_crs,
                                                                                    dst_crs,
                                                                                    src_info.src_bounds.left,
                                                                                    src_info.src_bounds.bottom,
                                                                                    src_info.src_bounds.right,
                                                                                    src_info.src_bounds.top,
                                                                                    densify_pts=21)

                dst_bounds = BoundingBox(left=left_coord,
                                         bottom=bottom_coord,
                                         right=right_coord,
                                         top=top_coord)

            else:
                dst_bounds = src_info.src_bounds

        else:

            # Ensure that the user bounds object is a ``BoundingBox``
            if isinstance(bounds, BoundingBox):
                dst_bounds = bounds
            elif isinstance(bounds, str):

                if bounds.startswith('BoundingBox'):
                    left_coord, bottom_coord, right_coord, top_coord = unpack_bounding_box(bounds)
                else:
                    logger.exception('  The bounds were not accepted.')
                    raise TypeError

                dst_bounds = BoundingBox(left=left_coord,
                                         bottom=bottom_coord,
                                         right=right_coord,
                                         top=top_coord)

            elif isinstance(bounds, tuple) or isinstance(bounds, list) or isinstance(bounds, np.ndarray):

                dst_bounds = BoundingBox(left=bounds[0],
                                         bottom=bounds[1],
                                         right=bounds[2],
                                         top=bounds[3])

            else:

                logger.exception(f'  The bounds type was not understood. Bounds should be given as a rasterio.coords.BoundingBox, tuple, or ndarray, not a {type(bounds)}.')
                raise TypeError

        dst_width = int((dst_bounds.right - dst_bounds.left) / dst_res[0])
        dst_height = int((dst_bounds.top - dst_bounds.bottom) / dst_res[1])

        # Do not warp if all the key metadata match the reference information
        if (tuple(src_info.src_bounds) == tuple(bounds)) and \
                (src_info.src_res == dst_res) and \
                (src_crs == dst_crs) and \
                (src_info.src_width == dst_width) and \
                (src_info.src_height == dst_height) and \
                ('.nc' not in filename.lower()):

            output = filename

        else:

            src_transform = Affine(src_info.src_res[0], 0.0, src_info.src_bounds.left, 0.0, -src_info.src_res[1], src_info.src_bounds.top)
            dst_transform = Affine(dst_res[0], 0.0, dst_bounds.left, 0.0, -dst_res[1], dst_bounds.top)

            if tac:

                # Align the cells to target coordinates
                tap_left = tac[0][np.abs(tac[0] - dst_bounds.left).argmin()]
                tap_top = tac[1][np.abs(tac[1] - dst_bounds.top).argmin()]

                dst_transform = Affine(dst_res[0], 0.0, tap_left, 0.0, -dst_res[1], tap_top)

            if tap:

                # Align the cells to the resolution
                dst_transform, dst_width, dst_height = aligned_target(dst_transform,
                                                                      dst_width,
                                                                      dst_height,
                                                                      dst_res)

            vrt_options = {'resampling': getattr(Resampling, resampling),
                           'src_crs': src_crs,
                           'crs': dst_crs,
                           'src_transform': src_transform,
                           'transform': dst_transform,
                           'height': dst_height,
                           'width': dst_width,
                           'nodata': nodata,
                           'warp_mem_limit': warp_mem_limit,
                           'warp_extras': {'multi': True,
                                           'warp_option': f'NUM_THREADS={num_threads}'}}

            with WarpedVRT(src, **vrt_options) as vrt:
                output = vrt

    return output
Exemple #16
0
def warp(filename,
         resampling='nearest',
         bounds=None,
         crs=None,
         res=None,
         nodata=0,
         warp_mem_limit=512,
         num_threads=1,
         tap=False,
         tac=None):

    """
    Warps an image to a VRT object

    Args:
        filename (str): The input file name.
        resampling (Optional[str]): The resampling method. Choices are ['average', 'bilinear', 'cubic',
            'cubic_spline', 'gauss', 'lanczos', 'max', 'med', 'min', 'mode', 'nearest'].
        bounds (Optional[tuple]): The extent bounds to warp to.
        crs (Optional[``CRS`` | int | dict | str]): The CRS to warp to.
        res (Optional[tuple]): The cell resolution to warp to.
        nodata (Optional[int or float]): The 'no data' value.
        warp_mem_limit (Optional[int]): The memory limit (in MB) for the ``rasterio.vrt.WarpedVRT`` function.
        num_threads (Optional[int]): The number of warp worker threads.
        tap (Optional[bool]): Whether to target align pixels.
        tac (Optional[tuple]): Target aligned raster coordinates (x, y).

    Returns:
        ``rasterio.vrt.WarpedVRT``
    """

    with rio.open(filename) as src:

        if res:
            dst_res = check_res(res)
        else:
            dst_res = src.res

        if crs:
            dst_crs = check_crs(crs)
        else:
            dst_crs = check_src_crs(src)

        # Check if the data need to be subset
        if bounds and (bounds != src.bounds):

            if isinstance(bounds, str):

                if bounds.startswith('BoundingBox'):
                    left_coord, bottom_coord, right_coord, top_coord = unpack_bounding_box(bounds)
                else:
                    logger.exception('  The bounds were not accepted.')

                dst_bounds = BoundingBox(left=left_coord,
                                         bottom=bottom_coord,
                                         right=right_coord,
                                         top=top_coord)

            else:

                dst_bounds = BoundingBox(left=bounds[0],
                                         bottom=bounds[1],
                                         right=bounds[2],
                                         top=bounds[3])

        else:
            dst_bounds = src.bounds

        dst_width = int((dst_bounds.right - dst_bounds.left) / dst_res[0])
        dst_height = int((dst_bounds.top - dst_bounds.bottom) / dst_res[1])

        # Do not warp if all the key metadata match the reference information
        if (src.bounds == bounds) and \
                (src.res == dst_res) and \
                (src.crs == dst_crs) and \
                (src.width == dst_width) and \
                (src.height == dst_height):

            output = filename

        else:

            dst_transform = Affine(dst_res[0], 0.0, dst_bounds.left, 0.0, -dst_res[1], dst_bounds.top)

            if tac:

                # Align the cells to target coordinates
                tap_left = tac[0][np.abs(tac[0] - dst_bounds.left).argmin()]
                tap_top = tac[1][np.abs(tac[1] - dst_bounds.top).argmin()]

                dst_transform = Affine(dst_res[0], 0.0, tap_left, 0.0, -dst_res[1], tap_top)

            if tap:

                # Align the cells to the resolution
                dst_transform, dst_width, dst_height = aligned_target(dst_transform,
                                                                      dst_width,
                                                                      dst_height,
                                                                      dst_res)

            vrt_options = {'resampling': getattr(Resampling, resampling),
                           'crs': dst_crs,
                           'transform': dst_transform,
                           'height': dst_height,
                           'width': dst_width,
                           'nodata': nodata,
                           'warp_mem_limit': warp_mem_limit,
                           'warp_extras': {'multi': True,
                                           'warp_option': 'NUM_THREADS={:d}'.format(num_threads)}}

            with WarpedVRT(src, **vrt_options) as vrt:
                output = vrt

    return output