flood-adapt 1.0.0__py3-none-any.whl → 1.0.1__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 +1 -1
- flood_adapt/adapter/__init__.py +0 -2
- flood_adapt/adapter/fiat_adapter.py +51 -51
- flood_adapt/adapter/interface/impact_adapter.py +1 -1
- flood_adapt/adapter/sfincs_adapter.py +69 -71
- flood_adapt/adapter/sfincs_offshore.py +5 -6
- flood_adapt/database_builder/database_builder.py +37 -5
- flood_adapt/dbs_classes/database.py +85 -21
- flood_adapt/dbs_classes/dbs_static.py +33 -9
- flood_adapt/dbs_classes/interface/static.py +8 -0
- flood_adapt/flood_adapt.py +28 -24
- flood_adapt/objects/forcing/forcing.py +1 -4
- flood_adapt/objects/forcing/tide_gauge.py +6 -7
- flood_adapt/objects/forcing/timeseries.py +0 -9
- flood_adapt/objects/output/floodmap.py +13 -0
- flood_adapt/workflows/scenario_runner.py +57 -31
- {flood_adapt-1.0.0.dist-info → flood_adapt-1.0.1.dist-info}/LICENSE +69 -46
- {flood_adapt-1.0.0.dist-info → flood_adapt-1.0.1.dist-info}/METADATA +70 -47
- {flood_adapt-1.0.0.dist-info → flood_adapt-1.0.1.dist-info}/RECORD +21 -22
- flood_adapt/workflows/floodmap.py +0 -85
- flood_adapt/workflows/impacts_integrator.py +0 -85
- {flood_adapt-1.0.0.dist-info → flood_adapt-1.0.1.dist-info}/WHEEL +0 -0
- {flood_adapt-1.0.0.dist-info → flood_adapt-1.0.1.dist-info}/top_level.txt +0 -0
|
@@ -88,6 +88,8 @@ from flood_adapt.objects.projections.projections import (
|
|
|
88
88
|
)
|
|
89
89
|
from flood_adapt.objects.scenarios.scenarios import Scenario
|
|
90
90
|
|
|
91
|
+
logger = FloodAdaptLogging.getLogger("SfincsAdapter")
|
|
92
|
+
|
|
91
93
|
|
|
92
94
|
class SfincsAdapter(IHazardAdapter):
|
|
93
95
|
"""Adapter for the SFINCS model.
|
|
@@ -100,7 +102,6 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
100
102
|
The settings for the SFINCS model.
|
|
101
103
|
"""
|
|
102
104
|
|
|
103
|
-
logger = FloodAdaptLogging.getLogger("SfincsAdapter")
|
|
104
105
|
_site: Site
|
|
105
106
|
_model: HydromtSfincsModel
|
|
106
107
|
|
|
@@ -119,9 +120,10 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
119
120
|
"""
|
|
120
121
|
self.settings = self.database.site.sfincs
|
|
121
122
|
self.units = self.database.site.gui.units
|
|
122
|
-
self.sfincs_logger = self._setup_sfincs_logger(model_root)
|
|
123
123
|
self._model = HydromtSfincsModel(
|
|
124
|
-
root=str(model_root.resolve()),
|
|
124
|
+
root=str(model_root.resolve()),
|
|
125
|
+
mode="r",
|
|
126
|
+
logger=self._setup_sfincs_logger(model_root),
|
|
125
127
|
)
|
|
126
128
|
self._model.read()
|
|
127
129
|
|
|
@@ -150,12 +152,12 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
150
152
|
|
|
151
153
|
def close_files(self):
|
|
152
154
|
"""Close all open files and clean up file handles."""
|
|
153
|
-
for
|
|
154
|
-
if hasattr(
|
|
155
|
-
for handler in
|
|
155
|
+
for _logger in [logger, self.sfincs_logger]:
|
|
156
|
+
if hasattr(_logger, "handlers"):
|
|
157
|
+
for handler in _logger.handlers:
|
|
156
158
|
if isinstance(handler, logging.FileHandler):
|
|
157
159
|
handler.close()
|
|
158
|
-
|
|
160
|
+
_logger.removeHandler(handler)
|
|
159
161
|
|
|
160
162
|
def __enter__(self) -> "SfincsAdapter":
|
|
161
163
|
return self
|
|
@@ -172,10 +174,18 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
172
174
|
self._get_simulation_path(scenario, sub_event=sub_event)
|
|
173
175
|
for sub_event in event.sub_events
|
|
174
176
|
]
|
|
175
|
-
|
|
176
|
-
|
|
177
|
+
if not all(sim_path.exists() for sim_path in sim_paths):
|
|
178
|
+
# simpaths dont exist, so check if the output files are still there
|
|
179
|
+
return self.run_completed(scenario)
|
|
180
|
+
else:
|
|
181
|
+
return all(self.sfincs_completed(sim_path) for sim_path in sim_paths)
|
|
177
182
|
else:
|
|
178
|
-
|
|
183
|
+
if not self._get_simulation_path(scenario).exists():
|
|
184
|
+
# simpath doesnt exist, so check if the output files are still there
|
|
185
|
+
return self.run_completed(scenario)
|
|
186
|
+
else:
|
|
187
|
+
# Check if the simulation folder exists
|
|
188
|
+
return self.sfincs_completed(self._get_simulation_path(scenario))
|
|
179
189
|
|
|
180
190
|
def execute(self, path: Path, strict: bool = True) -> bool:
|
|
181
191
|
"""
|
|
@@ -198,7 +208,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
198
208
|
|
|
199
209
|
"""
|
|
200
210
|
with cd(path):
|
|
201
|
-
|
|
211
|
+
logger.info(f"Running SFINCS in {path}")
|
|
202
212
|
process = subprocess.run(
|
|
203
213
|
str(Settings().sfincs_bin_path),
|
|
204
214
|
stdout=subprocess.PIPE,
|
|
@@ -206,7 +216,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
206
216
|
text=True,
|
|
207
217
|
)
|
|
208
218
|
self.sfincs_logger.info(process.stdout)
|
|
209
|
-
|
|
219
|
+
logger.debug(process.stdout)
|
|
210
220
|
|
|
211
221
|
self._cleanup_simulation_folder(path)
|
|
212
222
|
|
|
@@ -224,7 +234,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
224
234
|
if strict:
|
|
225
235
|
raise RuntimeError(f"SFINCS model failed to run in {path}.")
|
|
226
236
|
else:
|
|
227
|
-
|
|
237
|
+
logger.error(f"SFINCS model failed to run in {path}.")
|
|
228
238
|
|
|
229
239
|
return process.returncode == 0
|
|
230
240
|
|
|
@@ -261,7 +271,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
261
271
|
with SfincsAdapter(model_root=template_path) as model:
|
|
262
272
|
model._load_scenario_objects(scenario, event)
|
|
263
273
|
is_risk = "Probabilistic " if model._event_set is not None else ""
|
|
264
|
-
|
|
274
|
+
logger.info(
|
|
265
275
|
f"Preprocessing Scenario `{model._scenario.name}`: {is_risk}Event `{model._event.name}`, Strategy `{model._strategy.name}`, Projection `{model._projection.name}`"
|
|
266
276
|
)
|
|
267
277
|
# Write template model to output path and set it as the model root so focings can write to it
|
|
@@ -272,12 +282,11 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
272
282
|
for forcing in model._event.get_forcings():
|
|
273
283
|
model.add_forcing(forcing)
|
|
274
284
|
|
|
275
|
-
if
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
model.logger.warning(
|
|
279
|
-
"Failed to add event rainfall multiplier, no rainfall forcing found in the model."
|
|
285
|
+
if model.rainfall is not None:
|
|
286
|
+
logger.info(
|
|
287
|
+
f"Adding event's rainfall multiplier: {model._event.rainfall_multiplier}"
|
|
280
288
|
)
|
|
289
|
+
model.rainfall *= model._event.rainfall_multiplier
|
|
281
290
|
|
|
282
291
|
# Measures
|
|
283
292
|
for measure in model._strategy.get_hazard_measures():
|
|
@@ -297,14 +306,14 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
297
306
|
raise ValueError(f"Unsupported event mode: {event.mode}.")
|
|
298
307
|
|
|
299
308
|
sim_path = self._get_simulation_path(scenario=scenario, sub_event=event)
|
|
300
|
-
|
|
309
|
+
logger.info(f"Running SFINCS for single event Scenario `{scenario.name}`")
|
|
301
310
|
self.execute(sim_path)
|
|
302
311
|
|
|
303
312
|
def postprocess(self, scenario: Scenario, event: Event):
|
|
304
313
|
if event.mode != Mode.single_event:
|
|
305
314
|
raise ValueError(f"Unsupported event mode: {event.mode}.")
|
|
306
315
|
|
|
307
|
-
|
|
316
|
+
logger.info(f"Postprocessing SFINCS for Scenario `{scenario.name}`")
|
|
308
317
|
if not self.sfincs_completed(
|
|
309
318
|
self._get_simulation_path(scenario, sub_event=event)
|
|
310
319
|
):
|
|
@@ -316,7 +325,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
316
325
|
|
|
317
326
|
def set_timing(self, time: TimeFrame):
|
|
318
327
|
"""Set model reference times."""
|
|
319
|
-
|
|
328
|
+
logger.info(f"Setting timing for the SFINCS model: `{time}`")
|
|
320
329
|
self._model.set_config("tref", time.start_time)
|
|
321
330
|
self._model.set_config("tstart", time.start_time)
|
|
322
331
|
self._model.set_config("tstop", time.end_time)
|
|
@@ -326,7 +335,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
326
335
|
if forcing is None:
|
|
327
336
|
return
|
|
328
337
|
|
|
329
|
-
|
|
338
|
+
logger.info(
|
|
330
339
|
f"Adding {forcing.type.capitalize()}: {forcing.source.capitalize()}"
|
|
331
340
|
)
|
|
332
341
|
if isinstance(forcing, IRainfall):
|
|
@@ -338,13 +347,13 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
338
347
|
elif isinstance(forcing, IWaterlevel):
|
|
339
348
|
self._add_forcing_waterlevels(forcing)
|
|
340
349
|
else:
|
|
341
|
-
|
|
350
|
+
logger.warning(
|
|
342
351
|
f"Skipping unsupported forcing type {forcing.__class__.__name__}"
|
|
343
352
|
)
|
|
344
353
|
|
|
345
354
|
def add_measure(self, measure: Measure):
|
|
346
355
|
"""Get measure data and add it."""
|
|
347
|
-
|
|
356
|
+
logger.info(
|
|
348
357
|
f"Adding {measure.__class__.__name__.capitalize()} `{measure.name}`"
|
|
349
358
|
)
|
|
350
359
|
|
|
@@ -355,38 +364,30 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
355
364
|
elif isinstance(measure, Pump):
|
|
356
365
|
self._add_measure_pump(measure)
|
|
357
366
|
else:
|
|
358
|
-
|
|
367
|
+
logger.warning(
|
|
359
368
|
f"Skipping unsupported measure type {measure.__class__.__name__}"
|
|
360
369
|
)
|
|
361
370
|
|
|
362
371
|
def add_projection(self, projection: Projection):
|
|
363
372
|
"""Get forcing data currently in the sfincs model and add the projection it."""
|
|
364
|
-
|
|
373
|
+
logger.info(f"Adding Projection `{projection.name}`")
|
|
365
374
|
phys_projection = projection.physical_projection
|
|
366
375
|
|
|
367
376
|
if phys_projection.sea_level_rise:
|
|
368
|
-
self.logger.info(
|
|
369
|
-
f"Adding projected sea level rise `{phys_projection.sea_level_rise}`"
|
|
370
|
-
)
|
|
371
377
|
if self.waterlevels is not None:
|
|
378
|
+
logger.info(
|
|
379
|
+
f"Adding projected sea level rise `{phys_projection.sea_level_rise}`"
|
|
380
|
+
)
|
|
372
381
|
self.waterlevels += phys_projection.sea_level_rise.convert(
|
|
373
382
|
us.UnitTypesLength.meters
|
|
374
383
|
)
|
|
375
|
-
else:
|
|
376
|
-
self.logger.warning(
|
|
377
|
-
"Failed to add sea level rise, no water level forcing found in the model."
|
|
378
|
-
)
|
|
379
384
|
|
|
380
385
|
if phys_projection.rainfall_multiplier:
|
|
381
|
-
self.logger.info(
|
|
382
|
-
f"Adding projected rainfall multiplier `{phys_projection.rainfall_multiplier}`"
|
|
383
|
-
)
|
|
384
386
|
if self.rainfall is not None:
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
self.logger.warning(
|
|
388
|
-
"Failed to add projected rainfall multiplier, no rainfall forcing found in the model."
|
|
387
|
+
logger.info(
|
|
388
|
+
f"Adding projected rainfall multiplier `{phys_projection.rainfall_multiplier}`"
|
|
389
389
|
)
|
|
390
|
+
self.rainfall *= phys_projection.rainfall_multiplier
|
|
390
391
|
|
|
391
392
|
### GETTERS ###
|
|
392
393
|
def get_model_time(self) -> TimeFrame:
|
|
@@ -554,7 +555,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
554
555
|
sim_path : Path, optional
|
|
555
556
|
Path to the simulation folder, by default None.
|
|
556
557
|
"""
|
|
557
|
-
|
|
558
|
+
logger.info("Writing flood maps to geotiff")
|
|
558
559
|
results_path = self._get_result_path(scenario)
|
|
559
560
|
sim_path = sim_path or self._get_simulation_path(scenario)
|
|
560
561
|
demfile = self.database.static_path / "dem" / self.settings.dem.filename
|
|
@@ -587,7 +588,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
587
588
|
self, scenario: Scenario, sim_path: Optional[Path] = None
|
|
588
589
|
):
|
|
589
590
|
"""Read simulation results from SFINCS and saves a netcdf with the maximum water levels."""
|
|
590
|
-
|
|
591
|
+
logger.info("Writing water level map to netcdf")
|
|
591
592
|
results_path = self._get_result_path(scenario)
|
|
592
593
|
sim_path = sim_path or self._get_simulation_path(scenario)
|
|
593
594
|
|
|
@@ -604,10 +605,10 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
604
605
|
Only for single event scenarios, or for a specific simulation path containing the written and processed sfincs model.
|
|
605
606
|
"""
|
|
606
607
|
if not self.settings.obs_point:
|
|
607
|
-
|
|
608
|
+
logger.warning("No observation points provided in config.")
|
|
608
609
|
return
|
|
609
610
|
|
|
610
|
-
|
|
611
|
+
logger.info("Plotting water levels at observation points")
|
|
611
612
|
sim_path = self._get_simulation_path(scenario)
|
|
612
613
|
|
|
613
614
|
# read SFINCS model
|
|
@@ -690,7 +691,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
690
691
|
"""Add observation points provided in the site toml to SFINCS model."""
|
|
691
692
|
if self.settings.obs_point is None:
|
|
692
693
|
return
|
|
693
|
-
|
|
694
|
+
logger.info("Adding observation points to the overland flood model")
|
|
694
695
|
|
|
695
696
|
obs_points = self.settings.obs_point
|
|
696
697
|
names = []
|
|
@@ -718,7 +719,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
718
719
|
wl_df: pd.DataFrame
|
|
719
720
|
time series of water level.
|
|
720
721
|
"""
|
|
721
|
-
|
|
722
|
+
logger.info("Reading water levels from offshore model")
|
|
722
723
|
ds_his = utils.read_sfincs_his_results(
|
|
723
724
|
Path(self._model.root) / "sfincs_his.nc",
|
|
724
725
|
crs=self._model.crs.to_epsg(),
|
|
@@ -778,7 +779,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
778
779
|
zs_maps.append(zsmax)
|
|
779
780
|
|
|
780
781
|
# Create RP flood maps
|
|
781
|
-
|
|
782
|
+
logger.info("Calculating flood risk maps, this may take some time")
|
|
782
783
|
rp_flood_maps = self.calc_rp_maps(
|
|
783
784
|
floodmaps=zs_maps,
|
|
784
785
|
frequencies=frequencies,
|
|
@@ -831,7 +832,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
831
832
|
self.process(scenario, event)
|
|
832
833
|
self.postprocess(scenario, event)
|
|
833
834
|
|
|
834
|
-
if self.settings.config.save_simulation:
|
|
835
|
+
if not self.settings.config.save_simulation:
|
|
835
836
|
self._delete_simulation_folder(scenario, sub_event=event)
|
|
836
837
|
|
|
837
838
|
def _delete_simulation_folder(
|
|
@@ -841,7 +842,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
841
842
|
sim_path = self._get_simulation_path(scenario, sub_event=sub_event)
|
|
842
843
|
if sim_path.exists():
|
|
843
844
|
shutil.rmtree(sim_path, ignore_errors=True)
|
|
844
|
-
|
|
845
|
+
logger.info(f"Deleted simulation folder: {sim_path}")
|
|
845
846
|
|
|
846
847
|
if sim_path.parent.exists() and not any(sim_path.parent.iterdir()):
|
|
847
848
|
# Remove the parent directory `simulations` if it is empty
|
|
@@ -860,7 +861,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
860
861
|
|
|
861
862
|
# Preprocess
|
|
862
863
|
self.preprocess(scenario, event=sub_event)
|
|
863
|
-
|
|
864
|
+
logger.info(
|
|
864
865
|
f"Running SFINCS for Eventset Scenario `{scenario.name}`, Event `{sub_event.name}` ({i + 1}/{total})"
|
|
865
866
|
)
|
|
866
867
|
self.execute(sim_path)
|
|
@@ -972,9 +973,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
972
973
|
direction=None,
|
|
973
974
|
)
|
|
974
975
|
else:
|
|
975
|
-
|
|
976
|
-
f"Unsupported wind forcing type: {wind.__class__.__name__}"
|
|
977
|
-
)
|
|
976
|
+
logger.warning(f"Unsupported wind forcing type: {wind.__class__.__name__}")
|
|
978
977
|
return
|
|
979
978
|
|
|
980
979
|
def _add_forcing_rain(self, rainfall: IRainfall):
|
|
@@ -1041,7 +1040,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
1041
1040
|
ds *= conversion
|
|
1042
1041
|
self._model.setup_precip_forcing_from_grid(precip=ds, aggregate=False)
|
|
1043
1042
|
else:
|
|
1044
|
-
|
|
1043
|
+
logger.warning(
|
|
1045
1044
|
f"Unsupported rainfall forcing type: {rainfall.__class__.__name__}"
|
|
1046
1045
|
)
|
|
1047
1046
|
return
|
|
@@ -1059,7 +1058,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
1059
1058
|
if isinstance(forcing, (DischargeConstant, DischargeCSV, DischargeSynthetic)):
|
|
1060
1059
|
self._set_single_river_forcing(discharge=forcing)
|
|
1061
1060
|
else:
|
|
1062
|
-
|
|
1061
|
+
logger.warning(
|
|
1063
1062
|
f"Unsupported discharge forcing type: {forcing.__class__.__name__}"
|
|
1064
1063
|
)
|
|
1065
1064
|
|
|
@@ -1133,7 +1132,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
1133
1132
|
self._set_waterlevel_forcing(df_ts)
|
|
1134
1133
|
self._turn_off_bnd_press_correction()
|
|
1135
1134
|
else:
|
|
1136
|
-
|
|
1135
|
+
logger.warning(
|
|
1137
1136
|
f"Unsupported waterlevel forcing type: {forcing.__class__.__name__}"
|
|
1138
1137
|
)
|
|
1139
1138
|
|
|
@@ -1171,7 +1170,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
1171
1170
|
)
|
|
1172
1171
|
|
|
1173
1172
|
sim_path = self.get_model_root()
|
|
1174
|
-
|
|
1173
|
+
logger.info(f"Adding spiderweb forcing to Sfincs model: {sim_path.name}")
|
|
1175
1174
|
|
|
1176
1175
|
# prevent SameFileError
|
|
1177
1176
|
output_spw_path = sim_path / forcing.path.name
|
|
@@ -1222,9 +1221,9 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
1222
1221
|
for height in gdf_floodwall["z"]
|
|
1223
1222
|
]
|
|
1224
1223
|
gdf_floodwall["z"] = heights
|
|
1225
|
-
|
|
1224
|
+
logger.info("Using floodwall height from shape file.")
|
|
1226
1225
|
except Exception:
|
|
1227
|
-
|
|
1226
|
+
logger.warning(
|
|
1228
1227
|
f"Could not use height data from file due to missing `z` column or missing values therein. Using uniform height of {floodwall.elevation} instead."
|
|
1229
1228
|
)
|
|
1230
1229
|
gdf_floodwall["z"] = floodwall.elevation.convert(
|
|
@@ -1318,12 +1317,12 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
1318
1317
|
if not isinstance(
|
|
1319
1318
|
discharge, (DischargeConstant, DischargeSynthetic, DischargeCSV)
|
|
1320
1319
|
):
|
|
1321
|
-
|
|
1320
|
+
logger.warning(
|
|
1322
1321
|
f"Unsupported discharge forcing type: {discharge.__class__.__name__}"
|
|
1323
1322
|
)
|
|
1324
1323
|
return
|
|
1325
1324
|
|
|
1326
|
-
|
|
1325
|
+
logger.info(f"Setting discharge forcing for river: {discharge.river.name}")
|
|
1327
1326
|
|
|
1328
1327
|
time_frame = self.get_model_time()
|
|
1329
1328
|
model_rivers = self._read_river_locations()
|
|
@@ -1374,9 +1373,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
1374
1373
|
|
|
1375
1374
|
def _turn_off_bnd_press_correction(self):
|
|
1376
1375
|
"""Turn off the boundary pressure correction in the sfincs model."""
|
|
1377
|
-
|
|
1378
|
-
"Turning off boundary pressure correction in the offshore model"
|
|
1379
|
-
)
|
|
1376
|
+
logger.info("Turning off boundary pressure correction in the offshore model")
|
|
1380
1377
|
self._model.set_config("pavbnd", -9999)
|
|
1381
1378
|
|
|
1382
1379
|
def _set_waterlevel_forcing(self, df_ts: pd.DataFrame):
|
|
@@ -1426,7 +1423,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
1426
1423
|
- Required coordinates: ['time', 'y', 'x']
|
|
1427
1424
|
- spatial_ref: CRS
|
|
1428
1425
|
"""
|
|
1429
|
-
|
|
1426
|
+
logger.info("Adding pressure forcing to the offshore model")
|
|
1430
1427
|
self._model.setup_pressure_forcing_from_grid(press=ds)
|
|
1431
1428
|
|
|
1432
1429
|
def _add_bzs_from_bca(self, event: Event, physical_projection: PhysicalProjection):
|
|
@@ -1435,7 +1432,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
1435
1432
|
if self.settings.config.offshore_model is None:
|
|
1436
1433
|
raise ValueError("No offshore model found in sfincs config.")
|
|
1437
1434
|
|
|
1438
|
-
|
|
1435
|
+
logger.info("Adding water level forcing to the offshore model")
|
|
1439
1436
|
sb = SfincsBoundary()
|
|
1440
1437
|
sb.read_flow_boundary_points(self.get_model_root() / "sfincs.bnd")
|
|
1441
1438
|
sb.read_astro_boundary_conditions(self.get_model_root() / "sfincs.bca")
|
|
@@ -1656,10 +1653,10 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
1656
1653
|
|
|
1657
1654
|
# Rainfall
|
|
1658
1655
|
start = "Including" if include_rainfall else "Excluding"
|
|
1659
|
-
|
|
1656
|
+
logger.info(f"{start} rainfall in the spiderweb file")
|
|
1660
1657
|
tc.include_rainfall = include_rainfall
|
|
1661
1658
|
|
|
1662
|
-
|
|
1659
|
+
logger.info(
|
|
1663
1660
|
f"Creating spiderweb file for hurricane event `{name}`. This may take a while."
|
|
1664
1661
|
)
|
|
1665
1662
|
|
|
@@ -1678,7 +1675,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
1678
1675
|
):
|
|
1679
1676
|
return tc
|
|
1680
1677
|
|
|
1681
|
-
|
|
1678
|
+
logger.info(f"Translating the track of the tropical cyclone `{tc.name}`")
|
|
1682
1679
|
# First convert geodataframe to the local coordinate system
|
|
1683
1680
|
crs = pyproj.CRS.from_string(self.settings.config.csname)
|
|
1684
1681
|
tc.track = tc.track.to_crs(crs)
|
|
@@ -1756,6 +1753,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
1756
1753
|
)
|
|
1757
1754
|
sfincs_logger.setLevel(logging.DEBUG)
|
|
1758
1755
|
sfincs_logger.addHandler(file_handler)
|
|
1756
|
+
self.sfincs_logger = sfincs_logger
|
|
1759
1757
|
return sfincs_logger
|
|
1760
1758
|
|
|
1761
1759
|
def _cleanup_simulation_folder(
|
|
@@ -1799,7 +1797,7 @@ class SfincsAdapter(IHazardAdapter):
|
|
|
1799
1797
|
)
|
|
1800
1798
|
|
|
1801
1799
|
if df_gauge is None:
|
|
1802
|
-
|
|
1800
|
+
logger.warning(
|
|
1803
1801
|
"No water level data available for the tide gauge. Could not add it to the plot."
|
|
1804
1802
|
)
|
|
1805
1803
|
return
|
|
@@ -23,9 +23,10 @@ from flood_adapt.objects.forcing.meteo_handler import MeteoHandler
|
|
|
23
23
|
from flood_adapt.objects.forcing.wind import WindMeteo
|
|
24
24
|
from flood_adapt.objects.scenarios.scenarios import Scenario
|
|
25
25
|
|
|
26
|
+
logger = FloodAdaptLogging.getLogger("OffshoreSfincsAdapter")
|
|
27
|
+
|
|
26
28
|
|
|
27
29
|
class OffshoreSfincsHandler(IOffshoreSfincsHandler, DatabaseUser):
|
|
28
|
-
logger = FloodAdaptLogging.getLogger("OffshoreSfincsAdapter")
|
|
29
30
|
template_path: Path
|
|
30
31
|
|
|
31
32
|
def __init__(self, scenario: Scenario, event: Event) -> None:
|
|
@@ -90,7 +91,7 @@ class OffshoreSfincsHandler(IOffshoreSfincsHandler, DatabaseUser):
|
|
|
90
91
|
Args:
|
|
91
92
|
sim_path path to the root of the offshore model
|
|
92
93
|
"""
|
|
93
|
-
|
|
94
|
+
logger.info(
|
|
94
95
|
f"Preparing offshore model to generate waterlevels for `{self.scenario.name}`"
|
|
95
96
|
)
|
|
96
97
|
sim_path = self._get_simulation_path()
|
|
@@ -149,13 +150,11 @@ class OffshoreSfincsHandler(IOffshoreSfincsHandler, DatabaseUser):
|
|
|
149
150
|
_offshore_model.write(path_out=sim_path)
|
|
150
151
|
|
|
151
152
|
def _execute_sfincs_offshore(self, sim_path: Path):
|
|
152
|
-
|
|
153
|
+
logger.info(f"Running offshore model in {sim_path}")
|
|
153
154
|
sim_path = self._get_simulation_path()
|
|
154
155
|
with SfincsAdapter(model_root=sim_path) as _offshore_model:
|
|
155
156
|
if _offshore_model.sfincs_completed(sim_path):
|
|
156
|
-
|
|
157
|
-
"Skip running offshore model as it has already been run."
|
|
158
|
-
)
|
|
157
|
+
logger.info("Skip running offshore model as it has already been run.")
|
|
159
158
|
return
|
|
160
159
|
try:
|
|
161
160
|
_offshore_model.execute(path=sim_path)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import datetime
|
|
2
|
+
import gc
|
|
2
3
|
import logging
|
|
3
4
|
import math
|
|
4
5
|
import os
|
|
@@ -767,6 +768,7 @@ class DatabaseBuilder:
|
|
|
767
768
|
logger.info(
|
|
768
769
|
"Updating FIAT objects ground elevations from SFINCS ground elevation map."
|
|
769
770
|
)
|
|
771
|
+
# Get unit conversion factor
|
|
770
772
|
SFINCS_units = us.UnitfulLength(
|
|
771
773
|
value=1.0, units=us.UnitTypesLength.meters
|
|
772
774
|
) # SFINCS is always in meters
|
|
@@ -777,11 +779,32 @@ class DatabaseBuilder:
|
|
|
777
779
|
logger.info(
|
|
778
780
|
f"Ground elevation for FIAT objects is in '{FIAT_units}', while SFINCS ground elevation is in 'meters'. Values in the exposure csv will be converted by a factor of {conversion_factor}"
|
|
779
781
|
)
|
|
780
|
-
|
|
782
|
+
# Read in DEM and objects
|
|
781
783
|
exposure = self.fiat_model.exposure.exposure_db
|
|
782
784
|
dem = rxr.open_rasterio(dem_file)
|
|
783
|
-
|
|
784
785
|
gdf = self._get_fiat_gdf_full()
|
|
786
|
+
|
|
787
|
+
# Ensure gdf has the same CRS as dem
|
|
788
|
+
# Determine the CRS to use for sampling
|
|
789
|
+
if (
|
|
790
|
+
hasattr(self.sfincs_overland_model, "crs")
|
|
791
|
+
and self.sfincs_overland_model.crs is not None
|
|
792
|
+
):
|
|
793
|
+
target_crs = self.sfincs_overland_model.crs
|
|
794
|
+
elif (
|
|
795
|
+
hasattr(dem, "rio") and hasattr(dem.rio, "crs") and dem.rio.crs is not None
|
|
796
|
+
):
|
|
797
|
+
target_crs = dem.rio.crs
|
|
798
|
+
else:
|
|
799
|
+
target_crs = gdf.crs
|
|
800
|
+
logger.warning(
|
|
801
|
+
"Could not determine CRS from SFINCS model or DEM raster. Assuming the CRS is the same as the FIAT model."
|
|
802
|
+
)
|
|
803
|
+
|
|
804
|
+
if gdf.crs != target_crs:
|
|
805
|
+
gdf = gdf.to_crs(target_crs)
|
|
806
|
+
|
|
807
|
+
# Sample DEM at the centroid of each geometry
|
|
785
808
|
gdf["centroid"] = gdf.geometry.centroid
|
|
786
809
|
x_points = xr.DataArray(gdf["centroid"].x, dims="points")
|
|
787
810
|
y_points = xr.DataArray(gdf["centroid"].y, dims="points")
|
|
@@ -790,6 +813,7 @@ class DatabaseBuilder:
|
|
|
790
813
|
* conversion_factor
|
|
791
814
|
)
|
|
792
815
|
|
|
816
|
+
# Merge updated elevation back into exposure DataFrame
|
|
793
817
|
exposure = exposure.merge(
|
|
794
818
|
gdf[[_FIAT_COLUMNS.object_id, "elev"]],
|
|
795
819
|
on=_FIAT_COLUMNS.object_id,
|
|
@@ -1287,13 +1311,14 @@ class DatabaseBuilder:
|
|
|
1287
1311
|
def create_dem_model(self) -> DemModel:
|
|
1288
1312
|
if self.config.dem:
|
|
1289
1313
|
subgrid_sfincs = Path(self.config.dem.filename)
|
|
1314
|
+
delete_sfincs_folder = False
|
|
1290
1315
|
else:
|
|
1291
1316
|
logger.warning(
|
|
1292
1317
|
"No subgrid depth geotiff file provided in the config file. Using the one from the SFINCS model."
|
|
1293
1318
|
)
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1319
|
+
subgrid_sfincs_folder = Path(self.sfincs_overland_model.root) / "subgrid"
|
|
1320
|
+
subgrid_sfincs = subgrid_sfincs_folder / "dep_subgrid.tif"
|
|
1321
|
+
delete_sfincs_folder = True
|
|
1297
1322
|
|
|
1298
1323
|
dem_file = self._check_exists_and_absolute(subgrid_sfincs)
|
|
1299
1324
|
fa_subgrid_path = self.static_path / "dem" / dem_file.name
|
|
@@ -1324,6 +1349,13 @@ class DatabaseBuilder:
|
|
|
1324
1349
|
|
|
1325
1350
|
shutil.copy2(dem_file, fa_subgrid_path)
|
|
1326
1351
|
self._dem_path = fa_subgrid_path
|
|
1352
|
+
|
|
1353
|
+
# Remove the original subgrid folder if it exists
|
|
1354
|
+
if delete_sfincs_folder:
|
|
1355
|
+
gc.collect()
|
|
1356
|
+
if subgrid_sfincs_folder.exists() and subgrid_sfincs_folder.is_dir():
|
|
1357
|
+
shutil.rmtree(subgrid_sfincs_folder)
|
|
1358
|
+
|
|
1327
1359
|
return DemModel(
|
|
1328
1360
|
filename=fa_subgrid_path.name, units=us.UnitTypesLength.meters
|
|
1329
1361
|
) # always in meters
|