Source code for hydromt_wflow.workflows.glaciers

"""Glaciers workflows for Wflow plugin."""

import logging

import numpy as np
import pandas as pd

logger = logging.getLogger(__name__)


__all__ = ["glaciermaps", "glacierattrs"]


[docs] def glaciermaps( gdf, ds_like, id_column="simple_id", elevtn_name="elevtn", logger=logger, ): """Return glacier maps (see list below) at model resolution. The following glacier maps are calculated: - wflow_glacierareas: glacier IDs [ID] - wflow_glacierfrac: area fraction of glacier per cell [-] - wflow_glacierstore: storage (volume) of glacier per cell [mm] Parameters ---------- gdf : geopandas.GeoDataFrame GeoDataFrame containing glacier geometries and attributes. ds_like : xarray.DataArray Dataset at model resolution. id_column : str, optional, one of "simple_id", "C3S_id", "RGI_id", or "GLIMS_id" Column used for the glacier IDs, see data/data_sources.yml. Returns ------- ds_out : xarray.Dataset Dataset containing gridded glacier data """ # Rasterize the GeoDataFrame to get the areas mask of glaciers with their ids ds_out = ds_like.raster.rasterize( gdf, col_name=id_column, nodata=0, all_touched=True, dtype=None, sindex=False, ).astype("int32") ds_out = ds_out.rename("glacareas") ds_out = ds_out.to_dataset() # Calculate glacier storage for each glacier # 'old' version to calculate storage, by calculating area from the glacier geometry # proj = partial(pyproj.transform, pyproj.Proj('epsg:4326'), # pyproj.Proj('epsg:3857')) # def calcStore(polygon, k=0.2055, gamma=1.375, proj=proj): # return np.int(k * transform(proj, polygon).area ** gamma) # gdf['glaciervolume'] = gdf.apply(lambda row: calcStore(row['geometry']), axis=1) # def convStoremm(polygon, volm3, rhoice=0.9, proj=proj): # return np.int(rhoice * volm3 / transform(proj, polygon).area * 1000) # gdf['glacierstore'] = gdf.apply(lambda row: convStoremm(row['geometry'], # row['glaciervolume']), axis=1) # 'new' version to calculate storage, because the 'old' version is now returning # inf/nan values, using the area listed in the dataset gdf["geometry"] = gdf.geometry.buffer(0) # fix potential geometry errors # TODO: use alternative to calucate area? There seems to be a factor 2 difference # with the AREA column # gdf["AREA2"] = gdf.to_crs(3857).area / 1e6 # km2, area calculation # needs projected crs def calcStore(areakm2, k=0.2055, gamma=1.375): return int(k * (areakm2 * 1e6) ** gamma) def convStoremm(areakm2, volm3, rhoice=0.9): return int(rhoice * volm3 / (areakm2 * 1e6) * 1000) gdf["glaciervolume"] = gdf.apply(lambda row: calcStore(row["AREA"]), axis=1) gdf["glacierstore"] = gdf.apply( lambda row: convStoremm(row["AREA"], row["glaciervolume"]), axis=1 ) # Create vector grid (for calculating fraction and storage per grid cell) logger.debug( "Creating vector grid for calculating glacier fraction and \ storage per grid cell" ) elevtn = ds_like[elevtn_name] idx_valid = np.where(elevtn.values.flatten() != elevtn.raster.nodata)[0] gdf_grid = ds_like.raster.vector_grid().loc[idx_valid] gdf_grid["glacierfrac"] = np.zeros(len(idx_valid), dtype=np.float32) gdf_grid["glacierstore"] = np.zeros(len(idx_valid), dtype=np.float32) gdf_grid["area"] = gdf_grid.to_crs(3857).area # area calculation in projected crs # Calculate fraction and storage (i.e. volume) per (vector) grid cell # Looping over each vector GLACIER logger.debug("Setting glacierfrac and store values per glacier.") for i in range(len(gdf)): glacier = gdf.iloc[i] gridded_glacier = gdf_grid.intersection(glacier.geometry) gridded_glacier = gridded_glacier.loc[~gridded_glacier.is_empty] idxs = gridded_glacier.index # logger.debug(f"index: {i}, ID: {glacier[id_column]} // {len(idxs)} cells") if np.any(idxs): garea_cell = gridded_glacier.to_crs( 3857 ).area # area calculation needs projected crs gfrac = garea_cell / np.sum(garea_cell) gdf_grid.loc[idxs, "glacierfrac"] += garea_cell / gdf_grid.loc[idxs, "area"] gdf_grid.loc[idxs, "glacierstore"] += gfrac * glacier["glacierstore"] # reproject back to original projection # Create the rasterized glacier storage map ds_out["glacstore"] = ds_like.raster.rasterize( gdf_grid, col_name="glacierstore", nodata=0, all_touched=False, dtype=None, sindex=False, ).astype("float32") # Create the rasterized glacier fraction map ds_out["glacfracs"] = ds_like.raster.rasterize( gdf_grid, col_name="glacierfrac", nodata=0, all_touched=False, dtype=None, sindex=False, ).astype("float32") ds_out["glacareas"].raster.set_nodata(0) ds_out["glacstore"].raster.set_nodata(0) ds_out["glacfracs"].raster.set_nodata(0) return ds_out
[docs] def glacierattrs( gdf, TT=1.3, Cfmax=5.3, SIfrac=0.002, id_column="simple_id", logger=logger, ): """Return glacier intbls (see list below). The following glacier intbls are calculated: - glacTempThresh: glacier temperature threshold [°C] - glacCfmax: glacier melting factor [mm/(°C*day)] - glacSIfrac: fraction of snowpack converted into ice and added to \ glacier storage [-] Parameters ---------- gdf : geopandas.GeoDataFrame GeoDataFrame containing glacier geometries and attributes. TT : float, optional Default value for glacier temperature threshold. Cfmax : float, optional Default value for glacier melting factor. SIfrac : float, optional Default value for fraction of snowpack converted into ice and added to \ glacier storage. id_column : str, optional, one of "simple_id", "C3S_id", "RGI_id", or "GLIMS_id" Column used for the glacier IDs, see data/data_sources.yml. Returns ------- df_out : pandas.DataFrame DataFrame containing glacier attributes. """ # Initialize output DataFrame with empty values and glacier ID df_out = pd.DataFrame( index=range(len(gdf[id_column])), columns=( [ "glacId", "glacTempThresh", "glacCfmax", "glacSIfrac", ] ), ) df_out["glacId"] = gdf[id_column].values # Fill in other attributes df_out["glacTempThresh"] = list(np.full(len(gdf.index), TT)) df_out["glacCfmax"] = list(np.full(len(gdf.index), Cfmax)) df_out["glacSIfrac"] = list(np.full(len(gdf.index), SIfrac)) return df_out