flood-adapt 1.1.4__py3-none-any.whl → 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
flood_adapt/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # has to be here at the start to avoid circular imports
2
- __version__ = "1.1.4"
2
+ __version__ = "2.0.0"
3
3
 
4
4
  from flood_adapt import adapter, database_builder, dbs_classes, objects
5
5
  from flood_adapt.config.config import Settings
@@ -258,7 +258,7 @@ class FiatAdapter(IImpactAdapter):
258
258
  map_type=floodmap.map_type,
259
259
  var=var,
260
260
  is_risk=is_risk,
261
- units=us.UnitTypesLength.meters,
261
+ units=self.database.site.sfincs.config.floodmap_units,
262
262
  )
263
263
 
264
264
  # Save any changes made to disk as well
@@ -489,7 +489,10 @@ class FiatAdapter(IImpactAdapter):
489
489
  fiat_results_path = impacts_output_path.joinpath(
490
490
  f"Impacts_detailed_{scenario.name}.csv"
491
491
  )
492
- self.outputs["table"].to_csv(fiat_results_path, index=False)
492
+ try:
493
+ self.outputs["table"].to_csv(fiat_results_path, index=False)
494
+ except Exception as e:
495
+ logger.error(f"Error while saving detailed impacts file csv: {e}")
493
496
 
494
497
  # Add exceedance probabilities if needed (only for risk)
495
498
  if mode == Mode.risk:
@@ -500,13 +503,16 @@ class FiatAdapter(IImpactAdapter):
500
503
  )
501
504
  with open(config_path, mode="rb") as fp:
502
505
  config = tomli.load(fp)["flood_exceedance"]
503
- self.add_exceedance_probability(
504
- column=config[
505
- "column"
506
- ], # TODO check how to the correct version of column
507
- threshold=config["threshold"],
508
- period=config["period"],
509
- )
506
+ try:
507
+ self.add_exceedance_probability(
508
+ column=config[
509
+ "column"
510
+ ], # TODO check how to the correct version of column
511
+ threshold=config["threshold"],
512
+ period=config["period"],
513
+ )
514
+ except Exception as e:
515
+ logger.error(f"Error while adding exceedance probabilities: {e}")
510
516
 
511
517
  # Create the infometrics files
512
518
  if mode == Mode.risk:
@@ -531,7 +537,10 @@ class FiatAdapter(IImpactAdapter):
531
537
  metrics_outputs_path = impacts_output_path.parent.joinpath(
532
538
  f"Infometrics_{scenario.name}.csv"
533
539
  )
534
- self.create_infometrics(metric_config_paths, metrics_outputs_path)
540
+ try:
541
+ self.create_infometrics(metric_config_paths, metrics_outputs_path)
542
+ except Exception as e:
543
+ logger.error(f"Error while creating infometrics: {e}")
535
544
 
536
545
  # Get paths of created aggregated infometrics
537
546
  aggr_metrics_paths = list(
@@ -543,20 +552,28 @@ class FiatAdapter(IImpactAdapter):
543
552
  config_base_path = self.database.static_path.joinpath(
544
553
  "templates", "Infographics"
545
554
  )
546
- self.create_infographics(
547
- name=scenario.name,
548
- output_base_path=impacts_output_path.parent,
549
- config_base_path=config_base_path,
550
- metrics_path=metrics_outputs_path,
551
- mode=mode,
552
- )
555
+ try:
556
+ self.create_infographics(
557
+ name=scenario.name,
558
+ output_base_path=impacts_output_path.parent,
559
+ config_base_path=config_base_path,
560
+ metrics_path=metrics_outputs_path,
561
+ mode=mode,
562
+ )
563
+ except Exception as e:
564
+ logger.error(f"Error while creating infographics: {e}")
553
565
 
554
566
  # Calculate equity based damages
555
567
  if mode == Mode.risk:
556
568
  for file in aggr_metrics_paths:
557
569
  # Load metrics
558
570
  aggr_label = file.stem.split(f"{metrics_outputs_path.stem}_")[-1]
559
- self.add_equity(aggr_label=aggr_label, metrics_path=file)
571
+ try:
572
+ self.add_equity(aggr_label=aggr_label, metrics_path=file)
573
+ except Exception as e:
574
+ logger.error(
575
+ f"Error while calculating equity metrics for {aggr_label}: {e}"
576
+ )
560
577
 
561
578
  # Save aggregated metrics to shapefiles
562
579
  for file in aggr_metrics_paths:
@@ -564,24 +581,35 @@ class FiatAdapter(IImpactAdapter):
564
581
  output_path = impacts_output_path.joinpath(
565
582
  f"Impacts_aggregated_{scenario.name}_{aggr_label}.gpkg"
566
583
  )
567
- self.save_aggregation_spatial(
568
- aggr_label=aggr_label, metrics_path=file, output_path=output_path
569
- )
584
+ try:
585
+ self.save_aggregation_spatial(
586
+ aggr_label=aggr_label, metrics_path=file, output_path=output_path
587
+ )
588
+ except Exception as e:
589
+ logger.error(
590
+ f"Error while saving aggregation spatial for {aggr_label}: {e}"
591
+ )
570
592
 
571
593
  # Merge points data to building footprints
572
- self.save_building_footprints(
573
- output_path=impacts_output_path.joinpath(
574
- f"Impacts_building_footprints_{scenario.name}.gpkg"
594
+ try:
595
+ self.save_building_footprints(
596
+ output_path=impacts_output_path.joinpath(
597
+ f"Impacts_building_footprints_{scenario.name}.gpkg"
598
+ )
575
599
  )
576
- )
600
+ except Exception as e:
601
+ logger.error(f"Error while saving building footprints: {e}")
577
602
 
578
603
  # Create a roads spatial file
579
604
  if self.config.roads_file_name:
580
- self.save_roads(
581
- output_path=impacts_output_path.joinpath(
582
- f"Impacts_roads_{scenario.name}.gpkg"
605
+ try:
606
+ self.save_roads(
607
+ output_path=impacts_output_path.joinpath(
608
+ f"Impacts_roads_{scenario.name}.gpkg"
609
+ )
583
610
  )
584
- )
611
+ except Exception as e:
612
+ logger.error(f"Error while saving roads spatial file: {e}")
585
613
 
586
614
  logger.info("Delft-FIAT post-processing complete!")
587
615
 
@@ -15,12 +15,12 @@ import plotly.express as px
15
15
  import pyproj
16
16
  import shapely
17
17
  import xarray as xr
18
+ import xugrid as xu
18
19
  from cht_cyclones.tropical_cyclone import TropicalCyclone
19
20
  from cht_tide.read_bca import SfincsBoundary
20
21
  from cht_tide.tide_predict import predict
21
22
  from hydromt_sfincs import SfincsModel as HydromtSfincsModel
22
23
  from hydromt_sfincs.quadtree import QuadtreeGrid
23
- from numpy import matlib
24
24
  from shapely.affinity import translate
25
25
 
26
26
  from flood_adapt.adapter.interface.hazard_adapter import IHazardAdapter
@@ -308,9 +308,9 @@ class SfincsAdapter(IHazardAdapter):
308
308
  ):
309
309
  raise RuntimeError("SFINCS was not run successfully!")
310
310
 
311
+ self.write_water_level_map(scenario)
311
312
  self.write_floodmap_geotiff(scenario)
312
313
  self.plot_wl_obs(scenario)
313
- self.write_water_level_map(scenario)
314
314
 
315
315
  def set_timing(self, time: TimeFrame):
316
316
  """Set model reference times."""
@@ -386,20 +386,27 @@ class SfincsAdapter(IHazardAdapter):
386
386
  def get_model_root(self) -> Path:
387
387
  return Path(self._model.root)
388
388
 
389
- def get_mask(self):
389
+ def get_mask(self) -> xr.DataArray:
390
390
  """Get mask with inactive cells from model."""
391
- mask = self._model.grid["msk"]
391
+ mask = self._model.mask
392
392
  return mask
393
393
 
394
- def get_bedlevel(self):
394
+ def get_bedlevel(self) -> xr.DataArray:
395
395
  """Get bed level from model."""
396
396
  self._model.read_results()
397
397
  zb = self._model.results["zb"]
398
+ # Convert bed level from meters to floodmap units
399
+ conversion = us.UnitfulLength(
400
+ value=1.0, units=us.UnitTypesLength.meters
401
+ ).convert(self.settings.config.floodmap_units)
402
+ zb = zb * conversion
403
+ zb.attrs["units"] = self.settings.config.floodmap_units.value
398
404
  return zb
399
405
 
400
406
  def get_model_boundary(self) -> gpd.GeoDataFrame:
401
407
  """Get bounding box from model."""
402
- return self._model.region
408
+ boundary = self._model.region[["geometry"]]
409
+ return boundary
403
410
 
404
411
  def get_model_grid(self) -> QuadtreeGrid:
405
412
  """Get grid from model.
@@ -411,6 +418,15 @@ class SfincsAdapter(IHazardAdapter):
411
418
  """
412
419
  return self._model.quadtree
413
420
 
421
+ def get_finest_res(self) -> float:
422
+ """Get the finest resolution of the model grid."""
423
+ if self._model.grid_type == "quadtree":
424
+ res0 = self._model.quadtree.dx
425
+ res = res0 / 2**self._model.quadtree.nr_refinement_levels
426
+ else:
427
+ res = self._model.res[0]
428
+ return res
429
+
414
430
  # Forcing properties
415
431
  @property
416
432
  def waterlevels(self) -> xr.Dataset | xr.DataArray | None:
@@ -543,7 +559,7 @@ class SfincsAdapter(IHazardAdapter):
543
559
  sim_path : Path, optional
544
560
  Path to the simulation folder, by default None.
545
561
  """
546
- logger.info("Writing flood maps to geotiff")
562
+ logger.info("Writing flood maps to geotiff.")
547
563
  results_path = self._get_result_path(scenario)
548
564
  sim_path = sim_path or self._get_simulation_path(scenario)
549
565
  demfile = self.database.static_path / "dem" / self.settings.dem.filename
@@ -560,13 +576,8 @@ class SfincsAdapter(IHazardAdapter):
560
576
 
561
577
  floodmap_fn = results_path / f"FloodMap_{scenario.name}.tif"
562
578
 
563
- # convert zsmax from meters to floodmap units
564
- floodmap_conversion = us.UnitfulLength(
565
- value=1.0, units=us.UnitTypesLength.meters
566
- ).convert(self.settings.config.floodmap_units)
567
-
568
579
  utils.downscale_floodmap(
569
- zsmax=floodmap_conversion * zsmax,
580
+ zsmax=zsmax,
570
581
  dep=dem_conversion * dem,
571
582
  hmin=0.01,
572
583
  floodmap_fn=floodmap_fn.as_posix(),
@@ -582,7 +593,23 @@ class SfincsAdapter(IHazardAdapter):
582
593
 
583
594
  with SfincsAdapter(model_root=sim_path) as model:
584
595
  zsmax = model._get_zsmax()
585
- zsmax.to_netcdf(results_path / "max_water_level_map.nc")
596
+ if hasattr(zsmax, "ugrid"):
597
+ # First write netcdf with quadtree water levels
598
+ zsmax.to_netcdf(results_path / "max_water_level_map_qt.nc")
599
+ # Rasterize to regular grid with the finest resolution
600
+ zsmax = self._rasterize_quadtree(zsmax)
601
+ # Add CRS to the rasterized xarray
602
+ zsmax = zsmax.rio.write_crs(model._model.config["epsg"])
603
+ # Save as a Cloud Optimized GeoTIFF (COG)
604
+ zsmax.rio.to_raster(
605
+ results_path / "max_water_level_map.tif",
606
+ driver="COG",
607
+ compress="deflate",
608
+ dtype="float32",
609
+ nodata=np.nan,
610
+ OVERVIEW_RESAMPLING="nearest",
611
+ tags={"units": self.settings.config.floodmap_units.value},
612
+ )
586
613
 
587
614
  def plot_wl_obs(
588
615
  self,
@@ -727,83 +754,117 @@ class SfincsAdapter(IHazardAdapter):
727
754
 
728
755
  TODO: make this robust and more efficient for bigger datasets.
729
756
  """
757
+ # Check if the scenario is a risk scenario
730
758
  event: EventSet = self.database.events.get(scenario.event, load_all=True)
731
759
  if not isinstance(event, EventSet):
732
760
  raise ValueError("This function is only available for risk scenarios.")
761
+ logger.info("Calculating flood risk maps, this may take some time.")
733
762
 
763
+ # Get the simulation paths and result path
734
764
  result_path = self._get_result_path(scenario)
735
765
  sim_paths = [
736
766
  self._get_simulation_path(scenario, sub_event=sub_event)
737
767
  for sub_event in event._events
738
768
  ]
739
769
 
770
+ # Get the required return periods for flood maps
771
+ floodmap_rp = self.database.site.fiat.risk.return_periods
772
+
773
+ # Get the frequencies for each sub-event
774
+ frequencies = [sub_event.frequency for sub_event in event.sub_events]
740
775
  phys_proj = self.database.projections.get(
741
776
  scenario.projection
742
777
  ).physical_projection
743
778
 
744
- floodmap_rp = self.database.site.fiat.risk.return_periods
745
- frequencies = [sub_event.frequency for sub_event in event.sub_events]
746
-
747
- # adjust storm frequency for hurricane events
779
+ # adjust storm frequency for hurricane events if provided in the projection
748
780
  if not math.isclose(phys_proj.storm_frequency_increase, 0, abs_tol=1e-9):
749
781
  storminess_increase = phys_proj.storm_frequency_increase / 100.0
750
782
  for ii, event in enumerate(event._events):
751
783
  if event.template == Template.Hurricane:
752
784
  frequencies[ii] = frequencies[ii] * (1 + storminess_increase)
753
785
 
786
+ # Read static mask and bed level from the first simulation path
754
787
  with SfincsAdapter(model_root=sim_paths[0]) as dummymodel:
755
788
  # read mask and bed level
756
789
  mask = dummymodel.get_mask()
757
790
  zb = dummymodel.get_bedlevel()
758
791
 
792
+ # Read the max water level maps from each simulation path
759
793
  zs_maps = []
760
794
  for simulation_path in sim_paths:
761
795
  # read zsmax data from overland sfincs model
762
796
  with SfincsAdapter(model_root=simulation_path) as sim:
763
797
  zsmax = sim._get_zsmax().load()
764
- zs_maps.append(zsmax)
798
+ zs_maps.append(zsmax.values)
765
799
 
766
- # Create RP flood maps
767
- logger.info("Calculating flood risk maps, this may take some time")
800
+ # Calculate return period flood maps
801
+ # All units are in the floodmap units
768
802
  rp_flood_maps = self.calc_rp_maps(
769
803
  floodmaps=zs_maps,
770
804
  frequencies=frequencies,
771
- zb=zb,
772
- mask=mask,
805
+ zb=zb.values,
806
+ mask=mask.values,
773
807
  return_periods=floodmap_rp,
774
808
  )
775
809
 
810
+ # For each return period, save water level and flood depth maps
776
811
  for ii, rp in enumerate(floodmap_rp):
777
- zs_rp_single = rp_flood_maps[ii]
778
- zs_rp_single = zs_rp_single.rio.write_crs(
779
- zsmax.raster.crs
780
- ) # , inplace=True)
781
- zs_rp_single = zs_rp_single.to_dataset(name="risk_map").transpose()
782
- fn_rp = result_path / f"RP_{rp:04d}_maps.nc"
783
- zs_rp_single.to_netcdf(fn_rp)
784
-
785
- # write geotiff
812
+ # Prepare data array for the return period flood map
813
+ zs_rp_single = xr.DataArray(
814
+ rp_flood_maps[ii],
815
+ name="zsmax",
816
+ attrs={"units": self.settings.config.floodmap_units.value},
817
+ )
818
+ # If model is quadtree, write the quadtree netcdf with water levels since it is needed for visualizations
819
+ if self._model.grid_type == "quadtree":
820
+ # Use mask grid
821
+ zs_rp_single = xu.UgridDataArray.from_data(
822
+ zs_rp_single, grid=mask.grid, facet="face"
823
+ )
824
+ # Save to netcdf
825
+ zs_rp_single.to_netcdf(
826
+ result_path / f"RP_{rp:04d}_max_water_level_map_qt.nc"
827
+ )
828
+ # Rasterize to regular grid with the finest resolution
829
+ zs_rp_single = self._rasterize_quadtree(zs_rp_single)
830
+ # Prepare regular grid water level map
831
+ elif self._model.grid_type == "regular":
832
+ # Create a DataArray with the mask coordinates
833
+ zs_rp_single = xr.DataArray(
834
+ zs_rp_single,
835
+ dims=("y", "x"),
836
+ coords={"y": mask.y, "x": mask.x},
837
+ name="zsmax",
838
+ )
839
+ else:
840
+ raise ValueError("unsupported sfincs model type")
841
+ # Write COG geotiff with water levels
842
+ zs_rp_single = zs_rp_single.rio.write_crs(self._model.crs)
843
+ fn_rp = result_path / f"RP_{rp:04d}_max_water_level_map.tif"
844
+ zs_rp_single.transpose("y", "x").rio.to_raster(
845
+ fn_rp,
846
+ driver="COG",
847
+ compress="deflate",
848
+ dtype="float32",
849
+ nodata=np.nan,
850
+ OVERVIEW_RESAMPLING="nearest",
851
+ tags={"units": self.settings.config.floodmap_units.value},
852
+ )
853
+
786
854
  # dem file for high resolution flood depth map
787
855
  demfile = self.database.static_path / "dem" / self.settings.dem.filename
856
+ # convert dem from dem units to floodmap units
857
+ dem_conversion = us.UnitfulLength(
858
+ value=1.0, units=self.settings.dem.units
859
+ ).convert(self.settings.config.floodmap_units)
788
860
 
789
861
  # writing the geotiff to the scenario results folder
790
862
  with SfincsAdapter(model_root=sim_paths[0]) as dummymodel:
791
863
  dem = dummymodel._model.data_catalog.get_rasterdataset(demfile)
792
- zsmax = zs_rp_single.to_array().squeeze().transpose()
793
- floodmap_fn = fn_rp.with_suffix(".tif")
794
-
795
- # convert dem from dem units to floodmap units
796
- dem_conversion = us.UnitfulLength(
797
- value=1.0, units=self.settings.dem.units
798
- ).convert(self.settings.config.floodmap_units)
799
-
800
- # convert zsmax from meters to floodmap units
801
- floodmap_conversion = us.UnitfulLength(
802
- value=1.0, units=us.UnitTypesLength.meters
803
- ).convert(self.settings.config.floodmap_units)
864
+ floodmap_fn = result_path / f"RP_{rp:04d}_FloodMap.tif"
804
865
 
805
866
  utils.downscale_floodmap(
806
- zsmax=floodmap_conversion * zsmax,
867
+ zsmax=zs_rp_single,
807
868
  dep=dem_conversion * dem,
808
869
  hmin=0.01,
809
870
  floodmap_fn=floodmap_fn.as_posix(),
@@ -1525,9 +1586,9 @@ class SfincsAdapter(IHazardAdapter):
1525
1586
  if isinstance(event, EventSet):
1526
1587
  map_fn = []
1527
1588
  for rp in self.database.site.fiat.risk.return_periods:
1528
- map_fn.append(results_path / f"RP_{rp:04d}_maps.nc")
1589
+ map_fn.append(results_path / f"RP_{rp:04d}_max_water_level_map.tif")
1529
1590
  elif isinstance(event, Event):
1530
- map_fn = [results_path / "max_water_level_map.nc"]
1591
+ map_fn = [results_path / "max_water_level_map.tif"]
1531
1592
  else:
1532
1593
  raise ValueError(f"Unsupported mode: {event.mode}")
1533
1594
 
@@ -1541,7 +1602,14 @@ class SfincsAdapter(IHazardAdapter):
1541
1602
  """Read zsmax file and return absolute maximum water level over entire simulation."""
1542
1603
  self._model.read_results()
1543
1604
  zsmax = self._model.results["zsmax"].max(dim="timemax")
1544
- zsmax.attrs["units"] = "m"
1605
+
1606
+ # Convert from meters to floodmap units
1607
+ floodmap_conversion = us.UnitfulLength(
1608
+ value=1.0, units=us.UnitTypesLength.meters
1609
+ ).convert(self.settings.config.floodmap_units)
1610
+ zsmax = zsmax * floodmap_conversion
1611
+ zsmax.attrs["units"] = self.settings.config.floodmap_units.value
1612
+
1545
1613
  return zsmax
1546
1614
 
1547
1615
  def _get_zs_points(self):
@@ -1571,6 +1639,21 @@ class SfincsAdapter(IHazardAdapter):
1571
1639
  )
1572
1640
  return df, gdf
1573
1641
 
1642
+ def _rasterize_quadtree(self, zsmax: xu.UgridDataArray) -> xr.DataArray:
1643
+ """Rasterize the zsmax UgridDataArray to a regular grid."""
1644
+ xmin, ymin, xmax, ymax = zsmax.ugrid.bounds["mesh2d"]
1645
+ d = self.get_finest_res()
1646
+ x = np.arange(xmin + 0.5 * d, xmax, d)
1647
+ y = np.arange(ymax - 0.5 * d, ymin, -d)
1648
+ # Create a template DataArray with the desired x and y coordinates
1649
+ template = xr.DataArray(
1650
+ np.zeros((len(y), len(x))),
1651
+ coords={"y": y, "x": x},
1652
+ dims=("y", "x"),
1653
+ )
1654
+ zsmax = zsmax.ugrid.rasterize_like(template)
1655
+ return zsmax
1656
+
1574
1657
  def _create_spw_file_from_track(
1575
1658
  self,
1576
1659
  track_forcing: Union[RainfallTrack, WindTrack],
@@ -1810,12 +1893,12 @@ class SfincsAdapter(IHazardAdapter):
1810
1893
 
1811
1894
  @staticmethod
1812
1895
  def calc_rp_maps(
1813
- floodmaps: list[xr.DataArray],
1896
+ floodmaps: list[np.ndarray],
1814
1897
  frequencies: list[float],
1815
- zb: xr.DataArray,
1816
- mask: xr.DataArray,
1898
+ zb: np.ndarray,
1899
+ mask: np.ndarray,
1817
1900
  return_periods: list[float],
1818
- ) -> list[xr.DataArray]:
1901
+ ) -> list[np.ndarray]:
1819
1902
  """
1820
1903
  Calculate return period (RP) flood maps from a set of flood simulation results.
1821
1904
 
@@ -1824,26 +1907,25 @@ class SfincsAdapter(IHazardAdapter):
1824
1907
  using exceedance probabilities and handles masked or dry cells appropriately.
1825
1908
 
1826
1909
  Args:
1827
- floodmaps (list[xr.DataArray]): List of water level maps (xarray DataArrays), one for each simulation.
1910
+ floodmaps (list[np.ndarray]): List of water level maps (NumPy arrays), one for each simulation.
1828
1911
  frequencies (list[float]): List of frequencies (probabilities of occurrence) corresponding to each floodmap.
1829
1912
  zb (np.ndarray): Array of bed elevations for each grid cell.
1830
- mask (xr.DataArray): Mask indicating valid (1) and invalid (0) grid cells.
1913
+ mask (np.ndarray): Mask indicating valid (1) and invalid (0) grid cells.
1831
1914
  return_periods (list[float]): List of return periods (in years) for which to generate hazard maps.
1832
1915
 
1833
1916
  Returns
1834
1917
  -------
1835
- list[xr.DataArray]: List of xarray DataArrays, each representing the hazard map for a given return period.
1836
- Each DataArray contains water levels (meters) for the corresponding return period.
1918
+ list[np.ndarray]: List of NumPy arrays, each representing the hazard map for a given return period.
1919
+ Each array contains water levels (meters) for the corresponding return period.
1837
1920
  """
1838
- floodmaps = floodmaps.copy() # avoid modifying the original list
1839
- # Check that all floodmaps have the same shape and dimensions
1921
+ floodmaps = [np.asarray(fm) for fm in floodmaps]
1922
+ # Check that all floodmaps have the same shape
1840
1923
  first_shape = floodmaps[0].shape
1841
- first_dims = floodmaps[0].dims
1842
1924
  for i, floodmap in enumerate(floodmaps):
1843
- if floodmap.shape != first_shape or floodmap.dims != first_dims:
1925
+ if floodmap.shape != first_shape:
1844
1926
  raise ValueError(
1845
- f"Floodmap at index {i} does not match the shape or dimensions of the first floodmap. "
1846
- f"Expected shape {first_shape} and dims {first_dims}, got shape {floodmap.shape} and dims {floodmap.dims}."
1927
+ f"Floodmap at index {i} does not match the shape of the first floodmap. "
1928
+ f"Expected shape {first_shape}, got shape {floodmap.shape}."
1847
1929
  )
1848
1930
 
1849
1931
  # Check that zb and mask have the same shape
@@ -1863,22 +1945,21 @@ class SfincsAdapter(IHazardAdapter):
1863
1945
  f"Floodmap shape: {first_shape}, zb shape: {zb.shape}, mask shape: {mask.shape}."
1864
1946
  )
1865
1947
 
1866
- # stack dimensions if floodmaps are 2D
1867
- if len(floodmaps[0].shape) > 1:
1868
- stacking = True
1869
- for i, floodmap in enumerate(floodmaps):
1870
- floodmaps[i] = floodmap.stack(z=("x", "y"))
1871
- zb = zb.stack(z=("x", "y"))
1872
- mask = mask.stack(z=("x", "y"))
1873
- else:
1874
- stacking = False
1875
-
1948
+ # If input is 2D, reshape to 1D for processing and then reshape back to 2D
1949
+ reshape_needed = False
1950
+ if zb.ndim == 2:
1951
+ reshape_needed = True
1952
+ shape_orig = zb.shape
1953
+ n_cells = zb.size
1954
+ floodmaps = [fm.reshape(n_cells) for fm in floodmaps]
1955
+ zb = zb.reshape(n_cells)
1956
+ mask = mask.reshape(n_cells)
1876
1957
  # 1a: make a table of all water levels and associated frequencies
1877
- zs = xr.concat(floodmaps, pd.Index(frequencies, name="frequency"))
1958
+ zs = np.stack(floodmaps, axis=0)
1878
1959
  # Get the indices of columns with all NaN values
1879
1960
  nan_cells = np.where(np.all(np.isnan(zs), axis=0))[0]
1880
1961
  # fill nan values with minimum bed levels in each grid cell, np.interp cannot ignore nan values
1881
- zs = xr.where(np.isnan(zs), np.tile(zb, (zs.shape[0], 1)), zs)
1962
+ zs = np.where(np.isnan(zs), np.tile(zb, (zs.shape[0], 1)), zs)
1882
1963
  # Get table of frequencies
1883
1964
  freq = np.tile(frequencies, (zs.shape[1], 1)).transpose()
1884
1965
 
@@ -1886,7 +1967,7 @@ class SfincsAdapter(IHazardAdapter):
1886
1967
  # (i.e. each h-value should be linked to the same p-values as in step 1a)
1887
1968
  sort_index = zs.argsort(axis=0)
1888
1969
  sorted_prob = np.flipud(np.take_along_axis(freq, sort_index, axis=0))
1889
- sorted_zs = np.flipud(np.take_along_axis(zs.values, sort_index, axis=0))
1970
+ sorted_zs = np.flipud(np.take_along_axis(zs, sort_index, axis=0))
1890
1971
 
1891
1972
  # 1c: Compute exceedance probabilities of water depths
1892
1973
  # Method: accumulate probabilities from top to bottom
@@ -1902,23 +1983,19 @@ class SfincsAdapter(IHazardAdapter):
1902
1983
  # h(T) = interp1 (log(T*), h*, log(T))
1903
1984
  # in which t* and h* are the values from the table and T is the return period (T) of interest
1904
1985
  # The resulting T-year water depths for all grids combined form the T-year hazard map
1905
- rp_da = xr.DataArray(rp_zs, dims=zs.dims)
1906
-
1907
- # no_data_value = -999 # in SFINCS
1908
- # sorted_zs = xr.where(sorted_zs == no_data_value, np.nan, sorted_zs)
1909
1986
 
1910
1987
  valid_cells = np.where(mask == 1)[
1911
1988
  0
1912
1989
  ] # only loop over cells where model is not masked
1913
- h = matlib.repmat(
1914
- np.copy(zb), len(return_periods), 1
1990
+ h = np.tile(
1991
+ zb, (len(return_periods), 1)
1915
1992
  ) # if not flooded (i.e. not in valid_cells) revert to bed_level, read from SFINCS results so it is the minimum bed level in a grid cell
1916
1993
 
1917
1994
  for jj in valid_cells: # looping over all non-masked cells.
1918
1995
  # linear interpolation for all return periods to evaluate
1919
1996
  h[:, jj] = np.interp(
1920
1997
  np.log10(return_periods),
1921
- np.log10(rp_da[::-1, jj]),
1998
+ np.log10(rp_zs[::-1, jj]),
1922
1999
  sorted_zs[::-1, jj],
1923
2000
  left=0,
1924
2001
  )
@@ -1935,16 +2012,10 @@ class SfincsAdapter(IHazardAdapter):
1935
2012
 
1936
2013
  rp_maps = []
1937
2014
  for ii, rp in enumerate(return_periods):
1938
- da = xr.DataArray(
1939
- data=h[ii, :], coords={"z": zs["z"]}, attrs={"units": "meters"}
1940
- )
1941
- if stacking:
1942
- # Ensure unstacking creates (y, x) dimensions in the correct order
1943
- da = da.unstack()
1944
- # Reorder dimensions if needed
1945
- if set(da.dims) == {"y", "x"} and da.dims != ("y", "x"):
1946
- da = da.transpose("y", "x")
1947
- # #create single nc
1948
- rp_maps.append(da)
2015
+ rp_maps.append(h[ii, :])
2016
+
2017
+ if reshape_needed:
2018
+ # Reshape back to 2D if needed
2019
+ rp_maps = [rp_map.reshape(shape_orig) for rp_map in rp_maps]
1949
2020
 
1950
2021
  return rp_maps
@@ -116,6 +116,7 @@ class DemModel(BaseModel):
116
116
 
117
117
  filename: str
118
118
  units: us.UnitTypesLength
119
+ index_filename: str = "index.tif"
119
120
 
120
121
 
121
122
  class DatumModel(BaseModel):
@@ -2,7 +2,6 @@ import datetime
2
2
  import gc
3
3
  import logging
4
4
  import math
5
- import os
6
5
  import re
7
6
  import shutil
8
7
  import warnings
@@ -22,6 +21,7 @@ import xarray as xr
22
21
  from hydromt_fiat import FiatModel as HydromtFiatModel
23
22
  from hydromt_fiat.data_apis.open_street_maps import get_buildings_from_osm
24
23
  from hydromt_sfincs import SfincsModel as HydromtSfincsModel
24
+ from hydromt_sfincs.workflows.downscaling import make_index_cog
25
25
  from pydantic import BaseModel, Field
26
26
  from shapely import MultiLineString, MultiPolygon, Polygon
27
27
  from shapely.ops import nearest_points
@@ -1414,61 +1414,60 @@ class DatabaseBuilder:
1414
1414
 
1415
1415
  @debug_timer
1416
1416
  def create_dem_model(self) -> DemModel:
1417
+ subgrid_sfincs_folder = Path(self.sfincs_overland_model.root) / "subgrid"
1418
+ subgrid_sfincs_folder_exist = subgrid_sfincs_folder.is_dir()
1417
1419
  if self.config.dem:
1418
1420
  subgrid_sfincs = Path(self.config.dem.filename)
1419
- delete_sfincs_folder = False
1420
1421
  else:
1421
1422
  logger.warning(
1422
1423
  "No subgrid depth geotiff file provided in the config file. Using the one from the SFINCS model."
1423
1424
  )
1424
- subgrid_sfincs_folder = Path(self.sfincs_overland_model.root) / "subgrid"
1425
- subgrid_sfincs = subgrid_sfincs_folder / "dep_subgrid.tif"
1426
- delete_sfincs_folder = True
1427
-
1428
- dem_file = self._check_exists_and_absolute(subgrid_sfincs)
1425
+ if not subgrid_sfincs_folder_exist:
1426
+ raise FileNotFoundError(
1427
+ f"Subgrid folder {subgrid_sfincs_folder} does not exist in the SFINCS model."
1428
+ )
1429
+ if self.sfincs_overland_model.grid_type == "quadtree":
1430
+ # If SFINCS is quadtree use the 0 level
1431
+ subgrid_sfincs = subgrid_sfincs_folder / "dep_subgrid_lev0.tif"
1432
+ else:
1433
+ subgrid_sfincs = subgrid_sfincs_folder / "dep_subgrid.tif"
1434
+ # Check that file exists and get absolute path
1435
+ dem_file = self._check_exists_and_absolute(str(subgrid_sfincs))
1436
+ # Define where it will be stored in the database
1429
1437
  fa_subgrid_path = self.static_path / "dem" / dem_file.name
1430
1438
  fa_subgrid_path.parent.mkdir(parents=True, exist_ok=True)
1431
1439
 
1432
- # Check tiles
1433
- tiles_sfincs = Path(self.sfincs_overland_model.root) / "tiles"
1434
- fa_tiles_path = self.static_path / "dem" / "tiles"
1435
- if tiles_sfincs.exists():
1436
- shutil.move(tiles_sfincs, fa_tiles_path)
1437
- if (fa_tiles_path / "index").exists():
1438
- os.rename(fa_tiles_path / "index", fa_tiles_path / "indices")
1439
- logger.info(
1440
- "Tiles were already available in the SFINCS model and will directly be used in FloodAdapt."
1441
- )
1442
- else:
1443
- # Make tiles
1444
- fa_tiles_path.mkdir(parents=True)
1445
- self.sfincs_overland_model.setup_tiles(
1446
- path=fa_tiles_path,
1447
- datasets_dep=[{"elevtn": dem_file}],
1448
- zoom_range=[0, 13],
1449
- fmt="png",
1450
- )
1451
- logger.info(
1452
- f"Tiles were created using the {subgrid_sfincs.as_posix()} as the elevation map."
1453
- )
1454
-
1455
1440
  shutil.copy2(dem_file, fa_subgrid_path)
1441
+
1442
+ fa_index_path = self.static_path / "dem" / "index.tif"
1443
+ # Make index cog
1444
+ make_index_cog(
1445
+ model=self.sfincs_overland_model,
1446
+ indices_fn=fa_index_path,
1447
+ topobathy_fn=fa_subgrid_path,
1448
+ )
1449
+ logger.info(
1450
+ f"An index file was created using the {subgrid_sfincs.as_posix()} as the elevation map, and save at {fa_index_path.as_posix()}."
1451
+ )
1452
+
1456
1453
  self._dem_path = fa_subgrid_path
1457
1454
 
1458
1455
  # Remove the original subgrid folder if it exists
1459
- if delete_sfincs_folder:
1460
- gc.collect()
1461
- if subgrid_sfincs_folder.exists() and subgrid_sfincs_folder.is_dir():
1462
- try:
1463
- shutil.rmtree(subgrid_sfincs_folder)
1464
- except Exception:
1465
- logger.warning(
1466
- f"Could not delete temporary SFINCS subgrid folder at {subgrid_sfincs_folder.as_posix()}."
1467
- )
1456
+ gc.collect()
1457
+ if subgrid_sfincs_folder_exist:
1458
+ try:
1459
+ shutil.rmtree(subgrid_sfincs_folder)
1460
+ except Exception:
1461
+ logger.warning(
1462
+ f"Could not delete temporary SFINCS subgrid folder at {subgrid_sfincs_folder.as_posix()}."
1463
+ )
1468
1464
 
1465
+ # Dem file always assumed to be in /static/dem
1469
1466
  return DemModel(
1470
- filename=fa_subgrid_path.name, units=us.UnitTypesLength.meters
1471
- ) # always in meters
1467
+ filename=fa_subgrid_path.name,
1468
+ units=us.UnitTypesLength.meters, # SINFCS always in meters
1469
+ index_filename=fa_index_path.name,
1470
+ )
1472
1471
 
1473
1472
  @debug_timer
1474
1473
  def create_sfincs_model_config(self) -> SfincsConfigModel:
@@ -1663,7 +1662,7 @@ class DatabaseBuilder:
1663
1662
  db_file_path.parent.mkdir(parents=True, exist_ok=True)
1664
1663
  shutil.copyfile(self.config.tide_gauge.file, db_file_path)
1665
1664
 
1666
- rel_db_path = Path(db_file_path.relative_to(self.static_path))
1665
+ rel_db_path = Path(db_file_path.relative_to(self.static_path)).as_posix()
1667
1666
  logger.warning(
1668
1667
  f"Tide gauge from file {rel_db_path} assumed to be in {self.unit_system.default_length_units}!"
1669
1668
  )
@@ -1795,26 +1794,22 @@ class DatabaseBuilder:
1795
1794
  if self.sfincs_offshore_model is None:
1796
1795
  return None
1797
1796
  # Connect boundary points of overland to output points of offshore
1797
+ # First read in the boundary locations from the overland model
1798
1798
  fn = Path(self.sfincs_overland_model.root) / "sfincs.bnd"
1799
- bnd = pd.read_csv(fn, sep=" ", lineterminator="\n", header=None)
1800
- bnd = bnd.rename(columns={0: "x", 1: "y"})
1801
- bnd_geo = gpd.GeoDataFrame(
1802
- bnd,
1803
- geometry=gpd.points_from_xy(bnd.x, bnd.y),
1799
+ lines = []
1800
+ if fn.exists():
1801
+ with open(fn) as f:
1802
+ lines = f.readlines()
1803
+ coords = [(float(line.split()[0]), float(line.split()[1])) for line in lines]
1804
+ x, y = zip(*coords)
1805
+ bnd = gpd.GeoDataFrame(
1806
+ geometry=gpd.points_from_xy(x, y),
1804
1807
  crs=self.sfincs_overland_model.config["epsg"],
1805
1808
  )
1806
- obs_geo = bnd_geo.to_crs(4326)
1807
- obs_geo["x"] = obs_geo.geometry.x
1808
- obs_geo["y"] = obs_geo.geometry.y
1809
- del obs_geo["geometry"]
1810
- obs_geo["name"] = [f"bnd_pt{num:02d}" for num in range(1, len(obs_geo) + 1)]
1811
- fn_off = Path(self.sfincs_offshore_model.root) / "sfincs.obs"
1812
- obs_geo.to_csv(
1813
- fn_off,
1814
- sep="\t",
1815
- index=False,
1816
- header=False,
1817
- )
1809
+ # Then transform points to offshore crs and save them as observation points
1810
+ obs_geo = bnd.to_crs(self.sfincs_offshore_model.config["epsg"])
1811
+ self.sfincs_offshore_model.setup_observation_points(obs_geo)
1812
+ self.sfincs_offshore_model.write()
1818
1813
  logger.info(
1819
1814
  "Output points of the offshore SFINCS model were reconfigured to the boundary points of the overland SFINCS model."
1820
1815
  )
@@ -55,16 +55,16 @@ class Database(IDatabase):
55
55
  static_path: Path
56
56
  output_path: Path
57
57
 
58
- _site: Site
58
+ site: Site
59
59
 
60
- _events: DbsEvent
61
- _scenarios: DbsScenario
62
- _strategies: DbsStrategy
63
- _measures: DbsMeasure
64
- _projections: DbsProjection
65
- _benefits: DbsBenefit
60
+ events: DbsEvent
61
+ scenarios: DbsScenario
62
+ strategies: DbsStrategy
63
+ measures: DbsMeasure
64
+ projections: DbsProjection
65
+ benefits: DbsBenefit
66
66
 
67
- _static: DbsStatic
67
+ static: DbsStatic
68
68
 
69
69
  def __new__(cls, *args, **kwargs):
70
70
  if not cls._instance: # Singleton pattern
@@ -112,32 +112,39 @@ class Database(IDatabase):
112
112
  self.database_name = database_name
113
113
 
114
114
  # Set the paths
115
-
116
115
  self.base_path = Path(database_path) / database_name
117
116
  self.input_path = db_path(TopLevelDir.input)
118
117
  self.static_path = db_path(TopLevelDir.static)
119
118
  self.output_path = db_path(TopLevelDir.output)
120
119
 
121
- self._site = Site.load_file(self.static_path / "config" / "site.toml")
120
+ # Read site configuration
121
+ self.read_site()
122
+
123
+ # Delete any unfinished/crashed scenario output after initialization
124
+ self._init_done = True
125
+ self.cleanup()
126
+
127
+ def read_site(self, site_name: str = "site") -> None:
128
+ """Read the site configuration from the static config folder and update database attributes."""
129
+ site_path = self.static_path / "config" / f"{site_name}.toml"
130
+ if not site_path.exists():
131
+ raise ConfigError(
132
+ f"Site configuration file '{site_path}' does not exist in the database."
133
+ )
134
+ self.site = Site.load_file(site_path)
122
135
 
123
136
  # Initialize the different database objects
124
- self._static = DbsStatic(self)
125
- self._events = DbsEvent(
126
- self, standard_objects=self.site.standard_objects.events
127
- )
128
- self._scenarios = DbsScenario(self)
129
- self._strategies = DbsStrategy(
137
+ self.static = DbsStatic(self)
138
+ self.events = DbsEvent(self, standard_objects=self.site.standard_objects.events)
139
+ self.scenarios = DbsScenario(self)
140
+ self.strategies = DbsStrategy(
130
141
  self, standard_objects=self.site.standard_objects.strategies
131
142
  )
132
- self._measures = DbsMeasure(self)
133
- self._projections = DbsProjection(
143
+ self.measures = DbsMeasure(self)
144
+ self.projections = DbsProjection(
134
145
  self, standard_objects=self.site.standard_objects.projections
135
146
  )
136
- self._benefits = DbsBenefit(self)
137
- self._init_done = True
138
-
139
- # Delete any unfinished/crashed scenario output after initialization
140
- self.cleanup()
147
+ self.benefits = DbsBenefit(self)
141
148
 
142
149
  def shutdown(self):
143
150
  """Explicitly shut down the singleton and clear all references."""
@@ -148,39 +155,6 @@ class Database(IDatabase):
148
155
  self.__dict__.clear()
149
156
  gc.collect()
150
157
 
151
- # Property methods
152
- @property
153
- def site(self) -> Site:
154
- return self._site
155
-
156
- @property
157
- def static(self) -> DbsStatic:
158
- return self._static
159
-
160
- @property
161
- def events(self) -> DbsEvent:
162
- return self._events
163
-
164
- @property
165
- def scenarios(self) -> DbsScenario:
166
- return self._scenarios
167
-
168
- @property
169
- def strategies(self) -> DbsStrategy:
170
- return self._strategies
171
-
172
- @property
173
- def measures(self) -> DbsMeasure:
174
- return self._measures
175
-
176
- @property
177
- def projections(self) -> DbsProjection:
178
- return self._projections
179
-
180
- @property
181
- def benefits(self) -> DbsBenefit:
182
- return self._benefits
183
-
184
158
  def get_slr_scenarios(self) -> SlrScenariosModel:
185
159
  """Get the path to the SLR scenarios file.
186
160
 
@@ -203,7 +177,7 @@ class Database(IDatabase):
203
177
  dict[str, Any]
204
178
  Includes 'name', 'path', 'last_modification_date' and "finished" info
205
179
  """
206
- all_scenarios = pd.DataFrame(self._scenarios.summarize_objects())
180
+ all_scenarios = pd.DataFrame(self.scenarios.summarize_objects())
207
181
  if len(all_scenarios) > 0:
208
182
  df = all_scenarios[all_scenarios["finished"]]
209
183
  else:
@@ -231,14 +205,14 @@ class Database(IDatabase):
231
205
 
232
206
  if mode == Mode.single_event:
233
207
  if _type == FloodmapType.water_level:
234
- paths = [base_dir / "max_water_level_map.nc"]
208
+ paths = [base_dir / "max_water_level_map.tif"]
235
209
  elif _type == FloodmapType.water_depth:
236
210
  paths = [base_dir / f"FloodMap_{scenario_name}.tif"]
237
211
  elif mode == Mode.risk:
238
212
  if _type == FloodmapType.water_level:
239
- paths = list(base_dir.glob("RP_*_maps.nc"))
213
+ paths = list(base_dir.glob("RP_*_max_water_level_map.tif"))
240
214
  elif _type == FloodmapType.water_depth:
241
- paths = list(base_dir.glob("RP_*_maps.tif"))
215
+ paths = list(base_dir.glob("RP_*_FloodMap.tif"))
242
216
  else:
243
217
  raise DatabaseError(
244
218
  f"Flood map type '{_type}' is not valid. Must be one of 'water_level' or 'water_depth'."
@@ -284,7 +258,7 @@ class Database(IDatabase):
284
258
  str
285
259
  path to topobathy tiles
286
260
  """
287
- path = self.input_path.parent.joinpath("static", "dem", "tiles", "topobathy")
261
+ path = self.static_path / "dem" / self.site.sfincs.dem.filename
288
262
  return path.as_posix()
289
263
 
290
264
  def get_index_path(self) -> str:
@@ -295,7 +269,7 @@ class Database(IDatabase):
295
269
  str
296
270
  path to index tiles
297
271
  """
298
- path = self.input_path.parent.joinpath("static", "dem", "tiles", "indices")
272
+ path = self.static_path / "dem" / self.site.sfincs.dem.index_filename
299
273
  return path.as_posix()
300
274
 
301
275
  def get_depth_conversion(self) -> float:
@@ -333,23 +307,30 @@ class Database(IDatabase):
333
307
  np.array
334
308
  2D map of maximum water levels
335
309
  """
336
- # If single event read with hydromt-sfincs
310
+ output_path = self.scenarios.output_path.joinpath(scenario_name, "Flooding")
311
+ # Check which file to use (if quadtree or regular)
337
312
  if not return_period:
338
- map_path = self.scenarios.output_path.joinpath(
339
- scenario_name,
340
- "Flooding",
341
- "max_water_level_map.nc",
342
- )
343
- with xr.open_dataarray(map_path) as map:
344
- zsmax = map.to_numpy()
313
+ qt_path = output_path.joinpath("max_water_level_map_qt.nc")
314
+ if qt_path.is_file():
315
+ map_path = qt_path
316
+ else:
317
+ map_path = output_path.joinpath("max_water_level_map.tif")
345
318
  else:
346
- file_path = self.scenarios.output_path.joinpath(
347
- scenario_name,
348
- "Flooding",
349
- f"RP_{return_period:04d}_maps.nc",
319
+ qt_path = output_path.joinpath(
320
+ f"RP_{return_period:04d}_max_water_level_map_qt.nc"
350
321
  )
351
- with xr.open_dataset(file_path) as ds:
352
- zsmax = ds["risk_map"][:, :].to_numpy().T
322
+ if qt_path.is_file():
323
+ map_path = qt_path
324
+ else:
325
+ map_path = output_path.joinpath(
326
+ f"RP_{return_period:04d}_max_water_level_map.tif"
327
+ )
328
+ # Open file
329
+ with xr.open_dataarray(map_path) as map:
330
+ zsmax = map.to_numpy()
331
+ # Make sure output is 1D array
332
+ if zsmax.ndim >= 2:
333
+ zsmax = zsmax.flatten("F")
353
334
  return zsmax
354
335
 
355
336
  def get_flood_map_geotiff(
@@ -17,37 +17,14 @@ class IDatabase(ABC):
17
17
  output_path: Path
18
18
  static_path: Path
19
19
 
20
- @property
21
- @abstractmethod
22
- def site(self) -> Site: ...
23
-
24
- @property
25
- @abstractmethod
26
- def static(self) -> IDbsStatic: ...
27
-
28
- @property
29
- @abstractmethod
30
- def events(self) -> AbstractDatabaseElement: ...
31
-
32
- @property
33
- @abstractmethod
34
- def scenarios(self) -> AbstractDatabaseElement: ...
35
-
36
- @property
37
- @abstractmethod
38
- def strategies(self) -> AbstractDatabaseElement: ...
39
-
40
- @property
41
- @abstractmethod
42
- def measures(self) -> AbstractDatabaseElement: ...
43
-
44
- @property
45
- @abstractmethod
46
- def projections(self) -> AbstractDatabaseElement: ...
47
-
48
- @property
49
- @abstractmethod
50
- def benefits(self) -> AbstractDatabaseElement: ...
20
+ site: Site
21
+ static: IDbsStatic
22
+ events: AbstractDatabaseElement
23
+ scenarios: AbstractDatabaseElement
24
+ strategies: AbstractDatabaseElement
25
+ measures: AbstractDatabaseElement
26
+ projections: AbstractDatabaseElement
27
+ benefits: AbstractDatabaseElement
51
28
 
52
29
  @abstractmethod
53
30
  def __init__(
@@ -8,6 +8,7 @@ import requests
8
8
  from noaa_coops.station import COOPSAPIError
9
9
  from pydantic import BaseModel, model_validator
10
10
 
11
+ from flood_adapt.config import Settings
11
12
  from flood_adapt.misc.log import FloodAdaptLogging
12
13
  from flood_adapt.objects.forcing import unit_system as us
13
14
  from flood_adapt.objects.forcing.time_frame import TimeFrame
@@ -56,7 +57,7 @@ class TideGauge(BaseModel):
56
57
  source: TideGaugeSource
57
58
  reference: str
58
59
  ID: Optional[int] = None # Attribute used to download from correct gauge
59
- file: Optional[Path] = None # for locally stored data
60
+ file: Optional[str] = None # for locally stored data
60
61
  lat: Optional[float] = None
61
62
  lon: Optional[float] = None
62
63
  units: us.UnitTypesLength = (
@@ -104,7 +105,8 @@ class TideGauge(BaseModel):
104
105
  """
105
106
  logger.info(f"Retrieving waterlevels for tide gauge {self.ID} for {time}")
106
107
  if self.file:
107
- gauge_data = self._read_imported_waterlevels(time=time, path=self.file)
108
+ abs_path = Settings().database_path / "static" / self.file
109
+ gauge_data = self._read_imported_waterlevels(time=time, path=abs_path)
108
110
  else:
109
111
  gauge_data = self._download_tide_gauge_data(time=time)
110
112
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: flood-adapt
3
- Version: 1.1.4
3
+ Version: 2.0.0
4
4
  Summary: A software package support system which can be used to assess the benefits and costs of flood resilience measures
5
5
  Author-email: Gundula Winter <Gundula.Winter@deltares.nl>, Panos Athanasiou <Panos.Athanasiou@deltares.nl>, Frederique de Groen <Frederique.deGroen@deltares.nl>, Tim de Wilde <Tim.deWilde@deltares.nl>, Julian Hofer <Julian.Hofer@deltares.nl>, Daley Adrichem <Daley.Adrichem@deltares.nl>, Luuk Blom <Luuk.Blom@deltares.nl>
6
6
  License: ====================================================
@@ -717,7 +717,7 @@ Requires-Dist: fiona<2.0,>=1.0
717
717
  Requires-Dist: geojson<4.0,>=3.0
718
718
  Requires-Dist: geopandas<2.0,>=1.0
719
719
  Requires-Dist: hydromt-fiat<1.0,>=0.5.9
720
- Requires-Dist: hydromt-sfincs<2.0,>=1.2.2
720
+ Requires-Dist: hydromt-sfincs[quadtree]<2.0,>=1.2.2
721
721
  Requires-Dist: numpy<2.0,>=1.0
722
722
  Requires-Dist: numpy-financial<2.0,>=1.0
723
723
  Requires-Dist: pandas<3.0,>=2.0
@@ -1,8 +1,8 @@
1
- flood_adapt/__init__.py,sha256=HuJSWvtJutuwuiXwgvArRmpnG72guATUP3IFvzowaJY,779
1
+ flood_adapt/__init__.py,sha256=0Mmepm1Ybf2X10LM0cEeqbQkKiFRRCtOR95q9MEt58s,779
2
2
  flood_adapt/flood_adapt.py,sha256=HVFS4OFhcB0TqHtMw3kbEei0IfJxsciauHfG3XZ38-0,40747
3
3
  flood_adapt/adapter/__init__.py,sha256=vnF8NCkEVX-N-gtGS-J_A1H1YYAjihWjJZFyYGwcp8Q,180
4
- flood_adapt/adapter/fiat_adapter.py,sha256=seDjPoumkhUOd7qer3ni1_Ut3dwyq0-_yhJNaTEFc2E,60284
5
- flood_adapt/adapter/sfincs_adapter.py,sha256=_S-fABsi4flOkbrbIy_ROTuZrJyIMZtJu2wNh-TYSYs,79040
4
+ flood_adapt/adapter/fiat_adapter.py,sha256=qUuyPeT9W2o_g8e8ZW38U7pCjLNqXY2MbPMk7_O2J-w,61579
5
+ flood_adapt/adapter/sfincs_adapter.py,sha256=fuZOmLijmmz8RZY4aqSRUs_vAbcGrVwI4hmyTtaMd5c,82314
6
6
  flood_adapt/adapter/sfincs_offshore.py,sha256=DkqGwx0Fx4dojY1YH8tW3MUS4Omgd5DC6QINEsTP0Uk,7659
7
7
  flood_adapt/adapter/interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  flood_adapt/adapter/interface/hazard_adapter.py,sha256=S2NIUAMSRgxC_E-tZRJ2qIP06U1zEVdn-MnvMTrn86s,2828
@@ -13,12 +13,12 @@ flood_adapt/config/__init__.py,sha256=wfcPGORAGBXWUi8G7E5tLWvBS9A2ajJ9atHfxatExj
13
13
  flood_adapt/config/config.py,sha256=-aMmeBLE03O0Az-S9ms6Rz5hX8_5Kn2vr7IjjMI9w2o,9374
14
14
  flood_adapt/config/fiat.py,sha256=svx3zRp8ZcHifTl0hLbl2JCeYuqckxQFJjDd-C0oqok,3417
15
15
  flood_adapt/config/gui.py,sha256=9VFfzQOjRs4E6sY0bweBUpPGgHfKc7Az7yBlNetotcg,16784
16
- flood_adapt/config/hazard.py,sha256=Ev6mj78cZ_vQuJ11KYjhJOzmfRB6fz267OJeKI0bYaM,12216
16
+ flood_adapt/config/hazard.py,sha256=NUBgIF7a72cSGPFsZtNHjjt6Uvov6TcEYJKrDl7WdV4,12255
17
17
  flood_adapt/config/impacts.py,sha256=O7vE7jB3GSXnkqAvv7TqJiJ_j1uJ3mck_KQ-ScsB3bo,3192
18
18
  flood_adapt/config/sfincs.py,sha256=y8C3PzFwwgMB_sb8rBzgteaQ8fCxep6DnZxuk0q__bc,4825
19
19
  flood_adapt/config/site.py,sha256=VR90jCHWcxgoQJptNyXy7LseGjXUDRtdOjNGCddFVzI,4328
20
20
  flood_adapt/database_builder/__init__.py,sha256=h4ietZ6sAZa7j2kvSzp5-58BueGrfJsXvq8PFu1RLyI,1112
21
- flood_adapt/database_builder/database_builder.py,sha256=cv_uwkKqgc8RLQ5lAmTqRN1fm5gBOYbIVR3v2MgD_gY,109935
21
+ flood_adapt/database_builder/database_builder.py,sha256=GLhgceyj0R671XOQNbEah4d-OL3UBQN8IW3LgdZN4rQ,109986
22
22
  flood_adapt/database_builder/metrics_utils.py,sha256=aU7YfXLmBjFT0fQQQl3o0yIzdFJ6XJGlld0GnkJytGc,66258
23
23
  flood_adapt/database_builder/templates/default_units/imperial.toml,sha256=zIjPlxIa2kWLUjSYisd8UolXGo5iKdFoDDz_JkKBXTM,295
24
24
  flood_adapt/database_builder/templates/default_units/metric.toml,sha256=tc0XMKs7xGL9noB9lAb0gyQfjYxzokgHa3NqpccxWl0,302
@@ -56,7 +56,7 @@ flood_adapt/database_builder/templates/infographics/images/truck.png,sha256=0Ihv
56
56
  flood_adapt/database_builder/templates/infographics/images/walking_person.png,sha256=vaxO4oGejK5Q4KyGXuew2YgpGPabpnwLyxTFH1WMbmo,11080
57
57
  flood_adapt/database_builder/templates/output_layers/bin_colors.toml,sha256=yN3_h2IimOyjtfhZ-ZoWyNa-2cAeFRNlbvaNTLhEMfA,417
58
58
  flood_adapt/dbs_classes/__init__.py,sha256=J-a6BEkjDhuUzzRKuAn_AtTg_D9wNIsmY3BnVTiC2JA,731
59
- flood_adapt/dbs_classes/database.py,sha256=2OxzIrZOLmgv3wpq6cIKer1gchM6MuU2S9r2cHUZ4i8,23429
59
+ flood_adapt/dbs_classes/database.py,sha256=D-x3n_YT230nccr30w-2LIy4VJYY4C1KrR79-uZibys,23447
60
60
  flood_adapt/dbs_classes/dbs_benefit.py,sha256=ayEYz8ga49HLdYuUsDWZOuZnpRnBpTuyhvfe2IyWAKI,1825
61
61
  flood_adapt/dbs_classes/dbs_event.py,sha256=ak3kHan6L1EfC8agDLKiCe8gaY5leOmj_qUBsI61q9A,1869
62
62
  flood_adapt/dbs_classes/dbs_measure.py,sha256=vVs-LtnHJN7eSGIFUglJdpbtfq_QI_Ftkv4lh5mfnNM,4085
@@ -65,7 +65,7 @@ flood_adapt/dbs_classes/dbs_scenario.py,sha256=LHWx3Dr1XR47bPyPRkR70h3VcT0f0MVgB
65
65
  flood_adapt/dbs_classes/dbs_static.py,sha256=Yzs-bsfAq2jkZ_-0_ojuzNf81Wifaxw8a1APNNS0mqM,10565
66
66
  flood_adapt/dbs_classes/dbs_strategy.py,sha256=qiEObHZeYL93GmdjSiGQls1ZmxdMZPkRkwzHgmoYwyE,4856
67
67
  flood_adapt/dbs_classes/dbs_template.py,sha256=b2x2sWNYTnaWU8Plgp51PFPrZGEv2kRRn9JBAgYhLbI,11578
68
- flood_adapt/dbs_classes/interface/database.py,sha256=kPHsmreB-vHaFD_FRXbP06tVs7pGxt4Rucim6aEqiKg,2550
68
+ flood_adapt/dbs_classes/interface/database.py,sha256=uScyJ4HqKZv_zR5s_I-dRjkySL4WnSZ6zfpJiv-VG10,2112
69
69
  flood_adapt/dbs_classes/interface/element.py,sha256=XN3SjfEiAa4oZ61XJNnHkfKQu5Da42EH8PEkfosIJ9w,3528
70
70
  flood_adapt/dbs_classes/interface/static.py,sha256=amChHlParELA4vFMUn_kL1fx6z7fCFW-hJXBo7PGtbY,1588
71
71
  flood_adapt/misc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -95,7 +95,7 @@ flood_adapt/objects/forcing/meteo_handler.py,sha256=rTxY5WNobK_Ifzj2eVcoSPGgb3Tz
95
95
  flood_adapt/objects/forcing/netcdf.py,sha256=ZBzFtN5joVs36lVjvYErVaHEylUQ6eKIhR0uk_MD-zM,1388
96
96
  flood_adapt/objects/forcing/plotting.py,sha256=Y7f_9bY8d9jbd7BqEAeRmof-aaJhlznM3_wGBOI7g-s,14828
97
97
  flood_adapt/objects/forcing/rainfall.py,sha256=e6P3IMzItvnsmXbcMXl1oV-d9LDuh3jTIc_vt6Kz5zo,3282
98
- flood_adapt/objects/forcing/tide_gauge.py,sha256=XhplyNHtCn0hRM1oeD5v-fMYAOLAJIKidmxKxVxCUlw,7188
98
+ flood_adapt/objects/forcing/tide_gauge.py,sha256=uPgvlcvDtZFQrtTyXX2d5YmSvCnjYYg0Xsu8NAwQZPQ,7299
99
99
  flood_adapt/objects/forcing/time_frame.py,sha256=1X3G0Ax18BHRvAomf-CW_ISRk_3qgAakwgZCIBxIkL4,2855
100
100
  flood_adapt/objects/forcing/timeseries.py,sha256=bD27JWzC3owq5ah3zPzJ7xoUzSH_t4J03s_SycYW0mQ,19740
101
101
  flood_adapt/objects/forcing/unit_system.py,sha256=7FFOmaxq6EOvXx64QDxlpNU4uMExqridFcdFwyTJ4Lo,16542
@@ -114,8 +114,8 @@ flood_adapt/objects/strategies/strategies.py,sha256=Jw-WJDCamL9p_7VEir3AdmYPMVAi
114
114
  flood_adapt/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
115
115
  flood_adapt/workflows/benefit_runner.py,sha256=eA21TuHdeZ6QYO8ehXri6BHlkyHsVsZphIdIca5g0KA,21824
116
116
  flood_adapt/workflows/scenario_runner.py,sha256=9_Y6GmMYhYoTRkBUIlju0eBy6DosGf4Zl2tgu1QEubI,4119
117
- flood_adapt-1.1.4.dist-info/LICENSE,sha256=Ui5E03pQ0EVKxvKA54lTPA1xrtgA2HMGLQai95eOzoE,36321
118
- flood_adapt-1.1.4.dist-info/METADATA,sha256=70kUiml9cQzOes7B0XAgpqzYCU1kAYuhSAiXLdmqa8U,48806
119
- flood_adapt-1.1.4.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
120
- flood_adapt-1.1.4.dist-info/top_level.txt,sha256=JvzMi6cTcQPEThCfpgMEeVny3ghI1urSH0CCgVIqSzw,12
121
- flood_adapt-1.1.4.dist-info/RECORD,,
117
+ flood_adapt-2.0.0.dist-info/LICENSE,sha256=Ui5E03pQ0EVKxvKA54lTPA1xrtgA2HMGLQai95eOzoE,36321
118
+ flood_adapt-2.0.0.dist-info/METADATA,sha256=Wwh0sx_A5X37qDXGKZoTHgPUQiDtZe1nUOUeodSFt7I,48816
119
+ flood_adapt-2.0.0.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
120
+ flood_adapt-2.0.0.dist-info/top_level.txt,sha256=JvzMi6cTcQPEThCfpgMEeVny3ghI1urSH0CCgVIqSzw,12
121
+ flood_adapt-2.0.0.dist-info/RECORD,,