flood-adapt 0.3.0__py3-none-any.whl
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.
- flood_adapt/__init__.py +22 -0
- flood_adapt/adapter/__init__.py +9 -0
- flood_adapt/adapter/fiat_adapter.py +1502 -0
- flood_adapt/adapter/interface/__init__.py +0 -0
- flood_adapt/adapter/interface/hazard_adapter.py +70 -0
- flood_adapt/adapter/interface/impact_adapter.py +36 -0
- flood_adapt/adapter/interface/model_adapter.py +89 -0
- flood_adapt/adapter/interface/offshore.py +19 -0
- flood_adapt/adapter/sfincs_adapter.py +1857 -0
- flood_adapt/adapter/sfincs_offshore.py +193 -0
- flood_adapt/config/__init__.py +0 -0
- flood_adapt/config/config.py +245 -0
- flood_adapt/config/fiat.py +219 -0
- flood_adapt/config/gui.py +224 -0
- flood_adapt/config/sfincs.py +336 -0
- flood_adapt/config/site.py +124 -0
- flood_adapt/database_builder/__init__.py +0 -0
- flood_adapt/database_builder/database_builder.py +2175 -0
- flood_adapt/database_builder/templates/default_units/imperial.toml +9 -0
- flood_adapt/database_builder/templates/default_units/metric.toml +9 -0
- flood_adapt/database_builder/templates/green_infra_table/green_infra_lookup_table.csv +10 -0
- flood_adapt/database_builder/templates/icons/black_down_48x48.png +0 -0
- flood_adapt/database_builder/templates/icons/black_left_48x48.png +0 -0
- flood_adapt/database_builder/templates/icons/black_right_48x48.png +0 -0
- flood_adapt/database_builder/templates/icons/black_up_48x48.png +0 -0
- flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-16_white_down.png +0 -0
- flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-16_white_left.png +0 -0
- flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-16_white_right.png +0 -0
- flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-16_white_up.png +0 -0
- flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-24_black_down.png +0 -0
- flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-24_black_left.png +0 -0
- flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-24_black_right.png +0 -0
- flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-24_black_up.png +0 -0
- flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-24_white_left.png +0 -0
- flood_adapt/database_builder/templates/icons/icons8-triangle-arrow-24_white_right.png +0 -0
- flood_adapt/database_builder/templates/icons/white_down_48x48.png +0 -0
- flood_adapt/database_builder/templates/icons/white_left_48x48.png +0 -0
- flood_adapt/database_builder/templates/icons/white_right_48x48.png +0 -0
- flood_adapt/database_builder/templates/icons/white_up_48x48.png +0 -0
- flood_adapt/database_builder/templates/infographics/OSM/config_charts.toml +90 -0
- flood_adapt/database_builder/templates/infographics/OSM/config_people.toml +57 -0
- flood_adapt/database_builder/templates/infographics/OSM/config_risk_charts.toml +121 -0
- flood_adapt/database_builder/templates/infographics/OSM/config_roads.toml +65 -0
- flood_adapt/database_builder/templates/infographics/OSM/styles.css +45 -0
- flood_adapt/database_builder/templates/infographics/US_NSI/config_charts.toml +126 -0
- flood_adapt/database_builder/templates/infographics/US_NSI/config_people.toml +60 -0
- flood_adapt/database_builder/templates/infographics/US_NSI/config_risk_charts.toml +121 -0
- flood_adapt/database_builder/templates/infographics/US_NSI/config_roads.toml +65 -0
- flood_adapt/database_builder/templates/infographics/US_NSI/styles.css +45 -0
- flood_adapt/database_builder/templates/infographics/images/ambulance.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/car.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/cart.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/firetruck.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/hospital.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/house.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/info.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/money.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/person.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/school.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/truck.png +0 -0
- flood_adapt/database_builder/templates/infographics/images/walking_person.png +0 -0
- flood_adapt/database_builder/templates/infometrics/OSM/metrics_additional_risk_configs.toml +4 -0
- flood_adapt/database_builder/templates/infometrics/OSM/with_SVI/infographic_metrics_config.toml +143 -0
- flood_adapt/database_builder/templates/infometrics/OSM/with_SVI/infographic_metrics_config_risk.toml +153 -0
- flood_adapt/database_builder/templates/infometrics/OSM/without_SVI/infographic_metrics_config.toml +127 -0
- flood_adapt/database_builder/templates/infometrics/OSM/without_SVI/infographic_metrics_config_risk.toml +57 -0
- flood_adapt/database_builder/templates/infometrics/US_NSI/metrics_additional_risk_configs.toml +4 -0
- flood_adapt/database_builder/templates/infometrics/US_NSI/with_SVI/infographic_metrics_config.toml +191 -0
- flood_adapt/database_builder/templates/infometrics/US_NSI/with_SVI/infographic_metrics_config_risk.toml +153 -0
- flood_adapt/database_builder/templates/infometrics/US_NSI/without_SVI/infographic_metrics_config.toml +178 -0
- flood_adapt/database_builder/templates/infometrics/US_NSI/without_SVI/infographic_metrics_config_risk.toml +57 -0
- flood_adapt/database_builder/templates/infometrics/mandatory_metrics_config.toml +9 -0
- flood_adapt/database_builder/templates/infometrics/mandatory_metrics_config_risk.toml +65 -0
- flood_adapt/database_builder/templates/mapbox_layers/bin_colors.toml +5 -0
- flood_adapt/database_builder.py +16 -0
- flood_adapt/dbs_classes/__init__.py +21 -0
- flood_adapt/dbs_classes/database.py +716 -0
- flood_adapt/dbs_classes/dbs_benefit.py +97 -0
- flood_adapt/dbs_classes/dbs_event.py +91 -0
- flood_adapt/dbs_classes/dbs_measure.py +103 -0
- flood_adapt/dbs_classes/dbs_projection.py +52 -0
- flood_adapt/dbs_classes/dbs_scenario.py +150 -0
- flood_adapt/dbs_classes/dbs_static.py +261 -0
- flood_adapt/dbs_classes/dbs_strategy.py +147 -0
- flood_adapt/dbs_classes/dbs_template.py +302 -0
- flood_adapt/dbs_classes/interface/database.py +147 -0
- flood_adapt/dbs_classes/interface/element.py +137 -0
- flood_adapt/dbs_classes/interface/static.py +47 -0
- flood_adapt/flood_adapt.py +1371 -0
- flood_adapt/misc/__init__.py +0 -0
- flood_adapt/misc/database_user.py +16 -0
- flood_adapt/misc/log.py +183 -0
- flood_adapt/misc/path_builder.py +54 -0
- flood_adapt/misc/utils.py +185 -0
- flood_adapt/objects/__init__.py +59 -0
- flood_adapt/objects/benefits/__init__.py +0 -0
- flood_adapt/objects/benefits/benefits.py +61 -0
- flood_adapt/objects/events/__init__.py +0 -0
- flood_adapt/objects/events/event_factory.py +135 -0
- flood_adapt/objects/events/event_set.py +84 -0
- flood_adapt/objects/events/events.py +221 -0
- flood_adapt/objects/events/historical.py +55 -0
- flood_adapt/objects/events/hurricane.py +64 -0
- flood_adapt/objects/events/synthetic.py +48 -0
- flood_adapt/objects/forcing/__init__.py +0 -0
- flood_adapt/objects/forcing/csv.py +68 -0
- flood_adapt/objects/forcing/discharge.py +66 -0
- flood_adapt/objects/forcing/forcing.py +142 -0
- flood_adapt/objects/forcing/forcing_factory.py +182 -0
- flood_adapt/objects/forcing/meteo_handler.py +93 -0
- flood_adapt/objects/forcing/netcdf.py +40 -0
- flood_adapt/objects/forcing/plotting.py +428 -0
- flood_adapt/objects/forcing/rainfall.py +98 -0
- flood_adapt/objects/forcing/tide_gauge.py +191 -0
- flood_adapt/objects/forcing/time_frame.py +77 -0
- flood_adapt/objects/forcing/timeseries.py +552 -0
- flood_adapt/objects/forcing/unit_system.py +580 -0
- flood_adapt/objects/forcing/waterlevels.py +108 -0
- flood_adapt/objects/forcing/wind.py +124 -0
- flood_adapt/objects/measures/__init__.py +0 -0
- flood_adapt/objects/measures/measure_factory.py +92 -0
- flood_adapt/objects/measures/measures.py +506 -0
- flood_adapt/objects/object_model.py +68 -0
- flood_adapt/objects/projections/__init__.py +0 -0
- flood_adapt/objects/projections/projections.py +89 -0
- flood_adapt/objects/scenarios/__init__.py +0 -0
- flood_adapt/objects/scenarios/scenarios.py +22 -0
- flood_adapt/objects/strategies/__init__.py +0 -0
- flood_adapt/objects/strategies/strategies.py +68 -0
- flood_adapt/workflows/__init__.py +0 -0
- flood_adapt/workflows/benefit_runner.py +541 -0
- flood_adapt/workflows/floodmap.py +85 -0
- flood_adapt/workflows/impacts_integrator.py +82 -0
- flood_adapt/workflows/scenario_runner.py +69 -0
- flood_adapt-0.3.0.dist-info/LICENSE +21 -0
- flood_adapt-0.3.0.dist-info/METADATA +183 -0
- flood_adapt-0.3.0.dist-info/RECORD +139 -0
- flood_adapt-0.3.0.dist-info/WHEEL +5 -0
- flood_adapt-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
from flood_adapt.adapter.interface.offshore import IOffshoreSfincsHandler
|
|
7
|
+
from flood_adapt.adapter.sfincs_adapter import SfincsAdapter
|
|
8
|
+
from flood_adapt.misc.database_user import DatabaseUser
|
|
9
|
+
from flood_adapt.misc.log import FloodAdaptLogging
|
|
10
|
+
from flood_adapt.misc.path_builder import (
|
|
11
|
+
ObjectDir,
|
|
12
|
+
TopLevelDir,
|
|
13
|
+
db_path,
|
|
14
|
+
)
|
|
15
|
+
from flood_adapt.objects.events.event_set import EventSet
|
|
16
|
+
from flood_adapt.objects.events.events import Event, Mode
|
|
17
|
+
from flood_adapt.objects.events.historical import HistoricalEvent
|
|
18
|
+
from flood_adapt.objects.forcing.forcing import (
|
|
19
|
+
ForcingSource,
|
|
20
|
+
IWind,
|
|
21
|
+
)
|
|
22
|
+
from flood_adapt.objects.forcing.meteo_handler import MeteoHandler
|
|
23
|
+
from flood_adapt.objects.forcing.wind import WindMeteo
|
|
24
|
+
from flood_adapt.objects.scenarios.scenarios import Scenario
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class OffshoreSfincsHandler(IOffshoreSfincsHandler, DatabaseUser):
|
|
28
|
+
logger = FloodAdaptLogging.getLogger("OffshoreSfincsAdapter")
|
|
29
|
+
template_path: Path
|
|
30
|
+
|
|
31
|
+
def __init__(self, scenario: Scenario, event: Event) -> None:
|
|
32
|
+
self.template_path = (
|
|
33
|
+
self.database.static.get_offshore_sfincs_model().get_model_root()
|
|
34
|
+
)
|
|
35
|
+
self.scenario = scenario
|
|
36
|
+
if isinstance(event, EventSet):
|
|
37
|
+
raise ValueError(
|
|
38
|
+
"OffshoreSfincsHandler does not support EventSets. Provide the sub events directly "
|
|
39
|
+
)
|
|
40
|
+
self.event = event
|
|
41
|
+
|
|
42
|
+
def get_resulting_waterlevels(self) -> pd.DataFrame:
|
|
43
|
+
"""Get the water levels from the offshore model.
|
|
44
|
+
|
|
45
|
+
Note that the returned water levels are relative to the reference datum of the offshore model.
|
|
46
|
+
To convert to a different datum, add the offshore reference datum height and subtract the desired reference datum height.
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
pd.DataFrame
|
|
51
|
+
A DataFrame with the water levels for each boundary condition point. Relative to the reference datum of the offshore model.
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
path = self._get_simulation_path()
|
|
55
|
+
if not self.requires_offshore_run(self.event):
|
|
56
|
+
raise ValueError("Offshore model is not required for this event")
|
|
57
|
+
|
|
58
|
+
self.run_offshore()
|
|
59
|
+
|
|
60
|
+
with SfincsAdapter(model_root=path) as offshore_model:
|
|
61
|
+
waterlevels = offshore_model.get_wl_df_from_offshore_his_results()
|
|
62
|
+
|
|
63
|
+
return waterlevels
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def requires_offshore_run(event: Event) -> bool:
|
|
67
|
+
return any(
|
|
68
|
+
forcing.source in [ForcingSource.MODEL, ForcingSource.TRACK]
|
|
69
|
+
for forcing in event.get_forcings()
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def run_offshore(self):
|
|
73
|
+
"""Prepare the forcings of the historical event.
|
|
74
|
+
|
|
75
|
+
If the forcings require it, this function will:
|
|
76
|
+
- preprocess and run offshore model: prepare and run the offshore model to obtain water levels for the boundary condition of the nearshore model.
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
sim_path = self._get_simulation_path()
|
|
80
|
+
|
|
81
|
+
sim_path.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
self._preprocess_sfincs_offshore()
|
|
83
|
+
self._execute_sfincs_offshore(sim_path)
|
|
84
|
+
|
|
85
|
+
def _preprocess_sfincs_offshore(self):
|
|
86
|
+
"""Preprocess offshore model to obtain water levels for boundary condition of the nearshore model.
|
|
87
|
+
|
|
88
|
+
This function is reused for ForcingSources: MODEL & TRACK.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
sim_path path to the root of the offshore model
|
|
92
|
+
"""
|
|
93
|
+
self.logger.info(
|
|
94
|
+
f"Preparing offshore model to generate waterlevels for `{self.scenario.name}`"
|
|
95
|
+
)
|
|
96
|
+
sim_path = self._get_simulation_path()
|
|
97
|
+
# SfincsAdapter.write() doesnt write the bca file apparently so we need to copy the template
|
|
98
|
+
if sim_path.exists():
|
|
99
|
+
shutil.rmtree(sim_path)
|
|
100
|
+
shutil.copytree(self.template_path, sim_path)
|
|
101
|
+
|
|
102
|
+
with SfincsAdapter(model_root=sim_path) as _offshore_model:
|
|
103
|
+
if _offshore_model.sfincs_completed(sim_path):
|
|
104
|
+
_offshore_model.logger.info(
|
|
105
|
+
f"Skip preprocessing offshore model as it has already been run for `{self.scenario.name}`."
|
|
106
|
+
)
|
|
107
|
+
return
|
|
108
|
+
# Load objects, set root & write template model
|
|
109
|
+
_offshore_model._load_scenario_objects(self.scenario, self.event)
|
|
110
|
+
_offshore_model.write(path_out=sim_path)
|
|
111
|
+
_offshore_model.set_timing(self.event.time)
|
|
112
|
+
|
|
113
|
+
# Add water levels
|
|
114
|
+
_offshore_model._add_bzs_from_bca(
|
|
115
|
+
_offshore_model._event, _offshore_model._projection.physical_projection
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Add spw if applicable
|
|
119
|
+
track_forcings = [
|
|
120
|
+
f
|
|
121
|
+
for f in _offshore_model._event.get_forcings()
|
|
122
|
+
if f.source == ForcingSource.TRACK
|
|
123
|
+
]
|
|
124
|
+
if track_forcings:
|
|
125
|
+
for forcing in track_forcings:
|
|
126
|
+
_offshore_model.add_forcing(forcing)
|
|
127
|
+
|
|
128
|
+
# Add wind and if applicable pressure forcing from meteo data
|
|
129
|
+
elif isinstance(_offshore_model._event, HistoricalEvent):
|
|
130
|
+
wind_forcings = [
|
|
131
|
+
f
|
|
132
|
+
for f in _offshore_model._event.get_forcings()
|
|
133
|
+
if isinstance(f, IWind)
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
if wind_forcings:
|
|
137
|
+
if len(wind_forcings) > 1:
|
|
138
|
+
raise ValueError("Only one wind forcing is allowed")
|
|
139
|
+
wind_forcing = wind_forcings[0]
|
|
140
|
+
|
|
141
|
+
# Add wind forcing
|
|
142
|
+
if wind_forcing not in track_forcings:
|
|
143
|
+
_offshore_model.add_forcing(wind_forcing)
|
|
144
|
+
|
|
145
|
+
# Add pressure forcing for the offshore model (this doesnt happen normally in _add_forcing_wind() for overland models)
|
|
146
|
+
if isinstance(wind_forcing, WindMeteo):
|
|
147
|
+
ds = MeteoHandler().read(_offshore_model._event.time)
|
|
148
|
+
_offshore_model._add_pressure_forcing_from_grid(ds=ds)
|
|
149
|
+
|
|
150
|
+
# write sfincs model in output destination
|
|
151
|
+
_offshore_model.write(path_out=sim_path)
|
|
152
|
+
|
|
153
|
+
def _execute_sfincs_offshore(self, sim_path: Path):
|
|
154
|
+
self.logger.info(f"Running offshore model in {sim_path}")
|
|
155
|
+
sim_path = self._get_simulation_path()
|
|
156
|
+
with SfincsAdapter(model_root=sim_path) as _offshore_model:
|
|
157
|
+
if _offshore_model.sfincs_completed(sim_path):
|
|
158
|
+
self.logger.info(
|
|
159
|
+
"Skip running offshore model as it has already been run."
|
|
160
|
+
)
|
|
161
|
+
return
|
|
162
|
+
try:
|
|
163
|
+
_offshore_model.execute(path=sim_path)
|
|
164
|
+
except RuntimeError as e:
|
|
165
|
+
raise RuntimeError(
|
|
166
|
+
f"Failed to run offshore model for {self.scenario.name}"
|
|
167
|
+
) from e
|
|
168
|
+
|
|
169
|
+
def _get_simulation_path(self) -> Path:
|
|
170
|
+
main_event = self.database.events.get(self.scenario.event)
|
|
171
|
+
if main_event.mode == Mode.risk:
|
|
172
|
+
return (
|
|
173
|
+
db_path(
|
|
174
|
+
TopLevelDir.output,
|
|
175
|
+
object_dir=ObjectDir.scenario,
|
|
176
|
+
obj_name=self.scenario.name,
|
|
177
|
+
)
|
|
178
|
+
/ "Flooding"
|
|
179
|
+
/ "simulations"
|
|
180
|
+
/ self.event.name
|
|
181
|
+
/ self.template_path.name
|
|
182
|
+
)
|
|
183
|
+
else:
|
|
184
|
+
return (
|
|
185
|
+
db_path(
|
|
186
|
+
TopLevelDir.output,
|
|
187
|
+
object_dir=ObjectDir.scenario,
|
|
188
|
+
obj_name=self.scenario.name,
|
|
189
|
+
)
|
|
190
|
+
/ "Flooding"
|
|
191
|
+
/ "simulations"
|
|
192
|
+
/ self.template_path.name
|
|
193
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
from os import environ, listdir
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from platform import system
|
|
4
|
+
from typing import ClassVar
|
|
5
|
+
|
|
6
|
+
import tomli
|
|
7
|
+
import tomli_w
|
|
8
|
+
from pydantic import (
|
|
9
|
+
Field,
|
|
10
|
+
computed_field,
|
|
11
|
+
field_serializer,
|
|
12
|
+
model_validator,
|
|
13
|
+
)
|
|
14
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Settings(BaseSettings):
|
|
18
|
+
"""
|
|
19
|
+
The configuration settings for the FloodAdapt database and integrator.
|
|
20
|
+
|
|
21
|
+
Precedence is as follows: user arguments > environment variables > defaults in this class.
|
|
22
|
+
When loading is done, the settings are validated and the environment variables are updated with the loaded values.
|
|
23
|
+
|
|
24
|
+
If any required settings are missing or invalid, a ValidationError is raised.
|
|
25
|
+
|
|
26
|
+
Usage
|
|
27
|
+
-----
|
|
28
|
+
from flood_adapt.config import Settings
|
|
29
|
+
|
|
30
|
+
One of the following:
|
|
31
|
+
|
|
32
|
+
1) Load settings from environment variables, if no environment variables are set, use defaults defined in the class:
|
|
33
|
+
`settings = Settings()`
|
|
34
|
+
|
|
35
|
+
2) Load settings from a .toml file, overwriting any environment variables set:
|
|
36
|
+
`settings = Settings.read(toml_path: Path)`
|
|
37
|
+
|
|
38
|
+
3) Load settings from keyword arguments, overwriting any environment variables:
|
|
39
|
+
`settings = Settings(DATABASE_ROOT="path/to/database", DATABASE_NAME="database_name", SYSTEM_FOLDER="path/to/system_folder")`
|
|
40
|
+
|
|
41
|
+
Attributes
|
|
42
|
+
----------
|
|
43
|
+
database_name : str
|
|
44
|
+
The name of the database.
|
|
45
|
+
database_root : Path
|
|
46
|
+
The root directory of the database.
|
|
47
|
+
system_folder : Path
|
|
48
|
+
The root directory of the system folder containing the kernels.
|
|
49
|
+
delete_crashed_runs : bool
|
|
50
|
+
Whether to delete crashed/corrupted runs immediately after they are detected.
|
|
51
|
+
|
|
52
|
+
Properties
|
|
53
|
+
----------
|
|
54
|
+
database_path : Path
|
|
55
|
+
The full path to the database.
|
|
56
|
+
sfincs_path : Path
|
|
57
|
+
The path to the SFINCS binary.
|
|
58
|
+
fiat_path : Path
|
|
59
|
+
The path to the FIAT binary.
|
|
60
|
+
|
|
61
|
+
Raises
|
|
62
|
+
------
|
|
63
|
+
ValidationError
|
|
64
|
+
If required settings are missing or invalid.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
SYSTEM_SUFFIXES: ClassVar[dict[str, str]] = {
|
|
68
|
+
"Windows": ".exe",
|
|
69
|
+
"Linux": "",
|
|
70
|
+
"Darwin": "",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
model_config = SettingsConfigDict(env_ignore_empty=True, validate_default=True)
|
|
74
|
+
|
|
75
|
+
database_root: Path = Field(
|
|
76
|
+
alias="DATABASE_ROOT", # environment variable DATABASE_ROOT
|
|
77
|
+
default=Path(__file__).parents[3]
|
|
78
|
+
/ "Database", # If you clone FloodAdapt, default is to look for the Database next to the FloodAdapt folder
|
|
79
|
+
description="The root directory of the database that contains site(s). Usually the directory name is 'Database'. Default is to look for the Database in the same dir as the FloodAdapt cloned repo.",
|
|
80
|
+
)
|
|
81
|
+
database_name: str = Field(
|
|
82
|
+
alias="DATABASE_NAME", # environment variable DATABASE_NAME
|
|
83
|
+
default="",
|
|
84
|
+
description="The name of the database site, should be a folder inside the database root. The site must contain an 'input' and 'static' folder.",
|
|
85
|
+
)
|
|
86
|
+
system_folder: Path = Field(
|
|
87
|
+
alias="SYSTEM_FOLDER", # environment variable: SYSTEM_FOLDER
|
|
88
|
+
default=Path(__file__).parents[1] / "system",
|
|
89
|
+
description="The path of the system folder containing the kernels that run the calculations. Default is to look for the system folder in `FloodAdapt/flood_adapt/system`",
|
|
90
|
+
)
|
|
91
|
+
delete_crashed_runs: bool = Field(
|
|
92
|
+
alias="DELETE_CRASHED_RUNS", # environment variable: DELETE_CRASHED_RUNS
|
|
93
|
+
default=True,
|
|
94
|
+
description="Whether to delete the output of crashed/corrupted runs. Be careful when setting this to False, as it may lead to a broken database that cannot be read in anymore.",
|
|
95
|
+
exclude=True,
|
|
96
|
+
)
|
|
97
|
+
validate_allowed_forcings: bool = Field(
|
|
98
|
+
alias="VALIDATE_ALLOWED_FORCINGS", # environment variable: VALIDATE_ALLOWED_FORCINGS
|
|
99
|
+
default=True,
|
|
100
|
+
description="Whether to validate the forcing types and sources against the allowed forcings in the event model.",
|
|
101
|
+
exclude=True,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
@computed_field
|
|
105
|
+
@property
|
|
106
|
+
def sfincs_path(self) -> Path:
|
|
107
|
+
return self.system_folder / "sfincs" / f"sfincs{Settings._system_extension()}"
|
|
108
|
+
|
|
109
|
+
@computed_field
|
|
110
|
+
@property
|
|
111
|
+
def fiat_path(self) -> Path:
|
|
112
|
+
return self.system_folder / "fiat" / f"fiat{Settings._system_extension()}"
|
|
113
|
+
|
|
114
|
+
@computed_field
|
|
115
|
+
@property
|
|
116
|
+
def database_path(self) -> Path:
|
|
117
|
+
return self.database_root / self.database_name
|
|
118
|
+
|
|
119
|
+
@model_validator(mode="after")
|
|
120
|
+
def validate_settings(self):
|
|
121
|
+
self._validate_database_path()
|
|
122
|
+
self._validate_system_folder()
|
|
123
|
+
self._validate_fiat_path()
|
|
124
|
+
self._validate_sfincs_path()
|
|
125
|
+
self._update_environment_variables()
|
|
126
|
+
return self
|
|
127
|
+
|
|
128
|
+
def _update_environment_variables(self):
|
|
129
|
+
environ["DATABASE_ROOT"] = str(self.database_root)
|
|
130
|
+
environ["DATABASE_NAME"] = self.database_name
|
|
131
|
+
environ["SYSTEM_FOLDER"] = str(self.system_folder)
|
|
132
|
+
environ["DELETE_CRASHED_RUNS"] = str(self.delete_crashed_runs)
|
|
133
|
+
environ["VALIDATE_ALLOWED_FORCINGS"] = str(self.validate_allowed_forcings)
|
|
134
|
+
return self
|
|
135
|
+
|
|
136
|
+
def _validate_database_path(self):
|
|
137
|
+
if not self.database_root.is_dir():
|
|
138
|
+
raise ValueError(f"Database root {self.database_root} does not exist.")
|
|
139
|
+
|
|
140
|
+
if self.database_name == "":
|
|
141
|
+
# If database_name is not given as arg or set in env, compute default as the first dir in database_root excluding 'system'
|
|
142
|
+
sites = [
|
|
143
|
+
d
|
|
144
|
+
for d in listdir(self.database_root)
|
|
145
|
+
if d != "system" and not d.startswith(".")
|
|
146
|
+
]
|
|
147
|
+
if not sites:
|
|
148
|
+
raise ValueError(f"No databases found in {self.database_root}.")
|
|
149
|
+
self.database_name = sites[0]
|
|
150
|
+
|
|
151
|
+
if not self.database_path.is_dir():
|
|
152
|
+
raise ValueError(
|
|
153
|
+
f"Database {self.database_name} at {self.database_root} does not exist. Full path: {self.database_path}"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if not (self.database_path / "input").is_dir():
|
|
157
|
+
raise ValueError(
|
|
158
|
+
f"Database {self.database_name} at {self.database_path} does not contain an input folder."
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
if not (self.database_path / "static").is_dir():
|
|
162
|
+
raise ValueError(
|
|
163
|
+
f"Database {self.database_name} at {self.database_path} does not contain a static folder."
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
return self
|
|
167
|
+
|
|
168
|
+
def _validate_system_folder(self):
|
|
169
|
+
if not self.system_folder.is_dir():
|
|
170
|
+
raise ValueError(f"System folder {self.system_folder} does not exist.")
|
|
171
|
+
return self
|
|
172
|
+
|
|
173
|
+
def _validate_sfincs_path(self):
|
|
174
|
+
if not self.sfincs_path.exists():
|
|
175
|
+
raise ValueError(f"SFINCS binary {self.sfincs_path} does not exist.")
|
|
176
|
+
return self
|
|
177
|
+
|
|
178
|
+
def _validate_fiat_path(self):
|
|
179
|
+
if not self.fiat_path.exists():
|
|
180
|
+
raise ValueError(f"FIAT binary {self.fiat_path} does not exist.")
|
|
181
|
+
return self
|
|
182
|
+
|
|
183
|
+
@field_serializer(
|
|
184
|
+
"database_root", "system_folder", "sfincs_path", "fiat_path", "database_path"
|
|
185
|
+
)
|
|
186
|
+
def serialize_path(self, path: Path) -> str:
|
|
187
|
+
return str(path)
|
|
188
|
+
|
|
189
|
+
@staticmethod
|
|
190
|
+
def _system_extension() -> str:
|
|
191
|
+
if system() not in Settings.SYSTEM_SUFFIXES:
|
|
192
|
+
raise ValueError(f"Unsupported system {system()}")
|
|
193
|
+
return Settings.SYSTEM_SUFFIXES[system()]
|
|
194
|
+
|
|
195
|
+
@staticmethod
|
|
196
|
+
def read(toml_path: Path) -> "Settings":
|
|
197
|
+
"""
|
|
198
|
+
Parse the configuration file and return the parsed settings.
|
|
199
|
+
|
|
200
|
+
Parameters
|
|
201
|
+
----------
|
|
202
|
+
toml_path : Path
|
|
203
|
+
The path to the configuration file.
|
|
204
|
+
|
|
205
|
+
Returns
|
|
206
|
+
-------
|
|
207
|
+
Settings
|
|
208
|
+
The parsed configuration settings.
|
|
209
|
+
|
|
210
|
+
Raises
|
|
211
|
+
------
|
|
212
|
+
ValidationError
|
|
213
|
+
If required configuration values are missing or if there is an error parsing the configuration file.
|
|
214
|
+
"""
|
|
215
|
+
with open(toml_path, "rb") as f:
|
|
216
|
+
settings = tomli.load(f)
|
|
217
|
+
|
|
218
|
+
return Settings(**settings)
|
|
219
|
+
|
|
220
|
+
def write(self, toml_path: Path) -> None:
|
|
221
|
+
"""
|
|
222
|
+
Write the configuration settings to a .toml file.
|
|
223
|
+
|
|
224
|
+
Parameters
|
|
225
|
+
----------
|
|
226
|
+
toml_path : Path
|
|
227
|
+
The path to the configuration file.
|
|
228
|
+
|
|
229
|
+
Returns
|
|
230
|
+
-------
|
|
231
|
+
None
|
|
232
|
+
|
|
233
|
+
"""
|
|
234
|
+
toml_path = Path(toml_path).resolve()
|
|
235
|
+
if not toml_path.parent.exists():
|
|
236
|
+
toml_path.parent.mkdir(parents=True, exist_ok=True)
|
|
237
|
+
|
|
238
|
+
with open(toml_path, "wb") as f:
|
|
239
|
+
tomli_w.dump(
|
|
240
|
+
self.model_dump(
|
|
241
|
+
by_alias=True,
|
|
242
|
+
exclude={"sfincs_path", "fiat_path", "database_path"},
|
|
243
|
+
),
|
|
244
|
+
f,
|
|
245
|
+
)
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
from tomli import load as load_toml
|
|
7
|
+
|
|
8
|
+
from flood_adapt.config.sfincs import FloodmapType
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BenefitsModel(BaseModel):
|
|
12
|
+
"""The accepted input for the variable benefits in Site.
|
|
13
|
+
|
|
14
|
+
Attributes
|
|
15
|
+
----------
|
|
16
|
+
current_year : int
|
|
17
|
+
The current year used in benefits calculations.
|
|
18
|
+
current_projection : str
|
|
19
|
+
The current projection used in benefits calculations.
|
|
20
|
+
baseline_strategy : str
|
|
21
|
+
The baseline strategy used in benefits calculations.
|
|
22
|
+
event_set : str
|
|
23
|
+
The event set used in benefits calculations.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
current_year: int
|
|
27
|
+
current_projection: str
|
|
28
|
+
baseline_strategy: str
|
|
29
|
+
event_set: str
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class EquityModel(BaseModel):
|
|
33
|
+
"""
|
|
34
|
+
The accepted input for the variable equity in Site.
|
|
35
|
+
|
|
36
|
+
Attributes
|
|
37
|
+
----------
|
|
38
|
+
census_data : str
|
|
39
|
+
TODO
|
|
40
|
+
percapitaincome_label : Optional[str], default="PerCapitaIncome"
|
|
41
|
+
TODO
|
|
42
|
+
totalpopulation_label : Optional[str], default="TotalPopulation"
|
|
43
|
+
TODO
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
census_data: str
|
|
47
|
+
percapitaincome_label: Optional[str] = "PerCapitaIncome"
|
|
48
|
+
totalpopulation_label: Optional[str] = "TotalPopulation"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AggregationModel(BaseModel):
|
|
52
|
+
"""The accepted input for the variable aggregation in Site.
|
|
53
|
+
|
|
54
|
+
Attributes
|
|
55
|
+
----------
|
|
56
|
+
name : str
|
|
57
|
+
TODO
|
|
58
|
+
file : str
|
|
59
|
+
TODO
|
|
60
|
+
field_name : str
|
|
61
|
+
TODO
|
|
62
|
+
equity : Optional[EquityModel], default=None
|
|
63
|
+
TODO
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
name: str
|
|
67
|
+
file: str
|
|
68
|
+
field_name: str
|
|
69
|
+
equity: Optional[EquityModel] = None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class BFEModel(BaseModel):
|
|
73
|
+
"""The accepted input for the variable bfe in Site.
|
|
74
|
+
|
|
75
|
+
Attributes
|
|
76
|
+
----------
|
|
77
|
+
geom : str
|
|
78
|
+
TODO
|
|
79
|
+
table : Optional[str], default=None
|
|
80
|
+
TODO
|
|
81
|
+
field_name : str
|
|
82
|
+
TODO
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
geom: str
|
|
86
|
+
table: Optional[str] = None
|
|
87
|
+
field_name: str
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class SVIModel(BaseModel):
|
|
91
|
+
"""The accepted input for the variable svi in Site.
|
|
92
|
+
|
|
93
|
+
Attributes
|
|
94
|
+
----------
|
|
95
|
+
geom : str
|
|
96
|
+
TODO
|
|
97
|
+
field_name : str
|
|
98
|
+
TODO
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
geom: str
|
|
102
|
+
field_name: str
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class NoFootprintsModel(BaseModel):
|
|
106
|
+
"""
|
|
107
|
+
The configuration on the how to show objects with no footprints.
|
|
108
|
+
|
|
109
|
+
Attributes
|
|
110
|
+
----------
|
|
111
|
+
shape : Optional[str], default="triangle"
|
|
112
|
+
The shape of the object with no footprints.
|
|
113
|
+
diameter_meters : Optional[float], default=10
|
|
114
|
+
The diameter of the object with no footprints in meters.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
shape: Optional[str] = "triangle"
|
|
118
|
+
diameter_meters: Optional[float] = 10
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class RiskModel(BaseModel):
|
|
122
|
+
"""The accepted input for the variable risk in Site.
|
|
123
|
+
|
|
124
|
+
Attributes
|
|
125
|
+
----------
|
|
126
|
+
return_periods : list[int]
|
|
127
|
+
The return periods for the risk model.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
return_periods: list = [1, 2, 5, 10, 25, 50, 100]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class DamageType(str, Enum):
|
|
134
|
+
"""The accepted input for the variable footprints_dmg_type."""
|
|
135
|
+
|
|
136
|
+
absolute = "absolute"
|
|
137
|
+
relative = "relative"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class FiatConfigModel(BaseModel):
|
|
141
|
+
"""Configuration settings for the FIAT model.
|
|
142
|
+
|
|
143
|
+
Attributes
|
|
144
|
+
----------
|
|
145
|
+
exposure_crs : str
|
|
146
|
+
The coordinate reference system of the exposure data.
|
|
147
|
+
bfe : Optional[BFEModel], default=None
|
|
148
|
+
The base flood elevation model.
|
|
149
|
+
aggregation : list[AggregationModel]
|
|
150
|
+
Configuration for the aggregation model.
|
|
151
|
+
floodmap_type : FloodmapType
|
|
152
|
+
The type of flood map to be used.
|
|
153
|
+
non_building_names : Optional[list[str]], default=None
|
|
154
|
+
List of non-building names to be used in the model.
|
|
155
|
+
damage_unit : str, default="$"
|
|
156
|
+
The unit of damage used in the model.
|
|
157
|
+
building_footprints : Optional[str], default=None
|
|
158
|
+
Path to the building footprints data.
|
|
159
|
+
roads_file_name : Optional[str], default=None
|
|
160
|
+
Path to the roads data.
|
|
161
|
+
new_development_file_name : Optional[str], default="new_development_area.gpkg"
|
|
162
|
+
Path to the new development area data.
|
|
163
|
+
save_simulation : Optional[bool], default=False
|
|
164
|
+
Whether to keep or delete the simulation files after the simulation is finished and all output files are created.
|
|
165
|
+
If True, the simulation files are kept. If False, the simulation files are deleted.
|
|
166
|
+
svi : Optional[SVIModel], default=None
|
|
167
|
+
The social vulnerability index model.
|
|
168
|
+
infographics : Optional[bool], default=False
|
|
169
|
+
Whether to create infographics or not.
|
|
170
|
+
no_footprints : Optional[NoFootprintsModel], default=NoFootprintsModel()
|
|
171
|
+
Configuration for objects with no footprints.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
exposure_crs: str
|
|
175
|
+
bfe: Optional[BFEModel] = None
|
|
176
|
+
aggregation: list[AggregationModel]
|
|
177
|
+
floodmap_type: FloodmapType
|
|
178
|
+
non_building_names: Optional[list[str]]
|
|
179
|
+
damage_unit: str = "$"
|
|
180
|
+
building_footprints: Optional[str] = None
|
|
181
|
+
roads_file_name: Optional[str] = None
|
|
182
|
+
new_development_file_name: Optional[str] = "new_development_area.gpkg"
|
|
183
|
+
save_simulation: Optional[bool] = False
|
|
184
|
+
svi: Optional[SVIModel] = None
|
|
185
|
+
infographics: Optional[bool] = False
|
|
186
|
+
no_footprints: Optional[NoFootprintsModel] = NoFootprintsModel()
|
|
187
|
+
|
|
188
|
+
@staticmethod
|
|
189
|
+
def read_toml(path: Path) -> "FiatConfigModel":
|
|
190
|
+
with open(path, mode="rb") as fp:
|
|
191
|
+
toml_contents = load_toml(fp)
|
|
192
|
+
|
|
193
|
+
return FiatConfigModel(**toml_contents)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class FiatModel(BaseModel):
|
|
197
|
+
"""The expected variables and data types of attributes of the Fiat class.
|
|
198
|
+
|
|
199
|
+
Attributes
|
|
200
|
+
----------
|
|
201
|
+
risk : RiskModel
|
|
202
|
+
Configuration of probabilistic risk runs.
|
|
203
|
+
config : FiatConfigModel
|
|
204
|
+
Configuration for the FIAT model.
|
|
205
|
+
benefits : BenefitsModel
|
|
206
|
+
Configuration for running benefit calculations.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
risk: RiskModel
|
|
210
|
+
|
|
211
|
+
config: FiatConfigModel
|
|
212
|
+
benefits: BenefitsModel
|
|
213
|
+
|
|
214
|
+
@staticmethod
|
|
215
|
+
def read_toml(path: Path) -> "FiatModel":
|
|
216
|
+
with open(path, mode="rb") as fp:
|
|
217
|
+
toml_contents = load_toml(fp)
|
|
218
|
+
|
|
219
|
+
return FiatModel(**toml_contents)
|