Ejemplo n.º 1
0
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))
Ejemplo n.º 2
0
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]
Ejemplo n.º 3
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]
Ejemplo n.º 4
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]
Ejemplo n.º 5
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
Ejemplo n.º 6
0
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)
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
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))
Ejemplo n.º 11
0
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()))
Ejemplo n.º 12
0
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
Ejemplo n.º 13
0
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)
Ejemplo n.º 14
0
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]
Ejemplo n.º 15
0
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
Ejemplo n.º 16
0
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
Ejemplo n.º 17
0
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
Ejemplo n.º 18
0
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([])
Ejemplo n.º 19
0
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]
Ejemplo n.º 20
0
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]
Ejemplo n.º 21
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)
Ejemplo n.º 22
0
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()
        )
    )
Ejemplo n.º 23
0
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
Ejemplo n.º 24
0
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()
            )
        )
Ejemplo n.º 25
0
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
Ejemplo n.º 26
0
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)
Ejemplo n.º 27
0
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
            )
        )
Ejemplo n.º 28
0
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)
Ejemplo n.º 29
0
    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
Ejemplo n.º 30
0
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
Ejemplo n.º 31
0
    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
Ejemplo n.º 32
0
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)
Ejemplo n.º 33
0
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()
Ejemplo n.º 34
0
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
Ejemplo n.º 36
0
    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
Ejemplo n.º 37
0
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
Ejemplo n.º 38
0
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
Ejemplo n.º 39
0
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
Ejemplo n.º 40
0
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
Ejemplo n.º 41
0
def test_geometry_mask_no_transform(basic_geometry):
    with pytest.raises(TypeError):
        geometry_mask(
            [basic_geometry],
            out_shape=DEFAULT_SHAPE,
            transform=None)
Ejemplo n.º 42
0
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
Ejemplo n.º 43
0
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
Ejemplo n.º 44
0
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
Ejemplo n.º 45
0
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]))