def _intersection(w1, w2): """ Compute intersection of window 1 and window 2""" coeffs = _compute_intersection(w1, w2) if coeffs[2] > 0 and coeffs[3] > 0: return Window(*coeffs) else: raise WindowError(f"Intersection is empty {w1} {w2}")
def round_offsets(self, op='floor', pixel_precision=None): """Return a copy with column and row offsets rounded. Offsets are rounded to the nearest whole number. The lengths are not changed. Parameters ---------- op : str 'ceil' or 'floor' pixel_precision : int, optional (default: None) Number of places of rounding precision. Returns ------- Window """ operator = getattr(math, op, None) if not operator: raise WindowError("operator must be 'ceil' or 'floor'") else: return Window( operator(round(self.col_off, pixel_precision) if pixel_precision is not None else self.col_off), operator(round(self.row_off, pixel_precision) if pixel_precision is not None else self.row_off), self.width, self.height)
def round_offsets(self, op='floor', pixel_precision=None): """Return a copy with column and row offsets rounded. Offsets are rounded to the preceding (floor) or succeeding (ceil) whole number. The lengths are not changed. Parameters ---------- op : str 'ceil' or 'floor' pixel_precision : int, optional (default: None) Number of places of rounding precision. Returns ------- Window """ if op not in {'ceil', 'floor'}: raise WindowError("operator must be 'ceil' or 'floor', got '{}'".format(op)) operator = getattr(math, op) if pixel_precision is None: return Window(operator(self.col_off), operator(self.row_off), self.width, self.height) else: return Window(operator(round(self.col_off, pixel_precision)), operator(round(self.row_off, pixel_precision)), self.width, self.height)
def round_lengths(self, op='floor', pixel_precision=None): """Return a copy with width and height rounded. Lengths are rounded to the preceding (floor) or succeeding (ceil) whole number. The offsets are not changed. Parameters ---------- op: str 'ceil' or 'floor' pixel_precision: int, optional (default: None) Number of places of rounding precision. Returns ------- Window """ operator = getattr(math, op, None) if not operator: raise WindowError("operator must be 'ceil' or 'floor'") else: return Window( self.col_off, self.row_off, operator( round(self.width, pixel_precision ) if pixel_precision is not None else self.width), operator( round(self.height, pixel_precision ) if pixel_precision is not None else self.height))
def intersection(*windows): """Innermost extent of window intersections. Will raise WindowError if windows do not intersect. Parameters ---------- windows: sequence One or more Windows. Returns ------- Window """ if not intersect(windows): raise WindowError("windows do not intersect") stacked = np.dstack([toranges(w) for w in windows]) row_start, row_stop = stacked[0, 0].max(), stacked[0, 1].min() col_start, col_stop = stacked[1, 0].max(), stacked[1, 1].min() return Window( col_off=col_start, row_off=row_start, width=col_stop - col_start, height=row_stop - row_start, )
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 get_data_window(arr, nodata=None): """Window covering the input array's valid data pixels. Parameters ---------- arr: numpy ndarray, <= 3 dimensions nodata: number If None, will either return a full window if arr is not a masked array, or will use the mask to determine non-nodata pixels. If provided, it must be a number within the valid range of the dtype of the input array. Returns ------- Window """ num_dims = len(arr.shape) if num_dims > 3: raise WindowError( "get_data_window input array must have no more than " "3 dimensions") if nodata is None: if not hasattr(arr, 'mask'): return Window.from_slices((0, arr.shape[-2]), (0, arr.shape[-1])) else: arr = np.ma.masked_array(arr, arr == nodata) if num_dims == 2: data_rows, data_cols = np.where(np.equal(arr.mask, False)) else: data_rows, data_cols = np.where( np.any(np.equal(np.rollaxis(arr.mask, 0, 3), False), axis=2)) if data_rows.size: row_range = (data_rows.min(), data_rows.max() + 1) else: row_range = (0, 0) if data_cols.size: col_range = (data_cols.min(), data_cols.max() + 1) else: col_range = (0, 0) return Window.from_slices(row_range, col_range)
def intersection(*windows): """Innermost extent of window intersections. Will raise WindowError if windows do not intersect. Parameters ---------- windows: sequence One or more Windows. Returns ------- Window """ if not intersect(windows): raise WindowError("windows do not intersect") stacked = np.dstack([toranges(w) for w in windows]) return Window.from_slices((stacked[0, 0].max(), stacked[0, 1].min()), (stacked[1, 0].max(), stacked[1, 1].min()))
def round_lengths(self, op='floor', pixel_precision=3): """Return a copy with width and height rounded. Lengths are rounded to the nearest whole number. The offsets are not changed. Parameters ---------- op: str 'ceil' or 'floor' pixel_precision: int Number of places of rounding precision. Returns ------- Window """ operator = getattr(math, op, None) if not operator: raise WindowError("operator must be 'ceil' or 'floor'") else: return Window(self.col_off, self.row_off, operator(round(self.width, pixel_precision)), operator(round(self.height, pixel_precision)))
def round_window_to_full_blocks(window, block_shapes, height=0, width=0): """Round window to include full expanse of intersecting tiles. Parameters ---------- window: Window The input window. block_shapes : tuple of block shapes The input raster's block shape. All bands must have the same block/stripe structure Returns ------- Window """ if len(set(block_shapes)) != 1: # pragma: no cover raise WindowError( "All bands must have the same block/stripe structure") window = evaluate(window, height=height, width=width) height_shape = block_shapes[0][0] width_shape = block_shapes[0][1] (row_start, row_stop), (col_start, col_stop) = window.toranges() row_min = int(row_start // height_shape) * height_shape row_max = int(row_stop // height_shape) * height_shape + \ (height_shape if row_stop % height_shape != 0 else 0) col_min = int(col_start // width_shape) * width_shape col_max = int(col_stop // width_shape) * width_shape + \ (width_shape if col_stop % width_shape != 0 else 0) return Window(col_min, row_min, col_max - col_min, row_max - row_min)
def from_slices(cls, rows, cols, height=-1, width=-1, boundless=False): """Construct a Window from row and column slices or tuples. Parameters ---------- rows, cols: slice or tuple Slices or 2-tuples containing start, stop indexes. height, width: float A shape to resolve relative values against. boundless: bool, optional Whether the inputs are bounded or bot. Returns ------- Window """ # Convert the rows indexing obj to offset and height. # Normalize to slices if not isinstance(rows, (tuple, slice)): raise WindowError("rows must be a tuple or slice") else: rows = slice(*rows) if isinstance(rows, tuple) else rows # Resolve the window height. # Fail if the stop value is relative or implicit and there # is no height context. if not boundless and ( (rows.start is not None and rows.start < 0) or rows.stop is None or rows.stop < 0) and height < 0: raise WindowError( "A non-negative height is required") row_off = rows.start or 0.0 if not boundless and row_off < 0: row_off += height row_stop = height if rows.stop is None else rows.stop if not boundless and row_stop < 0: row_stop += height num_rows = row_stop - row_off # Number of rows is never less than 0. num_rows = max(num_rows, 0.0) # Do the same for the cols indexing object. if not isinstance(cols, (tuple, slice)): raise WindowError("cols must be a tuple or slice") else: cols = slice(*cols) if isinstance(cols, tuple) else cols if not boundless and ( (cols.start is not None and cols.start < 0) or cols.stop is None or cols.stop < 0) and width < 0: raise WindowError("A non-negative width is required") col_off = cols.start or 0.0 if not boundless and col_off < 0: col_off += width col_stop = width if cols.stop is None else cols.stop if not boundless and col_stop < 0: col_stop += width num_cols = col_stop - col_off num_cols = max(num_cols, 0.0) return cls(col_off=col_off, row_off=row_off, width=num_cols, height=num_rows)
def from_slices(cls, rows, cols, height=-1, width=-1, boundless=False): """Construct a Window from row and column slices or tuples / lists of start and stop indexes. Converts the rows and cols to offsets, height, and width. In general, indexes are defined relative to the upper left corner of the dataset: rows=(0, 10), cols=(0, 4) defines a window that is 4 columns wide and 10 rows high starting from the upper left. Start indexes may be `None` and will default to 0. Stop indexes may be `None` and will default to width or height, which must be provided in this case. Negative start indexes are evaluated relative to the lower right of the dataset: rows=(-2, None), cols=(-2, None) defines a window that is 2 rows high and 2 columns wide starting from the bottom right. Parameters ---------- rows, cols: slice, tuple, or list Slices or 2 element tuples/lists containing start, stop indexes. height, width: float A shape to resolve relative values against. Only used when a start or stop index is negative or a stop index is None. boundless: bool, optional Whether the inputs are bounded (default) or not. Returns ------- Window """ # Normalize to slices if isinstance(rows, (tuple, list)): if len(rows) != 2: raise WindowError("rows must have a start and stop index") rows = slice(*rows) elif not isinstance(rows, slice): raise WindowError("rows must be a slice, tuple, or list") if isinstance(cols, (tuple, list)): if len(cols) != 2: raise WindowError("cols must have a start and stop index") cols = slice(*cols) elif not isinstance(cols, slice): raise WindowError("cols must be a slice, tuple, or list") # Height and width are required if stop indices are implicit if rows.stop is None and height < 0: raise WindowError("height is required if row stop index is None") if cols.stop is None and width < 0: raise WindowError("width is required if col stop index is None") # Convert implicit indices to offsets, height, and width row_off = 0.0 if rows.start is None else rows.start row_stop = height if rows.stop is None else rows.stop col_off = 0.0 if cols.start is None else cols.start col_stop = width if cols.stop is None else cols.stop if not boundless: if (row_off < 0 or row_stop < 0): if height < 0: raise WindowError("height is required when providing " "negative indexes") if row_off < 0: row_off += height if row_stop < 0: row_stop += height if (col_off < 0 or col_stop < 0): if width < 0: raise WindowError("width is required when providing " "negative indexes") if col_off < 0: col_off += width if col_stop < 0: col_stop += width num_cols = max(col_stop - col_off, 0.0) num_rows = max(row_stop - row_off, 0.0) return cls(col_off=col_off, row_off=row_off, width=num_cols, height=num_rows)
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 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), )