flood-adapt 0.3.8__py3-none-any.whl → 0.3.10__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 +26 -22
- flood_adapt/adapter/__init__.py +9 -9
- flood_adapt/adapter/fiat_adapter.py +1541 -1536
- flood_adapt/adapter/interface/hazard_adapter.py +70 -70
- flood_adapt/adapter/interface/impact_adapter.py +36 -36
- flood_adapt/adapter/interface/model_adapter.py +89 -89
- flood_adapt/adapter/interface/offshore.py +19 -19
- flood_adapt/adapter/sfincs_adapter.py +1848 -1846
- flood_adapt/adapter/sfincs_offshore.py +193 -193
- flood_adapt/config/config.py +248 -290
- flood_adapt/config/fiat.py +219 -219
- flood_adapt/config/gui.py +331 -331
- flood_adapt/config/sfincs.py +481 -336
- flood_adapt/config/site.py +129 -129
- flood_adapt/database_builder/database_builder.py +2210 -2210
- flood_adapt/database_builder/templates/default_units/imperial.toml +9 -9
- flood_adapt/database_builder/templates/default_units/metric.toml +9 -9
- flood_adapt/database_builder/templates/green_infra_table/green_infra_lookup_table.csv +10 -10
- flood_adapt/database_builder/templates/infographics/OSM/config_charts.toml +90 -90
- flood_adapt/database_builder/templates/infographics/OSM/config_people.toml +57 -57
- flood_adapt/database_builder/templates/infographics/OSM/config_risk_charts.toml +121 -121
- flood_adapt/database_builder/templates/infographics/OSM/config_roads.toml +65 -65
- flood_adapt/database_builder/templates/infographics/OSM/styles.css +45 -45
- flood_adapt/database_builder/templates/infographics/US_NSI/config_charts.toml +126 -126
- flood_adapt/database_builder/templates/infographics/US_NSI/config_people.toml +60 -60
- flood_adapt/database_builder/templates/infographics/US_NSI/config_risk_charts.toml +121 -121
- flood_adapt/database_builder/templates/infographics/US_NSI/config_roads.toml +65 -65
- flood_adapt/database_builder/templates/infographics/US_NSI/styles.css +45 -45
- flood_adapt/database_builder/templates/infometrics/OSM/metrics_additional_risk_configs.toml +4 -4
- flood_adapt/database_builder/templates/infometrics/OSM/with_SVI/infographic_metrics_config.toml +143 -143
- flood_adapt/database_builder/templates/infometrics/OSM/with_SVI/infographic_metrics_config_risk.toml +153 -153
- flood_adapt/database_builder/templates/infometrics/OSM/without_SVI/infographic_metrics_config.toml +127 -127
- flood_adapt/database_builder/templates/infometrics/OSM/without_SVI/infographic_metrics_config_risk.toml +57 -57
- flood_adapt/database_builder/templates/infometrics/US_NSI/metrics_additional_risk_configs.toml +4 -4
- flood_adapt/database_builder/templates/infometrics/US_NSI/with_SVI/infographic_metrics_config.toml +191 -191
- flood_adapt/database_builder/templates/infometrics/US_NSI/with_SVI/infographic_metrics_config_risk.toml +153 -153
- flood_adapt/database_builder/templates/infometrics/US_NSI/without_SVI/infographic_metrics_config.toml +178 -178
- flood_adapt/database_builder/templates/infometrics/US_NSI/without_SVI/infographic_metrics_config_risk.toml +57 -57
- flood_adapt/database_builder/templates/infometrics/mandatory_metrics_config.toml +9 -9
- flood_adapt/database_builder/templates/infometrics/mandatory_metrics_config_risk.toml +65 -65
- flood_adapt/database_builder/templates/output_layers/bin_colors.toml +5 -5
- flood_adapt/database_builder.py +16 -16
- flood_adapt/dbs_classes/__init__.py +21 -21
- flood_adapt/dbs_classes/database.py +495 -688
- flood_adapt/dbs_classes/dbs_benefit.py +77 -76
- flood_adapt/dbs_classes/dbs_event.py +61 -59
- flood_adapt/dbs_classes/dbs_measure.py +112 -111
- flood_adapt/dbs_classes/dbs_projection.py +34 -34
- flood_adapt/dbs_classes/dbs_scenario.py +137 -137
- flood_adapt/dbs_classes/dbs_static.py +274 -273
- flood_adapt/dbs_classes/dbs_strategy.py +130 -129
- flood_adapt/dbs_classes/dbs_template.py +279 -278
- flood_adapt/dbs_classes/interface/database.py +107 -139
- flood_adapt/dbs_classes/interface/element.py +121 -121
- flood_adapt/dbs_classes/interface/static.py +47 -47
- flood_adapt/flood_adapt.py +1207 -1178
- flood_adapt/misc/database_user.py +16 -16
- flood_adapt/misc/exceptions.py +22 -0
- flood_adapt/misc/log.py +183 -183
- flood_adapt/misc/path_builder.py +54 -54
- flood_adapt/misc/utils.py +185 -185
- flood_adapt/objects/__init__.py +82 -82
- flood_adapt/objects/benefits/benefits.py +61 -61
- flood_adapt/objects/events/event_factory.py +135 -135
- flood_adapt/objects/events/event_set.py +88 -84
- flood_adapt/objects/events/events.py +234 -234
- flood_adapt/objects/events/historical.py +58 -58
- flood_adapt/objects/events/hurricane.py +68 -67
- flood_adapt/objects/events/synthetic.py +46 -50
- flood_adapt/objects/forcing/__init__.py +92 -92
- flood_adapt/objects/forcing/csv.py +68 -68
- flood_adapt/objects/forcing/discharge.py +66 -66
- flood_adapt/objects/forcing/forcing.py +150 -150
- flood_adapt/objects/forcing/forcing_factory.py +182 -182
- flood_adapt/objects/forcing/meteo_handler.py +93 -93
- flood_adapt/objects/forcing/netcdf.py +40 -40
- flood_adapt/objects/forcing/plotting.py +453 -429
- flood_adapt/objects/forcing/rainfall.py +98 -98
- flood_adapt/objects/forcing/tide_gauge.py +191 -191
- flood_adapt/objects/forcing/time_frame.py +90 -90
- flood_adapt/objects/forcing/timeseries.py +564 -564
- flood_adapt/objects/forcing/unit_system.py +580 -580
- flood_adapt/objects/forcing/waterlevels.py +108 -108
- flood_adapt/objects/forcing/wind.py +124 -124
- flood_adapt/objects/measures/measure_factory.py +92 -92
- flood_adapt/objects/measures/measures.py +529 -529
- flood_adapt/objects/object_model.py +74 -68
- flood_adapt/objects/projections/projections.py +103 -89
- flood_adapt/objects/scenarios/scenarios.py +22 -22
- flood_adapt/objects/strategies/strategies.py +89 -89
- flood_adapt/workflows/benefit_runner.py +579 -544
- flood_adapt/workflows/floodmap.py +85 -85
- flood_adapt/workflows/impacts_integrator.py +85 -82
- flood_adapt/workflows/scenario_runner.py +70 -70
- {flood_adapt-0.3.8.dist-info → flood_adapt-0.3.10.dist-info}/LICENSE +674 -674
- {flood_adapt-0.3.8.dist-info → flood_adapt-0.3.10.dist-info}/METADATA +866 -860
- flood_adapt-0.3.10.dist-info/RECORD +140 -0
- flood_adapt-0.3.8.dist-info/RECORD +0 -139
- {flood_adapt-0.3.8.dist-info → flood_adapt-0.3.10.dist-info}/WHEEL +0 -0
- {flood_adapt-0.3.8.dist-info → flood_adapt-0.3.10.dist-info}/top_level.txt +0 -0
|
@@ -1,429 +1,453 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
from tempfile import gettempdir
|
|
3
|
-
from typing import List, Optional
|
|
4
|
-
|
|
5
|
-
import pandas as pd
|
|
6
|
-
import plotly.express as px
|
|
7
|
-
import plotly.graph_objects as go
|
|
8
|
-
from plotly.subplots import make_subplots
|
|
9
|
-
|
|
10
|
-
from flood_adapt.config.site import Site
|
|
11
|
-
from flood_adapt.misc.log import FloodAdaptLogging
|
|
12
|
-
from flood_adapt.misc.path_builder import (
|
|
13
|
-
db_path,
|
|
14
|
-
)
|
|
15
|
-
from flood_adapt.objects.events.events import Event, Template
|
|
16
|
-
from flood_adapt.objects.forcing.discharge import (
|
|
17
|
-
DischargeConstant,
|
|
18
|
-
DischargeCSV,
|
|
19
|
-
DischargeSynthetic,
|
|
20
|
-
)
|
|
21
|
-
from flood_adapt.objects.forcing.forcing import (
|
|
22
|
-
ForcingSource,
|
|
23
|
-
ForcingType,
|
|
24
|
-
IDischarge,
|
|
25
|
-
)
|
|
26
|
-
from flood_adapt.objects.forcing.rainfall import (
|
|
27
|
-
RainfallConstant,
|
|
28
|
-
RainfallCSV,
|
|
29
|
-
RainfallSynthetic,
|
|
30
|
-
)
|
|
31
|
-
from flood_adapt.objects.forcing.waterlevels import (
|
|
32
|
-
WaterlevelCSV,
|
|
33
|
-
WaterlevelGauged,
|
|
34
|
-
WaterlevelSynthetic,
|
|
35
|
-
)
|
|
36
|
-
from flood_adapt.objects.forcing.wind import (
|
|
37
|
-
WindConstant,
|
|
38
|
-
WindCSV,
|
|
39
|
-
WindSynthetic,
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
# TODO remove from frontend
|
|
43
|
-
UNPLOTTABLE_SOURCES = [ForcingSource.TRACK, ForcingSource.METEO, ForcingSource.MODEL]
|
|
44
|
-
logger = FloodAdaptLogging.getLogger("Plotting")
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def plot_forcing(
|
|
48
|
-
event: Event,
|
|
49
|
-
site: Site,
|
|
50
|
-
forcing_type: ForcingType,
|
|
51
|
-
) -> tuple[str, Optional[List[Exception]]]:
|
|
52
|
-
"""Plot the forcing data for the event."""
|
|
53
|
-
if event.forcings.get(forcing_type) is None:
|
|
54
|
-
return "", None
|
|
55
|
-
|
|
56
|
-
match forcing_type:
|
|
57
|
-
case ForcingType.RAINFALL:
|
|
58
|
-
return plot_rainfall(event, site)
|
|
59
|
-
case ForcingType.WIND:
|
|
60
|
-
return plot_wind(event, site)
|
|
61
|
-
case ForcingType.WATERLEVEL:
|
|
62
|
-
return plot_waterlevel(event, site)
|
|
63
|
-
case ForcingType.DISCHARGE:
|
|
64
|
-
return plot_discharge(event, site)
|
|
65
|
-
case _:
|
|
66
|
-
raise NotImplementedError(
|
|
67
|
-
"Plotting only available for rainfall, wind, waterlevel, and discharge forcings."
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def plot_discharge(
|
|
72
|
-
event: Event,
|
|
73
|
-
site: Site,
|
|
74
|
-
) -> tuple[str, Optional[List[Exception]]]:
|
|
75
|
-
rivers: List[IDischarge] = event.forcings.get(ForcingType.DISCHARGE)
|
|
76
|
-
if site.sfincs.river is None:
|
|
77
|
-
raise ValueError("No rivers defined for this site.")
|
|
78
|
-
elif not rivers:
|
|
79
|
-
return "", None
|
|
80
|
-
logger.debug("Plotting discharge data")
|
|
81
|
-
|
|
82
|
-
units = site.gui.units.default_discharge_units
|
|
83
|
-
|
|
84
|
-
data = pd.DataFrame()
|
|
85
|
-
errors = []
|
|
86
|
-
|
|
87
|
-
for discharge in rivers:
|
|
88
|
-
try:
|
|
89
|
-
if discharge.source in UNPLOTTABLE_SOURCES:
|
|
90
|
-
logger.debug(
|
|
91
|
-
f"Plotting not supported for discharge data from `{discharge.source}`"
|
|
92
|
-
)
|
|
93
|
-
continue
|
|
94
|
-
elif isinstance(
|
|
95
|
-
discharge, (DischargeConstant, DischargeSynthetic, DischargeCSV)
|
|
96
|
-
):
|
|
97
|
-
river_data = discharge.to_dataframe(event.time)
|
|
98
|
-
else:
|
|
99
|
-
raise ValueError(f"Unknown discharge source: `{discharge.source}`")
|
|
100
|
-
|
|
101
|
-
# Rename columns to avoid conflicts
|
|
102
|
-
river_data.columns = [discharge.river.name]
|
|
103
|
-
if data.empty:
|
|
104
|
-
data = river_data
|
|
105
|
-
else:
|
|
106
|
-
# add river_data as a column to the dataframe. keep the same index
|
|
107
|
-
data = data.join(river_data, how="outer")
|
|
108
|
-
except Exception as e:
|
|
109
|
-
errors.append((discharge.river.name, e))
|
|
110
|
-
|
|
111
|
-
if errors:
|
|
112
|
-
logger.error(
|
|
113
|
-
f"Could not retrieve discharge data for {', '.join([entry[0] for entry in errors])}: {errors}"
|
|
114
|
-
)
|
|
115
|
-
return "", errors
|
|
116
|
-
|
|
117
|
-
river_names, river_descriptions = [], []
|
|
118
|
-
for river in site.sfincs.river:
|
|
119
|
-
river_names.append(river.name)
|
|
120
|
-
river_descriptions.append(river.description or river.name)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
data
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
)
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
)
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
return "",
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
fig
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from tempfile import gettempdir
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import plotly.express as px
|
|
7
|
+
import plotly.graph_objects as go
|
|
8
|
+
from plotly.subplots import make_subplots
|
|
9
|
+
|
|
10
|
+
from flood_adapt.config.site import Site
|
|
11
|
+
from flood_adapt.misc.log import FloodAdaptLogging
|
|
12
|
+
from flood_adapt.misc.path_builder import (
|
|
13
|
+
db_path,
|
|
14
|
+
)
|
|
15
|
+
from flood_adapt.objects.events.events import Event, Template
|
|
16
|
+
from flood_adapt.objects.forcing.discharge import (
|
|
17
|
+
DischargeConstant,
|
|
18
|
+
DischargeCSV,
|
|
19
|
+
DischargeSynthetic,
|
|
20
|
+
)
|
|
21
|
+
from flood_adapt.objects.forcing.forcing import (
|
|
22
|
+
ForcingSource,
|
|
23
|
+
ForcingType,
|
|
24
|
+
IDischarge,
|
|
25
|
+
)
|
|
26
|
+
from flood_adapt.objects.forcing.rainfall import (
|
|
27
|
+
RainfallConstant,
|
|
28
|
+
RainfallCSV,
|
|
29
|
+
RainfallSynthetic,
|
|
30
|
+
)
|
|
31
|
+
from flood_adapt.objects.forcing.waterlevels import (
|
|
32
|
+
WaterlevelCSV,
|
|
33
|
+
WaterlevelGauged,
|
|
34
|
+
WaterlevelSynthetic,
|
|
35
|
+
)
|
|
36
|
+
from flood_adapt.objects.forcing.wind import (
|
|
37
|
+
WindConstant,
|
|
38
|
+
WindCSV,
|
|
39
|
+
WindSynthetic,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# TODO remove from frontend
|
|
43
|
+
UNPLOTTABLE_SOURCES = [ForcingSource.TRACK, ForcingSource.METEO, ForcingSource.MODEL]
|
|
44
|
+
logger = FloodAdaptLogging.getLogger("Plotting")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def plot_forcing(
|
|
48
|
+
event: Event,
|
|
49
|
+
site: Site,
|
|
50
|
+
forcing_type: ForcingType,
|
|
51
|
+
) -> tuple[str, Optional[List[Exception]]]:
|
|
52
|
+
"""Plot the forcing data for the event."""
|
|
53
|
+
if event.forcings.get(forcing_type) is None:
|
|
54
|
+
return "", None
|
|
55
|
+
|
|
56
|
+
match forcing_type:
|
|
57
|
+
case ForcingType.RAINFALL:
|
|
58
|
+
return plot_rainfall(event, site)
|
|
59
|
+
case ForcingType.WIND:
|
|
60
|
+
return plot_wind(event, site)
|
|
61
|
+
case ForcingType.WATERLEVEL:
|
|
62
|
+
return plot_waterlevel(event, site)
|
|
63
|
+
case ForcingType.DISCHARGE:
|
|
64
|
+
return plot_discharge(event, site)
|
|
65
|
+
case _:
|
|
66
|
+
raise NotImplementedError(
|
|
67
|
+
"Plotting only available for rainfall, wind, waterlevel, and discharge forcings."
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def plot_discharge(
|
|
72
|
+
event: Event,
|
|
73
|
+
site: Site,
|
|
74
|
+
) -> tuple[str, Optional[List[Exception]]]:
|
|
75
|
+
rivers: List[IDischarge] = event.forcings.get(ForcingType.DISCHARGE)
|
|
76
|
+
if site.sfincs.river is None:
|
|
77
|
+
raise ValueError("No rivers defined for this site.")
|
|
78
|
+
elif not rivers:
|
|
79
|
+
return "", None
|
|
80
|
+
logger.debug("Plotting discharge data")
|
|
81
|
+
|
|
82
|
+
units = site.gui.units.default_discharge_units
|
|
83
|
+
|
|
84
|
+
data = pd.DataFrame()
|
|
85
|
+
errors = []
|
|
86
|
+
|
|
87
|
+
for discharge in rivers:
|
|
88
|
+
try:
|
|
89
|
+
if discharge.source in UNPLOTTABLE_SOURCES:
|
|
90
|
+
logger.debug(
|
|
91
|
+
f"Plotting not supported for discharge data from `{discharge.source}`"
|
|
92
|
+
)
|
|
93
|
+
continue
|
|
94
|
+
elif isinstance(
|
|
95
|
+
discharge, (DischargeConstant, DischargeSynthetic, DischargeCSV)
|
|
96
|
+
):
|
|
97
|
+
river_data = discharge.to_dataframe(event.time)
|
|
98
|
+
else:
|
|
99
|
+
raise ValueError(f"Unknown discharge source: `{discharge.source}`")
|
|
100
|
+
|
|
101
|
+
# Rename columns to avoid conflicts
|
|
102
|
+
river_data.columns = [discharge.river.name]
|
|
103
|
+
if data.empty:
|
|
104
|
+
data = river_data
|
|
105
|
+
else:
|
|
106
|
+
# add river_data as a column to the dataframe. keep the same index
|
|
107
|
+
data = data.join(river_data, how="outer")
|
|
108
|
+
except Exception as e:
|
|
109
|
+
errors.append((discharge.river.name, e))
|
|
110
|
+
|
|
111
|
+
if errors:
|
|
112
|
+
logger.error(
|
|
113
|
+
f"Could not retrieve discharge data for {', '.join([entry[0] for entry in errors])}: {errors}"
|
|
114
|
+
)
|
|
115
|
+
return "", errors
|
|
116
|
+
|
|
117
|
+
river_names, river_descriptions = [], []
|
|
118
|
+
for river in site.sfincs.river:
|
|
119
|
+
river_names.append(river.name)
|
|
120
|
+
river_descriptions.append(river.description or river.name)
|
|
121
|
+
|
|
122
|
+
if event.template == Template.Synthetic:
|
|
123
|
+
data.index = (
|
|
124
|
+
data.index - data.index[0]
|
|
125
|
+
).total_seconds() / 3600 # Convert to hours
|
|
126
|
+
x_title = "Hours from start"
|
|
127
|
+
else:
|
|
128
|
+
x_title = "Time"
|
|
129
|
+
|
|
130
|
+
# Plot actual thing
|
|
131
|
+
fig = go.Figure()
|
|
132
|
+
for ii, col in enumerate(data.columns):
|
|
133
|
+
fig.add_trace(
|
|
134
|
+
go.Scatter(
|
|
135
|
+
x=data.index,
|
|
136
|
+
y=data[col],
|
|
137
|
+
name=river_descriptions[ii],
|
|
138
|
+
mode="lines",
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
fig.update_layout(
|
|
143
|
+
autosize=False,
|
|
144
|
+
height=100 * 2,
|
|
145
|
+
width=280 * 2,
|
|
146
|
+
margin={"r": 0, "l": 0, "b": 0, "t": 0},
|
|
147
|
+
font={"size": 10, "color": "black", "family": "Arial"},
|
|
148
|
+
title_font={"size": 10, "color": "black", "family": "Arial"},
|
|
149
|
+
yaxis_title_font={"size": 10, "color": "black", "family": "Arial"},
|
|
150
|
+
xaxis_title_font={"size": 10, "color": "black", "family": "Arial"},
|
|
151
|
+
xaxis_title={"text": x_title},
|
|
152
|
+
yaxis_title={"text": f"River discharge [{units.value}]"},
|
|
153
|
+
xaxis={"range": [event.time.start_time, event.time.end_time]},
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Only save to the the event folder if that has been created already.
|
|
157
|
+
# Otherwise this will create the folder and break the db since there is no event.toml yet
|
|
158
|
+
output_dir = db_path(object_dir="events", obj_name=event.name)
|
|
159
|
+
if not output_dir.exists():
|
|
160
|
+
output_dir = gettempdir()
|
|
161
|
+
output_loc = Path(output_dir) / "discharge_timeseries.html"
|
|
162
|
+
if output_loc.exists():
|
|
163
|
+
output_loc.unlink()
|
|
164
|
+
fig.write_html(output_loc)
|
|
165
|
+
return str(output_loc), None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def plot_waterlevel(
|
|
169
|
+
event: Event,
|
|
170
|
+
site: Site,
|
|
171
|
+
) -> tuple[str, Optional[List[Exception]]]:
|
|
172
|
+
forcing_list = event.forcings.get(ForcingType.WATERLEVEL)
|
|
173
|
+
if not forcing_list:
|
|
174
|
+
return "", None
|
|
175
|
+
elif site.sfincs.water_level is None:
|
|
176
|
+
raise ValueError("No water levels defined for this site.")
|
|
177
|
+
|
|
178
|
+
waterlevel = forcing_list[0]
|
|
179
|
+
if waterlevel.source in UNPLOTTABLE_SOURCES:
|
|
180
|
+
logger.debug(
|
|
181
|
+
f"Plotting not supported for waterlevel data from {waterlevel.source}"
|
|
182
|
+
)
|
|
183
|
+
return "", None
|
|
184
|
+
|
|
185
|
+
logger.debug("Plotting water level data")
|
|
186
|
+
units = site.gui.units.default_length_units
|
|
187
|
+
data = None
|
|
188
|
+
try:
|
|
189
|
+
if isinstance(waterlevel, WaterlevelGauged):
|
|
190
|
+
if site.sfincs.tide_gauge is None:
|
|
191
|
+
raise ValueError("No tide gauge defined for this site.")
|
|
192
|
+
data = site.sfincs.tide_gauge.get_waterlevels_in_time_frame(
|
|
193
|
+
event.time, units=units
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Convert to main reference
|
|
197
|
+
datum_correction = site.sfincs.water_level.get_datum(
|
|
198
|
+
site.sfincs.tide_gauge.reference
|
|
199
|
+
).height.convert(units)
|
|
200
|
+
data += datum_correction
|
|
201
|
+
|
|
202
|
+
elif isinstance(waterlevel, WaterlevelCSV):
|
|
203
|
+
data = waterlevel.to_dataframe(event.time)
|
|
204
|
+
elif isinstance(waterlevel, WaterlevelSynthetic):
|
|
205
|
+
data = waterlevel.to_dataframe(time_frame=event.time)
|
|
206
|
+
datum_correction = site.sfincs.water_level.get_datum(
|
|
207
|
+
site.gui.plotting.synthetic_tide.datum
|
|
208
|
+
).height.convert(units)
|
|
209
|
+
data += datum_correction
|
|
210
|
+
else:
|
|
211
|
+
raise ValueError(f"Unknown waterlevel type: {waterlevel}")
|
|
212
|
+
|
|
213
|
+
except Exception as e:
|
|
214
|
+
logger.error(f"Error getting water level data: {e}")
|
|
215
|
+
return "", [e]
|
|
216
|
+
|
|
217
|
+
if data is not None and data.empty:
|
|
218
|
+
logger.error(f"Could not retrieve waterlevel data: {waterlevel} {data}")
|
|
219
|
+
return "", None
|
|
220
|
+
|
|
221
|
+
if event.template == Template.Synthetic:
|
|
222
|
+
data.index = (
|
|
223
|
+
data.index - data.index[0]
|
|
224
|
+
).total_seconds() / 3600 # Convert to hours
|
|
225
|
+
x_title = "Hours from start"
|
|
226
|
+
else:
|
|
227
|
+
x_title = "Time"
|
|
228
|
+
|
|
229
|
+
# Plot actual thing
|
|
230
|
+
fig = px.line(data)
|
|
231
|
+
|
|
232
|
+
# plot main reference
|
|
233
|
+
fig.add_hline(
|
|
234
|
+
y=0,
|
|
235
|
+
line_dash="dash",
|
|
236
|
+
line_color="#000000",
|
|
237
|
+
annotation_text=site.sfincs.water_level.reference,
|
|
238
|
+
annotation_position="bottom right",
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# plot other references
|
|
242
|
+
for wl_ref in site.sfincs.water_level.datums:
|
|
243
|
+
if (
|
|
244
|
+
wl_ref.name == site.sfincs.config.overland_model.reference
|
|
245
|
+
or wl_ref.name in site.gui.plotting.excluded_datums
|
|
246
|
+
):
|
|
247
|
+
continue
|
|
248
|
+
|
|
249
|
+
fig.add_hline(
|
|
250
|
+
y=wl_ref.height.convert(units),
|
|
251
|
+
line_dash="dash",
|
|
252
|
+
line_color="#3ec97c",
|
|
253
|
+
annotation_text=wl_ref.name,
|
|
254
|
+
annotation_position="bottom right",
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
fig.update_layout(
|
|
258
|
+
autosize=False,
|
|
259
|
+
height=100 * 2,
|
|
260
|
+
width=280 * 2,
|
|
261
|
+
margin={"r": 0, "l": 0, "b": 0, "t": 0},
|
|
262
|
+
font={"size": 10, "color": "black", "family": "Arial"},
|
|
263
|
+
title_font={"size": 10, "color": "black", "family": "Arial"},
|
|
264
|
+
legend=None,
|
|
265
|
+
xaxis_title=x_title,
|
|
266
|
+
yaxis_title=f"Water level [{units.value}]",
|
|
267
|
+
yaxis_title_font={"size": 10, "color": "black", "family": "Arial"},
|
|
268
|
+
xaxis_title_font={"size": 10, "color": "black", "family": "Arial"},
|
|
269
|
+
showlegend=False,
|
|
270
|
+
xaxis={"range": [data.index.min(), data.index.max()]},
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# Only save to the the event folder if that has been created already.
|
|
274
|
+
# Otherwise this will create the folder and break the db since there is no event.toml yet
|
|
275
|
+
output_dir = db_path(object_dir="events", obj_name=event.name)
|
|
276
|
+
if not output_dir.exists():
|
|
277
|
+
output_dir = gettempdir()
|
|
278
|
+
output_loc = Path(output_dir) / "waterlevel_timeseries.html"
|
|
279
|
+
if output_loc.exists():
|
|
280
|
+
output_loc.unlink()
|
|
281
|
+
fig.write_html(output_loc)
|
|
282
|
+
return str(output_loc), None
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def plot_rainfall(
|
|
286
|
+
event: Event,
|
|
287
|
+
site: Site,
|
|
288
|
+
) -> tuple[str, Optional[List[Exception]]]:
|
|
289
|
+
forcing_list = event.forcings.get(ForcingType.RAINFALL)
|
|
290
|
+
if not forcing_list:
|
|
291
|
+
return "", None
|
|
292
|
+
elif forcing_list[0].source in UNPLOTTABLE_SOURCES:
|
|
293
|
+
logger.warning(
|
|
294
|
+
f"Plotting not supported for rainfall datafrom sources {', '.join(UNPLOTTABLE_SOURCES)}"
|
|
295
|
+
)
|
|
296
|
+
return "", None
|
|
297
|
+
|
|
298
|
+
rainfall = forcing_list[0]
|
|
299
|
+
logger.debug("Plotting rainfall data")
|
|
300
|
+
|
|
301
|
+
data = None
|
|
302
|
+
try:
|
|
303
|
+
if isinstance(rainfall, (RainfallConstant, RainfallCSV, RainfallSynthetic)):
|
|
304
|
+
data = rainfall.to_dataframe(event.time)
|
|
305
|
+
else:
|
|
306
|
+
raise ValueError(f"Unknown rainfall type: {rainfall}")
|
|
307
|
+
except Exception as e:
|
|
308
|
+
logger.error(f"Error getting rainfall data: {e}")
|
|
309
|
+
return "", [e]
|
|
310
|
+
|
|
311
|
+
if data is None or data.empty:
|
|
312
|
+
logger.error(f"Could not retrieve rainfall data: {rainfall} {data}")
|
|
313
|
+
return "", None
|
|
314
|
+
|
|
315
|
+
# Add multiplier
|
|
316
|
+
data *= event.rainfall_multiplier
|
|
317
|
+
|
|
318
|
+
if event.template == Template.Synthetic:
|
|
319
|
+
data.index = (
|
|
320
|
+
data.index - data.index[0]
|
|
321
|
+
).total_seconds() / 3600 # Convert to hours
|
|
322
|
+
x_title = "Hours from start"
|
|
323
|
+
else:
|
|
324
|
+
x_title = "Time"
|
|
325
|
+
|
|
326
|
+
# Plot actual thing
|
|
327
|
+
fig = px.line(data_frame=data)
|
|
328
|
+
|
|
329
|
+
fig.update_layout(
|
|
330
|
+
autosize=False,
|
|
331
|
+
height=100 * 2,
|
|
332
|
+
width=280 * 2,
|
|
333
|
+
margin={"r": 0, "l": 0, "b": 0, "t": 0},
|
|
334
|
+
font={"size": 10, "color": "black", "family": "Arial"},
|
|
335
|
+
title_font={"size": 10, "color": "black", "family": "Arial"},
|
|
336
|
+
legend=None,
|
|
337
|
+
yaxis_title_font={"size": 10, "color": "black", "family": "Arial"},
|
|
338
|
+
xaxis_title_font={"size": 10, "color": "black", "family": "Arial"},
|
|
339
|
+
xaxis_title={"text": x_title},
|
|
340
|
+
yaxis_title={
|
|
341
|
+
"text": f"Rainfall intensity [{site.gui.units.default_intensity_units.value}]"
|
|
342
|
+
},
|
|
343
|
+
showlegend=False,
|
|
344
|
+
xaxis={"range": [event.time.start_time, event.time.end_time]},
|
|
345
|
+
)
|
|
346
|
+
# Only save to the the event folder if that has been created already.
|
|
347
|
+
# Otherwise this will create the folder and break the db since there is no event.toml yet
|
|
348
|
+
output_dir = db_path(object_dir="events", obj_name=event.name)
|
|
349
|
+
if not output_dir.exists():
|
|
350
|
+
output_dir = gettempdir()
|
|
351
|
+
output_loc = Path(output_dir) / "rainfall_timeseries.html"
|
|
352
|
+
if output_loc.exists():
|
|
353
|
+
output_loc.unlink()
|
|
354
|
+
fig.write_html(output_loc)
|
|
355
|
+
return str(output_loc), None
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def plot_wind(
|
|
359
|
+
event: Event,
|
|
360
|
+
site: Site,
|
|
361
|
+
) -> tuple[str, Optional[List[Exception]]]:
|
|
362
|
+
logger.debug("Plotting wind data")
|
|
363
|
+
forcing_list = event.forcings.get(ForcingType.WIND)
|
|
364
|
+
if not forcing_list:
|
|
365
|
+
return "", None
|
|
366
|
+
elif forcing_list[0].source in UNPLOTTABLE_SOURCES:
|
|
367
|
+
logger.warning(
|
|
368
|
+
f"Plotting not supported for wind data from sources {', '.join(UNPLOTTABLE_SOURCES)}"
|
|
369
|
+
)
|
|
370
|
+
return "", None
|
|
371
|
+
|
|
372
|
+
wind = forcing_list[0]
|
|
373
|
+
data = None
|
|
374
|
+
try:
|
|
375
|
+
if isinstance(wind, (WindConstant, WindCSV, WindSynthetic)):
|
|
376
|
+
data = wind.to_dataframe(event.time)
|
|
377
|
+
else:
|
|
378
|
+
raise ValueError(f"Unknown wind type: {wind}")
|
|
379
|
+
except Exception as e:
|
|
380
|
+
logger.error(f"Error getting wind data: {e}")
|
|
381
|
+
return "", [e]
|
|
382
|
+
|
|
383
|
+
if data is None or data.empty:
|
|
384
|
+
logger.error(
|
|
385
|
+
f"Could not retrieve wind data: {event.forcings.get(ForcingType.WIND)} {data}"
|
|
386
|
+
)
|
|
387
|
+
return "", None
|
|
388
|
+
|
|
389
|
+
if event.template == Template.Synthetic:
|
|
390
|
+
data.index = (
|
|
391
|
+
data.index - data.index[0]
|
|
392
|
+
).total_seconds() / 3600 # Convert to hours
|
|
393
|
+
x_title = "Hours from start"
|
|
394
|
+
else:
|
|
395
|
+
x_title = "Time"
|
|
396
|
+
|
|
397
|
+
# Plot actual thing
|
|
398
|
+
# Create figure with secondary y-axis
|
|
399
|
+
|
|
400
|
+
fig = make_subplots(specs=[[{"secondary_y": True}]])
|
|
401
|
+
|
|
402
|
+
# Add traces
|
|
403
|
+
fig.add_trace(
|
|
404
|
+
go.Scatter(
|
|
405
|
+
x=data.index,
|
|
406
|
+
y=data.iloc[:, 0],
|
|
407
|
+
name="Wind speed",
|
|
408
|
+
mode="lines",
|
|
409
|
+
),
|
|
410
|
+
secondary_y=False,
|
|
411
|
+
)
|
|
412
|
+
fig.add_trace(
|
|
413
|
+
go.Scatter(
|
|
414
|
+
x=data.index, y=data.iloc[:, 1], name="Wind direction", mode="markers"
|
|
415
|
+
),
|
|
416
|
+
secondary_y=True,
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
# Set y-axes titles
|
|
420
|
+
fig.update_yaxes(
|
|
421
|
+
title_text=f"Wind speed [{site.gui.units.default_velocity_units.value}]",
|
|
422
|
+
secondary_y=False,
|
|
423
|
+
)
|
|
424
|
+
fig.update_yaxes(
|
|
425
|
+
title_text=f"Wind direction {site.gui.units.default_direction_units.value}",
|
|
426
|
+
secondary_y=True,
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
fig.update_layout(
|
|
430
|
+
autosize=False,
|
|
431
|
+
height=100 * 2,
|
|
432
|
+
width=280 * 2,
|
|
433
|
+
margin={"r": 0, "l": 0, "b": 0, "t": 0},
|
|
434
|
+
font={"size": 10, "color": "black", "family": "Arial"},
|
|
435
|
+
title_font={"size": 10, "color": "black", "family": "Arial"},
|
|
436
|
+
legend=None,
|
|
437
|
+
yaxis_title_font={"size": 10, "color": "black", "family": "Arial"},
|
|
438
|
+
xaxis_title_font={"size": 10, "color": "black", "family": "Arial"},
|
|
439
|
+
xaxis={"range": [event.time.start_time, event.time.end_time]},
|
|
440
|
+
xaxis_title={"text": x_title},
|
|
441
|
+
showlegend=False,
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
# Only save to the the event folder if that has been created already.
|
|
445
|
+
# Otherwise this will create the folder and break the db since there is no event.toml yet
|
|
446
|
+
output_dir = db_path(object_dir="events", obj_name=event.name)
|
|
447
|
+
if not output_dir.exists():
|
|
448
|
+
output_dir = gettempdir()
|
|
449
|
+
output_loc = Path(output_dir) / "wind_timeseries.html"
|
|
450
|
+
if output_loc.exists():
|
|
451
|
+
output_loc.unlink()
|
|
452
|
+
fig.write_html(output_loc)
|
|
453
|
+
return str(output_loc), None
|