flood-adapt 1.1.3__tar.gz → 1.1.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/PKG-INFO +2 -4
  2. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/__init__.py +1 -1
  3. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/__init__.py +10 -0
  4. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/database_builder.py +215 -51
  5. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/dbs_classes/dbs_scenario.py +2 -1
  6. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/events/events.py +57 -0
  7. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/forcing/forcing.py +46 -1
  8. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/forcing/tide_gauge.py +4 -2
  9. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/forcing/unit_system.py +4 -2
  10. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt.egg-info/PKG-INFO +2 -4
  11. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt.egg-info/requires.txt +1 -3
  12. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/pyproject.toml +1 -3
  13. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/LICENSE +0 -0
  14. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/README.md +0 -0
  15. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/adapter/__init__.py +0 -0
  16. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/adapter/fiat_adapter.py +0 -0
  17. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/adapter/interface/__init__.py +0 -0
  18. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/adapter/interface/hazard_adapter.py +0 -0
  19. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/adapter/interface/impact_adapter.py +0 -0
  20. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/adapter/interface/model_adapter.py +0 -0
  21. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/adapter/interface/offshore.py +0 -0
  22. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/adapter/sfincs_adapter.py +0 -0
  23. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/adapter/sfincs_offshore.py +0 -0
  24. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/config/__init__.py +0 -0
  25. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/config/config.py +0 -0
  26. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/config/fiat.py +0 -0
  27. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/config/gui.py +0 -0
  28. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/config/hazard.py +0 -0
  29. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/config/impacts.py +0 -0
  30. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/config/sfincs.py +0 -0
  31. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/config/site.py +0 -0
  32. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/metrics_utils.py +0 -0
  33. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/default_units/imperial.toml +0 -0
  34. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/default_units/metric.toml +0 -0
  35. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/green_infra_table/green_infra_lookup_table.csv +0 -0
  36. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/icons/black_down_48x48.png +0 -0
  37. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/icons/black_left_48x48.png +0 -0
  38. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/icons/black_right_48x48.png +0 -0
  39. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/icons/black_up_48x48.png +0 -0
  40. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-16_white_down.png +0 -0
  41. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-16_white_left.png +0 -0
  42. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-16_white_right.png +0 -0
  43. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-16_white_up.png +0 -0
  44. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-24_black_down.png +0 -0
  45. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-24_black_left.png +0 -0
  46. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-24_black_right.png +0 -0
  47. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-24_black_up.png +0 -0
  48. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-24_white_left.png +0 -0
  49. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-24_white_right.png +0 -0
  50. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/icons/white_down_48x48.png +0 -0
  51. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/icons/white_left_48x48.png +0 -0
  52. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/icons/white_right_48x48.png +0 -0
  53. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/icons/white_up_48x48.png +0 -0
  54. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/infographics/images/ambulance.png +0 -0
  55. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/infographics/images/car.png +0 -0
  56. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/infographics/images/cart.png +0 -0
  57. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/infographics/images/firetruck.png +0 -0
  58. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/infographics/images/hospital.png +0 -0
  59. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/infographics/images/house.png +0 -0
  60. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/infographics/images/info.png +0 -0
  61. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/infographics/images/money.png +0 -0
  62. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/infographics/images/person.png +0 -0
  63. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/infographics/images/school.png +0 -0
  64. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/infographics/images/truck.png +0 -0
  65. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/infographics/images/walking_person.png +0 -0
  66. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/infographics/styles.css +0 -0
  67. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/database_builder/templates/output_layers/bin_colors.toml +0 -0
  68. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/dbs_classes/__init__.py +0 -0
  69. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/dbs_classes/database.py +0 -0
  70. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/dbs_classes/dbs_benefit.py +0 -0
  71. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/dbs_classes/dbs_event.py +0 -0
  72. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/dbs_classes/dbs_measure.py +0 -0
  73. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/dbs_classes/dbs_projection.py +0 -0
  74. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/dbs_classes/dbs_static.py +0 -0
  75. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/dbs_classes/dbs_strategy.py +0 -0
  76. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/dbs_classes/dbs_template.py +0 -0
  77. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/dbs_classes/interface/database.py +0 -0
  78. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/dbs_classes/interface/element.py +0 -0
  79. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/dbs_classes/interface/static.py +0 -0
  80. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/flood_adapt.py +0 -0
  81. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/misc/__init__.py +0 -0
  82. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/misc/database_user.py +0 -0
  83. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/misc/debug_timer.py +0 -0
  84. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/misc/exceptions.py +0 -0
  85. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/misc/log.py +0 -0
  86. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/misc/path_builder.py +0 -0
  87. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/misc/utils.py +0 -0
  88. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/__init__.py +0 -0
  89. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/benefits/__init__.py +0 -0
  90. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/benefits/benefits.py +0 -0
  91. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/events/__init__.py +0 -0
  92. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/events/event_factory.py +0 -0
  93. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/events/event_set.py +0 -0
  94. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/events/historical.py +0 -0
  95. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/events/hurricane.py +0 -0
  96. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/events/synthetic.py +0 -0
  97. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/forcing/__init__.py +0 -0
  98. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/forcing/csv.py +0 -0
  99. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/forcing/discharge.py +0 -0
  100. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/forcing/forcing_factory.py +0 -0
  101. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/forcing/meteo_handler.py +0 -0
  102. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/forcing/netcdf.py +0 -0
  103. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/forcing/plotting.py +0 -0
  104. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/forcing/rainfall.py +0 -0
  105. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/forcing/time_frame.py +0 -0
  106. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/forcing/timeseries.py +0 -0
  107. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/forcing/waterlevels.py +0 -0
  108. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/forcing/wind.py +0 -0
  109. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/measures/__init__.py +0 -0
  110. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/measures/measure_factory.py +0 -0
  111. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/measures/measures.py +0 -0
  112. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/object_model.py +0 -0
  113. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/output/floodmap.py +0 -0
  114. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/projections/__init__.py +0 -0
  115. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/projections/projections.py +0 -0
  116. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/scenarios/__init__.py +0 -0
  117. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/scenarios/scenarios.py +0 -0
  118. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/strategies/__init__.py +0 -0
  119. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/objects/strategies/strategies.py +0 -0
  120. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/workflows/__init__.py +0 -0
  121. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/workflows/benefit_runner.py +0 -0
  122. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt/workflows/scenario_runner.py +0 -0
  123. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt.egg-info/SOURCES.txt +0 -0
  124. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt.egg-info/dependency_links.txt +0 -0
  125. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt.egg-info/not-zip-safe +0 -0
  126. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/flood_adapt.egg-info/top_level.txt +0 -0
  127. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/setup.cfg +0 -0
  128. {flood_adapt-1.1.3 → flood_adapt-1.1.5}/tests/test_flood_adapt.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: flood-adapt
3
- Version: 1.1.3
3
+ Version: 1.1.5
4
4
  Summary: A software package support system which can be used to assess the benefits and costs of flood resilience measures
5
5
  Author-email: Gundula Winter <Gundula.Winter@deltares.nl>, Panos Athanasiou <Panos.Athanasiou@deltares.nl>, Frederique de Groen <Frederique.deGroen@deltares.nl>, Tim de Wilde <Tim.deWilde@deltares.nl>, Julian Hofer <Julian.Hofer@deltares.nl>, Daley Adrichem <Daley.Adrichem@deltares.nl>, Luuk Blom <Luuk.Blom@deltares.nl>
6
6
  License: ====================================================
@@ -712,14 +712,12 @@ Requires-Dist: cht-cyclones<2.0,>=1.0.3
712
712
  Requires-Dist: cht-meteo<1.0,>=0.3.1
713
713
  Requires-Dist: cht-observations<1.0,>=0.2.1
714
714
  Requires-Dist: cht-tide<1.0,>=0.1.1
715
- Requires-Dist: dask==2024.11.2
716
- Requires-Dist: numba_celltree==0.2.2
717
715
  Requires-Dist: fiat-toolbox<0.2.0,>=0.1.22
718
716
  Requires-Dist: fiona<2.0,>=1.0
719
717
  Requires-Dist: geojson<4.0,>=3.0
720
718
  Requires-Dist: geopandas<2.0,>=1.0
721
719
  Requires-Dist: hydromt-fiat<1.0,>=0.5.9
722
- Requires-Dist: hydromt-sfincs<2.0,>=1.2.0
720
+ Requires-Dist: hydromt-sfincs<2.0,>=1.2.2
723
721
  Requires-Dist: numpy<2.0,>=1.0
724
722
  Requires-Dist: numpy-financial<2.0,>=1.0
725
723
  Requires-Dist: pandas<3.0,>=2.0
@@ -1,5 +1,5 @@
1
1
  # has to be here at the start to avoid circular imports
2
- __version__ = "1.1.3"
2
+ __version__ = "1.1.5"
3
3
 
4
4
  from flood_adapt import adapter, database_builder, dbs_classes, objects
5
5
  from flood_adapt.config.config import Settings
@@ -1,11 +1,17 @@
1
+ from flood_adapt.config import (
2
+ FloodModel,
3
+ SlrScenariosModel,
4
+ )
1
5
  from flood_adapt.database_builder.database_builder import (
2
6
  Basins,
3
7
  ConfigModel,
4
8
  FootprintsOptions,
5
9
  GuiConfigModel,
10
+ ObsPointModel,
6
11
  SpatialJoinModel,
7
12
  SviConfigModel,
8
13
  TideGaugeConfigModel,
14
+ TideGaugeSource,
9
15
  UnitSystems,
10
16
  create_database,
11
17
  )
@@ -28,6 +34,7 @@ __all__ = [
28
34
  "SpatialJoinModel",
29
35
  "SviConfigModel",
30
36
  "TideGaugeConfigModel",
37
+ "TideGaugeSource",
31
38
  "UnitSystems",
32
39
  "create_database",
33
40
  "BuildingsInfographicModel",
@@ -37,4 +44,7 @@ __all__ = [
37
44
  "RiskInfographicModel",
38
45
  "RoadsInfographicModel",
39
46
  "ImpactCategoriesModel",
47
+ "FloodModel",
48
+ "SlrScenariosModel",
49
+ "ObsPointModel",
40
50
  ]
@@ -17,12 +17,14 @@ import numpy as np
17
17
  import pandas as pd
18
18
  import rioxarray as rxr
19
19
  import tomli
20
+ import tomli_w
20
21
  import xarray as xr
21
22
  from hydromt_fiat import FiatModel as HydromtFiatModel
22
23
  from hydromt_fiat.data_apis.open_street_maps import get_buildings_from_osm
23
24
  from hydromt_sfincs import SfincsModel as HydromtSfincsModel
24
25
  from pydantic import BaseModel, Field
25
26
  from shapely import MultiLineString, MultiPolygon, Polygon
27
+ from shapely.ops import nearest_points
26
28
 
27
29
  from flood_adapt.adapter.fiat_adapter import _FIAT_COLUMNS
28
30
  from flood_adapt.config.fiat import (
@@ -133,6 +135,31 @@ def path_check(str_path: str, config_path: Optional[Path] = None) -> str:
133
135
  return path.as_posix()
134
136
 
135
137
 
138
+ def make_relative(str_path: str | Path, toml_path: Path) -> str:
139
+ """Make a path relative to the config file path.
140
+
141
+ Parameters
142
+ ----------
143
+ str_path : str | Path
144
+ The path to be made relative.
145
+ toml_path : Path
146
+ The path to the config file.
147
+
148
+ Returns
149
+ -------
150
+ str
151
+ The relative path as a string.
152
+ """
153
+ path = Path(str_path)
154
+ if not path.is_absolute():
155
+ return path.as_posix()
156
+ try:
157
+ relative_path = path.relative_to(toml_path.parent)
158
+ return relative_path.as_posix()
159
+ except ValueError:
160
+ return path.as_posix()
161
+
162
+
136
163
  class SpatialJoinModel(BaseModel):
137
164
  """
138
165
  Model for representing a spatial join between geometries and tabular data.
@@ -421,6 +448,8 @@ class ConfigModel(BaseModel):
421
448
  config.database_path = path_check(config.database_path, toml_path)
422
449
  config.fiat = path_check(config.fiat, toml_path)
423
450
  config.sfincs_overland.name = path_check(config.sfincs_overland.name, toml_path)
451
+ if config.dem:
452
+ config.dem.filename = path_check(config.dem.filename, toml_path)
424
453
  if config.sfincs_offshore:
425
454
  config.sfincs_offshore.name = path_check(
426
455
  config.sfincs_offshore.name, toml_path
@@ -445,6 +474,67 @@ class ConfigModel(BaseModel):
445
474
 
446
475
  return config
447
476
 
477
+ def write(self, toml_path: Path) -> None:
478
+ """
479
+ Write the configuration model to a TOML file.
480
+
481
+ Parameters
482
+ ----------
483
+ toml_path : Path
484
+ The path to the TOML file where the configuration will be saved.
485
+ """
486
+ config_dict = self.model_dump(exclude_none=True)
487
+
488
+ # Make paths relative to the config file
489
+ config_dict["database_path"] = make_relative(
490
+ config_dict["database_path"], toml_path
491
+ )
492
+ config_dict["fiat"] = make_relative(config_dict["fiat"], toml_path)
493
+ config_dict["sfincs_overland"]["name"] = make_relative(
494
+ config_dict["sfincs_overland"]["name"], toml_path
495
+ )
496
+ if self.dem:
497
+ config_dict["dem"]["filename"] = make_relative(
498
+ config_dict["dem"]["filename"], toml_path
499
+ )
500
+ if config_dict.get("sfincs_offshore"):
501
+ config_dict["sfincs_offshore"]["name"] = make_relative(
502
+ config_dict["sfincs_offshore"]["name"], toml_path
503
+ )
504
+ if isinstance(self.building_footprints, SpatialJoinModel):
505
+ config_dict["building_footprints"]["file"] = make_relative(
506
+ config_dict["building_footprints"]["file"], toml_path
507
+ )
508
+ if self.tide_gauge and self.tide_gauge.file:
509
+ config_dict["tide_gauge"]["file"] = make_relative(
510
+ config_dict["tide_gauge"]["file"], toml_path
511
+ )
512
+ if self.svi:
513
+ config_dict["svi"]["file"] = make_relative(
514
+ config_dict["svi"]["file"], toml_path
515
+ )
516
+ if self.bfe:
517
+ config_dict["bfe"]["file"] = make_relative(
518
+ config_dict["bfe"]["file"], toml_path
519
+ )
520
+ if self.slr_scenarios:
521
+ config_dict["slr_scenarios"]["file"] = make_relative(
522
+ config_dict["slr_scenarios"]["file"], toml_path
523
+ )
524
+
525
+ if config_dict.get("probabilistic_set"):
526
+ config_dict["probabilistic_set"] = make_relative(
527
+ config_dict["probabilistic_set"], toml_path
528
+ )
529
+
530
+ if config_dict.get("aggregation_areas"):
531
+ for ag in config_dict["aggregation_areas"]:
532
+ ag["file"] = make_relative(ag["file"], toml_path)
533
+
534
+ toml_path.parent.mkdir(parents=True, exist_ok=True)
535
+ with open(toml_path, mode="wb") as fp:
536
+ tomli_w.dump(config_dict, fp)
537
+
448
538
 
449
539
  class DatabaseBuilder:
450
540
  _has_roads: bool = False
@@ -472,15 +562,16 @@ class DatabaseBuilder:
472
562
  @debug_timer
473
563
  def build(self, overwrite: bool = False) -> None:
474
564
  # Check if database already exists
475
- if self.root.exists() and not overwrite:
476
- raise ValueError(
477
- f"There is already a Database folder in '{self.root.as_posix()}'."
478
- )
479
- if self.root.exists() and overwrite:
480
- shutil.rmtree(self.root)
481
- warnings.warn(
482
- f"There is already a Database folder in '{self.root.as_posix()}, which will be overwritten'."
483
- )
565
+ if self.root.exists():
566
+ if overwrite:
567
+ shutil.rmtree(self.root)
568
+ warnings.warn(
569
+ f"There is already a Database folder in '{self.root.as_posix()}, which will be overwritten'."
570
+ )
571
+ else:
572
+ raise ValueError(
573
+ f"There is already a Database folder in '{self.root.as_posix()}'."
574
+ )
484
575
  # Create database folder
485
576
  self.root.mkdir(parents=True)
486
577
 
@@ -823,7 +914,7 @@ class DatabaseBuilder:
823
914
  exposure[_FIAT_COLUMNS.ground_elevation] = exposure["elev"]
824
915
  del exposure["elev"]
825
916
 
826
- self.fiat_model.exposure.exposure_db = exposure
917
+ self.fiat_model.exposure.exposure_db = self._clean_suffix_columns(exposure)
827
918
 
828
919
  def read_damage_unit(self) -> str:
829
920
  if self.fiat_model.exposure.damage_unit is None:
@@ -1087,7 +1178,9 @@ class DatabaseBuilder:
1087
1178
  exposure_csv = exposure_csv.merge(
1088
1179
  gdf_joined, on=_FIAT_COLUMNS.object_id, how="left"
1089
1180
  )
1090
- self.fiat_model.exposure.exposure_db = exposure_csv
1181
+ self.fiat_model.exposure.exposure_db = self._clean_suffix_columns(
1182
+ exposure_csv
1183
+ )
1091
1184
  # Update spatial joins in FIAT model
1092
1185
  if self.fiat_model.spatial_joins["aggregation_areas"] is None:
1093
1186
  self.fiat_model.spatial_joins["aggregation_areas"] = []
@@ -1149,7 +1242,9 @@ class DatabaseBuilder:
1149
1242
  exposure_csv = exposure_csv.merge(
1150
1243
  gdf_joined, on=_FIAT_COLUMNS.object_id, how="left"
1151
1244
  )
1152
- self.fiat_model.exposure.exposure_db = exposure_csv
1245
+ self.fiat_model.exposure.exposure_db = self._clean_suffix_columns(
1246
+ exposure_csv
1247
+ )
1153
1248
  logger.warning(
1154
1249
  "No aggregation areas were available in the FIAT model and none were provided in the config file. The region file will be used as a mock aggregation area."
1155
1250
  )
@@ -1180,7 +1275,9 @@ class DatabaseBuilder:
1180
1275
  exposure_csv = exposure_csv.merge(
1181
1276
  buildings_joined, on=_FIAT_COLUMNS.object_id, how="left"
1182
1277
  )
1183
- self.fiat_model.exposure.exposure_db = exposure_csv
1278
+ self.fiat_model.exposure.exposure_db = self._clean_suffix_columns(
1279
+ exposure_csv
1280
+ )
1184
1281
 
1185
1282
  # Save the spatial file for future use
1186
1283
  svi_path = self.static_path / "templates" / "fiat" / "svi" / "svi.gpkg"
@@ -1199,8 +1296,15 @@ class DatabaseBuilder:
1199
1296
  "'SVI' column present in the FIAT exposure csv. Vulnerability type infometrics can be produced."
1200
1297
  )
1201
1298
  add_attrs = self.fiat_model.spatial_joins["additional_attributes"]
1299
+ if add_attrs is None:
1300
+ logger.warning(
1301
+ "'SVI' column present in the FIAT exposure csv, but no spatial join found with the SVI map."
1302
+ )
1303
+ return None
1304
+
1202
1305
  if "SVI" not in [attr["name"] for attr in add_attrs]:
1203
1306
  logger.warning("No SVI map found to display in the FloodAdapt GUI!")
1307
+ return None
1204
1308
 
1205
1309
  ind = [attr["name"] for attr in add_attrs].index("SVI")
1206
1310
  svi = add_attrs[ind]
@@ -1407,19 +1511,44 @@ class DatabaseBuilder:
1407
1511
  logger.info("Observation points were provided in the config file.")
1408
1512
  obs_points = self.config.obs_point
1409
1513
 
1514
+ model_region = self.sfincs_overland_model.region.union_all()
1515
+
1410
1516
  if self.tide_gauge is not None:
1411
- logger.info(
1412
- "A tide gauge has been setup in the database. It will be used as an observation point as well."
1413
- )
1414
- obs_points.append(
1415
- ObsPointModel(
1416
- name=self.tide_gauge.name,
1417
- description="Tide gauge observation point",
1418
- ID=self.tide_gauge.ID,
1419
- lon=self.tide_gauge.lon,
1420
- lat=self.tide_gauge.lat,
1517
+ # Check if tide gauge is within model domain
1518
+ coord = (
1519
+ gpd.GeoSeries(
1520
+ gpd.points_from_xy(
1521
+ x=[self.tide_gauge.lon], y=[self.tide_gauge.lat]
1522
+ ),
1523
+ crs="EPSG:4326",
1421
1524
  )
1525
+ .to_crs(self.sfincs_overland_model.crs)
1526
+ .iloc[0]
1422
1527
  )
1528
+ # Add tide gauge as obs point if within model region
1529
+ if coord.within(model_region):
1530
+ if self.tide_gauge.name is None:
1531
+ name = "tide_gauge"
1532
+ else:
1533
+ name = self.tide_gauge.name
1534
+ obs_points.append(
1535
+ ObsPointModel(
1536
+ name=name,
1537
+ description="Tide gauge observation point",
1538
+ ID=self.tide_gauge.ID,
1539
+ lon=self.tide_gauge.lon,
1540
+ lat=self.tide_gauge.lat,
1541
+ )
1542
+ )
1543
+ else:
1544
+ boundary = model_region.boundary
1545
+ snapped = nearest_points(coord, boundary)[1]
1546
+ distance = us.UnitfulLength(
1547
+ value=coord.distance(snapped), units=us.UnitTypesLength.meters
1548
+ )
1549
+ logger.warning(
1550
+ f"Tide gauge lies outside the model domain by {distance}. It will not be used as an observation point in FloodAdapt."
1551
+ )
1423
1552
 
1424
1553
  if not obs_points:
1425
1554
  logger.warning(
@@ -1427,20 +1556,25 @@ class DatabaseBuilder:
1427
1556
  )
1428
1557
  return None
1429
1558
 
1559
+ # Check if all obs points are within model domain
1430
1560
  lon = [p.lon for p in obs_points]
1431
1561
  lat = [p.lat for p in obs_points]
1432
1562
  names = [p.name for p in obs_points]
1433
1563
  coords = gpd.GeoDataFrame(
1434
- {"names": names},
1564
+ {"name": names},
1435
1565
  geometry=gpd.points_from_xy(lon, lat),
1436
1566
  crs="EPSG:4326",
1437
1567
  )
1438
1568
  coords = coords.to_crs(self.sfincs_overland_model.crs)
1439
- model_region = self.sfincs_overland_model.region.union_all()
1440
1569
  valid_coords = coords.within(model_region)
1441
1570
  if not valid_coords.all():
1442
1571
  invalid = coords.loc[~valid_coords, "name"].tolist()
1443
- raise ValueError(f"Observation points outside model domain: {invalid}")
1572
+ lat = coords.loc[~valid_coords].geometry.y.tolist()
1573
+ lon = coords.loc[~valid_coords].geometry.x.tolist()
1574
+ bounds = model_region.bounds
1575
+ raise ValueError(
1576
+ f"Observation points outside model domain: {invalid}, {lat=}, {lon=}, {bounds=}"
1577
+ )
1444
1578
 
1445
1579
  return obs_points
1446
1580
 
@@ -1533,7 +1667,7 @@ class DatabaseBuilder:
1533
1667
  db_file_path.parent.mkdir(parents=True, exist_ok=True)
1534
1668
  shutil.copyfile(self.config.tide_gauge.file, db_file_path)
1535
1669
 
1536
- rel_db_path = Path(db_file_path.relative_to(self.static_path))
1670
+ rel_db_path = Path(db_file_path.relative_to(self.static_path)).as_posix()
1537
1671
  logger.warning(
1538
1672
  f"Tide gauge from file {rel_db_path} assumed to be in {self.unit_system.default_length_units}!"
1539
1673
  )
@@ -1665,26 +1799,22 @@ class DatabaseBuilder:
1665
1799
  if self.sfincs_offshore_model is None:
1666
1800
  return None
1667
1801
  # Connect boundary points of overland to output points of offshore
1802
+ # First read in the boundary locations from the overland model
1668
1803
  fn = Path(self.sfincs_overland_model.root) / "sfincs.bnd"
1669
- bnd = pd.read_csv(fn, sep=" ", lineterminator="\n", header=None)
1670
- bnd = bnd.rename(columns={0: "x", 1: "y"})
1671
- bnd_geo = gpd.GeoDataFrame(
1672
- bnd,
1673
- geometry=gpd.points_from_xy(bnd.x, bnd.y),
1804
+ lines = []
1805
+ if fn.exists():
1806
+ with open(fn) as f:
1807
+ lines = f.readlines()
1808
+ coords = [(float(line.split()[0]), float(line.split()[1])) for line in lines]
1809
+ x, y = zip(*coords)
1810
+ bnd = gpd.GeoDataFrame(
1811
+ geometry=gpd.points_from_xy(x, y),
1674
1812
  crs=self.sfincs_overland_model.config["epsg"],
1675
1813
  )
1676
- obs_geo = bnd_geo.to_crs(4326)
1677
- obs_geo["x"] = obs_geo.geometry.x
1678
- obs_geo["y"] = obs_geo.geometry.y
1679
- del obs_geo["geometry"]
1680
- obs_geo["name"] = [f"bnd_pt{num:02d}" for num in range(1, len(obs_geo) + 1)]
1681
- fn_off = Path(self.sfincs_offshore_model.root) / "sfincs.obs"
1682
- obs_geo.to_csv(
1683
- fn_off,
1684
- sep="\t",
1685
- index=False,
1686
- header=False,
1687
- )
1814
+ # Then transform points to offshore crs and save them as observation points
1815
+ obs_geo = bnd.to_crs(self.sfincs_offshore_model.config["epsg"])
1816
+ self.sfincs_offshore_model.setup_observation_points(obs_geo)
1817
+ self.sfincs_offshore_model.write()
1688
1818
  logger.info(
1689
1819
  "Output points of the offshore SFINCS model were reconfigured to the boundary points of the overland SFINCS model."
1690
1820
  )
@@ -1973,12 +2103,12 @@ class DatabaseBuilder:
1973
2103
 
1974
2104
  self.metrics = metrics
1975
2105
 
1976
- def _create_mandatory_metrics(self, metrics):
2106
+ def _create_mandatory_metrics(self, metrics: Metrics):
1977
2107
  metrics.create_mandatory_metrics_event()
1978
2108
  if self._probabilistic_set_name is not None:
1979
2109
  metrics.create_mandatory_metrics_risk()
1980
2110
 
1981
- def _create_event_infographics(self, metrics):
2111
+ def _create_event_infographics(self, metrics: Metrics):
1982
2112
  exposure_type = self._get_exposure_type()
1983
2113
  # If not specific infographic config is given, create a standard one
1984
2114
  if not self.config.event_infographics:
@@ -2010,7 +2140,7 @@ class DatabaseBuilder:
2010
2140
  )
2011
2141
  metrics.create_infographics_metrics_event(config=self.config.event_infographics)
2012
2142
 
2013
- def _create_risk_infographics(self, metrics):
2143
+ def _create_risk_infographics(self, metrics: Metrics):
2014
2144
  exposure_type = self._get_exposure_type()
2015
2145
  # If not specific infographic config is given, create a standard one
2016
2146
  if not self.config.risk_infographics:
@@ -2027,17 +2157,17 @@ class DatabaseBuilder:
2027
2157
  )
2028
2158
  metrics.create_infographics_metrics_risk(config=self.config.risk_infographics)
2029
2159
 
2030
- def _add_additional_event_metrics(self, metrics):
2160
+ def _add_additional_event_metrics(self, metrics: Metrics):
2031
2161
  if self.config.event_additional_infometrics:
2032
2162
  for metric in self.config.event_additional_infometrics:
2033
2163
  metrics.add_event_metric(metric)
2034
2164
 
2035
- def _add_additional_risk_metrics(self, metrics):
2165
+ def _add_additional_risk_metrics(self, metrics: Metrics):
2036
2166
  if self.config.risk_additional_infometrics:
2037
2167
  for metric in self.config.risk_additional_infometrics:
2038
2168
  metrics.add_risk_metric(metric)
2039
2169
 
2040
- def _write_infometrics(self, metrics, path_im, path_ig):
2170
+ def _write_infometrics(self, metrics: Metrics, path_im: Path, path_ig: Path):
2041
2171
  if self._aggregation_areas is None:
2042
2172
  self._aggregation_areas = self.create_aggregation_areas()
2043
2173
  aggr_levels = [aggr.name for aggr in self._aggregation_areas]
@@ -2216,7 +2346,7 @@ class DatabaseBuilder:
2216
2346
 
2217
2347
  # Set model building footprints
2218
2348
  self.fiat_model.building_footprint = building_footprints
2219
- self.fiat_model.exposure.exposure_db = exposure_csv
2349
+ self.fiat_model.exposure.exposure_db = self._clean_suffix_columns(exposure_csv)
2220
2350
 
2221
2351
  # Save site attributes
2222
2352
  buildings_path = geo_path.relative_to(self.static_path)
@@ -2483,6 +2613,11 @@ class DatabaseBuilder:
2483
2613
  """
2484
2614
  # Make sure only csv objects have geometries
2485
2615
  for i, geoms in enumerate(self.fiat_model.exposure.exposure_geoms):
2616
+ if _FIAT_COLUMNS.object_id not in geoms.columns:
2617
+ logger.warning(
2618
+ f"Geometry '{self.fiat_model.exposure.geom_names[i]}' does not have an '{_FIAT_COLUMNS.object_id}' column and will be ignored."
2619
+ )
2620
+ continue
2486
2621
  keep = geoms[_FIAT_COLUMNS.object_id].isin(
2487
2622
  self.fiat_model.exposure.exposure_db[_FIAT_COLUMNS.object_id]
2488
2623
  )
@@ -2523,6 +2658,35 @@ class DatabaseBuilder:
2523
2658
 
2524
2659
  return gdf
2525
2660
 
2661
+ @staticmethod
2662
+ def _clean_suffix_columns(df: pd.DataFrame) -> pd.DataFrame:
2663
+ """Detect and resolves duplicate columns with _x/_y suffixes that appear after a pandas merge.
2664
+
2665
+ (e.g., 'Aggregation Label: Census Blockgroup_x' and 'Aggregation Label: Census Blockgroup_y').
2666
+
2667
+ Keeps the first non-null column of each pair and removes redundant ones.
2668
+ """
2669
+ cols = df.columns.tolist()
2670
+ suffix_pairs = {}
2671
+
2672
+ for col in cols:
2673
+ if col.endswith("_x"):
2674
+ base = col[:-2]
2675
+ if f"{base}_y" in df.columns:
2676
+ suffix_pairs[base] = (f"{base}_x", f"{base}_y")
2677
+
2678
+ for base, (col_x, col_y) in suffix_pairs.items():
2679
+ # If both columns exist, prefer the one with more non-null values
2680
+ x_notna = df[col_x].notna().sum()
2681
+ y_notna = df[col_y].notna().sum()
2682
+ keep_col = col_x if x_notna >= y_notna else col_y
2683
+ df[base] = df[keep_col]
2684
+
2685
+ # Drop the old suffixed versions
2686
+ df = df.drop(columns=[col_x, col_y])
2687
+
2688
+ return df
2689
+
2526
2690
 
2527
2691
  def create_database(config: Union[str, Path, ConfigModel], overwrite=False) -> None:
2528
2692
  """Create a new database from a configuration file or ConfigModel.
@@ -79,7 +79,8 @@ class DbsScenario(DbsTemplate[Scenario]):
79
79
  """
80
80
  event_left = self._database.events.get(left.event)
81
81
  event_right = self._database.events.get(right.event)
82
- equal_events = event_left == event_right
82
+ # Deep-compare events including forcing data contents
83
+ equal_events = event_left.data_equivalent(event_right)
83
84
 
84
85
  left_projection = self._database.projections.get(left.projection)
85
86
  right_projection = self._database.projections.get(right.projection)
@@ -117,6 +117,63 @@ class Event(Object):
117
117
  for forcing in self.get_forcings():
118
118
  forcing.save_additional(output_dir)
119
119
 
120
+ def data_equivalent(self, other: "Event") -> bool:
121
+ """Deep-compare two events, including forcing data contents.
122
+
123
+ Compares core attributes (time, template, mode, rainfall_multiplier) and then
124
+ verifies that each forcing (by type) has the same data fingerprint. For
125
+ path-based forcings, the fingerprint hashes the file bytes; for others,
126
+ a canonical attribute-based hash is used.
127
+
128
+ Parameters
129
+ ----------
130
+ other : Event
131
+ The event to compare against.
132
+
133
+ Returns
134
+ -------
135
+ bool
136
+ True when events are equivalent in terms of their hazard inputs.
137
+ """
138
+ if not isinstance(other, Event):
139
+ return False
140
+
141
+ # Compare high-level attributes first
142
+ if (
143
+ self.template != other.template
144
+ or self.mode != other.mode
145
+ or self.rainfall_multiplier != other.rainfall_multiplier
146
+ or self.time != other.time
147
+ ):
148
+ return False
149
+
150
+ # Compare allowed forcing types present in each event
151
+ if set(self.forcings.keys()) != set(other.forcings.keys()):
152
+ return False
153
+
154
+ # Build comparable, sorted fingerprint lists per forcing type
155
+ def fingerprints(evt: "Event") -> dict[ForcingType, list[tuple[str, str]]]:
156
+ d: dict[ForcingType, list[tuple[str, str]]] = {}
157
+ for ftype, flist in evt.forcings.items():
158
+ fps: list[tuple[str, str]] = []
159
+ for f in flist:
160
+ # Include source and the fingerprint to guard against collisions across different sources
161
+ src = f.source.value if hasattr(f, "source") else ""
162
+ fp = f.content_fingerprint() # type: ignore[attr-defined]
163
+ fps.append((src, fp))
164
+ # Sort for order-insensitive comparison
165
+ d[ftype] = sorted(fps, key=lambda t: (t[0], t[1]))
166
+ return d
167
+
168
+ left_fp = fingerprints(self)
169
+ right_fp = fingerprints(other)
170
+
171
+ for ftype in left_fp.keys():
172
+ if left_fp[ftype] != right_fp[ftype]:
173
+ return False
174
+
175
+ return True
176
+
120
177
  @classmethod
121
178
  def load_file(cls, file_path: Path | str | os.PathLike) -> "Event":
122
179
  """Load object from file.
@@ -1,3 +1,5 @@
1
+ import hashlib
2
+ import json
1
3
  import os
2
4
  from abc import ABC, abstractmethod
3
5
  from enum import Enum
@@ -72,7 +74,7 @@ class IForcing(BaseModel, ABC):
72
74
 
73
75
  def save_additional(self, output_dir: Path | str | os.PathLike) -> None:
74
76
  """Save additional data of the forcing."""
75
- return
77
+ pass
76
78
 
77
79
  @field_serializer("path", check_fields=False)
78
80
  @classmethod
@@ -80,6 +82,49 @@ class IForcing(BaseModel, ABC):
80
82
  """Serialize filepath-like fields by saving only the filename. It is assumed that the file will be saved in the same directory."""
81
83
  return value.name
82
84
 
85
+ def content_fingerprint(self) -> str:
86
+ """Return a stable fingerprint of the forcing's underlying data.
87
+
88
+ - If a file-backed `path` attribute exists and the file exists, hash its bytes (SHA-256).
89
+ - Otherwise, hash a canonical JSON dump of the model (excluding volatile fields like `path`).
90
+
91
+ Returns
92
+ -------
93
+ str
94
+ A fingerprint string that changes when the forcing's effective data changes.
95
+ """
96
+ # Prefer hashing the actual file content when available
97
+ try:
98
+ p = getattr(self, "path", None)
99
+ if isinstance(p, Path) and p and p.exists():
100
+ sha = hashlib.sha256()
101
+ with p.open("rb") as f:
102
+ for chunk in iter(lambda: f.read(8192), b""):
103
+ sha.update(chunk)
104
+ # Include filename to disambiguate multi-forcing scenarios with identical content
105
+ return f"FILE:{p.name}:{sha.hexdigest()}"
106
+ except Exception:
107
+ # Fall through to attribute-based hashing if anything goes wrong
108
+ pass
109
+
110
+ # Fallback: hash the model attributes (excluding volatile fields like path)
111
+ data = self.model_dump(exclude_none=True)
112
+ # Remove potentially non-stable/absolute fields
113
+ data.pop("path", None)
114
+
115
+ # Ensure enums are serialized to their values for stable hashing
116
+ if isinstance(data.get("type"), Enum):
117
+ data["type"] = data["type"].value
118
+ if isinstance(data.get("source"), Enum):
119
+ data["source"] = data["source"].value
120
+
121
+ payload = json.dumps(data, sort_keys=True, default=str).encode("utf-8")
122
+ return f"ATTR:{hashlib.sha256(payload).hexdigest()}"
123
+
124
+ def _post_load(self, file_path: Path | str | os.PathLike, **kwargs) -> None:
125
+ """Post-load hook, called at the end of `load_file`, to perform any additional loading steps after loading from file."""
126
+ return
127
+
83
128
 
84
129
  class IDischarge(IForcing):
85
130
  type: ForcingType = ForcingType.DISCHARGE
@@ -8,6 +8,7 @@ import requests
8
8
  from noaa_coops.station import COOPSAPIError
9
9
  from pydantic import BaseModel, model_validator
10
10
 
11
+ from flood_adapt.config import Settings
11
12
  from flood_adapt.misc.log import FloodAdaptLogging
12
13
  from flood_adapt.objects.forcing import unit_system as us
13
14
  from flood_adapt.objects.forcing.time_frame import TimeFrame
@@ -56,7 +57,7 @@ class TideGauge(BaseModel):
56
57
  source: TideGaugeSource
57
58
  reference: str
58
59
  ID: Optional[int] = None # Attribute used to download from correct gauge
59
- file: Optional[Path] = None # for locally stored data
60
+ file: Optional[str] = None # for locally stored data
60
61
  lat: Optional[float] = None
61
62
  lon: Optional[float] = None
62
63
  units: us.UnitTypesLength = (
@@ -104,7 +105,8 @@ class TideGauge(BaseModel):
104
105
  """
105
106
  logger.info(f"Retrieving waterlevels for tide gauge {self.ID} for {time}")
106
107
  if self.file:
107
- gauge_data = self._read_imported_waterlevels(time=time, path=self.file)
108
+ abs_path = Settings().database_path / "static" / self.file
109
+ gauge_data = self._read_imported_waterlevels(time=time, path=abs_path)
108
110
  else:
109
111
  gauge_data = self._download_tide_gauge_data(time=time)
110
112
 
@@ -110,10 +110,12 @@ class ValueUnitPair(ABC, BaseModel, Generic[TUnit]):
110
110
  raise ValueError(f"Unsupported or unknown unit: {str_unit}")
111
111
 
112
112
  def __str__(self) -> str:
113
- return f"{self.value} {self.units.value}"
113
+ return f"{self.value:.2f} {self.units.value}"
114
114
 
115
115
  def __repr__(self) -> str:
116
- return f"{type(self).__name__}(value={self.value}, units={self.units})"
116
+ return (
117
+ f"{type(self).__name__}(value={self.value:.2f}, units={self.units.value})"
118
+ )
117
119
 
118
120
  def __sub__(self: TClass, other: TClass) -> TClass:
119
121
  if not isinstance(other, type(self)):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: flood-adapt
3
- Version: 1.1.3
3
+ Version: 1.1.5
4
4
  Summary: A software package support system which can be used to assess the benefits and costs of flood resilience measures
5
5
  Author-email: Gundula Winter <Gundula.Winter@deltares.nl>, Panos Athanasiou <Panos.Athanasiou@deltares.nl>, Frederique de Groen <Frederique.deGroen@deltares.nl>, Tim de Wilde <Tim.deWilde@deltares.nl>, Julian Hofer <Julian.Hofer@deltares.nl>, Daley Adrichem <Daley.Adrichem@deltares.nl>, Luuk Blom <Luuk.Blom@deltares.nl>
6
6
  License: ====================================================
@@ -712,14 +712,12 @@ Requires-Dist: cht-cyclones<2.0,>=1.0.3
712
712
  Requires-Dist: cht-meteo<1.0,>=0.3.1
713
713
  Requires-Dist: cht-observations<1.0,>=0.2.1
714
714
  Requires-Dist: cht-tide<1.0,>=0.1.1
715
- Requires-Dist: dask==2024.11.2
716
- Requires-Dist: numba_celltree==0.2.2
717
715
  Requires-Dist: fiat-toolbox<0.2.0,>=0.1.22
718
716
  Requires-Dist: fiona<2.0,>=1.0
719
717
  Requires-Dist: geojson<4.0,>=3.0
720
718
  Requires-Dist: geopandas<2.0,>=1.0
721
719
  Requires-Dist: hydromt-fiat<1.0,>=0.5.9
722
- Requires-Dist: hydromt-sfincs<2.0,>=1.2.0
720
+ Requires-Dist: hydromt-sfincs<2.0,>=1.2.2
723
721
  Requires-Dist: numpy<2.0,>=1.0
724
722
  Requires-Dist: numpy-financial<2.0,>=1.0
725
723
  Requires-Dist: pandas<3.0,>=2.0
@@ -2,14 +2,12 @@ cht-cyclones<2.0,>=1.0.3
2
2
  cht-meteo<1.0,>=0.3.1
3
3
  cht-observations<1.0,>=0.2.1
4
4
  cht-tide<1.0,>=0.1.1
5
- dask==2024.11.2
6
- numba_celltree==0.2.2
7
5
  fiat-toolbox<0.2.0,>=0.1.22
8
6
  fiona<2.0,>=1.0
9
7
  geojson<4.0,>=3.0
10
8
  geopandas<2.0,>=1.0
11
9
  hydromt-fiat<1.0,>=0.5.9
12
- hydromt-sfincs<2.0,>=1.2.0
10
+ hydromt-sfincs<2.0,>=1.2.2
13
11
  numpy<2.0,>=1.0
14
12
  numpy-financial<2.0,>=1.0
15
13
  pandas<3.0,>=2.0
@@ -30,14 +30,12 @@ dependencies = [
30
30
  "cht-meteo >=0.3.1,<1.0",
31
31
  "cht-observations >=0.2.1,<1.0",
32
32
  "cht-tide >=0.1.1,<1.0",
33
- "dask ==2024.11.2", # not a dependency, but a we need to use this version
34
- "numba_celltree ==0.2.2", # not a dependency, but a we need to use this version
35
33
  "fiat-toolbox >=0.1.22,<0.2.0",
36
34
  "fiona >=1.0,<2.0",
37
35
  "geojson >=3.0,<4.0",
38
36
  "geopandas >=1.0,<2.0",
39
37
  "hydromt-fiat >=0.5.9,<1.0",
40
- "hydromt-sfincs >=1.2.0,<2.0",
38
+ "hydromt-sfincs >=1.2.2,<2.0",
41
39
  "numpy >=1.0,<2.0",
42
40
  "numpy-financial >=1.0,<2.0",
43
41
  "pandas >=2.0,<3.0",
File without changes
File without changes
File without changes