wolfhece 2.2.38__py3-none-any.whl → 2.2.40__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 (50) hide show
  1. wolfhece/Coordinates_operations.py +5 -0
  2. wolfhece/GraphNotebook.py +72 -1
  3. wolfhece/GraphProfile.py +1 -1
  4. wolfhece/MulticriteriAnalysis.py +1579 -0
  5. wolfhece/PandasGrid.py +62 -1
  6. wolfhece/PyCrosssections.py +194 -43
  7. wolfhece/PyDraw.py +891 -73
  8. wolfhece/PyGui.py +913 -72
  9. wolfhece/PyGuiHydrology.py +528 -74
  10. wolfhece/PyPalette.py +26 -4
  11. wolfhece/PyParams.py +33 -0
  12. wolfhece/PyPictures.py +2 -2
  13. wolfhece/PyVertex.py +25 -0
  14. wolfhece/PyVertexvectors.py +94 -28
  15. wolfhece/PyWMS.py +52 -36
  16. wolfhece/acceptability/acceptability.py +15 -8
  17. wolfhece/acceptability/acceptability_gui.py +507 -360
  18. wolfhece/acceptability/func.py +80 -183
  19. wolfhece/apps/version.py +1 -1
  20. wolfhece/compare_series.py +480 -0
  21. wolfhece/drawing_obj.py +12 -1
  22. wolfhece/hydrology/Catchment.py +228 -162
  23. wolfhece/hydrology/Internal_variables.py +43 -2
  24. wolfhece/hydrology/Models_characteristics.py +69 -67
  25. wolfhece/hydrology/Optimisation.py +893 -182
  26. wolfhece/hydrology/PyWatershed.py +267 -165
  27. wolfhece/hydrology/SubBasin.py +185 -140
  28. wolfhece/hydrology/cst_exchanges.py +76 -1
  29. wolfhece/hydrology/forcedexchanges.py +413 -49
  30. wolfhece/hydrology/read.py +65 -5
  31. wolfhece/hydrometry/kiwis.py +14 -7
  32. wolfhece/insyde_be/INBE_func.py +746 -0
  33. wolfhece/insyde_be/INBE_gui.py +1776 -0
  34. wolfhece/insyde_be/__init__.py +3 -0
  35. wolfhece/interpolating_raster.py +366 -0
  36. wolfhece/irm_alaro.py +1457 -0
  37. wolfhece/irm_qdf.py +889 -57
  38. wolfhece/lazviewer/laz_viewer.py +4 -1
  39. wolfhece/lifewatch.py +6 -3
  40. wolfhece/picc.py +124 -8
  41. wolfhece/pyLandUseFlanders.py +146 -0
  42. wolfhece/pydownloader.py +35 -1
  43. wolfhece/pywalous.py +225 -31
  44. wolfhece/toolshydrology_dll.py +149 -0
  45. wolfhece/wolf_array.py +63 -25
  46. {wolfhece-2.2.38.dist-info → wolfhece-2.2.40.dist-info}/METADATA +3 -1
  47. {wolfhece-2.2.38.dist-info → wolfhece-2.2.40.dist-info}/RECORD +50 -41
  48. {wolfhece-2.2.38.dist-info → wolfhece-2.2.40.dist-info}/WHEEL +0 -0
  49. {wolfhece-2.2.38.dist-info → wolfhece-2.2.40.dist-info}/entry_points.txt +0 -0
  50. {wolfhece-2.2.38.dist-info → wolfhece-2.2.40.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,746 @@
1
+ """Imports et bons paths"""
2
+ import os
3
+ import sys
4
+ _root_dir = os.getcwd()
5
+ original_sys_path = sys.path.copy()
6
+ sys.path.insert(0, os.path.join(_root_dir, r"..\.."))
7
+
8
+ from wolfhece.PyVertexvectors import Zones, zone, vector, wolfvertex
9
+ from wolfhece.picc import Picc_data, bbox_creation
10
+ from wolfhece.wolf_array import WolfArray
11
+ from wolfhece.Results2DGPU import wolfres2DGPU
12
+ import geopandas as gpd
13
+ import logging
14
+ from pathlib import Path
15
+ from tqdm import tqdm
16
+ import pandas as pd
17
+ import numpy as np
18
+ import matplotlib.pyplot as plt
19
+ from wolfhece.wolf_vrt import create_vrt_from_diverged_files_first_based, translate_vrt2tif
20
+ from wolfhece.wolf_array import WolfArray
21
+ from wolfhece.PyTranslate import _
22
+ from osgeo import gdal
23
+
24
+
25
+ #Other repository : hece_damage-------------------------------------------------------------
26
+ _root_dir = os.path.dirname(os.path.abspath(__file__))
27
+ hece_damage_path = os.path.abspath(os.path.join(_root_dir, "..", "..", "..", "hece_damage"))
28
+ sys.path.insert(0, hece_damage_path)
29
+ try:
30
+ from insyde_be.Py_INBE import PyINBE, INBE_Manager
31
+ print("Import OK")
32
+ except Exception as e:
33
+ logging.error(_(f"Problem to import insyde_be.Py_INBE: {e}"))
34
+ try:
35
+ from insyde_be.Py_INBE import PyINBE, INBE_Manager
36
+ print("Import OK")
37
+ except Exception as e:
38
+ logging.error(_(f"Problem to import insyde_be.Py_INBE: {e}"))
39
+ sys.path.pop(0)
40
+
41
+ class INBE_functions():
42
+ def __init__(self):
43
+ pass
44
+
45
+ # Assembly (FR : agglomération)
46
+ # -----------------------------
47
+ """Basically the same operations as in the config manager to agglomerate several rasters
48
+ The class Config_Manager_2D_GPU is called, however some functions were rewritten to allow
49
+ the search of a more specific word ('vuln', and not 'bath', 'mann', or 'inf').
50
+ """
51
+
52
+ def tree_name_tif(self, folder_path, name):
53
+ """Find all .tiff files starting with 'vuln' in the directory and return paths"""
54
+ folder = Path(folder_path)
55
+ vuln_tiff_files = {file for file in folder.rglob("*.tiff") if file.name.startswith(name)}
56
+ vuln_tif_files = {file for file in folder.rglob("*.tif") if file.name.startswith(name)}
57
+
58
+ vuln_files = vuln_tiff_files.union(vuln_tif_files)
59
+
60
+ tiff_trees = []
61
+ if len(vuln_files) !=0:
62
+ for tiff in vuln_files:
63
+ curtree = [tiff]
64
+ while tiff.parent != folder:
65
+ tiff = tiff.parent
66
+ curtree.insert(0, tiff)
67
+ tiff_trees.append(curtree)
68
+ return tiff_trees
69
+
70
+ def select_name_tif(self, path_baseline: Path, folder_path: Path, name, filter_type = False) -> tuple[list[Path], list[Path]]:
71
+ """
72
+ Collects all .tiff files starting with `name` from `folder_path` and appends them to a list.
73
+ Checks each file's data type against the baseline raster and renames files with a mismatched type,
74
+ (allowing it not to be surimposed as it does not begin by "vuln_" anymore).
75
+
76
+ :param path_baseline: Path to the baseline raster file.
77
+ :type path_baseline: pathlib.Path
78
+ :param folder_path: Path to the folder containing raster files to check.
79
+ :type folder_path: pathlib.Path
80
+ :param name: Prefix name to filter .tiff files in the folder.
81
+ :type name: str
82
+ :return: A tuple containing two lists:
83
+ - files: List of paths with matching data type.
84
+ - unused_files: List of paths renamed due to type mismatch.
85
+ :rtype: tuple[list[pathlib.Path], list[pathlib.Path]]
86
+ """
87
+
88
+ files = []
89
+ unused_files = []
90
+
91
+ # Check type of base of assembly
92
+ ds_base = gdal.Open(path_baseline.as_posix())
93
+ type_base = gdal.GetDataTypeName(ds_base.GetRasterBand(1).DataType)
94
+ ds_base = None
95
+
96
+ files.append(path_baseline.as_posix())
97
+
98
+ #any files that begin by 'name'
99
+ tiff_trees = self.tree_name_tif(folder_path, name)
100
+ for tree in tiff_trees:
101
+ file_path = tree[-1]
102
+ ds = gdal.Open(file_path.as_posix())
103
+ file_type = gdal.GetDataTypeName(ds.GetRasterBand(1).DataType)
104
+ ds = None
105
+ #filter if not the same type and renaming
106
+ if filter_type == True :
107
+ if file_type != type_base:
108
+ suffix_to_add = f"_{file_type}"
109
+ new_name = file_path.parent / f"{file_path.name}{suffix_to_add}"
110
+ if not file_path.stem.endswith(suffix_to_add):
111
+ file_path.rename(new_name)
112
+ unused_files.append(new_name)
113
+ else:
114
+ unused_files.append(file_path)
115
+ else:
116
+ files.append(file_path.as_posix())
117
+ else:
118
+ files.append(file_path.as_posix())
119
+ return files, unused_files
120
+
121
+ def check_nodata(self, name, path_baseline, fn_scenario):
122
+ """ Check nodata in a path """
123
+ from ..wolf_array import WOLF_ARRAY_FULL_INTEGER8
124
+ list_tif, unused = self.select_name_tif(path_baseline, fn_scenario, name)
125
+ for cur_lst in list_tif:
126
+ curarray:WolfArray = WolfArray(cur_lst)
127
+ if curarray.wolftype != WOLF_ARRAY_FULL_INTEGER8:
128
+ if curarray.nullvalue != 99999.:
129
+ curarray.nullvalue = 99999.
130
+ curarray.set_nullvalue_in_mask()
131
+ curarray.write_all()
132
+ logging.warning(_('nodata changed in favor of 99999. value for file {} !'.format(cur_lst)))
133
+ else:
134
+ logging.info(_('nodata value is {} for file {} !'.format(curarray.nullvalue, cur_lst)))
135
+
136
+
137
+ def create_vrtIfExists(self, fn_baseline, fn_scenario, fn_vrt, name):
138
+ """ Create a vrt file from a path """
139
+ logging.info(_('Checking nodata values...'))
140
+ self.check_nodata(name, fn_baseline, fn_scenario)
141
+ list_tif, list_fail = self.select_name_tif(fn_baseline, fn_scenario, name, filter_type = True)
142
+ if len(list_fail)>0:
143
+ return False, list_fail
144
+ #création du fichier vrt - assembly/agglomération
145
+ if len(list_tif)>1:
146
+ logging.info(_('Creating .vrt from files (first based)...'))
147
+ if name == "vuln_":
148
+ create_vrt_from_diverged_files_first_based(list_tif, fn_vrt, Nodata = 127)
149
+ else:
150
+ create_vrt_from_diverged_files_first_based(list_tif, fn_vrt)
151
+ return True, list_fail
152
+ else:
153
+ return False, list_fail
154
+
155
+
156
+ def translate_vrt2tif(self, fn_VRT, fn_vuln_s):
157
+ """ Translate vrt from OUTPUT > ... > Scenario to tif saved in the same folder, and delete the vrt file """
158
+ if (fn_VRT).exists():
159
+ translate_vrt2tif(fn_VRT, fn_vuln_s)
160
+ os.remove(fn_VRT)
161
+
162
+ def copy_tif_files(self, files: list[Path], destination_dir: Path) -> None:
163
+ destination_dir.mkdir(parents=True, exist_ok=True)
164
+
165
+ for file in files:
166
+ destination_file = destination_dir / file.name
167
+ dataset = gdal.Open(str(file))
168
+ if dataset is None:
169
+ logging.warning(f"Could not open {file} with GDAL.")
170
+ continue
171
+ gdal_driver = gdal.GetDriverByName('GTiff')
172
+ gdal_driver.CreateCopy(str(destination_file), dataset, strict=0)
173
+
174
+ dataset = None
175
+
176
+ logging.info(_("All the existing .tif files have been copied to the destination directory."))
177
+
178
+ # Divers
179
+ def name_existence(self, Zones1, key, name):
180
+ names = [str(curzone.get_values(key)[0]) for curzone in Zones1.myzones]
181
+ if (str(name) in names):
182
+ print(f'There exists objects with name {name}.')
183
+ else :
184
+ print(f'No objects with name {name}')
185
+
186
+ def PICC_read(self, manager_inbe, name:str = "Habitation"):
187
+ study_area_path = Path(manager_inbe.IN_STUDY_AREA) / manager_inbe._study_area
188
+ bbox = bbox_creation(study_area_path)
189
+
190
+ _picc_data = Picc_data(data_dir=Path(),bbox=bbox)
191
+ path_vector = Path(manager_inbe.IN_DATABASE) / "PICC_Vesdre.shp"
192
+ if not path_vector.exists():
193
+ return None, None
194
+
195
+ zones_hab = _picc_data.create_zone_picc(path_vector = path_vector, path_points = Path(manager_inbe.IN_DATABASE) / "PICC_Vesdre_points.shp", name=name, column = "GEOREF_ID", bbox=bbox)
196
+ return zones_hab, zones_hab.nbzones, bbox
197
+
198
+ def create_table_wolfsimu_INTERPWD(self, manager_inbe, simu, operator_wd:str, ZonesX:Zones, Zones_v:Zones = None, percentil:float=None, title: str = "table_wolfsimu.xlsx"):
199
+ """Creates the minimum inputs needed for INBE based on the simulations. One table per simulations."""
200
+ where_wd = manager_inbe.IN_SA_INTERP
201
+ where_v = manager_inbe.IN_SCEN_DIR_V
202
+ out= manager_inbe.IN_CSV_SCEN
203
+ out = Path(out) / title.replace(".xlsx", f"_{simu}.xlsx")
204
+
205
+ colonnes = [
206
+ "code", "he_max", "he_out", "h_gf", "v", "date_arr_base",
207
+ "date_leave_base", "no_s", "no_q", "FA", "NF", "GL", "YY", "Y_renov", "EP"
208
+ ]
209
+ df = pd.DataFrame(columns=colonnes)
210
+
211
+ where_v = Path(where_v) / f"v_danger_{simu}_{manager_inbe.scenario}.tif"
212
+
213
+ simu_path = simu + ".tif"
214
+ print(f"where_v : {where_v}")
215
+ print(f"where_wd : {Path(where_wd) / simu_path}")
216
+ if not ((Path(where_wd) / simu_path).exists() and (where_v).exists()):
217
+ return print(f"Files of water depths and/or velocities does not exist.")
218
+
219
+ WA_wd = WolfArray(where_wd / simu_path)
220
+ WA_v = WolfArray(where_v)
221
+
222
+ #Exclure le mask dilaté (car pas plein bord...) de la rivière !
223
+ WA_river = WolfArray(manager_inbe.IN_RIVER_MASK_SCEN_tif)
224
+ i,j = np.where(WA_river.array.mask == False)
225
+ WA_wd.array[i,j] = WA_wd.nodata
226
+ WA_v.array[i,j] = WA_v.nodata
227
+
228
+ i = 0
229
+ val_h = 0
230
+ logging.info(_("Scanning zone for water depth inside buildings."))
231
+ for curzone in tqdm(ZonesX.myzones):
232
+ for curvector in curzone.myvectors:
233
+ if i >= ZonesX.nbzones:
234
+ break
235
+ wd_values = WA_wd.get_values_insidepoly(curvector)
236
+
237
+ if np.all(len(wd_values[0]) != 0):#np.all(len(wd_values[0]) != 0): #critère sur WD #statistics WA
238
+ if operator_wd == "mean":
239
+ val_h = wd_values[0].mean()
240
+ elif operator_wd == "max":
241
+ val_h = wd_values[0].max()
242
+ elif operator_wd == "median":
243
+ val_h = np.median(wd_values[0])
244
+ elif operator_wd == "percentil":
245
+ if percentil != None:
246
+ val_h = np.quantile(wd_values[0], percentil / 100)
247
+ else:
248
+ logging.error(_(f"Bad value for percentil {percentil}"))
249
+ return
250
+ else:
251
+ logging.error(_("To be coded."))
252
+
253
+ if val_h !=0:
254
+ df.loc[i, "code"] = str(curvector)
255
+ df.loc[i, "he_out"] = val_h
256
+ df.loc[i, "FA"] = curzone.area
257
+ curvector.update_lengths()
258
+ df.loc[i, "EP"] = curvector.length2D#perimeters[0]
259
+ i += 1
260
+
261
+ if Zones_v != None : #Si prise en compte de la vitesse, alors demande de parcourir un objet Zones avec buffer (autre loop necessaire)
262
+ logging.info(_("Scanning zone for velocity along the buffers."))
263
+ for curzone_v in tqdm(Zones_v.myzones):
264
+ for curvector_v in curzone_v.myvectors:
265
+ if i >= Zones_v.nbzones:
266
+ break
267
+ if str(curvector_v) in df['code'].astype(str).values:
268
+ v_values = WA_v.get_values_insidepoly(curvector_v, usemask=False)
269
+ v_values[0][v_values[0] == -99999] = 0
270
+ if len(v_values[0]) != 0:
271
+ if v_values[0].max() != 0 :
272
+ df.loc[df['code'] == str(curvector_v), "v"] = v_values[0].max()
273
+ else :
274
+ print("No v_values")
275
+ try:
276
+ df.to_excel(out, index=False)
277
+ except PermissionError:
278
+ logging.error(_(f"Permission denied: please ensure the file '{out.name}' is not open in another program."))
279
+ return
280
+
281
+ def pre_processing_auto(self, Ti_list, main_dir, Study_area, scenario, multiple, dx, percentil = None, operator_wd = "mean", hazard = "both"):
282
+ manager_inbe = INBE_Manager(main_dir=main_dir, Study_area=Study_area, scenario = scenario)
283
+
284
+ """Création bbox par Polygon pour la classe Zone et lecture du PICC"""
285
+ if hazard == "both":
286
+ """Prise en compte de la vitesse, via buffer"""
287
+ """Buffer """
288
+ Zones_reading, unused, unused = self.PICC_read(manager_inbe, "Habitation")
289
+
290
+ """Buffer creation"""
291
+ dx_buffer = multiple #multiple de la resolution topo
292
+ dx_buffer = multiple*dx
293
+ Zones_v = Zones_reading.buffer(distance = dx_buffer, resolution= 4, inplace = False)
294
+ #Zones_v.saveas(filename = r"C:\Users\damien\Desktop\test_zone_reading.shp")
295
+
296
+ elif hazard == "wd":
297
+ """Seulement water_depth, pas de vitesse"""
298
+ Zones_reading, unused, unused = self.PICC_read(manager_inbe, "Habitation")
299
+ Zones_v = None
300
+
301
+ save_where_output = []
302
+ for curT in Ti_list:
303
+ print(f"----------------------------> Computing input table for {curT}")
304
+ #create_table_wolfsimu_BUFFERWD(manager_inbe = manager_inbe, simu=curT, ZonesX= Zones_reading, Zones_v = Zones_reading2)
305
+ self.create_table_wolfsimu_INTERPWD(manager_inbe = manager_inbe, simu=curT, operator_wd = operator_wd, ZonesX= Zones_reading, Zones_v = Zones_v, percentil=percentil)
306
+ print(f"--Preprocessing table created in {manager_inbe.IN_CSV_SCEN}")
307
+ save_where_output.append(manager_inbe.IN_CSV_SCEN)
308
+ return save_where_output
309
+
310
+
311
+ def computation_dfresults(self, input_table,type_computation, inflation):
312
+ Pyinsyde = PyINBE(input_table=input_table)
313
+ df_results,unused,unused,unused,unused,unused,unused,unused,df_input = Pyinsyde.run_R_insyde(type_computation=type_computation, inflation=inflation)
314
+ df_ifvalue = df_results[df_results['d_total'] > 0]
315
+ return df_ifvalue, df_input
316
+
317
+ def computation_combined_damage(self, pond: dict, manager_inbe) -> pd.DataFrame:
318
+ """
319
+ ! Première version assez basique
320
+
321
+ Compute the weighted sum of damage results across multiple return‑period DataFrames.
322
+
323
+ :param df_results_Ti: Dictionary mapping return‑period keys (e.g. "T2", "T5", ...) to pandas DataFrames.
324
+ Each DataFrame must have a "code" column identifying each building and one or more numeric columns representing damage categories.
325
+ :type df_results_Ti: dict
326
+ :param pond: Dictionary mapping the same return‑period keys to their weighting coefficients (e.g. {"T2": 0.65, "T5": 0.216667, ...}).
327
+ :type pond: dict
328
+ :return: A DataFrame with one row per unique building "code" (coordinate of the center of the polygon), containing the column "code"
329
+ plus each damage category column equal to the weighted sum across all Ti.
330
+ :rtype: pd.DataFrame
331
+ """
332
+ df_results_Ti = manager_inbe.get_individualdamage()
333
+ weighted_dfs = []
334
+ for Ti, df in df_results_Ti.items():
335
+ coeff = pond.loc[int(Ti), "Ponderation"]
336
+ df_w = df.copy()
337
+ # Multiplying by the weigting coeff
338
+ numeric_cols = df_w.select_dtypes(include="number").columns
339
+ df_w[numeric_cols] = df_w[numeric_cols].mul(coeff)
340
+ weighted_dfs.append(df_w)
341
+
342
+ # Grouping and summing thanks to 'code' whioch
343
+ all_concat = pd.concat(weighted_dfs, ignore_index=True, sort=False)
344
+ final_df = all_concat.groupby("code", as_index=False).sum()
345
+ output_path = manager_inbe.OUT_COMB #Path(manager_inbe.OUT_SCEN_DIR) / f"combined_damage.xlsx"
346
+ try:
347
+ with open(output_path, 'wb') as f:
348
+ pass
349
+ final_df.to_excel(output_path, index=False)
350
+ except PermissionError:
351
+ logging.error(_(f"Permission denied: please ensure the file '{output_path}' is not open in another program."))
352
+ return
353
+
354
+ return final_df, output_path
355
+
356
+ def plot_damage(self, df_results, idx=None, cat=None, sorted_cond=None):
357
+ """
358
+ Displays a damage bar chart for a specific index (specific building) or for all entries, and for all
359
+ damage categories or only one.
360
+
361
+ :param df_results: DataFrame containing damage computed by INBE.
362
+ :type df_results: pd.DataFrame
363
+ :param idx: Index of a specific row to plot. Defaults to None (every building plotted).
364
+ :type idx: int, optional
365
+ :param cat: Specific damage category to plot if idx is None (global mode). Defaults to None (every category plotted).
366
+ :type cat: str, optional
367
+ """
368
+
369
+ with plt.rc_context({'text.usetex': True, 'font.size': 18, 'font.family': 'serif'}):
370
+ plt.rcParams.update({'text.usetex': False})
371
+ labels = ['d_cleaning', 'd_removal', 'd_non_stru', 'd_structural', 'd_finishing', 'd_systems']
372
+ labels_latex = [r'$D_{\mathrm{cleaning}}$',r'$D_{\mathrm{removal}}$',r'$D_{\mathrm{non stru}}$',r'$D_{\mathrm{structural}}$',r'$D_{\mathrm{finishing}}$',r'$D_{\mathrm{systems}}$']
373
+
374
+ if cat is not None:
375
+ labels = [cat]
376
+
377
+ if sorted_cond == True:
378
+ df_sorted = df_results.sort_values('d_total')
379
+ df_plot = df_sorted
380
+ else:
381
+ df_plot = df_results
382
+
383
+ fig, ax = plt.subplots(figsize=(8,6))
384
+
385
+ if idx is not None:
386
+ values = [df_plot[label][idx] / 1e3 for label in labels]
387
+
388
+ x = np.arange(len(labels))
389
+ colors = plt.cm.tab10.colors[:len(labels)]
390
+ bars = ax.bar(x, values, color=colors, alpha=0.6)
391
+ ax.set_xticks(x)
392
+ ax.set_xticklabels(labels, rotation=45)
393
+ ax.tick_params(axis='y', labelsize=13)
394
+ ax.set_xlabel("Categories of damage [-]")
395
+ ax.set_ylabel("d_total [10³€]", fontsize=13)
396
+ ax.grid(True, axis='y', linestyle='--', alpha=0.7)
397
+
398
+ else:
399
+ combined = list(zip(labels, labels_latex))
400
+ combined_sorted = sorted(combined, key=lambda x: df_plot[x[0]].sum(), reverse=True)
401
+ labels, labels_latex = zip(*combined_sorted)
402
+
403
+ colors = plt.cm.tab10.colors[:len(labels)]
404
+ x = np.arange(len(df_plot.index))
405
+ bottom = np.zeros(len(df_plot.index))
406
+ bars = []
407
+ for i, label in enumerate(labels):
408
+ values = df_plot[label] / 100
409
+ bar = ax.bar(x, values, bottom=bottom, color=colors[i], label=labels_latex[i], alpha=0.6)
410
+ bars.append(bar)
411
+ bottom += values
412
+
413
+ ax.set_xticks([]) # pas de ticks sur x
414
+ ax.set_ylabel("Damage [10³€]")
415
+ ax.legend(loc='lower center', bbox_to_anchor=(0.5, 1.02), ncol=3, borderaxespad=0.)
416
+ ax.grid(True, axis='y', linestyle='--', alpha=0.7)
417
+
418
+ # Tooltip setup - gadget... mais sympa
419
+ annot = ax.annotate("", xy=(0,0), xytext=(15,15), textcoords="offset points",
420
+ bbox=dict(boxstyle="round", fc="w"),
421
+ arrowprops=dict(arrowstyle="->"))
422
+ annot.set_visible(False)
423
+
424
+ def update_annot(bar, idx_bar):
425
+ x_mid_data = (ax.get_xlim()[0] + ax.get_xlim()[1]) / 2
426
+
427
+ y_pos = bar.get_height() + bar.get_y()
428
+
429
+ annot.xy = (bar.get_x() + bar.get_width() / 2, y_pos)
430
+ code = df_plot['code'].iloc[idx_bar]
431
+ annot.set_text(f"code: {code}")
432
+ annot.get_bbox_patch().set_alpha(0.8)
433
+ annot.set_fontsize(10)
434
+
435
+ arrow_disp = ax.transData.transform(annot.xy)
436
+ fig_width = fig.bbox.width
437
+ x_text_disp = fig_width / 2
438
+ y_text_disp = arrow_disp[1] + 15 # décalage vertical 15 pixels
439
+
440
+ annot.set_position((x_text_disp - arrow_disp[0], y_text_disp - arrow_disp[1]))
441
+
442
+ def hover(event):
443
+ vis = annot.get_visible()
444
+ for bar_group in bars:
445
+ for i, bar in enumerate(bar_group):
446
+ if bar.contains(event)[0]:
447
+ update_annot(bar, i)
448
+ annot.set_visible(True)
449
+ fig.canvas.draw_idle()
450
+ return
451
+ if vis:
452
+ annot.set_visible(False)
453
+ fig.canvas.draw_idle()
454
+
455
+ fig.canvas.mpl_connect("motion_notify_event", hover)
456
+
457
+ plt.tight_layout()
458
+ plt.show()
459
+
460
+
461
+ type_computation = "from_wolfsimu"
462
+ def computation_dfesults_forfolder(self, manager_inbe, type_computation, Ti_list, inflation): #changer nom "for folder" --> "for selected Ti"
463
+ """
464
+ Process Excel files in the folder INPUT>CSVs matching the pattern 'table_wolfsimu_T<number>.xlsx',
465
+ extracts and sorts the T identifiers numerically, computes results (INBE) for each file using
466
+ computation_dfresults, and returns a dictionary of results keyed by each T identifier.
467
+
468
+ :param manager_inbe: Object containing the path to the input CSV scenario folder.
469
+ :type manager_inbe: Any
470
+ :param type_computation: Parameter specifying the type of computation to perform.
471
+ :type type_computation: Any
472
+ :param inflation: Inflation parameter used in the computation.
473
+ :type inflation: Any
474
+ :return: Dictionary with keys as T identifiers (e.g., 'T2') and values as computation results.
475
+ :rtype: dict
476
+ """
477
+
478
+ df_results = {} #dictionnaire dont la clé sera les Ti dispo dans CSVs
479
+ df_input = {}
480
+ save_output_TEMP = []
481
+ save_output_defaultINPUT = []
482
+
483
+ for curT in Ti_list:
484
+ input_table = manager_inbe.IN_CSV_SCEN / ("table_wolfsimu_" + curT + ".xlsx")
485
+ if not input_table.exists():
486
+ logging.error(_(f"Input table table_wolfsimu_{curT}.xlsx does not exist."))
487
+ continue
488
+ logging.info(_(f"Computing damage for table_wolfsimu{curT}"))
489
+ df_results[curT], df_input[curT] = self.computation_dfresults(input_table, type_computation, inflation)
490
+ output_path = Path(manager_inbe.OUT_SCEN_DIR) / f"individual_damage_{curT}.xlsx"
491
+ try:
492
+ with open(output_path, 'wb') as f:
493
+ pass
494
+ df_results[curT].to_excel(output_path, index=False)
495
+ except PermissionError:
496
+ logging.error(_(f"Permission denied: please ensure the file '{output_path}' is not open in another program."))
497
+ return
498
+
499
+ output_path = Path(*output_path.parts[-5:])
500
+ save_output_TEMP.append(output_path)
501
+ output_path = Path(manager_inbe.OUT_SCEN_DIR_IN) / f"input_with_default_data_{curT}.xlsx"
502
+ try:
503
+ with open(output_path, 'wb') as f:
504
+ pass
505
+ df_input[curT].to_excel(output_path, index=False)
506
+ except PermissionError:
507
+ logging.error(_(f"Permission denied: please ensure the file '{output_path}' is not open in another program."))
508
+ return
509
+
510
+ output_path = Path(*output_path.parts[-5:])
511
+ save_output_defaultINPUT.append(output_path)
512
+ return df_results, save_output_TEMP, save_output_defaultINPUT
513
+
514
+ def adding_INBEresults_toZones(self, manager_inbex, Zones_trix, Ti = None):
515
+ cols = ["code", "d_cleaning", "d_removal", "d_non_stru", "d_structural", "d_finishing", "d_systems", "d_total"]
516
+ cols_d = ["d_cleaning", "d_removal", "d_non_stru", "d_structural", "d_finishing", "d_systems", "d_total"]
517
+ if Ti != None:
518
+ path_to_indiv_damage = manager_inbex.OUT_SCEN_DIR / ("individual_damage_" + str(Ti) + ".xlsx")
519
+ df_results = pd.read_excel(path_to_indiv_damage, usecols=cols)
520
+ if Ti == None :
521
+ Ti = None
522
+ df_results = pd.read_excel(manager_inbex.OUT_COMB, usecols=cols)
523
+
524
+ Zones_torasterize = Zones_trix
525
+
526
+ for curzone in tqdm(Zones_torasterize.myzones):
527
+ for curvector in curzone.myvectors:
528
+ row_match = df_results[df_results['code'].astype(str) == str(curvector)]#donne bool
529
+ if not row_match.empty:
530
+ row = row_match.iloc[0]
531
+ for key in cols_d:
532
+ read_value = []
533
+ read_value.append(str(row[key]))
534
+ arr = np.array(read_value, dtype=str)
535
+ curzone.add_values(key=str(key), values=arr)
536
+ #print(f"E.g : pour l'id {curzone.get_values('GEOREF_ID')} on a d = {curzone.get_values('d_total')}")
537
+ return Zones_torasterize
538
+
539
+ def label_0_needed(self, Zones1):
540
+ for curzone in tqdm(Zones1.myzones):
541
+ cols_d = ["d_cleaning", "d_removal", "d_non_stru", "d_structural", "d_finishing", "d_systems", "d_total"]
542
+ id_vals = [curzone.get_values(col) for col in cols_d]
543
+ id_clean = [v[0] if isinstance(v, (list, np.ndarray)) else v for v in id_vals]
544
+
545
+ if all(v is None for v in id_clean):
546
+ for col in cols_d:
547
+ curzone.add_values(key=col, values=np.array([0], dtype=int))
548
+
549
+ def raster_damage_in_zones(self, path_damage_out, path_onedanger, Zones_torasterize, scenario, Ti):
550
+ self.label_0_needed(Zones_torasterize)
551
+ WA_raster = WolfArray(path_onedanger)
552
+ WA_raster.unmask
553
+ WA_raster.array[WA_raster.array<=99999] = 99999
554
+
555
+ WA_raster.rasterize_zones_valuebyid(Zones_torasterize, id="d_total", method="linear")
556
+
557
+ if Ti != None:
558
+ out_title = Path(path_damage_out) / f"inbe_{scenario}_{Ti}.tif"
559
+ else :
560
+ out_title = Path(path_damage_out) / ("raster_" + str(scenario) + ".tif")
561
+ WA_raster.unmask
562
+ WA_raster.nodata=99999
563
+ WA_raster.write_all(out_title)
564
+ return print(f"WolfArray written at {out_title}")
565
+
566
+ def select_and_count_in_zones(self, Zones1, name):
567
+ comptage = 0
568
+ Zones2=Zones()
569
+ for curzone in Zones1.myzones:
570
+ if str(curzone.get_values("NATUR_DESC")[0]) == str(name):
571
+ comptage +=1
572
+ Zones2.add_zone(curzone, forceparent = True)
573
+ #print(f"--> Number of {name} : {comptage}")
574
+ return Zones2, comptage
575
+
576
+ def raster_auto(self, manager_inbe, which_result, Ti_list=None):
577
+ #1. Lecture du PICC
578
+ Zones_tri,unused,unused = self.PICC_read(manager_inbe, "Habitation")
579
+ if which_result == "individual_damage":
580
+ for Ti in Ti_list:
581
+ logging.info(_(f"Raster creation for {Ti}."))
582
+ #3. """Calcul damage : individual and combined""" --> déjà calculés et save dans excel
583
+ Zones_torasterize = self.adding_INBEresults_toZones(manager_inbe, Zones_tri, Ti = Ti)
584
+ tif_files = list(manager_inbe.IN_SA_INTERP.glob("T*.tif"))
585
+ if not tif_files:
586
+ logging.error(_(r"Please provide the water depth input."))
587
+ return
588
+ one_danger = tif_files[0]
589
+ self.raster_damage_in_zones(manager_inbe.OUT_SCEN_DIR, one_danger, Zones_torasterize, manager_inbe.scenario, Ti)
590
+
591
+ if which_result == "combined" :
592
+ Zones_tri,unused = self.select_and_count_in_zones(Zones_tri, "Habitation")
593
+
594
+ tif_files = list(manager_inbe.IN_SA_INTERP.glob("T*.tif"))
595
+ if not tif_files:
596
+ logging.error(_(r"Please provide the water depth input."))
597
+ return
598
+ one_danger = tif_files[0]
599
+ Zones_torasterize = self.adding_INBEresults_toZones(manager_inbe, Zones_tri, Ti = None)
600
+ self.raster_damage_in_zones(manager_inbe.OUT_SCEN_DIR, one_danger, Zones_torasterize, manager_inbe.scenario, None)
601
+
602
+ def load_quant_sum(self, path, quant):
603
+ d_total = pd.read_excel(path)['d_total']
604
+ p75 = d_total.quantile(quant)
605
+ return d_total[d_total <= p75].sum()
606
+
607
+ def extract_scenario_label(self, path_str):
608
+ p = Path(path_str)
609
+ parts = p.parts
610
+ label = None
611
+ for i, part in enumerate(parts):
612
+ if part == "TEMP" or part == "OUTPUT":
613
+ if i + 1 < len(parts):
614
+ label = parts[i + 2]
615
+ break
616
+ return label
617
+
618
+ def histo_total_from_list(self, list_path, quant):
619
+ damages = []
620
+ labels = []
621
+ colors = []
622
+
623
+ base_color = (212/255, 101/255, 10/255)
624
+ alt_colors = [
625
+ (119/255, 147/255, 60/255),
626
+ (127/255, 127/255, 127/255),
627
+ (91/255, 155/255, 213/255),
628
+ (255/255, 192/255, 0/255),
629
+ (255/255, 0/255, 255/255)
630
+ ]
631
+ for i, path in enumerate(list_path):
632
+ D_tot = self.load_quant_sum(path, quant) / 1000
633
+ damages.append(D_tot)
634
+ labels.append(self.extract_scenario_label(path))
635
+ colors.append(base_color if i == 0 else alt_colors[(i - 1) % len(alt_colors)])
636
+
637
+ base_damage = damages[0]
638
+ rel_percents = [0]
639
+ for dmg in damages[1:]:
640
+ rel = round(dmg / base_damage * 100 - 100)
641
+ rel_percents.append(rel)
642
+
643
+ with plt.rc_context({'text.usetex': False, 'font.size': 16, 'font.family': 'serif'}):
644
+ labels2 = [str(lab) for lab in labels]
645
+ bars = plt.bar(labels2, damages, color=colors)
646
+
647
+ ylim_max = max(damages) * 1.15
648
+
649
+ for i in range(1, len(damages)):
650
+ rel = rel_percents[i]
651
+ label = f"+{rel}%" if rel > 0 else f"{rel}%"
652
+ color_txt = 'red' if rel > 0 else 'green'
653
+ plt.text(i, damages[i] + ylim_max*0.02, label, ha='center', color=color_txt, fontsize=20)
654
+
655
+ plt.ylabel('Total damage [10³€]')
656
+ plt.xticks(rotation=45)
657
+ plt.grid(True)
658
+ plt.ylim(0, ylim_max)
659
+ plt.tight_layout()
660
+ plt.show()
661
+
662
+
663
+ def scatter_INBE_dtotal(out, manager1, manager2, max_val=None, Ti=None, quant=None):
664
+ #...2 : baseline (...1 is the scenario we compare to ...2)
665
+ if Ti != None:
666
+ path2 = Path(manager1.OUT_SCEN_DIR) / f"individual_damage_T{Ti}.xlsx"
667
+ path1 = Path(manager2.OUT_SCEN_DIR) / f"individual_damage_T{Ti}.xlsx"
668
+ #title = r"$\textrm{" + Ti[1:] + r"-year flood}$"
669
+ title = rf"{Ti}-year flood"
670
+ else :
671
+ path2 = Path(manager1.OUT_COMB)
672
+ path1 = Path(manager2.OUT_COMB)
673
+ title = r"Combined damage"
674
+
675
+ df1 = pd.read_excel(path1)
676
+ df2 = pd.read_excel(path2)
677
+
678
+ df1 = df1[['code', 'd_total']].copy()
679
+ df2 = df2[['code', 'd_total']].copy()
680
+
681
+ df1.rename(columns={'d_total': 'd_total_sc1'}, inplace=True)
682
+ df2.rename(columns={'d_total': 'd_total_sc2'}, inplace=True)
683
+
684
+ merged = pd.merge(df1, df2, on='code', how='outer')
685
+
686
+ if quant != None :
687
+ percentile_sc1 = merged['d_total_sc1'].quantile(quant)
688
+ percentile_sc2 = merged['d_total_sc2'].quantile(quant)
689
+
690
+ # Filtrer les données pour ne garder que les valeurs supérieures ou égales au 90ᵉ percentile
691
+ filtered = merged[(merged['d_total_sc1'] <= percentile_sc1) | (merged['d_total_sc2'] <= percentile_sc2)]
692
+ merged=filtered
693
+
694
+ #ENSEMBLES
695
+ common = merged[ merged['d_total_sc1'].notna() & merged['d_total_sc2'].notna() ]
696
+ only_sc1 = merged[ merged['d_total_sc1'].notna() & merged['d_total_sc2'].isna() ]
697
+ only_sc2 = merged[ merged['d_total_sc2'].notna() & merged['d_total_sc1'].isna() ]
698
+
699
+ only_sc1_filled = only_sc1.copy()
700
+ only_sc1_filled['d_total_sc2'] = 0
701
+
702
+ only_sc2_filled = only_sc2.copy()
703
+ only_sc2_filled['d_total_sc1'] = 0
704
+
705
+ #COLOUR
706
+ #colors_common = [get_color(x, y) for x, y in zip(common['d_total_sc2'], common['d_total_sc1'])]
707
+ #colors_sc1 = [get_color(x, y) for x, y in zip(only_sc1_filled['d_total_sc2'], only_sc1_filled['d_total_sc1'])]
708
+ #colors_sc2 = [get_color(x, y) for x, y in zip(only_sc2_filled['d_total_sc2'], only_sc2_filled['d_total_sc1'])]
709
+
710
+ with plt.rc_context({'text.usetex': False, 'font.size': 16, 'font.family': 'serif'}):
711
+ fig, ax = plt.subplots(figsize=(8, 8))
712
+ ax.scatter(common['d_total_sc2'], common['d_total_sc1'], marker = 'x', color='black')#colors_common, label='Both scenarios')
713
+ ax.scatter(only_sc1_filled['d_total_sc2'], only_sc1_filled['d_total_sc1'], marker = 'x', color='black')#colors_common s=45, facecolors='none', color=colors_sc1, marker='s', label=f'Only in {scenario1}')
714
+ ax.scatter(only_sc2_filled['d_total_sc2'], only_sc2_filled['d_total_sc1'], marker = 'x', color='black')#colors_common s=60, facecolors='none', edgecolors=colors_sc2, label=f'Only in {scenario2}')
715
+
716
+ label2 = manager1.scenario
717
+ label1 = manager2.scenario
718
+
719
+ #min_val = merged[['d_total_sc1', 'd_total_sc2']].min().min()
720
+ if max_val ==None :
721
+ max_val = merged[['d_total_sc1', 'd_total_sc2']].max().max()
722
+ #common_marker = mlines.Line2D([], [], marker='x', color='black', linestyle='None', markersize=10, label='Both scenarios')
723
+ #sc1_marker = mlines.Line2D([], [], marker='s', color='black', linestyle='None', markersize=10, markerfacecolor='none', label=f'Only in {scenario1}')
724
+ #sc2_marker = mlines.Line2D([], [], marker='o', linestyle='None', markersize=10, markerfacecolor='none', markeredgecolor='black', label=f'Only in {scenario2}')
725
+ #ax.legend(handles=[common_marker, sc1_marker, sc2_marker])
726
+
727
+ #Formatting for latex
728
+ #label2_escaped = label2.replace(' ', r'\ ')
729
+ #label1_escaped = label1.replace(' ', r'\ ')
730
+ #xlabel = r"$\textrm{Total damage [€] for }\mathrm{" + label2_escaped + "}$"
731
+ #ylabel = r"$\textrm{Total damage [€] for }\mathrm{" + label1_escaped + "}$"
732
+ #ylabel = r"$\textrm{Scores [-] in }\mathrm{" + label1_escaped + "}$"
733
+
734
+ xlabel = rf"Total damage [€] for {label2}"
735
+ ylabel = rf"Total damage [€] for {label1}"
736
+
737
+
738
+ plt.plot([0, max_val], [0, max_val], '--', color='grey')
739
+ plt.xlabel(xlabel)
740
+ plt.ylabel(ylabel)
741
+ plt.title(title, loc='left')
742
+ plt.grid(True)
743
+ plt.tight_layout()
744
+ #out = Path(out) / f"scatterD_{Ti}_{label1}VS{label2}.PNG"
745
+ #plt.savefig(out, dpi=900)
746
+ plt.show()