diff --git a/.zenodo.json b/.zenodo.json index a90860f8..9a733d62 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -1,17 +1,17 @@ { - "title": "hydroMT-sfincs", - "description": "SFINCS plugin for hydroMT.", + "title": "HydroMT-SFINCS", + "description": "SFINCS plugin for HydroMT.", "upload_type": "software", "creators": [ + { + "affiliation":"Deltares", + "name": "de Goede, Roel" + }, { "affiliation": "Deltares", "name": "Eilander, Dirk", "orcid": "0000-0002-0951-8418" }, - { - "affiliation":"Deltares", - "name": "de Goede, Roel" - } { "affiliation": "Deltares", "name": "Leijnse, Tim", @@ -21,8 +21,12 @@ "affiliation": "Deltares", "name": "Winsemius, Hessel C.", "orcid": "0000-0001-5471-172X" + }, + { + "affiliation": "Deltares-USA", + "name": "van Ormondt, Maarten" } ], "access_right": "open", - "license": "GPL-3.0", + "license": "GPLv3" } \ No newline at end of file diff --git a/README.rst b/README.rst index 26bc8f08..e95259d9 100644 --- a/README.rst +++ b/README.rst @@ -41,7 +41,7 @@ from conda-forge in a clean environment, see `installation guide. How to cite? ------------ -To reference the software please use the the DOI provided in the Zenodo badge |doi| that points to the latest release. +To reference the software please use the the DOI provided in the Zenodo badge that points to the latest release |doi|. The following paper presents a real-world application of HydroMT-SFINCS: diff --git a/docs/changelog.rst b/docs/changelog.rst index 7bb247e8..0332fa60 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,19 +5,68 @@ All notable changes to this project will be documented in this page. The format is based on `Keep a Changelog`_, and this project adheres to `Semantic Versioning`_. -Unreleased ----------- +v1.0 (17 April 2023) +==================== + +This release is a major update of the SfincsModel interface. It contains many new features, +such as support for *rotated grids*, *subgrid* and improved support for *building models from Python* scripts. +The documentation and exmaples have been updated to reflect these changes. + +The release however also contains several breaking changes as we have tried to improve the +consistency of the interface and match it more closely to the SFINCS model itself. +Please carefully check the API reference for the new methods and arguments. + +Main differences +---------------- +- `setup_region` has been replaced by `setup_grid_from_region` and `setup_grid`. + This method actually creates an empty regular grid based on a region of interest or user-defined coordinates, shape, rotation, etc.. +- `setup_dep` has replaced `setup_topobathy` and `setup_merge_topobathy`. + This method can now also be used to setup a bathymetry map from multiple sources at once. +- `setup_mask_active` has replaced `setup_mask`. +- `setup_mask_bounds` has replaced `setup_bounds` +- `setup_waterlevel_forcing` has replaced `setup_h_forcing` and now supports merging fording from several data sources +- `setup_discharge_forcing` has replaced `setup_q_forcing` and now supports merging fording from several data sources +- `setup_discharge_forcing_from_grid` has replaces `setup_q_forcing_from_grid` +- `setup_precip_forcing` has replaced `setup_p_forcing` +- `setup_precip_forcing_from_grid` has replaced `setup_p_forcing_from_grid` +- `setup_observation_points` has replace `setup_gauges` + +New methods +----------- +- `setup_grid` to setup a user-defined regular grid based coordinates, shape, rotation, etc. +- `setup_subgrid` to setup subgrid tables (sbgfile) based on one ore more elevation and Manning roughness datasets +- `setup_constant_infiltration` to setup a constant infiltration rate maps (qinffile) +- `setup_waterlevel_bnd_from_mask` to setup water level boundary points (bndfile) based on the SFINCS model mask (mskfile) +- `setup_tiles` to create tiles of the model for fast visualization + +Changed methods +--------------- +- `setup_river_inflow` and `setup_river_outflow` are now based river centerline data (which can be derivded from hydrography data). + This is more robust compared to the previous method which was based on reprojected flow direction data. + +Removed methods (not replaced) +------------------------------ +- `setup_basemaps` This method was already deprecated in v0.2.1 and has now been removed. +- `setup_river_hydrography` This method was removed as reprojection of the hydrography data is no longer required for river inflow/outflow. +- `setup_river_bathymetry` This method was removed as river bathymetry should ideally be burned in the subgrid data of the model rather + than the dep file itself to be able to include rivers with widths smaller than the model grid cell size. A new option to burn rivers + in the subgrid data will be added in to `setup_subgrid` a future release. + + +New low-level classes +--------------------- +These classes are not intended to be used directly by the user, but are used internally by the SfincsModel class. + +- The `SfincsInput` class contains methods to generate, read and write SFINCS input files +- The `RegularGrid` class contains methods to create and manipulate regular grids +- The `SubgridTableRegular` class contains methods to create and manipulate subgrid tables for regular grids -Changed -^^^^^^^ -- **setup_q_forcing_from_grid** also saves the snapped gauge locations. -- **snap_discharge** method checks for valid (nonnull) discharge cells v0.2.1 (23 February 2022) -------------------------- +========================= Deprecated -^^^^^^^^^^ +---------- - **setup_basemaps** has been replaced by **setup_topobathy** - In **setup_mask**, the "active_mask_fn" argument has been renamed to "mask_fn" for consistency - In **setup_river_inflow** and **setup_river_outflow** the "basemaps_fn" argument has been renamed to "hydrography_fn" for consistency @@ -25,13 +74,13 @@ Deprecated - **setup_q_forcing_from_grid** and **workflows.snap_discharge** have a "rel_error" and "abs_error" argument instead of a single "max_error" argument. Bugfix -^^^^^^ +------ - bugfix **setup_p_forcing** to ensure the data is 1D when passed to set_forcing_1d method - bugfix **setup_p_forcing_from_grid** when aggregating with a multi polygon region. - bugfix **read_results** with new corner_x/y instead of edge_x/y dimensions in sfincs_map.nc New -^^^ +--- - **setup_region** method to set the (hydrological) model region of interest (before part of **setup_basemaps**). - **setup_river_hydrography** allows to derive hydrography data ['flwdir', 'uparea'] from the model elevation or reproject it from a global dataset. Derived 'uparea' and 'flwdir' maps are saved in the GIS folder and can be reused later (if kept together with the model) @@ -39,7 +88,7 @@ New - Added parameter mapping file for ESA Worldcover dataset Changed -^^^^^^^ +------- - **setup_mask** and **setup_bounds** both have a "mask_fn", "include_mask_fn" and "exclude_mask_fn" polygon and "min_elv" and "max_elv" elevation arguments to determine valid / boundary cells. - **setup_mask** and **setup_bounds** have a "reset_mask" and "reset_bounds" option respectively to start with a clean mask or remove previously set boundary cells. - **setup_mask** takes a new "drop_area" argument to drop regions of contiguous cells smaller than this maximum area threshold, useful to remove (spurious) small islands. @@ -62,15 +111,15 @@ Changed v0.2.0 (2 August 2021) ----------------------- +====================== Bugfix -^^^^^^ +------ - scsfile variable changed to maximum soil moisture retention [inch]; was curve number [-] - fix setting delimited text based geodatasets for h and q forcing. Changed -^^^^^^^ +------- - Bumped minimal hydromt version to 0.4.2 - splitted ``setup_topobathy`` into multiple smaller methods: ``setup_merge_topobathy``, ``setup_mask`` and ``setup_bounds`` - separated many low-level methods into utils.py and plots.py @@ -80,7 +129,7 @@ Changed Added -^^^^^ +----- support for SFINCS files: - structures: sfincs.thd & sfincs.weir @@ -106,31 +155,31 @@ new workflows: - ``river_inflow_points`` & ``river_outflow_points`` Documentation -^^^^^^^^^^^^^ +------------- - build from python example - overviews with SfincsModel setup components & SfincsModel data Deprecated -^^^^^^^^^^^ +----------- - ``setup_p_gridded`` v0.1.0 (18 May 2021) --------------------- +==================== Noticeable changes are a new ``setup_river_inflow`` and ``setup_river_outflow`` methods Added -^^^^^ +----- - setup_river_outflow method to set ouflow (msk=3) boundary at river outflow points Changed -^^^^^^^ +------- - Updated to hydromt v0.4.1 Documentation -^^^^^^^^^^^^^ +------------- - Now **latest** and **stable** versions. - Updated build instructions diff --git a/docs/dev_guide/roadmap.rst b/docs/dev_guide/roadmap.rst index 881f699b..35cb2d9d 100644 --- a/docs/dev_guide/roadmap.rst +++ b/docs/dev_guide/roadmap.rst @@ -9,8 +9,10 @@ Short-term plans ---------------- - Add the possibility to setup, read and write the pump and culvert to the :py:class:`~hydromt_sfincs.SfincsModel` class. Note that SFINCS already supports this functionality. - Next to the `inifile`, also support reading and writing of the `rstfile` option to restart a simulation from a previous state. -- Add more eleborate options to account for spatially varying infiltration +- Add more eleborate options to account for spatially varying infiltration. +- Add functionality to burn rivers in subgrid tabels. - Add functionality to locally adjust subgrid tables, e.g. to account for green infrastructure. +- Add support for other forcings such as wind and waves. Long-term plans --------------- diff --git a/docs/getting_started/installation.rst b/docs/getting_started/installation.rst index 7d0d18f8..30db9b52 100644 --- a/docs/getting_started/installation.rst +++ b/docs/getting_started/installation.rst @@ -9,37 +9,34 @@ Prerequisites For more information about the prerequisites for an installation of the HydroMT package and related dependencies, please visit the documentation of `HydroMT core `_ +.. Note:: + + We recommend using the `mamba` package manager. However, in the commands below you can exchange `mamba` for `conda`, see + `here `_ for the difference between both. + If you already have a python & conda installation but do not yet have mamba installed, -we recommend installing it into your *base* environment using: +you can install it into your *base* environment using: .. code-block:: console $ conda install mamba -n base -c conda-forge - Installation ============ HydroMT-SFINCS is available from pypi and conda-forge. We recommend installing from conda-forge in a new conda environment. -.. Note:: - - In the commands below you can exchange `mamba` for `conda`, see - `here `_ for the difference between both. Install HydroMT-SFINCS in a new environment (recommended!) ---------------------------------------------------------- -You can install HydroMT-SFINCS in a new environment called `hydromt-sfincs` together with a few additional dependencies with: +You can install HydroMT-SFINCS in a new environment called `hydromt-sfincs` together with a few additional dependencies +which are required for the HydroMT-SFINCS examples. To do so, create a new environment from the `environment.yml` file +in the repository root with the following command: .. code-block:: console $ mamba env create -f https://raw.githubusercontent.com/Deltares/hydromt_sfincs/main/environment.yml -.. Note:: - - The environment yaml-files in https://raw.githubusercontent.com/Deltares/hydromt_sfincs/envs are meant for testing and development, - so please use the one mentioned above. In case you want to develop, see the :ref:`Developer installation guide `. - Then, activate the environment (as stated by mamba/conda) to start making use of HydroMT-Wflow: .. code-block:: console @@ -52,12 +49,19 @@ Then, activate the environment (as stated by mamba/conda) to start making use of `conda env remove -n hydromt-sfincs` **or** set a new name for the environment by adding `-n ` to the line above. +.. Note:: + + The environment yaml-files in `envs` folder are meant for testing and development, + so please use the `environment.yml` file in the repository root for normal usage. + In case you want to develop, see the :ref:`Developer installation guide `. + + Install HydroMT-SFINCS in an existing environment ------------------------------------------------- To install HydroMT-SFINCS in an existing environment execute the command below where you replace `` with the name of the existing environment. -Note that if some dependencies are not installed from conda-forge but from other +Note that if some dependencies are not installed from conda-forge but from pip or other channels the installation may fail. .. code-block:: console diff --git a/docs/user_guide/sfincs.rst b/docs/user_guide/sfincs.rst index a7481b6a..3e644d6e 100644 --- a/docs/user_guide/sfincs.rst +++ b/docs/user_guide/sfincs.rst @@ -1,3 +1,5 @@ +.. _working_with_sfincs: + ============================= Working with the SFINCS model ============================= @@ -6,11 +8,14 @@ There are 2 main ways to use HydroMT to build your SFINCS model: **1. Command Line Interface (basic user)**: Provide some information about the model configuration in a .yml-file and quickly build the model using the Command Line Interface (CLI). + The .yml-file provides a way to create a reproducible model setup recipe, which can be easily be shared with others. + Addtionally, no Python knowledge is required to use the CLI. **2. Python scripting (advanced user)**: Dive into the underlying Python functions and use those to build your model from scratch in a Python script. - This option is recommended when the user wants to (locally) adjust the model input data before building the model, - e.g. in the case of modifications of the bed levels or variations on the boundary conditions. + This option is recommended for the expert user who wants to (locally) adjust the model input data as part of the model building process, + e.g. in the case of in-memory modifications of the bed levels or variations on the boundary conditions. + The Python interface provides a lot of flexibility and access to the full HydroMT-SFINCS API, but requires some knowledge of Python. .. toctree:: :maxdepth: 2 diff --git a/docs/user_guide/sfincs_build_update.rst b/docs/user_guide/sfincs_build_update.rst index bc52e7c4..2615df27 100644 --- a/docs/user_guide/sfincs_build_update.rst +++ b/docs/user_guide/sfincs_build_update.rst @@ -4,11 +4,11 @@ Building or updating a model ============================= -This plugin allows users to build or update a SFINCS model from available data. -For beginning users, we recommend to use the :ref:`command line interface ` to build or update a model. -In case you want to (locally) modify the model input data before generating a model, we recommend to use the :ref:`Python scripting `. +This plugin allows users to build or update a SFINCS model from available data using +the :ref:`command line interface ` or :ref:`Python scripting `. +For a brief overview of the differences, see :ref:`Working with the SFINCS model `. -In the following sections, examples are provided in iPython notebooks how to build your SFINCS model with HydroMT using either the CLI or python scripting. +In the following sections, examples are provided how to build your SFINCS model with HydroMT using either the CLI or Python scripting. .. _sfincs_cli: @@ -67,26 +67,28 @@ in its corresponding section. See the HydroMT core documentation for more info a Note that the order in which the components are listed in the yml-file is important (methods are executed from top to bottom): - :py:func:`~hydromt_sfincs.SfincsModel.setup_grid` or :py:func:`~hydromt_sfincs.SfincsModel.setup_grid_from_region` should always be run first to define the model grid. -- a lot of methods (e.g. :py:func:`~hydromt_sfincs.SfincsModel.setup_mask_active`) need elevation data to work properly, so :py:func:`~hydromt_sfincs.SfincsModel.setup_dep` should be run before most other methods. -- if discharge locations are inferred from hydrography, :py:func:`~hydromt_sfincs.SfincsModel.setup_river_inflow` should be run before :py:func:`~hydromt_sfincs.SfincsModel.setup_discharge_forcing` or :py:func:`~hydromt_sfincs.SfincsModel.setup_discharge_forcing_from_grid`. - -Data libraries ----------------- - -Data sources in HydroMT are provided in one of several yaml libraries. These libraries contain required -information on the different data sources so that HydroMT can process them for the different models. There -are three ways for the user to select which data libraries to use: - -- If no yaml file is selected, HydroMT will use the data stored in the - `hydromt-artifacts `_ - which contains an extract of global data for a small region around the Piave river in Northern Italy. -- Another options for Deltares users is to select the deltares-data library (requires access to the Deltares - P-drive). In the command line interface, this is done by adding either **-dd** or **--deltares-data** - to the build / update command line. -- Finally, the user can prepare its own yaml libary (or libraries) (see +- Many methods (e.g., :py:func:`~hydromt_sfincs.SfincsModel.setup_mask_active`) need elevation data to work properly, hence :py:func:`~hydromt_sfincs.SfincsModel.setup_dep` should be run before most other methods. +- If discharge locations are inferred from hydrography, :py:func:`~hydromt_sfincs.SfincsModel.setup_river_inflow` should be run before :py:func:`~hydromt_sfincs.SfincsModel.setup_discharge_forcing` or :py:func:`~hydromt_sfincs.SfincsModel.setup_discharge_forcing_from_grid`. +- If water level bounary points are inferred from the water level mask cells, :py:func:`~hydromt_sfincs.SfincsModel.setup_waterlevel_bnd_from_mask` should be run before :py:func:`~hydromt_sfincs.SfincsModel.setup_waterlevel_forcing`. + +Data Catalogs +------------- + +Data sources are provided to HydroMT in one or more user-definfed data catalog (yaml) files +or from pre-defined data catalogs. These data catalogs contain required information on the +different data sources so that HydroMT can process them for the different models. +There are three ways for the user to select which data catalog to use: + +- There are several `pre-defined data catalog `_ + Amongst other, these include the `deltares_data` data catalog for Deltares users which requires access to the Deltares P-drive. + More pre-defined data catalogs will be added in the future. +- Furthermore, the user can prepare its own yaml libary (or libraries) (see `HydroMT documentation `_ to check the guidelines). These user libraries can be added either in the command line using the **-d** option and path/to/yaml or in the **yml file** with the **data_libs** option in the `global` section (see example above). +- Finally, if no catalog is provided, HydroMT will use the data stored in the + `hydromt-artifacts `_ + which contains an extract of global data for a small region around the Piave river in Northern Italy. Example -------- diff --git a/docs/user_guide/sfincs_model_setup.rst b/docs/user_guide/sfincs_model_setup.rst index 8aefc2e9..658369ed 100644 --- a/docs/user_guide/sfincs_model_setup.rst +++ b/docs/user_guide/sfincs_model_setup.rst @@ -73,12 +73,10 @@ General setup methods - Explanation * - :py:func:`~hydromt_sfincs.SfincsModel.setup_config` - Update SFINCS config (sfincs.inp) with a dictionary. - * - :py:func:`~hydromt_sfincs.SfincsModel.setup_region` - - This component sets the region of interest and res of the model. * - :py:func:`~hydromt_sfincs.SfincsModel.setup_grid` - - This component generates a user-defined grid. + - This component generates a user-defined model grid. * - :py:func:`~hydromt_sfincs.SfincsModel.setup_grid_from_region` - - This component automatically generates a model grid covering the region of interest with a given res(olution). + - This component automatically generates a model grid covering the region of interest with a given resolution. Grid setup methods ------------------ @@ -94,19 +92,19 @@ Grid setup methods * - :py:func:`~hydromt_sfincs.SfincsModel.setup_dep` - This component interpolates topobathy (depfile) data to the model grid. * - :py:func:`~hydromt_sfincs.SfincsModel.setup_mask_active` - - This component generates a mask (mskfile) defining which part of the model grid is active. + - This component generates a mask (mskfile) defining which part of the model grid is active based on elevation criteria and/or polygons. * - :py:func:`~hydromt_sfincs.SfincsModel.setup_mask_bounds` - - This component adds boundary cells in the model mask (mskfile). + - This component adds boundary cells in the model mask (mskfile) based on elevation criteria and/or polygons. * - :py:func:`~hydromt_sfincs.SfincsModel.setup_river_outflow` - - This component adds open boundary cells (mask=3) where a river flows out of the model domain. + - This component adds boundary cells in the model mask (mskfile) where a river flows out of the model domain. * - :py:func:`~hydromt_sfincs.SfincsModel.setup_manning_roughness` - - This component adds a manning roughness map (manningfile) to the model grid from gridded manning data or a combinataion of gridded land-use/land-cover map and manning roughness mapping table. + - This component adds a Manning roughness map (manningfile) to the model grid based on gridded Manning roughness data or a combinataion of gridded land-use/land-cover map and a Manning roughness mapping table. * - :py:func:`~hydromt_sfincs.SfincsModel.setup_constant_infiltration` - This component adds a spatially varying constant infiltration rate map (qinffile) to the model grid. * - :py:func:`~hydromt_sfincs.SfincsModel.setup_cn_infiltration` - This component adds a potential maximum soil moisture retention map (scsfile) to the model grid based on a gridded curve number map. * - :py:func:`~hydromt_sfincs.SfincsModel.setup_subgrid` - - This component generates subgrid tables (sbgfile) for the model grid based on a list of elevation and Manning's roughness datasets + - This component generates subgrid tables (sbgfile) for the model grid based on a list of elevation and Manning roughness datasets Geoms setup methods ------------------- @@ -122,7 +120,7 @@ Geoms setup methods * - :py:func:`~hydromt_sfincs.SfincsModel.setup_observation_points` - This component adds observation points to the model (obsfile). * - :py:func:`~hydromt_sfincs.SfincsModel.setup_structures` - - This component adds structures to the model (thdfile, weirfile). + - This component adds line element structures to the model (thdfile, weirfile). Forcing setup methods --------------------- @@ -135,20 +133,20 @@ Forcing setup methods * - :py:class:`~hydromt_sfincs.SfincsModel` Method - Explanation - * - :py:func:`~hydromt_sfincs.SfincsModel.setup_waterlevel_forcing` - - Setup waterlevel forcing (bndfile, bzsfile) from a `geodataset` (geospatial point timeseries) or a tabular `timeseries` dataframe * - :py:func:`~hydromt_sfincs.SfincsModel.setup_waterlevel_bnd_from_mask` - - Setup waterlevel boundary (bndfile) points along model waterlevel boundary (msk=2). + - This component adds waterlevel boundary points (bndfile) along model waterlevel boundary (msk=2). + * - :py:func:`~hydromt_sfincs.SfincsModel.setup_waterlevel_forcing` + - This component adds waterlevel forcing (bndfile, bzsfile) from a `geodataset` (geospatial point timeseries) or a tabular `timeseries` dataframe. + * - :py:func:`~hydromt_sfincs.SfincsModel.setup_river_inflow` + - This component adds discharge points (srcfile) where a river enters the model domain. * - :py:func:`~hydromt_sfincs.SfincsModel.setup_discharge_forcing` - - Setup discharge forcing (srcfile, disfile) from a `geodataset` (geospatial point timeseries) or a tabular `timeseries` dataframe) + - This component adds discharge forcing (srcfile, disfile) from a `geodataset` (geospatial point timeseries) or a tabular `timeseries` dataframe. * - :py:func:`~hydromt_sfincs.SfincsModel.setup_discharge_forcing_from_grid` - - Setup discharge forcing (srcfile, disfile) based on a gridded discharge dataset. - * - :py:func:`~hydromt_sfincs.SfincsModel.setup_river_inflow` - - Setup discharge (srcfile) points where a river enters the model domain. + - This component adds discharge forcing (srcfile, disfile) based on a gridded discharge dataset. * - :py:func:`~hydromt_sfincs.SfincsModel.setup_precip_forcing` - - Setup spatially uniform precipitation forcing (precipfile). + - This component adds spatially uniform precipitation forcing (precipfile). * - :py:func:`~hydromt_sfincs.SfincsModel.setup_precip_forcing_from_grid` - - Setup precipitation forcing from a gridded spatially varying data source (netamprfile). + - This component adds precipitation forcing from a gridded spatially varying data source (netamprfile). Other setup methods ------------------- @@ -162,6 +160,6 @@ Other setup methods * - :py:class:`~hydromt_sfincs.SfincsModel` Method - Explanation * - :py:func:`~hydromt_sfincs.SfincsModel.setup_tiles` - - This component generates webmercator index and topobathy tiles for the SFINCS model. + - This component generates webmercator index and topobathy tiles for visualization of the SFINCS model. .. _region: https://deltares.github.io/hydromt/latest/user_guide/model_region.html diff --git a/docs/user_guide/sfincs_run.rst b/docs/user_guide/sfincs_run.rst index dfda5131..02b35778 100644 --- a/docs/user_guide/sfincs_run.rst +++ b/docs/user_guide/sfincs_run.rst @@ -11,6 +11,12 @@ The model is situated in **Northern Italy** and is forced with waterlevel and di If you want to read more about running SFINCS on different platforms, please read the `SFINCS manual `_. +.. Note:: + HydroMT-SFINCS does **not** contain the SFINCS kernel itself. + For more information on how to obtain and install the SFINCS kernel, + please read the `SFINCS manual `_. + + .. toctree:: :hidden: diff --git a/environment.yml b/environment.yml index ccbb124d..3a7c48cc 100644 --- a/environment.yml +++ b/environment.yml @@ -11,10 +11,8 @@ dependencies: - ffmpeg # to run examples (animation) # - git - hydromt>=0.7.1 - - hydromt_sfincs + - hydromt_sfincs>=1.0 - jupyterlab # to run examples - matplotlib==3.5.* # to run examples / https://github.com/SciTools/cartopy/issues/2086 - pip - - python=3.10 # fixing python version for binder - - pip: - - git+https://github.com/Deltares/hydromt_sfincs.git + - python=3.10 # fixing python version for binder diff --git a/envs/hydromt-sfincs-dev.yml b/envs/hydromt-sfincs-dev.yml index baaf9edd..fa15d35c 100644 --- a/envs/hydromt-sfincs-dev.yml +++ b/envs/hydromt-sfincs-dev.yml @@ -27,6 +27,7 @@ dependencies: - pyproj - python>=3.9 - pytest # tests + - pytest-cov # tests - rasterio - scipy - shapely diff --git a/examples/sfincs_results_hmax.ipynb b/examples/sfincs_results_hmax.ipynb index d3d8fc0b..eff8c781 100644 --- a/examples/sfincs_results_hmax.ipynb +++ b/examples/sfincs_results_hmax.ipynb @@ -87,13 +87,8 @@ "metadata": {}, "outputs": [], "source": [ - "# write hmax to /gis/hmax.tif\n", - "mod.write_raster(\"results.hmax\", compress=\"LZW\")\n", - "\n", - "# this is identical to the following:\n", - "# hmax = mod.results['hmax']\n", - "# hmax = hmax.reindex(y=list(reversed(hmax['y'].values))) # change orientation to N -> S\n", - "# hmax.raster.to_raster(join(mod.root, 'gis', 'hmax.tif'), compress='LZW')" + "# uncomment to write hmax to /gis/hmax.tif\n", + "# mod.write_raster(\"results.hmax\", compress=\"LZW\")" ] }, { @@ -226,7 +221,7 @@ "source": [ "In the example, we used *GEBCO* (~450m resolution) and *MERIT Hydro* (~90m resolution) to create a model on 50 meters resolution. In this case, including subgrid tables didnt't add much information to the model. To still illustrate the process of downscaling, we will use the *dep.tif* (in the gis folder) which has 50m resolution. \n", "\n", - "When creating a subgrid with `setup_subgrid`, you can easily create a geotiff on the subgrid resolution with the argument `write_dep_tif." + "When creating a subgrid with `setup_subgrid`, you can easily create a geotiff on the subgrid resolution in the `subgrid` folder with the argument `write_dep_tif=True`." ] }, { @@ -285,9 +280,14 @@ "metadata": {}, "outputs": [], "source": [ - "# Fourthly, we downscale the floodmap and save to /floodmap.tif\n", - "floodmap_fn = join(sfincs_root, \"floodmap.tif\")\n", - "hmax = utils.downscale_floodmap(zsmax=zsmax, dep=dep, hmin=hmin, gdf_mask=gdf_osm, floodmap_fn=floodmap_fn)" + "# Fourthly, we downscale the floodmap \n", + "hmax = utils.downscale_floodmap(\n", + " zsmax=zsmax, \n", + " dep=dep, \n", + " hmin=hmin, \n", + " gdf_mask=gdf_osm, \n", + " # floodmap_fn=join(sfincs_root, \"floodmap.tif\") # uncomment to save to /floodmap.tif\n", + ")" ] }, { @@ -336,7 +336,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.10.10" } }, "nbformat": 4, diff --git a/hydromt_sfincs/__init__.py b/hydromt_sfincs/__init__.py index c502f852..8e57469d 100644 --- a/hydromt_sfincs/__init__.py +++ b/hydromt_sfincs/__init__.py @@ -3,7 +3,7 @@ from os.path import dirname, join, abspath -__version__ = "1.0.0.dev" +__version__ = "1.0.0" DATADIR = join(dirname(abspath(__file__)), "data") diff --git a/hydromt_sfincs/sfincs.py b/hydromt_sfincs/sfincs.py index bfafca2e..469a94df 100644 --- a/hydromt_sfincs/sfincs.py +++ b/hydromt_sfincs/sfincs.py @@ -703,6 +703,9 @@ def setup_river_inflow( Waterlevel or outflow boundary cells intersecting with the river are removed from the model mask. + Discharge is set to zero at these points, but can be updated + using the `setup_discharge_forcing` or `setup_discharge_forcing_from_grid` methods. + Note: this method assumes the rivers are directed from up- to downstream. Adds model layers: @@ -736,6 +739,11 @@ def setup_river_inflow( If True, keep a geometry of the rivers "rivers_inflow" in geoms. By default False. buffer: int, optional Buffer [no. of cells] around model domain, by default 10. + + See Also + -------- + setup_discharge_forcing + setup_discharge_forcing_from_grid """ da_flwdir, da_uparea, gdf_riv = None, None, None if hydrography is not None: @@ -917,7 +925,7 @@ def setup_river_outflow( if keep_rivers_geom and len(gdf_riv) > 0: self.set_geoms(gdf_riv, name="rivers_outflow") - def setup_constant_infiltration(self, qinf): + def setup_constant_infiltration(self, qinf, reproj_method="average"): """Setup spatially varying constant infiltration rate (qinffile). Adds model layers: @@ -928,37 +936,34 @@ def setup_constant_infiltration(self, qinf): ---------- qinf : str, Path, or RasterDataset Spatially varying infiltration rates [mm/hr] + reproj_method : str, optional + Resampling method for reprojecting the infiltration data to the model grid. + By default 'average'. For more information see, :py:meth:`hydromt.raster.RasterDataArray.reproject_like` """ # get infiltration data - da_inf = self.data_catalog.get_rasterdataset(qinf, geom=sf.region, buffer=10) + da_inf = self.data_catalog.get_rasterdataset(qinf, geom=self.region, buffer=10) + da_inf = da_inf.raster.mask_nodata() # set nodata to nan # reproject infiltration data to model grid - # this workflow automatically determines the best resampling method (bilinear or average) - da_inf = workflows.merge_multi_dataarrays( - da_list=[{"da": da_inf}], - da_like=sf.mask, - logger=self.logger, - ) + da_inf = da_inf.raster.reproject_like(self.mask, method=reproj_method) # check on nan values - if np.isnan(da_inf).any(): + if np.logical_and(np.isnan(da_inf), self.mask >= 1).any(): self.logger.warning("NaN values found in infiltration data; filled with 0") da_inf = da_inf.fillna(0) - - # add to sfincs model da_inf.raster.set_nodata(-9999.0) # set grid mname = "qinf" - da_inf.attrs.update(**sf._ATTRS.get(mname, {})) - sf.set_grid(da_inf, name=mname) + da_inf.attrs.update(**self._ATTRS.get(mname, {})) + self.set_grid(da_inf, name=mname) - # update config: remove scs infiltration values and set qinf map - self.config.pop("scs", None) + # update config: remove default inf and set qinf map self.set_config(f"{mname}file", f"sfincs.{mname}") + self.config.pop("qinf", None) - def setup_cn_infiltration(self, cn, antecedent_moisture="avg"): + def setup_cn_infiltration(self, cn, antecedent_moisture="avg", reproj_method="med"): """Setup model potential maximum soil moisture retention map (scsfile) from gridded curve number map. @@ -977,24 +982,27 @@ def setup_cn_infiltration(self, cn, antecedent_moisture="avg"): Antecedent runoff conditions. None if data has no antecedent runoff conditions. By default `avg` + reproj_method : str, optional + Resampling method for reprojecting the curve number data to the model grid. + By default 'med'. For more information see, :py:meth:`hydromt.raster.RasterDataArray.reproject_like` """ # get data + da_org = self.data_catalog.get_rasterdataset(cn, geom=self.region, buffer=10) + # read variable v = "cn" if antecedent_moisture: v = f"cn_{antecedent_moisture}" - da_org = self.data_catalog.get_rasterdataset( - cn, geom=self.region, buffer=10, variables=[v] - ) + if isinstance(da_org, xr.Dataset) and v in da_org.data_vars: + da_org = da_org[v] + elif not isinstance(da_org, xr.DataArray): + raise ValueError(f"Could not find variable {v} in {cn}") + # reproject using median - da_cn = da_org.raster.reproject_like(self.grid, method="med") - # CN=100 based on water mask - if "rivmsk" in self.grid: - self.logger.info( - 'Updating CN map based on "rivmsk" from setup_river_hydrography method.' - ) - da_cn = da_cn.where(self.grid["rivmsk"] == 0, 100) + da_cn = da_org.raster.reproject_like(self.grid, method=reproj_method) + # convert to potential maximum soil moisture retention S (1000/CN - 10) [inch] da_scs = workflows.cn_to_s(da_cn, self.mask > 0).round(3) + # set grid mname = "scs" da_scs.attrs.update(**self._ATTRS.get(mname, {})) @@ -1405,6 +1413,12 @@ def setup_waterlevel_bnd_from_mask( ): """Setup waterlevel boundary (bnd) points along model waterlevel boundary (msk=2). + The waterlevel boundary (msk=2) should be set before calling this method, + e.g.: with `setup_mask_bounds` + + Waterlevels (bzs) are set to zero at these points, but can be updated + with `setup_waterlevel_forcing`. + Parameters ---------- distance: float, optional @@ -1412,6 +1426,11 @@ def setup_waterlevel_bnd_from_mask( by default 10 km. merge : bool, optional If True, merge with existing forcing data, by default True. + + See Also + -------- + setup_waterlevel_forcing + setup_mask_bounds """ # get waterlevel boundary vector based on mask gdf_msk = utils.get_bounds_vector(self.mask) @@ -1440,7 +1459,8 @@ def setup_discharge_forcing( or a tabular `timeseries` dataframe. At least one of these must be provided. The tabular timeseries data is combined with `locations` if provided, - or with existing 'src' locations if previously set. + or with existing 'src' locations if previously set, e.g., with the + `setup_river_inflow` method. Adds model layers: @@ -1456,6 +1476,10 @@ def setup_discharge_forcing( Path, data source name, or geopandas object for bnd point locations. merge : bool, optional If True, merge with existing forcing data, by default True. + + See Also + -------- + setup_river_inflow """ gdf_locs, df_ts = None, None tstart, tstop = self.get_model_time() # model time @@ -1548,7 +1572,7 @@ def setup_discharge_forcing_from_grid( See Also -------- - hydromt_sfincs.SfincsModel.setup_river_inflow + setup_river_inflow """ if locations is not None: gdf = self.data_catalog.get_geodataframe( diff --git a/hydromt_sfincs/utils.py b/hydromt_sfincs/utils.py index e48c5727..d9625ad5 100644 --- a/hydromt_sfincs/utils.py +++ b/hydromt_sfincs/utils.py @@ -53,37 +53,6 @@ logger = logging.getLogger(__name__) -## CONFIG: sfincs.inp ## - - -class ConfigParserSfincs(ConfigParser): - def __init__(self, **kwargs): - defaults = dict( - comment_prefixes=("!", "/", "#"), - inline_comment_prefixes=("!"), - allow_no_value=True, - delimiters=("="), - ) - defaults.update(**kwargs) - super(ConfigParserSfincs, self).__init__(**defaults) - - def read_file(self, f, **kwargs): - def add_header(f, header_name="dummy"): - """add header""" - yield "[{}]\n".format(header_name) - for line in f: - yield line - - super(ConfigParserSfincs, self).read_file(add_header(f), **kwargs) - - def _write_section(self, fp, section_name, section_items, delimiter): - """Write a single section to the specified `fp'.""" - for key, value in section_items: - value = self._interpolation.before_write(self, section_name, key, value) - fp.write("{:<15} {:<1} {:<}\n".format(key, self._delimiters[0], value)) - fp.write("\n") - - ## BINARY MAPS: sfincs.ind, sfincs.msk, sfincs.dep etc. ## @@ -769,8 +738,9 @@ def downscale_floodmap( dep: xr.DataArray, hmin: float = 0.05, gdf_mask: gpd.GeoDataFrame = None, - floodmap_fn: Union[Path, str] = "floodmap.tif", + floodmap_fn: Union[Path, str] = None, reproj_method: str = "nearest", + **kwargs, ) -> xr.Dataset: """Create a downscaled floodmap for (model) region. @@ -786,50 +756,54 @@ def downscale_floodmap( Geodataframe with polygons to mask floodmap, example containing the landarea, by default None Note that the area outside the polygons is set to nodata. floodmap_fn : Union[Path, str], optional - Name (path) of output floodmap, by default "floodmap.tif" + Name (path) of output floodmap, by default None. If provided, the floodmap is written to disk. reproj_method : str, optional Reprojection method for downscaling the water levels, by default "nearest". Other option is "bilinear". + kwargs : dict, optional + Additional keyword arguments passed to `RasterDataArray.to_raster`. Returns ------- hmax: xr.Dataset Downscaled and masked floodmap. + + See Also + -------- + hydromt.raster.RasterDataArray.to_raster """ + # get maximum water level + timedim = set(zsmax.dims) - set(zsmax.raster.dims) + if timedim: + zsmax = zsmax.max(timedim) # interpolate zsmax to dep grid zsmax = zsmax.raster.reproject_like(dep, method=reproj_method) - zsmax.raster.set_nodata(np.nan) + zsmax = zsmax.raster.mask_nodata() # make sure nodata is nan - # compute hmax - if "timemax" in zsmax.dims: - hmax = zsmax.max("timemax") - dep - elif "time" in zsmax.dims: - hmax = zsmax.max("time") - dep - else: - hmax = zsmax - dep - - # remove flood-depths below threshold - hmax = hmax.where(hmax > hmin, np.nan) + # get flood depth + hmax = (zsmax - dep).astype("float32") + hmax.raster.set_nodata(np.nan) + # mask floodmap + hmax = hmax.where(hmax > hmin) if gdf_mask is not None: mask = hmax.raster.geometry_mask(gdf_mask, all_touched=True) hmax = hmax.where(mask) - floodmap_fn = floodmap_fn.replace(".tif", "_mask.tif") # write floodmap - hmax.raster.to_raster( - floodmap_fn, - driver="GTiff", - dtype=np.float32, - tiled=True, - blockxsize=256, - blockysize=256, - compress="deflate", - predictor=2, - profile="COG", - nodata=np.nan, - ) + if floodmap_fn is not None: + if not kwargs: # write COG by default + kwargs = dict( + driver="GTiff", + tiled=True, + blockxsize=256, + blockysize=256, + compress="deflate", + predictor=2, + profile="COG", + ) + hmax.raster.to_raster(floodmap_fn, **kwargs) return hmax diff --git a/tests/test_1model_class.py b/tests/test_1model_class.py index 4c688ae7..892c5216 100644 --- a/tests/test_1model_class.py +++ b/tests/test_1model_class.py @@ -54,6 +54,32 @@ def test_states(tmpdir): assert np.allclose(mod1.states["zsini"], mod.states["zsini"]) +def test_infiltration(tmpdir): + # FIXME: very shallow test, add more specific tests + root = TESTMODELDIR + mod = SfincsModel(root=root, mode="r") + mod.read() + mod.set_root(str(tmpdir.join("infiltration_test")), mode="w") + + # set constant infiltration + qinf = xr.where(mod.grid["dep"] < -0.5, -9999, 0.1) + qinf.raster.set_nodata(-9999.0) + qinf.raster.set_crs(mod.crs) + mod.setup_constant_infiltration(qinf, reproj_method="nearest") + assert "qinf" not in mod.config # qinf removed from config + assert "qinffile" in mod.config + assert "qinf" in mod.grid + + # set cn infiltration + cn = xr.where(mod.grid["dep"] < -0.5, 0, 50) + cn.raster.set_nodata(-1) + cn.raster.set_crs(mod.crs) + mod.setup_cn_infiltration(cn, reproj_method="nearest") + assert "scsfile" in mod.config + assert "scs" in mod.grid + assert (mod.grid["scs"].where(mod.mask > 0)).min() == 10 + + def test_structs(tmpdir): root = TESTMODELDIR mod = SfincsModel(root=root, mode="r+")