def from_bounds(left, bottom, right, top, transform, height=None, width=None, boundless=False, precision=6): """Get the window corresponding to the bounding coordinates. Parameters ---------- left : float Left (west) bounding coordinate bottom : float Bottom (south) bounding coordinate right : float Right (east) bounding coordinate top : float Top (north) bounding coordinate transform : Affine Affine transform matrix height : int Number of rows width : int Number of columns boundless : boolean, optional If boundless is False, window is limited to extent of this dataset. precision : int, optional float precision Returns ------- window: tuple ((row_start, row_stop), (col_start, col_stop)) corresponding to the bounding coordinates """ window_start = rowcol(transform, left, top, op=math.floor, precision=precision) window_stop = rowcol(transform, right, bottom, op=math.ceil, precision=precision) window = tuple(zip(window_start, window_stop)) if boundless: return window else: if None in (height, width): raise ValueError("Must supply height and width unless boundless") return crop(window, height, width)
def from_bounds(left, bottom, right, top, transform=None, height=None, width=None, precision=None): """Get the window corresponding to the bounding coordinates. Parameters ---------- left, bottom, right, top: float Left (west), bottom (south), right (east), and top (north) bounding coordinates. transform: Affine Affine transform matrix. height, width: int Number of rows and columns of the window. precision: int, optional Number of decimal points of precision when computing inverse transform. Returns ------- Window A new Window """ row_start, col_start = rowcol( transform, left, top, op=float, precision=precision) row_stop, col_stop = rowcol( transform, right, bottom, op=float, precision=precision) return Window.from_slices( (row_start, row_stop), (col_start, col_stop), height=height, width=width, boundless=True)
def test_rowcol(): with rasterio.open("tests/data/RGB.byte.tif", 'r') as src: aff = src.transform left, bottom, right, top = src.bounds assert rowcol(aff, left, top) == (0, 0) assert rowcol(aff, right, top) == (0, src.width) assert rowcol(aff, right, bottom) == (src.height, src.width) assert rowcol(aff, left, bottom) == (src.height, 0) assert rowcol(aff, 101985.0, 2826915.0) == (0, 0)
def from_bounds(left, bottom, right, top, transform=None, height=None, width=None, precision=None): """Get the window corresponding to the bounding coordinates. Parameters ---------- left, bottom, right, top: float Left (west), bottom (south), right (east), and top (north) bounding coordinates. transform: Affine, required Affine transform matrix. height, width: int, required Number of rows and columns of the window. precision: int, optional Number of decimal points of precision when computing inverse transform. Returns ------- Window A new Window. Raises ------ WindowError If a window can't be calculated. """ if not isinstance(transform, Affine): # TODO: RPCs? raise WindowError( "A transform object is required to calculate the window") row_start, col_start = rowcol(transform, left, top, op=float, precision=precision) row_stop, col_stop = rowcol(transform, right, bottom, op=float, precision=precision) return Window.from_slices((row_start, row_stop), (col_start, col_stop), height=height, width=width, boundless=True)
def discretize(p, t): if type(p) is Polygon: x, y = p.exterior.xy rc = rowcol(t, x, y) return Polygon(list(zip(rc[0], rc[1]))) if type(p) is MultiPolygon: polygons = [] for pp in p: x, y = pp.exterior.xy rc = rowcol(t, x, y) polygons.append(Polygon(list(zip(rc[0], rc[1])))) return MultiPolygon(polygons)
def from_bounds(left, bottom, right, top, transform, height=None, width=None, boundless=False, precision=6): """Get the window corresponding to the bounding coordinates. Parameters ---------- left, bottom, right, top : float Left (west), bottom (south), right (east), and top (north) bounding coordinates. transform : Affine Affine transform matrix. height, width : int Number of rows and columns of the window. boundless : boolean, optional If True, the output window's size may exceed the given height and width. precision : int, optional Number of decimal points of precision when computing inverse transform. Returns ------- Window A new Window """ window_start = rowcol(transform, left, top, op=math.floor, precision=precision) window_stop = rowcol(transform, right, bottom, op=math.ceil, precision=precision) window = Window.from_ranges(*tuple(zip(window_start, window_stop))) if boundless: return window else: if None in (height, width): raise ValueError("Must supply height and width unless boundless") return crop(window, height, width)
def from_bounds(left, bottom, right, top, transform=None, height=None, width=None, precision=6, **kwargs): """Get the window corresponding to the bounding coordinates. Parameters ---------- left, bottom, right, top: float Left (west), bottom (south), right (east), and top (north) bounding coordinates. transform: Affine Affine transform matrix. height, width: int Number of rows and columns of the window. precision: int, optional Number of decimal points of precision when computing inverse transform. kwargs: mapping Absorbs deprecated keyword args Returns ------- Window A new Window """ if 'boundless' in kwargs: warnings.warn("boundless keyword should not be used", RasterioDeprecationWarning) row_start, col_start = rowcol(transform, left, top, op=float, precision=precision) row_stop, col_stop = rowcol(transform, right, bottom, op=float, precision=precision) return Window.from_slices((row_start, row_stop), (col_start, col_stop), height=height, width=width, boundless=True)
def test_rowcol_gcps_rpcs(dataset, transform_attr, coords, expected): with rasterio.open(dataset, 'r') as src: transform = getattr(src, transform_attr) if transform_attr == 'gcps': transform = transform[0] for coord, truth in zip(coords, expected): assert rowcol(transform, *coord) == truth
def extract_training_data_over_centroids(centroid_shapefiles, image_stack, class_labels, image_meta, label_meta, save_directory, tile_size=224): assert (image_stack.shape[1] == class_labels.shape[0]) assert (image_stack.shape[2] == class_labels.shape[1]) x = [] y = [] for shapefile in centroid_shapefiles: shp = gpd.read_file(shapefile) shp = shp[shp.geometry.notnull()] crs = CRS(image_meta['crs']) shp = shp.to_crs(crs) features = get_features(shp) xs = [f['coordinates'][0] for f in features] ys = [f['coordinates'][1] for f in features] x.extend(xs) y.extend(ys) rows, cols = rowcol(image_meta['transform'], x, y) ts = tile_size // 2 for x, y in zip(rows, cols): image_tile = image_stack[:, x - ts:x + ts, y - ts:y + ts] class_label_tile = class_labels[x - ts:x + ts, y - ts:y + ts] save_image_tile_and_mask(save_directory, image_tile, class_label_tile, image_meta, label_meta)
def index(self, x, y, op=math.floor, precision=6): """ Returns the (row, col) index of the pixel containing (x, y) given a coordinate reference system. Use an epsilon, magnitude determined by the precision parameter and sign determined by the op function: positive for floor, negative for ceil. Parameters ---------- x : float x value in coordinate reference system y : float y value in coordinate reference system op : function, optional (default: math.floor) Function to convert fractional pixels to whole numbers (floor, ceiling, round) precision : int, optional (default: 6) Decimal places of precision in indexing, as in `round()`. Returns ------- tuple (row index, col index) """ return rowcol(self.transform, x, y, op=op, precision=precision)
def load_image_data(self): all_files = os.listdir(self.base_dir) all_files = [fn for fn in all_files if fn.endswith(self.file_suffix)] gdf = gps.read_file(self.annotation_file) frame_infos = [] total_annotations = 0 for i, fn in enumerate(all_files): annotations = [] image = rasterio.open(os.path.join(self.base_dir, fn)) for _, row in gdf[gdf.image_idx == fn].iterrows(): p = row['geometry'] coord = list(p.coords[0]) ann = rowcol(image.transform, coord[0], coord[1]) if not np.isnan(row['KRONE_DM']): tree_size = int(row['KRONE_DM']) else: tree_size = 4 # Convert from metres to pixels and convert to radius from diameter tree_size *= 2.5 tree_size = min(tree_size, 60) # Is the annotation correctly initialized??? annotations.append((ann[0], ann[1], int(tree_size))) #print((ann[0], ann[1], tree_size)) frame_info = FrameInfo(self.base_dir, fn, (0, 0, image.shape[0], image.shape[1]), annotations) frame_infos.append(frame_info) total_annotations += len(annotations) return frame_infos
def coords2Array(a, x, y): """ * convert between coords and array position * returns row,col (y,x) as expected by rasterio """ r, c = rowcol(a, x, y) return int(r), int(c)
def load_image_data(self): all_files = os.listdir(self.base_dir) all_files = [fn for fn in all_files if fn.endswith(self.file_suffix)] gdf = gps.read_file(self.annotation_file) frame_infos = [] total_annotations = 0 for i, fn in enumerate(all_files): annotations = [] image = rasterio.open(os.path.join(self.base_dir, fn)) for _, row in gdf[gdf.image_idx == fn].iterrows(): p = row['geometry'] coord = list(p.coords[0]) ann = rowcol(image.transform, coord[0], coord[1]) if not np.isnan(row['KRONE_DM']): tree_size = int(row['KRONE_DM']) else: tree_size = 10 tree_size = min(20, max(tree_size, 60)) annotations.append((ann[1], ann[0], tree_size)) frame_info = FrameInfo(self.base_dir, fn, (0, 0, image.shape[0], image.shape[1]), annotations) frame_infos.append(frame_info) total_annotations += len(annotations) return frame_infos
def test_xy_rowcol_inverse(): # TODO this is an ideal candiate for # property-based testing with hypothesis aff = Affine.identity() rows_cols = ([0, 0, 10, 10], [0, 10, 0, 10]) assert rows_cols == rowcol(aff, *xy(aff, *rows_cols))
def pixel_position(x: float, y: float, transform: Affine) -> list: """ CONVERT SPATIAL COORDINATES TO PIXEL X AND Y :param transform: :param x: :param y: :return: """ return rowcol(transform, x, y)
def _get_window(self, idx: int, transform: Affine = None) -> Window: """Returns a Window in the given transformation with sides of self.feature_chip_size and with an upper left corner self.chip_xs[idx], self.chip_ys[idx]""" if transform is None: transform = self.input_transform row_off, col_off = rowcol(transform, self.chip_xs[idx], self.chip_ys[idx]) return Window(col_off, row_off, self.feature_chip_size, self.feature_chip_size)
def sample_gen(dataset, xy, indexes=None, masked=False): """Sample pixels from a dataset Parameters ---------- dataset : rasterio Dataset Opened in "r" mode. xy : iterable Pairs of x, y coordinates in the dataset's reference system. indexes : int or list of int Indexes of dataset bands to sample. masked : bool, default: False Whether to mask samples that fall outside the extent of the dataset. Yields ------ array A array of length equal to the number of specified indexes containing the dataset values for the bands corresponding to those indexes. """ dt = dataset.transform read = dataset.read height = dataset.height width = dataset.width if indexes is None: indexes = dataset.indexes elif isinstance(indexes, int): indexes = [indexes] nodata = np.full(len(indexes), (dataset.nodata or 0), dtype=dataset.dtypes[0]) if masked: # Masks for masked arrays are inverted (False means valid) mask = [ MaskFlags.all_valid not in dataset.mask_flag_enums[i - 1] for i in indexes ] nodata = np.ma.array(nodata, mask=mask) for pts in _grouper(xy, 256): pts = zip(*filter(None, pts)) for row_off, col_off in zip(*rowcol(dt, *pts)): if row_off < 0 or col_off < 0 or row_off >= height or col_off >= width: yield nodata else: window = Window(col_off, row_off, 1, 1) data = read(indexes, window=window, masked=masked) yield data[:, 0, 0]
def create_cutline( src_dst: Union[DatasetReader, DatasetWriter, WarpedVRT], geometry: Dict, geometry_crs: CRS = None, ) -> str: """ Create WKT Polygon Cutline for GDALWarpOptions. Ref: https://gdal.org/api/gdalwarp_cpp.html?highlight=vrt#_CPPv415GDALWarpOptions Args: src_dst (rasterio.io.DatasetReader or rasterio.io.DatasetWriter or rasterio.vrt.WarpedVRT): Rasterio dataset. geometry (dict): GeoJSON feature or GeoJSON geometry. By default the cordinates are considered to be in the dataset CRS. Use `geometry_crs` to set a specific CRS. geometry_crs (rasterio.crs.CRS, optional): Input geometry Coordinate Reference System Returns: str: WKT geometry in form of `POLYGON ((x y, x y, ...))) """ if "geometry" in geometry: geometry = geometry["geometry"] if not is_valid_geom(geometry): raise RioTilerError("Invalid geometry") geom_type = geometry["type"] if geom_type not in ["Polygon", "MultiPolygon"]: raise RioTilerError( "Invalid geometry type: {geom_type}. Should be Polygon or MultiPolygon" ) if geometry_crs: geometry = transform_geom(geometry_crs, src_dst.crs, geometry) polys = [] geom = ( [geometry["coordinates"]] if geom_type == "Polygon" else geometry["coordinates"] ) for p in geom: xs, ys = zip(*coords(p)) src_y, src_x = rowcol(src_dst.transform, xs, ys) src_x = [max(0, min(src_dst.width, x)) for x in src_x] src_y = [max(0, min(src_dst.height, y)) for y in src_y] poly = ", ".join([f"{x} {y}" for x, y in list(zip(src_x, src_y))]) polys.append(f"(({poly}))") str_poly = ",".join(polys) return ( f"POLYGON {str_poly}" if geom_type == "Polygon" else f"MULTIPOLYGON ({str_poly})" )
def map_to_pixel(self, map_point): """Transform point from map to pixel-based coordinates. Args: map_point: (x, y) tuple in map coordinates Returns: (x, y) tuple in pixel coordinates """ image_point = self.map2image.transform(*map_point) pixel_point = rowcol(self.transform, image_point[0], image_point[1]) pixel_point = (pixel_point[1], pixel_point[0]) return pixel_point
def from_bounds(left, bottom, right, top, transform, height=None, width=None, boundless=False, precision=6): """Get the window corresponding to the bounding coordinates. Parameters ---------- left, bottom, right, top : float Left (west), bottom (south), right (east), and top (north) bounding coordinates. transform : Affine Affine transform matrix. height, width : int Number of rows and columns of the window. boundless : boolean, optional If True, the output window's size may exceed the given height and width. precision : int, optional Number of decimal points of precision when computing inverse transform. Returns ------- Window A new Window """ window_start = rowcol( transform, left, top, op=math.floor, precision=precision) window_stop = rowcol( transform, right, bottom, op=math.ceil, precision=precision) window = Window.from_ranges(*tuple(zip(window_start, window_stop))) if boundless: return window else: if None in (height, width): raise ValueError("Must supply height and width unless boundless") return crop(window, height, width)
def elevation_adjustment(coords, elevation_array, transformation_matrix): '''' Function to return adjustment time coords - Must be the coordinates in [x, y] elevation_array - Must be a numpy array containing the elevation data transformation_matrix - The transformation matrix output. Code adapted from a function by Mahmoud Abdelrazek, 2019 - https://github.com/razekmh '''' rise = 0 # Initialise rise to zero try: for i, point in enumerate(coords): # Extract coordinates x_coord, y_coord = point if i == 0: # First value back_height = elevation_array[rowcol(transformation_matrix, x_coord, y_coord)] # Get elevation else: fore_height = elevation_array[rowcol(transformation_matrix, x_coord, y_coord)] # Get elevation if fore_height > back_height: # Ignore negative elevation changes rise += fore_height - back_height # Add each posotive changes to the rise back_height = fore_height elevation_adjustment = rise * 0.1 # To get the adjustment value in minutes. return elevation_adjustment except IOError: print("Unable to perform this operation")
def get_window_from_xy(self, xy): """Get the window index given a coordinate (raster CRS).""" a_transform = self._get_template_for_given_resolution(res=self.dst_res, return_="meta")["transform"] row, col = transform.rowcol(a_transform, xy[0], xy[1]) ij_containing_xy = None for ji, win in enumerate(self.windows): (row_start, row_end), (col_start, col_end) = rasterio.windows.toranges(win) # print(row, col, row_start, row_end, col_start, col_end) if ((col >= col_start) & (col < col_end)) & ((row >= row_start) & (row < row_end)): ij_containing_xy = ji break if ij_containing_xy is None: raise ValueError("The given 'xy' value is not contained in any window.") return ij_containing_xy
def from_bounds(left, bottom, right, top, transform=None, height=None, width=None, precision=6, **kwargs): """Get the window corresponding to the bounding coordinates. Parameters ---------- left, bottom, right, top: float Left (west), bottom (south), right (east), and top (north) bounding coordinates. transform: Affine Affine transform matrix. height, width: int Number of rows and columns of the window. precision: int, optional Number of decimal points of precision when computing inverse transform. kwargs: mapping Absorbs deprecated keyword args Returns ------- Window A new Window """ if 'boundless' in kwargs: warnings.warn("boundless keyword should not be used", RasterioDeprecationWarning) row_start, col_start = rowcol( transform, left, top, op=float, precision=precision) row_stop, col_stop = rowcol( transform, right, bottom, op=float, precision=precision) return Window.from_slices( (row_start, row_stop), (col_start, col_stop), height=height, width=width, boundless=True)
def test_rowcol(): with rasterio.open("tests/data/RGB.byte.tif", 'r') as src: aff = src.transform left, bottom, right, top = src.bounds assert rowcol(aff, left, top) == (0, 0) assert rowcol(aff, right, top) == (0, src.width) assert rowcol(aff, right, bottom) == (src.height, src.width) assert rowcol(aff, left, bottom) == (src.height, 0) assert rowcol(aff, 101985.0, 2826915.0) == (0, 0) assert rowcol(aff, 101985.0 + 400.0, 2826915.0) == (0, 1)
def create_cutline(src_dst: DataSet, geometry: Dict, geometry_crs: CRS = None) -> str: """ Create WKT Polygon Cutline for GDALWarpOptions. Ref: https://gdal.org/api/gdalwarp_cpp.html?highlight=vrt#_CPPv415GDALWarpOptions Attributes ---------- src_dst: rasterio.io.DatasetReader rasterio.io.DatasetReader object geometry: dict GeoJSON feature or GeoJSON geometry geometry_crs: CRS or str, optional Specify bounds coordinate reference system, default is same as input dataset. Returns ------- wkt: str Cutline WKT geometry in form of `POLYGON ((x y, x y, ...))) """ if "geometry" in geometry: geometry = geometry["geometry"] geom_type = geometry["type"] if not geom_type == "Polygon": raise RioTilerError( "Invalid geometry type: {geom_type}. Should be Polygon") if geometry_crs: geometry = transform_geom(geometry_crs, src_dst.crs, geometry) xs, ys = zip(*coords(geometry)) src_y, src_x = rowcol(src_dst.transform, xs, ys) src_x = [max(0, min(src_dst.width, x)) for x in src_x] src_y = [max(0, min(src_dst.height, y)) for y in src_y] poly = ", ".join([f"{x} {y}" for x, y in list(zip(src_x, src_y))]) return f"POLYGON (({poly}))"
def cell_series(rast_dir, lon, lat): """ get the values of a particular latitude longitude in a time series of rasters """ cell_series = [] with rasterio.open(rast_dir + '/' + os.listdir(rast_dir)[0]) as data: aff = data.transform row, col = rowcol(aff, lon, lat) for fname in os.listdir(rast_dir): with rasterio.open(rast_dir + '/' + fname) as rast: vals = rast.read() val_at_coord = vals[0, row, col] date_str = fname[9:-4] date = dt.datetime.strptime(date_str, '%Y%m%d_%H%M%S') cell_series.append((date, val_at_coord)) return (cell_series)
def geometry_window( dataset, shapes, pad_x=0, pad_y=0, north_up=None, rotated=None, pixel_precision=None, boundless=False, ): """Calculate the window within the raster that fits the bounds of the geometry plus optional padding. The window is the outermost pixel indices that contain the geometry (floor of offsets, ceiling of width and height). If shapes do not overlap raster, a WindowError is raised. Parameters ---------- dataset : dataset object opened in 'r' mode Raster for which the mask will be created. shapes : iterable over geometries. A geometry is a GeoJSON-like object or implements the geo interface. Must be in same coordinate system as dataset. pad_x : float Amount of padding (as fraction of raster's x pixel size) to add to left and right side of bounds. pad_y : float Amount of padding (as fraction of raster's y pixel size) to add to top and bottom of bounds. north_up : optional This parameter is ignored since version 1.2.1. A deprecation warning will be emitted in 1.3.0. rotated : optional This parameter is ignored since version 1.2.1. A deprecation warning will be emitted in 1.3.0. pixel_precision : int or float, optional Number of places of rounding precision or absolute precision for evaluating bounds of shapes. boundless : bool, optional Whether to allow a boundless window or not. Returns ------- rasterio.windows.Window """ if pad_x: pad_x = abs(pad_x * dataset.res[0]) if pad_y: pad_y = abs(pad_y * dataset.res[1]) all_bounds = [bounds(shape) for shape in shapes] xs = [ x for (left, bottom, right, top) in all_bounds for x in (left - pad_x, right + pad_x, right + pad_x, left - pad_x) ] ys = [ y for (left, bottom, right, top) in all_bounds for y in (top + pad_y, top + pad_y, bottom - pad_x, bottom - pad_x) ] rows1, cols1 = rowcol(dataset.transform, xs, ys, op=math.floor, precision=pixel_precision) if isinstance(rows1, (int, float)): rows1 = [rows1] if isinstance(cols1, (int, float)): cols1 = [cols1] rows2, cols2 = rowcol(dataset.transform, xs, ys, op=math.ceil, precision=pixel_precision) if isinstance(rows2, (int, float)): rows2 = [rows2] if isinstance(cols2, (int, float)): cols2 = [cols2] rows = rows1 + rows2 cols = cols1 + cols2 row_start, row_stop = min(rows), max(rows) col_start, col_stop = min(cols), max(cols) window = Window( col_off=col_start, row_off=row_start, width=max(col_stop - col_start, 0.0), height=max(row_stop - row_start, 0.0), ) # Make sure that window overlaps raster raster_window = Window(0, 0, dataset.width, dataset.height) if not boundless: window = window.intersection(raster_window) return window
def from_bounds(left, bottom, right, top, transform=None, height=None, width=None, precision=None): """Get the window corresponding to the bounding coordinates. Parameters ---------- left: float, required Left (west) bounding coordinates bottom: float, required Bottom (south) bounding coordinates right: float, required Right (east) bounding coordinates top: float, required Top (north) bounding coordinates transform: Affine, required Affine transform matrix. precision, height, width: int, optional These parameters are unused, deprecated in rasterio 1.3.0, and will be removed in version 2.0.0. Returns ------- Window A new Window. Raises ------ WindowError If a window can't be calculated. """ if height is not None or width is not None or precision is not None: warnings.warn( "The height, width, and precision parameters are unused, deprecated, and will be removed in 2.0.0.", RasterioDeprecationWarning, ) if not isinstance(transform, Affine): # TODO: RPCs? raise WindowError( "A transform object is required to calculate the window") if (right - left) / transform.a < 0: raise WindowError("Bounds and transform are inconsistent") if (bottom - top) / transform.e < 0: raise WindowError("Bounds and transform are inconsistent") rows, cols = rowcol( transform, [left, right, right, left], [top, top, bottom, bottom], op=float, ) row_start, row_stop = min(rows), max(rows) col_start, col_stop = min(cols), max(cols) return Window( col_off=col_start, row_off=row_start, width=max(col_stop - col_start, 0.0), height=max(row_stop - row_start, 0.0), )
def merge_rgba_tool(sources, outtif, bounds=None, res=None, precision=7, creation_options={}): """A windowed, top-down approach to merging. For each block window, it loops through the sources, reads the corresponding source window until the block is filled with data or we run out of sources. Uses more disk IO but is faster* and consumes significantly less memory * The read efficiencies comes from using RGBA tifs where we can assume band 4 is the sole determinant of nodata. This avoids the use of expensive masked reads but, of course, limits what data can used. Hence merge_rgba. """ first = sources[0] first_res = first.res dtype = first.dtypes[0] profile = first.profile # Extent from option or extent of all inputs. if bounds: dst_w, dst_s, dst_e, dst_n = bounds else: # scan input files. # while we're at it, validate assumptions about inputs xs = [] ys = [] for src in sources: left, bottom, right, top = src.bounds xs.extend([left, right]) ys.extend([bottom, top]) if src.profile['count'] != 4: # TODO, how to test for alpha? raise ValueError("Inputs must be 4-band RGBA rasters") dst_w, dst_s, dst_e, dst_n = min(xs), min(ys), max(xs), max(ys) logger.debug("Output bounds: %r", (dst_w, dst_s, dst_e, dst_n)) output_transform = Affine.translation(dst_w, dst_n) logger.debug("Output transform, before scaling: %r", output_transform) # Resolution/pixel size. if not res: res = first_res elif not np.iterable(res): res = (res, res) elif len(res) == 1: res = (res[0], res[0]) output_transform *= Affine.scale(res[0], -res[1]) logger.debug("Output transform, after scaling: %r", output_transform) # Compute output array shape. We guarantee it will cover the output # bounds completely. output_width = int(math.ceil((dst_e - dst_w) / res[0])) output_height = int(math.ceil((dst_n - dst_s) / res[1])) # Adjust bounds to fit. dst_e, dst_s = output_transform * (output_width, output_height) logger.debug("Output width: %d, height: %d", output_width, output_height) logger.debug("Adjusted bounds: %r", (dst_w, dst_s, dst_e, dst_n)) profile['transform'] = output_transform profile['height'] = output_height profile['width'] = output_width profile['nodata'] = None # rely on alpha mask # Creation opts profile.update(creation_options) # create destination file with rasterio.open(outtif, 'w', **profile) as dstrast: for idx, dst_window in dstrast.block_windows(): left, bottom, right, top = dstrast.window_bounds(dst_window) blocksize = ((dst_window[0][1] - dst_window[0][0]) * (dst_window[1][1] - dst_window[1][0])) # initialize array destined for the block dst_count = first.count dst_rows, dst_cols = tuple(b - a for a, b in dst_window) dst_shape = (dst_count, dst_rows, dst_cols) logger.debug("Temp shape: %r", dst_shape) dstarr = np.zeros(dst_shape, dtype=dtype) # Read up srcs until # a. everything is data; i.e. no nodata # b. no sources left for src in sources: # The full_cover behavior is problematic here as it includes # extra pixels along the bottom right when the sources are # slightly misaligned # # src_window = get_window(left, bottom, right, top, # src.transform, precision=precision) # # With rio merge this just adds an extra row, but when the # imprecision occurs at each block, you get artifacts # Alternative, custom get_window using rounding window_start = rowcol( src.transform, left, top, op=round, precision=precision) window_stop = rowcol( src.transform, right, bottom, op=round, precision=precision) src_window = tuple(zip(window_start, window_stop)) temp = np.zeros(dst_shape, dtype=dtype) temp = src.read(out=temp, window=src_window, boundless=True, masked=False) # pixels without data yet are available to write write_region = np.logical_and( (dstarr[3] == 0), # 0 is nodata (temp[3] != 0)) np.copyto(dstarr, temp, where=write_region) # check if dest has any nodata pixels available if np.count_nonzero(dstarr[3]) == blocksize: break dstrast.write(dstarr, window=dst_window) return output_transform
def merge(input_ortho_and_ortho_cuts, output_orthophoto, orthophoto_vars={}): """ Based on https://github.com/mapbox/rio-merge-rgba/ Merge orthophotos around cutlines using a blend buffer. """ inputs = [] bounds = None precision = 7 for o, c in input_ortho_and_ortho_cuts: if not io.file_exists(o): log.ODM_WARNING( "%s does not exist. Will skip from merged orthophoto." % o) continue if not io.file_exists(c): log.ODM_WARNING( "%s does not exist. Will skip from merged orthophoto." % c) continue inputs.append((o, c)) if len(inputs) == 0: log.ODM_WARNING("No input orthophotos, skipping merge.") return with rasterio.open(inputs[0][0]) as first: res = first.res dtype = first.dtypes[0] profile = first.profile num_bands = first.meta['count'] - 1 # minus alpha colorinterp = first.colorinterp log.ODM_INFO("%s valid orthophoto rasters to merge" % len(inputs)) sources = [(rasterio.open(o), rasterio.open(c)) for o, c in inputs] # scan input files. # while we're at it, validate assumptions about inputs xs = [] ys = [] for src, _ in sources: left, bottom, right, top = src.bounds xs.extend([left, right]) ys.extend([bottom, top]) if src.profile["count"] < 4: raise ValueError("Inputs must be at least 4-band rasters") dst_w, dst_s, dst_e, dst_n = min(xs), min(ys), max(xs), max(ys) log.ODM_INFO("Output bounds: %r %r %r %r" % (dst_w, dst_s, dst_e, dst_n)) output_transform = Affine.translation(dst_w, dst_n) output_transform *= Affine.scale(res[0], -res[1]) # Compute output array shape. We guarantee it will cover the output # bounds completely. output_width = int(math.ceil((dst_e - dst_w) / res[0])) output_height = int(math.ceil((dst_n - dst_s) / res[1])) # Adjust bounds to fit. dst_e, dst_s = output_transform * (output_width, output_height) log.ODM_INFO("Output width: %d, height: %d" % (output_width, output_height)) log.ODM_INFO("Adjusted bounds: %r %r %r %r" % (dst_w, dst_s, dst_e, dst_n)) profile["transform"] = output_transform profile["height"] = output_height profile["width"] = output_width profile["tiled"] = orthophoto_vars.get('TILED', 'YES') == 'YES' profile["blockxsize"] = orthophoto_vars.get('BLOCKXSIZE', 512) profile["blockysize"] = orthophoto_vars.get('BLOCKYSIZE', 512) profile["compress"] = orthophoto_vars.get('COMPRESS', 'LZW') profile["predictor"] = orthophoto_vars.get('PREDICTOR', '2') profile["bigtiff"] = orthophoto_vars.get('BIGTIFF', 'IF_SAFER') profile.update() # create destination file with rasterio.open(output_orthophoto, "w", **profile) as dstrast: dstrast.colorinterp = colorinterp for idx, dst_window in dstrast.block_windows(): left, bottom, right, top = dstrast.window_bounds(dst_window) blocksize = dst_window.width dst_rows, dst_cols = (dst_window.height, dst_window.width) # initialize array destined for the block dst_count = first.count dst_shape = (dst_count, dst_rows, dst_cols) dstarr = np.zeros(dst_shape, dtype=dtype) # First pass, write all rasters naively without blending for src, _ in sources: src_window = tuple( zip( rowcol(src.transform, left, top, op=round, precision=precision), rowcol(src.transform, right, bottom, op=round, precision=precision))) temp = np.zeros(dst_shape, dtype=dtype) temp = src.read(out=temp, window=src_window, boundless=True, masked=False) # pixels without data yet are available to write write_region = np.logical_and( (dstarr[-1] == 0), (temp[-1] != 0) # 0 is nodata ) np.copyto(dstarr, temp, where=write_region) # check if dest has any nodata pixels available if np.count_nonzero(dstarr[-1]) == blocksize: break # Second pass, write all feathered rasters # blending the edges for src, _ in sources: src_window = tuple( zip( rowcol(src.transform, left, top, op=round, precision=precision), rowcol(src.transform, right, bottom, op=round, precision=precision))) temp = np.zeros(dst_shape, dtype=dtype) temp = src.read(out=temp, window=src_window, boundless=True, masked=False) where = temp[-1] != 0 for b in range(0, num_bands): blended = temp[-1] / 255.0 * temp[b] + ( 1 - temp[-1] / 255.0) * dstarr[b] np.copyto(dstarr[b], blended, casting='unsafe', where=where) dstarr[-1][where] = 255.0 # check if dest has any nodata pixels available if np.count_nonzero(dstarr[-1]) == blocksize: break # Third pass, write cut rasters # blending the cutlines for _, cut in sources: src_window = tuple( zip( rowcol(cut.transform, left, top, op=round, precision=precision), rowcol(cut.transform, right, bottom, op=round, precision=precision))) temp = np.zeros(dst_shape, dtype=dtype) temp = cut.read(out=temp, window=src_window, boundless=True, masked=False) # For each band, average alpha values between # destination raster and cut raster for b in range(0, num_bands): blended = temp[-1] / 255.0 * temp[b] + ( 1 - temp[-1] / 255.0) * dstarr[b] np.copyto(dstarr[b], blended, casting='unsafe', where=temp[-1] != 0) dstrast.write(dstarr, window=dst_window) return output_orthophoto
def extent_to_windows(self, extent, actual_transform): window_min = transform.rowcol(actual_transform, extent['xmin'], extent['ymin']) window_max = transform.rowcol(actual_transform, extent['xmax'], extent['ymax']) return self.window_transform(window_min, window_max)
def from_bounds( left, bottom, right, top, transform=None, height=None, width=None, precision=None ): """Get the window corresponding to the bounding coordinates. Parameters ---------- left: float, required Left (west) bounding coordinates bottom: float, required Bottom (south) bounding coordinates right: float, required Right (east) bounding coordinates top: float, required Top (north) bounding coordinates transform: Affine, required Affine transform matrix. height: int, required Number of rows of the window. width: int, required Number of columns of the window. precision: int or float, optional An integer number of decimal points of precision when computing inverse transform, or an absolute float precision. Returns ------- Window A new Window. Raises ------ WindowError If a window can't be calculated. """ if not isinstance(transform, Affine): # TODO: RPCs? raise WindowError("A transform object is required to calculate the window") if (right - left) / transform.a < 0: raise WindowError("Bounds and transform are inconsistent") if (bottom - top) / transform.e < 0: raise WindowError("Bounds and transform are inconsistent") rows, cols = rowcol( transform, [left, right, right, left], [top, top, bottom, bottom], op=float, precision=precision, ) row_start, row_stop = min(rows), max(rows) col_start, col_stop = min(cols), max(cols) return Window( col_off=col_start, row_off=row_start, width=max(col_stop - col_start, 0.0), height=max(row_stop - row_start, 0.0), )
def cmip( store='az', df=None, tlim=None, model=None, scenario=None, coarsen=None, variables=['ppt', 'tmean'], mask=None, member=None, method='bias-corrected', sampling='annual', historical=False, remove_nans=False, ): with warnings.catch_warnings(): warnings.simplefilter('ignore', category=ResourceWarning) warnings.simplefilter('ignore', category=FutureWarning) warnings.simplefilter('ignore', category=RuntimeWarning) if scenario is None: raise ValueError('must specify scenario') if model is None: raise ValueError('must specify model') path = setup.loading(store) prefix = f'cmip6/{method}/conus/4000m/{sampling}/{model}.{scenario}.{member}.zarr' if store == 'az': mapper = zarr.storage.ABSStore('carbonplan-downscaling', prefix=prefix, account_name='carbonplan') else: mapper = fsspec.get_mapper( (path / 'carbonplan-downscaling' / prefix).as_uri()) ds = xr.open_zarr(mapper, consolidated=True) if historical: prefix = f'cmip6/{method}/conus/4000m/{sampling}/{model}.historical.{member}.zarr' if store == 'az': mapper = zarr.storage.ABSStore('carbonplan-downscaling', prefix=prefix, account_name='carbonplan') else: mapper = fsspec.get_mapper( (path / 'carbonplan-downscaling' / prefix).as_uri()) ds_historical = xr.open_zarr(mapper, consolidated=True) ds = xr.concat([ds_historical, ds], 'time') ds['cwd'] = ds['def'] ds['pdsi'] = ds['pdsi'].clip(-16, 16) X = xr.Dataset() keys = variables for key in keys: X[key] = ds[key] if tlim is not None: tlim = list(map(str, tlim)) X = X.sel(time=slice(*tlim)) if mask is not None: vals = mask.values vals[vals == 0] = np.NaN X = X * vals if coarsen: X_coarse = xr.Dataset() for key in keys: X_coarse[key] = X[key].coarsen(x=coarsen, y=coarsen, boundary='trim').mean() X = X_coarse if df is not None: t = Affine(*utils.albers_conus_transform(4000)) p1 = Proj(utils.albers_conus_crs()) p2 = Proj(proj='latlong', datum='WGS84') x, y = transform(p2, p1, df['lon'].values, df['lat'].values) rc = rowcol(t, x, y) ind_r = xr.DataArray(rc[0], dims=['c']) ind_c = xr.DataArray(rc[1], dims=['c']) base = X[keys].isel(y=ind_r, x=ind_c).load() for key in keys: df[key + '_mean'] = base[key].mean('time').values df[key + '_min'] = base[key].min('time').values df[key + '_max'] = base[key].max('time').values if remove_nans: for key in keys: df = df[~np.isnan(df[key + '_mean'])] df = df.reset_index(drop=True) return df X = X.drop(['x', 'y']) X.load(retries=10) return X
def get_window(extent: tuple, transform: Affine) -> (tuple, tuple): row_start, col_start = rowcol(transform, extent[0], extent[-1], op=int) row_stop, col_stop = rowcol(transform, extent[2], extent[1], op=int) return (row_start, row_stop), (col_start, col_stop)