def clip_array_with_vector( array, array_affine, geometries, inverted=False, clip_buffer=0 ): """ Clip input array with a vector list. Parameters ---------- array : array input raster data array_affine : Affine Affine object describing the raster's geolocation geometries : iterable iterable of dictionaries, where every entry has a 'geometry' and 'properties' key. inverted : bool invert clip (default: False) clip_buffer : integer buffer (in pixels) geometries before clipping Returns ------- clipped array : array """ # buffer input geometries and clean up buffered_geometries = [] for feature in geometries: feature_geom = to_shape(feature["geometry"]) if feature_geom.is_empty: continue if feature_geom.geom_type == "GeometryCollection": # for GeometryCollections apply buffer to every subgeometry # and make union buffered_geom = unary_union([g.buffer(clip_buffer) for g in feature_geom]) else: buffered_geom = feature_geom.buffer(clip_buffer) if not buffered_geom.is_empty: buffered_geometries.append(buffered_geom) # mask raster by buffered geometries if buffered_geometries: if array.ndim == 2: return ma.masked_array( array, geometry_mask( buffered_geometries, array.shape, array_affine, invert=inverted)) elif array.ndim == 3: mask = geometry_mask( buffered_geometries, (array.shape[1], array.shape[2]), array_affine, invert=inverted ) return ma.masked_array(array, mask=np.stack([mask for band in array])) # if no geometries, return unmasked array else: fill = False if inverted else True return ma.masked_array(array, mask=np.full(array.shape, fill, dtype=bool))
def test_geometry_invalid_geom(geom): """An invalid geometry should fail""" with pytest.raises(ValueError) as exc_info, pytest.warns(ShapeSkipWarning): geometry_mask([geom], out_shape=DEFAULT_SHAPE, transform=Affine.identity()) assert 'No valid geometry objects found for rasterize' in exc_info.value.args[ 0]
def test_geometry_mask_invalid_shape(basic_geometry): """A width==0 or height==0 should fail with ValueError""" for shape in [(0, 0), (1, 0), (0, 1)]: with pytest.raises(ValueError) as exc_info: geometry_mask([basic_geometry], out_shape=shape, transform=Affine.identity()) assert 'must be > 0' in exc_info.value.args[0]
def test_geometry_mask_invalid_shape(basic_geometry): """A width==0 or height==0 should fail with ValueError""" for shape in [(0, 0), (1, 0), (0, 1)]: with pytest.raises(ValueError) as exc_info: geometry_mask( [basic_geometry], out_shape=shape, transform=Affine.identity()) assert 'must be > 0' in exc_info.value.args[0]
def read_raster_from_polygon( src: rio.DatasetReader, poly: Union[Polygon, MultiPolygon]) -> np.ma.MaskedArray: """Read valid pixel values from all locations inside a polygon Uses the polygon as a mask in addition to the existing raster mask Args: src: an open rasterio dataset to read from poly: a shapely Polygon or MultiPolygon Returns: masked array of shape (nbands, nrows, ncols) """ # get the read parameters window = rio.windows.from_bounds(*poly.bounds, src.transform) transform = rio.windows.transform(window, src.transform) # get the data data = src.read(window=window, masked=True, boundless=True) bands, rows, cols = data.shape poly_mask = geometry_mask([poly], transform=transform, out_shape=(rows, cols)) # update the mask data[:, poly_mask] = np.ma.masked return data
def _clip_xarray(xds, geometries, all_touched, drop, invert): """ clip the xarray DataArray """ clip_mask_arr = geometry_mask( geometries=geometries, out_shape=(int(xds.rio.height), int(xds.rio.width)), transform=xds.rio.transform(recalc=True), invert=not invert, all_touched=all_touched, ) clip_mask_xray = xarray.DataArray( clip_mask_arr, dims=(xds.rio.y_dim, xds.rio.x_dim), ) cropped_ds = xds.where(clip_mask_xray) if drop: cropped_ds.rio.set_spatial_dims(x_dim=xds.rio.x_dim, y_dim=xds.rio.y_dim, inplace=True) cropped_ds = cropped_ds.rio.isel_window( rasterio.windows.get_data_window( np.ma.masked_array(clip_mask_arr, ~clip_mask_arr))) if xds.rio.nodata is not None and not np.isnan(xds.rio.nodata): cropped_ds = cropped_ds.fillna(xds.rio.nodata) return cropped_ds.astype(xds.dtype)
def mask_rast_from_geom(raster_file, geom_iterator): """Returns a numpy mask of the same dimensions as the input raster and also a numpy array copy of the input raster. requires inputs: - (gdal) raster file - iterator of json like geometries returns: - a (numpy) raster mask - a numpy array copy of the input raster (should be handled elsewhere?) """ with rasterio.open(raster_file) as src_rst: dsm = src_rst.read() out_meta = src_rst.meta.copy() print(out_meta) out_meta.update(dtype=rasterio.float32, driver='GTiff') mask = features.geometry_mask( [feature["feature"]["geometry"] for feature in geom_iterator], src_rst.shape, transform=src_rst.transform, all_touched=True, invert=True) new_dsm = np.copy(np.squeeze( dsm)) #Forstaer ikke hvorfor, men dsm'en har en ekstra dimension, #som jeg fjerner med squeeze, saa den passer med result dsm'en return mask, new_dsm
def get_slope_raster(dc, x_range, y_range, inputcrs, resolution, geom_selectedarea): ds_dem = dc.load(product='dem', x=x_range, y=y_range, crs=inputcrs, output_crs=inputcrs, resolution=resolution, dask_chunks={'time': 1}) # Construct a mask to only select pixels within the drawn polygon mask_dem = features.geometry_mask( [geom_selectedarea for geoms in [geom_selectedarea]], out_shape=ds_dem.geobox.shape, transform=ds_dem.geobox.affine, all_touched=False, invert=True) # Calculate Slope category slope = get_slope(ds_dem.band1.squeeze('time', drop=True), *resolution).where(mask_dem) slope_cat = xr.apply_ufunc(slope_category, slope, vectorize=True, dask='parallelized', output_dtypes=[np.float32]) slope_cat.name = 'slope_category' return slope_cat
def get_dlcd_raster(dc, x_range, y_range, inputcrs, resolution, geom_selectedarea): """ :param x_range: tuple() of x values :param y_range: tuple of y values :param inputcrs: crs of x and y values :param resolution: :param geom_selectedarea: :return: """ ds_dlcd = dc.load(product='dlcdnsw', x=x_range, y=y_range, crs=inputcrs, output_crs=inputcrs, resolution=resolution, dask_chunks={'time': 1}) mask_dlcd = features.geometry_mask( [geom_selectedarea for geoms in [geom_selectedarea]], out_shape=ds_dlcd.geobox.shape, transform=ds_dlcd.geobox.affine, all_touched=False, invert=True) masked_dlcd = ds_dlcd.band1.where(mask_dlcd) masked_dlcd.name = 'dlcd' return masked_dlcd
def test_geometry_mask_invert(basic_geometry, basic_image_2x2): assert np.array_equal( basic_image_2x2, geometry_mask([basic_geometry], out_shape=DEFAULT_SHAPE, transform=Affine.identity(), invert=True))
def test_geometry_mask(basic_geometry, basic_image_2x2): with rasterio.Env(): assert np.array_equal( basic_image_2x2 == 0, geometry_mask([basic_geometry], out_shape=DEFAULT_SHAPE, transform=Affine.identity()))
def get_income_level_segmentation_mask(labels_dict, levels, image_shape, image_transform): """ Generate a roof segmentation mask from a dictionary of geometries :param labels_dict: labels dictionary :type labels_dict: dict :param levels: levels - mask index dictionary :type levels: dict :param image_shape: satellite image patch shape :type image_shape: tuple :param image_transform: satellite image patch affine transform :type image_transform: any """ mask = np.ndarray( (len(levels), image_shape[0], image_shape[1])).astype(int) for level, labels in labels_dict.items(): mask[levels[level]] = geometry_mask(labels, image_shape, image_transform, all_touched=False, invert=True) \ if labels else np.zeros(image_shape) return mask
def rasterize(feature_collection, transform, out_shape, name='mask'): """ Transform vector geometries to raster form, return band sample where raster is np.array of bool dtype (`True` value correspond to objects area) Args: feature_collection: `FeatureCollection` object transform: Affine transformation object Transformation from pixel coordinates of `source` to the coordinate system of the input `shapes`. See the `transform` property of dataset objects. out_shape: tuple or list Shape of output numpy ndarray. name: output sample name, default `mask` Returns: `BandSample` object """ if len(feature_collection) > 0: geometries = (f.geometry for f in feature_collection) mask = geometry_mask(geometries, out_shape=out_shape, transform=transform, invert=True).astype('uint8') else: mask = np.zeros(out_shape, dtype='uint8') return BandSample(name, mask, feature_collection.crs, transform)
def clip_with_shape(raster, shape): if isinstance(raster,str): raster = rasterio.open(raster) window = raster.window(*shape.bounds) out_transform = raster.window_transform(window) out_image = raster.read(window=window, masked=True) out_shape = out_image.shape[1:] if out_shape[0] * out_shape[1] > 0: shape_mask = geometry_mask([shape], transform=out_transform, invert=False, out_shape=out_shape, all_touched=False) out_image.mask = out_image.mask | shape_mask out_image.fill_value = None out_image = out_image.filled(np.nan) #out_image, out_transform = mask.mask(src, [Polygon(geoms_inters[41]['geometry']['coordinates'][0])], crop=True) out_meta = raster.meta.copy() # GTiff out_meta.update({"driver": "GTiff", "height": out_image.shape[1], "width": out_image.shape[2], "transform": out_transform, "count": 2, 'compression': 'lzw', 'tiled': True }) return (out_image, out_transform, out_meta) else: return [False]
def extract_values(pycno_array, gdf, transform, fieldname='Estimate'): """Extract raster value sums according to a provided polygon geodataframe Args: pycno_array (numpy array): 2D numpy array of pycnophylactic surface. gdf (geopandas.geodataframe.GeoDataFrame): Target GeoDataFrame. transform (rasterio geotransform): Relevant transform from pycno() fieldname (str, optional): New gdf field to save estimates in. Default name: 'Estimate'. Returns: geopandas.geodataframe.GeoDataFrame: Target GeoDataFrame with appended estimates. """ from numpy import nansum from rasterio.features import geometry_mask out = gdf.copy() estimates = [] # Iterate through geodataframe and extract values for idx, geom in gdf['geometry'].iteritems(): mask = geometry_mask([geom], pycno_array.shape, transform=transform, invert=True) estimates.append(nansum(pycno_array[mask])) out[fieldname] = estimates return out
def shapefile_mask(dataset: xr.Dataset, shapefile) -> np.array: """ Extracts a mask from a shapefile using dataset latitude and longitude extents. Args: dataset (xarray.Dataset): The dataset with latitude and longitude extents. shapefile (string): The shapefile to be used. Returns: A boolean mask array. """ import pyproj with fiona.open(shapefile, 'r') as src: collection = list(src) geometries = [] for feature in collection: geom = shape(feature['geometry']) project = partial( pyproj.transform, pyproj.Proj(init=src.crs['init']), # source crs pyproj.Proj(init='epsg:4326')) # destination crs geom = transform(project, geom) # apply projection geometries.append(geom) geobox = dataset.geobox mask = geometry_mask( geometries, out_shape=geobox.shape, transform=geobox.affine, all_touched=True, invert=True) return mask
def get_roof_segmentation_mask(labels_dict, image_shape, image_transform): """ Generate a roof segmentation mask from a dictionary of geometries :param labels_dict: labels dictionary :type labels_dict: dict :param image_shape: satellite image patch shape :type image_shape: tuple :param image_transform: satellite image patch affine transform :type image_transform: any """ geometries = [] for _, labels in labels_dict.items(): geometries += labels if geometries: mask = geometry_mask(geometries, image_shape, image_transform, all_touched=True, invert=True) else: mask = np.zeros(image_shape).astype(int) return mask
def mask_geom_on_raster(raster_data: np.ndarray, shifted_affine: Affine, geom: BasePolygon) -> np.ndarray: """" For a given polygon, returns a numpy masked array with the intersecting values of the raster at `raster_path` unmasked, all non-intersecting cells are masked. This assumes that the input geometry is in the same SRS as the raster. Currently only reads from a single band. Args: geom (Shapely Geometry): A polygon in the same SRS as `raster_path` which will define the area of the raster to mask. raster_path (string): A local file path to a geographic raster containing values to extract. Returns Numpy masked array of source raster, cropped to the extent of the input geometry, with any modifications applied. Areas where the supplied geometry does not intersect are masked. """ if raster_data.size > 0: # Create a numpy array to mask cells which don't intersect with the # polygon. Cells that intersect will have value of 1 (unmasked), the # rest are filled with 0s (masked) geom_mask = features.geometry_mask([geom], out_shape=raster_data.shape, transform=shifted_affine, invert=True) # Mask the data array, with modifications applied, by the query polygon return geom_mask else: return np.array([])
def test_geometry_invalid_geom(): """An invalid geometry should fail""" invalid_geoms = [ {'type': 'Invalid'}, # wrong type {'type': 'Point'}, # missing coordinates {'type': 'Point', 'coordinates': []} # empty coordinates ] for geom in invalid_geoms: with pytest.raises(ValueError) as exc_info: geometry_mask( [geom], out_shape=DEFAULT_SHAPE, transform=Affine.identity()) assert 'Invalid geometry' in exc_info.value.args[0]
def test_geometry_mask(): rows = cols = 10 transform = (1.0, 0.0, 0.0, 0.0, -1.0, 0.0) truth = numpy.zeros((rows, cols), dtype=rasterio.bool_) truth[2:5, 2:5] = True with rasterio.drivers(): s = shapes((truth * 10).astype(rasterio.ubyte), transform=transform) # Strip out values returned from shapes, and only keep first shape geoms = [next(s)[0]] # Regular mask should be the inverse of truth raster mask = geometry_mask(geoms, out_shape=(rows, cols), transform=transform) assert numpy.array_equal(mask, numpy.invert(truth)) # Inverted mask should be the same as the truth raster mask = geometry_mask(geoms, out_shape=(rows, cols), transform=transform, invert=True) assert numpy.array_equal(mask, truth)
def test_geometry_mask(basic_geometry, basic_image_2x2): assert np.array_equal( basic_image_2x2 == 0, geometry_mask( [basic_geometry], out_shape=DEFAULT_SHAPE, transform=Affine.identity() ) )
def rasterize(geometries, transform, shape): """Convert geometries to raster mask""" if len(geometries) > 0: mask = geometry_mask(geometries, out_shape=shape, transform=transform, invert=True).astype('uint8') else: mask = np.zeros(shape, dtype='uint8') return mask
def test_geometry_mask(basic_geometry, basic_image_2x2): with rasterio.drivers(): assert numpy.array_equal( basic_image_2x2 == 0, geometry_mask( [basic_geometry], out_shape=DEFAULT_SHAPE, transform=Affine.identity() ) )
def viewshed(vrt,list_of_dicts,distance,point, observer_height,grassdb,burn_viewshed_rst,total_cells_output): ## deles op - foerste funktion with rasterio.open(vrt) as src_rst: dsm = src_rst.read() out_meta = src_rst.meta.copy() print(out_meta) out_meta.update(dtype=rasterio.int16,driver='GTiff') mask = features.geometry_mask( [feature["feature"]["geometry"] for feature in list_of_dicts], src_rst.shape, transform=src_rst.transform, all_touched=True, invert=True) new_dsm = np.copy(np.squeeze(dsm)) #Forstaer ikke hvorfor, men dsm'en har en ekstra dimension, #som jeg fjerner med squeeze, saa den passer med result dsm'en ## deles op - anden funktion, maaske with rasterio.Env(): result = features.rasterize( ((feature['feature']['geometry'],np.int(feature['feature']['properties']['hoejde']) * 1000) for feature in list_of_dicts), out_shape=src_rst.shape, transform=src_rst.transform, all_touched=True) new_dsm[mask] = result[mask] ## deles op - tredje funktion with Session(gisdb=grassdb, location="test",create_opts=vrt): import grass.script.array as garray r_viewshed = Module('r.viewshed') r_out_gdal = Module('r.out.gdal') r_stats = Module('r.stats') r_univar = Module('r.univar') from_np_raster = garray.array() from_np_raster[...] = new_dsm from_np_raster.write('ny_rast',overwrite=True) print(from_np_raster) gcore.run_command('r.viewshed', overwrite=True, memory=2000, input='ny_rast', output='viewshed', max_distance=distance, coordinates=point, observer_elevation=observer_height) r_stats(flags='nc',overwrite=True,input='viewshed',output=total_cells_output) ## finde ud af hvordan r_stats kan outputte til noget som ## python kan laese direkte with open(total_cells_output) as tcls: counts = [] for line in tcls: nbr = int(line.split()[-1]) counts.append(nbr) # summary = r_univar(map='viewshed') #r_viewshed(input=from_np_raster, output='viewshed', max_distance=1000, memory=1424, coordinates=(701495,6201503), observer_elevation=500.0) r_out_gdal(overwrite=True, input='viewshed', output=burn_viewshed_rst) return sum(counts) #visible_cells
def geometry_mask(geom, data, affine, all_touched=True): # Create a numpy array to mask cells which don't intersect with the # polygon. Cells that intersect will have value of 0 (unmasked), the # rest are filled with 1s (masked) geom_mask = features.geometry_mask([geom], out_shape=data.shape, transform=affine, all_touched=all_touched) # Mask the data array, with modifications applied, by the query polygon return np.ma.array(data=data, mask=geom_mask)
def test_geometry_mask_invert(basic_geometry, basic_image_2x2): with Env(): assert np.array_equal( basic_image_2x2, geometry_mask( [basic_geometry], out_shape=DEFAULT_SHAPE, transform=Affine.identity(), invert=True ) )
def test_rasterize(): """ Test roundtripping from image mask to polygon and back again using rasterio's built-in functions. """ image_shape = (200, 100) coords = [(33, 27), (50, 22), (80, 55)] coords.append(coords[0]) poly = Polygon(shell=coords) p = mapping(poly) # Create initial mask mask0 = geometry_mask((p, ), image_shape, Affine.identity(), invert=True) m = mask0.astype(N.uint8) s = [geom for geom, value in shapes(m) if value == 1] mask1 = geometry_mask(s, image_shape, Affine.identity(), invert=True) assert mask0.sum() == mask1.sum() assert N.allclose(mask0, mask1)
def get_image_and_mask(self, tile_geometry, debug_base_file_name=None): km2_to_m2 = 1000.0 * 1000.0 surface_area_m2 = tile_geometry.area * km2_to_m2 min_tile_e = int(tile_geometry.bounds[0]) min_tile_n = int(tile_geometry.bounds[1]) max_tile_e = int(tile_geometry.bounds[2]) max_tile_n = int(tile_geometry.bounds[3]) image_bgr = self.download_image(max_tile_e, max_tile_n, min_tile_e, min_tile_n) # [a, b, d, e, xoff, yoff] # x' = a * x + b * y + xoff # y' = d * x + e * y + yoff m = [ self.__final_tile_size, 0, 0, self.__final_tile_size, -min_tile_e * self.__final_tile_size, -min_tile_n * self.__final_tile_size ] affine_geometry = affine_transform(tile_geometry, m) min_x = floor(affine_geometry.bounds[0]) min_y = floor(affine_geometry.bounds[1]) max_x = floor(affine_geometry.bounds[2]) max_y = floor(affine_geometry.bounds[3]) max_y_vertically_flipped = image_bgr.shape[0] - 1 - min_y min_y_vertically_flipped = image_bgr.shape[0] - 1 - max_y affine = rasterio.Affine(1, 0, min_x, 0, -1, max_y) pixels_within_geometry = geometry_mask( [affine_geometry], (max_y_vertically_flipped - min_y_vertically_flipped + 1, max_x - min_x + 1), affine, invert=True) image_bgri_cropped = image_bgr[ min_y_vertically_flipped:max_y_vertically_flipped + 1, min_x:max_x + 1, :] tile_file_name = None if debug_base_file_name is not None: centre_point = tile_geometry.centroid tile_code = tile_eastings_and_northings_to_tile_code( centre_point.x, centre_point.y) tile_file_name = debug_base_file_name + '-' + tile_code return image_bgri_cropped, pixels_within_geometry, surface_area_m2, tile_file_name
def xarray_geomask( ds: Union[xr.Dataset, xr.DataArray], geometry: Union[Polygon, MultiPolygon, Tuple[float, float, float, float]], geo_crs: str, ds_dims: Tuple[str, str] = ("y", "x"), ): """Mask a ``xarray.Dataset`` based on a geometry. Parameters ---------- ds : xarray.Dataset or xarray.DataArray The dataset(array) to be masked geometry : Polygon, MultiPolygon, or tuple of length 4 The geometry or bounding box to mask the data geo_crs : str The spatial reference of the input geometry ds_dims : tuple of str, optional The names of the vertical and horizontal dimensions (in that order) of the dataset, default to ("y", "x"). Returns ------- xarray.Dataset or xarray.DataArray The input dataset with a mask applied (np.nan) """ if not isinstance(ds_dims, tuple) or len(ds_dims) != 2: raise InvalidInputType("ds_dims", "tuple of length 2", '("y", "x")') if "crs" not in ds.attrs: raise ValueError("The input dataset is missing the crs attribute.") _geometry = geo2polygon(geometry, geo_crs, ds.crs) height, width = ds.sizes[ds_dims[0]], ds.sizes[ds_dims[1]] transform = get_transform(_geometry, height, width) _mask = rio_features.geometry_mask([_geometry], (height, width), transform, invert=True) coords = { ds_dims[0]: ds.coords[ds_dims[0]], ds_dims[1]: ds.coords[ds_dims[1]] } mask = xr.DataArray(_mask, coords, dims=ds_dims) ds_masked = ds.where(mask, drop=True) ds_masked.attrs["transform"] = transform ds_masked.attrs["bounds"] = _geometry.bounds return ds_masked
def get_image_and_mask(self, tile_geometry, debug_base_file_name=None): surface_area = tile_geometry.area min_tile_y = int(tile_geometry.bounds[0]) min_tile_x = int(tile_geometry.bounds[1]) max_tile_y = int(tile_geometry.bounds[2]) max_tile_x = int(tile_geometry.bounds[3]) image_bgr = self.download_image(max_tile_y, max_tile_x, min_tile_y, min_tile_x) # [a, b, d, e, xoff, yoff] # x' = a * x + b * y + xoff # y' = d * x + e * y + yoff m = [ 0, self.__tile_size, self.__tile_size, 0.0, -min_tile_x * self.__tile_size, -min_tile_y * self.__tile_size ] affine_geometry = affine_transform(tile_geometry, m) min_y = floor(affine_geometry.bounds[1]) min_x = floor(affine_geometry.bounds[0]) max_y = floor(affine_geometry.bounds[3]) max_x = floor(affine_geometry.bounds[2]) affine = rasterio.Affine(1, 0, min_x, 0, 1, min_y) pixels_within_geometry = geometry_mask( [affine_geometry], (max_y - min_y + 1, max_x - min_x + 1), affine, invert=True) image_bgr_cropped = image_bgr[min_y:max_y + 1, min_x:max_x + 1, :] tile_file_name = None if debug_base_file_name is not None: centre_point = tile_geometry.centroid centre_tile_x = int(centre_point.x) centre_tile_y = int(centre_point.y) centre_pixel_x = int( (centre_point.x - centre_tile_x) * self.__tile_size) centre_pixel_y = int( (centre_point.y - centre_tile_y) * self.__tile_size) tile_code = f'{centre_tile_x}_{centre_pixel_x}={centre_tile_y}_{centre_pixel_y}' tile_file_name = debug_base_file_name + '-' + tile_code return image_bgr_cropped, pixels_within_geometry, surface_area, tile_file_name
def clip_array_with_vector( array, array_affine, geometries, inverted=False, clip_buffer=0 ): """ Clips input array with a vector list. """ buffered_geometries = [] for feature in geometries: geom = shape(feature['geometry']).buffer(clip_buffer) if not isinstance(geom, (Polygon, MultiPolygon, GeometryCollection)): break if geom.is_empty: break if isinstance(geom, GeometryCollection): polygons = [ subgeom for subgeom in geom if isinstance(subgeom, (Polygon, MultiPolygon)) ] if not polygons: break new_geom = MultiPolygon(polygons) geom = new_geom buffered_geometries.append(geom) if buffered_geometries: mask = geometry_mask( buffered_geometries, array.shape, array_affine, invert=inverted ) else: if inverted: fill = False else: fill = True mask = np.full(array.shape, fill, dtype=bool) return ma.masked_array(array, mask)
def extract_area(geom, dem, **kwargs): # RasterIO's image-reading algorithm uses the location # of polygon centers to determine the extent of polygons msk = geometry_mask( (mapping(geom),), dem.shape, dem.transform, invert=True) # shrink mask to the minimal area for efficient extraction offset, msk = offset_mask(msk) window = tuple((o,o+s) for o,s in zip(offset,msk.shape)) # Currently just for a single band # We could generalize to multiple # bands if desired z = dem.read(1, window=window, masked=True) # mask out unused area z[msk == False] = N.ma.masked # Make vectors of rows and columns rows, cols = (N.arange(first,last,1) for first,last in window) # 2d arrays of x,y,z z = z.flatten() xyz = [i.flatten() for i in N.meshgrid(cols,rows)] + [z] x,y,z = tuple(i[z.mask == False] for i in xyz) # Transform into 3xn matrix of # flattened coordinate values coords = N.vstack((x,y,N.ones(z.shape))) # Get affine transform for pixel centers affine = dem.transform * Affine.translation(0.5, 0.5) # Transform coordinates to DEM's reference _ = N.array(affine).reshape((3,3)) coords = N.dot(_,coords) coords[2] = z return coords.transpose()
def determine_mosaic_quads_for_geometry(geometry: dict) -> List[str]: # Parameters width = MOSAIC_TILE_SIZE * 2 * abs(WEBM_ORIGIN) / (2**MOSAIC_LEVEL * 256) num_tiles = int(2.0**MOSAIC_LEVEL * 256 / MOSAIC_TILE_SIZE) # Generate a grid where values are True if the geometry is present and False otherwise transform = rio.transform.from_origin(WEBM_ORIGIN, -WEBM_ORIGIN, width, width) shape = shapely.geometry.shape(geometry) grid = np.flipud( geometry_mask([shape], (num_tiles, num_tiles), transform, all_touched=True, invert=True)) # Get quad labels quads = list() norths, easts = np.where(grid) for north, east in zip(norths, easts): quads.append('L15-{:04d}E-{:04d}N'.format(east, north)) return quads
def preprocessed_hazardlevel(self, geometry): hazardlevel = None reader = self.readers[0] for polygon in geometry.geoms: if not polygon.intersects(self.bbox): continue window = reader.window(*polygon.bounds) data = reader.read(1, window=window, masked=True) if data.shape[0] * data.shape[1] == 0: continue if data.mask.all(): continue geometry_mask = features.geometry_mask( [polygon], out_shape=data.shape, transform=reader.window_transform(window), all_touched=True, ) data.mask = data.mask | geometry_mask del geometry_mask if data.mask.all(): continue for level in ("HIG", "MED", "LOW", "VLO"): level_obj = HazardLevel.get(self.dbsession, level) if level_obj <= hazardlevel: break if level in self.type_settings["values"]: values = self.type_settings["values"][level] for value in values: if value in data: hazardlevel = level_obj break return hazardlevel
def get_segment_masks(image, polys, invert=True): """Image masks of the segments. Converted the segment polygons to binary images. :param image: CatalogImage :param polys: list of segment Shapely Polygons :param invert: specify whether or not to invert the image mask :returns: a list of image aois given the bounds of the segment and list of segment masks """ image_aoi_segs = [] seg_masks = [] for poly in polys: bounds = poly.bounds image_aoi_seg = image.aoi(bbox=list(bounds)) segment_mask = geometry_mask([poly], transform=image_aoi_seg.affine, out_shape=(image_aoi_seg.shape[1], image_aoi_seg.shape[2]), invert=invert) image_aoi_segs.append(image_aoi_seg) seg_masks.append(segment_mask) return image_aoi_segs, seg_masks
def preprocessed_hazardlevel(type_settings, layer, reader, geometry): hazardlevel = None for polygon in geometry.geoms: window = reader.window(*polygon.bounds) data = reader.read(1, window=window, masked=True) if data.shape[0] * data.shape[1] == 0: continue if data.mask.all(): continue geometry_mask = features.geometry_mask( [polygon], out_shape=data.shape, transform=reader.window_transform(window), all_touched=True) data.mask = data.mask | geometry_mask del geometry_mask if data.mask.all(): continue for level in (u'HIG', u'MED', u'LOW', u'VLO'): level_obj = HazardLevel.get(level) if level_obj <= hazardlevel: break if level in type_settings['values']: values = type_settings['values'][level] for value in values: if value in data: hazardlevel = level_obj break return hazardlevel
def mask(raster, shapes, nodata=None, crop=False, all_touched=False, invert=False, pad=False): """Mask the area outside of the input shapes with nodata. For all regions in the input raster outside of the regions defined by `shapes`, sets any data present to nodata. Parameters ---------- raster: rasterio RasterReader object Raster to which the mask will be applied. shapes: list of polygons Polygons are GeoJSON-like dicts specifying the boundaries of features in the raster to be kept. All data outside of specified polygons will be set to nodata. nodata: int or float (opt) Value representing nodata within each raster band. If not set, defaults to the nodata value for the input raster. If there is no set nodata value for the raster, it defaults to 0. crop: bool (opt) Whether to crop the raster to the extent of the data. Defaults to False. all_touched: bool (opt) Use all pixels touched by features. If False (default), use only pixels whose center is within the polygon or that are selected by Bresenhams line algorithm. invert: bool (opt) If True, mask will be True for pixels that overlap shapes. False by default. pad: bool (opt) If True, the cropped output will be padded in each direction by one half of a pixel. Defaults to False. Returns ------- tuple Two elements: masked : numpy ndarray Data contained in raster after applying the mask. out_transform : affine.Affine() Information for mapping pixel coordinates in `masked` to another coordinate system. """ if crop and invert: raise ValueError("crop and invert cannot both be True.") if nodata is None: if raster.nodata is not None: nodata = raster.nodata else: nodata = 0 # "North down" georeferencing or ungeoreferenced rasters require # bounds shuffling. north_up = raster.transform.e <= 0 # Calculate the bounds of all features. all_bounds = [ rasterio.features.bounds(shape, north_up=north_up) for shape in shapes] lefts, bottoms, rights, tops = zip(*all_bounds) if pad: dx = raster.res[0] / 2 dy = raster.res[1] / 2 else: dx = 0.0 dy = 0.0 if north_up: mask_bounds = (min(lefts) - dx, min(bottoms) - dy, max(rights) + dx, max(tops) + dy) else: mask_bounds = (min(lefts) - dx, max(bottoms) + dy, max(rights) + dx, min(tops) - dy) source_bounds = raster.bounds # Raise or warn about bounds mismatches. if rasterio.coords.disjoint_bounds(source_bounds, mask_bounds): if crop: raise ValueError("Input shapes do not overlap raster.") else: warnings.warn("GeoJSON outside bounds of existing output " + "raster. Are they in different coordinate " + "reference systems?") if crop: bounds_window = raster.window(*mask_bounds) # Get the window with integer height # and width that contains the bounds window. out_window = bounds_window.round_shape(op='ceil') height = int(out_window.height) width = int(out_window.width) out_shape = (raster.count, height, width) out_transform = raster.window_transform(out_window) logger.debug("Out window: %r", out_window) logger.debug("Out transform: %r", out_transform) else: out_window = None out_shape = (raster.count, raster.height, raster.width) out_transform = raster.transform # Read the window of imagery. out_image = raster.read(window=out_window, out_shape=out_shape, masked=True) mask_shape = out_image.shape[1:] shape_mask = geometry_mask(shapes, transform=out_transform, invert=invert, out_shape=mask_shape, all_touched=all_touched) out_image.mask = out_image.mask | shape_mask out_image.fill_value = nodata for i in range(raster.count): out_image[i] = out_image[i].filled(nodata) return out_image, out_transform
def notpreprocessed_hazardlevel(hazardtype, type_settings, layers, readers, geometry): level_vlo = HazardLevel.get(u'VLO') hazardlevel = None # Create some optimization caches polygons = {} bboxes = {} geometry_masks = {} # Storage for the geometry geometry_masks inverted_comparison = ('inverted_comparison' in type_settings and type_settings['inverted_comparison']) for level in (u'HIG', u'MED', u'LOW'): layer = layers[level] reader = readers[level] threshold = get_threshold(hazardtype, layer.local, layer.hazardlevel.mnemonic, layer.hazardunit) if threshold is None: raise ProcessException( 'No threshold found for {} {} {} {}' .format(hazardtype, 'local' if layer.local else 'global', layer.hazardlevel.mnemonic, layer.hazardunit)) for i in xrange(0, len(geometry.geoms)): if i not in polygons: polygon = geometry.geoms[i] bbox = polygon.bounds polygons[i] = polygon bboxes[i] = bbox else: polygon = polygons[i] bbox = bboxes[i] window = reader.window(*bbox) # data: MaskedArray data = reader.read(1, window=window, masked=True) # check if data is empty (cols x rows) if data.shape[0] * data.shape[1] == 0: continue # all data is masked which means that all is NODATA if data.mask.all(): continue if inverted_comparison: data = data < threshold else: data = data > threshold # some hazard types have a specific mask layer with very low # return period which should be used as mask for other layers # for example River Flood if ('mask_return_period' in type_settings): mask = readers['mask'].read(1, window=window, masked=True) if inverted_comparison: mask = mask < threshold else: mask = mask > threshold # apply the specific layer mask data.mask = ma.getmaskarray(data) | mask.filled(False) del mask if data.mask.all(): continue if i in geometry_masks: geometry_mask = geometry_masks[i] else: geometry_mask = features.geometry_mask( [polygon], out_shape=data.shape, transform=reader.window_transform(window), all_touched=True) geometry_masks[i] = geometry_mask data.mask = ma.getmaskarray(data) | geometry_mask del geometry_mask # If at least one value is True this means that there's at least # one raw value > threshold if data.any(): hazardlevel = layer.hazardlevel break # check one last time is array is filled with NODATA if data.mask.all(): continue # Here we have at least one value lower than the current level # threshold if hazardlevel is None: hazardlevel = level_vlo # we got a value for the level, no need to go further, this will be # the highest one if hazardlevel == layer.hazardlevel: break return hazardlevel
def test_geometry_mask_no_transform(basic_geometry): with pytest.raises(TypeError): geometry_mask( [basic_geometry], out_shape=DEFAULT_SHAPE, transform=None)
def notpreprocessed_hazardlevel(hazardtype, type_settings, layers, readers, geometry): level_VLO = HazardLevel.get(u'VLO') hazardlevel = None # Create some optimization caches polygons = {} bboxes = {} masks = {} inverted_comparison = ('inverted_comparison' in type_settings and type_settings['inverted_comparison']) for level in (u'HIG', u'MED', u'LOW'): layer = layers[level] reader = readers[level] threshold = get_threshold(hazardtype, layer.local, layer.hazardlevel.mnemonic, layer.hazardunit) if threshold is None: raise ProcessException( 'No threshold found for {} {} {} {}' .format(hazardtype, 'local' if layer.local else 'global', layer.hazardlevel.mnemonic, layer.hazardunit)) for i in xrange(0, len(geometry.geoms)): if i not in polygons: polygon = geometry.geoms[i] bbox = polygon.bounds polygons[i] = polygon bboxes[i] = bbox else: polygon = polygons[i] bbox = bboxes[i] window = reader.window(*bbox) data = reader.read(1, window=window, masked=True) if data.shape[0] * data.shape[1] == 0: continue if data.mask.all(): continue if inverted_comparison: data = data < threshold else: data = data > threshold if ('mask_return_period' in type_settings): mask = readers['mask'].read(1, window=window, masked=True) if inverted_comparison: mask = mask < threshold else: mask = mask > threshold data.mask = ma.getmaskarray(data) | mask.filled(False) if data.mask.all(): continue if i in masks: mask = masks[i] else: mask = features.geometry_mask( [polygon], out_shape=data.shape, transform=reader.window_transform(window), all_touched=True) masks[i] = mask data.mask = ma.getmaskarray(data) | mask if data.any(): hazardlevel = layer.hazardlevel break # No need to go further if data.mask.all(): continue if hazardlevel is None: hazardlevel = level_VLO if hazardlevel == layer.hazardlevel: break # No need to go further return hazardlevel
def mask(raster, shapes, nodata=None, crop=False, all_touched=False, invert=False): """Mask the area outside of the input shapes with nodata. For all regions in the input raster outside of the regions defined by `shapes`, sets any data present to nodata. Parameters ---------- raster: rasterio RasterReader object Raster to which the mask will be applied. shapes: list of polygons Polygons are GeoJSON-like dicts specifying the boundaries of features in the raster to be kept. All data outside of specified polygons will be set to nodata. nodata: int or float (opt) Value representing nodata within each raster band. If not set, defaults to the nodata value for the input raster. If there is no set nodata value for the raster, it defaults to 0. crop: bool (opt) Whether to crop the raster to the extent of the data. Defaults to False. all_touched: bool (opt) Use all pixels touched by features. If False (default), use only pixels whose center is within the polygon or that are selected by Bresenhams line algorithm. invert: bool (opt) If True, mask will be True for pixels that overlap shapes. False by default. Returns ------- masked: numpy ndarray Data contained in raster after applying the mask. out_transform: affine object Information for mapping pixel coordinates in `masked` to another coordinate system. """ if crop and invert: raise ValueError("crop and invert cannot both be True.") if nodata is None: if raster.nodata is not None: nodata = raster.nodata else: nodata = 0 all_bounds = [rasterio.features.bounds(shape) for shape in shapes] minxs, minys, maxxs, maxys = zip(*all_bounds) mask_bounds = (min(minxs), min(minys), max(maxxs), max(maxys)) invert_y = raster.affine.e > 0 source_bounds = raster.bounds if invert_y: source_bounds = [source_bounds[0], source_bounds[3], source_bounds[2], source_bounds[1]] if rasterio.coords.disjoint_bounds(source_bounds, mask_bounds): if crop: raise ValueError("Input shapes do not overlap raster.") else: warnings.warn("GeoJSON outside bounds of existing output " + "raster. Are they in different coordinate " + "reference systems?") if invert_y: mask_bounds = [mask_bounds[0], mask_bounds[3], mask_bounds[2], mask_bounds[1]] if crop: window = raster.window(*mask_bounds) out_transform = raster.window_transform(window) else: window = None out_transform = raster.affine out_image = raster.read(window=window, masked=True) out_shape = out_image.shape[1:] shape_mask = geometry_mask(shapes, transform=out_transform, invert=invert, out_shape=out_shape, all_touched=all_touched) out_image.mask = out_image.mask | shape_mask out_image.fill_value = nodata for i in range(raster.count): out_image[i] = out_image[i].filled(nodata) return out_image, out_transform
def raster_geometry_mask(dataset, shapes, all_touched=False, invert=False, crop=False, pad=False): """Create a mask from shapes, transform, and optional window within original raster. By default, mask is intended for use as a numpy mask, where pixels that overlap shapes are False. If shapes do not overlap the raster and crop=True, a ValueError is raised. Otherwise, a warning is raised, and a completely True mask is returned (if invert is False). Parameters ---------- dataset: a dataset object opened in 'r' mode Raster for which the mask will be created. shapes: list of polygons GeoJSON-like dict representation of polygons that will be used to create the mask. all_touched: bool (opt) Include a pixel in the mask if it touches any of the shapes. If False (default), include a pixel only if its center is within one of the shapes, or if it is selected by Bresenham's line algorithm. invert: bool (opt) If False (default), mask will be `False` inside shapes and `True` outside. If True, mask will be `True` inside shapes and `False` outside. crop: bool (opt) Whether to crop the dataset to the extent of the shapes. Defaults to False. pad: bool (opt) If True, the features will be padded in each direction by one half of a pixel prior to cropping dataset. Defaults to False. Returns ------- tuple Three elements: mask : numpy ndarray of type 'bool' Mask that is `True` outside shapes, and `False` within shapes. out_transform : affine.Affine() Information for mapping pixel coordinates in `masked` to another coordinate system. window: rasterio.windows.Window instance Window within original raster covered by shapes. None if crop is False. """ if crop and invert: raise ValueError("crop and invert cannot both be True.") if crop and pad: pad_x = 0.5 # pad by 1/2 of pixel size pad_y = 0.5 else: pad_x = 0 pad_y = 0 north_up = dataset.transform.e <= 0 rotated = dataset.transform.b != 0 or dataset.transform.d != 0 try: window = geometry_window(dataset, shapes, north_up=north_up, rotated=rotated, pad_x=pad_x, pad_y=pad_y) except WindowError: # If shapes do not overlap raster, raise Exception or UserWarning # depending on value of crop if crop: raise ValueError('Input shapes do not overlap raster.') else: warnings.warn('shapes are outside bounds of raster. ' 'Are they in different coordinate reference systems?') # Return an entirely True mask (if invert is False) mask = np.ones(shape=dataset.shape[-2:], dtype='bool') * (not invert) return mask, dataset.transform, None if crop: transform = dataset.window_transform(window) out_shape = (int(window.height), int(window.width)) else: window = None transform = dataset.transform out_shape = (int(dataset.height), int(dataset.width)) mask = geometry_mask(shapes, transform=transform, invert=invert, out_shape=out_shape, all_touched=all_touched) return mask, transform, window
def mask( ctx, files, output, geojson_mask, driver, all_touched, crop, invert, creation_options): """Masks in raster using GeoJSON features (masks out all areas not covered by features), and optionally crops the output raster to the extent of the features. Features are assumed to be in the same coordinate reference system as the input raster. GeoJSON must be the first input file or provided from stdin: > rio mask input.tif output.tif --geojson-mask features.json > rio mask input.tif output.tif --geojson-mask - < features.json If the output raster exists, it will be completely overwritten with the results of this operation. The result is always equal to or within the bounds of the input raster. --crop and --invert options are mutually exclusive. --crop option is not valid if features are completely outside extent of input raster. """ from rasterio.features import geometry_mask from rasterio.features import bounds as calculate_bounds verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1 output, files = resolve_inout(files=files, output=output) input = files[0] if geojson_mask is None: click.echo('No GeoJSON provided, INPUT will be copied to OUTPUT', err=True) shutil.copy(input, output) return if crop and invert: click.echo('Invert option ignored when using --crop', err=True) invert = False with rasterio.drivers(CPL_DEBUG=verbosity > 2): try: with click.open_file(geojson_mask) as f: geojson = json.loads(f.read()) except ValueError: raise click.BadParameter('GeoJSON could not be read from ' '--geojson-mask or stdin', param_hint='--geojson-mask') if 'features' in geojson: geometries = (f['geometry'] for f in geojson['features']) elif 'geometry' in geojson: geometries = (geojson['geometry'], ) else: raise click.BadParameter('Invalid GeoJSON', param=input, param_hint='input') bounds = geojson.get('bbox', calculate_bounds(geojson)) with rasterio.open(input) as src: # If y pixel value is positive, then invert y dimension in bounds invert_y = src.affine.e > 0 src_bounds = src.bounds if invert_y: src_bounds = [src.bounds[0], src.bounds[3], src.bounds[2], src.bounds[1]] has_disjoint_bounds = disjoint_bounds(bounds, src_bounds) if crop: if has_disjoint_bounds: raise click.BadParameter('not allowed for GeoJSON outside ' 'the extent of the input raster', param=crop, param_hint='--crop') if invert_y: bounds = (bounds[0], bounds[3], bounds[2], bounds[1]) window = src.window(*bounds) transform = src.window_transform(window) (r1, r2), (c1, c2) = window mask_shape = (r2 - r1, c2 - c1) else: if has_disjoint_bounds: click.echo('GeoJSON outside bounds of existing output ' 'raster. Are they in different coordinate ' 'reference systems?', err=True) window = None transform = src.affine mask_shape = src.shape mask = geometry_mask( geometries, out_shape=mask_shape, transform=transform, all_touched=all_touched, invert=invert) meta = src.meta.copy() meta.update(**creation_options) meta.update({ 'driver': driver, 'height': mask.shape[0], 'width': mask.shape[1], 'transform': transform }) with rasterio.open(output, 'w', **meta) as out: for bidx in range(1, src.count + 1): img = src.read(bidx, masked=True, window=window) img.mask = img.mask | mask out.write_band(bidx, img.filled(src.nodatavals[bidx-1]))