flood-adapt 0.3.9__py3-none-any.whl → 0.3.11__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.
Files changed (100) hide show
  1. flood_adapt/__init__.py +26 -22
  2. flood_adapt/adapter/__init__.py +9 -9
  3. flood_adapt/adapter/fiat_adapter.py +1541 -1541
  4. flood_adapt/adapter/interface/hazard_adapter.py +70 -70
  5. flood_adapt/adapter/interface/impact_adapter.py +36 -36
  6. flood_adapt/adapter/interface/model_adapter.py +89 -89
  7. flood_adapt/adapter/interface/offshore.py +19 -19
  8. flood_adapt/adapter/sfincs_adapter.py +1853 -1848
  9. flood_adapt/adapter/sfincs_offshore.py +187 -193
  10. flood_adapt/config/config.py +248 -248
  11. flood_adapt/config/fiat.py +219 -219
  12. flood_adapt/config/gui.py +331 -331
  13. flood_adapt/config/sfincs.py +481 -336
  14. flood_adapt/config/site.py +129 -129
  15. flood_adapt/database_builder/database_builder.py +2210 -2210
  16. flood_adapt/database_builder/templates/default_units/imperial.toml +9 -9
  17. flood_adapt/database_builder/templates/default_units/metric.toml +9 -9
  18. flood_adapt/database_builder/templates/green_infra_table/green_infra_lookup_table.csv +10 -10
  19. flood_adapt/database_builder/templates/infographics/OSM/config_charts.toml +90 -90
  20. flood_adapt/database_builder/templates/infographics/OSM/config_people.toml +57 -57
  21. flood_adapt/database_builder/templates/infographics/OSM/config_risk_charts.toml +121 -121
  22. flood_adapt/database_builder/templates/infographics/OSM/config_roads.toml +65 -65
  23. flood_adapt/database_builder/templates/infographics/OSM/styles.css +45 -45
  24. flood_adapt/database_builder/templates/infographics/US_NSI/config_charts.toml +126 -126
  25. flood_adapt/database_builder/templates/infographics/US_NSI/config_people.toml +60 -60
  26. flood_adapt/database_builder/templates/infographics/US_NSI/config_risk_charts.toml +121 -121
  27. flood_adapt/database_builder/templates/infographics/US_NSI/config_roads.toml +65 -65
  28. flood_adapt/database_builder/templates/infographics/US_NSI/styles.css +45 -45
  29. flood_adapt/database_builder/templates/infometrics/OSM/metrics_additional_risk_configs.toml +4 -4
  30. flood_adapt/database_builder/templates/infometrics/OSM/with_SVI/infographic_metrics_config.toml +143 -143
  31. flood_adapt/database_builder/templates/infometrics/OSM/with_SVI/infographic_metrics_config_risk.toml +153 -153
  32. flood_adapt/database_builder/templates/infometrics/OSM/without_SVI/infographic_metrics_config.toml +127 -127
  33. flood_adapt/database_builder/templates/infometrics/OSM/without_SVI/infographic_metrics_config_risk.toml +57 -57
  34. flood_adapt/database_builder/templates/infometrics/US_NSI/metrics_additional_risk_configs.toml +4 -4
  35. flood_adapt/database_builder/templates/infometrics/US_NSI/with_SVI/infographic_metrics_config.toml +191 -191
  36. flood_adapt/database_builder/templates/infometrics/US_NSI/with_SVI/infographic_metrics_config_risk.toml +153 -153
  37. flood_adapt/database_builder/templates/infometrics/US_NSI/without_SVI/infographic_metrics_config.toml +178 -178
  38. flood_adapt/database_builder/templates/infometrics/US_NSI/without_SVI/infographic_metrics_config_risk.toml +57 -57
  39. flood_adapt/database_builder/templates/infometrics/mandatory_metrics_config.toml +9 -9
  40. flood_adapt/database_builder/templates/infometrics/mandatory_metrics_config_risk.toml +65 -65
  41. flood_adapt/database_builder/templates/output_layers/bin_colors.toml +5 -5
  42. flood_adapt/database_builder.py +16 -16
  43. flood_adapt/dbs_classes/__init__.py +21 -21
  44. flood_adapt/dbs_classes/database.py +533 -684
  45. flood_adapt/dbs_classes/dbs_benefit.py +77 -76
  46. flood_adapt/dbs_classes/dbs_event.py +61 -59
  47. flood_adapt/dbs_classes/dbs_measure.py +112 -111
  48. flood_adapt/dbs_classes/dbs_projection.py +34 -34
  49. flood_adapt/dbs_classes/dbs_scenario.py +137 -137
  50. flood_adapt/dbs_classes/dbs_static.py +274 -273
  51. flood_adapt/dbs_classes/dbs_strategy.py +130 -129
  52. flood_adapt/dbs_classes/dbs_template.py +279 -278
  53. flood_adapt/dbs_classes/interface/database.py +107 -139
  54. flood_adapt/dbs_classes/interface/element.py +121 -121
  55. flood_adapt/dbs_classes/interface/static.py +47 -47
  56. flood_adapt/flood_adapt.py +1229 -1178
  57. flood_adapt/misc/database_user.py +16 -16
  58. flood_adapt/misc/exceptions.py +22 -0
  59. flood_adapt/misc/log.py +183 -183
  60. flood_adapt/misc/path_builder.py +54 -54
  61. flood_adapt/misc/utils.py +185 -185
  62. flood_adapt/objects/__init__.py +82 -82
  63. flood_adapt/objects/benefits/benefits.py +61 -61
  64. flood_adapt/objects/events/event_factory.py +135 -135
  65. flood_adapt/objects/events/event_set.py +88 -84
  66. flood_adapt/objects/events/events.py +236 -234
  67. flood_adapt/objects/events/historical.py +58 -58
  68. flood_adapt/objects/events/hurricane.py +68 -67
  69. flood_adapt/objects/events/synthetic.py +46 -50
  70. flood_adapt/objects/forcing/__init__.py +92 -92
  71. flood_adapt/objects/forcing/csv.py +68 -68
  72. flood_adapt/objects/forcing/discharge.py +66 -66
  73. flood_adapt/objects/forcing/forcing.py +150 -150
  74. flood_adapt/objects/forcing/forcing_factory.py +182 -182
  75. flood_adapt/objects/forcing/meteo_handler.py +93 -93
  76. flood_adapt/objects/forcing/netcdf.py +40 -40
  77. flood_adapt/objects/forcing/plotting.py +453 -429
  78. flood_adapt/objects/forcing/rainfall.py +98 -98
  79. flood_adapt/objects/forcing/tide_gauge.py +191 -191
  80. flood_adapt/objects/forcing/time_frame.py +90 -90
  81. flood_adapt/objects/forcing/timeseries.py +564 -564
  82. flood_adapt/objects/forcing/unit_system.py +580 -580
  83. flood_adapt/objects/forcing/waterlevels.py +108 -108
  84. flood_adapt/objects/forcing/wind.py +124 -124
  85. flood_adapt/objects/measures/measure_factory.py +92 -92
  86. flood_adapt/objects/measures/measures.py +551 -529
  87. flood_adapt/objects/object_model.py +74 -68
  88. flood_adapt/objects/projections/projections.py +103 -103
  89. flood_adapt/objects/scenarios/scenarios.py +22 -22
  90. flood_adapt/objects/strategies/strategies.py +89 -89
  91. flood_adapt/workflows/benefit_runner.py +579 -554
  92. flood_adapt/workflows/floodmap.py +85 -85
  93. flood_adapt/workflows/impacts_integrator.py +85 -85
  94. flood_adapt/workflows/scenario_runner.py +70 -70
  95. {flood_adapt-0.3.9.dist-info → flood_adapt-0.3.11.dist-info}/LICENSE +674 -674
  96. {flood_adapt-0.3.9.dist-info → flood_adapt-0.3.11.dist-info}/METADATA +867 -865
  97. flood_adapt-0.3.11.dist-info/RECORD +140 -0
  98. flood_adapt-0.3.9.dist-info/RECORD +0 -139
  99. {flood_adapt-0.3.9.dist-info → flood_adapt-0.3.11.dist-info}/WHEEL +0 -0
  100. {flood_adapt-0.3.9.dist-info → flood_adapt-0.3.11.dist-info}/top_level.txt +0 -0
@@ -1,529 +1,551 @@
1
- import os
2
- from enum import Enum
3
- from pathlib import Path
4
- from typing import Any, Optional
5
-
6
- import geopandas as gpd
7
- import pyproj
8
- from pydantic import Field, field_validator, model_validator
9
-
10
- from flood_adapt.config.site import Site
11
- from flood_adapt.misc.utils import resolve_filepath, save_file_to_database
12
- from flood_adapt.objects.forcing import unit_system as us
13
- from flood_adapt.objects.object_model import Object
14
-
15
-
16
- class MeasureCategory(str, Enum):
17
- """Class describing the accepted input for the variable 'type' in Measure."""
18
-
19
- impact = "impact"
20
- hazard = "hazard"
21
-
22
-
23
- class MeasureType(str, Enum):
24
- """Class describing the accepted input for the variable 'type' in Measure.
25
-
26
- Each type of measure is associated with a category (hazard or impact) and can be used to determine the type of measure.
27
-
28
- Attributes
29
- ----------
30
- floodwall : A floodwall measure.
31
- thin_dam : A thin dam measure.
32
- levee : A levee measure.
33
- pump : A pump measure.
34
- culvert : A culvert measure.
35
- water_square : A water square measure.
36
- greening : A greening measure.
37
- total_storage : A total storage measure.
38
- elevate_properties : An elevate properties measure.
39
- buyout_properties : A buyout properties measure.
40
- floodproof_properties : A floodproof properties measure.
41
- """
42
-
43
- # Hazard measures
44
- floodwall = "floodwall"
45
- thin_dam = "thin_dam" # For now, same functionality as floodwall TODO: Add thin dam functionality
46
- levee = "levee" # For now, same functionality as floodwall TODO: Add levee functionality
47
- pump = "pump"
48
- culvert = (
49
- "culvert" # For now, same functionality as pump TODO: Add culvert functionality
50
- )
51
- water_square = "water_square"
52
- greening = "greening"
53
- total_storage = "total_storage"
54
-
55
- # Impact measures
56
- elevate_properties = "elevate_properties"
57
- buyout_properties = "buyout_properties"
58
- floodproof_properties = "floodproof_properties"
59
-
60
- @classmethod
61
- def is_hazard(cls, measure_type: str) -> bool:
62
- return measure_type in [
63
- cls.floodwall,
64
- cls.thin_dam,
65
- cls.levee,
66
- cls.pump,
67
- cls.culvert,
68
- cls.water_square,
69
- cls.greening,
70
- cls.total_storage,
71
- ]
72
-
73
- @classmethod
74
- def is_impact(cls, measure_type: str) -> bool:
75
- return measure_type in [
76
- cls.elevate_properties,
77
- cls.buyout_properties,
78
- cls.floodproof_properties,
79
- ]
80
-
81
- @classmethod
82
- def get_measure_category(cls, measure_type: str) -> MeasureCategory:
83
- if cls.is_hazard(measure_type):
84
- return MeasureCategory.hazard
85
- elif cls.is_impact(measure_type):
86
- return MeasureCategory.impact
87
- else:
88
- raise ValueError(f"Invalid measure type: {measure_type}")
89
-
90
-
91
- class SelectionType(str, Enum):
92
- """Class describing the accepted input for the variable 'selection_type' in Measures.
93
-
94
- It is used to determine where to apply the measure to a model.
95
-
96
- Attributes
97
- ----------
98
- aggregation_area : Use aggregation area as geometry for the measure.
99
- polygon : Use polygon as geometry for the measure.
100
- polyline : Use polyline as geometry for the measure.
101
- all : Apply the measure to all geometries in the database.
102
- """
103
-
104
- aggregation_area = "aggregation_area"
105
- polygon = "polygon"
106
- polyline = "polyline"
107
- all = "all"
108
-
109
-
110
- class Measure(Object):
111
- """The expected variables and data types of attributes common to all measures.
112
-
113
- A measure is a collection of attributes that can be applied to a model.
114
-
115
- Attributes
116
- ----------
117
- name: str
118
- Name of the measure.
119
- description: str
120
- Description of the measure.
121
- type: MeasureType
122
- Type of measure. Should be one of the MeasureType enum values.
123
- """
124
-
125
- type: MeasureType
126
-
127
-
128
- class HazardMeasure(Measure):
129
- """The expected variables and data types of attributes common to all hazard measures.
130
-
131
- Attributes
132
- ----------
133
- name: str
134
- Name of the measure.
135
- description: str
136
- Description of the measure.
137
- type: MeasureType
138
- Type of measure. Should be one of the MeasureType enum values and is_hazard.
139
- selection_type: SelectionType
140
- Type of selection. Should be one of the SelectionType enum values.
141
- polygon_file: str, Optional, default = None
142
- Path to a polygon file, either absolute or relative to the measure path in the database.
143
-
144
- """
145
-
146
- selection_type: SelectionType
147
- polygon_file: Optional[str] = Field(
148
- default=None,
149
- min_length=1,
150
- description="Path to a polygon file, either absolute or relative to the measure path.",
151
- )
152
-
153
- @field_validator("type")
154
- def validate_type(cls, value):
155
- if not MeasureType.is_hazard(value):
156
- raise ValueError(f"Invalid hazard type: {value}")
157
- return value
158
-
159
- @model_validator(mode="after")
160
- def validate_selection_type(self) -> "HazardMeasure":
161
- if (
162
- self.selection_type
163
- not in [SelectionType.aggregation_area, SelectionType.all]
164
- and self.polygon_file is None
165
- ):
166
- raise ValueError(
167
- "If `selection_type` is not 'aggregation_area' or 'all', then `polygon_file` needs to be set."
168
- )
169
- return self
170
-
171
- def save_additional(self, output_dir: Path | str | os.PathLike) -> None:
172
- if self.polygon_file:
173
- Path(output_dir).mkdir(parents=True, exist_ok=True)
174
- src_path = resolve_filepath("measures", self.name, self.polygon_file)
175
- path = save_file_to_database(src_path, Path(output_dir))
176
- # Update the shapefile path in the object so it is saved in the toml file as well
177
- self.polygon_file = path.name
178
-
179
-
180
- class ImpactMeasure(Measure):
181
- """The expected variables and data types of attributes common to all impact measures.
182
-
183
- Attributes
184
- ----------
185
- name: str
186
- Name of the measure.
187
- description: str
188
- Description of the measure.
189
- type: MeasureType
190
- Type of measure. Should be one of the MeasureType enum values and is_hazard.
191
- selection_type: SelectionType
192
- Type of selection. Should be one of the SelectionType enum values.
193
- polygon_file: str, Optional, default = None
194
- Path to a polygon file, either absolute or relative to the measure path in the database.
195
- property_type: str
196
- Type of property. Should be one of the PropertyType enum values.
197
- aggregation_area_type: str, Optional, default = None
198
- Type of aggregation area. Should be one of the SelectionType enum values.
199
- aggregation_area_name: str, Optional, default = None
200
- Name of the aggregation area.
201
- """
202
-
203
- type: MeasureType
204
- selection_type: SelectionType
205
- aggregation_area_type: Optional[str] = None
206
- aggregation_area_name: Optional[str] = None
207
- polygon_file: Optional[str] = Field(
208
- default=None,
209
- min_length=1,
210
- description="Path to a polygon file, relative to the database path.",
211
- )
212
- property_type: str # TODO make enum
213
-
214
- @field_validator("type")
215
- def validate_type(cls, value):
216
- if not MeasureType.is_impact(value):
217
- raise ValueError(f"Invalid impact type: {value}")
218
- return value
219
-
220
- @model_validator(mode="after")
221
- def validate_aggregation_area_name(self):
222
- if (
223
- self.selection_type == SelectionType.aggregation_area
224
- and self.aggregation_area_name is None
225
- ):
226
- raise ValueError(
227
- "If `selection_type` is 'aggregation_area', then `aggregation_area_name` needs to be set."
228
- )
229
- return self
230
-
231
- @model_validator(mode="after")
232
- def validate_polygon_file(self):
233
- if self.selection_type == SelectionType.polygon and self.polygon_file is None:
234
- raise ValueError(
235
- "If `selection_type` is 'polygon', then `polygon_file` needs to be set."
236
- )
237
-
238
- return self
239
-
240
- def save_additional(self, output_dir: Path | str | os.PathLike) -> None:
241
- """Save the additional files to the database."""
242
- if self.polygon_file:
243
- src_path = resolve_filepath("measures", self.name, self.polygon_file)
244
- path = save_file_to_database(src_path, Path(output_dir))
245
- # Update the shapefile path in the object so it is saved in the toml file as well
246
- self.polygon_file = path.name
247
-
248
-
249
- class Elevate(ImpactMeasure):
250
- """The expected variables and data types of the "elevate" impact measure.
251
-
252
- Attributes
253
- ----------
254
- name: str
255
- Name of the measure.
256
- description: str
257
- Description of the measure.
258
- type : MeasureType
259
- Type of measure. Should be "elevate_properties".
260
- selection_type : SelectionType
261
- Type of selection. Should be "polygon" or "aggregation_area".
262
- polygon_file : str, Optional
263
- Path to a polygon file, either absolute or relative to the measure path.
264
- aggregation_area_type : str, Optional
265
- Type of aggregation area. Should be "aggregation_area" or "all".
266
- aggregation_area_name : str, Optional
267
- Name of the aggregation area.
268
- property_type : str
269
- Type of property. Should be "residential" or "commercial".
270
- elevation : us.UnitfulLengthRefValue
271
- Elevation of the properties.
272
- """
273
-
274
- type: MeasureType = MeasureType.elevate_properties
275
- elevation: us.UnitfulLengthRefValue
276
-
277
-
278
- class Buyout(ImpactMeasure):
279
- """The expected variables and data types of the "buyout" impact measure.
280
-
281
- Attributes
282
- ----------
283
- name: str
284
- Name of the measure.
285
- description: str, default ""
286
- Description of the measure.
287
- type : MeasureType, default MeasureType.buyout_properties
288
- Type of measure.
289
- selection_type : SelectionType
290
- Type of selection. Should be "polygon" or "aggregation_area".
291
- polygon_file : str, Optional
292
- Path to a polygon file, either absolute or relative to the measure path.
293
- aggregation_area_type : str, Optional
294
- Type of aggregation area. Should be "aggregation_area" or "all".
295
- aggregation_area_name : str, Optional
296
- Name of the aggregation area.
297
- property_type : str
298
- Type of property. Should be "residential" or "commercial".
299
- elevation : us.UnitfulLengthRefValue
300
- Elevation of the properties.
301
-
302
- """
303
-
304
- # Buyout has only the basic impact measure attributes
305
- type: MeasureType = MeasureType.buyout_properties
306
-
307
-
308
- class FloodProof(ImpactMeasure):
309
- """The expected variables and data types of the "floodproof" impact measure.
310
-
311
- Attributes
312
- ----------
313
- name: str
314
- Name of the measure.
315
- description: str
316
- Description of the measure.
317
- type : MeasureType
318
- Type of measure. Should be "floodproof_properties".
319
- selection_type : SelectionType
320
- Type of selection. Should be "polygon" or "aggregation_area".
321
- polygon_file : str, Optional
322
- Path to a polygon file, either absolute or relative to the measure path.
323
- aggregation_area_type : str, Optional
324
- Type of aggregation area. Should be "aggregation_area" or "all".
325
- aggregation_area_name : str, Optional
326
- Name of the aggregation area.
327
- property_type : str
328
- Type of property. Should be "residential" or "commercial".
329
- elevation : us.UnitfulLengthRefValue
330
- Elevation of the properties.
331
- """
332
-
333
- type: MeasureType = MeasureType.floodproof_properties
334
- elevation: us.UnitfulLength
335
-
336
-
337
- class FloodWall(HazardMeasure):
338
- """
339
- The expected variables and data types of the "floodwall" hazard measure.
340
-
341
- Attributes
342
- ----------
343
- name: str
344
- Name of the measure.
345
- description: str
346
- Description of the measure.
347
- type : MeasureType
348
- Type of measure. Should be "MeasureType.floodwall"
349
- selection_type : SelectionType
350
- Type of selection. Should be "SelectionType.polygon" or "SelectionType.aggregation_area".
351
- polygon_file : Optional[str]
352
- Path to a polygon file, either absolute or relative to the measure path.
353
- elevation : us.UnitfulLength
354
- Height of the floodwall.
355
- absolute_elevation : bool
356
- TODO remove?
357
- """
358
-
359
- type: MeasureType = MeasureType.floodwall
360
- elevation: us.UnitfulLength
361
- absolute_elevation: Optional[bool] = False
362
-
363
-
364
- class Pump(HazardMeasure):
365
- """
366
- The expected variables and data types of the "pump" hazard measure.
367
-
368
- Attributes
369
- ----------
370
- name: str
371
- Name of the measure.
372
- description: str
373
- Description of the measure.
374
- type : MeasureType
375
- Type of measure. Should be "pump"
376
- selection_type : SelectionType
377
- Type of selection. Should be "polyline".
378
- polygon_file : str, Optional
379
- Path to a polygon file, either absolute or relative to the measure path.
380
- elevation : us.UnitfulLength
381
- Height of the floodwall.
382
- absolute_elevation : bool
383
- TODO remove?
384
- """
385
-
386
- type: MeasureType = MeasureType.pump
387
- discharge: us.UnitfulDischarge
388
-
389
-
390
- class GreenInfrastructure(HazardMeasure):
391
- """The expected variables and data types of the "green infrastructure" hazard measure.
392
-
393
- Attributes
394
- ----------
395
- name: str
396
- Name of the measure.
397
- description: str
398
- Description of the measure.
399
- type : MeasureType
400
- Type of measure. Should be "greening"
401
- selection_type : SelectionType
402
- Type of selection. Should be "polygon" or "aggregation_area".
403
- height : us.UnitfulHeight, Optional
404
- Height of the green infrastructure.
405
- volume : us.UnitfulVolume, Optional
406
- Volume of the green infrastructure.
407
- polygon_file : str, Optional
408
- Path to a polygon file, either absolute or relative to the measure path.
409
- aggregation_area_type : str, Optional
410
- Type of aggregation area. Should be "aggregation_area".
411
- aggregation_area_name : str, Optional
412
- Name of the aggregation area.
413
- percent_area : float, Optional
414
- Percentage of the area that is green infrastructure.
415
- """
416
-
417
- type: MeasureType = MeasureType.greening
418
- volume: us.UnitfulVolume
419
- height: Optional[us.UnitfulHeight] = None
420
- aggregation_area_type: Optional[str] = None
421
- aggregation_area_name: Optional[str] = None
422
- percent_area: Optional[float] = Field(default=None, ge=0, le=100)
423
-
424
- @field_validator("height", mode="before", check_fields=False)
425
- def height_from_length(value: Any) -> Any:
426
- if isinstance(value, us.UnitfulLength):
427
- return us.UnitfulHeight(value=value.value, units=value.units)
428
- return value
429
-
430
- @model_validator(mode="after")
431
- def validate_hazard_type_values(self) -> "GreenInfrastructure":
432
- e_msg = f"Error parsing GreenInfrastructure: {self.name}"
433
-
434
- if self.type == MeasureType.total_storage:
435
- if self.height is not None or self.percent_area is not None:
436
- raise ValueError(
437
- f"{e_msg}\nHeight and percent_area cannot be set for total storage type measures"
438
- )
439
- return self
440
- elif self.type == MeasureType.water_square:
441
- if self.percent_area is not None:
442
- raise ValueError(
443
- f"{e_msg}\nPercentage_area cannot be set for water square type measures"
444
- )
445
- elif not isinstance(self.height, us.UnitfulHeight):
446
- raise ValueError(
447
- f"{e_msg}\nHeight needs to be set for water square type measures"
448
- )
449
- return self
450
- elif self.type == MeasureType.greening:
451
- if not isinstance(self.height, us.UnitfulHeight) or not isinstance(
452
- self.percent_area, float
453
- ):
454
- raise ValueError(
455
- f"{e_msg}\nHeight and percent_area needs to be set for greening type measures"
456
- )
457
- else:
458
- raise ValueError(
459
- f"{e_msg}\nType must be one of 'water_square', 'greening', or 'total_storage'"
460
- )
461
- return self
462
-
463
- @model_validator(mode="after")
464
- def validate_selection_type_values(self) -> "GreenInfrastructure":
465
- if self.selection_type == SelectionType.aggregation_area:
466
- if self.aggregation_area_name is None:
467
- raise ValueError(
468
- "If `selection_type` is 'aggregation_area', then `aggregation_area_name` needs to be set."
469
- )
470
- if self.aggregation_area_type is None:
471
- raise ValueError(
472
- "If `selection_type` is 'aggregation_area', then `aggregation_area_type` needs to be set."
473
- )
474
- return self
475
-
476
- @staticmethod
477
- def calculate_volume(
478
- area: us.UnitfulArea,
479
- height: us.UnitfulHeight,
480
- percent_area: float = 100.0,
481
- ) -> float:
482
- """Determine volume from area of the polygon and infiltration height.
483
-
484
- Parameters
485
- ----------
486
- area : us.UnitfulArea
487
- Area of polygon with units (calculated using calculate_polygon_area)
488
- height : us.UnitfulHeight
489
- Water height with units
490
- percent_area : float, optional
491
- Percentage area covered by green infrastructure [%], by default 100.0
492
-
493
- Returns
494
- -------
495
- float
496
- Volume [m3]
497
- """
498
- volume = (
499
- area.convert(us.UnitTypesArea.m2)
500
- * height.convert(us.UnitTypesLength.meters)
501
- * (percent_area / 100.0)
502
- )
503
- return volume
504
-
505
- @staticmethod
506
- def calculate_polygon_area(gdf: gpd.GeoDataFrame, site: Site) -> float:
507
- """Calculate area of a GeoDataFrame Polygon.
508
-
509
- Parameters
510
- ----------
511
- gdf : gpd.GeoDataFrame
512
- Polygon object
513
- site : Site
514
- site config (used for CRS)
515
-
516
- Returns
517
- -------
518
- area : float
519
- Area of the given polygon
520
- """
521
- # Determine local CRS
522
- crs = pyproj.CRS.from_string(site.sfincs.config.csname)
523
- gdf = gdf.to_crs(crs)
524
-
525
- # The GeoJSON file can contain multiple polygons
526
- polygon = gdf.geometry
527
- # Calculate the area of all polygons
528
- area = polygon.area.sum()
529
- return area
1
+ import os
2
+ from enum import Enum
3
+ from pathlib import Path
4
+ from typing import Any, Optional, Type, TypeVar
5
+
6
+ import geopandas as gpd
7
+ import pyproj
8
+ import tomli
9
+ from pydantic import Field, field_serializer, field_validator, model_validator
10
+
11
+ from flood_adapt.config.site import Site
12
+ from flood_adapt.misc.utils import resolve_filepath, save_file_to_database
13
+ from flood_adapt.objects.forcing import unit_system as us
14
+ from flood_adapt.objects.object_model import Object
15
+
16
+
17
+ class MeasureCategory(str, Enum):
18
+ """Class describing the accepted input for the variable 'type' in Measure."""
19
+
20
+ impact = "impact"
21
+ hazard = "hazard"
22
+
23
+
24
+ class MeasureType(str, Enum):
25
+ """Class describing the accepted input for the variable 'type' in Measure.
26
+
27
+ Each type of measure is associated with a category (hazard or impact) and can be used to determine the type of measure.
28
+
29
+ Attributes
30
+ ----------
31
+ floodwall : A floodwall measure.
32
+ thin_dam : A thin dam measure.
33
+ levee : A levee measure.
34
+ pump : A pump measure.
35
+ culvert : A culvert measure.
36
+ water_square : A water square measure.
37
+ greening : A greening measure.
38
+ total_storage : A total storage measure.
39
+ elevate_properties : An elevate properties measure.
40
+ buyout_properties : A buyout properties measure.
41
+ floodproof_properties : A floodproof properties measure.
42
+ """
43
+
44
+ # Hazard measures
45
+ floodwall = "floodwall"
46
+ thin_dam = "thin_dam" # For now, same functionality as floodwall TODO: Add thin dam functionality
47
+ levee = "levee" # For now, same functionality as floodwall TODO: Add levee functionality
48
+ pump = "pump"
49
+ culvert = (
50
+ "culvert" # For now, same functionality as pump TODO: Add culvert functionality
51
+ )
52
+ water_square = "water_square"
53
+ greening = "greening"
54
+ total_storage = "total_storage"
55
+
56
+ # Impact measures
57
+ elevate_properties = "elevate_properties"
58
+ buyout_properties = "buyout_properties"
59
+ floodproof_properties = "floodproof_properties"
60
+
61
+ @classmethod
62
+ def is_hazard(cls, measure_type: str) -> bool:
63
+ return measure_type in [
64
+ cls.floodwall,
65
+ cls.thin_dam,
66
+ cls.levee,
67
+ cls.pump,
68
+ cls.culvert,
69
+ cls.water_square,
70
+ cls.greening,
71
+ cls.total_storage,
72
+ ]
73
+
74
+ @classmethod
75
+ def is_impact(cls, measure_type: str) -> bool:
76
+ return measure_type in [
77
+ cls.elevate_properties,
78
+ cls.buyout_properties,
79
+ cls.floodproof_properties,
80
+ ]
81
+
82
+ @classmethod
83
+ def get_measure_category(cls, measure_type: str) -> MeasureCategory:
84
+ if cls.is_hazard(measure_type):
85
+ return MeasureCategory.hazard
86
+ elif cls.is_impact(measure_type):
87
+ return MeasureCategory.impact
88
+ else:
89
+ raise ValueError(f"Invalid measure type: {measure_type}")
90
+
91
+
92
+ class SelectionType(str, Enum):
93
+ """Class describing the accepted input for the variable 'selection_type' in Measures.
94
+
95
+ It is used to determine where to apply the measure to a model.
96
+
97
+ Attributes
98
+ ----------
99
+ aggregation_area : Use aggregation area as geometry for the measure.
100
+ polygon : Use polygon as geometry for the measure.
101
+ polyline : Use polyline as geometry for the measure.
102
+ all : Apply the measure to all geometries in the database.
103
+ """
104
+
105
+ aggregation_area = "aggregation_area"
106
+ polygon = "polygon"
107
+ polyline = "polyline"
108
+ all = "all"
109
+
110
+
111
+ T = TypeVar("T", bound="Measure")
112
+
113
+
114
+ class Measure(Object):
115
+ """The expected variables and data types of attributes common to all measures.
116
+
117
+ A measure is a collection of attributes that can be applied to a model.
118
+
119
+ Attributes
120
+ ----------
121
+ name: str
122
+ Name of the measure.
123
+ description: str
124
+ Description of the measure.
125
+ type: MeasureType
126
+ Type of measure. Should be one of the MeasureType enum values.
127
+ selection_type: SelectionType
128
+ Type of selection. Should be one of the SelectionType enum values.
129
+ polygon_file: str, Optional
130
+ Path to a polygon file, either absolute or relative to the measure's toml path in the database.
131
+ aggregation_area_name: str, Optional
132
+ Name of the aggregation area. Required if `selection_type` is 'aggregation_area'.
133
+ aggregation_area_type: str, Optional
134
+ Type of aggregation area. Required if `selection_type` is 'aggregation_area'.
135
+ """
136
+
137
+ type: MeasureType
138
+ selection_type: SelectionType
139
+
140
+ polygon_file: Optional[str] = Field(
141
+ default=None,
142
+ min_length=1,
143
+ description="Path to a polygon file, either absolute or relative to the measure path.",
144
+ )
145
+
146
+ aggregation_area_type: Optional[str] = None
147
+ aggregation_area_name: Optional[str] = None
148
+
149
+ @model_validator(mode="after")
150
+ def validate_selection_type(self) -> "Measure":
151
+ match self.selection_type:
152
+ case SelectionType.all:
153
+ pass
154
+ case SelectionType.polygon | SelectionType.polyline:
155
+ if not self.polygon_file:
156
+ raise ValueError(
157
+ "If `selection_type` is 'polygon' or 'polyline', then `polygon_file` needs to be set."
158
+ )
159
+ case SelectionType.aggregation_area:
160
+ if not self.aggregation_area_name:
161
+ raise ValueError(
162
+ "If `selection_type` is 'aggregation_area', then `aggregation_area_name` needs to be set."
163
+ )
164
+ if not self.aggregation_area_type:
165
+ raise ValueError(
166
+ "If `selection_type` is 'aggregation_area', then `aggregation_area_type` needs to be set."
167
+ )
168
+ case _:
169
+ raise ValueError(
170
+ f"Invalid selection type: {self.selection_type}. "
171
+ "Must be one of 'aggregation_area', 'polygon', 'polyline', or 'all'."
172
+ )
173
+ return self
174
+
175
+ @field_serializer("polygon_file")
176
+ def serialize_polygon_file(self, value: Optional[str]) -> Optional[str]:
177
+ """Serialize the polygon_file attribute to a string of only the file name."""
178
+ if value is None:
179
+ return None
180
+ return Path(value).name
181
+
182
+ @classmethod
183
+ def load_file(cls: Type[T], file_path: Path | str | os.PathLike) -> T:
184
+ """Load the measure from a file.
185
+
186
+ Parameters
187
+ ----------
188
+ filepath : Path | str | os.PathLike
189
+ Path to the file to load the measure from.
190
+
191
+ Returns
192
+ -------
193
+ Measure
194
+ The loaded measure object.
195
+ """
196
+ with open(file_path, mode="rb") as fp:
197
+ toml = tomli.load(fp)
198
+ measure = cls.model_validate(toml)
199
+
200
+ if measure.polygon_file:
201
+ measure.polygon_file = str(Path(file_path).parent / measure.polygon_file)
202
+
203
+ return measure
204
+
205
+ def save_additional(self, output_dir: Path | str | os.PathLike) -> None:
206
+ if self.polygon_file:
207
+ Path(output_dir).mkdir(parents=True, exist_ok=True)
208
+ src_path = resolve_filepath("measures", self.name, self.polygon_file)
209
+ path = save_file_to_database(src_path, Path(output_dir))
210
+ # Update the shapefile path in the object so it is saved in the toml file as well
211
+ self.polygon_file = path.name
212
+
213
+
214
+ class HazardMeasure(Measure):
215
+ """The expected variables and data types of attributes common to all hazard measures.
216
+
217
+ Attributes
218
+ ----------
219
+ name: str
220
+ Name of the measure.
221
+ description: str
222
+ Description of the measure.
223
+ type: MeasureType
224
+ Type of measure. Should be one of the MeasureType enum values and is_hazard.
225
+ selection_type: SelectionType
226
+ Type of selection. Should be one of the SelectionType enum values.
227
+ polygon_file: str, Optional, default = None
228
+ Path to a polygon file, either absolute or relative to the measure path in the database.
229
+
230
+ """
231
+
232
+ @field_validator("type")
233
+ def validate_type(cls, value):
234
+ if not MeasureType.is_hazard(value):
235
+ raise ValueError(f"Invalid hazard type: {value}")
236
+ return value
237
+
238
+
239
+ class ImpactMeasure(Measure):
240
+ """The expected variables and data types of attributes common to all impact measures.
241
+
242
+ Attributes
243
+ ----------
244
+ name: str
245
+ Name of the measure.
246
+ description: str
247
+ Description of the measure.
248
+ type: MeasureType
249
+ Type of measure. Should be one of the MeasureType enum values and is_hazard.
250
+ selection_type: SelectionType
251
+ Type of selection. Should be one of the SelectionType enum values.
252
+ polygon_file: str, Optional, default = None
253
+ Path to a polygon file, either absolute or relative to the measure path in the database.
254
+ property_type: str
255
+ Type of property. Should be one of the PropertyType enum values.
256
+ aggregation_area_type: str, Optional, default = None
257
+ Type of aggregation area. Should be one of the SelectionType enum values.
258
+ aggregation_area_name: str, Optional, default = None
259
+ Name of the aggregation area.
260
+ """
261
+
262
+ property_type: str # TODO make enum
263
+
264
+ @field_validator("type")
265
+ def validate_type(cls, value):
266
+ if not MeasureType.is_impact(value):
267
+ raise ValueError(f"Invalid impact type: {value}")
268
+ return value
269
+
270
+
271
+ class Elevate(ImpactMeasure):
272
+ """The expected variables and data types of the "elevate" impact measure.
273
+
274
+ Attributes
275
+ ----------
276
+ name: str
277
+ Name of the measure.
278
+ description: str
279
+ Description of the measure.
280
+ type : MeasureType
281
+ Type of measure. Should be "elevate_properties".
282
+ selection_type : SelectionType
283
+ Type of selection. Should be "polygon" or "aggregation_area".
284
+ polygon_file : str, Optional
285
+ Path to a polygon file, either absolute or relative to the measure path.
286
+ aggregation_area_type : str, Optional
287
+ Type of aggregation area. Should be "aggregation_area" or "all".
288
+ aggregation_area_name : str, Optional
289
+ Name of the aggregation area.
290
+ property_type : str
291
+ Type of property. Should be "residential" or "commercial".
292
+ elevation : us.UnitfulLengthRefValue
293
+ Elevation of the properties.
294
+ """
295
+
296
+ type: MeasureType = MeasureType.elevate_properties
297
+ elevation: us.UnitfulLengthRefValue
298
+
299
+
300
+ class Buyout(ImpactMeasure):
301
+ """The expected variables and data types of the "buyout" impact measure.
302
+
303
+ Attributes
304
+ ----------
305
+ name: str
306
+ Name of the measure.
307
+ description: str, default ""
308
+ Description of the measure.
309
+ type : MeasureType, default MeasureType.buyout_properties
310
+ Type of measure.
311
+ selection_type : SelectionType
312
+ Type of selection. Should be "polygon" or "aggregation_area".
313
+ polygon_file : str, Optional
314
+ Path to a polygon file, either absolute or relative to the measure path.
315
+ aggregation_area_type : str, Optional
316
+ Type of aggregation area. Should be "aggregation_area" or "all".
317
+ aggregation_area_name : str, Optional
318
+ Name of the aggregation area.
319
+ property_type : str
320
+ Type of property. Should be "residential" or "commercial".
321
+ elevation : us.UnitfulLengthRefValue
322
+ Elevation of the properties.
323
+
324
+ """
325
+
326
+ # Buyout has only the basic impact measure attributes
327
+ type: MeasureType = MeasureType.buyout_properties
328
+
329
+
330
+ class FloodProof(ImpactMeasure):
331
+ """The expected variables and data types of the "floodproof" impact measure.
332
+
333
+ Attributes
334
+ ----------
335
+ name: str
336
+ Name of the measure.
337
+ description: str
338
+ Description of the measure.
339
+ type : MeasureType
340
+ Type of measure. Should be "floodproof_properties".
341
+ selection_type : SelectionType
342
+ Type of selection. Should be "polygon" or "aggregation_area".
343
+ polygon_file : str, Optional
344
+ Path to a polygon file, either absolute or relative to the measure path.
345
+ aggregation_area_type : str, Optional
346
+ Type of aggregation area. Should be "aggregation_area" or "all".
347
+ aggregation_area_name : str, Optional
348
+ Name of the aggregation area.
349
+ property_type : str
350
+ Type of property. Should be "residential" or "commercial".
351
+ elevation : us.UnitfulLengthRefValue
352
+ Elevation of the properties.
353
+ """
354
+
355
+ type: MeasureType = MeasureType.floodproof_properties
356
+ elevation: us.UnitfulLength
357
+
358
+
359
+ class FloodWall(HazardMeasure):
360
+ """
361
+ The expected variables and data types of the "floodwall" hazard measure.
362
+
363
+ Attributes
364
+ ----------
365
+ name: str
366
+ Name of the measure.
367
+ description: str
368
+ Description of the measure.
369
+ type : MeasureType
370
+ Type of measure. Should be "MeasureType.floodwall"
371
+ selection_type : SelectionType
372
+ Type of selection. Should be "SelectionType.polygon" or "SelectionType.aggregation_area".
373
+ polygon_file : Optional[str]
374
+ Path to a polygon file, either absolute or relative to the measure path.
375
+ elevation : us.UnitfulLength
376
+ Height of the floodwall.
377
+ absolute_elevation : bool
378
+ TODO remove?
379
+ """
380
+
381
+ type: MeasureType = MeasureType.floodwall
382
+ elevation: us.UnitfulLength
383
+ absolute_elevation: Optional[bool] = False
384
+
385
+
386
+ class Pump(HazardMeasure):
387
+ """
388
+ The expected variables and data types of the "pump" hazard measure.
389
+
390
+ Attributes
391
+ ----------
392
+ name: str
393
+ Name of the measure.
394
+ description: str
395
+ Description of the measure.
396
+ type : MeasureType
397
+ Type of measure. Should be "pump"
398
+ selection_type : SelectionType
399
+ Type of selection. Should be "polyline".
400
+ polygon_file : str, Optional
401
+ Path to a polygon file, either absolute or relative to the measure path.
402
+ elevation : us.UnitfulLength
403
+ Height of the floodwall.
404
+ absolute_elevation : bool
405
+ TODO remove?
406
+ """
407
+
408
+ type: MeasureType = MeasureType.pump
409
+ discharge: us.UnitfulDischarge
410
+
411
+
412
+ class GreenInfrastructure(HazardMeasure):
413
+ """The expected variables and data types of the "green infrastructure" hazard measure.
414
+
415
+ Attributes
416
+ ----------
417
+ name: str
418
+ Name of the measure.
419
+ description: str
420
+ Description of the measure.
421
+ type : MeasureType
422
+ Type of measure. Should be "greening"
423
+ selection_type : SelectionType
424
+ Type of selection. Should be "polygon" or "aggregation_area".
425
+ height : us.UnitfulHeight, Optional
426
+ Height of the green infrastructure.
427
+ volume : us.UnitfulVolume, Optional
428
+ Volume of the green infrastructure.
429
+ polygon_file : str, Optional
430
+ Path to a polygon file, either absolute or relative to the measure path.
431
+ aggregation_area_type : str, Optional
432
+ Type of aggregation area. Should be "aggregation_area".
433
+ aggregation_area_name : str, Optional
434
+ Name of the aggregation area.
435
+ percent_area : float, Optional
436
+ Percentage of the area that is green infrastructure.
437
+ """
438
+
439
+ type: MeasureType = MeasureType.greening
440
+ volume: us.UnitfulVolume
441
+ height: Optional[us.UnitfulHeight] = None
442
+ aggregation_area_type: Optional[str] = None
443
+ aggregation_area_name: Optional[str] = None
444
+ percent_area: Optional[float] = Field(default=None, ge=0, le=100)
445
+
446
+ @field_validator("height", mode="before", check_fields=False)
447
+ def height_from_length(value: Any) -> Any:
448
+ if isinstance(value, us.UnitfulLength):
449
+ return us.UnitfulHeight(value=value.value, units=value.units)
450
+ return value
451
+
452
+ @model_validator(mode="after")
453
+ def validate_hazard_type_values(self) -> "GreenInfrastructure":
454
+ e_msg = f"Error parsing GreenInfrastructure: {self.name}"
455
+
456
+ if self.type == MeasureType.total_storage:
457
+ if self.height is not None or self.percent_area is not None:
458
+ raise ValueError(
459
+ f"{e_msg}\nHeight and percent_area cannot be set for total storage type measures"
460
+ )
461
+ return self
462
+ elif self.type == MeasureType.water_square:
463
+ if self.percent_area is not None:
464
+ raise ValueError(
465
+ f"{e_msg}\nPercentage_area cannot be set for water square type measures"
466
+ )
467
+ elif not isinstance(self.height, us.UnitfulHeight):
468
+ raise ValueError(
469
+ f"{e_msg}\nHeight needs to be set for water square type measures"
470
+ )
471
+ return self
472
+ elif self.type == MeasureType.greening:
473
+ if not isinstance(self.height, us.UnitfulHeight) or not isinstance(
474
+ self.percent_area, float
475
+ ):
476
+ raise ValueError(
477
+ f"{e_msg}\nHeight and percent_area needs to be set for greening type measures"
478
+ )
479
+ else:
480
+ raise ValueError(
481
+ f"{e_msg}\nType must be one of 'water_square', 'greening', or 'total_storage'"
482
+ )
483
+ return self
484
+
485
+ @model_validator(mode="after")
486
+ def validate_selection_type_values(self) -> "GreenInfrastructure":
487
+ if self.selection_type == SelectionType.aggregation_area:
488
+ if self.aggregation_area_name is None:
489
+ raise ValueError(
490
+ "If `selection_type` is 'aggregation_area', then `aggregation_area_name` needs to be set."
491
+ )
492
+ if self.aggregation_area_type is None:
493
+ raise ValueError(
494
+ "If `selection_type` is 'aggregation_area', then `aggregation_area_type` needs to be set."
495
+ )
496
+ return self
497
+
498
+ @staticmethod
499
+ def calculate_volume(
500
+ area: us.UnitfulArea,
501
+ height: us.UnitfulHeight,
502
+ percent_area: float = 100.0,
503
+ ) -> float:
504
+ """Determine volume from area of the polygon and infiltration height.
505
+
506
+ Parameters
507
+ ----------
508
+ area : us.UnitfulArea
509
+ Area of polygon with units (calculated using calculate_polygon_area)
510
+ height : us.UnitfulHeight
511
+ Water height with units
512
+ percent_area : float, optional
513
+ Percentage area covered by green infrastructure [%], by default 100.0
514
+
515
+ Returns
516
+ -------
517
+ float
518
+ Volume [m3]
519
+ """
520
+ volume = (
521
+ area.convert(us.UnitTypesArea.m2)
522
+ * height.convert(us.UnitTypesLength.meters)
523
+ * (percent_area / 100.0)
524
+ )
525
+ return volume
526
+
527
+ @staticmethod
528
+ def calculate_polygon_area(gdf: gpd.GeoDataFrame, site: Site) -> float:
529
+ """Calculate area of a GeoDataFrame Polygon.
530
+
531
+ Parameters
532
+ ----------
533
+ gdf : gpd.GeoDataFrame
534
+ Polygon object
535
+ site : Site
536
+ site config (used for CRS)
537
+
538
+ Returns
539
+ -------
540
+ area : float
541
+ Area of the given polygon
542
+ """
543
+ # Determine local CRS
544
+ crs = pyproj.CRS.from_string(site.sfincs.config.csname)
545
+ gdf = gdf.to_crs(crs)
546
+
547
+ # The GeoJSON file can contain multiple polygons
548
+ polygon = gdf.geometry
549
+ # Calculate the area of all polygons
550
+ area = polygon.area.sum()
551
+ return area