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,68 @@
|
|
|
1
|
+
from flood_adapt.objects.measures.measures import (
|
|
2
|
+
HazardMeasure,
|
|
3
|
+
ImpactMeasure,
|
|
4
|
+
Measure,
|
|
5
|
+
)
|
|
6
|
+
from flood_adapt.objects.object_model import Object
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Strategy(Object):
|
|
10
|
+
"""
|
|
11
|
+
Class representing a strategy in FloodAdapt.
|
|
12
|
+
|
|
13
|
+
A strategy is a collection of measures that can be applied to a model.
|
|
14
|
+
|
|
15
|
+
Attributes
|
|
16
|
+
----------
|
|
17
|
+
measures : list[str]
|
|
18
|
+
A list of measures associated with the strategy. Should be a list of measure names that are saved in the database.
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
measures: list[str]
|
|
23
|
+
|
|
24
|
+
_measure_objects: list[Measure] | None = None
|
|
25
|
+
|
|
26
|
+
def initialize_measure_objects(self, measures: list[Measure]) -> None:
|
|
27
|
+
self._measure_objects = measures
|
|
28
|
+
|
|
29
|
+
def get_measures(self) -> list[Measure]:
|
|
30
|
+
"""Get the measures paths and types."""
|
|
31
|
+
# Get measure paths using a database structure
|
|
32
|
+
if self._measure_objects is None:
|
|
33
|
+
raise ValueError(
|
|
34
|
+
"Measure objects have not been initialized. Call `initialize_measure_objects()` first."
|
|
35
|
+
)
|
|
36
|
+
return self._measure_objects
|
|
37
|
+
|
|
38
|
+
def get_impact_measures(self) -> list[ImpactMeasure]:
|
|
39
|
+
return [
|
|
40
|
+
measure
|
|
41
|
+
for measure in self.get_measures()
|
|
42
|
+
if isinstance(measure, ImpactMeasure)
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
def get_impact_strategy(self) -> "Strategy":
|
|
46
|
+
impact_measures = self.get_impact_measures()
|
|
47
|
+
impact_strategy = Strategy(
|
|
48
|
+
name=self.name,
|
|
49
|
+
measures=[m.name for m in impact_measures],
|
|
50
|
+
)
|
|
51
|
+
impact_strategy.initialize_measure_objects(impact_measures)
|
|
52
|
+
return impact_strategy
|
|
53
|
+
|
|
54
|
+
def get_hazard_measures(self) -> list[HazardMeasure]:
|
|
55
|
+
return [
|
|
56
|
+
measure
|
|
57
|
+
for measure in self.get_measures()
|
|
58
|
+
if isinstance(measure, HazardMeasure)
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
def get_hazard_strategy(self) -> "Strategy":
|
|
62
|
+
hazard_measures = self.get_hazard_measures()
|
|
63
|
+
hazard_strategy = Strategy(
|
|
64
|
+
name=self.name,
|
|
65
|
+
measures=[m.name for m in hazard_measures],
|
|
66
|
+
)
|
|
67
|
+
hazard_strategy.initialize_measure_objects(hazard_measures)
|
|
68
|
+
return hazard_strategy
|
|
File without changes
|
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import geopandas as gpd
|
|
5
|
+
import numpy as np
|
|
6
|
+
import numpy_financial as npf
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import plotly.graph_objects as go
|
|
9
|
+
import tomli
|
|
10
|
+
import tomli_w
|
|
11
|
+
from fiat_toolbox.metrics_writer.fiat_read_metrics_file import MetricsFileReader
|
|
12
|
+
|
|
13
|
+
from flood_adapt.misc.path_builder import (
|
|
14
|
+
ObjectDir,
|
|
15
|
+
TopLevelDir,
|
|
16
|
+
db_path,
|
|
17
|
+
)
|
|
18
|
+
from flood_adapt.objects.benefits.benefits import Benefit
|
|
19
|
+
from flood_adapt.objects.scenarios.scenarios import Scenario
|
|
20
|
+
from flood_adapt.workflows.scenario_runner import ScenarioRunner
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BenefitRunner:
|
|
24
|
+
"""Object holding all attributes and methods related to a benefit analysis."""
|
|
25
|
+
|
|
26
|
+
benefit: Benefit
|
|
27
|
+
_scenarios: pd.DataFrame
|
|
28
|
+
|
|
29
|
+
def __init__(self, database, benefit: Benefit):
|
|
30
|
+
"""Initialize function called when object is created through the load_file or load_dict methods."""
|
|
31
|
+
self.database = database
|
|
32
|
+
self.benefit = benefit
|
|
33
|
+
|
|
34
|
+
# Get output path based on database path
|
|
35
|
+
self.check_scenarios()
|
|
36
|
+
self.results_path = self.database.benefits.output_path.joinpath(
|
|
37
|
+
self.benefit.name
|
|
38
|
+
)
|
|
39
|
+
self.site_info = self.database.site
|
|
40
|
+
self.unit = self.site_info.fiat.config.damage_unit
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def has_run(self):
|
|
44
|
+
return self.has_run_check()
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def results(self):
|
|
48
|
+
if hasattr(self, "_results"):
|
|
49
|
+
return self._results
|
|
50
|
+
self._results = self.get_output()
|
|
51
|
+
return self._results
|
|
52
|
+
|
|
53
|
+
def get_output(self) -> dict[str, Any]:
|
|
54
|
+
if not self.has_run:
|
|
55
|
+
raise RuntimeError(
|
|
56
|
+
f"Cannot read output since benefit analysis '{self.benefit.name}' has not been run yet."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
results_toml = self.results_path.joinpath("results.toml")
|
|
60
|
+
results_html = self.results_path.joinpath("benefits.html")
|
|
61
|
+
with open(results_toml, mode="rb") as fp:
|
|
62
|
+
results = tomli.load(fp)
|
|
63
|
+
results["html"] = str(results_html)
|
|
64
|
+
self._results = results
|
|
65
|
+
return results
|
|
66
|
+
|
|
67
|
+
def has_run_check(self) -> bool:
|
|
68
|
+
"""Check if the benefit analysis has already been run.
|
|
69
|
+
|
|
70
|
+
Returns
|
|
71
|
+
-------
|
|
72
|
+
bool
|
|
73
|
+
True if the analysis has already been run, else False
|
|
74
|
+
"""
|
|
75
|
+
# Output files to check
|
|
76
|
+
results_toml = self.results_path.joinpath("results.toml")
|
|
77
|
+
results_csv = self.results_path.joinpath("time_series.csv")
|
|
78
|
+
results_html = self.results_path.joinpath("benefits.html")
|
|
79
|
+
|
|
80
|
+
check = all(
|
|
81
|
+
result.exists() for result in [results_toml, results_csv, results_html]
|
|
82
|
+
)
|
|
83
|
+
return check
|
|
84
|
+
|
|
85
|
+
def check_scenarios(self) -> pd.DataFrame:
|
|
86
|
+
"""Check which scenarios are needed for this benefit calculation and if they have already been created.
|
|
87
|
+
|
|
88
|
+
The scenarios attribute of the object is updated accordingly and the table of the scenarios is returned.
|
|
89
|
+
|
|
90
|
+
Returns
|
|
91
|
+
-------
|
|
92
|
+
pd.DataFrame
|
|
93
|
+
a table with the scenarios of the Benefit analysis and their status
|
|
94
|
+
"""
|
|
95
|
+
# Define names of scenarios
|
|
96
|
+
scenarios_calc = {
|
|
97
|
+
"current_no_measures": {},
|
|
98
|
+
"current_with_strategy": {},
|
|
99
|
+
"future_no_measures": {},
|
|
100
|
+
"future_with_strategy": {},
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# Use the predefined names for the current projections and no measures strategy
|
|
104
|
+
for scenario in scenarios_calc.keys():
|
|
105
|
+
scenarios_calc[scenario]["event"] = self.benefit.event_set
|
|
106
|
+
|
|
107
|
+
if "current" in scenario:
|
|
108
|
+
scenarios_calc[scenario]["projection"] = (
|
|
109
|
+
self.benefit.current_situation.projection
|
|
110
|
+
)
|
|
111
|
+
elif "future" in scenario:
|
|
112
|
+
scenarios_calc[scenario]["projection"] = self.benefit.projection
|
|
113
|
+
|
|
114
|
+
if "no_measures" in scenario:
|
|
115
|
+
scenarios_calc[scenario]["strategy"] = self.benefit.baseline_strategy
|
|
116
|
+
else:
|
|
117
|
+
scenarios_calc[scenario]["strategy"] = self.benefit.strategy
|
|
118
|
+
|
|
119
|
+
# Get the available scenarios
|
|
120
|
+
scenarios_avail = self.database.scenarios.list_objects()["objects"]
|
|
121
|
+
|
|
122
|
+
# Check if any of the needed scenarios are already there
|
|
123
|
+
for scenario in scenarios_calc.keys():
|
|
124
|
+
scn_dict = scenarios_calc[scenario].copy()
|
|
125
|
+
scn_dict["name"] = scenario
|
|
126
|
+
scenario_obj = Scenario(**scn_dict)
|
|
127
|
+
created = [
|
|
128
|
+
scn_avl for scn_avl in scenarios_avail if scenario_obj == scn_avl
|
|
129
|
+
]
|
|
130
|
+
if len(created) > 0:
|
|
131
|
+
runner = ScenarioRunner(self.database, scenario=created[0])
|
|
132
|
+
scenarios_calc[scenario]["scenario created"] = created[0].name
|
|
133
|
+
scenarios_calc[scenario]["scenario run"] = runner.impacts.has_run
|
|
134
|
+
else:
|
|
135
|
+
scenarios_calc[scenario]["scenario created"] = "No"
|
|
136
|
+
scenarios_calc[scenario]["scenario run"] = False
|
|
137
|
+
|
|
138
|
+
df = pd.DataFrame(scenarios_calc).T
|
|
139
|
+
self.scenarios = df.astype(
|
|
140
|
+
dtype={
|
|
141
|
+
"event": "str",
|
|
142
|
+
"projection": "str",
|
|
143
|
+
"strategy": "str",
|
|
144
|
+
"scenario created": "str",
|
|
145
|
+
"scenario run": bool,
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
return self.scenarios
|
|
149
|
+
|
|
150
|
+
def ready_to_run(self) -> bool:
|
|
151
|
+
"""Check if all the required scenarios have already been run.
|
|
152
|
+
|
|
153
|
+
Returns
|
|
154
|
+
-------
|
|
155
|
+
bool
|
|
156
|
+
True if required scenarios have been already run
|
|
157
|
+
"""
|
|
158
|
+
self.check_scenarios()
|
|
159
|
+
check = all(self.scenarios["scenario run"])
|
|
160
|
+
|
|
161
|
+
return check
|
|
162
|
+
|
|
163
|
+
def run_cost_benefit(self):
|
|
164
|
+
"""Run the cost-benefit calculation for the total study area and the different aggregation levels."""
|
|
165
|
+
# Throw an error if not all runs are finished
|
|
166
|
+
if not self.ready_to_run():
|
|
167
|
+
# First check is scenarios are there
|
|
168
|
+
if "No" in self.scenarios["scenario created"].to_numpy():
|
|
169
|
+
raise RuntimeError("Necessary scenarios have not been created yet.")
|
|
170
|
+
scens = self.scenarios["scenario created"][~self.scenarios["scenario run"]]
|
|
171
|
+
raise RuntimeError(
|
|
172
|
+
f"Scenarios {', '.join(scens.values)} need to be run before the cost-benefit analysis can be performed"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# If path for results does not yet exist, make it, and if it does delete it and recreate it
|
|
176
|
+
if not self.results_path.is_dir():
|
|
177
|
+
self.results_path.mkdir(parents=True)
|
|
178
|
+
else:
|
|
179
|
+
shutil.rmtree(self.results_path)
|
|
180
|
+
self.results_path.mkdir(parents=True)
|
|
181
|
+
|
|
182
|
+
# Run the cost-benefit analysis
|
|
183
|
+
self.cba()
|
|
184
|
+
# Run aggregation benefits
|
|
185
|
+
self.cba_aggregation()
|
|
186
|
+
# Updates results
|
|
187
|
+
self.has_run_check()
|
|
188
|
+
|
|
189
|
+
# Cache results
|
|
190
|
+
self.results
|
|
191
|
+
|
|
192
|
+
def cba(self):
|
|
193
|
+
"""Cost-benefit analysis for the whole study area."""
|
|
194
|
+
# Get EAD for each scenario and save to new dataframe
|
|
195
|
+
scenarios = self.scenarios.copy(deep=True)
|
|
196
|
+
scenarios["EAD"] = None
|
|
197
|
+
|
|
198
|
+
scn_output_path = db_path(TopLevelDir.output, ObjectDir.scenario)
|
|
199
|
+
|
|
200
|
+
# Get metrics per scenario
|
|
201
|
+
for index, scenario in scenarios.iterrows():
|
|
202
|
+
scn_name = scenario["scenario created"]
|
|
203
|
+
collective_fn = scn_output_path.joinpath(
|
|
204
|
+
scn_name, f"Infometrics_{scn_name}.csv"
|
|
205
|
+
)
|
|
206
|
+
collective_metrics = MetricsFileReader(
|
|
207
|
+
collective_fn,
|
|
208
|
+
).read_metrics_from_file()
|
|
209
|
+
|
|
210
|
+
# Fill scenarios EAD column with values from metrics
|
|
211
|
+
scenarios.loc[index, "EAD"] = float(
|
|
212
|
+
collective_metrics["Value"]["ExpectedAnnualDamages"]
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Get years of interest
|
|
216
|
+
year_start = self.benefit.current_situation.year
|
|
217
|
+
year_end = self.benefit.future_year
|
|
218
|
+
|
|
219
|
+
# Calculate benefits
|
|
220
|
+
cba = self._calc_benefits(
|
|
221
|
+
years=[year_start, year_end],
|
|
222
|
+
risk_no_measures=[
|
|
223
|
+
scenarios.loc["current_no_measures", "EAD"],
|
|
224
|
+
scenarios.loc["future_no_measures", "EAD"],
|
|
225
|
+
],
|
|
226
|
+
risk_with_strategy=[
|
|
227
|
+
scenarios.loc["current_with_strategy", "EAD"],
|
|
228
|
+
scenarios.loc["future_with_strategy", "EAD"],
|
|
229
|
+
],
|
|
230
|
+
discount_rate=self.benefit.discount_rate,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# Save indicators in dictionary
|
|
234
|
+
results = {}
|
|
235
|
+
# Get net present value of benefits
|
|
236
|
+
results["benefits"] = cba["benefits_discounted"].sum()
|
|
237
|
+
|
|
238
|
+
# Only if costs are provided do the full cost-benefit analysis
|
|
239
|
+
cost_calc = (self.benefit.implementation_cost is not None) and (
|
|
240
|
+
self.benefit.annual_maint_cost is not None
|
|
241
|
+
)
|
|
242
|
+
if cost_calc:
|
|
243
|
+
cba = self._calc_costs(
|
|
244
|
+
benefits=cba,
|
|
245
|
+
implementation_cost=self.benefit.implementation_cost,
|
|
246
|
+
annual_maint_cost=self.benefit.annual_maint_cost,
|
|
247
|
+
discount_rate=self.benefit.discount_rate,
|
|
248
|
+
)
|
|
249
|
+
# Calculate costs
|
|
250
|
+
results["costs"] = cba["costs_discounted"].sum()
|
|
251
|
+
# Benefit to Cost Ratio
|
|
252
|
+
results["BCR"] = np.round(results["benefits"] / results["costs"], 2)
|
|
253
|
+
# Net present value
|
|
254
|
+
results["NPV"] = cba["profits_discounted"].sum()
|
|
255
|
+
# Internal Rate of Return
|
|
256
|
+
results["IRR"] = np.round(npf.irr(cba["profits"]), 3)
|
|
257
|
+
|
|
258
|
+
# Save results
|
|
259
|
+
if not self.results_path.is_dir():
|
|
260
|
+
self.results_path.mkdir(parents=True)
|
|
261
|
+
|
|
262
|
+
# Save indicators in a toml file
|
|
263
|
+
indicators = self.results_path.joinpath("results.toml")
|
|
264
|
+
with open(indicators, "wb") as f:
|
|
265
|
+
tomli_w.dump(results, f)
|
|
266
|
+
|
|
267
|
+
# Save time-series in a csv
|
|
268
|
+
time_series = self.results_path.joinpath("time_series.csv")
|
|
269
|
+
cba.to_csv(time_series)
|
|
270
|
+
|
|
271
|
+
# Make html
|
|
272
|
+
self._make_html(cba)
|
|
273
|
+
|
|
274
|
+
def cba_aggregation(self):
|
|
275
|
+
"""Zonal Benefits analysis for the different aggregation areas."""
|
|
276
|
+
results_path = db_path(TopLevelDir.output, ObjectDir.scenario)
|
|
277
|
+
# Get years of interest
|
|
278
|
+
year_start = self.benefit.current_situation.year
|
|
279
|
+
year_end = self.benefit.future_year
|
|
280
|
+
|
|
281
|
+
# Get EAD for each scenario and save to new dataframe
|
|
282
|
+
scenarios = self.scenarios.copy(deep=True)
|
|
283
|
+
|
|
284
|
+
# Read in the names of the aggregation area types
|
|
285
|
+
aggregations = [aggr.name for aggr in self.site_info.fiat.config.aggregation]
|
|
286
|
+
|
|
287
|
+
# Check if equity information is available to define variables to use
|
|
288
|
+
vars = []
|
|
289
|
+
for i, aggr_name in enumerate(aggregations):
|
|
290
|
+
if self.site_info.fiat.config.aggregation[i].equity is not None:
|
|
291
|
+
vars.append(["EAD", "EWEAD"])
|
|
292
|
+
else:
|
|
293
|
+
vars.append(["EAD"])
|
|
294
|
+
|
|
295
|
+
# Define which names are used in the metric tables
|
|
296
|
+
var_metric = {"EAD": "ExpectedAnnualDamages", "EWEAD": "EWEAD"}
|
|
297
|
+
|
|
298
|
+
# Prepare dictionary to save values
|
|
299
|
+
risk = {}
|
|
300
|
+
|
|
301
|
+
# Fill in the dictionary
|
|
302
|
+
for i, aggr_name in enumerate(aggregations):
|
|
303
|
+
risk[aggr_name] = {}
|
|
304
|
+
values = {}
|
|
305
|
+
for var in vars[i]:
|
|
306
|
+
values[var] = []
|
|
307
|
+
for index, scenario in scenarios.iterrows():
|
|
308
|
+
scn_name = scenario["scenario created"]
|
|
309
|
+
# Get available aggregation levels
|
|
310
|
+
aggregation_fn = results_path.joinpath(
|
|
311
|
+
scn_name, f"Infometrics_{scn_name}_{aggr_name}.csv"
|
|
312
|
+
)
|
|
313
|
+
for var in vars[i]:
|
|
314
|
+
# Get metrics per scenario and per aggregation
|
|
315
|
+
aggregated_metrics = MetricsFileReader(
|
|
316
|
+
aggregation_fn,
|
|
317
|
+
).read_aggregated_metric_from_file(var_metric[var])[2:]
|
|
318
|
+
aggregated_metrics = aggregated_metrics.loc[
|
|
319
|
+
aggregated_metrics.index.dropna()
|
|
320
|
+
]
|
|
321
|
+
aggregated_metrics.name = scenario.name
|
|
322
|
+
values[var].append(aggregated_metrics)
|
|
323
|
+
|
|
324
|
+
# Combine values in a single dataframe
|
|
325
|
+
for var in vars[i]:
|
|
326
|
+
risk[aggr_name][var] = pd.DataFrame(values[var]).T.astype(float)
|
|
327
|
+
|
|
328
|
+
var_output = {"EAD": "Benefits", "EWEAD": "Equity Weighted Benefits"}
|
|
329
|
+
|
|
330
|
+
# Calculate benefits
|
|
331
|
+
benefits = {}
|
|
332
|
+
for i, aggr_name in enumerate(aggregations):
|
|
333
|
+
benefits[aggr_name] = pd.DataFrame()
|
|
334
|
+
benefits[aggr_name].index = risk[aggr_name]["EAD"].index
|
|
335
|
+
for var in vars[i]:
|
|
336
|
+
for index, row in risk[aggr_name][var].iterrows():
|
|
337
|
+
cba = self._calc_benefits(
|
|
338
|
+
years=[year_start, year_end],
|
|
339
|
+
risk_no_measures=[
|
|
340
|
+
row["current_no_measures"],
|
|
341
|
+
row["future_no_measures"],
|
|
342
|
+
],
|
|
343
|
+
risk_with_strategy=[
|
|
344
|
+
row["current_with_strategy"],
|
|
345
|
+
row["future_with_strategy"],
|
|
346
|
+
],
|
|
347
|
+
discount_rate=self.benefit.discount_rate,
|
|
348
|
+
)
|
|
349
|
+
benefits[aggr_name].loc[row.name, var_output[var]] = cba[
|
|
350
|
+
"benefits_discounted"
|
|
351
|
+
].sum()
|
|
352
|
+
|
|
353
|
+
# Save results
|
|
354
|
+
if not self.results_path.is_dir():
|
|
355
|
+
self.results_path.mkdir(parents=True)
|
|
356
|
+
|
|
357
|
+
# Save benefits per aggregation area (csv and gpkg)
|
|
358
|
+
for i, aggr_name in enumerate(aggregations):
|
|
359
|
+
csv_filename = self.results_path.joinpath(f"benefits_{aggr_name}.csv")
|
|
360
|
+
benefits[aggr_name].to_csv(csv_filename, index=True)
|
|
361
|
+
|
|
362
|
+
# Load aggregation areas
|
|
363
|
+
ind = [
|
|
364
|
+
i
|
|
365
|
+
for i, n in enumerate(self.site_info.fiat.config.aggregation)
|
|
366
|
+
if n.name == aggr_name
|
|
367
|
+
][0]
|
|
368
|
+
aggr_areas_path = (
|
|
369
|
+
db_path(TopLevelDir.static)
|
|
370
|
+
/ self.site_info.fiat.config.aggregation[ind].file
|
|
371
|
+
)
|
|
372
|
+
aggr_areas = gpd.read_file(aggr_areas_path, engine="pyogrio")
|
|
373
|
+
# Define output path
|
|
374
|
+
outpath = self.results_path.joinpath(f"benefits_{aggr_name}.gpkg")
|
|
375
|
+
# Save file
|
|
376
|
+
aggr_areas = aggr_areas.join(
|
|
377
|
+
benefits[aggr_name],
|
|
378
|
+
on=self.site_info.fiat.config.aggregation[ind].field_name,
|
|
379
|
+
)
|
|
380
|
+
aggr_areas.to_file(outpath, driver="GPKG")
|
|
381
|
+
|
|
382
|
+
@staticmethod
|
|
383
|
+
def _calc_benefits(
|
|
384
|
+
years: list[int, int],
|
|
385
|
+
risk_no_measures: list[float, float],
|
|
386
|
+
risk_with_strategy: list[float, float],
|
|
387
|
+
discount_rate: float,
|
|
388
|
+
) -> pd.DataFrame:
|
|
389
|
+
"""Calculate per year benefits and discounted benefits.
|
|
390
|
+
|
|
391
|
+
Parameters
|
|
392
|
+
----------
|
|
393
|
+
years : list[int, int]
|
|
394
|
+
the current and future year for the analysis
|
|
395
|
+
risk_no_measures : list[float, float]
|
|
396
|
+
the current and future risk value without any measures
|
|
397
|
+
risk_with_strategy : list[float, float]
|
|
398
|
+
the current and future risk value with the strategy under investigation
|
|
399
|
+
discount_rate : float
|
|
400
|
+
the yearly discount rate used to calculated the total benefit
|
|
401
|
+
|
|
402
|
+
Returns
|
|
403
|
+
-------
|
|
404
|
+
pd.DataFrame
|
|
405
|
+
Dataframe containing the time-series of risks and benefits per year
|
|
406
|
+
"""
|
|
407
|
+
benefits = pd.DataFrame(
|
|
408
|
+
data={"risk_no_measures": np.nan, "risk_with_strategy": np.nan},
|
|
409
|
+
index=np.arange(years[0], years[1] + 1),
|
|
410
|
+
)
|
|
411
|
+
benefits.index.names = ["year"]
|
|
412
|
+
|
|
413
|
+
# Fill in dataframe
|
|
414
|
+
for strat, risk in zip(
|
|
415
|
+
["no_measures", "with_strategy"], [risk_no_measures, risk_with_strategy]
|
|
416
|
+
):
|
|
417
|
+
benefits.loc[years[0], f"risk_{strat}"] = risk[0]
|
|
418
|
+
benefits.loc[years[1], f"risk_{strat}"] = risk[1]
|
|
419
|
+
|
|
420
|
+
# Assume linear trend between current and future
|
|
421
|
+
benefits = benefits.interpolate(method="linear")
|
|
422
|
+
|
|
423
|
+
# Calculate benefits
|
|
424
|
+
benefits["benefits"] = (
|
|
425
|
+
benefits["risk_no_measures"] - benefits["risk_with_strategy"]
|
|
426
|
+
)
|
|
427
|
+
# Calculate discounted benefits using the provided discount rate
|
|
428
|
+
benefits["benefits_discounted"] = benefits["benefits"] / (
|
|
429
|
+
1 + discount_rate
|
|
430
|
+
) ** (benefits.index - benefits.index[0])
|
|
431
|
+
|
|
432
|
+
return benefits
|
|
433
|
+
|
|
434
|
+
@staticmethod
|
|
435
|
+
def _calc_costs(
|
|
436
|
+
benefits: pd.DataFrame,
|
|
437
|
+
implementation_cost: float,
|
|
438
|
+
annual_maint_cost: float,
|
|
439
|
+
discount_rate: float,
|
|
440
|
+
) -> pd.DataFrame:
|
|
441
|
+
"""Calculate per year costs and discounted costs.
|
|
442
|
+
|
|
443
|
+
Parameters
|
|
444
|
+
----------
|
|
445
|
+
benefits : pd.DataFrame
|
|
446
|
+
a time series of benefits per year (produced with __calc_benefits method)
|
|
447
|
+
implementation_cost : float
|
|
448
|
+
initial costs of implementing the adaptation strategy
|
|
449
|
+
annual_maint_cost : float
|
|
450
|
+
annual maintenance cost of the adaptation strategy
|
|
451
|
+
discount_rate : float
|
|
452
|
+
yearly discount rate
|
|
453
|
+
|
|
454
|
+
Returns
|
|
455
|
+
-------
|
|
456
|
+
pd.DataFrame
|
|
457
|
+
Dataframe containing the time-series of benefits, costs and profits per year
|
|
458
|
+
"""
|
|
459
|
+
benefits = benefits.copy()
|
|
460
|
+
benefits["costs"] = np.nan
|
|
461
|
+
# implementations costs at current year and maintenance from year 1
|
|
462
|
+
benefits.loc[benefits.index[0], "costs"] = implementation_cost
|
|
463
|
+
benefits.loc[benefits.index[1:], "costs"] = annual_maint_cost
|
|
464
|
+
benefits["costs_discounted"] = benefits["costs"] / (1 + discount_rate) ** (
|
|
465
|
+
benefits.index - benefits.index[0]
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
# Benefit to Cost Ratio
|
|
469
|
+
benefits["profits"] = benefits["benefits"] - benefits["costs"]
|
|
470
|
+
benefits["profits_discounted"] = benefits["profits"] / (1 + discount_rate) ** (
|
|
471
|
+
benefits.index - benefits.index[0]
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
return benefits
|
|
475
|
+
|
|
476
|
+
def _make_html(self, cba):
|
|
477
|
+
"""Make an html with the time-series of the benefits and discounted benefits."""
|
|
478
|
+
# Save a plotly graph in an html
|
|
479
|
+
fig = go.Figure()
|
|
480
|
+
|
|
481
|
+
# Get only endpoints
|
|
482
|
+
cba2 = cba.iloc[[0, -1]]
|
|
483
|
+
|
|
484
|
+
# Add graph with benefits
|
|
485
|
+
fig.add_trace(
|
|
486
|
+
go.Scatter(
|
|
487
|
+
x=cba.index,
|
|
488
|
+
y=cba["benefits"],
|
|
489
|
+
mode="lines",
|
|
490
|
+
line_color="black",
|
|
491
|
+
name="Interpolated benefits",
|
|
492
|
+
)
|
|
493
|
+
)
|
|
494
|
+
fig.add_trace(
|
|
495
|
+
go.Scatter(
|
|
496
|
+
x=cba2.index,
|
|
497
|
+
y=cba2["benefits"],
|
|
498
|
+
mode="markers",
|
|
499
|
+
marker_size=10,
|
|
500
|
+
marker_color="rgba(53,217,44,1)",
|
|
501
|
+
name="Calculated benefits",
|
|
502
|
+
)
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
fig.add_trace(
|
|
506
|
+
go.Scatter(
|
|
507
|
+
x=cba.index,
|
|
508
|
+
y=cba["benefits_discounted"],
|
|
509
|
+
mode="lines",
|
|
510
|
+
line_color="rgba(35,150,29,1)",
|
|
511
|
+
name="Discounted benefits",
|
|
512
|
+
),
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
fig.add_trace(
|
|
516
|
+
go.Scatter(
|
|
517
|
+
x=cba.index,
|
|
518
|
+
y=cba["benefits_discounted"],
|
|
519
|
+
mode="none",
|
|
520
|
+
fill="tozeroy",
|
|
521
|
+
fillcolor="rgba(35,150,29,0.5)",
|
|
522
|
+
name="Benefits",
|
|
523
|
+
)
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
# Update xaxis properties
|
|
527
|
+
fig.update_xaxes(title_text="Year")
|
|
528
|
+
# Update yaxis properties
|
|
529
|
+
fig.update_yaxes(title_text=f"Annual Benefits ({self.unit})")
|
|
530
|
+
|
|
531
|
+
fig.update_layout(
|
|
532
|
+
autosize=False,
|
|
533
|
+
height=400,
|
|
534
|
+
width=800,
|
|
535
|
+
margin={"r": 0, "l": 0, "b": 0, "t": 0},
|
|
536
|
+
font={"size": 12, "color": "black", "family": "Arial"},
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
# write html to results folder
|
|
540
|
+
html = self.results_path.joinpath("benefits.html")
|
|
541
|
+
fig.write_html(html)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from flood_adapt.config.sfincs import FloodmapType
|
|
5
|
+
from flood_adapt.misc.database_user import DatabaseUser
|
|
6
|
+
from flood_adapt.misc.log import FloodAdaptLogging
|
|
7
|
+
from flood_adapt.objects.events.event_set import EventSet
|
|
8
|
+
from flood_adapt.objects.events.events import Mode
|
|
9
|
+
from flood_adapt.objects.strategies.strategies import Strategy
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FloodMap(DatabaseUser):
|
|
13
|
+
logger = FloodAdaptLogging.getLogger("FloodMap")
|
|
14
|
+
|
|
15
|
+
type: FloodmapType
|
|
16
|
+
|
|
17
|
+
name: str
|
|
18
|
+
path: Path | os.PathLike | list[Path | os.PathLike]
|
|
19
|
+
event_set: EventSet
|
|
20
|
+
|
|
21
|
+
def __init__(self, scenario_name: str) -> None:
|
|
22
|
+
self.name = scenario_name
|
|
23
|
+
self.type = self.database.site.fiat.config.floodmap_type
|
|
24
|
+
self._get_flood_map_paths()
|
|
25
|
+
|
|
26
|
+
def _get_flood_map_paths(self):
|
|
27
|
+
base_dir = self.database.scenarios.output_path / self.name / "Flooding"
|
|
28
|
+
# TODO check naming of files
|
|
29
|
+
if self.mode == Mode.single_event:
|
|
30
|
+
if self.type == FloodmapType.water_level:
|
|
31
|
+
self.path = base_dir / "max_water_level_map.nc"
|
|
32
|
+
elif self.type == FloodmapType.water_depth:
|
|
33
|
+
self.path = base_dir / f"FloodMap_{self.name}.tif"
|
|
34
|
+
elif self.mode == Mode.risk:
|
|
35
|
+
if self.type == FloodmapType.water_level:
|
|
36
|
+
self.path = list(base_dir.glob("RP_*_maps.nc"))
|
|
37
|
+
elif self.type == FloodmapType.water_depth:
|
|
38
|
+
self.path = list(base_dir.glob("RP_*_maps.tif"))
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def has_run(self) -> bool:
|
|
42
|
+
if self.mode == Mode.single_event:
|
|
43
|
+
return self.path.exists()
|
|
44
|
+
elif self.mode == Mode.risk:
|
|
45
|
+
check_files = [RP_map.exists() for RP_map in self.path]
|
|
46
|
+
check_rps = len(self.path) == len(
|
|
47
|
+
self.database.site.fiat.risk.return_periods
|
|
48
|
+
)
|
|
49
|
+
return all(check_files) & check_rps
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def scenario(self):
|
|
53
|
+
if hasattr(self, "_scenario"):
|
|
54
|
+
return self._scenario
|
|
55
|
+
self._scenario = self.database.scenarios.get(self.name)
|
|
56
|
+
return self._scenario
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def mode(self):
|
|
60
|
+
if hasattr(self, "_mode"):
|
|
61
|
+
return self._mode
|
|
62
|
+
self._mode = self.database.events.get(self.scenario.event).mode
|
|
63
|
+
return self._mode
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def crs(self):
|
|
67
|
+
if hasattr(self, "_crs"):
|
|
68
|
+
return self._crs
|
|
69
|
+
self._crs = self.database.site.crs
|
|
70
|
+
return self._crs
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def hazard_strategy(self) -> Strategy:
|
|
74
|
+
return self.database.strategies.get(
|
|
75
|
+
self.scenario.strategy
|
|
76
|
+
).get_hazard_strategy()
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def physical_projection(self):
|
|
80
|
+
if hasattr(self, "_physical_projection"):
|
|
81
|
+
return self._physical_projection
|
|
82
|
+
self._physical_projection = self.database.projections.get(
|
|
83
|
+
self.scenario.projection
|
|
84
|
+
).physical_projection
|
|
85
|
+
return self._physical_projection
|