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
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
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 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
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)
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
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
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)
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)
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)
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)
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
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
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
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