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
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 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)
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)
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)
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
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
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 ]