flood-adapt 1.1.3__py3-none-any.whl → 1.1.5__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/database_builder/__init__.py +10 -0
- flood_adapt/database_builder/database_builder.py +215 -51
- flood_adapt/dbs_classes/dbs_scenario.py +2 -1
- flood_adapt/objects/events/events.py +57 -0
- flood_adapt/objects/forcing/forcing.py +46 -1
- flood_adapt/objects/forcing/tide_gauge.py +4 -2
- flood_adapt/objects/forcing/unit_system.py +4 -2
- {flood_adapt-1.1.3.dist-info → flood_adapt-1.1.5.dist-info}/METADATA +2 -4
- {flood_adapt-1.1.3.dist-info → flood_adapt-1.1.5.dist-info}/RECORD +13 -13
- {flood_adapt-1.1.3.dist-info → flood_adapt-1.1.5.dist-info}/LICENSE +0 -0
- {flood_adapt-1.1.3.dist-info → flood_adapt-1.1.5.dist-info}/WHEEL +0 -0
- {flood_adapt-1.1.3.dist-info → flood_adapt-1.1.5.dist-info}/top_level.txt +0 -0
flood_adapt/__init__.py
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
+
from flood_adapt.config import (
|
|
2
|
+
FloodModel,
|
|
3
|
+
SlrScenariosModel,
|
|
4
|
+
)
|
|
1
5
|
from flood_adapt.database_builder.database_builder import (
|
|
2
6
|
Basins,
|
|
3
7
|
ConfigModel,
|
|
4
8
|
FootprintsOptions,
|
|
5
9
|
GuiConfigModel,
|
|
10
|
+
ObsPointModel,
|
|
6
11
|
SpatialJoinModel,
|
|
7
12
|
SviConfigModel,
|
|
8
13
|
TideGaugeConfigModel,
|
|
14
|
+
TideGaugeSource,
|
|
9
15
|
UnitSystems,
|
|
10
16
|
create_database,
|
|
11
17
|
)
|
|
@@ -28,6 +34,7 @@ __all__ = [
|
|
|
28
34
|
"SpatialJoinModel",
|
|
29
35
|
"SviConfigModel",
|
|
30
36
|
"TideGaugeConfigModel",
|
|
37
|
+
"TideGaugeSource",
|
|
31
38
|
"UnitSystems",
|
|
32
39
|
"create_database",
|
|
33
40
|
"BuildingsInfographicModel",
|
|
@@ -37,4 +44,7 @@ __all__ = [
|
|
|
37
44
|
"RiskInfographicModel",
|
|
38
45
|
"RoadsInfographicModel",
|
|
39
46
|
"ImpactCategoriesModel",
|
|
47
|
+
"FloodModel",
|
|
48
|
+
"SlrScenariosModel",
|
|
49
|
+
"ObsPointModel",
|
|
40
50
|
]
|
|
@@ -17,12 +17,14 @@ import numpy as np
|
|
|
17
17
|
import pandas as pd
|
|
18
18
|
import rioxarray as rxr
|
|
19
19
|
import tomli
|
|
20
|
+
import tomli_w
|
|
20
21
|
import xarray as xr
|
|
21
22
|
from hydromt_fiat import FiatModel as HydromtFiatModel
|
|
22
23
|
from hydromt_fiat.data_apis.open_street_maps import get_buildings_from_osm
|
|
23
24
|
from hydromt_sfincs import SfincsModel as HydromtSfincsModel
|
|
24
25
|
from pydantic import BaseModel, Field
|
|
25
26
|
from shapely import MultiLineString, MultiPolygon, Polygon
|
|
27
|
+
from shapely.ops import nearest_points
|
|
26
28
|
|
|
27
29
|
from flood_adapt.adapter.fiat_adapter import _FIAT_COLUMNS
|
|
28
30
|
from flood_adapt.config.fiat import (
|
|
@@ -133,6 +135,31 @@ def path_check(str_path: str, config_path: Optional[Path] = None) -> str:
|
|
|
133
135
|
return path.as_posix()
|
|
134
136
|
|
|
135
137
|
|
|
138
|
+
def make_relative(str_path: str | Path, toml_path: Path) -> str:
|
|
139
|
+
"""Make a path relative to the config file path.
|
|
140
|
+
|
|
141
|
+
Parameters
|
|
142
|
+
----------
|
|
143
|
+
str_path : str | Path
|
|
144
|
+
The path to be made relative.
|
|
145
|
+
toml_path : Path
|
|
146
|
+
The path to the config file.
|
|
147
|
+
|
|
148
|
+
Returns
|
|
149
|
+
-------
|
|
150
|
+
str
|
|
151
|
+
The relative path as a string.
|
|
152
|
+
"""
|
|
153
|
+
path = Path(str_path)
|
|
154
|
+
if not path.is_absolute():
|
|
155
|
+
return path.as_posix()
|
|
156
|
+
try:
|
|
157
|
+
relative_path = path.relative_to(toml_path.parent)
|
|
158
|
+
return relative_path.as_posix()
|
|
159
|
+
except ValueError:
|
|
160
|
+
return path.as_posix()
|
|
161
|
+
|
|
162
|
+
|
|
136
163
|
class SpatialJoinModel(BaseModel):
|
|
137
164
|
"""
|
|
138
165
|
Model for representing a spatial join between geometries and tabular data.
|
|
@@ -421,6 +448,8 @@ class ConfigModel(BaseModel):
|
|
|
421
448
|
config.database_path = path_check(config.database_path, toml_path)
|
|
422
449
|
config.fiat = path_check(config.fiat, toml_path)
|
|
423
450
|
config.sfincs_overland.name = path_check(config.sfincs_overland.name, toml_path)
|
|
451
|
+
if config.dem:
|
|
452
|
+
config.dem.filename = path_check(config.dem.filename, toml_path)
|
|
424
453
|
if config.sfincs_offshore:
|
|
425
454
|
config.sfincs_offshore.name = path_check(
|
|
426
455
|
config.sfincs_offshore.name, toml_path
|
|
@@ -445,6 +474,67 @@ class ConfigModel(BaseModel):
|
|
|
445
474
|
|
|
446
475
|
return config
|
|
447
476
|
|
|
477
|
+
def write(self, toml_path: Path) -> None:
|
|
478
|
+
"""
|
|
479
|
+
Write the configuration model to a TOML file.
|
|
480
|
+
|
|
481
|
+
Parameters
|
|
482
|
+
----------
|
|
483
|
+
toml_path : Path
|
|
484
|
+
The path to the TOML file where the configuration will be saved.
|
|
485
|
+
"""
|
|
486
|
+
config_dict = self.model_dump(exclude_none=True)
|
|
487
|
+
|
|
488
|
+
# Make paths relative to the config file
|
|
489
|
+
config_dict["database_path"] = make_relative(
|
|
490
|
+
config_dict["database_path"], toml_path
|
|
491
|
+
)
|
|
492
|
+
config_dict["fiat"] = make_relative(config_dict["fiat"], toml_path)
|
|
493
|
+
config_dict["sfincs_overland"]["name"] = make_relative(
|
|
494
|
+
config_dict["sfincs_overland"]["name"], toml_path
|
|
495
|
+
)
|
|
496
|
+
if self.dem:
|
|
497
|
+
config_dict["dem"]["filename"] = make_relative(
|
|
498
|
+
config_dict["dem"]["filename"], toml_path
|
|
499
|
+
)
|
|
500
|
+
if config_dict.get("sfincs_offshore"):
|
|
501
|
+
config_dict["sfincs_offshore"]["name"] = make_relative(
|
|
502
|
+
config_dict["sfincs_offshore"]["name"], toml_path
|
|
503
|
+
)
|
|
504
|
+
if isinstance(self.building_footprints, SpatialJoinModel):
|
|
505
|
+
config_dict["building_footprints"]["file"] = make_relative(
|
|
506
|
+
config_dict["building_footprints"]["file"], toml_path
|
|
507
|
+
)
|
|
508
|
+
if self.tide_gauge and self.tide_gauge.file:
|
|
509
|
+
config_dict["tide_gauge"]["file"] = make_relative(
|
|
510
|
+
config_dict["tide_gauge"]["file"], toml_path
|
|
511
|
+
)
|
|
512
|
+
if self.svi:
|
|
513
|
+
config_dict["svi"]["file"] = make_relative(
|
|
514
|
+
config_dict["svi"]["file"], toml_path
|
|
515
|
+
)
|
|
516
|
+
if self.bfe:
|
|
517
|
+
config_dict["bfe"]["file"] = make_relative(
|
|
518
|
+
config_dict["bfe"]["file"], toml_path
|
|
519
|
+
)
|
|
520
|
+
if self.slr_scenarios:
|
|
521
|
+
config_dict["slr_scenarios"]["file"] = make_relative(
|
|
522
|
+
config_dict["slr_scenarios"]["file"], toml_path
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
if config_dict.get("probabilistic_set"):
|
|
526
|
+
config_dict["probabilistic_set"] = make_relative(
|
|
527
|
+
config_dict["probabilistic_set"], toml_path
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
if config_dict.get("aggregation_areas"):
|
|
531
|
+
for ag in config_dict["aggregation_areas"]:
|
|
532
|
+
ag["file"] = make_relative(ag["file"], toml_path)
|
|
533
|
+
|
|
534
|
+
toml_path.parent.mkdir(parents=True, exist_ok=True)
|
|
535
|
+
with open(toml_path, mode="wb") as fp:
|
|
536
|
+
tomli_w.dump(config_dict, fp)
|
|
537
|
+
|
|
448
538
|
|
|
449
539
|
class DatabaseBuilder:
|
|
450
540
|
_has_roads: bool = False
|
|
@@ -472,15 +562,16 @@ class DatabaseBuilder:
|
|
|
472
562
|
@debug_timer
|
|
473
563
|
def build(self, overwrite: bool = False) -> None:
|
|
474
564
|
# Check if database already exists
|
|
475
|
-
if self.root.exists()
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
565
|
+
if self.root.exists():
|
|
566
|
+
if overwrite:
|
|
567
|
+
shutil.rmtree(self.root)
|
|
568
|
+
warnings.warn(
|
|
569
|
+
f"There is already a Database folder in '{self.root.as_posix()}, which will be overwritten'."
|
|
570
|
+
)
|
|
571
|
+
else:
|
|
572
|
+
raise ValueError(
|
|
573
|
+
f"There is already a Database folder in '{self.root.as_posix()}'."
|
|
574
|
+
)
|
|
484
575
|
# Create database folder
|
|
485
576
|
self.root.mkdir(parents=True)
|
|
486
577
|
|
|
@@ -823,7 +914,7 @@ class DatabaseBuilder:
|
|
|
823
914
|
exposure[_FIAT_COLUMNS.ground_elevation] = exposure["elev"]
|
|
824
915
|
del exposure["elev"]
|
|
825
916
|
|
|
826
|
-
self.fiat_model.exposure.exposure_db = exposure
|
|
917
|
+
self.fiat_model.exposure.exposure_db = self._clean_suffix_columns(exposure)
|
|
827
918
|
|
|
828
919
|
def read_damage_unit(self) -> str:
|
|
829
920
|
if self.fiat_model.exposure.damage_unit is None:
|
|
@@ -1087,7 +1178,9 @@ class DatabaseBuilder:
|
|
|
1087
1178
|
exposure_csv = exposure_csv.merge(
|
|
1088
1179
|
gdf_joined, on=_FIAT_COLUMNS.object_id, how="left"
|
|
1089
1180
|
)
|
|
1090
|
-
self.fiat_model.exposure.exposure_db =
|
|
1181
|
+
self.fiat_model.exposure.exposure_db = self._clean_suffix_columns(
|
|
1182
|
+
exposure_csv
|
|
1183
|
+
)
|
|
1091
1184
|
# Update spatial joins in FIAT model
|
|
1092
1185
|
if self.fiat_model.spatial_joins["aggregation_areas"] is None:
|
|
1093
1186
|
self.fiat_model.spatial_joins["aggregation_areas"] = []
|
|
@@ -1149,7 +1242,9 @@ class DatabaseBuilder:
|
|
|
1149
1242
|
exposure_csv = exposure_csv.merge(
|
|
1150
1243
|
gdf_joined, on=_FIAT_COLUMNS.object_id, how="left"
|
|
1151
1244
|
)
|
|
1152
|
-
self.fiat_model.exposure.exposure_db =
|
|
1245
|
+
self.fiat_model.exposure.exposure_db = self._clean_suffix_columns(
|
|
1246
|
+
exposure_csv
|
|
1247
|
+
)
|
|
1153
1248
|
logger.warning(
|
|
1154
1249
|
"No aggregation areas were available in the FIAT model and none were provided in the config file. The region file will be used as a mock aggregation area."
|
|
1155
1250
|
)
|
|
@@ -1180,7 +1275,9 @@ class DatabaseBuilder:
|
|
|
1180
1275
|
exposure_csv = exposure_csv.merge(
|
|
1181
1276
|
buildings_joined, on=_FIAT_COLUMNS.object_id, how="left"
|
|
1182
1277
|
)
|
|
1183
|
-
self.fiat_model.exposure.exposure_db =
|
|
1278
|
+
self.fiat_model.exposure.exposure_db = self._clean_suffix_columns(
|
|
1279
|
+
exposure_csv
|
|
1280
|
+
)
|
|
1184
1281
|
|
|
1185
1282
|
# Save the spatial file for future use
|
|
1186
1283
|
svi_path = self.static_path / "templates" / "fiat" / "svi" / "svi.gpkg"
|
|
@@ -1199,8 +1296,15 @@ class DatabaseBuilder:
|
|
|
1199
1296
|
"'SVI' column present in the FIAT exposure csv. Vulnerability type infometrics can be produced."
|
|
1200
1297
|
)
|
|
1201
1298
|
add_attrs = self.fiat_model.spatial_joins["additional_attributes"]
|
|
1299
|
+
if add_attrs is None:
|
|
1300
|
+
logger.warning(
|
|
1301
|
+
"'SVI' column present in the FIAT exposure csv, but no spatial join found with the SVI map."
|
|
1302
|
+
)
|
|
1303
|
+
return None
|
|
1304
|
+
|
|
1202
1305
|
if "SVI" not in [attr["name"] for attr in add_attrs]:
|
|
1203
1306
|
logger.warning("No SVI map found to display in the FloodAdapt GUI!")
|
|
1307
|
+
return None
|
|
1204
1308
|
|
|
1205
1309
|
ind = [attr["name"] for attr in add_attrs].index("SVI")
|
|
1206
1310
|
svi = add_attrs[ind]
|
|
@@ -1407,19 +1511,44 @@ class DatabaseBuilder:
|
|
|
1407
1511
|
logger.info("Observation points were provided in the config file.")
|
|
1408
1512
|
obs_points = self.config.obs_point
|
|
1409
1513
|
|
|
1514
|
+
model_region = self.sfincs_overland_model.region.union_all()
|
|
1515
|
+
|
|
1410
1516
|
if self.tide_gauge is not None:
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
ID=self.tide_gauge.ID,
|
|
1419
|
-
lon=self.tide_gauge.lon,
|
|
1420
|
-
lat=self.tide_gauge.lat,
|
|
1517
|
+
# Check if tide gauge is within model domain
|
|
1518
|
+
coord = (
|
|
1519
|
+
gpd.GeoSeries(
|
|
1520
|
+
gpd.points_from_xy(
|
|
1521
|
+
x=[self.tide_gauge.lon], y=[self.tide_gauge.lat]
|
|
1522
|
+
),
|
|
1523
|
+
crs="EPSG:4326",
|
|
1421
1524
|
)
|
|
1525
|
+
.to_crs(self.sfincs_overland_model.crs)
|
|
1526
|
+
.iloc[0]
|
|
1422
1527
|
)
|
|
1528
|
+
# Add tide gauge as obs point if within model region
|
|
1529
|
+
if coord.within(model_region):
|
|
1530
|
+
if self.tide_gauge.name is None:
|
|
1531
|
+
name = "tide_gauge"
|
|
1532
|
+
else:
|
|
1533
|
+
name = self.tide_gauge.name
|
|
1534
|
+
obs_points.append(
|
|
1535
|
+
ObsPointModel(
|
|
1536
|
+
name=name,
|
|
1537
|
+
description="Tide gauge observation point",
|
|
1538
|
+
ID=self.tide_gauge.ID,
|
|
1539
|
+
lon=self.tide_gauge.lon,
|
|
1540
|
+
lat=self.tide_gauge.lat,
|
|
1541
|
+
)
|
|
1542
|
+
)
|
|
1543
|
+
else:
|
|
1544
|
+
boundary = model_region.boundary
|
|
1545
|
+
snapped = nearest_points(coord, boundary)[1]
|
|
1546
|
+
distance = us.UnitfulLength(
|
|
1547
|
+
value=coord.distance(snapped), units=us.UnitTypesLength.meters
|
|
1548
|
+
)
|
|
1549
|
+
logger.warning(
|
|
1550
|
+
f"Tide gauge lies outside the model domain by {distance}. It will not be used as an observation point in FloodAdapt."
|
|
1551
|
+
)
|
|
1423
1552
|
|
|
1424
1553
|
if not obs_points:
|
|
1425
1554
|
logger.warning(
|
|
@@ -1427,20 +1556,25 @@ class DatabaseBuilder:
|
|
|
1427
1556
|
)
|
|
1428
1557
|
return None
|
|
1429
1558
|
|
|
1559
|
+
# Check if all obs points are within model domain
|
|
1430
1560
|
lon = [p.lon for p in obs_points]
|
|
1431
1561
|
lat = [p.lat for p in obs_points]
|
|
1432
1562
|
names = [p.name for p in obs_points]
|
|
1433
1563
|
coords = gpd.GeoDataFrame(
|
|
1434
|
-
{"
|
|
1564
|
+
{"name": names},
|
|
1435
1565
|
geometry=gpd.points_from_xy(lon, lat),
|
|
1436
1566
|
crs="EPSG:4326",
|
|
1437
1567
|
)
|
|
1438
1568
|
coords = coords.to_crs(self.sfincs_overland_model.crs)
|
|
1439
|
-
model_region = self.sfincs_overland_model.region.union_all()
|
|
1440
1569
|
valid_coords = coords.within(model_region)
|
|
1441
1570
|
if not valid_coords.all():
|
|
1442
1571
|
invalid = coords.loc[~valid_coords, "name"].tolist()
|
|
1443
|
-
|
|
1572
|
+
lat = coords.loc[~valid_coords].geometry.y.tolist()
|
|
1573
|
+
lon = coords.loc[~valid_coords].geometry.x.tolist()
|
|
1574
|
+
bounds = model_region.bounds
|
|
1575
|
+
raise ValueError(
|
|
1576
|
+
f"Observation points outside model domain: {invalid}, {lat=}, {lon=}, {bounds=}"
|
|
1577
|
+
)
|
|
1444
1578
|
|
|
1445
1579
|
return obs_points
|
|
1446
1580
|
|
|
@@ -1533,7 +1667,7 @@ class DatabaseBuilder:
|
|
|
1533
1667
|
db_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1534
1668
|
shutil.copyfile(self.config.tide_gauge.file, db_file_path)
|
|
1535
1669
|
|
|
1536
|
-
rel_db_path = Path(db_file_path.relative_to(self.static_path))
|
|
1670
|
+
rel_db_path = Path(db_file_path.relative_to(self.static_path)).as_posix()
|
|
1537
1671
|
logger.warning(
|
|
1538
1672
|
f"Tide gauge from file {rel_db_path} assumed to be in {self.unit_system.default_length_units}!"
|
|
1539
1673
|
)
|
|
@@ -1665,26 +1799,22 @@ class DatabaseBuilder:
|
|
|
1665
1799
|
if self.sfincs_offshore_model is None:
|
|
1666
1800
|
return None
|
|
1667
1801
|
# Connect boundary points of overland to output points of offshore
|
|
1802
|
+
# First read in the boundary locations from the overland model
|
|
1668
1803
|
fn = Path(self.sfincs_overland_model.root) / "sfincs.bnd"
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1804
|
+
lines = []
|
|
1805
|
+
if fn.exists():
|
|
1806
|
+
with open(fn) as f:
|
|
1807
|
+
lines = f.readlines()
|
|
1808
|
+
coords = [(float(line.split()[0]), float(line.split()[1])) for line in lines]
|
|
1809
|
+
x, y = zip(*coords)
|
|
1810
|
+
bnd = gpd.GeoDataFrame(
|
|
1811
|
+
geometry=gpd.points_from_xy(x, y),
|
|
1674
1812
|
crs=self.sfincs_overland_model.config["epsg"],
|
|
1675
1813
|
)
|
|
1676
|
-
|
|
1677
|
-
obs_geo
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
obs_geo["name"] = [f"bnd_pt{num:02d}" for num in range(1, len(obs_geo) + 1)]
|
|
1681
|
-
fn_off = Path(self.sfincs_offshore_model.root) / "sfincs.obs"
|
|
1682
|
-
obs_geo.to_csv(
|
|
1683
|
-
fn_off,
|
|
1684
|
-
sep="\t",
|
|
1685
|
-
index=False,
|
|
1686
|
-
header=False,
|
|
1687
|
-
)
|
|
1814
|
+
# Then transform points to offshore crs and save them as observation points
|
|
1815
|
+
obs_geo = bnd.to_crs(self.sfincs_offshore_model.config["epsg"])
|
|
1816
|
+
self.sfincs_offshore_model.setup_observation_points(obs_geo)
|
|
1817
|
+
self.sfincs_offshore_model.write()
|
|
1688
1818
|
logger.info(
|
|
1689
1819
|
"Output points of the offshore SFINCS model were reconfigured to the boundary points of the overland SFINCS model."
|
|
1690
1820
|
)
|
|
@@ -1973,12 +2103,12 @@ class DatabaseBuilder:
|
|
|
1973
2103
|
|
|
1974
2104
|
self.metrics = metrics
|
|
1975
2105
|
|
|
1976
|
-
def _create_mandatory_metrics(self, metrics):
|
|
2106
|
+
def _create_mandatory_metrics(self, metrics: Metrics):
|
|
1977
2107
|
metrics.create_mandatory_metrics_event()
|
|
1978
2108
|
if self._probabilistic_set_name is not None:
|
|
1979
2109
|
metrics.create_mandatory_metrics_risk()
|
|
1980
2110
|
|
|
1981
|
-
def _create_event_infographics(self, metrics):
|
|
2111
|
+
def _create_event_infographics(self, metrics: Metrics):
|
|
1982
2112
|
exposure_type = self._get_exposure_type()
|
|
1983
2113
|
# If not specific infographic config is given, create a standard one
|
|
1984
2114
|
if not self.config.event_infographics:
|
|
@@ -2010,7 +2140,7 @@ class DatabaseBuilder:
|
|
|
2010
2140
|
)
|
|
2011
2141
|
metrics.create_infographics_metrics_event(config=self.config.event_infographics)
|
|
2012
2142
|
|
|
2013
|
-
def _create_risk_infographics(self, metrics):
|
|
2143
|
+
def _create_risk_infographics(self, metrics: Metrics):
|
|
2014
2144
|
exposure_type = self._get_exposure_type()
|
|
2015
2145
|
# If not specific infographic config is given, create a standard one
|
|
2016
2146
|
if not self.config.risk_infographics:
|
|
@@ -2027,17 +2157,17 @@ class DatabaseBuilder:
|
|
|
2027
2157
|
)
|
|
2028
2158
|
metrics.create_infographics_metrics_risk(config=self.config.risk_infographics)
|
|
2029
2159
|
|
|
2030
|
-
def _add_additional_event_metrics(self, metrics):
|
|
2160
|
+
def _add_additional_event_metrics(self, metrics: Metrics):
|
|
2031
2161
|
if self.config.event_additional_infometrics:
|
|
2032
2162
|
for metric in self.config.event_additional_infometrics:
|
|
2033
2163
|
metrics.add_event_metric(metric)
|
|
2034
2164
|
|
|
2035
|
-
def _add_additional_risk_metrics(self, metrics):
|
|
2165
|
+
def _add_additional_risk_metrics(self, metrics: Metrics):
|
|
2036
2166
|
if self.config.risk_additional_infometrics:
|
|
2037
2167
|
for metric in self.config.risk_additional_infometrics:
|
|
2038
2168
|
metrics.add_risk_metric(metric)
|
|
2039
2169
|
|
|
2040
|
-
def _write_infometrics(self, metrics, path_im, path_ig):
|
|
2170
|
+
def _write_infometrics(self, metrics: Metrics, path_im: Path, path_ig: Path):
|
|
2041
2171
|
if self._aggregation_areas is None:
|
|
2042
2172
|
self._aggregation_areas = self.create_aggregation_areas()
|
|
2043
2173
|
aggr_levels = [aggr.name for aggr in self._aggregation_areas]
|
|
@@ -2216,7 +2346,7 @@ class DatabaseBuilder:
|
|
|
2216
2346
|
|
|
2217
2347
|
# Set model building footprints
|
|
2218
2348
|
self.fiat_model.building_footprint = building_footprints
|
|
2219
|
-
self.fiat_model.exposure.exposure_db = exposure_csv
|
|
2349
|
+
self.fiat_model.exposure.exposure_db = self._clean_suffix_columns(exposure_csv)
|
|
2220
2350
|
|
|
2221
2351
|
# Save site attributes
|
|
2222
2352
|
buildings_path = geo_path.relative_to(self.static_path)
|
|
@@ -2483,6 +2613,11 @@ class DatabaseBuilder:
|
|
|
2483
2613
|
"""
|
|
2484
2614
|
# Make sure only csv objects have geometries
|
|
2485
2615
|
for i, geoms in enumerate(self.fiat_model.exposure.exposure_geoms):
|
|
2616
|
+
if _FIAT_COLUMNS.object_id not in geoms.columns:
|
|
2617
|
+
logger.warning(
|
|
2618
|
+
f"Geometry '{self.fiat_model.exposure.geom_names[i]}' does not have an '{_FIAT_COLUMNS.object_id}' column and will be ignored."
|
|
2619
|
+
)
|
|
2620
|
+
continue
|
|
2486
2621
|
keep = geoms[_FIAT_COLUMNS.object_id].isin(
|
|
2487
2622
|
self.fiat_model.exposure.exposure_db[_FIAT_COLUMNS.object_id]
|
|
2488
2623
|
)
|
|
@@ -2523,6 +2658,35 @@ class DatabaseBuilder:
|
|
|
2523
2658
|
|
|
2524
2659
|
return gdf
|
|
2525
2660
|
|
|
2661
|
+
@staticmethod
|
|
2662
|
+
def _clean_suffix_columns(df: pd.DataFrame) -> pd.DataFrame:
|
|
2663
|
+
"""Detect and resolves duplicate columns with _x/_y suffixes that appear after a pandas merge.
|
|
2664
|
+
|
|
2665
|
+
(e.g., 'Aggregation Label: Census Blockgroup_x' and 'Aggregation Label: Census Blockgroup_y').
|
|
2666
|
+
|
|
2667
|
+
Keeps the first non-null column of each pair and removes redundant ones.
|
|
2668
|
+
"""
|
|
2669
|
+
cols = df.columns.tolist()
|
|
2670
|
+
suffix_pairs = {}
|
|
2671
|
+
|
|
2672
|
+
for col in cols:
|
|
2673
|
+
if col.endswith("_x"):
|
|
2674
|
+
base = col[:-2]
|
|
2675
|
+
if f"{base}_y" in df.columns:
|
|
2676
|
+
suffix_pairs[base] = (f"{base}_x", f"{base}_y")
|
|
2677
|
+
|
|
2678
|
+
for base, (col_x, col_y) in suffix_pairs.items():
|
|
2679
|
+
# If both columns exist, prefer the one with more non-null values
|
|
2680
|
+
x_notna = df[col_x].notna().sum()
|
|
2681
|
+
y_notna = df[col_y].notna().sum()
|
|
2682
|
+
keep_col = col_x if x_notna >= y_notna else col_y
|
|
2683
|
+
df[base] = df[keep_col]
|
|
2684
|
+
|
|
2685
|
+
# Drop the old suffixed versions
|
|
2686
|
+
df = df.drop(columns=[col_x, col_y])
|
|
2687
|
+
|
|
2688
|
+
return df
|
|
2689
|
+
|
|
2526
2690
|
|
|
2527
2691
|
def create_database(config: Union[str, Path, ConfigModel], overwrite=False) -> None:
|
|
2528
2692
|
"""Create a new database from a configuration file or ConfigModel.
|
|
@@ -79,7 +79,8 @@ class DbsScenario(DbsTemplate[Scenario]):
|
|
|
79
79
|
"""
|
|
80
80
|
event_left = self._database.events.get(left.event)
|
|
81
81
|
event_right = self._database.events.get(right.event)
|
|
82
|
-
|
|
82
|
+
# Deep-compare events including forcing data contents
|
|
83
|
+
equal_events = event_left.data_equivalent(event_right)
|
|
83
84
|
|
|
84
85
|
left_projection = self._database.projections.get(left.projection)
|
|
85
86
|
right_projection = self._database.projections.get(right.projection)
|
|
@@ -117,6 +117,63 @@ class Event(Object):
|
|
|
117
117
|
for forcing in self.get_forcings():
|
|
118
118
|
forcing.save_additional(output_dir)
|
|
119
119
|
|
|
120
|
+
def data_equivalent(self, other: "Event") -> bool:
|
|
121
|
+
"""Deep-compare two events, including forcing data contents.
|
|
122
|
+
|
|
123
|
+
Compares core attributes (time, template, mode, rainfall_multiplier) and then
|
|
124
|
+
verifies that each forcing (by type) has the same data fingerprint. For
|
|
125
|
+
path-based forcings, the fingerprint hashes the file bytes; for others,
|
|
126
|
+
a canonical attribute-based hash is used.
|
|
127
|
+
|
|
128
|
+
Parameters
|
|
129
|
+
----------
|
|
130
|
+
other : Event
|
|
131
|
+
The event to compare against.
|
|
132
|
+
|
|
133
|
+
Returns
|
|
134
|
+
-------
|
|
135
|
+
bool
|
|
136
|
+
True when events are equivalent in terms of their hazard inputs.
|
|
137
|
+
"""
|
|
138
|
+
if not isinstance(other, Event):
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
# Compare high-level attributes first
|
|
142
|
+
if (
|
|
143
|
+
self.template != other.template
|
|
144
|
+
or self.mode != other.mode
|
|
145
|
+
or self.rainfall_multiplier != other.rainfall_multiplier
|
|
146
|
+
or self.time != other.time
|
|
147
|
+
):
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
# Compare allowed forcing types present in each event
|
|
151
|
+
if set(self.forcings.keys()) != set(other.forcings.keys()):
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
# Build comparable, sorted fingerprint lists per forcing type
|
|
155
|
+
def fingerprints(evt: "Event") -> dict[ForcingType, list[tuple[str, str]]]:
|
|
156
|
+
d: dict[ForcingType, list[tuple[str, str]]] = {}
|
|
157
|
+
for ftype, flist in evt.forcings.items():
|
|
158
|
+
fps: list[tuple[str, str]] = []
|
|
159
|
+
for f in flist:
|
|
160
|
+
# Include source and the fingerprint to guard against collisions across different sources
|
|
161
|
+
src = f.source.value if hasattr(f, "source") else ""
|
|
162
|
+
fp = f.content_fingerprint() # type: ignore[attr-defined]
|
|
163
|
+
fps.append((src, fp))
|
|
164
|
+
# Sort for order-insensitive comparison
|
|
165
|
+
d[ftype] = sorted(fps, key=lambda t: (t[0], t[1]))
|
|
166
|
+
return d
|
|
167
|
+
|
|
168
|
+
left_fp = fingerprints(self)
|
|
169
|
+
right_fp = fingerprints(other)
|
|
170
|
+
|
|
171
|
+
for ftype in left_fp.keys():
|
|
172
|
+
if left_fp[ftype] != right_fp[ftype]:
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
return True
|
|
176
|
+
|
|
120
177
|
@classmethod
|
|
121
178
|
def load_file(cls, file_path: Path | str | os.PathLike) -> "Event":
|
|
122
179
|
"""Load object from file.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import json
|
|
1
3
|
import os
|
|
2
4
|
from abc import ABC, abstractmethod
|
|
3
5
|
from enum import Enum
|
|
@@ -72,7 +74,7 @@ class IForcing(BaseModel, ABC):
|
|
|
72
74
|
|
|
73
75
|
def save_additional(self, output_dir: Path | str | os.PathLike) -> None:
|
|
74
76
|
"""Save additional data of the forcing."""
|
|
75
|
-
|
|
77
|
+
pass
|
|
76
78
|
|
|
77
79
|
@field_serializer("path", check_fields=False)
|
|
78
80
|
@classmethod
|
|
@@ -80,6 +82,49 @@ class IForcing(BaseModel, ABC):
|
|
|
80
82
|
"""Serialize filepath-like fields by saving only the filename. It is assumed that the file will be saved in the same directory."""
|
|
81
83
|
return value.name
|
|
82
84
|
|
|
85
|
+
def content_fingerprint(self) -> str:
|
|
86
|
+
"""Return a stable fingerprint of the forcing's underlying data.
|
|
87
|
+
|
|
88
|
+
- If a file-backed `path` attribute exists and the file exists, hash its bytes (SHA-256).
|
|
89
|
+
- Otherwise, hash a canonical JSON dump of the model (excluding volatile fields like `path`).
|
|
90
|
+
|
|
91
|
+
Returns
|
|
92
|
+
-------
|
|
93
|
+
str
|
|
94
|
+
A fingerprint string that changes when the forcing's effective data changes.
|
|
95
|
+
"""
|
|
96
|
+
# Prefer hashing the actual file content when available
|
|
97
|
+
try:
|
|
98
|
+
p = getattr(self, "path", None)
|
|
99
|
+
if isinstance(p, Path) and p and p.exists():
|
|
100
|
+
sha = hashlib.sha256()
|
|
101
|
+
with p.open("rb") as f:
|
|
102
|
+
for chunk in iter(lambda: f.read(8192), b""):
|
|
103
|
+
sha.update(chunk)
|
|
104
|
+
# Include filename to disambiguate multi-forcing scenarios with identical content
|
|
105
|
+
return f"FILE:{p.name}:{sha.hexdigest()}"
|
|
106
|
+
except Exception:
|
|
107
|
+
# Fall through to attribute-based hashing if anything goes wrong
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
# Fallback: hash the model attributes (excluding volatile fields like path)
|
|
111
|
+
data = self.model_dump(exclude_none=True)
|
|
112
|
+
# Remove potentially non-stable/absolute fields
|
|
113
|
+
data.pop("path", None)
|
|
114
|
+
|
|
115
|
+
# Ensure enums are serialized to their values for stable hashing
|
|
116
|
+
if isinstance(data.get("type"), Enum):
|
|
117
|
+
data["type"] = data["type"].value
|
|
118
|
+
if isinstance(data.get("source"), Enum):
|
|
119
|
+
data["source"] = data["source"].value
|
|
120
|
+
|
|
121
|
+
payload = json.dumps(data, sort_keys=True, default=str).encode("utf-8")
|
|
122
|
+
return f"ATTR:{hashlib.sha256(payload).hexdigest()}"
|
|
123
|
+
|
|
124
|
+
def _post_load(self, file_path: Path | str | os.PathLike, **kwargs) -> None:
|
|
125
|
+
"""Post-load hook, called at the end of `load_file`, to perform any additional loading steps after loading from file."""
|
|
126
|
+
return
|
|
127
|
+
|
|
83
128
|
|
|
84
129
|
class IDischarge(IForcing):
|
|
85
130
|
type: ForcingType = ForcingType.DISCHARGE
|
|
@@ -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[
|
|
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
|
-
|
|
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
|
|
|
@@ -110,10 +110,12 @@ class ValueUnitPair(ABC, BaseModel, Generic[TUnit]):
|
|
|
110
110
|
raise ValueError(f"Unsupported or unknown unit: {str_unit}")
|
|
111
111
|
|
|
112
112
|
def __str__(self) -> str:
|
|
113
|
-
return f"{self.value} {self.units.value}"
|
|
113
|
+
return f"{self.value:.2f} {self.units.value}"
|
|
114
114
|
|
|
115
115
|
def __repr__(self) -> str:
|
|
116
|
-
return
|
|
116
|
+
return (
|
|
117
|
+
f"{type(self).__name__}(value={self.value:.2f}, units={self.units.value})"
|
|
118
|
+
)
|
|
117
119
|
|
|
118
120
|
def __sub__(self: TClass, other: TClass) -> TClass:
|
|
119
121
|
if not isinstance(other, type(self)):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: flood-adapt
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.5
|
|
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: ====================================================
|
|
@@ -712,14 +712,12 @@ Requires-Dist: cht-cyclones<2.0,>=1.0.3
|
|
|
712
712
|
Requires-Dist: cht-meteo<1.0,>=0.3.1
|
|
713
713
|
Requires-Dist: cht-observations<1.0,>=0.2.1
|
|
714
714
|
Requires-Dist: cht-tide<1.0,>=0.1.1
|
|
715
|
-
Requires-Dist: dask==2024.11.2
|
|
716
|
-
Requires-Dist: numba_celltree==0.2.2
|
|
717
715
|
Requires-Dist: fiat-toolbox<0.2.0,>=0.1.22
|
|
718
716
|
Requires-Dist: fiona<2.0,>=1.0
|
|
719
717
|
Requires-Dist: geojson<4.0,>=3.0
|
|
720
718
|
Requires-Dist: geopandas<2.0,>=1.0
|
|
721
719
|
Requires-Dist: hydromt-fiat<1.0,>=0.5.9
|
|
722
|
-
Requires-Dist: hydromt-sfincs<2.0,>=1.2.
|
|
720
|
+
Requires-Dist: hydromt-sfincs<2.0,>=1.2.2
|
|
723
721
|
Requires-Dist: numpy<2.0,>=1.0
|
|
724
722
|
Requires-Dist: numpy-financial<2.0,>=1.0
|
|
725
723
|
Requires-Dist: pandas<3.0,>=2.0
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
flood_adapt/__init__.py,sha256=
|
|
1
|
+
flood_adapt/__init__.py,sha256=qZBLP5qxMYURIbpEB6pRkz3wIeIVQZ0N9wTSJgxs_BA,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
4
|
flood_adapt/adapter/fiat_adapter.py,sha256=seDjPoumkhUOd7qer3ni1_Ut3dwyq0-_yhJNaTEFc2E,60284
|
|
@@ -17,8 +17,8 @@ flood_adapt/config/hazard.py,sha256=Ev6mj78cZ_vQuJ11KYjhJOzmfRB6fz267OJeKI0bYaM,
|
|
|
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
|
-
flood_adapt/database_builder/__init__.py,sha256=
|
|
21
|
-
flood_adapt/database_builder/database_builder.py,sha256=
|
|
20
|
+
flood_adapt/database_builder/__init__.py,sha256=h4ietZ6sAZa7j2kvSzp5-58BueGrfJsXvq8PFu1RLyI,1112
|
|
21
|
+
flood_adapt/database_builder/database_builder.py,sha256=nPwkBQpDzl74OrgF3hjQiDJprtxtdJuu0HsY3jNvJiM,110065
|
|
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
|
|
@@ -61,7 +61,7 @@ flood_adapt/dbs_classes/dbs_benefit.py,sha256=ayEYz8ga49HLdYuUsDWZOuZnpRnBpTuyhv
|
|
|
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
|
|
63
63
|
flood_adapt/dbs_classes/dbs_projection.py,sha256=lyiU_ctP2ixK28RKnBN6mVJbOuaDsWCj1y6-MHlyi_k,1078
|
|
64
|
-
flood_adapt/dbs_classes/dbs_scenario.py,sha256=
|
|
64
|
+
flood_adapt/dbs_classes/dbs_scenario.py,sha256=bD5VjuojgE_79ZN7bZNpnbpthI9uYUwMSsrPDDcjlvg,4070
|
|
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
|
|
@@ -82,23 +82,23 @@ flood_adapt/objects/benefits/benefits.py,sha256=1Di8v2B7YOdMkRlg0A6k6qtMqYE_JaaN
|
|
|
82
82
|
flood_adapt/objects/events/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
83
83
|
flood_adapt/objects/events/event_factory.py,sha256=CYq8itWurILPGNSDtWKWV5FF4UxjrRFiza6w2OQJEdU,4276
|
|
84
84
|
flood_adapt/objects/events/event_set.py,sha256=fpmNbrd3qXWPgyqphv_Rf2iNsZCLvwz6YEbjMV7WIXA,2810
|
|
85
|
-
flood_adapt/objects/events/events.py,sha256=
|
|
85
|
+
flood_adapt/objects/events/events.py,sha256=Kqwyeh9MMp8ex6DBbg-rqBnp6RsrbR1J7bfO9ybevh4,10580
|
|
86
86
|
flood_adapt/objects/events/historical.py,sha256=uIQ7icy9F3pOW0R-fBqjEjU857SGlJO81S104mzvtLY,2081
|
|
87
87
|
flood_adapt/objects/events/hurricane.py,sha256=dOPMa5c4mz1umLne9ydkBqnLTig9D95LmPHSFE5wit8,2401
|
|
88
88
|
flood_adapt/objects/events/synthetic.py,sha256=Q8OHbBumeEwbrzA1imBrxiGiB92RWyigVcGVUbisSh4,1366
|
|
89
89
|
flood_adapt/objects/forcing/__init__.py,sha256=wzPrflMLiEolocs3UcLeg05u5ydMDhF00jEf9zsC76c,2141
|
|
90
90
|
flood_adapt/objects/forcing/csv.py,sha256=AMHlE5K_QiMOqD2pRRbG4ojnZ9VPiQjtk2wMFSouKjk,2027
|
|
91
91
|
flood_adapt/objects/forcing/discharge.py,sha256=GIuKETXizwuaeIbHlu0fELQwC0ttfZSDiUMRm4HEyWY,2057
|
|
92
|
-
flood_adapt/objects/forcing/forcing.py,sha256=
|
|
92
|
+
flood_adapt/objects/forcing/forcing.py,sha256=n2IEk21Ih0GkddH3IqoV8egAYh4UCAVEyz_O0kk2fvE,6386
|
|
93
93
|
flood_adapt/objects/forcing/forcing_factory.py,sha256=lpugKlMt0MN8BxyykottMOuQd_fp1MHrkuuqbHWWdRY,5965
|
|
94
94
|
flood_adapt/objects/forcing/meteo_handler.py,sha256=rTxY5WNobK_Ifzj2eVcoSPGgb3TzuZljSv_dLn5FLo8,3016
|
|
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=
|
|
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
|
-
flood_adapt/objects/forcing/unit_system.py,sha256=
|
|
101
|
+
flood_adapt/objects/forcing/unit_system.py,sha256=7FFOmaxq6EOvXx64QDxlpNU4uMExqridFcdFwyTJ4Lo,16542
|
|
102
102
|
flood_adapt/objects/forcing/waterlevels.py,sha256=8lCmUdeyABurJwftae4_Iut9hCn24xVqCEPEa73OOcA,3437
|
|
103
103
|
flood_adapt/objects/forcing/wind.py,sha256=xs_xZdUoZUDP1y1xITlNVJwiyDt6wQsFbPFhVRDjSqg,3925
|
|
104
104
|
flood_adapt/objects/measures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -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.
|
|
118
|
-
flood_adapt-1.1.
|
|
119
|
-
flood_adapt-1.1.
|
|
120
|
-
flood_adapt-1.1.
|
|
121
|
-
flood_adapt-1.1.
|
|
117
|
+
flood_adapt-1.1.5.dist-info/LICENSE,sha256=Ui5E03pQ0EVKxvKA54lTPA1xrtgA2HMGLQai95eOzoE,36321
|
|
118
|
+
flood_adapt-1.1.5.dist-info/METADATA,sha256=yGKj5wuwDfXXwLSnJVfZmywRC4hZAJ_g2NrYeuCvg5I,48806
|
|
119
|
+
flood_adapt-1.1.5.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
|
120
|
+
flood_adapt-1.1.5.dist-info/top_level.txt,sha256=JvzMi6cTcQPEThCfpgMEeVny3ghI1urSH0CCgVIqSzw,12
|
|
121
|
+
flood_adapt-1.1.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|