flood-adapt 1.0.4__py3-none-any.whl → 1.1.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 +2 -3
- flood_adapt/adapter/fiat_adapter.py +8 -3
- flood_adapt/adapter/sfincs_adapter.py +2 -2
- flood_adapt/adapter/sfincs_offshore.py +1 -1
- flood_adapt/config/fiat.py +1 -1
- flood_adapt/config/gui.py +185 -8
- flood_adapt/database_builder/database_builder.py +155 -129
- flood_adapt/database_builder/metrics_utils.py +1834 -0
- flood_adapt/dbs_classes/database.py +23 -28
- flood_adapt/dbs_classes/dbs_benefit.py +0 -26
- flood_adapt/dbs_classes/dbs_event.py +2 -2
- flood_adapt/dbs_classes/dbs_measure.py +2 -2
- flood_adapt/dbs_classes/dbs_scenario.py +0 -24
- flood_adapt/dbs_classes/dbs_static.py +4 -4
- flood_adapt/dbs_classes/dbs_strategy.py +2 -4
- flood_adapt/dbs_classes/dbs_template.py +65 -25
- flood_adapt/flood_adapt.py +63 -12
- flood_adapt/misc/exceptions.py +43 -6
- {flood_adapt-1.0.4.dist-info → flood_adapt-1.1.0.dist-info}/METADATA +3 -3
- {flood_adapt-1.0.4.dist-info → flood_adapt-1.1.0.dist-info}/RECORD +24 -44
- flood_adapt/database_builder/templates/infographics/OSM/config_charts.toml +0 -90
- flood_adapt/database_builder/templates/infographics/OSM/config_people.toml +0 -57
- flood_adapt/database_builder/templates/infographics/OSM/config_risk_charts.toml +0 -121
- flood_adapt/database_builder/templates/infographics/OSM/config_roads.toml +0 -65
- flood_adapt/database_builder/templates/infographics/US_NSI/config_charts.toml +0 -126
- flood_adapt/database_builder/templates/infographics/US_NSI/config_people.toml +0 -60
- flood_adapt/database_builder/templates/infographics/US_NSI/config_risk_charts.toml +0 -121
- flood_adapt/database_builder/templates/infographics/US_NSI/config_roads.toml +0 -65
- flood_adapt/database_builder/templates/infographics/US_NSI/styles.css +0 -45
- flood_adapt/database_builder/templates/infometrics/OSM/metrics_additional_risk_configs.toml +0 -4
- flood_adapt/database_builder/templates/infometrics/OSM/with_SVI/infographic_metrics_config.toml +0 -143
- flood_adapt/database_builder/templates/infometrics/OSM/with_SVI/infographic_metrics_config_risk.toml +0 -153
- flood_adapt/database_builder/templates/infometrics/OSM/without_SVI/infographic_metrics_config.toml +0 -127
- flood_adapt/database_builder/templates/infometrics/OSM/without_SVI/infographic_metrics_config_risk.toml +0 -57
- flood_adapt/database_builder/templates/infometrics/US_NSI/metrics_additional_risk_configs.toml +0 -4
- flood_adapt/database_builder/templates/infometrics/US_NSI/with_SVI/infographic_metrics_config.toml +0 -191
- flood_adapt/database_builder/templates/infometrics/US_NSI/with_SVI/infographic_metrics_config_risk.toml +0 -153
- flood_adapt/database_builder/templates/infometrics/US_NSI/without_SVI/infographic_metrics_config.toml +0 -178
- flood_adapt/database_builder/templates/infometrics/US_NSI/without_SVI/infographic_metrics_config_risk.toml +0 -57
- flood_adapt/database_builder/templates/infometrics/mandatory_metrics_config.toml +0 -9
- flood_adapt/database_builder/templates/infometrics/mandatory_metrics_config_risk.toml +0 -65
- /flood_adapt/database_builder/templates/infographics/{OSM/styles.css → styles.css} +0 -0
- {flood_adapt-1.0.4.dist-info → flood_adapt-1.1.0.dist-info}/LICENSE +0 -0
- {flood_adapt-1.0.4.dist-info → flood_adapt-1.1.0.dist-info}/WHEEL +0 -0
- {flood_adapt-1.0.4.dist-info → flood_adapt-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import gc
|
|
2
2
|
import os
|
|
3
3
|
import shutil
|
|
4
|
-
import time
|
|
5
4
|
from pathlib import Path
|
|
6
5
|
from typing import Any, Literal, Optional, Union
|
|
7
6
|
|
|
@@ -11,6 +10,8 @@ import pandas as pd
|
|
|
11
10
|
import xarray as xr
|
|
12
11
|
from geopandas import GeoDataFrame
|
|
13
12
|
|
|
13
|
+
from flood_adapt.adapter.fiat_adapter import FiatAdapter
|
|
14
|
+
from flood_adapt.adapter.sfincs_adapter import SfincsAdapter
|
|
14
15
|
from flood_adapt.config.hazard import SlrScenariosModel
|
|
15
16
|
from flood_adapt.config.impacts import FloodmapType
|
|
16
17
|
from flood_adapt.config.site import Site
|
|
@@ -22,7 +23,7 @@ from flood_adapt.dbs_classes.dbs_scenario import DbsScenario
|
|
|
22
23
|
from flood_adapt.dbs_classes.dbs_static import DbsStatic
|
|
23
24
|
from flood_adapt.dbs_classes.dbs_strategy import DbsStrategy
|
|
24
25
|
from flood_adapt.dbs_classes.interface.database import IDatabase
|
|
25
|
-
from flood_adapt.misc.exceptions import DatabaseError
|
|
26
|
+
from flood_adapt.misc.exceptions import ConfigError, DatabaseError
|
|
26
27
|
from flood_adapt.misc.log import FloodAdaptLogging
|
|
27
28
|
from flood_adapt.misc.path_builder import (
|
|
28
29
|
TopLevelDir,
|
|
@@ -140,8 +141,6 @@ class Database(IDatabase):
|
|
|
140
141
|
|
|
141
142
|
def shutdown(self):
|
|
142
143
|
"""Explicitly shut down the singleton and clear all references."""
|
|
143
|
-
import gc
|
|
144
|
-
|
|
145
144
|
self._instance = None
|
|
146
145
|
self._init_done = False
|
|
147
146
|
|
|
@@ -191,7 +190,7 @@ class Database(IDatabase):
|
|
|
191
190
|
SLR scenarios configuration model with the file path set to the static path.
|
|
192
191
|
"""
|
|
193
192
|
if self.site.sfincs.slr_scenarios is None:
|
|
194
|
-
raise
|
|
193
|
+
raise ConfigError("No SLR scenarios defined in the site configuration.")
|
|
195
194
|
slr = self.site.sfincs.slr_scenarios
|
|
196
195
|
slr.file = str(self.static_path / slr.file)
|
|
197
196
|
return slr
|
|
@@ -577,20 +576,13 @@ class Database(IDatabase):
|
|
|
577
576
|
for dir in os.listdir(self.scenarios.output_path)
|
|
578
577
|
]
|
|
579
578
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
return # Exit if successful
|
|
588
|
-
except Exception as e:
|
|
589
|
-
print(
|
|
590
|
-
f"Attempt {attempt + 1}/{retries} failed to delete {path}: {e}"
|
|
591
|
-
)
|
|
592
|
-
|
|
593
|
-
print(f"Giving up on deleting {path} after {retries} attempts.")
|
|
579
|
+
# Init model instances outside the loop
|
|
580
|
+
overland = self.static.get_overland_sfincs_model()
|
|
581
|
+
fiat = self.static.get_fiat_model()
|
|
582
|
+
if self.site.sfincs.config.offshore_model is not None:
|
|
583
|
+
offshore = self.static.get_offshore_sfincs_model()
|
|
584
|
+
else:
|
|
585
|
+
offshore = None
|
|
594
586
|
|
|
595
587
|
for _dir in output_scenarios:
|
|
596
588
|
# Delete if: input was deleted or corrupted output due to unfinished run
|
|
@@ -598,12 +590,18 @@ class Database(IDatabase):
|
|
|
598
590
|
path.name for path in input_scenarios
|
|
599
591
|
] or not finished_file_exists(_dir):
|
|
600
592
|
logger.info(f"Cleaning up corrupted outputs of scenario: {_dir.name}.")
|
|
601
|
-
shutil.rmtree(_dir,
|
|
593
|
+
shutil.rmtree(_dir, ignore_errors=True)
|
|
602
594
|
# If the scenario is finished, delete the simulation folders depending on `save_simulation`
|
|
603
595
|
elif finished_file_exists(_dir):
|
|
604
|
-
self._delete_simulations(_dir.name)
|
|
596
|
+
self._delete_simulations(_dir.name, overland, fiat, offshore)
|
|
605
597
|
|
|
606
|
-
def _delete_simulations(
|
|
598
|
+
def _delete_simulations(
|
|
599
|
+
self,
|
|
600
|
+
scenario_name: str,
|
|
601
|
+
overland: SfincsAdapter,
|
|
602
|
+
fiat: FiatAdapter,
|
|
603
|
+
offshore: Optional[SfincsAdapter],
|
|
604
|
+
) -> None:
|
|
607
605
|
"""Delete all simulation folders for a given scenario.
|
|
608
606
|
|
|
609
607
|
Parameters
|
|
@@ -617,7 +615,6 @@ class Database(IDatabase):
|
|
|
617
615
|
|
|
618
616
|
if not self.site.sfincs.config.save_simulation:
|
|
619
617
|
# Delete SFINCS overland
|
|
620
|
-
overland = self.static.get_overland_sfincs_model()
|
|
621
618
|
if sub_events:
|
|
622
619
|
for sub_event in sub_events:
|
|
623
620
|
overland._delete_simulation_folder(scn, sub_event=sub_event)
|
|
@@ -625,8 +622,7 @@ class Database(IDatabase):
|
|
|
625
622
|
overland._delete_simulation_folder(scn)
|
|
626
623
|
|
|
627
624
|
# Delete SFINCS offshore
|
|
628
|
-
if self.site.sfincs.config.offshore_model:
|
|
629
|
-
offshore = self.static.get_offshore_sfincs_model()
|
|
625
|
+
if self.site.sfincs.config.offshore_model and offshore is not None:
|
|
630
626
|
if sub_events:
|
|
631
627
|
for sub_event in sub_events:
|
|
632
628
|
sim_path = offshore._get_simulation_path_offshore(
|
|
@@ -639,7 +635,7 @@ class Database(IDatabase):
|
|
|
639
635
|
sim_path.parent.iterdir()
|
|
640
636
|
):
|
|
641
637
|
# Remove the parent directory `simulations` if it is empty
|
|
642
|
-
sim_path.parent
|
|
638
|
+
shutil.rmtree(sim_path.parent, ignore_errors=True)
|
|
643
639
|
else:
|
|
644
640
|
sim_path = offshore._get_simulation_path_offshore(scn)
|
|
645
641
|
if sim_path.exists():
|
|
@@ -648,9 +644,8 @@ class Database(IDatabase):
|
|
|
648
644
|
|
|
649
645
|
if sim_path.parent.exists() and not any(sim_path.parent.iterdir()):
|
|
650
646
|
# Remove the parent directory `simulations` if it is empty
|
|
651
|
-
sim_path.parent
|
|
647
|
+
shutil.rmtree(sim_path.parent, ignore_errors=True)
|
|
652
648
|
|
|
653
649
|
if not self.site.fiat.config.save_simulation:
|
|
654
650
|
# Delete FIAT
|
|
655
|
-
fiat = self.static.get_fiat_model()
|
|
656
651
|
fiat._delete_simulation_folder(scn)
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import shutil
|
|
2
|
-
|
|
3
1
|
from flood_adapt.dbs_classes.dbs_template import DbsTemplate
|
|
4
2
|
from flood_adapt.misc.exceptions import DatabaseError
|
|
5
3
|
from flood_adapt.workflows.benefit_runner import Benefit, BenefitRunner
|
|
@@ -37,30 +35,6 @@ class DbsBenefit(DbsTemplate[Benefit]):
|
|
|
37
35
|
# Save the benefit
|
|
38
36
|
super().save(object_model, overwrite=overwrite)
|
|
39
37
|
|
|
40
|
-
def delete(self, name: str, toml_only: bool = False):
|
|
41
|
-
"""Delete an already existing benefit in the database.
|
|
42
|
-
|
|
43
|
-
Parameters
|
|
44
|
-
----------
|
|
45
|
-
name : str
|
|
46
|
-
name of the benefit
|
|
47
|
-
toml_only : bool, optional
|
|
48
|
-
whether to only delete the toml file or the entire folder. If the folder is empty after deleting the toml,
|
|
49
|
-
it will always be deleted. By default False
|
|
50
|
-
|
|
51
|
-
Raises
|
|
52
|
-
------
|
|
53
|
-
DatabaseError
|
|
54
|
-
Raise error if benefit has already model output
|
|
55
|
-
"""
|
|
56
|
-
# First delete the benefit
|
|
57
|
-
super().delete(name, toml_only=toml_only)
|
|
58
|
-
|
|
59
|
-
# Delete output if edited
|
|
60
|
-
output_path = self.output_path / name
|
|
61
|
-
if output_path.exists():
|
|
62
|
-
shutil.rmtree(output_path, ignore_errors=True)
|
|
63
|
-
|
|
64
38
|
def get_runner(self, name: str) -> BenefitRunner:
|
|
65
39
|
return BenefitRunner(self._database, self.get(name))
|
|
66
40
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
3
|
from flood_adapt.dbs_classes.dbs_template import DbsTemplate
|
|
4
|
-
from flood_adapt.misc.exceptions import
|
|
4
|
+
from flood_adapt.misc.exceptions import DoesNotExistError
|
|
5
5
|
from flood_adapt.objects.events.event_factory import EventFactory
|
|
6
6
|
from flood_adapt.objects.events.event_set import EventSet
|
|
7
7
|
from flood_adapt.objects.events.events import Event
|
|
@@ -31,7 +31,7 @@ class DbsEvent(DbsTemplate[Event]):
|
|
|
31
31
|
|
|
32
32
|
# Check if the object exists
|
|
33
33
|
if not Path(event_path).is_file():
|
|
34
|
-
raise
|
|
34
|
+
raise DoesNotExistError(name, self.display_name)
|
|
35
35
|
|
|
36
36
|
# Load event
|
|
37
37
|
return EventFactory.load_file(event_path, load_all=load_all)
|
|
@@ -4,7 +4,7 @@ from typing import Any
|
|
|
4
4
|
import geopandas as gpd
|
|
5
5
|
|
|
6
6
|
from flood_adapt.dbs_classes.dbs_template import DbsTemplate
|
|
7
|
-
from flood_adapt.misc.exceptions import DatabaseError
|
|
7
|
+
from flood_adapt.misc.exceptions import DatabaseError, DoesNotExistError
|
|
8
8
|
from flood_adapt.misc.utils import resolve_filepath
|
|
9
9
|
from flood_adapt.objects.measures.measure_factory import MeasureFactory
|
|
10
10
|
from flood_adapt.objects.measures.measures import Measure
|
|
@@ -34,7 +34,7 @@ class DbsMeasure(DbsTemplate[Measure]):
|
|
|
34
34
|
|
|
35
35
|
# Check if the object exists
|
|
36
36
|
if not Path(full_path).is_file():
|
|
37
|
-
raise
|
|
37
|
+
raise DoesNotExistError(name, self.display_name)
|
|
38
38
|
|
|
39
39
|
# Load and return the object
|
|
40
40
|
return MeasureFactory.get_measure_object(full_path)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import shutil
|
|
2
1
|
from typing import Any
|
|
3
2
|
|
|
4
3
|
from flood_adapt.dbs_classes.dbs_template import DbsTemplate
|
|
@@ -36,29 +35,6 @@ class DbsScenario(DbsTemplate[Scenario]):
|
|
|
36
35
|
|
|
37
36
|
return scenarios
|
|
38
37
|
|
|
39
|
-
def delete(self, name: str, toml_only: bool = False):
|
|
40
|
-
"""Delete an already existing scenario in the database.
|
|
41
|
-
|
|
42
|
-
Parameters
|
|
43
|
-
----------
|
|
44
|
-
name : str
|
|
45
|
-
name of the scenario to be deleted
|
|
46
|
-
toml_only : bool, optional
|
|
47
|
-
whether to only delete the toml file or the entire folder. If the folder is empty after deleting the toml,
|
|
48
|
-
it will always be deleted. By default False
|
|
49
|
-
|
|
50
|
-
Raises
|
|
51
|
-
------
|
|
52
|
-
DatabaseError
|
|
53
|
-
Raise error if scenario to be deleted is already in use.
|
|
54
|
-
"""
|
|
55
|
-
# First delete the scenario
|
|
56
|
-
super().delete(name, toml_only)
|
|
57
|
-
|
|
58
|
-
# Then delete the results
|
|
59
|
-
if (self.output_path / name).exists():
|
|
60
|
-
shutil.rmtree(self.output_path / name, ignore_errors=False)
|
|
61
|
-
|
|
62
38
|
def check_higher_level_usage(self, name: str) -> list[str]:
|
|
63
39
|
"""Check if a scenario is used in a benefit.
|
|
64
40
|
|
|
@@ -12,7 +12,7 @@ from flood_adapt.adapter.sfincs_adapter import SfincsAdapter
|
|
|
12
12
|
from flood_adapt.config.config import Settings
|
|
13
13
|
from flood_adapt.dbs_classes.interface.database import IDatabase
|
|
14
14
|
from flood_adapt.dbs_classes.interface.static import IDbsStatic
|
|
15
|
-
from flood_adapt.misc.exceptions import DatabaseError
|
|
15
|
+
from flood_adapt.misc.exceptions import ConfigError, DatabaseError
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def cache_method_wrapper(func: Callable) -> Callable:
|
|
@@ -260,7 +260,7 @@ class DbsStatic(IDbsStatic):
|
|
|
260
260
|
def get_offshore_sfincs_model(self) -> SfincsAdapter:
|
|
261
261
|
"""Get the template overland Sfincs model."""
|
|
262
262
|
if self._database.site.sfincs.config.offshore_model is None:
|
|
263
|
-
raise
|
|
263
|
+
raise ConfigError("No offshore model defined in the site configuration.")
|
|
264
264
|
|
|
265
265
|
offshore_path = (
|
|
266
266
|
self._database.static_path
|
|
@@ -273,7 +273,7 @@ class DbsStatic(IDbsStatic):
|
|
|
273
273
|
def get_fiat_model(self) -> FiatAdapter:
|
|
274
274
|
"""Get the path to the FIAT model."""
|
|
275
275
|
if self._database.site.fiat is None:
|
|
276
|
-
raise
|
|
276
|
+
raise ConfigError("No FIAT model defined in the site configuration.")
|
|
277
277
|
template_path = self._database.static_path / "templates" / "fiat"
|
|
278
278
|
with FiatAdapter(
|
|
279
279
|
model_root=template_path,
|
|
@@ -287,7 +287,7 @@ class DbsStatic(IDbsStatic):
|
|
|
287
287
|
@cache_method_wrapper
|
|
288
288
|
def get_cyclone_track_database(self) -> CycloneTrackDatabase:
|
|
289
289
|
if self._database.site.sfincs.cyclone_track_database is None:
|
|
290
|
-
raise
|
|
290
|
+
raise ConfigError(
|
|
291
291
|
"No cyclone track database defined in the site configuration."
|
|
292
292
|
)
|
|
293
293
|
database_file = str(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from itertools import combinations
|
|
2
2
|
|
|
3
3
|
from flood_adapt.dbs_classes.dbs_template import DbsTemplate
|
|
4
|
-
from flood_adapt.misc.exceptions import DatabaseError
|
|
4
|
+
from flood_adapt.misc.exceptions import AlreadyExistsError, DatabaseError
|
|
5
5
|
from flood_adapt.objects.measures.measures import MeasureType
|
|
6
6
|
from flood_adapt.objects.strategies.strategies import Strategy
|
|
7
7
|
|
|
@@ -49,9 +49,7 @@ class DbsStrategy(DbsTemplate[Strategy]):
|
|
|
49
49
|
if overwrite and object_exists:
|
|
50
50
|
self.delete(object_model.name, toml_only=True)
|
|
51
51
|
elif not overwrite and object_exists:
|
|
52
|
-
raise
|
|
53
|
-
f"'{object_model.name}' name is already used by another {self.display_name}. Choose a different name"
|
|
54
|
-
)
|
|
52
|
+
raise AlreadyExistsError(object_model.name, self.display_name)
|
|
55
53
|
|
|
56
54
|
# Check if any measures overlap
|
|
57
55
|
self._check_overlapping_measures(object_model.measures)
|
|
@@ -8,7 +8,13 @@ import tomli_w
|
|
|
8
8
|
|
|
9
9
|
from flood_adapt.dbs_classes.interface.database import IDatabase
|
|
10
10
|
from flood_adapt.dbs_classes.interface.element import AbstractDatabaseElement
|
|
11
|
-
from flood_adapt.misc.exceptions import
|
|
11
|
+
from flood_adapt.misc.exceptions import (
|
|
12
|
+
AlreadyExistsError,
|
|
13
|
+
DatabaseError,
|
|
14
|
+
DoesNotExistError,
|
|
15
|
+
IsStandardObjectError,
|
|
16
|
+
IsUsedInError,
|
|
17
|
+
)
|
|
12
18
|
from flood_adapt.objects.object_model import Object
|
|
13
19
|
|
|
14
20
|
T_OBJECTMODEL = TypeVar("T_OBJECTMODEL", bound=Object)
|
|
@@ -41,13 +47,18 @@ class DbsTemplate(AbstractDatabaseElement[T_OBJECTMODEL]):
|
|
|
41
47
|
-------
|
|
42
48
|
Object
|
|
43
49
|
object of the type of the specified object model
|
|
50
|
+
|
|
51
|
+
Raises
|
|
52
|
+
------
|
|
53
|
+
DoesNotExistError
|
|
54
|
+
Raise error if the object does not exist.
|
|
44
55
|
"""
|
|
45
56
|
# Make the full path to the object
|
|
46
57
|
full_path = self.input_path / name / f"{name}.toml"
|
|
47
58
|
|
|
48
59
|
# Check if the object exists
|
|
49
60
|
if not Path(full_path).is_file():
|
|
50
|
-
raise
|
|
61
|
+
raise DoesNotExistError(name, self.display_name)
|
|
51
62
|
|
|
52
63
|
# Load and return the object
|
|
53
64
|
return self._object_class.load_file(full_path)
|
|
@@ -74,6 +85,15 @@ class DbsTemplate(AbstractDatabaseElement[T_OBJECTMODEL]):
|
|
|
74
85
|
name of the new measure
|
|
75
86
|
new_description : str
|
|
76
87
|
description of the new measure
|
|
88
|
+
|
|
89
|
+
Raises
|
|
90
|
+
------
|
|
91
|
+
AlreadyExistsError
|
|
92
|
+
Raise error if an object with the new name already exists.
|
|
93
|
+
IsStandardObjectError
|
|
94
|
+
Raise error if an object with the new name is a standard object.
|
|
95
|
+
DatabaseError
|
|
96
|
+
Raise error if the saving of the object fails.
|
|
77
97
|
"""
|
|
78
98
|
copy_object = self.get(old_name)
|
|
79
99
|
copy_object.name = new_name
|
|
@@ -123,8 +143,14 @@ class DbsTemplate(AbstractDatabaseElement[T_OBJECTMODEL]):
|
|
|
123
143
|
|
|
124
144
|
Raises
|
|
125
145
|
------
|
|
146
|
+
AlreadyExistsError
|
|
147
|
+
Raise error if object to be saved already exists.
|
|
148
|
+
IsStandardObjectError
|
|
149
|
+
Raise error if object to be overwritten is a standard object.
|
|
150
|
+
IsUsedInError
|
|
151
|
+
Raise error if object to be overwritten is already in use.
|
|
126
152
|
DatabaseError
|
|
127
|
-
Raise error if
|
|
153
|
+
Raise error if the overwriting of the object fails.
|
|
128
154
|
"""
|
|
129
155
|
self._validate_to_save(object_model, overwrite=overwrite)
|
|
130
156
|
|
|
@@ -138,7 +164,7 @@ class DbsTemplate(AbstractDatabaseElement[T_OBJECTMODEL]):
|
|
|
138
164
|
)
|
|
139
165
|
|
|
140
166
|
def delete(self, name: str, toml_only: bool = False):
|
|
141
|
-
"""Delete an already existing object
|
|
167
|
+
"""Delete an already existing object as well as its outputs from the database.
|
|
142
168
|
|
|
143
169
|
Parameters
|
|
144
170
|
----------
|
|
@@ -150,34 +176,50 @@ class DbsTemplate(AbstractDatabaseElement[T_OBJECTMODEL]):
|
|
|
150
176
|
|
|
151
177
|
Raises
|
|
152
178
|
------
|
|
153
|
-
|
|
179
|
+
IsStandardObjectError
|
|
180
|
+
Raise error if object to be deleted is a standard object.
|
|
181
|
+
IsUsedInError
|
|
154
182
|
Raise error if object to be deleted is already in use.
|
|
183
|
+
DoesNotExistError
|
|
184
|
+
Raise error if object to be deleted does not exist.
|
|
185
|
+
DatabaseError
|
|
186
|
+
Raise error if the deletion of the object fails.
|
|
155
187
|
"""
|
|
156
188
|
# Check if the object is a standard object. If it is, raise an error
|
|
157
189
|
if self._check_standard_objects(name):
|
|
158
|
-
raise
|
|
159
|
-
f"'{name}' cannot be deleted/modified since it is a standard {self.display_name}."
|
|
160
|
-
)
|
|
190
|
+
raise IsStandardObjectError(name, self.display_name)
|
|
161
191
|
|
|
162
192
|
# Check if object is used in a higher level object. If it is, raise an error
|
|
163
193
|
if used_in := self.check_higher_level_usage(name):
|
|
164
|
-
raise
|
|
165
|
-
|
|
194
|
+
raise IsUsedInError(
|
|
195
|
+
name, self.display_name, self._higher_lvl_object, used_in
|
|
166
196
|
)
|
|
167
197
|
|
|
168
|
-
#
|
|
198
|
+
# Check if the object exists
|
|
169
199
|
toml_path = self.input_path / name / f"{name}.toml"
|
|
170
|
-
if
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
shutil.rmtree(toml_path.parent
|
|
179
|
-
|
|
180
|
-
|
|
200
|
+
if not toml_path.exists():
|
|
201
|
+
raise DoesNotExistError(name, self.display_name)
|
|
202
|
+
|
|
203
|
+
# Once all checks are passed, delete the object
|
|
204
|
+
toml_path.unlink(missing_ok=True)
|
|
205
|
+
|
|
206
|
+
# Delete the entire folder
|
|
207
|
+
if not list(toml_path.parent.iterdir()):
|
|
208
|
+
shutil.rmtree(toml_path.parent)
|
|
209
|
+
elif not toml_only:
|
|
210
|
+
try:
|
|
211
|
+
shutil.rmtree(toml_path.parent)
|
|
212
|
+
except OSError as e:
|
|
213
|
+
raise DatabaseError(f"Failed to delete `{name}` due to: {e}") from e
|
|
214
|
+
|
|
215
|
+
# Delete output
|
|
216
|
+
if (self.output_path / name).exists():
|
|
217
|
+
try:
|
|
218
|
+
shutil.rmtree(self.output_path / name)
|
|
219
|
+
except OSError as e:
|
|
220
|
+
raise DatabaseError(
|
|
221
|
+
f"Failed to delete output of `{name}` due to: {e}"
|
|
222
|
+
) from e
|
|
181
223
|
|
|
182
224
|
def _check_standard_objects(self, name: str) -> bool:
|
|
183
225
|
"""Check if an object is a standard object.
|
|
@@ -275,6 +317,4 @@ class DbsTemplate(AbstractDatabaseElement[T_OBJECTMODEL]):
|
|
|
275
317
|
if overwrite and object_exists:
|
|
276
318
|
self.delete(object_model.name, toml_only=True)
|
|
277
319
|
elif not overwrite and object_exists:
|
|
278
|
-
raise
|
|
279
|
-
f"'{object_model.name}' name is already used by another {self.display_name.lower()}. Choose a different name"
|
|
280
|
-
)
|
|
320
|
+
raise AlreadyExistsError(object_model.name, self.display_name)
|
flood_adapt/flood_adapt.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from typing import Any, List, Optional, Union
|
|
2
|
+
from typing import Any, List, Literal, Optional, Union
|
|
3
3
|
|
|
4
4
|
import geopandas as gpd
|
|
5
5
|
import numpy as np
|
|
@@ -935,43 +935,94 @@ class FloodAdapt:
|
|
|
935
935
|
|
|
936
936
|
return infographic_path
|
|
937
937
|
|
|
938
|
-
def get_infometrics(
|
|
939
|
-
|
|
938
|
+
def get_infometrics(
|
|
939
|
+
self, name: str, aggr_name: Optional[str] = None
|
|
940
|
+
) -> pd.DataFrame:
|
|
941
|
+
"""Return the infometrics DataFrame for the given scenario and optional aggregation.
|
|
940
942
|
|
|
941
943
|
Parameters
|
|
942
944
|
----------
|
|
943
945
|
name : str
|
|
944
946
|
The name of the scenario.
|
|
947
|
+
aggr_name : Optional[str], default None
|
|
948
|
+
The name of the aggregation, if any.
|
|
945
949
|
|
|
946
950
|
Returns
|
|
947
951
|
-------
|
|
948
|
-
|
|
949
|
-
The
|
|
952
|
+
df : pd.DataFrame
|
|
953
|
+
The infometrics DataFrame for the scenario (and aggregation if specified).
|
|
950
954
|
|
|
951
955
|
Raises
|
|
952
956
|
------
|
|
953
957
|
FileNotFoundError
|
|
954
|
-
If the metrics file does not exist.
|
|
958
|
+
If the metrics file does not exist for the given scenario (and aggregation).
|
|
955
959
|
"""
|
|
960
|
+
if aggr_name is not None:
|
|
961
|
+
fn = f"Infometrics_{name}_{aggr_name}.csv"
|
|
962
|
+
else:
|
|
963
|
+
fn = f"Infometrics_{name}.csv"
|
|
956
964
|
# Create the infographic path
|
|
957
|
-
metrics_path = self.database.scenarios.output_path.joinpath(
|
|
958
|
-
name,
|
|
959
|
-
f"Infometrics_{name}.csv",
|
|
960
|
-
)
|
|
965
|
+
metrics_path = self.database.scenarios.output_path.joinpath(name, fn)
|
|
961
966
|
|
|
962
967
|
# Check if the file exists
|
|
963
968
|
if not metrics_path.exists():
|
|
964
969
|
raise FileNotFoundError(
|
|
965
970
|
f"The metrics file for scenario {name}({str(metrics_path)}) does not exist."
|
|
966
971
|
)
|
|
967
|
-
|
|
968
972
|
# Read the metrics file
|
|
969
|
-
|
|
973
|
+
df = MetricsFileReader(str(metrics_path)).read_metrics_from_file(
|
|
970
974
|
include_long_names=True,
|
|
971
975
|
include_description=True,
|
|
972
976
|
include_metrics_table_selection=True,
|
|
977
|
+
include_metrics_map_selection=True,
|
|
978
|
+
)
|
|
979
|
+
if aggr_name is not None:
|
|
980
|
+
df = df.T
|
|
981
|
+
return df
|
|
982
|
+
|
|
983
|
+
def get_aggr_metric_layers(
|
|
984
|
+
self,
|
|
985
|
+
name: str,
|
|
986
|
+
aggr_type: str,
|
|
987
|
+
type: Literal["single_event", "risk"] = "single_event",
|
|
988
|
+
rp: Optional[int] = None,
|
|
989
|
+
equity: bool = False,
|
|
990
|
+
) -> list[dict]:
|
|
991
|
+
# Read infometrics from csv file
|
|
992
|
+
metrics_df = self.get_infometrics(name, aggr_name=aggr_type)
|
|
993
|
+
|
|
994
|
+
# Filter based on "Show in Metrics Map" column
|
|
995
|
+
if "Show In Metrics Map" in metrics_df.index:
|
|
996
|
+
mask = metrics_df.loc["Show In Metrics Map"].to_numpy().astype(bool)
|
|
997
|
+
metrics_df = metrics_df.loc[:, mask]
|
|
998
|
+
|
|
999
|
+
# Keep only relevant attributes of the infometrics
|
|
1000
|
+
keep_rows = [
|
|
1001
|
+
"Description",
|
|
1002
|
+
"Long Name",
|
|
1003
|
+
"Show In Metrics Table",
|
|
1004
|
+
"Show In Metrics Map",
|
|
1005
|
+
]
|
|
1006
|
+
metrics_df = metrics_df.loc[
|
|
1007
|
+
[row for row in keep_rows if row in metrics_df.index]
|
|
1008
|
+
]
|
|
1009
|
+
|
|
1010
|
+
# Transform to list of dicts
|
|
1011
|
+
metrics = []
|
|
1012
|
+
for col in metrics_df.columns:
|
|
1013
|
+
metric_dict = {"name": col}
|
|
1014
|
+
# Add the first 4 rows as key-value pairs
|
|
1015
|
+
for i, idx in enumerate(metrics_df.index):
|
|
1016
|
+
metric_dict[idx] = metrics_df.loc[idx, col]
|
|
1017
|
+
metrics.append(metric_dict)
|
|
1018
|
+
|
|
1019
|
+
# Get the filtered metrics layers from the GUI configuration
|
|
1020
|
+
filtered_metrics = self.database.site.gui.output_layers.get_aggr_metrics_layers(
|
|
1021
|
+
metrics, type, rp, equity
|
|
973
1022
|
)
|
|
974
1023
|
|
|
1024
|
+
return filtered_metrics
|
|
1025
|
+
|
|
975
1026
|
# Static
|
|
976
1027
|
def load_static_data(self):
|
|
977
1028
|
"""Read the static data into the cache.
|
flood_adapt/misc/exceptions.py
CHANGED
|
@@ -10,13 +10,50 @@ class DatabaseError(FloodAdaptError):
|
|
|
10
10
|
pass
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
class
|
|
14
|
-
"""
|
|
13
|
+
class IsStandardObjectError(DatabaseError):
|
|
14
|
+
"""Raised when an operation is attempted on a standard object."""
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
name: str,
|
|
19
|
+
object_type: str,
|
|
20
|
+
):
|
|
21
|
+
msg = f"The {object_type} '{name}' is a standard object and cannot be modified or deleted."
|
|
22
|
+
super().__init__(msg)
|
|
17
23
|
|
|
18
24
|
|
|
19
|
-
class
|
|
20
|
-
"""
|
|
25
|
+
class AlreadyExistsError(DatabaseError):
|
|
26
|
+
"""Raised when an attempt is made to create an object that already exists."""
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
name: str,
|
|
31
|
+
object_type: str,
|
|
32
|
+
):
|
|
33
|
+
msg = f"The {object_type} '{name}' already exists."
|
|
34
|
+
super().__init__(msg)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class DoesNotExistError(DatabaseError):
|
|
38
|
+
"""Raised when an attempt is made to access an object that does not exist."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, name: str, object_type: str):
|
|
41
|
+
msg = f"The {object_type} '{name}' does not exist."
|
|
42
|
+
super().__init__(msg)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class IsUsedInError(DatabaseError):
|
|
46
|
+
"""Raised when an attempt is made to delete or modify an object that is in use / referenced by another object."""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self, name: str, object_type: str, used_in_type: str, used_in: list[str]
|
|
50
|
+
):
|
|
51
|
+
msg = f"The {object_type} '{name}' cannot be deleted/modified since it is already used in the {used_in_type}(s): {', '.join(used_in)}"
|
|
52
|
+
super().__init__(msg)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ConfigError(DatabaseError):
|
|
56
|
+
"""Raised when optional configuration, usually in the site, is missing or invalid."""
|
|
57
|
+
|
|
58
|
+
def __init__(self, message: str):
|
|
59
|
+
super().__init__(message)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: flood-adapt
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
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: ====================================================
|
|
@@ -714,7 +714,7 @@ Requires-Dist: cht-observations==0.2.1
|
|
|
714
714
|
Requires-Dist: cht-tide==0.1.1
|
|
715
715
|
Requires-Dist: dask==2024.11.2
|
|
716
716
|
Requires-Dist: numba_celltree==0.2.2
|
|
717
|
-
Requires-Dist: fiat-toolbox
|
|
717
|
+
Requires-Dist: fiat-toolbox<0.2.0,>=0.1.22
|
|
718
718
|
Requires-Dist: fiona<2.0,>=1.0
|
|
719
719
|
Requires-Dist: geojson<4.0,>=3.0
|
|
720
720
|
Requires-Dist: geopandas<2.0,>=1.0
|
|
@@ -723,7 +723,7 @@ Requires-Dist: hydromt-sfincs==1.2.0
|
|
|
723
723
|
Requires-Dist: numpy<2.0,>=1.0
|
|
724
724
|
Requires-Dist: numpy-financial<2.0,>=1.0
|
|
725
725
|
Requires-Dist: pandas<3.0,>=2.0
|
|
726
|
-
Requires-Dist: plotly<
|
|
726
|
+
Requires-Dist: plotly<6.3,>=6.0
|
|
727
727
|
Requires-Dist: pydantic<3.0,>=2.0
|
|
728
728
|
Requires-Dist: pydantic-settings<3.0,>=2.0
|
|
729
729
|
Requires-Dist: pyogrio<1.0
|