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
@@ -10,8 +10,9 @@ copying or distribution of this file, via any medium, is strictly prohibited.
10
10
 
11
11
  from . import constant as cst
12
12
  from enum import Enum
13
- # Constants representing the exchanges - Fortran
13
+ # Constants representing the exchanges - Fortran (cfr. Fortran cst_exchange.f90)
14
14
 
15
+ # Types of exchanges between blocks/models
15
16
  exchange_parameters_VHM_Umax = 20 #Paramètre modèle VHM
16
17
  exchange_parameters_VHM_Uevap = 21 #Paramètre modèle VHM
17
18
  exchange_parameters_VHM_au1 = 22 #Paramètre modèle VHM
@@ -59,6 +60,8 @@ exchange_parameters_SAC_riva = 77 #Paramètre modèle SAC-SMA (SACRAM
59
60
  exchange_parameters_SAC_adimp = 78 #Paramètre modèle SAC-SMA (SACRAMENTO)
60
61
  exchange_parameters_SAC_impv = 79 #Paramètre modèle SAC-SMA (SACRAMENTO)
61
62
 
63
+ exchange_parameters_SAC_kof = 120 #Paramètre modèle SAC-SMA (SACRAMENTO) with LR OF
64
+
62
65
  exchange_parameters_NAM_UMAX = 82 #Paramètre modèle NAM
63
66
  exchange_parameters_NAM_TOF = 83 #Paramètre modèle NAM
64
67
  exchange_parameters_NAM_TIF = 84 #Paramètre modèle NAM
@@ -94,6 +97,65 @@ exchange_parameters_Dist_Horton_K = 116 #Paramètre modèle distribué Hor
94
97
  exchange_parameters_Dist_kif = 117 #Paramètre modèle distribué pour réservoir linéaire couche épidermique (if)
95
98
  exchange_parameters_Dist_qlif = 118 #Paramètre modèle distribué pour réservoir linéaire couche épidermique (if)
96
99
 
100
+ # Internal variables ids
101
+ iv_VHM_qof = 201
102
+ iv_VHM_qif = 202
103
+ iv_VHM_qbf = 203
104
+ iv_VHM_U = 204
105
+ iv_VHM_xu = 205
106
+ iv_VHM_xof = 206
107
+ iv_VHM_xif = 207
108
+ iv_VHM_xbf = 208
109
+
110
+ iv_2layers_linBF_qof = 701
111
+ iv_2layers_linBF_qif = 702
112
+ iv_2layers_linBF_U = 703
113
+ iv_2layers_linBF_S = 704
114
+ iv_2layers_linBF_xif = 705
115
+ iv_2layers_linBF_xp = 706
116
+
117
+ iv_HBV_qr = 901
118
+ iv_HBV_qif = 902
119
+ iv_HBV_qbf = 903
120
+ iv_HBV_qrech = 904
121
+ iv_HBV_soil_qcap = 905
122
+ iv_HBV_qperc = 906
123
+ iv_HBV_UZ_qcap = 907
124
+ iv_HBV_U = 908
125
+ iv_HBV_Su = 909
126
+ iv_HBV_etr = 910
127
+
128
+ iv_SACSMA_qof = 1001
129
+ iv_SACSMA_qif = 1002
130
+ iv_SACSMA_qbf = 1003
131
+ iv_SACSMA_qsubbf = 1004
132
+ iv_SACSMA_qsurf = 1005
133
+ iv_SACSMA_qbase = 1006
134
+ iv_SACSMA_etot = 1007
135
+ iv_SACSMA_e1 = 1008
136
+ iv_SACSMA_e2 = 1009
137
+ iv_SACSMA_e5 = 1010
138
+ iv_SACSMA_qqif = 1011
139
+ iv_SACSMA_qqsr = 1012
140
+ iv_SACSMA_qqdr = 1013
141
+ iv_SACSMA_CUZTW = 1014
142
+ iv_SACSMA_CUZFW = 1015
143
+ iv_SACSMA_CADIMP = 1016
144
+ iv_SACSMA_CLZTW = 1017
145
+ iv_SACSMA_CLZFP = 1018
146
+ iv_SACSMA_CLZFS = 1019
147
+ iv_SACSMA_e3 = 1020
148
+ iv_SACSMA_qoutLR = 1021
149
+
150
+ iv_NAM_qof = 1101
151
+ iv_NAM_qif = 1102
152
+ iv_NAM_qbf = 1103
153
+ iv_NAM_ea = 1104
154
+ iv_NAM_erz = 1105
155
+ iv_NAM_qg = 1106
156
+ iv_NAM_U = 1107
157
+ iv_NAM_L = 1108
158
+
97
159
 
98
160
  # Constants representing the exchanges - Python
99
161
  exchange_parameters_py_timeDelay = -11
@@ -497,6 +559,17 @@ SAC_SMA["Parameters"][exchange_parameters_SAC_impv]["Unit"] = "[-]"
497
559
  SAC_SMA["Parameters"][exchange_parameters_SAC_impv]["Range"] = (0.0, 0.05)
498
560
 
499
561
 
562
+ SAC_SMA_LROF = SAC_SMA.copy()
563
+ SAC_SMA_LROF["Nb"] = 17
564
+ SAC_SMA_LROF["Parameters"][exchange_parameters_SAC_kof] = {}
565
+ SAC_SMA_LROF["Parameters"][exchange_parameters_SAC_kof]["Name"] = "Kof"
566
+ SAC_SMA_LROF["Parameters"][exchange_parameters_SAC_kof]["File"] = "simul_of.param"
567
+ SAC_SMA_LROF["Parameters"][exchange_parameters_SAC_kof]["Group"] = "Time Parameters"
568
+ SAC_SMA_LROF["Parameters"][exchange_parameters_SAC_kof]["Key"] = "Lagtime"
569
+ SAC_SMA_LROF["Parameters"][exchange_parameters_SAC_kof]["Unit"] = "[sec]"
570
+ SAC_SMA_LROF["Parameters"][exchange_parameters_SAC_kof]["Convertion Factor"] = 1/3600.0 # [sec] -> [h]
571
+
572
+
500
573
  NAM = {}
501
574
  NAM["Nb"] = 10
502
575
  NAM["Parameters"] = {}
@@ -583,6 +656,7 @@ modelParamsDict[cst.tom_2layers_linIF]= UHDIST_LINBF
583
656
  modelParamsDict[cst.tom_HBV]= HBV
584
657
  modelParamsDict[cst.tom_SAC_SMA]= SAC_SMA
585
658
  modelParamsDict[cst.tom_NAM]= NAM
659
+ modelParamsDict[cst.tom_SAC_SMA_LROF]= SAC_SMA_LROF
586
660
 
587
661
  # %% Python-Fortran exchange constants
588
662
 
@@ -590,6 +664,7 @@ ptr_params = 1
590
664
  ptr_opti_factors = 2
591
665
  ptr_q_all = 3
592
666
  ptr_time_delays = 4
667
+ ptr_iv_saved = 5
593
668
 
594
669
  fptr_update = 1
595
670
  fptr_get_cvg = 2
@@ -10,59 +10,423 @@ copying or distribution of this file, via any medium, is strictly prohibited.
10
10
  """
11
11
 
12
12
  from os import path
13
+ from pathlib import Path
13
14
 
14
15
  #from ..color_constants import *
15
16
  from ..PyVertexvectors import *
16
17
  from ..PyVertex import cloud_vertices,wolfvertex
17
- from ..PyTranslate import _
18
-
18
+ from ..PyTranslate import _
19
+
19
20
  class forced_exchanges:
20
-
21
- def __init__(self,workingdir='') -> None:
22
-
23
- self.type='COORDINATES'
24
- self.mypairs=[]
25
- self.mycloudup=cloud_vertices()
26
- self.myclouddown=cloud_vertices()
27
-
28
- self.mysegs = Zones()
29
- self.myzone=zone(name='segments_fe')
30
- self.mysegs.add_zone(self.myzone)
31
-
32
- self.mycloudup.myprop.color=getIfromRGB((0,238,0))
33
- self.mycloudup.myprop.filled=True
34
- self.myclouddown.myprop.color=getIfromRGB((255,52,179))
35
- self.myclouddown.myprop.filled=True
36
-
37
- fname=path.join(workingdir,'Coupled_pairs.txt')
38
- if path.exists(fname):
39
- f=open(fname,'rt')
21
+ """ Forced exchanges For Hydrological model.
22
+
23
+ A forced exchange is a pair of vertices that are coupled together.
24
+ The first vertex is the upper one, the second is the lower one.
25
+ """
26
+
27
+ def __init__(self, workingdir='', fname='', mapviewer=None) -> None:
28
+
29
+ self.mapviewer = mapviewer
30
+ self._workingdir = Path(workingdir)
31
+
32
+ self._color_up = (0, 238, 0) # Green
33
+ self._color_down = (255, 52, 179) # Pink
34
+
35
+ self.type='COORDINATES'
36
+ self._mycloudup = cloud_vertices(mapviewer=self.mapviewer)
37
+ self._myclouddown= cloud_vertices(mapviewer=self.mapviewer)
38
+ self._mysegs = Zones(mapviewer=self.mapviewer)
39
+
40
+ tmp_zone = zone(name='temporary')
41
+ self._mysegs.add_zone(tmp_zone, forceparent=True)
42
+ tmpvec = vector(name='temporary')
43
+ tmpvec.myprop.color = getIfromRGB((0, 0, 128))
44
+ tmpvec.myprop.width = 2
45
+ tmp_zone.add_vector(tmpvec, forceparent=True)
46
+
47
+ self._myzone = zone(name='segments_fe')
48
+ self._mysegs.add_zone(self._myzone, forceparent=True)
49
+
50
+ self._mycloudup.myprop.color = getIfromRGB((0,238,0))
51
+ self._mycloudup.myprop.filled = True
52
+ self._myclouddown.myprop.color = getIfromRGB((255,52,179))
53
+ self._myclouddown.myprop.filled= True
54
+
55
+ if fname:
56
+ if isinstance(fname, str):
57
+ if fname == 'N-O':
58
+ fname = self._workingdir / 'Coupled_pairs.txt'
59
+ else:
60
+ fname = self._workingdir / fname
61
+ if not fname.exists():
62
+ logging.error(f"The file {fname} does not exist.")
63
+
64
+ self._filename = fname if fname else self._workingdir / 'Coupled_pairs.txt'
65
+
66
+ try:
67
+ self._read_file()
68
+ except:
69
+ logging.error(f"Could not read the file {self._filename}. It may not be in the correct format or may be corrupted.")
70
+
71
+ self._myzone.find_minmax(True)
72
+
73
+ def is_empty(self):
74
+ """ Check if the forced exchanges are empty. """
75
+ return self._mysegs['segments_fe'].nbvectors == 0
76
+
77
+ @property
78
+ def pairs(self):
79
+ """ Get the list of pairs of vertices. """
80
+ seg_zone = self._mysegs['segments_fe']
81
+ if not seg_zone:
82
+ raise ValueError("The segments zone is not initialized or does not exist.")
83
+
84
+ return [[vec[0].x, vec[0].y, vec[-1].x, vec[-1].y] for vec in seg_zone.myvectors]
85
+
86
+ @property
87
+ def temporary_vector(self):
88
+ """ Get the temporary vector used for forced exchanges. """
89
+ return self._mysegs[('temporary', 'temporary')]
90
+
91
+ @property
92
+ def color_up_integer(self):
93
+ """ Get the color of the upper vertices as an integer. """
94
+ return getIfromRGB(self._color_up)
95
+
96
+ @property
97
+ def color_down_integer(self):
98
+ """ Get the color of the lower vertices as an integer. """
99
+ return getIfromRGB(self._color_down)
100
+
101
+ @property
102
+ def color_up_rgb(self):
103
+ """ Get the color of the upper vertices as an RGB tuple. """
104
+ return self._color_up
105
+
106
+ @property
107
+ def color_down_rgb(self):
108
+ """ Get the color of the lower vertices as an RGB tuple. """
109
+ return self._color_down
110
+
111
+ @color_up_rgb.setter
112
+ def color_up_rgb(self, value):
113
+ """ Set the color of the upper vertices from an RGB tuple. """
114
+ if isinstance(value, tuple) and len(value) == 3:
115
+ self._color_up = value
116
+ self._mycloudup.myprop.color = getIfromRGB(value)
117
+ else:
118
+ raise ValueError("color_up_rgb must be a tuple of three integers (R, G, B).")
119
+
120
+ @color_down_rgb.setter
121
+ def color_down_rgb(self, value):
122
+ """ Set the color of the lower vertices from an RGB tuple. """
123
+ if isinstance(value, tuple) and len(value) == 3:
124
+ self._color_down = value
125
+ self._myclouddown.myprop.color = getIfromRGB(value)
126
+ else:
127
+ raise ValueError("color_down_rgb must be a tuple of three integers (R, G, B).")
128
+
129
+ @color_up_integer.setter
130
+ def color_up_integer(self, value):
131
+ """ Set the color of the upper vertices from an integer. """
132
+ if isinstance(value, int):
133
+ self._color_up = getRGBfromI(value)
134
+ self._mycloudup.myprop.color = value
135
+ else:
136
+ raise ValueError("color_up_integer must be an integer representing a color.")
137
+
138
+ @color_down_integer.setter
139
+ def color_down_integer(self, value):
140
+ """ Set the color of the lower vertices from an integer. """
141
+ if isinstance(value, int):
142
+ self._color_down = getRGBfromI(value)
143
+ self._myclouddown.myprop.color = value
144
+ else:
145
+ raise ValueError("color_down_integer must be an integer representing a color.")
146
+
147
+ def _read_file(self):
148
+ """ Read the forced exchanges from a file. """
149
+
150
+ if not self._filename.exists():
151
+ logging.error(f"The file {self._filename} does not exist.")
152
+ return
153
+
154
+ with open(self._filename, 'rt') as f:
40
155
  content = f.read().splitlines()
41
- f.close()
42
-
43
- self.type = content[0]
44
- idx=1
45
- for curline in content[1:]:
46
- coords=curline.split('\t')
47
- coords = [float(x) for x in coords]
48
- self.mypairs.append(coords)
49
-
50
- vert1 = wolfvertex(coords[0],coords[1])
51
- vert2 = wolfvertex(coords[2],coords[3])
52
-
53
- myseg = vector(name='fe'+str(idx))
54
- myseg.myprop.width = 2
55
- myseg.myprop.color = getIfromRGB((0,0,128))
56
- myseg.add_vertex([vert1,vert2])
57
- self.myzone.add_vector(myseg)
58
-
59
- self.mycloudup.add_vertex(vert1)
60
- self.myclouddown.add_vertex(vert2)
61
- idx+=1
62
-
63
- self.myzone.find_minmax(True)
64
-
156
+
157
+ self.type = content[0]
158
+ idx=1
159
+ for curline in content[1:]:
160
+ coords=curline.split('\t')
161
+ coords = [float(x) for x in coords]
162
+
163
+ vert1 = wolfvertex(coords[0],coords[1])
164
+ vert2 = wolfvertex(coords[2],coords[3])
165
+
166
+ myseg = vector(name='fe'+str(idx))
167
+ myseg.myprop.width = 2
168
+ myseg.myprop.color = getIfromRGB((0,0,128))
169
+ myseg.add_vertex([vert1,vert2])
170
+ self._myzone.add_vector(myseg, forceparent=True)
171
+
172
+ self._mycloudup.add_vertex(vert1)
173
+ self._myclouddown.add_vertex(vert2)
174
+ idx+=1
175
+
176
+ def _save_file(self):
177
+ """ Save the forced exchanges to a file. """
178
+
179
+ with open(self._filename, 'wt') as f:
180
+ f.write(f"{self.type}\n")
181
+ for pair in self.pairs:
182
+ f.write(f"{pair[0]}\t{pair[1]}\t{pair[2]}\t{pair[3]}\n")
183
+
184
+ logging.info(f"Forced exchanges saved to {self._filename}.")
185
+
186
+ def save(self):
187
+ """ Save the forced exchanges to the file. """
188
+ if not self._filename:
189
+ raise ValueError("Filename is not set. Cannot save forced exchanges.")
190
+
191
+ self._save_file()
192
+
193
+ def add_pair_dict(self, pair:dict):
194
+ """ Add a pair of vertices to the forced exchanges from a dictionary.
195
+
196
+ :param pair: A dictionary with 'up' and 'down' keys containing the vertices.
197
+ :type pair: dict
198
+ """
199
+
200
+ if not isinstance(pair, dict):
201
+ raise TypeError("pair must be a dictionary with 'up' and 'down' keys.")
202
+
203
+ if 'up' not in pair or 'down' not in pair:
204
+ raise KeyError("The dictionary must contain 'up' and 'down' keys.")
205
+
206
+ self.add_pair(pair['up'], pair['down'])
207
+
208
+ def add_pair_XY(self, x1, y1, x2, y2, reset_ogl:bool = False):
209
+ """ Add a pair of coordinates to the forced exchanges. """
210
+
211
+ if not isinstance(x1, (int, float)) or not isinstance(y1, (int, float)):
212
+ raise TypeError("x1 and y1 must be numeric values.")
213
+ if not isinstance(x2, (int, float)) or not isinstance(y2, (int, float)):
214
+ raise TypeError("x2 and y2 must be numeric values.")
215
+
216
+ vertex_up = wolfvertex(x1, y1)
217
+ vertex_down = wolfvertex(x2, y2)
218
+
219
+ vec = vector(name= self._find_first_available_name())
220
+ vec.add_vertex([vertex_up, vertex_down])
221
+ self._myzone.add_vector(vec, forceparent=True)
222
+
223
+ self._mycloudup.add_vertex(vertex_up)
224
+ self._myclouddown.add_vertex(vertex_down)
225
+
226
+ if reset_ogl:
227
+ self.reset_listogl()
228
+
229
+ def add_pairs_XY(self, ups: list[list[float, float]], downs: list[float, float]):
230
+ """ Add multiple upstreams to one downstream as forced exchanges.
231
+
232
+ :param ups: A list of lists containing the coordinates of the upstream vertices.
233
+ :type ups: list[list[float, float]]
234
+ :param downs: A pair containing the coordinates of the downstream vertex.
235
+ """
236
+ if not isinstance(ups, list) or not all(isinstance(coord, list) and len(coord) == 2 for coord in ups):
237
+ raise TypeError("ups must be a list of lists with two numeric values each.")
238
+ if not isinstance(downs, list) or len(downs) != 2:
239
+ raise TypeError("downs must be a list with two numeric values.")
240
+
241
+ x_down, y_down = downs
242
+ for up in ups:
243
+ x_up, y_up = up
244
+ self.add_pair_XY(x_up, y_up, x_down, y_down)
245
+
246
+ self.reset_listogl()
247
+
248
+ def add_pair(self, vertex_up, vertex_down):
249
+ """ Add a pair of vertices to the forced exchanges. """
250
+
251
+ if not isinstance(vertex_up, wolfvertex) or not isinstance(vertex_down, wolfvertex):
252
+ raise TypeError("Both vertices must be of type wolfvertex.")
253
+
254
+ vec = vector(name= self._find_first_available_name())
255
+ vec.add_vertex([vertex_up, vertex_down])
256
+ self._myzone.add_vector(vec, forceparent=True)
257
+
258
+ self._mycloudup.add_vertex(vertex_up)
259
+ self._myclouddown.add_vertex(vertex_down)
260
+
261
+ self.reset_listogl()
262
+
263
+ def reset_listogl(self):
264
+ """ Reset the OpenGL lists for the forced exchanges. """
265
+
266
+ if not self._myclouddown or not self._mycloudup:
267
+ raise ValueError("Clouds for down and up vertices are not initialized.")
268
+
269
+ self._myclouddown.reset_listogl()
270
+ self._mycloudup.reset_listogl()
271
+
272
+ if not self._mysegs:
273
+ raise ValueError("Segments zone is not initialized.")
274
+
275
+ self._mysegs.reset_listogl()
276
+
277
+ def _find_nearest_pair(self, x, y):
278
+ """ Find the nearest pair of vertices to the given coordinates. """
279
+
280
+ if not isinstance(x, (int, float)) or not isinstance(y, (int, float)):
281
+ raise TypeError("x and y must be numeric values.")
282
+
283
+ if not self.pairs:
284
+ return None
285
+
286
+ min_dist = float('inf')
287
+ nearest_pair = None
288
+
289
+ for pair in self.pairs:
290
+ dist = ((pair[0] - x) ** 2 + (pair[1] - y) ** 2) ** 0.5
291
+ if dist < min_dist:
292
+ min_dist = dist
293
+ nearest_pair = pair
294
+
295
+ return nearest_pair
296
+
297
+ def get_nearest_pair(self, x:float, y:float) -> dict:
298
+ """ Get the nearest pair of vertices to the given coordinates.
299
+
300
+ :return: A dictionary with 'up' and 'down' keys containing the nearest vertices.
301
+ :rtype: dict
302
+ """
303
+
304
+ nearest_pair = self._find_nearest_pair(x, y)
305
+
306
+ if nearest_pair is None:
307
+ return None
308
+
309
+ return {
310
+ 'up': wolfvertex(nearest_pair[0], nearest_pair[1]),
311
+ 'down': wolfvertex(nearest_pair[2], nearest_pair[3])
312
+ }
313
+
314
+ def get_nearest_pair_as_vector(self, x:float, y:float) -> vector:
315
+ """ Get the nearest pair of vertices as a vector. """
316
+
317
+ nearest_index = self._find_nearest_pair_index(x, y)
318
+ vec = self._myzone[f'fe{nearest_index}'] if nearest_index != -1 else None
319
+
320
+ return vec
321
+
322
+ def _find_nearest_pair_index(self, x, y):
323
+ """ Find the index of the nearest pair of vertices to the given coordinates. """
324
+
325
+ if not isinstance(x, (int, float)) or not isinstance(y, (int, float)):
326
+ raise TypeError("x and y must be numeric values.")
327
+
328
+ if not self.pairs:
329
+ return -1
330
+
331
+ min_dist = float('inf')
332
+ nearest_index = -1
333
+
334
+ for idx, pair in enumerate(self.pairs):
335
+ dist = ((pair[0] - x) ** 2 + (pair[1] - y) ** 2) ** 0.5
336
+ if dist < min_dist:
337
+ min_dist = dist
338
+ nearest_index = idx
339
+
340
+ return nearest_index
341
+
342
+ def remove_nearest_pair(self, x, y):
343
+ """ Remove the nearest pair of vertices to the given coordinates. """
344
+
345
+ nearest_index = self._find_nearest_pair_index(x, y)
346
+
347
+ if nearest_index != -1:
348
+ self._myclouddown.remove_vertex(nearest_index)
349
+ self._mycloudup.remove_vertex(nearest_index)
350
+ self._myzone.myvectors.pop(nearest_index)
351
+
352
+ self.reset_listogl()
353
+
354
+ def remove_nearest_pairs(self, xy:list[list[float, float]]):
355
+ """ Remove the nearest pairs of vertices to the given coordinates. """
356
+
357
+ if not isinstance(xy, list) or not all(isinstance(coord, list) and len(coord) == 2 for coord in xy):
358
+ raise TypeError("xy must be a list of lists with two numeric values each.")
359
+
360
+ idx_to_remove = list(set([self._find_nearest_pair_index(coords[0], coords[1]) for coords in xy]))
361
+
362
+ for i in reversed(idx_to_remove):
363
+ if i != -1:
364
+ self._myclouddown.remove_vertex(i)
365
+ self._mycloudup.remove_vertex(i)
366
+ self._myzone.myvectors.pop(i)
367
+
368
+ self.reset_listogl()
369
+
370
+ def remove_pairs_inside_vector(self, vec:vector):
371
+ """ Remove pairs of vertices that are inside the given vector.
372
+
373
+ :param vec: The vector to check against.
374
+ :type vec: vector
375
+ """
376
+
377
+ if not isinstance(vec, vector):
378
+ raise TypeError("vec must be an instance of vector.")
379
+
380
+ idx_to_remove = []
381
+ for idx, pair in enumerate(self.pairs):
382
+ if vec.isinside(pair[0], pair[1]) or vec.isinside(pair[2], pair[3]):
383
+ idx_to_remove.append(idx)
384
+
385
+ idx_to_remove = list(set(idx_to_remove))
386
+
387
+ for idx in reversed(idx_to_remove):
388
+ self._myclouddown.remove_vertex(idx)
389
+ self._mycloudup.remove_vertex(idx)
390
+ self._myzone.myvectors.pop(idx)
391
+
392
+ self.reset_listogl()
393
+
394
+ def _find_first_available_name(self):
395
+ """ Find the first available name for a new forced exchange. """
396
+
397
+ idx = 1
398
+ names = [v.myname for v in self._myzone.myvectors]
399
+ while True:
400
+ name = f'fe{idx}'
401
+ if name not in self._myzone.myvectors:
402
+ return name
403
+ idx += 1
404
+
65
405
  def paint(self):
66
- self.mycloudup.plot()
67
- self.myclouddown.plot()
68
- self.mysegs.plot()
406
+ self._mycloudup.plot()
407
+ self._myclouddown.plot()
408
+ self._mysegs.plot()
409
+
410
+ def reset(self):
411
+ """ Reset the forced exchanges. """
412
+ self.reset_listogl()
413
+
414
+ self._mycloudup = cloud_vertices(mapviewer=self.mapviewer)
415
+ self._myclouddown = cloud_vertices(mapviewer=self.mapviewer)
416
+ self._mysegs = Zones(mapviewer=self.mapviewer)
417
+
418
+ tmp_zone = zone(name='temporary')
419
+ self._mysegs.add_zone(tmp_zone, forceparent=True)
420
+ tmpvec = vector(name='temporary')
421
+ tmpvec.myprop.color = getIfromRGB((0, 0, 128))
422
+ tmpvec.myprop.width = 2
423
+ tmp_zone.add_vector(tmpvec, forceparent=True)
424
+
425
+ self._myzone = zone(name='segments_fe')
426
+ self._mysegs.add_zone(self._myzone, forceparent=True)
427
+
428
+ self._mycloudup.myprop.color = getIfromRGB((0, 238, 0))
429
+ self._mycloudup.myprop.filled = True
430
+ self._myclouddown.myprop.color = getIfromRGB((255, 52, 179))
431
+ self._myclouddown.myprop.filled = True
432
+
@@ -13,8 +13,7 @@ import os
13
13
  import logging
14
14
  from datetime import datetime as date
15
15
  from datetime import timezone
16
- from struct import unpack, calcsize, unpack_from
17
- from pathlib import Path
16
+ from struct import unpack, calcsize, unpack_from, pack
18
17
 
19
18
 
20
19
  # Constants
@@ -145,12 +144,16 @@ def read_bin(path, fileName, format="", nbBytes=[], uniform_format=-1, hydro=Fal
145
144
  all_bytes = file.read()
146
145
  nbL = int.from_bytes(all_bytes[:4], byteorder='little', signed=True)
147
146
  nbC = int.from_bytes(all_bytes[4:8], byteorder='little', signed=True)
148
- Data = unpack(format*nbL, all_bytes[8:])
147
+ z = all_bytes[8:8+nbL*calcsize(format)]
148
+ flat_data = unpack(format[0] + format[1:]*nbL, z)
149
+ data = [list(flat_data[i * (nbC+1) : (i + 1) * (nbC+1)]) for i in range(nbL)]
150
+ # data = np.array(flat_data, dtype=np.float64).reshape(nbL, nbC+1)
149
151
  else:
150
- Data = read_bin_old(path, fileName, nbBytes=nbBytes, uniform_format=uniform_format, hydro=hydro)
152
+ data = read_bin_old(path, fileName, nbBytes=nbBytes, uniform_format=uniform_format, hydro=hydro)
151
153
 
152
154
 
153
- return Data
155
+ return data
156
+
154
157
 
155
158
 
156
159
  def read_binary_file(path, fileName, format="", buffer_size=-1, init_offset=8):
@@ -196,6 +199,8 @@ def read_binary_file(path, fileName, format="", buffer_size=-1, init_offset=8):
196
199
  file.seek(offset - len(buffer), 1)
197
200
  # break
198
201
 
202
+ #print(f"Number of values read: {len(values_list)} / {nbL}")
203
+
199
204
  return values_list
200
205
 
201
206
 
@@ -321,3 +326,58 @@ def check_path(fileName:str, prefix:str="", applyCWD:bool=True) -> tuple[bool, s
321
326
  return info, fileName
322
327
 
323
328
  return info, os.path.normpath(finalName)
329
+
330
+
331
+ def write_binary_file(path:str, fileName:str, data:list, format:str=""):
332
+ if not data:
333
+ raise ValueError("Data cannot be empty")
334
+
335
+ if format == "":
336
+ # Default format
337
+ format = "<bbhbbbd"
338
+ elif "<" not in format:
339
+ logging.warning("Format should start with '<' if you are on Windows.")
340
+
341
+ nbL = len(data)
342
+ nbC = len(format.replace("<", "")) - 1
343
+
344
+ with open(os.path.join(path, fileName), 'wb') as file:
345
+ # Write header: number of rows and columns as 4-byte little-endian signed integers
346
+ file.write(nbL.to_bytes(4, byteorder='little', signed=True))
347
+ file.write(nbC.to_bytes(4, byteorder='little', signed=True))
348
+
349
+ # Write the data rows
350
+ for row in data:
351
+ if len(row) != nbC+1:
352
+ raise ValueError(f"Each row must have {nbC} values according to the format.")
353
+ binary_row = pack(format, *row)
354
+ file.write(binary_row)
355
+
356
+
357
+ def read_txt_file(path:str, fileName:str, sep:str="\t", header:int=2) -> tuple[np.array, np.array]:
358
+ """
359
+ Read a text file and return the data as two numpy arrays.
360
+
361
+ Args:
362
+ path (str): The path to the text file.
363
+ fileName (str): The name of the text file.
364
+ sep (str): The separator used in the text file. Default is tab.
365
+ header (int): The number of header lines to skip. Default is 0.
366
+
367
+ Returns:
368
+ tuple: A tuple containing two numpy arrays: time and values.
369
+ """
370
+ data = np.loadtxt(os.path.join(path, fileName), delimiter=sep, skiprows=header)
371
+ # time = data[:, :-1]
372
+ # values = data[:, -1]
373
+
374
+ return data
375
+
376
+
377
+ def write_txt_file(path:str, fileName:str, data:np.array, sep:str="\t", header:str=None, format:list=['%d']*6+['%.15f']) -> None:
378
+
379
+ if header is None:
380
+ header = f"{data.shape[0]:d}\n{data.shape[1]:d}"
381
+
382
+ full_name = os.path.join(path, fileName)
383
+ np.savetxt(full_name, data, header=header, fmt=format, comments='',delimiter=sep)