From 8f51a13f037c72a738945ba4aaf69c4b85aec395 Mon Sep 17 00:00:00 2001 From: LuukBlom Date: Wed, 12 Jun 2024 09:49:55 +0200 Subject: [PATCH 1/6] delete temporary fiatmodels when they are no longer needed --- .pre-commit-config.yaml | 4 ++-- flood_adapt/dbs_controller.py | 6 ++++-- .../object_model/direct_impact/measure/impact_measure.py | 2 ++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b205f339f..b993d070f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,11 +16,11 @@ repos: hooks: - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.277 + rev: v0.4.8 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/crate-ci/typos - rev: v1.15.10 + rev: v1.22.0 hooks: - id: typos diff --git a/flood_adapt/dbs_controller.py b/flood_adapt/dbs_controller.py index cdc05b2d8..80fdc68e8 100644 --- a/flood_adapt/dbs_controller.py +++ b/flood_adapt/dbs_controller.py @@ -86,7 +86,7 @@ def __init__( if database_path is None or database_name is None: if not self._init_done: raise ValueError( - """Database path and name must be provided for the first initialization. + """Database path and name must be provided for the first initialization. To do this, run api_static.read_database(database_path, site_name) first.""" ) else: @@ -739,7 +739,7 @@ def get_buildings(self) -> GeoDataFrame: non_building_names=self.site.attrs.fiat.non_building_names, return_gdf=True, ) - + del fm return buildings def get_property_types(self) -> list: @@ -762,6 +762,8 @@ def get_property_types(self) -> list: types.remove(name) # Add "all" type for using as identifier types.append("all") + + del fm return types def write_to_csv(self, name: str, event: IEvent, df: pd.DataFrame): diff --git a/flood_adapt/object_model/direct_impact/measure/impact_measure.py b/flood_adapt/object_model/direct_impact/measure/impact_measure.py index 98b13b00f..61b523d1a 100644 --- a/flood_adapt/object_model/direct_impact/measure/impact_measure.py +++ b/flood_adapt/object_model/direct_impact/measure/impact_measure.py @@ -60,4 +60,6 @@ def get_object_ids(self, fiat_model: Optional[FiatModel] = None) -> list[Any]: polygon_file=polygon_file, ) + del fiat_model + return ids From eed9b2b9e64d3579dc40f25bde6d351fbf3aa8f8 Mon Sep 17 00:00:00 2001 From: LuukBlom Date: Wed, 12 Jun 2024 09:51:20 +0200 Subject: [PATCH 2/6] Revert "delete temporary fiatmodels when they are no longer needed" This reverts commit 8f51a13f037c72a738945ba4aaf69c4b85aec395. --- .pre-commit-config.yaml | 4 ++-- flood_adapt/dbs_controller.py | 6 ++---- .../object_model/direct_impact/measure/impact_measure.py | 2 -- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b993d070f..b205f339f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,11 +16,11 @@ repos: hooks: - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.8 + rev: v0.0.277 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/crate-ci/typos - rev: v1.22.0 + rev: v1.15.10 hooks: - id: typos diff --git a/flood_adapt/dbs_controller.py b/flood_adapt/dbs_controller.py index 80fdc68e8..cdc05b2d8 100644 --- a/flood_adapt/dbs_controller.py +++ b/flood_adapt/dbs_controller.py @@ -86,7 +86,7 @@ def __init__( if database_path is None or database_name is None: if not self._init_done: raise ValueError( - """Database path and name must be provided for the first initialization. + """Database path and name must be provided for the first initialization. To do this, run api_static.read_database(database_path, site_name) first.""" ) else: @@ -739,7 +739,7 @@ def get_buildings(self) -> GeoDataFrame: non_building_names=self.site.attrs.fiat.non_building_names, return_gdf=True, ) - del fm + return buildings def get_property_types(self) -> list: @@ -762,8 +762,6 @@ def get_property_types(self) -> list: types.remove(name) # Add "all" type for using as identifier types.append("all") - - del fm return types def write_to_csv(self, name: str, event: IEvent, df: pd.DataFrame): diff --git a/flood_adapt/object_model/direct_impact/measure/impact_measure.py b/flood_adapt/object_model/direct_impact/measure/impact_measure.py index 61b523d1a..98b13b00f 100644 --- a/flood_adapt/object_model/direct_impact/measure/impact_measure.py +++ b/flood_adapt/object_model/direct_impact/measure/impact_measure.py @@ -60,6 +60,4 @@ def get_object_ids(self, fiat_model: Optional[FiatModel] = None) -> list[Any]: polygon_file=polygon_file, ) - del fiat_model - return ids From 82f4bac79c98e650997f8210b420b648b3c4b122 Mon Sep 17 00:00:00 2001 From: dladrichem <136334482+dladrichem@users.noreply.github.com> Date: Fri, 14 Jun 2024 11:43:10 +0200 Subject: [PATCH 3/6] Fix naming issues (#456) * Incorporate database in hazard and direct_impacts * Replace path calls in direct_impacts and hazard * Fix rest of backend * Undo some unneeded changes * Bugfixes * Fix black * Add standard method for output paths * Some more forgotten output paths * Fix black * Fix ruff and black --- flood_adapt/api/events.py | 2 +- flood_adapt/api/output.py | 33 +++--- flood_adapt/dbs_classes/dbs_benefit.py | 9 +- flood_adapt/dbs_classes/dbs_event.py | 23 +++-- flood_adapt/dbs_classes/dbs_interface.py | 17 +++ flood_adapt/dbs_classes/dbs_measure.py | 1 - flood_adapt/dbs_classes/dbs_projection.py | 5 +- flood_adapt/dbs_classes/dbs_scenario.py | 6 +- flood_adapt/dbs_classes/dbs_strategy.py | 5 +- flood_adapt/dbs_classes/dbs_template.py | 20 ++++ flood_adapt/dbs_controller.py | 52 +++++----- flood_adapt/object_model/direct_impacts.py | 82 ++++++--------- flood_adapt/object_model/hazard/hazard.py | 115 ++++++++------------- flood_adapt/object_model/scenario.py | 18 ++-- 14 files changed, 191 insertions(+), 197 deletions(-) diff --git a/flood_adapt/api/events.py b/flood_adapt/api/events.py index f431b4fe8..7a834f53a 100644 --- a/flood_adapt/api/events.py +++ b/flood_adapt/api/events.py @@ -31,7 +31,7 @@ def get_event(name: str) -> IEvent: def get_event_mode(name: str) -> str: - filename = Database().input_path / "events" / f"{name}" / f"{name}.toml" + filename = Database().events.get_database_path() / f"{name}" / f"{name}.toml" return Event.get_mode(filename) diff --git a/flood_adapt/api/output.py b/flood_adapt/api/output.py index 0ab0e823a..fd2baf93d 100644 --- a/flood_adapt/api/output.py +++ b/flood_adapt/api/output.py @@ -1,4 +1,3 @@ -from pathlib import Path from typing import Any import geopandas as gpd @@ -67,8 +66,12 @@ def get_obs_point_timeseries(name: str) -> gpd.GeoDataFrame: f"Scenario {name} has not been run. Please run the scenario first." ) - output_path = Path(Database().output_path).joinpath("Scenarios", hazard.name) - gdf = Database().get_obs_points() + output_path = ( + Database() + .scenarios.get_database_path(get_input_path=False) + .joinpath(hazard.name) + ) + gdf = Database().static.get_obs_points() gdf["html"] = [ str(output_path.joinpath("Flooding", f"{station}_timeseries.html")) for station in gdf.name @@ -93,7 +96,8 @@ def get_infographic(name: str) -> str: The HTML string of the infographic. """ # Get the direct_impacts objects from the scenario - impact = Database().scenarios.get(name).direct_impacts + database = Database() + impact = database.scenarios.get(name).direct_impacts # Check if the scenario has run if not impact.has_run_check(): @@ -101,12 +105,11 @@ def get_infographic(name: str) -> str: f"Scenario {name} has not been run. Please run the scenario first." ) - database_path = Path(Database().input_path).parent - config_path = database_path.joinpath("static", "templates", "infographics") - output_path = database_path.joinpath("output", "Scenarios", impact.name) - metrics_outputs_path = database_path.joinpath( - "output", "Scenarios", impact.name, f"Infometrics_{impact.name}.csv" + config_path = database.static_path.joinpath("templates", "infographics") + output_path = database.scenarios.get_database_path(get_input_path=False).joinpath( + impact.name ) + metrics_outputs_path = output_path.joinpath(f"Infometrics_{impact.name}.csv") infographic_path = InforgraphicFactory.create_infographic_file_writer( infographic_mode=impact.hazard.event_mode, @@ -139,11 +142,13 @@ def get_infometrics(name: str) -> pd.DataFrame: If the metrics file does not exist. """ # Create the infographic path - metrics_path = Path(Database().input_path).parent.joinpath( - "output", - "Scenarios", - name, - f"Infometrics_{name}.csv", + metrics_path = ( + Database() + .scenarios.get_database_path(get_input_path=False) + .joinpath( + name, + f"Infometrics_{name}.csv", + ) ) # Check if the file exists diff --git a/flood_adapt/dbs_classes/dbs_benefit.py b/flood_adapt/dbs_classes/dbs_benefit.py index 5a6228d43..364d644da 100644 --- a/flood_adapt/dbs_classes/dbs_benefit.py +++ b/flood_adapt/dbs_classes/dbs_benefit.py @@ -54,7 +54,9 @@ def delete(self, name: str, toml_only: bool = False): super().delete(name, toml_only=toml_only) # Delete output if edited - output_path = self._database.output_path / "Benefits" / name + output_path = ( + self._database.benefits.get_database_path(get_input_path=False) / name + ) if output_path.exists(): shutil.rmtree(output_path, ignore_errors=True) @@ -76,7 +78,10 @@ def edit(self, benefit: IBenefit): super().edit(benefit) # Delete output if edited - output_path = self._database.output_path / "Benefits" / benefit.attrs.name + output_path = ( + self._database.benefits.get_database_path(get_input_path=False) + / benefit.attrs.name + ) if output_path.exists(): shutil.rmtree(output_path, ignore_errors=True) diff --git a/flood_adapt/dbs_classes/dbs_event.py b/flood_adapt/dbs_classes/dbs_event.py index 85c5c0397..0b493aa5a 100644 --- a/flood_adapt/dbs_classes/dbs_event.py +++ b/flood_adapt/dbs_classes/dbs_event.py @@ -4,9 +4,8 @@ from flood_adapt.dbs_classes.dbs_template import DbsTemplate from flood_adapt.object_model.hazard.event.event import Event from flood_adapt.object_model.hazard.event.event_factory import EventFactory -from flood_adapt.object_model.hazard.hazard import Hazard -from flood_adapt.object_model.interface.events import IEvent -from flood_adapt.object_model.scenario import Scenario +from flood_adapt.object_model.hazard.event.eventset import EventSet +from flood_adapt.object_model.interface.events import IEvent, Mode class DbsEvent(DbsTemplate): @@ -35,9 +34,14 @@ def get(self, name: str) -> IEvent: raise ValueError(f"{self._type.capitalize()} '{name}' does not exist.") # Load event - event_template = Event.get_template(event_path) - event = EventFactory.get_event(event_template).load_file(event_path) - return event + mode = Event.get_mode(event_path) + if mode == Mode.single_event: + # parse event config file to get event template + template = Event.get_template(event_path) + # use event template to get the associated event child class + return EventFactory.get_event(template).load_file(event_path) + elif mode == Mode.risk: + return EventSet.load_file(event_path) def list_objects(self) -> dict[str, Any]: """Return a dictionary with info on the events that currently exist in the database. @@ -48,8 +52,7 @@ def list_objects(self) -> dict[str, Any]: Includes 'name', 'description', 'path' and 'last_modification_date' info """ events = self._get_object_list() - objects = [Hazard.get_event_object(path) for path in events["path"]] - events["name"] = [obj.attrs.name for obj in objects] + objects = [self._database.events.get(name) for name in events["name"]] events["description"] = [obj.attrs.description for obj in objects] events["objects"] = objects return events @@ -89,8 +92,8 @@ def check_higher_level_usage(self, name: str) -> list[str]: """ # Get all the scenarios scenarios = [ - Scenario.load_file(path) - for path in self._database.scenarios.list_objects()["path"] + self._database.scenarios.get(name) + for name in self._database.scenarios.list_objects()["name"] ] # Check if event is used in a scenario diff --git a/flood_adapt/dbs_classes/dbs_interface.py b/flood_adapt/dbs_classes/dbs_interface.py index 59ae05def..45912d755 100644 --- a/flood_adapt/dbs_classes/dbs_interface.py +++ b/flood_adapt/dbs_classes/dbs_interface.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from pathlib import Path from typing import Any, Union from flood_adapt.object_model.interface.benefits import IBenefit @@ -129,3 +130,19 @@ def check_higher_level_usage(self, name: str) -> list[str]: list of higher level objects that use the object """ pass + + @abstractmethod + def get_database_path(self, get_input_path: bool) -> Path: + """Return the path to the database. + + Parameters + ---------- + get_input_path : bool + whether to return the input or output path + + Returns + ------- + Path + path to the database + """ + pass diff --git a/flood_adapt/dbs_classes/dbs_measure.py b/flood_adapt/dbs_classes/dbs_measure.py index c69c02bb4..ee94dbbcb 100644 --- a/flood_adapt/dbs_classes/dbs_measure.py +++ b/flood_adapt/dbs_classes/dbs_measure.py @@ -41,7 +41,6 @@ def list_objects(self) -> dict[str, Any]: """ measures = self._get_object_list() objects = [MeasureFactory.get_measure_object(path) for path in measures["path"]] - measures["name"] = [obj.attrs.name for obj in objects] measures["description"] = [obj.attrs.description for obj in objects] measures["objects"] = objects diff --git a/flood_adapt/dbs_classes/dbs_projection.py b/flood_adapt/dbs_classes/dbs_projection.py index 1ff381895..ac0c56fb3 100644 --- a/flood_adapt/dbs_classes/dbs_projection.py +++ b/flood_adapt/dbs_classes/dbs_projection.py @@ -1,6 +1,5 @@ from flood_adapt.dbs_classes.dbs_template import DbsTemplate from flood_adapt.object_model.projection import Projection -from flood_adapt.object_model.scenario import Scenario class DbsProjection(DbsTemplate): @@ -43,8 +42,8 @@ def check_higher_level_usage(self, name: str) -> list[str]: """ # Get all the scenarios scenarios = [ - Scenario.load_file(path) - for path in self._database.scenarios.list_objects()["path"] + self._database.scenarios.get(name) + for name in self._database.scenarios.list_objects()["name"] ] # Check if projection is used in a scenario diff --git a/flood_adapt/dbs_classes/dbs_scenario.py b/flood_adapt/dbs_classes/dbs_scenario.py index 120b49f58..a02ed23ed 100644 --- a/flood_adapt/dbs_classes/dbs_scenario.py +++ b/flood_adapt/dbs_classes/dbs_scenario.py @@ -66,7 +66,7 @@ def delete(self, name: str, toml_only: bool = False): super().delete(name, toml_only) # Then delete the results - results_path = self._database.output_path / "Scenarios" / name + results_path = self._database.output_path / self._folder_name / name if results_path.exists(): shutil.rmtree(results_path, ignore_errors=False) @@ -88,7 +88,9 @@ def edit(self, scenario: IScenario): super().edit(scenario) # Delete output if edited - output_path = self._database.output_path / "Scenarios" / scenario.attrs.name + output_path = ( + self._database.output_path / self._folder_name / scenario.attrs.name + ) if output_path.exists(): shutil.rmtree(output_path, ignore_errors=True) diff --git a/flood_adapt/dbs_classes/dbs_strategy.py b/flood_adapt/dbs_classes/dbs_strategy.py index 67f322f82..694fa8f53 100644 --- a/flood_adapt/dbs_classes/dbs_strategy.py +++ b/flood_adapt/dbs_classes/dbs_strategy.py @@ -1,5 +1,4 @@ from flood_adapt.dbs_classes.dbs_template import DbsTemplate -from flood_adapt.object_model.scenario import Scenario from flood_adapt.object_model.strategy import Strategy @@ -43,8 +42,8 @@ def check_higher_level_usage(self, name: str) -> list[str]: """ # Get all the scenarios scenarios = [ - Scenario.load_file(path) - for path in self._database.scenarios.list_objects()["path"] + self._database.scenarios.get(name) + for name in self._database.scenarios.list_objects()["name"] ] # Check if strategy is used in a scenario diff --git a/flood_adapt/dbs_classes/dbs_template.py b/flood_adapt/dbs_classes/dbs_template.py index f1d53a565..5aac7de4e 100644 --- a/flood_adapt/dbs_classes/dbs_template.py +++ b/flood_adapt/dbs_classes/dbs_template.py @@ -247,6 +247,24 @@ def check_higher_level_usage(self, name: str) -> list[str]: # level object. By default, return an empty list return [] + def get_database_path(self, get_input_path: bool = True) -> Path: + """Return the path to the database. + + Parameters + ---------- + get_input_path : bool + whether to return the input path or the output path + + Returns + ------- + Path + path to the database + """ + if get_input_path: + return Path(self._path) + else: + return Path(self._database.output_path / self._folder_name) + def _get_object_list(self) -> dict[Path, datetime]: """Get a dictionary with all the toml paths and last modification dates that exist in the database of the given object_type. @@ -258,11 +276,13 @@ def _get_object_list(self) -> dict[Path, datetime]: base_path = self.input_path / self._folder_name directories = list(base_path.iterdir()) paths = [Path(dir / f"{dir.name}.toml") for dir in directories] + names = [dir.name for dir in directories] last_modification_date = [ datetime.fromtimestamp(file.stat().st_mtime) for file in paths ] objects = { + "name": names, "path": paths, "last_modification_date": last_modification_date, } diff --git a/flood_adapt/dbs_controller.py b/flood_adapt/dbs_controller.py index cdc05b2d8..cd022ec28 100644 --- a/flood_adapt/dbs_controller.py +++ b/flood_adapt/dbs_controller.py @@ -47,6 +47,7 @@ class Database(IDatabase): database_name: str _init_done: bool = False + base_path: Path input_path: Path static_path: Path output_path: Path @@ -106,9 +107,11 @@ def __init__( self.database_path = database_path self.database_name = database_name - self.input_path = Path(database_path / database_name / "input") - self.static_path = Path(database_path / database_name / "static") - self.output_path = Path(database_path / database_name / "output") + # Set the paths + self.base_path = Path(database_path / database_name) + self.input_path = self.base_path / "input" + self.static_path = self.base_path / "static" + self.output_path = self.base_path / "output" self._site = Site.load_file(self.static_path / "site" / "site.toml") @@ -591,7 +594,7 @@ def plot_river( str ): # I think we need a separate function for the different timeseries when we also want to plot multiple rivers temp_event = EventFactory.get_event(event["template"]).load_dict(event) - event_dir = self.input_path.joinpath("events", temp_event.attrs.name) + event_dir = self.events.get_database_path().joinpath(temp_event.attrs.name) temp_event.add_dis_ts(event_dir, self.site.attrs.river, input_river_df) river_descriptions = [i.description for i in self.site.attrs.river] river_names = [i.description for i in self.site.attrs.river] @@ -766,14 +769,13 @@ def get_property_types(self) -> list: def write_to_csv(self, name: str, event: IEvent, df: pd.DataFrame): df.to_csv( - Path(self.input_path, "events", event.attrs.name, f"{name}.csv"), + self.events.get_database_path().joinpath(event.attrs.name, f"{name}.csv"), header=False, ) def write_cyc(self, event: IEvent, track: TropicalCyclone): cyc_file = ( - self.input_path - / "events" + self.events.get_database_path() / event.attrs.name / f"{event.attrs.track_name}.cyc" ) @@ -913,9 +915,7 @@ def get_max_water_level( """ # If single event read with hydromt-sfincs if not return_period: - map_path = self.input_path.parent.joinpath( - "output", - "Scenarios", + map_path = self.scenarios.get_database_path(get_input_path=False).joinpath( scenario_name, "Flooding", "max_water_level_map.nc", @@ -925,9 +925,7 @@ def get_max_water_level( zsmax = map.to_numpy() else: - file_path = self.input_path.parent.joinpath( - "output", - "Scenarios", + file_path = self.scenarios.get_database_path(get_input_path=False).joinpath( scenario_name, "Flooding", f"RP_{return_period:04d}_maps.nc", @@ -948,8 +946,8 @@ def get_fiat_footprints(self, scenario_name: str) -> GeoDataFrame: GeoDataFrame impacts at footprint level """ - out_path = self.input_path.parent.joinpath( - "output", "Scenarios", scenario_name, "Impacts" + out_path = self.scenarios.get_database_path(get_input_path=False).joinpath( + scenario_name, "Impacts" ) footprints = out_path / f"Impacts_building_footprints_{scenario_name}.gpkg" gdf = gpd.read_file(footprints, engine="pyogrio") @@ -969,8 +967,8 @@ def get_roads(self, scenario_name: str) -> GeoDataFrame: GeoDataFrame Impacts at roads """ - out_path = self.input_path.parent.joinpath( - "output", "Scenarios", scenario_name, "Impacts" + out_path = self.scenarios.get_database_path(get_input_path=False).joinpath( + scenario_name, "Impacts" ) roads = out_path / f"Impacts_roads_{scenario_name}.gpkg" gdf = gpd.read_file(roads, engine="pyogrio") @@ -990,8 +988,8 @@ def get_aggregation(self, scenario_name: str) -> dict[GeoDataFrame]: dict[GeoDataFrame] dictionary with aggregated damages per aggregation type """ - out_path = self.input_path.parent.joinpath( - "output", "Scenarios", scenario_name, "Impacts" + out_path = self.scenarios.get_database_path(get_input_path=False).joinpath( + scenario_name, "Impacts" ) gdfs = {} for aggr_area in out_path.glob(f"Impacts_aggregated_{scenario_name}_*.gpkg"): @@ -1013,9 +1011,7 @@ def get_aggregation_benefits(self, benefit_name: str) -> dict[GeoDataFrame]: dict[GeoDataFrame] dictionary with aggregated benefits per aggregation type """ - out_path = self.input_path.parent.joinpath( - "output", - "Benefits", + out_path = self.benefits.get_database_path(get_input_path=False).joinpath( benefit_name, ) gdfs = {} @@ -1073,12 +1069,12 @@ def has_run_hazard(self, scenario_name: str) -> None: for scn in scns_simulated: if scn.direct_impacts.hazard == scenario.direct_impacts.hazard: - path_0 = self.input_path.parent.joinpath( - "output", "Scenarios", scn.attrs.name, "Flooding" - ) - path_new = self.input_path.parent.joinpath( - "output", "Scenarios", scenario.attrs.name, "Flooding" - ) + path_0 = self.scenarios.get_database_path( + get_input_path=False + ).joinpath(scn.attrs.name, "Flooding") + path_new = self.scenarios.get_database_path( + get_input_path=False + ).joinpath(scenario.attrs.name, "Flooding") if ( scn.direct_impacts.hazard.has_run_check() ): # only copy results if the hazard model has actually finished and skip simulation folders diff --git a/flood_adapt/object_model/direct_impacts.py b/flood_adapt/object_model/direct_impacts.py index 4d72861cb..69dc5dda1 100644 --- a/flood_adapt/object_model/direct_impacts.py +++ b/flood_adapt/object_model/direct_impacts.py @@ -23,11 +23,8 @@ SocioEconomicChange, ) from flood_adapt.object_model.hazard.hazard import Hazard, ScenarioModel -from flood_adapt.object_model.projection import Projection -from flood_adapt.object_model.site import Site # from flood_adapt.object_model.scenario import ScenarioModel -from flood_adapt.object_model.strategy import Strategy from flood_adapt.object_model.utils import cd @@ -38,29 +35,22 @@ class DirectImpacts: """ name: str - database_input_path: Path socio_economic_change: SocioEconomicChange impact_strategy: ImpactStrategy hazard: Hazard has_run: bool = False - def __init__( - self, scenario: ScenarioModel, database_input_path: Path, results_path: Path - ) -> None: + def __init__(self, scenario: ScenarioModel, database, results_path: Path) -> None: self.name = scenario.name - self.database_input_path = database_input_path + self.database = database self.scenario = scenario self.results_path = results_path self.set_socio_economic_change(scenario.projection) self.set_impact_strategy(scenario.strategy) - self.set_hazard( - scenario, database_input_path, self.results_path.joinpath("Flooding") - ) + self.set_hazard(scenario, database, self.results_path.joinpath("Flooding")) # Get site config - self.site_toml_path = ( - Path(self.database_input_path).parent / "static" / "site" / "site.toml" - ) - self.site_info = Site.load_file(self.site_toml_path) + self.site_toml_path = self.database.static_path / "site" / "site.toml" + self.site_info = database.site # Define results path self.impacts_path = self.results_path.joinpath("Impacts") self.fiat_path = self.impacts_path.joinpath("fiat_model") @@ -104,11 +94,8 @@ def set_socio_economic_change(self, projection: str) -> None: projection : str Name of the projection used in the scenario """ - projection_path = ( - self.database_input_path / "Projections" / projection / f"{projection}.toml" - ) - self.socio_economic_change = Projection.load_file( - projection_path + self.socio_economic_change = self.database.projections.get( + projection ).get_socio_economic_change() def set_impact_strategy(self, strategy: str) -> None: @@ -119,14 +106,11 @@ def set_impact_strategy(self, strategy: str) -> None: strategy : str Name of the strategy used in the scenario """ - strategy_path = ( - self.database_input_path / "Strategies" / strategy / f"{strategy}.toml" - ) - self.impact_strategy = Strategy.load_file(strategy_path).get_impact_strategy() + self.impact_strategy = self.database.strategies.get( + strategy + ).get_impact_strategy() - def set_hazard( - self, scenario: ScenarioModel, database_input_path: Path, results_dir: Path - ) -> None: + def set_hazard(self, scenario: ScenarioModel, database, results_dir: Path) -> None: """Set the Hazard object of the scenario. Parameters @@ -134,7 +118,7 @@ def set_hazard( scenario : str Name of the scenario """ - self.hazard = Hazard(scenario, database_input_path, results_dir) + self.hazard = Hazard(scenario, database, results_dir) def preprocess_models(self): logging.info("Preparing impact models...") @@ -170,13 +154,11 @@ def preprocess_fiat(self): ) # Get the location of the FIAT template model - template_path = ( - self.database_input_path.parent / "static" / "templates" / "fiat" - ) + template_path = self.database.static_path / "templates" / "fiat" # Read FIAT template with FIAT adapter fa = FiatAdapter( - model_root=template_path, database_path=self.database_input_path.parent + model_root=template_path, database_path=self.database.base_path ) # If path for results does not yet exist, make it @@ -202,19 +184,13 @@ def preprocess_fiat(self): if self.socio_economic_change.attrs.population_growth_new != 0: # Get path of new development area geometry area_path = ( - self.database_input_path - / "projections" + self.database.projections.get_database_path() / self.scenario.projection / self.socio_economic_change.attrs.new_development_shapefile ) - dem = ( - self.database_input_path.parent - / "static" - / "dem" - / self.site_info.attrs.dem.filename - ) + dem = self.database.static_path / "Dem" / self.site_info.attrs.dem.filename aggregation_areas = [ - self.database_input_path.parent / "static" / "site" / aggr.file + self.database.static_path / "site" / aggr.file for aggr in self.site_info.attrs.fiat.aggregation ] attribute_names = [ @@ -511,8 +487,8 @@ def _add_exeedance_probability(self, fiat_results_path): FIAT results dataframe with exceedance probability added """ # Get config path - config_path = self.database_input_path.parent.joinpath( - "static", "templates", "infometrics", "metrics_additional_risk_configs.toml" + config_path = self.database.static_path.joinpath( + "templates", "infometrics", "metrics_additional_risk_configs.toml" ) with open(config_path, mode="rb") as fp: config = tomli.load(fp)["flood_exceedance"] @@ -539,17 +515,16 @@ def _create_infometrics(self, fiat_results_df) -> Path: else: ext = "" - metrics_config_path = self.database_input_path.parent.joinpath( - "static", + metrics_config_path = self.database.static_path.joinpath( "templates", "infometrics", f"metrics_config{ext}.toml", ) # Specify the metrics output path - metrics_outputs_path = self.database_input_path.parent.joinpath( - "output", - "Scenarios", + metrics_outputs_path = self.database.scenarios.get_database_path( + get_input_path=False + ).joinpath( self.name, f"Infometrics_{self.name}.csv", ) @@ -575,13 +550,14 @@ def _create_infographics(self, mode, metrics_path): logging.info("Creating infographics...") # Get the infographic - database_path = Path(self.database_input_path).parent - config_path = database_path.joinpath("static", "templates", "infographics") - output_path = database_path.joinpath("output", "Scenarios", self.name) InforgraphicFactory.create_infographic_file_writer( infographic_mode=mode, scenario_name=self.name, metrics_full_path=metrics_path, - config_base_path=config_path, - output_base_path=output_path, + config_base_path=self.database.static_path.joinpath( + "templates", "Infographics" + ), + output_base_path=self.database.scenarios.get_database_path( + get_input_path=False + ).joinpath(self.name), ).write_infographics_to_file() diff --git a/flood_adapt/object_model/hazard/hazard.py b/flood_adapt/object_model/hazard/hazard.py index 9324e2f43..b01416f88 100644 --- a/flood_adapt/object_model/hazard/hazard.py +++ b/flood_adapt/object_model/hazard/hazard.py @@ -35,9 +35,6 @@ UnitTypesLength, UnitTypesVelocity, ) -from flood_adapt.object_model.projection import Projection -from flood_adapt.object_model.site import Site -from flood_adapt.object_model.strategy import Strategy from flood_adapt.object_model.utils import cd @@ -56,22 +53,18 @@ class Hazard: hazard_strategy: HazardStrategy has_run: bool = False - def __init__( - self, scenario: ScenarioModel, database_input_path: Path, results_dir: Path - ) -> None: + def __init__(self, scenario: ScenarioModel, database, results_dir: Path) -> None: self._mode: Mode self.simulation_paths: List[Path] self.simulation_paths_offshore: List[Path] self.name = scenario.name self.results_dir = results_dir - self.database_input_path = database_input_path + self.database = database self.event_name = scenario.event self.set_event() # also setting the mode (single_event or risk here) self.set_hazard_strategy(scenario.strategy) self.set_physical_projection(scenario.projection) - self.site = Site.load_file( - database_input_path.parent / "static" / "site" / "site.toml" - ) + self.site = database.site self.has_run = self.has_run_check() @property @@ -85,9 +78,9 @@ def event_mode(self, mode: Mode) -> None: def set_simulation_paths(self) -> None: if self._mode == Mode.single_event: self.simulation_paths = [ - self.database_input_path.parent.joinpath( - "output", - "Scenarios", + self.database.scenarios.get_database_path( + get_input_path=False + ).joinpath( self.name, "Flooding", "simulations", @@ -96,9 +89,9 @@ def set_simulation_paths(self) -> None: ] # Create a folder name for the offshore model (will not be used if offshore model is not created) self.simulation_paths_offshore = [ - self.database_input_path.parent.joinpath( - "output", - "Scenarios", + self.database.scenarios.get_database_path( + get_input_path=False + ).joinpath( self.name, "Flooding", "simulations", @@ -110,9 +103,9 @@ def set_simulation_paths(self) -> None: self.simulation_paths_offshore = [] for subevent in self.event_list: self.simulation_paths.append( - self.database_input_path.parent.joinpath( - "output", - "Scenarios", + self.database.scenarios.get_database_path( + get_input_path=False + ).joinpath( self.name, "Flooding", "simulations", @@ -122,9 +115,9 @@ def set_simulation_paths(self) -> None: ) # Create a folder name for the offshore model (will not be used if offshore model is not created) self.simulation_paths_offshore.append( - self.database_input_path.parent.joinpath( - "output", - "Scenarios", + self.database.scenarios.get_database_path( + get_input_path=False + ).joinpath( self.name, "Flooding", "simulations", @@ -182,10 +175,9 @@ def set_event(self) -> None: event_name (str): name of event used in scenario. """ self.event_set_path = ( - self.database_input_path - / "events" + self.database.events.get_database_path() / self.event_name - / "{}.toml".format(self.event_name) + / f"{self.event_name}.toml" ) # set mode (probabilistic_set or single_event) self.event_mode = Event.get_mode(self.event_set_path) @@ -202,11 +194,10 @@ def _set_event_objects(self) -> None: for subevent in subevents: event_path = ( - self.database_input_path - / "events" + self.database.events.get_database_path() / self.event_name / subevent - / "{}.toml".format(subevent) + / f"{subevent}.toml" ) self.event_set.event_paths.append(event_path) @@ -223,32 +214,17 @@ def _set_event_objects(self) -> None: ] # set event for single_event to be able to plot wl etc def set_physical_projection(self, projection: str) -> None: - projection_path = ( - self.database_input_path / "Projections" / projection / f"{projection}.toml" - ) - self.physical_projection = Projection.load_file( - projection_path + self.physical_projection = self.database.projections.get( + projection ).get_physical_projection() def set_hazard_strategy(self, strategy: str) -> None: - strategy_path = ( - self.database_input_path / "Strategies" / strategy / f"{strategy}.toml" - ) - self.hazard_strategy = Strategy.load_file(strategy_path).get_hazard_strategy() + self.hazard_strategy = self.database.strategies.get( + strategy + ).get_hazard_strategy() # no write function is needed since this is only used internally - @staticmethod - def get_event_object(event_path): - mode = Event.get_mode(event_path) - if mode == Mode.single_event: - # parse event config file to get event template - template = Event.get_template(event_path) - # use event template to get the associated event child class - return EventFactory.get_event(template).load_file(event_path) - elif mode == Mode.risk: - return EventSet.load_file(event_path) - def preprocess_models(self): logging.info("Preparing hazard models...") # Preprocess all hazard model input @@ -343,9 +319,8 @@ def preprocess_sfincs( ): self._set_event_objects() self.set_simulation_paths() - base_path = self.database_input_path.parent - path_in = base_path.joinpath( - "static", "templates", self.site.attrs.sfincs.overland_model + path_in = self.database.static_path.joinpath( + "templates", self.site.attrs.sfincs.overland_model ) for ii, event in enumerate(self.event_list): @@ -369,11 +344,9 @@ def preprocess_sfincs( or self.event.attrs.template == "Historical_offshore" ): logging.info("Downloading meteo data...") - meteo_dir = self.database_input_path.parent.joinpath("output", "meteo") + meteo_dir = self.database.output_path.joinpath("meteo") if not meteo_dir.is_dir(): - os.mkdir( - self.database_input_path.parent.joinpath("output", "meteo") - ) + os.mkdir(self.database.output_path.joinpath("meteo")) ds = self.event.download_meteo( site=self.site, path=meteo_dir ) # =event_dir) @@ -509,8 +482,8 @@ def preprocess_sfincs( "Adding rainfall shape timeseries to the overland flood model..." ) if self.event.attrs.rainfall.shape_type == "scs": - scsfile = self.database_input_path.parent.joinpath( - "static", "scs", self.site.attrs.scs.file + scsfile = self.database.static_path.joinpath( + "scs", self.site.attrs.scs.file ) scstype = self.site.attrs.scs.type self.event.add_rainfall_ts(scsfile=scsfile, scstype=scstype) @@ -577,8 +550,8 @@ def preprocess_sfincs( # Add hazard measures if included if self.hazard_strategy.measures is not None: for measure in self.hazard_strategy.measures: - measure_path = base_path.joinpath( - "input", "measures", measure.attrs.name + measure_path = self.database.measures.get_database_path().joinpath( + measure.attrs.name ) if measure.attrs.type == "floodwall": logging.info("Adding floodwall to the overland flood model...") @@ -624,19 +597,17 @@ def preprocess_sfincs_offshore(self, ds: xr.DataArray, ii: int): ii (int): Iterator for event set """ # Determine folders for offshore model - base_path = self.database_input_path.parent - path_in_offshore = base_path.joinpath( - "static", "templates", self.site.attrs.sfincs.offshore_model + path_in_offshore = self.database.static_path.joinpath( + "templates", self.site.attrs.sfincs.offshore_model ) if self.event_mode == Mode.risk: event_dir = ( - self.database_input_path - / "events" + self.database.events.get_database_path() / self.event_set.attrs.name / self.event.attrs.name ) else: - event_dir = self.database_input_path / "events" / self.event.attrs.name + event_dir = self.database.events.get_database_path() / self.event.attrs.name # Create folders for offshore model self.simulation_paths_offshore[ii].mkdir(parents=True, exist_ok=True) @@ -682,7 +653,7 @@ def preprocess_sfincs_offshore(self, ds: xr.DataArray, ii: int): ) offshore_model.add_spw_forcing( historical_hurricane=self.event, - database_path=base_path, + database_path=self.database.base_path, model_dir=self.simulation_paths_offshore[ii], ) # save created spw file in the event directory @@ -822,8 +793,8 @@ def plot_wl_obs(self): or self.site.attrs.obs_point[ii].file is not None ): if self.site.attrs.obs_point[ii].file is not None: - file = self.database_input_path.parent.joinpath( - "static", self.site.attrs.obs_point[ii].file + file = self.database.static_path.joinpath( + self.site.attrs.obs_point[ii].file ) else: file = None @@ -868,8 +839,8 @@ def write_floodmap_geotiff(self): # read SFINCS model model = SfincsAdapter(model_root=sim_path, site=self.site) # dem file for high resolution flood depth map - demfile = self.database_input_path.parent.joinpath( - "static", "dem", self.site.attrs.dem.filename + demfile = self.database.static_path.joinpath( + "dem", self.site.attrs.dem.filename ) # read max. water level @@ -1015,8 +986,8 @@ def calculate_rp_floodmaps(self): # write geotiff # dem file for high resolution flood depth map - demfile = self.database_input_path.parent.joinpath( - "static", "dem", self.site.attrs.dem.filename + demfile = self.database.static_path.joinpath( + "dem", self.site.attrs.dem.filename ) # writing the geotiff to the scenario results folder dummymodel = SfincsAdapter( diff --git a/flood_adapt/object_model/scenario.py b/flood_adapt/object_model/scenario.py index d70e482c3..0281981f1 100644 --- a/flood_adapt/object_model/scenario.py +++ b/flood_adapt/object_model/scenario.py @@ -10,7 +10,6 @@ from flood_adapt.object_model.direct_impacts import DirectImpacts from flood_adapt.object_model.hazard.hazard import ScenarioModel from flood_adapt.object_model.interface.scenarios import IScenario -from flood_adapt.object_model.site import Site class Scenario(IScenario): @@ -20,17 +19,20 @@ class Scenario(IScenario): direct_impacts: DirectImpacts database_input_path: Union[str, os.PathLike] - def init_object_model(self): + def init_object_model(self) -> "Scenario": """Create a Direct Impact object.""" - self.site_info = Site.load_file( - Path(self.database_input_path).parent / "static" / "site" / "site.toml" - ) - self.results_path = Path(self.database_input_path).parent.joinpath( - "output", "Scenarios", self.attrs.name + from flood_adapt.dbs_controller import ( + Database, # TODO: Fix circular import and move to top of file. There is too much entanglement between classes to fix this now ) + + database = Database() + self.site_info = database.site + self.results_path = database.scenarios.get_database_path( + get_input_path=False + ).joinpath(self.attrs.name) self.direct_impacts = DirectImpacts( scenario=self.attrs, - database_input_path=Path(self.database_input_path), + database=database, results_path=self.results_path, ) return self From dd2810c1de44b8456c617d6d16635aa32472c8c1 Mon Sep 17 00:00:00 2001 From: LuukBlom <153174893+LuukBlom@users.noreply.github.com> Date: Mon, 17 Jun 2024 09:10:09 +0200 Subject: [PATCH 4/6] =?UTF-8?q?chore:=20Remove=20duplicate=20functions=20i?= =?UTF-8?q?n=20dbs=5Fcontroller=20&=20Update=20pre-comm=E2=80=A6=20(#459)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: Remove duplicate functions in dbs_controller & Update pre-commit yaml to use the same versions as FloodAdapt-GUI * Add back function that shouldnt have been removed * fix aggregation function * removed function that is in api/benefit * remove test that called deleted function * re-implement get_aggregated_damages * delete temporary fiatmodels when they are no longer needed --- .pre-commit-config.yaml | 4 +- flood_adapt/api/output.py | 4 +- flood_adapt/dbs_controller.py | 184 +----------------- .../direct_impact/measure/impact_measure.py | 1 + tests/test_api/test_output.py | 4 +- 5 files changed, 9 insertions(+), 188 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b205f339f..b993d070f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,11 +16,11 @@ repos: hooks: - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.277 + rev: v0.4.8 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/crate-ci/typos - rev: v1.15.10 + rev: v1.22.0 hooks: - id: typos diff --git a/flood_adapt/api/output.py b/flood_adapt/api/output.py index fd2baf93d..cb3820731 100644 --- a/flood_adapt/api/output.py +++ b/flood_adapt/api/output.py @@ -34,8 +34,8 @@ def get_fiat_footprints(name: str) -> gpd.GeoDataFrame: return Database().get_fiat_footprints(name) -def get_aggregation(name: str) -> dict[gpd.GeoDataFrame]: - return Database().get_aggregation(name) +def get_aggregated_damages(name: str) -> dict[gpd.GeoDataFrame]: + return Database().get_aggregated_damages(name) def get_roads(name: str) -> gpd.GeoDataFrame: diff --git a/flood_adapt/dbs_controller.py b/flood_adapt/dbs_controller.py index cd022ec28..7313a8a57 100644 --- a/flood_adapt/dbs_controller.py +++ b/flood_adapt/dbs_controller.py @@ -13,8 +13,6 @@ import xarray as xr from cht_cyclones.tropical_cyclone import TropicalCyclone from geopandas import GeoDataFrame -from hydromt_fiat.fiat import FiatModel -from hydromt_sfincs.quadtree import QuadtreeGrid from flood_adapt.dbs_classes.dbs_benefit import DbsBenefit from flood_adapt.dbs_classes.dbs_event import DbsEvent @@ -87,7 +85,7 @@ def __init__( if database_path is None or database_name is None: if not self._init_done: raise ValueError( - """Database path and name must be provided for the first initialization. + """Database path and name must be provided for the first initialization. To do this, run api_static.read_database(database_path, site_name) first.""" ) else: @@ -167,137 +165,6 @@ def projections(self) -> DbsProjection: def benefits(self) -> DbsBenefit: return self._benefits - # General methods - def get_aggregation_areas(self) -> dict: - """Get a list of the aggregation areas that are provided in the site configuration. - - These are expected to much the ones in the FIAT model. - - Returns - ------- - list[GeoDataFrame] - list of geodataframes with the polygons defining the aggregation areas - """ - aggregation_areas = {} - for aggr_dict in self.site.attrs.fiat.aggregation: - aggregation_areas[aggr_dict.name] = gpd.read_file( - self.input_path.parent / "static" / "site" / aggr_dict.file, - engine="pyogrio", - ).to_crs(4326) - # Use always the same column name for name labels - aggregation_areas[aggr_dict.name] = aggregation_areas[ - aggr_dict.name - ].rename(columns={aggr_dict.field_name: "name"}) - # Make sure they are ordered alphabetically - aggregation_areas[aggr_dict.name].sort_values(by="name").reset_index( - drop=True - ) - - return aggregation_areas - - def get_model_boundary(self) -> GeoDataFrame: - """Get the model boundary from the SFINCS model.""" - bnd = self.static_sfincs_model.get_model_boundary() - return bnd - - def get_model_grid(self) -> QuadtreeGrid: - """Get the model grid from the SFINCS model. - - Returns - ------- - QuadtreeGrid - The model grid - """ - grid = self.static_sfincs_model.get_model_grid() - return grid - - def get_obs_points(self) -> GeoDataFrame: - """Get the observation points from the flood hazard model.""" - if self.site.attrs.obs_point is not None: - obs_points = self.site.attrs.obs_point - names = [] - descriptions = [] - lat = [] - lon = [] - for pt in obs_points: - names.append(pt.name) - descriptions.append(pt.description) - lat.append(pt.lat) - lon.append(pt.lon) - - # create GeoDataFrame from obs_points in site file - df = pd.DataFrame({"name": names, "description": descriptions}) - # TODO: make crs flexible and add this as a parameter to site.toml? - gdf = gpd.GeoDataFrame( - df, geometry=gpd.points_from_xy(lon, lat), crs="EPSG:4326" - ) - return gdf - - def get_static_map(self, path: Union[str, Path]) -> gpd.GeoDataFrame: - """Get a map from the static folder. - - Parameters - ---------- - path : Union[str, Path] - Path to the map relative to the static folder - - Returns - ------- - gpd.GeoDataFrame - GeoDataFrame with the map in crs 4326 - - Raises - ------ - FileNotFoundError - If the file is not found - """ - # Read the map - full_path = self.static_path / path - if full_path.is_file(): - return gpd.read_file(full_path, engine="pyogrio").to_crs(4326) - - # If the file is not found, throw an error - raise FileNotFoundError(f"File {full_path} not found") - - def get_slr_scn_names(self) -> list: - input_file = self.input_path.parent.joinpath("static", "slr", "slr.csv") - df = pd.read_csv(input_file) - return df.columns[2:].to_list() - - def get_green_infra_table(self, measure_type: str) -> pd.DataFrame: - """Return a table with different types of green infrastructure measures and their infiltration depths. - - This is read by a csv file in the database. - - Returns - ------- - pd.DataFrame - Table with values - """ - # Read file from database - df = pd.read_csv( - self.input_path.parent.joinpath( - "static", "green_infra_table", "green_infra_lookup_table.csv" - ) - ) - - # Get column with values - val_name = "Infiltration depth" - col_name = [name for name in df.columns if val_name in name][0] - if not col_name: - raise KeyError(f"A column with a name containing {val_name} was not found!") - - # Get list of types per measure - df["types"] = [ - [x.strip() for x in row["types"].split(",")] for i, row in df.iterrows() - ] - - # Show specific values based on measure type - inds = [i for i, row in df.iterrows() if measure_type in row["types"]] - df = df.drop(columns="types").iloc[inds, :] - - return df - def interp_slr(self, slr_scenario: str, year: float) -> float: r"""Interpolate SLR value and reference it to the SLR reference year from the site toml. @@ -720,53 +587,6 @@ def plot_wind( ) return str("") - def get_buildings(self) -> GeoDataFrame: - """Get the building footprints from the FIAT model. - - This should only be the buildings excluding any other types (e.g., roads) - The parameters non_building_names in the site config is used for that. - - Returns - ------- - GeoDataFrame - building footprints with all the FIAT columns - """ - # use hydromt-fiat to load the fiat model - fm = FiatModel( - root=self.input_path.parent / "static" / "templates" / "fiat", - mode="r", - ) - fm.read() - buildings = fm.exposure.select_objects( - primary_object_type="ALL", - non_building_names=self.site.attrs.fiat.non_building_names, - return_gdf=True, - ) - - return buildings - - def get_property_types(self) -> list: - """_summary_. - - Returns - ------- - list - _description_ - """ - # use hydromt-fiat to load the fiat model - fm = FiatModel( - root=self.input_path.parent / "static" / "templates" / "fiat", - mode="r", - ) - fm.read() - types = fm.exposure.get_primary_object_type() - for name in self.site.attrs.fiat.non_building_names: - if name in types: - types.remove(name) - # Add "all" type for using as identifier - types.append("all") - return types - def write_to_csv(self, name: str, event: IEvent, df: pd.DataFrame): df.to_csv( self.events.get_database_path().joinpath(event.attrs.name, f"{name}.csv"), @@ -975,7 +795,7 @@ def get_roads(self, scenario_name: str) -> GeoDataFrame: gdf = gdf.to_crs(4326) return gdf - def get_aggregation(self, scenario_name: str) -> dict[GeoDataFrame]: + def get_aggregated_damages(self, scenario_name: str) -> dict[GeoDataFrame]: """Get a dictionary with the aggregated damages as geodataframes. Parameters diff --git a/flood_adapt/object_model/direct_impact/measure/impact_measure.py b/flood_adapt/object_model/direct_impact/measure/impact_measure.py index 98b13b00f..6a49b1e71 100644 --- a/flood_adapt/object_model/direct_impact/measure/impact_measure.py +++ b/flood_adapt/object_model/direct_impact/measure/impact_measure.py @@ -60,4 +60,5 @@ def get_object_ids(self, fiat_model: Optional[FiatModel] = None) -> list[Any]: polygon_file=polygon_file, ) + del fiat_model return ids diff --git a/tests/test_api/test_output.py b/tests/test_api/test_output.py index accb3d68f..f388a4270 100644 --- a/tests/test_api/test_output.py +++ b/tests/test_api/test_output.py @@ -25,7 +25,7 @@ def test_impact_footprints(scenario_event): assert isinstance(footprints, gpd.GeoDataFrame) -def test_impact_aggr_areas(scenario_event): +def test_impact_aggr_damages(scenario_event): _, scenario_name = scenario_event - aggr_areas = api_output.get_aggregation(scenario_name) + aggr_areas = api_output.get_aggregated_damages(scenario_name) assert isinstance(aggr_areas, dict) From 029f26c25c9b6f5e2264aa52a14b4ff72ad6a718 Mon Sep 17 00:00:00 2001 From: LuukBlom <153174893+LuukBlom@users.noreply.github.com> Date: Mon, 17 Jun 2024 11:12:20 +0200 Subject: [PATCH 5/6] Fix numpy version to 1.x --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 73f3023e6..d75984b67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ dependencies = [ "geopandas", "hydromt-fiat@ git+https://github.com/deltares/hydromt_fiat.git@floodadapt", "hydromt-sfincs@ git+https://github.com/deltares/hydromt_sfincs.git@FA_quadtree", - "numpy", + "numpy < 2.0", "numpy-financial", "pandas", "plotly", From f873cd38f612a2c6df14303213c5ed2c596a8134 Mon Sep 17 00:00:00 2001 From: dladrichem <136334482+dladrichem@users.noreply.github.com> Date: Tue, 18 Jun 2024 11:08:27 +0200 Subject: [PATCH 6/6] Remove relative path in site toml (#464) * First setup * Black * Missed one in benefits --- flood_adapt/dbs_classes/dbs_scenario.py | 5 ++--- flood_adapt/dbs_classes/dbs_static.py | 2 +- flood_adapt/integrator/fiat_adapter.py | 7 ++----- flood_adapt/integrator/sfincs_adapter.py | 2 +- flood_adapt/object_model/benefit.py | 4 ++-- flood_adapt/object_model/direct_impacts.py | 8 ++++---- tests/test_object_model/test_site.py | 16 ++++++++-------- 7 files changed, 20 insertions(+), 24 deletions(-) diff --git a/flood_adapt/dbs_classes/dbs_scenario.py b/flood_adapt/dbs_classes/dbs_scenario.py index a02ed23ed..12b2cb501 100644 --- a/flood_adapt/dbs_classes/dbs_scenario.py +++ b/flood_adapt/dbs_classes/dbs_scenario.py @@ -2,7 +2,6 @@ from typing import Any from flood_adapt.dbs_classes.dbs_template import DbsTemplate -from flood_adapt.object_model.benefit import Benefit from flood_adapt.object_model.interface.scenarios import IScenario from flood_adapt.object_model.scenario import Scenario @@ -110,8 +109,8 @@ def check_higher_level_usage(self, name: str) -> list[str]: """ # Get all the benefits benefits = [ - Benefit.load_file(path) - for path in self._database.benefits.list_objects()["path"] + self._database.benefits.get(name) + for name in self._database.benefits.list_objects()["name"] ] # Check in which benefits this scenario is used diff --git a/flood_adapt/dbs_classes/dbs_static.py b/flood_adapt/dbs_classes/dbs_static.py index 6347d5d8a..6b2accbde 100644 --- a/flood_adapt/dbs_classes/dbs_static.py +++ b/flood_adapt/dbs_classes/dbs_static.py @@ -52,7 +52,7 @@ def get_aggregation_areas(self) -> dict: aggregation_areas = {} for aggr_dict in self._database.site.attrs.fiat.aggregation: aggregation_areas[aggr_dict.name] = gpd.read_file( - self._database.static_path / "site" / aggr_dict.file, + self._database.static_path / aggr_dict.file, engine="pyogrio", ).to_crs(4326) # Use always the same column name for name labels diff --git a/flood_adapt/integrator/fiat_adapter.py b/flood_adapt/integrator/fiat_adapter.py index 804c5bac4..079a875ee 100644 --- a/flood_adapt/integrator/fiat_adapter.py +++ b/flood_adapt/integrator/fiat_adapter.py @@ -40,16 +40,13 @@ def __init__(self, model_root: str, database_path: str) -> None: if self.site.attrs.fiat.bfe.table: self.bfe["mode"] = "table" self.bfe["table"] = ( - Path(database_path) - / "static" - / "site" - / self.site.attrs.fiat.bfe.table + Path(database_path) / "static" / self.site.attrs.fiat.bfe.table ) else: self.bfe["mode"] = "geom" # Map is always needed! self.bfe["geom"] = ( - Path(database_path) / "static" / "site" / self.site.attrs.fiat.bfe.geom + Path(database_path) / "static" / self.site.attrs.fiat.bfe.geom ) self.bfe["name"] = self.site.attrs.fiat.bfe.field_name diff --git a/flood_adapt/integrator/sfincs_adapter.py b/flood_adapt/integrator/sfincs_adapter.py index 5042e68d5..abbf7fc0e 100644 --- a/flood_adapt/integrator/sfincs_adapter.py +++ b/flood_adapt/integrator/sfincs_adapter.py @@ -342,7 +342,7 @@ def add_green_infrastructure( continue # load geodataframe aggr_areas = gpd.read_file( - measure_path.parents[2] / "static" / "site" / aggr_dict.file, + measure_path.parents[2] / "static" / aggr_dict.file, engine="pyogrio", ).to_crs(4326) # keep only aggregation area chosen diff --git a/flood_adapt/object_model/benefit.py b/flood_adapt/object_model/benefit.py index e4bc55ca5..0143ea0c2 100644 --- a/flood_adapt/object_model/benefit.py +++ b/flood_adapt/object_model/benefit.py @@ -372,8 +372,8 @@ def cba_aggregation(self): for i, n in enumerate(self.site_info.attrs.fiat.aggregation) if n.name == aggr_name ][0] - aggr_areas_path = self.site_toml_path.parent.joinpath( - self.site_info.attrs.fiat.aggregation[ind].file + aggr_areas_path = self.database_input_path.parent.joinpath( + "static", self.site_info.attrs.fiat.aggregation[ind].file ) aggr_areas = gpd.read_file(aggr_areas_path, engine="pyogrio") diff --git a/flood_adapt/object_model/direct_impacts.py b/flood_adapt/object_model/direct_impacts.py index 69dc5dda1..43dee4279 100644 --- a/flood_adapt/object_model/direct_impacts.py +++ b/flood_adapt/object_model/direct_impacts.py @@ -190,7 +190,7 @@ def preprocess_fiat(self): ) dem = self.database.static_path / "Dem" / self.site_info.attrs.dem.filename aggregation_areas = [ - self.database.static_path / "site" / aggr.file + self.database.static_path / aggr.file for aggr in self.site_info.attrs.fiat.aggregation ] attribute_names = [ @@ -357,7 +357,7 @@ def _create_equity(self, metrics_path): # Create Equity object equity = Equity( - census_table=self.site_toml_path.parent.joinpath( + census_table=self.database.static_path.joinpath( self.site_info.attrs.fiat.aggregation[ind].equity.census_data ), damages_table=fiat_data, @@ -420,7 +420,7 @@ def _create_aggregation(self, metrics_path): for i, n in enumerate(self.site_info.attrs.fiat.aggregation) if n.name == aggr_label ][0] - aggr_areas_path = self.site_toml_path.parent.joinpath( + aggr_areas_path = self.database.static_path.joinpath( self.site_info.attrs.fiat.aggregation[ind].file ) @@ -447,7 +447,7 @@ def _create_footprints(self, fiat_results_df): if not self.site_info.attrs.fiat.building_footprints: raise ValueError("No building footprints are provided.") # Get footprints file - footprints_path = self.site_toml_path.parent.joinpath( + footprints_path = self.database.static_path.joinpath( self.site_info.attrs.fiat.building_footprints ) # Define where footprint results are saved diff --git a/tests/test_object_model/test_site.py b/tests/test_object_model/test_site.py index 5e413b239..1f3ad9322 100644 --- a/tests/test_object_model/test_site.py +++ b/tests/test_object_model/test_site.py @@ -111,36 +111,36 @@ def test_dict(): "floodmap_type": "water_level", "non_building_names": ["road"], "damage_unit": "$", - "building_footprints": "../templates/fiat/footprints/Buildings.shp", + "building_footprints": "templates/fiat/footprints/Buildings.shp", "roads_file_name": "spatial2.gpkg", "new_development_file_name": "spatial3.gpkg", "save_simulation": "False", "svi": { - "geom": "../templates/fiat/svi/CDC_svi_2020.gpkg", + "geom": "templates/fiat/svi/CDC_svi_2020.gpkg", "field_name": "SVI", }, "bfe": { - "geom": "../bfe/bfe.geojson", - "table": "../bfe/bfe.csv", + "geom": "bfe/bfe.geojson", + "table": "bfe/bfe.csv", "field_name": "bfe", }, "aggregation": [ { "name": "aggr_lvl_1", - "file": "../templates/fiat/aggregation_areas/aggr_lvl_1.geojson", + "file": "templates/fiat/aggregation_areas/aggr_lvl_1.geojson", "field_name": "name", "equity": { - "census_data": "../templates/fiat/equity/census_data_aggr_lvl_1.csv", + "census_data": "templates/fiat/equity/census_data_aggr_lvl_1.csv", "percapitaincome_label": "PerCapitaIncome", "totalpopulation_label": "TotalPopulation", }, }, { "name": "aggr_lvl_2", - "file": "../templates/fiat/aggregation_areas/aggr_lvl_2.geojson", + "file": "templates/fiat/aggregation_areas/aggr_lvl_2.geojson", "field_name": "name", "equity": { - "census_data": "../templates/fiat/equity/census_data_aggr_lvl_2.csv", + "census_data": "templates/fiat/equity/census_data_aggr_lvl_2.csv", "percapitaincome_label": "PerCapitaIncome", "totalpopulation_label": "TotalPopulation", },