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.
@@ -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()), mode="r", logger=self.sfincs_logger
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 logger in [self.logger, self.sfincs_logger]:
154
- if hasattr(logger, "handlers"):
155
- for handler in logger.handlers:
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
- logger.removeHandler(handler)
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
- # No need to check postprocessing for risk scenarios
176
- return all(self.sfincs_completed(sim_path) for sim_path in sim_paths)
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
- return self.sfincs_completed(self._get_simulation_path(scenario))
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
- self.logger.info(f"Running SFINCS in {path}")
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
- self.logger.debug(process.stdout)
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
- self.logger.error(f"SFINCS model failed to run in {path}.")
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
- self.logger.info(
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 self.rainfall is not None:
276
- model.rainfall *= model._event.rainfall_multiplier
277
- else:
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
- self.logger.info(f"Running SFINCS for single event Scenario `{scenario.name}`")
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
- self.logger.info(f"Postprocessing SFINCS for Scenario `{scenario.name}`")
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
- self.logger.info(f"Setting timing for the SFINCS model: `{time}`")
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
- self.logger.info(
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
- self.logger.warning(
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
- self.logger.info(
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
- self.logger.warning(
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
- self.logger.info(f"Adding Projection `{projection.name}`")
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
- self.rainfall *= phys_projection.rainfall_multiplier
386
- else:
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
- self.logger.info("Writing flood maps to geotiff")
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
- self.logger.info("Writing water level map to netcdf")
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
- self.logger.warning("No observation points provided in config.")
608
+ logger.warning("No observation points provided in config.")
608
609
  return
609
610
 
610
- self.logger.info("Plotting water levels at observation points")
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
- self.logger.info("Adding observation points to the overland flood model")
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
- self.logger.info("Reading water levels from offshore model")
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
- self.logger.info("Calculating flood risk maps, this may take some time")
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
- self.logger.info(f"Deleted simulation folder: {sim_path}")
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
- self.logger.info(
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
- self.logger.warning(
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
- self.logger.warning(
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
- self.logger.warning(
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
- self.logger.warning(
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
- self.logger.info(f"Adding spiderweb forcing to Sfincs model: {sim_path.name}")
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
- self.logger.info("Using floodwall height from shape file.")
1224
+ logger.info("Using floodwall height from shape file.")
1226
1225
  except Exception:
1227
- self.logger.warning(
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
- self.logger.warning(
1320
+ logger.warning(
1322
1321
  f"Unsupported discharge forcing type: {discharge.__class__.__name__}"
1323
1322
  )
1324
1323
  return
1325
1324
 
1326
- self.logger.info(f"Setting discharge forcing for river: {discharge.river.name}")
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
- self.logger.info(
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
- self.logger.info("Adding pressure forcing to the offshore model")
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
- self.logger.info("Adding water level forcing to the offshore model")
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
- self.logger.info(f"{start} rainfall in the spiderweb file")
1656
+ logger.info(f"{start} rainfall in the spiderweb file")
1660
1657
  tc.include_rainfall = include_rainfall
1661
1658
 
1662
- self.logger.info(
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
- self.logger.info(f"Translating the track of the tropical cyclone `{tc.name}`")
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
- self.logger.warning(
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
- self.logger.info(
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
- self.logger.info(f"Running offshore model in {sim_path}")
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
- self.logger.info(
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
- subgrid_sfincs = (
1295
- Path(self.sfincs_overland_model.root) / "subgrid" / "dep_subgrid.tif"
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