Esempio n. 1
0
def nlcd(
    geometry: Union[Polygon, MultiPolygon, Tuple[float, float, float, float]],
    resolution: float,
    years: Optional[Dict[str, Optional[int]]] = None,
    geo_crs: str = DEF_CRS,
    crs: str = DEF_CRS,
) -> xr.Dataset:
    """Get data from NLCD database (2016).

    Download land use/land cover data from NLCD (2016) database within
    a given geometry in epsg:4326.

    Parameters
    ----------
    geometry : Polygon, MultiPolygon, or tuple of length 4
        The geometry or bounding box (west, south, east, north) for extracting the data.
    resolution : float
        The data resolution in meters. The width and height of the output are computed in pixel
        based on the geometry bounds and the given resolution.
    years : dict, optional
        The years for NLCD data as a dictionary, defaults to
        {'impervious': 2016, 'cover': 2016, 'canopy': 2016}. Set the value of a layer to None,
        to ignore it.
    geo_crs : str, optional
        The CRS of the input geometry, defaults to epsg:4326.
    crs : str, optional
        The spatial reference system to be used for requesting the data, defaults to
        epsg:4326.

    Returns
    -------
     xarray.DataArray
         NLCD within a geometry
    """
    years = {"impervious": 2016, "cover": 2016, "canopy": 2016} if years is None else years
    layers = _nlcd_layers(years)

    _geometry = geoutils.geo2polygon(geometry, geo_crs, crs)

    wms = WMS(ServiceURL().wms.mrlc, layers=layers, outformat="image/geotiff", crs=crs)
    r_dict = wms.getmap_bybox(_geometry.bounds, resolution, box_crs=crs)

    ds = geoutils.gtiff2xarray(r_dict, _geometry, crs)

    if isinstance(ds, xr.DataArray):
        ds = ds.to_dataset()

    for n in ds.keys():
        if "cover" in n.lower():
            ds = ds.rename({n: "cover"})
            ds.cover.attrs["units"] = "classes"
        elif "canopy" in n.lower():
            ds = ds.rename({n: "canopy"})
            ds.canopy.attrs["units"] = "%"
        elif "impervious" in n.lower():
            ds = ds.rename({n: "impervious"})
            ds.impervious.attrs["units"] = "%"

    return ds
Esempio n. 2
0
    def __init__(
        self,
        years: Optional[Mapping[str, Union[int, List[int]]]] = None,
        region: str = "L48",
        crs: str = DEF_CRS,
        expire_after: float = EXPIRE,
        disable_caching: bool = False,
    ) -> None:
        default_years = {
            "impervious": [2019],
            "cover": [2019],
            "canopy": [2016],
            "descriptor": [2019],
        }
        years = default_years if years is None else years
        if not isinstance(years, dict):
            raise InvalidInputType("years", "dict", f"{default_years}")
        self.years = tlz.valmap(lambda x: x
                                if isinstance(x, list) else [x], years)
        self.region = region
        self.valid_crs = ogc_utils.valid_wms_crs(ServiceURL().wms.mrlc)
        if pyproj.CRS(crs).to_string().lower() not in self.valid_crs:
            raise InvalidInputValue("crs", self.valid_crs)
        self.crs = crs
        self.expire_after = expire_after
        self.disable_caching = disable_caching
        self.layers = self.get_layers(self.region, self.years)
        self.units = OrderedDict((("impervious", "%"), ("cover", "classes"),
                                  ("canopy", "%"), ("descriptor", "classes")))
        self.types = OrderedDict((("impervious", "f4"), ("cover", "u1"),
                                  ("canopy", "f4"), ("descriptor", "u1")))
        self.nodata = OrderedDict((("impervious", 0), ("cover", 127),
                                   ("canopy", 0), ("descriptor", 127)))

        self.wms = WMS(
            ServiceURL().wms.mrlc,
            layers=self.layers,
            outformat="image/geotiff",
            crs=self.crs,
            validation=False,
            expire_after=self.expire_after,
            disable_caching=self.disable_caching,
        )
Esempio n. 3
0
def test_gtiff2array(geometry_nat):
    url_wms = "https://www.fws.gov/wetlands/arcgis/services/Wetlands_Raster/ImageServer/WMSServer"
    wms = WMS(
        url_wms,
        layers="0",
        outformat="image/tiff",
        crs="epsg:3857",
    )
    r_dict = wms.getmap_bybox(
        geometry_nat.bounds,
        1e3,
        box_crs=DEF_CRS,
    )
    wetlands_box = geoutils.gtiff2xarray(r_dict, geometry_nat.bounds, DEF_CRS)
    wetlands_msk = geoutils.xarray_geomask(wetlands_box, geometry_nat, DEF_CRS)
    wetlands = geoutils.gtiff2xarray(r_dict, geometry_nat, DEF_CRS)

    assert (abs(wetlands_msk.isel(band=0).mean().values.item() - 17.208) < 1e-3
            and
            abs(wetlands.isel(band=0).mean().values.item() - 16.542) < 1e-3)
Esempio n. 4
0
def _elevation_bybox(
    bbox: Tuple[float, float, float, float],
    crs: str,
    resolution: float,
) -> xr.DataArray:
    """Get elevation from DEM data for a list of coordinates.

    This function is intended for getting elevations for a gridded dataset.

    Parameters
    ----------
    bbox : tuple of two lists of floats
        A list containing x- and y-coordinates of a mesh, [[x-coords], [y-coords]].
    crs : str
        The spatial reference system of the input grid, defaults to epsg:4326.
    resolution : float
        The accuracy of the output, defaults to 10 m which is the highest
        available resolution that covers CONUS. Note that higher resolution
        increases computation time so chose this value with caution.

    Returns
    -------
    numpy.ndarray
        An array of elevations where its index matches the input gridxy list
    """
    if not isinstance(bbox, tuple) or len(bbox) != 4:
        raise InvalidInputType("bbox", "tuple of length 4")

    ratio_min = 0.01
    ratio_x = abs((bbox[2] - bbox[0]) / bbox[0])
    ratio_y = abs((bbox[3] - bbox[1]) / bbox[1])
    if (ratio_x < ratio_min) or (ratio_y < ratio_min):
        rad = ratio_min * abs(bbox[0])
        bbox = (bbox[0] - rad, bbox[1] - rad, bbox[2] + rad, bbox[3] + rad)

    req_crs = crs if crs.lower() in [DEF_CRS, "epsg:3857"] else DEF_CRS
    wms = WMS(ServiceURL().wms.nm_3dep,
              layers="3DEPElevation:None",
              outformat="image/tiff",
              crs=req_crs)
    return wms.getmap_bybox(bbox, resolution, box_crs=crs)
Esempio n. 5
0
def test_wms(geometry_nat):
    url_wms = ServiceURL().wms.fws

    wms_111 = WMS(url_wms,
                  layers="0",
                  outformat="image/tiff",
                  crs=DEF_CRS,
                  version="1.1.1",
                  validation=False)
    r_dict_111 = wms_111.getmap_bybox(geometry_nat.bounds, 20, DEF_CRS)
    wms = WMS(url_wms, layers="0", outformat="image/tiff", crs=DEF_CRS)
    print(wms)
    r_dict = wms.getmap_bybox(geometry_nat.bounds, 20, DEF_CRS)
    assert (wms_111.get_validlayers()["0"] == "Wetlands_Raster"
            and sys.getsizeof(r_dict_111["0_dd_0_0"]) == 12536763
            and sys.getsizeof(r_dict["0_dd_0_0"]) == 12536763)
Esempio n. 6
0
def elevation_bygrid(
    gridxy: Tuple[List[float], List[float]],
    crs: str,
    resolution: float,
    resampling: rio_warp.Resampling = rio_warp.Resampling.bilinear,
) -> xr.DataArray:
    """Get elevation from DEM data for a list of coordinates.

    This function is intended for getting elevations for a gridded dataset.

    Parameters
    ----------
    gridxy : tuple of two lists of floats
        A list containing x- and y-coordinates of a mesh, [[x-coords], [y-coords]].
    crs : str
        The spatial reference system of the input grid, defaults to epsg:4326.
    resolution : float
        The accuracy of the output, defaults to 10 m which is the highest
        available resolution that covers CONUS. Note that higher resolution
        increases computation time so chose this value with caution.
    resampling : rasterio.warp.Resampling
        The reasmpling method to use if the input crs is not in the supported
        3DEP's CRS list which are epsg:4326 and epsg:3857. It defaults to bilinear.
        The available methods can be found `here <https://rasterio.readthedocs.io/en/latest/api/rasterio.enums.html#rasterio.enums.Resampling>`__

    Returns
    -------
    xarray.DataArray
        A data array with dims ``x`` and ``y``
    """
    gx, gy = gridxy
    bbox = (min(gx), min(gy), max(gx), max(gy))

    ratio_min = 0.01
    ratio_x = abs((bbox[2] - bbox[0]) / bbox[0])
    ratio_y = abs((bbox[3] - bbox[1]) / bbox[1])
    if (ratio_x < ratio_min) or (ratio_y < ratio_min):
        rad = ratio_min * abs(bbox[0])
        bbox = (bbox[0] - rad, bbox[1] - rad, bbox[2] + rad, bbox[3] + rad)

    req_crs = crs if crs.lower() in [DEF_CRS, "epsg:3857"] else DEF_CRS

    wms = WMS(
        ServiceURL().wms.nm_3dep,
        layers="3DEPElevation:None",
        outformat="image/tiff",
        crs=req_crs,
    )

    r_dict = wms.getmap_bybox(
        bbox,
        resolution,
        box_crs=crs,
    )

    def reproject(content):
        with rio.MemoryFile() as memfile:
            memfile.write(content)
            with memfile.open() as src:
                transform, width, height = rio_warp.calculate_default_transform(
                    src.crs, crs, src.width, src.height, *src.bounds
                )
                kwargs = src.meta.copy()
                kwargs.update(
                    {"crs": crs, "transform": transform, "width": width, "height": height}
                )

                with rio.vrt.WarpedVRT(src, **kwargs) as vrt:
                    if crs != src.crs:
                        for i in range(1, src.count + 1):
                            rio_warp.reproject(
                                source=rio.band(src, i),
                                destination=rio.band(vrt, i),
                                src_transform=src.transform,
                                src_crs=src.crs,
                                dst_transform=transform,
                                crs=crs,
                                resampling=resampling,
                            )

                    da = xr.open_rasterio(vrt)
                    try:
                        da = da.squeeze("band", drop=True)
                    except ValueError:
                        pass

                    da.name = "elevation"
                    da.attrs["transform"] = transform
                    da.attrs["res"] = (transform[0], transform[4])
                    da.attrs["bounds"] = tuple(vrt.bounds)
                    da.attrs["nodatavals"] = vrt.nodatavals
                    da.attrs["crs"] = vrt.crs.to_string()
        return da

    _elev = xr.merge([reproject(c) for c in r_dict.values()])
    elev = _elev.elevation.interp(x=gx, y=gy)
    elev.attrs["units"] = "meters"
    return elev
Esempio n. 7
0
def get_map(
    layers: Union[str, List[str]],
    geometry: Union[Polygon, Tuple[float, float, float, float]],
    resolution: float,
    geo_crs: str = DEF_CRS,
    crs: str = DEF_CRS,
) -> xr.DataArray:
    """Access to `3DEP <https://www.usgs.gov/core-science-systems/ngp/3dep>`__ service.

    The 3DEP service has multi-resolution sources so depending on the user
    provided resolution the data is resampled on server-side based
    on all the available data sources. The following layers are available:
    - "DEM"
    - "Hillshade Gray"
    - "Aspect Degrees"
    - "Aspect Map"
    - "GreyHillshade_elevationFill"
    - "Hillshade Multidirectional"
    - "Slope Map"
    - "Slope Degrees"
    - "Hillshade Elevation Tinted"
    - "Height Ellipsoidal"
    - "Contour 25"
    - "Contour Smoothed 25"

    Parameters
    ----------
    layers : str or list
        A valid 3DEP layer or a list of them
    geometry : Polygon, MultiPolygon, or tuple
        A shapely Polygon or a bounding box (west, south, east, north)
    resolution : float
        The data resolution in meters. The width and height of the output are computed in pixel
        based on the geometry bounds and the given resolution.
    geo_crs : str, optional
        The spatial reference system of the input geometry, defaults to
        epsg:4326.
    crs : str, optional
        The spatial reference system to be used for requesting the data, defaults to
        epsg:4326.

    Returns
    -------
    xarray.DataArray
        The requeted data within the geometry
    """
    if not isinstance(geometry, (Polygon, MultiPolygon, tuple)):
        raise InvalidInputType("geometry", "Polygon or tuple of length 4")

    _geometry = geoutils.geo2polygon(geometry, geo_crs, crs)

    _layers = layers if isinstance(layers, list) else [layers]
    if "DEM" in _layers:
        _layers[_layers.index("DEM")] = "None"

    _layers = [f"3DEPElevation:{lyr}" for lyr in _layers]

    wms = WMS(ServiceURL().wms.nm_3dep, layers=_layers, outformat="image/tiff", crs=crs)
    r_dict = wms.getmap_bybox(_geometry.bounds, resolution, box_crs=crs)

    ds = geoutils.gtiff2xarray(r_dict, _geometry, crs)

    valid_layers = wms.get_validlayers()
    rename = {lyr: lyr.split(":")[-1].replace(" ", "_").lower() for lyr in valid_layers}
    rename.update({"3DEPElevation:None": "elevation"})

    if isinstance(ds, xr.DataArray):
        ds.name = rename[ds.name]
    else:
        ds = ds.rename({n: rename[n] for n in ds.keys()})

    return ds
Esempio n. 8
0
class _NLCD:
    """Get data from NLCD database (2019).

    Parameters
    ----------
    years : dict, optional
        The years for NLCD layers as a dictionary, defaults to
        ``{'impervious': [2019], 'cover': [2019], 'canopy': [2019], "descriptor": [2019]}``.
        Layers that are not in years are ignored, e.g., ``{'cover': [2016, 2019]}`` returns
        land cover data for 2016 and 2019.
    region : str, optional
        Region in the US, defaults to ``L48``. Valid values are L48 (for CONUS), HI (for Hawaii),
        AK (for Alaska), and PR (for Puerto Rico). Both lower and upper cases are acceptable.
    crs : str, optional
        The spatial reference system to be used for requesting the data, defaults to
        ``epsg:4326``.
    expire_after : int, optional
        Expiration time for response caching in seconds, defaults to -1 (never expire).
    disable_caching : bool, optional
        If ``True``, disable caching requests, defaults to False.
    """
    def __init__(
        self,
        years: Optional[Mapping[str, Union[int, List[int]]]] = None,
        region: str = "L48",
        crs: str = DEF_CRS,
        expire_after: float = EXPIRE,
        disable_caching: bool = False,
    ) -> None:
        default_years = {
            "impervious": [2019],
            "cover": [2019],
            "canopy": [2016],
            "descriptor": [2019],
        }
        years = default_years if years is None else years
        if not isinstance(years, dict):
            raise InvalidInputType("years", "dict", f"{default_years}")
        self.years = tlz.valmap(lambda x: x
                                if isinstance(x, list) else [x], years)
        self.region = region
        self.valid_crs = ogc_utils.valid_wms_crs(ServiceURL().wms.mrlc)
        if pyproj.CRS(crs).to_string().lower() not in self.valid_crs:
            raise InvalidInputValue("crs", self.valid_crs)
        self.crs = crs
        self.expire_after = expire_after
        self.disable_caching = disable_caching
        self.layers = self.get_layers(self.region, self.years)
        self.units = OrderedDict((("impervious", "%"), ("cover", "classes"),
                                  ("canopy", "%"), ("descriptor", "classes")))
        self.types = OrderedDict((("impervious", "f4"), ("cover", "u1"),
                                  ("canopy", "f4"), ("descriptor", "u1")))
        self.nodata = OrderedDict((("impervious", 0), ("cover", 127),
                                   ("canopy", 0), ("descriptor", 127)))

        self.wms = WMS(
            ServiceURL().wms.mrlc,
            layers=self.layers,
            outformat="image/geotiff",
            crs=self.crs,
            validation=False,
            expire_after=self.expire_after,
            disable_caching=self.disable_caching,
        )

    def __repr__(self) -> str:
        """Return NLCD's WMS information."""
        return self.wms.__repr__()

    def get_response(self, bounds: Tuple[float, float, float, float],
                     resolution: float) -> Dict[str, bytes]:
        """Get response from a url."""
        return self.wms.getmap_bybox(bounds,
                                     resolution,
                                     self.crs,
                                     kwargs={"styles": "raster"})

    def to_xarray(
            self,
            r_dict: Dict[str, bytes],
            geometry: Union[Polygon, MultiPolygon, None] = None) -> xr.Dataset:
        """Convert response to xarray.DataArray."""
        if isinstance(geometry, (Polygon, MultiPolygon)):
            gtiff2xarray = tlz.partial(geoutils.gtiff2xarray,
                                       geometry=geometry,
                                       geo_crs=self.crs)
        else:
            gtiff2xarray = tlz.partial(geoutils.gtiff2xarray)

        try:
            _ds = gtiff2xarray(r_dict=r_dict)
        except rio.RasterioIOError as ex:
            raise ServiceUnavailable(self.wms.url) from ex

        ds: xr.Dataset = _ds.to_dataset() if isinstance(_ds,
                                                        xr.DataArray) else _ds
        ds.attrs = _ds.attrs
        for lyr in self.layers:
            name = [n for n in self.units if n in lyr.lower()][-1]
            lyr_name = f"{name}_{lyr.split('_')[1]}"
            ds = ds.rename({lyr: lyr_name})
            ds[lyr_name].attrs["units"] = self.units[name]
            ds[lyr_name] = ds[lyr_name].astype(self.types[name])
            ds[lyr_name].attrs["nodatavals"] = (self.nodata[name], )
        return ds

    @staticmethod
    def get_layers(region: str, years: Dict[str, List[int]]) -> List[str]:
        """Get NLCD layers for the provided years dictionary."""
        valid_regions = ["L48", "HI", "PR", "AK"]
        region = region.upper()
        if region not in valid_regions:
            raise InvalidInputValue("region", valid_regions)

        nlcd_meta = helpers.nlcd_helper()

        names = ["impervious", "cover", "canopy", "descriptor"]
        avail_years = {n: nlcd_meta[f"{n}_years"] for n in names}

        if any(yr not in avail_years[lyr] or lyr not in names
               for lyr, yrs in years.items() for yr in yrs):
            vals = [
                f"\n{lyr}: {', '.join(str(y) for y in yr)}"
                for lyr, yr in avail_years.items()
            ]
            raise InvalidInputValue("years", vals)

        def layer_name(lyr: str) -> str:
            if lyr == "canopy":
                return "Tree_Canopy"
            if lyr == "cover":
                return "Land_Cover_Science_Product"
            if lyr == "impervious":
                return "Impervious"
            return "Impervious_Descriptor" if region == "AK" else "Impervious_descriptor"

        return [
            f"NLCD_{yr}_{layer_name(lyr)}_{region}"
            for lyr, yrs in years.items() for yr in yrs
        ]