wolfhece 2.1.21__py3-none-any.whl → 2.1.23__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.
- wolfhece/PyDraw.py +179 -39
- wolfhece/PyGui.py +2 -3
- wolfhece/PyParams.py +3 -2
- wolfhece/PyVertexvectors.py +91 -12
- wolfhece/apps/version.py +1 -1
- wolfhece/coupling/__init__.py +0 -0
- wolfhece/coupling/hydrology_2d.py +1226 -0
- wolfhece/irm_qdf.py +19 -2
- wolfhece/wintab/__init__.py +0 -0
- wolfhece/wintab/wintab.py +248 -0
- {wolfhece-2.1.21.dist-info → wolfhece-2.1.23.dist-info}/METADATA +1 -1
- {wolfhece-2.1.21.dist-info → wolfhece-2.1.23.dist-info}/RECORD +15 -11
- {wolfhece-2.1.21.dist-info → wolfhece-2.1.23.dist-info}/WHEEL +1 -1
- {wolfhece-2.1.21.dist-info → wolfhece-2.1.23.dist-info}/entry_points.txt +0 -0
- {wolfhece-2.1.21.dist-info → wolfhece-2.1.23.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1226 @@
|
|
1
|
+
from ..hydrology.Catchment import Catchment, SubBasin
|
2
|
+
from ..hydrology.PyWatershed import Watershed, Node_Watershed, RiverSystem, SubWatershed
|
3
|
+
from ..wolf_array import WolfArray, WOLF_ARRAY_FULL_INTEGER, WOLF_ARRAY_FULL_SINGLE
|
4
|
+
from ..PyVertexvectors import Zones,zone,vector, wolfvertex
|
5
|
+
from ..PyTranslate import _
|
6
|
+
|
7
|
+
from scipy.spatial import KDTree
|
8
|
+
import pandas as pd
|
9
|
+
from pathlib import Path
|
10
|
+
import numpy as np
|
11
|
+
from typing import Literal, Union
|
12
|
+
from enum import Enum
|
13
|
+
import logging
|
14
|
+
import matplotlib.pyplot as plt
|
15
|
+
from matplotlib.figure import Figure
|
16
|
+
from matplotlib.axes import Axes
|
17
|
+
|
18
|
+
|
19
|
+
class InjectionType(Enum):
|
20
|
+
GLOBAL = 'Global'
|
21
|
+
PARTIAL = 'Partial'
|
22
|
+
ANTHROPOGENIC = 'Anthropogenic'
|
23
|
+
CONSTANT = 'Constant'
|
24
|
+
VIRTUAL = 'Virtual'
|
25
|
+
|
26
|
+
|
27
|
+
class Searching_Context():
|
28
|
+
|
29
|
+
def __init__(self,
|
30
|
+
river_axis:vector,
|
31
|
+
kdtree:KDTree,
|
32
|
+
nodes:Node_Watershed,
|
33
|
+
downstream_reaches:list[int],
|
34
|
+
up_node:Node_Watershed) -> None:
|
35
|
+
|
36
|
+
self.river_axis:vector = river_axis # river axis -- vector
|
37
|
+
self.kdtree:KDTree = kdtree # KDTree of the downstream reaches
|
38
|
+
self.nodes:Node_Watershed = nodes # nodes of the downstream reaches - from Hydrology model
|
39
|
+
self.downstream_reaches:list[int] = downstream_reaches# downstream reaches - from Hydrology model
|
40
|
+
self.up_node:Node_Watershed = up_node # up node - from Hydrology model
|
41
|
+
|
42
|
+
def __str__(self) -> str:
|
43
|
+
|
44
|
+
ret = ''
|
45
|
+
|
46
|
+
ret += f" Number of reaches: {len(self.downstream_reaches)}\n"
|
47
|
+
ret += f" Downstream reaches: {self.downstream_reaches}\n"
|
48
|
+
ret += f" Number of nodes: {len(self.nodes)}\n"
|
49
|
+
ret += f" Length of the river axis: {self.river_axis.length2D}\n"
|
50
|
+
|
51
|
+
return ret
|
52
|
+
|
53
|
+
|
54
|
+
class Scaled_Infiltration():
|
55
|
+
|
56
|
+
def __init__(self, idx:int, type:InjectionType, colref:str, factor:float, lagtime:float) -> None:
|
57
|
+
|
58
|
+
self.index = idx # index of the infiltration
|
59
|
+
self.type = type # type of the infiltration
|
60
|
+
self.colref = colref # reference column
|
61
|
+
self.factor = factor # multiplicator factor [-]
|
62
|
+
self.lagtime = lagtime # lag time [s]
|
63
|
+
|
64
|
+
class Coupling_Hydrology_2D():
|
65
|
+
|
66
|
+
def __init__(self) -> None:
|
67
|
+
|
68
|
+
|
69
|
+
self._rivers_zones:list[Zones] = []
|
70
|
+
self.rivers:dict[str, vector] = {}
|
71
|
+
|
72
|
+
self.upstreams:dict[str, wolfvertex] = {}
|
73
|
+
|
74
|
+
self._locale_injection_zones:Zones = None
|
75
|
+
self.locale_injections:dict[str, vector] = {}
|
76
|
+
|
77
|
+
self._hydrology_model:Catchment = None
|
78
|
+
|
79
|
+
self._searching:dict[str, Searching_Context] = {}
|
80
|
+
|
81
|
+
self.hydrographs_total:pd.DataFrame = None
|
82
|
+
self.hydrographs_local:pd.DataFrame = None
|
83
|
+
self._hydrographs_virtual = []
|
84
|
+
|
85
|
+
self._dem:WolfArray = None
|
86
|
+
self._infil_idx:WolfArray = None
|
87
|
+
self.counter_zone_infil = 0
|
88
|
+
|
89
|
+
# Les infiltrations sont définies par des zones de mailles.
|
90
|
+
# Chaque zone est définie par :
|
91
|
+
# - un type d'infiltration
|
92
|
+
# - un nom d'hydrogramme (ou une valeur constante)
|
93
|
+
# - un facteur podérateur
|
94
|
+
# - un temps de déphasage
|
95
|
+
|
96
|
+
self.infiltrations:list[Scaled_Infiltration] = []
|
97
|
+
self.local_infiltrations:dict[vector:int] = {}
|
98
|
+
|
99
|
+
# lists part is a dictionary with the following structure:
|
100
|
+
# - key : river name
|
101
|
+
# - value : tuple with two elements
|
102
|
+
# - first element : list of unique drained areas
|
103
|
+
# - second element : dictionary with the following structure
|
104
|
+
# - key : drained area
|
105
|
+
# - value : list of coordinates of the river points in the dem
|
106
|
+
self.lists_part:dict[str, tuple[np.ndarray, dict[float, list[tuple[float, float]]]]] = {}
|
107
|
+
|
108
|
+
self.df_2d = None
|
109
|
+
|
110
|
+
self_along = None
|
111
|
+
self._locales = None
|
112
|
+
|
113
|
+
def __str__(self) -> str:
|
114
|
+
""" Return a string representation of the coupling """
|
115
|
+
|
116
|
+
ret =''
|
117
|
+
|
118
|
+
ret += _("Rivers: {}\n").format(len(self.rivers))
|
119
|
+
for curriver in self.rivers:
|
120
|
+
ret += f"{curriver}\n"
|
121
|
+
|
122
|
+
ret += _("Local injections: {}\n").format(len(self.locale_injections))
|
123
|
+
for curinj in self.locale_injections:
|
124
|
+
ret += f"{curinj}\n"
|
125
|
+
|
126
|
+
|
127
|
+
ret += _("Upstreams: {}\n").format(len(self.upstreams))
|
128
|
+
for curup in self.upstreams:
|
129
|
+
ret += f" {curup} :\n"
|
130
|
+
ret += f" X : {self.upstreams[curup][0]}\n"
|
131
|
+
ret += f" Y : {self.upstreams[curup][1]}\n"
|
132
|
+
ret += f" Drained area : {self.river_system.get_nearest_nodes(self.upstreams[curup])[1].uparea}\n"
|
133
|
+
|
134
|
+
for cursearch in self._searching:
|
135
|
+
ret += f"{cursearch} : \n{self._searching[cursearch]}\n"
|
136
|
+
|
137
|
+
ret += f"Coupling array: \n{self._infil_idx}\n"
|
138
|
+
|
139
|
+
return ret
|
140
|
+
|
141
|
+
@property
|
142
|
+
def number_of_injections(self) -> int:
|
143
|
+
|
144
|
+
if self._infil_idx is None:
|
145
|
+
logging.error(_("No infiltration array -- Spread the injections first"))
|
146
|
+
return 0
|
147
|
+
|
148
|
+
return self._infil_idx.array.max()
|
149
|
+
|
150
|
+
@property
|
151
|
+
def number_of_nodes_per_zone(self) -> dict[int, int]:
|
152
|
+
""" Return the number of nodes per zone """
|
153
|
+
|
154
|
+
if self._infil_idx is None:
|
155
|
+
logging.error(_("No infiltration array -- Spread the injections first"))
|
156
|
+
return {}
|
157
|
+
|
158
|
+
non_zeros = self._infil_idx.array[np.where(self._infil_idx.array > 0)]
|
159
|
+
|
160
|
+
return {i:np.count_nonzero(non_zeros == i) for i in range(1, self.number_of_injections+1)}
|
161
|
+
|
162
|
+
def plot_number_of_nodes_per_zone(self) -> tuple[Figure, Axes]:
|
163
|
+
""" Plot the number of nodes per zone """
|
164
|
+
|
165
|
+
if self._infil_idx is None:
|
166
|
+
logging.error(_("No infiltration array -- Spread the injections first"))
|
167
|
+
return None, None
|
168
|
+
|
169
|
+
fig, ax = plt.subplots()
|
170
|
+
ax.hist(self.number_of_nodes_per_zone)
|
171
|
+
ax.set_title(_("Number of nodes per zone"))
|
172
|
+
ax.set_xlabel(_("Zone index"))
|
173
|
+
ax.set_ylabel(_("Number of nodes"))
|
174
|
+
|
175
|
+
return fig, ax
|
176
|
+
|
177
|
+
@property
|
178
|
+
def along(self) -> list[str, str]:
|
179
|
+
return self._along
|
180
|
+
|
181
|
+
@along.setter
|
182
|
+
def along(self, value:list[tuple[str, str]]) -> None:
|
183
|
+
|
184
|
+
for curval in value:
|
185
|
+
|
186
|
+
curcol, currivers = curval
|
187
|
+
|
188
|
+
assert curcol in self.hydrographs_local.columns, f"Column {curcol} not found in hydrographs"
|
189
|
+
|
190
|
+
def check_rivers(currivers):
|
191
|
+
if isinstance(currivers, list):
|
192
|
+
for curriver in currivers:
|
193
|
+
check_rivers(curriver)
|
194
|
+
else:
|
195
|
+
assert currivers in self.rivers, f"River {currivers} not found"
|
196
|
+
|
197
|
+
self._along = value
|
198
|
+
|
199
|
+
@property
|
200
|
+
def locales(self) -> list[str]:
|
201
|
+
return self._locales
|
202
|
+
|
203
|
+
@locales.setter
|
204
|
+
def locales(self, value:list[str]) -> None:
|
205
|
+
|
206
|
+
for curvect, curcol, curtype in value:
|
207
|
+
|
208
|
+
if curtype != InjectionType.CONSTANT:
|
209
|
+
assert curvect in self.locale_injections, f"Vector {curvect} not found"
|
210
|
+
assert curcol in list(self.hydrographs_total.columns)+ self._hydrographs_virtual, f"Column {curcol} not found in hydrographs"
|
211
|
+
|
212
|
+
self._locales = value
|
213
|
+
|
214
|
+
@property
|
215
|
+
def watershed(self) -> Watershed:
|
216
|
+
return self._hydrology_model.charact_watrshd
|
217
|
+
|
218
|
+
@property
|
219
|
+
def river_system(self) -> RiverSystem:
|
220
|
+
return self.watershed.riversystem
|
221
|
+
|
222
|
+
@property
|
223
|
+
def subs_array(self) -> WolfArray:
|
224
|
+
return self.watershed.subs_array
|
225
|
+
|
226
|
+
def set_array_to_coupling(self, array:WolfArray | Path) -> None:
|
227
|
+
""" Set the array to coupling
|
228
|
+
|
229
|
+
:param array: The array to coupling
|
230
|
+
"""
|
231
|
+
|
232
|
+
if isinstance(array, Path):
|
233
|
+
if array.exists():
|
234
|
+
self._dem = WolfArray(array)
|
235
|
+
else:
|
236
|
+
logging.error(_("File {} not found").format(array))
|
237
|
+
return
|
238
|
+
|
239
|
+
self._dem = array
|
240
|
+
self._create_infiltration_array()
|
241
|
+
|
242
|
+
def _create_infiltration_array(self):
|
243
|
+
""" Create the infiltration array """
|
244
|
+
|
245
|
+
if self._dem is None:
|
246
|
+
logging.error(_("No array to coupling"))
|
247
|
+
return
|
248
|
+
|
249
|
+
self._infil_idx = WolfArray(srcheader=self._dem.get_header(), whichtype=WOLF_ARRAY_FULL_INTEGER)
|
250
|
+
self._infil_idx.add_ops_sel()
|
251
|
+
self._infil_idx.array[:,:] = 0
|
252
|
+
|
253
|
+
self.counter_zone_infil = 0
|
254
|
+
|
255
|
+
def add_hydrology_model(self, name:str, filename:str | Path) -> None:
|
256
|
+
""" Add a hydrology model to the coupling
|
257
|
+
|
258
|
+
:param filename: The filename of the hydrology model
|
259
|
+
"""
|
260
|
+
|
261
|
+
self._hydrology_model = Catchment(name, str(filename), False, True)
|
262
|
+
|
263
|
+
def get_anthropogenic_names(self) -> list[str]:
|
264
|
+
""" Print the names of the anthropogenic hydrographs """
|
265
|
+
|
266
|
+
if self._hydrology_model is None:
|
267
|
+
logging.error(_("No hydrology model loaded"))
|
268
|
+
return []
|
269
|
+
|
270
|
+
return [" : ".join([cur_anth.name, name])
|
271
|
+
for cur_anth in self._hydrology_model.retentionBasinDict.values()
|
272
|
+
for name in cur_anth.get_outFlow_names()]
|
273
|
+
|
274
|
+
def get_names_areas(self) -> list[str]:
|
275
|
+
""" Print the names of the areas """
|
276
|
+
|
277
|
+
if self._hydrology_model is None:
|
278
|
+
logging.error(_("No hydrology model loaded"))
|
279
|
+
return []
|
280
|
+
|
281
|
+
names= [cur_sub.name for cur_sub in sorted(self._hydrology_model.subBasinDict.values(), key=lambda sub: sub.iDSorted)]
|
282
|
+
area_subs = [cur_sub.surfaceDrained for cur_sub in sorted(self._hydrology_model.subBasinDict.values(), key=lambda sub: sub.iDSorted)]
|
283
|
+
area_glob = [cur_sub.surfaceDrainedHydro for cur_sub in sorted(self._hydrology_model.subBasinDict.values(), key=lambda sub: sub.iDSorted)]
|
284
|
+
|
285
|
+
return names, area_subs, area_glob
|
286
|
+
|
287
|
+
def create_hydrographs_local_global(self, unit_discharge:float, total_duration:float, anth_discharge:dict = None) -> None:
|
288
|
+
"""
|
289
|
+
Create the hydrographs from the hydrology model .
|
290
|
+
|
291
|
+
Global and local hydrographs are created based on a unit discharge and a total duration.
|
292
|
+
|
293
|
+
You can also add anthropogenic hydrographs from a dictionary.
|
294
|
+
The key is the name of the anthropogenic hydrograph and the value is the discharge.
|
295
|
+
The keys can be obtained with the method get_anthropogenic_names.
|
296
|
+
|
297
|
+
:param unit_discharge: The discharge per square kilometer [m³/s/km²]
|
298
|
+
:param total_duration: The total duration of the hydrographs [s]
|
299
|
+
"""
|
300
|
+
|
301
|
+
# Extract the column names according to their sorted subbasin indices
|
302
|
+
col_time = "Time [s]"
|
303
|
+
col_subs, area_subs, area_glob = self.get_names_areas()
|
304
|
+
col_anth = [" : ".join([cur_anth.name, name])
|
305
|
+
for cur_anth in self._hydrology_model.retentionBasinDict.values()
|
306
|
+
for name in cur_anth.get_outFlow_names()]
|
307
|
+
|
308
|
+
#Create a dictionnary
|
309
|
+
|
310
|
+
dict_glob = {col_time : [0., total_duration]}
|
311
|
+
|
312
|
+
for cur_sub, cur_area in zip(col_subs, area_glob):
|
313
|
+
discharge = cur_area * unit_discharge
|
314
|
+
dict_glob[cur_sub] = [discharge, discharge]
|
315
|
+
|
316
|
+
for cur_anth in col_anth:
|
317
|
+
dict_glob[cur_anth] = [0., 0.]
|
318
|
+
|
319
|
+
if anth_discharge is not None:
|
320
|
+
for cur_anth, cur_discharge in anth_discharge.items():
|
321
|
+
dict_glob[cur_anth] = [cur_discharge, cur_discharge]
|
322
|
+
|
323
|
+
dict_loc = {col_time : [0., total_duration]}
|
324
|
+
|
325
|
+
for cur_sub, cur_area in zip(col_subs, area_subs):
|
326
|
+
discharge = cur_area * unit_discharge
|
327
|
+
dict_loc[cur_sub] = [discharge, discharge]
|
328
|
+
|
329
|
+
self.hydrographs_total = pd.DataFrame(dict_glob)
|
330
|
+
self.hydrographs_local = pd.DataFrame(dict_loc)
|
331
|
+
|
332
|
+
self.hydrographs_local.set_index(col_time, inplace=True)
|
333
|
+
self.hydrographs_total.set_index(col_time, inplace=True)
|
334
|
+
|
335
|
+
def save_hydrographs(self, directory:str | Path = None, total:str = None, partial:str = None) -> None:
|
336
|
+
""" Write the hydrographs from the hydrology model """
|
337
|
+
|
338
|
+
if directory is None:
|
339
|
+
|
340
|
+
if self._hydrology_model is None:
|
341
|
+
logging.error(_("No hydrology model loaded"))
|
342
|
+
return
|
343
|
+
|
344
|
+
directory = Path(self._hydrology_model.workingDir) / 'PostProcess'
|
345
|
+
|
346
|
+
if total is not None:
|
347
|
+
self.hydrographs_total.to_csv(directory / total, sep='\t', decimal='.', encoding='latin1')
|
348
|
+
else:
|
349
|
+
self.hydrographs_total.to_csv(directory / 'Hydros_2_simul2D.txt', sep='\t', decimal='.', encoding='latin1')
|
350
|
+
|
351
|
+
if partial is not None:
|
352
|
+
self.hydrographs_local.to_csv(directory / partial, sep='\t', decimal='.', encoding='latin1')
|
353
|
+
else:
|
354
|
+
self.hydrographs_local.to_csv(directory / 'HydrosSub_2_simul2D.txt', sep='\t', decimal='.', encoding='latin1')
|
355
|
+
|
356
|
+
|
357
|
+
def load_hydrographs(self, directory:str | Path = None, total:str = None, partial:str = None) -> None:
|
358
|
+
""" Load the hydrographs from the hydrology model
|
359
|
+
|
360
|
+
:param directory: The directory of the hydrology model -- If None, the working directory of the loaded hydrology model is used
|
361
|
+
:param total: The filename of the total hydrographs - If None, the default filename is used
|
362
|
+
:param partial: The filename of the partial hydrographs - If None, the default filename is used
|
363
|
+
|
364
|
+
"""
|
365
|
+
|
366
|
+
if directory is None:
|
367
|
+
|
368
|
+
if self._hydrology_model is None:
|
369
|
+
logging.error(_("No hydrology model loaded"))
|
370
|
+
return
|
371
|
+
|
372
|
+
directory = Path(self._hydrology_model.workingDir) / 'PostProcess'
|
373
|
+
|
374
|
+
if total is not None:
|
375
|
+
total = directory / total
|
376
|
+
if total.exists():
|
377
|
+
self.hydrographs_total = pd.read_csv(total, sep='\t', decimal='.', header=0, index_col=0, encoding='latin1')
|
378
|
+
else:
|
379
|
+
logging.error(_("File {} not found").format(total))
|
380
|
+
|
381
|
+
else:
|
382
|
+
total = directory / 'Hydros_2_simul2D.txt'
|
383
|
+
if total.exists():
|
384
|
+
self.hydrographs_total = pd.read_csv(total, sep='\t', decimal='.', header=0, index_col=0, encoding='latin1')
|
385
|
+
else:
|
386
|
+
logging.error(_("File {} not found").format(total))
|
387
|
+
|
388
|
+
if partial is not None:
|
389
|
+
partial = directory / partial
|
390
|
+
if partial.exists():
|
391
|
+
self.hydrographs_local = pd.read_csv(partial, sep='\t', decimal='.', header=0, index_col=0, encoding='latin1')
|
392
|
+
else:
|
393
|
+
logging.error(_("File {} not found").format(partial))
|
394
|
+
|
395
|
+
else:
|
396
|
+
partial = directory / 'HydrosSub_2_simul2D.txt'
|
397
|
+
if partial.exists():
|
398
|
+
self.hydrographs_local = pd.read_csv(partial, sep='\t', decimal='.', header=0, index_col=0, encoding='latin1')
|
399
|
+
else:
|
400
|
+
logging.error(_("File {} not found").format(partial))
|
401
|
+
|
402
|
+
|
403
|
+
def print_hydrographs(self, total:bool = True, partial:bool = True) -> None:
|
404
|
+
""" Print the hydrographs from the hydrology model """
|
405
|
+
|
406
|
+
if total:
|
407
|
+
print(_("Total hydrographs:"))
|
408
|
+
print(self.hydrographs_total.columns)
|
409
|
+
|
410
|
+
if partial:
|
411
|
+
print(_("Partial hydrographs:"))
|
412
|
+
print(self.hydrographs_local.columns)
|
413
|
+
|
414
|
+
def plot_hydrographs(self, total:bool = True, partial:bool = True) -> tuple[tuple[Figure, Axes],tuple[Figure, Axes]]:
|
415
|
+
""" Plot the hydrographs from the hydrology model """
|
416
|
+
|
417
|
+
if total:
|
418
|
+
ax1 = self.hydrographs_total.plot()
|
419
|
+
fig1 = ax1.figure
|
420
|
+
ax1.legend(loc='upper center', ncol=8)
|
421
|
+
ax1.set_ylim(0, 1000)
|
422
|
+
fig1.set_size_inches(15, 5)
|
423
|
+
fig1.tight_layout()
|
424
|
+
else:
|
425
|
+
fig1, ax1 = None, None
|
426
|
+
|
427
|
+
if partial:
|
428
|
+
ax2 = self.hydrographs_local.plot()
|
429
|
+
fig2 = ax2.figure
|
430
|
+
ax2.legend(loc='upper center', ncol=8)
|
431
|
+
ax2.set_ylim(0, 1000)
|
432
|
+
fig2.set_size_inches(15, 5)
|
433
|
+
fig2.tight_layout()
|
434
|
+
else:
|
435
|
+
fig2, ax2 = None, None
|
436
|
+
|
437
|
+
return (fig1, ax1), (fig2, ax2)
|
438
|
+
|
439
|
+
|
440
|
+
def add_virtual_hydrograph(self, name:str, src_hydrograph_name:str, factor:float, lag:float=0.):
|
441
|
+
""" Add a virtual hydrograph to the hydrology model """
|
442
|
+
|
443
|
+
self._hydrographs_virtual.append((name, src_hydrograph_name, factor, lag))
|
444
|
+
|
445
|
+
def add_river(self, filename:str | Path) -> None:
|
446
|
+
""" Add a river to the hydrology model
|
447
|
+
|
448
|
+
:param filename: The filename of the river
|
449
|
+
"""
|
450
|
+
|
451
|
+
self._rivers_zones.append(Zones(filename))
|
452
|
+
|
453
|
+
def reset(self) -> None:
|
454
|
+
""" Reset the hydrology model """
|
455
|
+
|
456
|
+
self._rivers_zones = []
|
457
|
+
self.rivers = {}
|
458
|
+
self._locale_injection_zones = None
|
459
|
+
self.locale_injections = {}
|
460
|
+
self.lists_part = {}
|
461
|
+
self.df_2d = None
|
462
|
+
|
463
|
+
self._searching = {}
|
464
|
+
|
465
|
+
self.reset_injections()
|
466
|
+
|
467
|
+
def add_locale_injections(self, filename:str | Path) -> None:
|
468
|
+
""" Add a local injection to the hydrology model
|
469
|
+
|
470
|
+
:param filename: The filename of the local injection
|
471
|
+
"""
|
472
|
+
|
473
|
+
self._locale_injection_zones = Zones(filename)
|
474
|
+
|
475
|
+
def find_river_axis(self):
|
476
|
+
""" Find the river axis from Zones """
|
477
|
+
|
478
|
+
for curriver in self._rivers_zones:
|
479
|
+
for curzone in curriver.myzones:
|
480
|
+
if curzone.nbvectors !=3:
|
481
|
+
logging.error(_("Zone {} has {} vectors, should be 3").format(curzone.myname, curzone.nbvectors))
|
482
|
+
else:
|
483
|
+
# Select the river axis
|
484
|
+
curvector = curzone.myvectors[1]
|
485
|
+
if curvector.used:
|
486
|
+
self.rivers[curzone.myname] = curvector
|
487
|
+
else:
|
488
|
+
logging.warning(_("Vector {} in zone {} is not used -- Ignoring it as a river axis").format(curvector.myname, curzone.myname))
|
489
|
+
|
490
|
+
def _add_injection(self, name:str, vect:vector):
|
491
|
+
""" Add an injection to the hydrology model
|
492
|
+
|
493
|
+
:param name: The name of the injection
|
494
|
+
:param vect: The vector of the injection
|
495
|
+
"""
|
496
|
+
|
497
|
+
self.locale_injections[name] = vect
|
498
|
+
|
499
|
+
def find_injections(self):
|
500
|
+
""" Find the injection points from Zones """
|
501
|
+
|
502
|
+
for curzone in self._locale_injection_zones.myzones:
|
503
|
+
|
504
|
+
names = [curvect.myname for curvect in curzone.myvectors]
|
505
|
+
|
506
|
+
if 'injection' in names:
|
507
|
+
vect = curzone.myvectors[names.index('injection')]
|
508
|
+
if vect.used:
|
509
|
+
self._add_injection(curzone.myname, vect)
|
510
|
+
else:
|
511
|
+
logging.warning(_("Vector {} in zone {} is not used -- Ignoring it as an injection point").format(vect.myname, curzone.myname))
|
512
|
+
else:
|
513
|
+
logging.error(_("Zone {} does not contain an injection point").format(curzone.myname))
|
514
|
+
|
515
|
+
def find_rivers_upstream(self):
|
516
|
+
""" Find the upstreams of the rivers """
|
517
|
+
|
518
|
+
for curriver, curvect in self.rivers.items():
|
519
|
+
up = self._find_upstream(curvect)
|
520
|
+
self.upstreams[curriver] = (up.x, up.y)
|
521
|
+
|
522
|
+
def _find_upstream(self, curvect:vector) -> wolfvertex:
|
523
|
+
""" Find the upstream of a vector
|
524
|
+
|
525
|
+
:param curvect: The river's axis
|
526
|
+
"""
|
527
|
+
|
528
|
+
vert1 = curvect.myvertices[0]
|
529
|
+
vert2 = curvect.myvertices[-1]
|
530
|
+
|
531
|
+
riv1 = self.river_system.get_nearest_nodes([vert1.x, vert1.y])[1]
|
532
|
+
riv2 = self.river_system.get_nearest_nodes([vert2.x, vert2.y])[1]
|
533
|
+
|
534
|
+
# Drained area
|
535
|
+
area1 = riv1.uparea
|
536
|
+
area2 = riv2.uparea
|
537
|
+
|
538
|
+
# Get the minimum
|
539
|
+
if area1 > area2:
|
540
|
+
return vert2
|
541
|
+
else:
|
542
|
+
return vert1
|
543
|
+
|
544
|
+
def prepare_search(self, rivers:list[str] = None):
|
545
|
+
"""
|
546
|
+
Prepare the search for the hydrology model.
|
547
|
+
|
548
|
+
The order is important because the reaches will be
|
549
|
+
progressively excluded from the search for the next ones.
|
550
|
+
|
551
|
+
So, you have to start with the **main river** and then the **tributaries**.
|
552
|
+
|
553
|
+
:param rivers: The list of rivers to prepare
|
554
|
+
"""
|
555
|
+
|
556
|
+
excluded = []
|
557
|
+
|
558
|
+
if rivers is None:
|
559
|
+
rivers = self.rivers.keys()
|
560
|
+
else:
|
561
|
+
for curriver in rivers:
|
562
|
+
if curriver not in self.rivers:
|
563
|
+
logging.error(f"River {curriver} not found")
|
564
|
+
return
|
565
|
+
logging.info(f"Rivers to prepare in thos order: {rivers}")
|
566
|
+
|
567
|
+
for curriver in rivers:
|
568
|
+
|
569
|
+
curvect = self.rivers[curriver]
|
570
|
+
|
571
|
+
# Récupération de la maille rivière la plus proche
|
572
|
+
dist, node_up = self.river_system.get_nearest_nodes(self.upstreams[curriver])
|
573
|
+
|
574
|
+
# Récupération de la liste des biefs en aval
|
575
|
+
downstream = self.river_system.get_downstream_reaches_excluded(node_up, excluded)
|
576
|
+
|
577
|
+
excluded += downstream
|
578
|
+
|
579
|
+
# Mis en place d'une structure de recherche rapide
|
580
|
+
nodes, kdtree = self.river_system.get_kdtree_from_reaches(downstream)
|
581
|
+
|
582
|
+
self._searching[curriver] = Searching_Context(curvect, kdtree, nodes, downstream, node_up)
|
583
|
+
|
584
|
+
def _is_global(self, col_name:str):
|
585
|
+
""" Vérifie si la colonne est un hydrogramme global """
|
586
|
+
|
587
|
+
return col_name in self.hydrographs_total.columns
|
588
|
+
|
589
|
+
def _is_partial(self, col_name:str):
|
590
|
+
""" Vérifie si la colonne est un hydrogramme partiel """
|
591
|
+
|
592
|
+
return col_name in self.hydrographs_local.columns
|
593
|
+
|
594
|
+
def _is_anthropic(self, col_name:str):
|
595
|
+
"""
|
596
|
+
Vérifie si la colonne est un hydrogramme anthropique
|
597
|
+
(c'est-à-dire une colonne de l'hydrogramme total qui n'est pas un hydrogramme partiel)
|
598
|
+
|
599
|
+
"""
|
600
|
+
|
601
|
+
return self._is_global(col_name) and not self._is_partial(col_name)
|
602
|
+
|
603
|
+
def _is_virtual(self, col_name:str):
|
604
|
+
""" Vérifie si la colonne est un hydrogramme virtuel """
|
605
|
+
|
606
|
+
return col_name in [virtualname for virtualname, src_name, frac, lag in self._hydrographs_virtual]
|
607
|
+
|
608
|
+
def _add_infil(self,
|
609
|
+
type_name:InjectionType,
|
610
|
+
col_name_q:str | float,
|
611
|
+
factor:float,
|
612
|
+
lag:float,
|
613
|
+
index_zone:int = None):
|
614
|
+
"""
|
615
|
+
Ajoute une infiltration à la liste des infiltrations
|
616
|
+
|
617
|
+
:param type_name: nom du type d'infiltration
|
618
|
+
:param col_name: nom de la colonne de l'hydrogramme
|
619
|
+
:param factor: facteur multiplicatif
|
620
|
+
:param lag: déphasage
|
621
|
+
"""
|
622
|
+
|
623
|
+
if type_name == InjectionType.GLOBAL:
|
624
|
+
if not self._is_global(col_name_q):
|
625
|
+
raise ValueError(f"Colonne {col_name_q} not found in global hydrographs")
|
626
|
+
elif type_name == InjectionType.PARTIAL:
|
627
|
+
if not self._is_partial(col_name_q):
|
628
|
+
raise ValueError(f"Colonne {col_name_q} not found in partial hydrographs")
|
629
|
+
elif type_name == InjectionType.ANTHROPOGENIC:
|
630
|
+
if not self._is_anthropic(col_name_q):
|
631
|
+
raise ValueError(f"Colonne {col_name_q} not found in anthropic hydrographs")
|
632
|
+
elif type_name == InjectionType.VIRTUAL:
|
633
|
+
if not self._is_virtual(col_name_q):
|
634
|
+
raise ValueError(f"Colonne {col_name_q} not found in virtual hydrographs")
|
635
|
+
|
636
|
+
if index_zone is None:
|
637
|
+
self.counter_zone_infil += 1
|
638
|
+
index_zone = self.counter_zone_infil
|
639
|
+
|
640
|
+
self.infiltrations.append(Scaled_Infiltration(index_zone, type_name, col_name_q, factor, lag))
|
641
|
+
|
642
|
+
return index_zone
|
643
|
+
|
644
|
+
def _add_local_injecton(self,
|
645
|
+
local_vect:vector,
|
646
|
+
type_name:InjectionType,
|
647
|
+
col_name:str,
|
648
|
+
factor:float,
|
649
|
+
lag:float):
|
650
|
+
|
651
|
+
"""
|
652
|
+
Ajoute une injection locale à la liste des infiltrations
|
653
|
+
et remplissage de la matrice d'infiltration
|
654
|
+
|
655
|
+
:param local_vect: vecteur de la zone d'injection
|
656
|
+
:param type_name: nom du type d'injection
|
657
|
+
:param col_name: nom de la colonne de l'hydrogramme
|
658
|
+
:param factor: facteur multiplicatif
|
659
|
+
:param lag: déphasage
|
660
|
+
|
661
|
+
"""
|
662
|
+
|
663
|
+
assert type_name in InjectionType, f"Unknown type {type_name}"
|
664
|
+
|
665
|
+
if local_vect not in self.local_infiltrations:
|
666
|
+
# Pas encore d'injection dans cette zone
|
667
|
+
|
668
|
+
self.local_infiltrations[local_vect] = self._add_infil(type_name, col_name, factor, lag)
|
669
|
+
|
670
|
+
# Mise à zéro de la sélection dans la matrice d'infiltration
|
671
|
+
self._infil_idx.SelectionData.reset()
|
672
|
+
# Sélection des mailles à l'intérieur du polygone de la zone d'injection
|
673
|
+
self._infil_idx.SelectionData.select_insidepoly(local_vect)
|
674
|
+
|
675
|
+
# Récupération des coordonnées des mailles sélectionnées
|
676
|
+
xy = np.array(self._infil_idx.SelectionData.myselection)
|
677
|
+
# Conversion des coordonnées en indices de mailles
|
678
|
+
ij = self._infil_idx.get_ij_from_xy_array(xy)
|
679
|
+
|
680
|
+
# Affectation de l'indice de la zone d'infiltration
|
681
|
+
for i,j in ij:
|
682
|
+
self._infil_idx.array[i,j] = self.local_infiltrations[local_vect]
|
683
|
+
|
684
|
+
# Vérification du nombre de mailles affectées
|
685
|
+
assert len(ij) == np.count_nonzero(self._infil_idx.array == self.local_infiltrations[local_vect]), "Bad count for {}".format(type_name)
|
686
|
+
|
687
|
+
else:
|
688
|
+
# Une injection existe déjà dans cette zone
|
689
|
+
# On ajoute une nouvelle infiltration qui sera additionnée à la précédente
|
690
|
+
|
691
|
+
self._add_infil(type_name, col_name, factor, lag, self.local_infiltrations[local_vect])
|
692
|
+
|
693
|
+
|
694
|
+
def _add_along_injection(self,
|
695
|
+
list_part:list[float, float],
|
696
|
+
type_name:InjectionType,
|
697
|
+
col_name:str,
|
698
|
+
factor:float,
|
699
|
+
lag:float):
|
700
|
+
"""
|
701
|
+
Ajoute une injection le long de la rivière
|
702
|
+
et remplissage de la matrice d'infiltration
|
703
|
+
|
704
|
+
:param list_part: liste des coordonnées des points de la rivière
|
705
|
+
:param type_name: nom du type d'injection
|
706
|
+
:param col_name: nom de la colonne de l'hydrogramme
|
707
|
+
:param factor: facteur multiplicatif
|
708
|
+
:param lag: déphasage
|
709
|
+
"""
|
710
|
+
|
711
|
+
idx_zone = self._add_infil(type_name, col_name, factor, lag)
|
712
|
+
|
713
|
+
for x, y in list_part:
|
714
|
+
i,j = self._infil_idx.get_ij_from_xy(x,y)
|
715
|
+
self._infil_idx.array[i,j] = idx_zone
|
716
|
+
|
717
|
+
|
718
|
+
def write_infil_array(self, dirout:Path):
|
719
|
+
""" Sauvegarde de la matrice d'infiltration """
|
720
|
+
|
721
|
+
self._infil_idx.mask_data(0)
|
722
|
+
self._infil_idx.nullvalue = 99999
|
723
|
+
self._infil_idx.set_nullvalue_in_mask()
|
724
|
+
self._infil_idx.write_all(dirout / f'infiltration.tif')
|
725
|
+
|
726
|
+
def _get_reaches_in_sub(self, subbasin:SubWatershed, rivers_names:list[str]) -> list[list[int]]:
|
727
|
+
"""
|
728
|
+
Retourne une liste de listes des biefs dans le sous-bassin
|
729
|
+
|
730
|
+
:param rivers: liste des noms des rivières
|
731
|
+
:return: liste des biefs dans le sous-bassin
|
732
|
+
"""
|
733
|
+
|
734
|
+
ret = []
|
735
|
+
|
736
|
+
if rivers_names[0] not in self._searching:
|
737
|
+
logging.error(f"River {rivers_names[0]} not found")
|
738
|
+
return ret
|
739
|
+
|
740
|
+
reaches1 = self._searching[rivers_names[0]].downstream_reaches
|
741
|
+
|
742
|
+
reaches_in_sub = [idx for idx in reaches1 if subbasin.is_reach_in_sub(idx)]
|
743
|
+
|
744
|
+
if len(reaches_in_sub) == 0:
|
745
|
+
logging.error(f"No reaches in subbasin for river {rivers_names[0]}")
|
746
|
+
|
747
|
+
ret.append(reaches_in_sub)
|
748
|
+
|
749
|
+
if isinstance(rivers_names[1], list):
|
750
|
+
locret = self._get_reaches_in_sub(subbasin, rivers_names[1])
|
751
|
+
|
752
|
+
for loc in locret:
|
753
|
+
ret.append(loc)
|
754
|
+
else:
|
755
|
+
|
756
|
+
if rivers_names[1] not in self._searching:
|
757
|
+
logging.error(f"River {rivers_names[1]} not found")
|
758
|
+
return ret
|
759
|
+
|
760
|
+
reaches2 = self._searching[rivers_names[1]].downstream_reaches
|
761
|
+
|
762
|
+
reaches_in_sub = [idx for idx in reaches2 if subbasin.is_reach_in_sub(idx)]
|
763
|
+
if len(reaches_in_sub) == 0:
|
764
|
+
logging.error(f"No reaches in subbasin for river {rivers_names[1]}")
|
765
|
+
|
766
|
+
ret.append(reaches_in_sub)
|
767
|
+
|
768
|
+
return ret
|
769
|
+
|
770
|
+
def _get_outlet_reaches(self, subbasin:SubWatershed, idx_reaches:list[int]) -> Node_Watershed:
|
771
|
+
"""
|
772
|
+
Retourne le noeud de sortie du sous-bassin
|
773
|
+
|
774
|
+
:param reaches: liste des biefs dans le sous-bassin
|
775
|
+
:return: noeud de sortie du sous-bassin
|
776
|
+
"""
|
777
|
+
|
778
|
+
down_reach = max(idx_reaches)
|
779
|
+
return subbasin.get_downstream_node_in_reach(down_reach)
|
780
|
+
|
781
|
+
def _split_subwatershed(self, subbasin:SubWatershed, river_names:list[str, list[str]]):
|
782
|
+
|
783
|
+
reaches = self._get_reaches_in_sub(subbasin, river_names)
|
784
|
+
out1 = self._get_outlet_reaches(subbasin, reaches[0])
|
785
|
+
out2 = self._get_outlet_reaches(subbasin, reaches[1])
|
786
|
+
|
787
|
+
newsub1 = self.watershed.create_virtual_subwatershed(out1, [out2])
|
788
|
+
newsub2 = self.watershed.create_virtual_subwatershed(out2)
|
789
|
+
|
790
|
+
return newsub1, newsub2
|
791
|
+
|
792
|
+
def _split_hydrographs(self, subbasin:SubWatershed | str, river_names:list[str, list[str]]):
|
793
|
+
"""
|
794
|
+
Séparation de l'hydrogramme partiel en fonction
|
795
|
+
des surfaces drainées par chaque rivère
|
796
|
+
|
797
|
+
On attend au maximum 2 rivières ou 1 rivière et une liste de rivières.
|
798
|
+
|
799
|
+
Les rivières seront traitées 2 par 2 de façon récursive.
|
800
|
+
|
801
|
+
La seconde rivière et l'affluent de la première rivière.
|
802
|
+
|
803
|
+
"""
|
804
|
+
|
805
|
+
if isinstance(subbasin, str):
|
806
|
+
subbasin = self.watershed.get_subwatershed(subbasin)
|
807
|
+
|
808
|
+
sub1, sub2 = self._split_subwatershed(subbasin, river_names)
|
809
|
+
|
810
|
+
fraction1 = sub1.area / subbasin.area
|
811
|
+
fraction2 = sub2.area / subbasin.area
|
812
|
+
|
813
|
+
assert fraction1 + fraction2 == 1., "Bad fractions"
|
814
|
+
|
815
|
+
self.add_virtual_hydrograph(sub1.name, subbasin.name, fraction1, 0.)
|
816
|
+
self.add_virtual_hydrograph(sub2.name, subbasin.name, fraction2, 0.)
|
817
|
+
|
818
|
+
added = []
|
819
|
+
added.append((sub1.name, river_names[0]))
|
820
|
+
|
821
|
+
if isinstance(river_names[1], list):
|
822
|
+
# Recursive call
|
823
|
+
added.extend(self._split_hydrographs(sub2, river_names[1]))
|
824
|
+
else:
|
825
|
+
added.append((sub2.name, river_names[1]))
|
826
|
+
|
827
|
+
return added
|
828
|
+
|
829
|
+
def get_locale_injection_names(self):
|
830
|
+
""" Print the names of the local injections """
|
831
|
+
|
832
|
+
return list(self.locale_injections.keys())
|
833
|
+
|
834
|
+
def get_along_injection_names(self) -> tuple[list[str], list[str]]:
|
835
|
+
""" Get the names of the along injections
|
836
|
+
|
837
|
+
:return: The names of the rivers along which the injections are made and the columns of the hydrographs
|
838
|
+
"""
|
839
|
+
|
840
|
+
return list(self.lists_part.keys()), list(self.hydrographs_local.columns)
|
841
|
+
|
842
|
+
def reset_injections(self):
|
843
|
+
""" Reset the injections """
|
844
|
+
|
845
|
+
self.counter_zone_infil = 0
|
846
|
+
|
847
|
+
if self._infil_idx is not None:
|
848
|
+
self._infil_idx.array[:,:] = 0
|
849
|
+
|
850
|
+
self._hydrographs_virtual = []
|
851
|
+
|
852
|
+
self.infiltrations = []
|
853
|
+
self.local_infiltrations = {}
|
854
|
+
|
855
|
+
def spread_infiltrations(self):
|
856
|
+
""" Traite les injections """
|
857
|
+
|
858
|
+
self.reset_injections()
|
859
|
+
|
860
|
+
self.injections_locales()
|
861
|
+
self.injections_along()
|
862
|
+
|
863
|
+
self.create_hydrographs()
|
864
|
+
|
865
|
+
def injections_locales(self, couplings:list[tuple[str, str, InjectionType]] = None):
|
866
|
+
""" Ajoute les injections locales """
|
867
|
+
|
868
|
+
if couplings is None:
|
869
|
+
couplings = self._locales
|
870
|
+
|
871
|
+
if couplings is None:
|
872
|
+
logging.error(_("No local injections defined"))
|
873
|
+
return
|
874
|
+
|
875
|
+
for curinj in couplings:
|
876
|
+
|
877
|
+
vec_name, col_name, injtype = curinj
|
878
|
+
|
879
|
+
self._add_local_injecton(self.locale_injections[vec_name], injtype, col_name, 1., 0.)
|
880
|
+
|
881
|
+
|
882
|
+
def link_area2nodes(self):
|
883
|
+
"""
|
884
|
+
Searching cells in dem associated to the river nodes in the hydrological model.
|
885
|
+
|
886
|
+
We use the river axis to select the cells in the dem.
|
887
|
+
|
888
|
+
Then we search the nearest river nodes in the hydrological
|
889
|
+
model.
|
890
|
+
|
891
|
+
We create local lists of cells associated to one river node.
|
892
|
+
|
893
|
+
Due to the fact that the river axis is not exactly the same
|
894
|
+
as the river nodes (not the same spatial resolution, rester vs vector),
|
895
|
+
all river nodes in the hydrological model
|
896
|
+
are not necessarely associated to cells in the dem.
|
897
|
+
|
898
|
+
"""
|
899
|
+
|
900
|
+
for key_river in self.rivers:
|
901
|
+
|
902
|
+
river_axis = self._searching[key_river].river_axis
|
903
|
+
kdtree = self._searching[key_river].kdtree
|
904
|
+
nodes = self._searching[key_river].nodes
|
905
|
+
|
906
|
+
# Mise à 0 des zones sélectionnées
|
907
|
+
self._dem.SelectionData.reset()
|
908
|
+
# Sélection des mailles sur l'axe du lit mineur
|
909
|
+
self._dem.SelectionData.select_underpoly(river_axis)
|
910
|
+
|
911
|
+
# Coordonnées XY des mailles sélectionnées
|
912
|
+
xy_selected = self._dem.SelectionData.myselection
|
913
|
+
|
914
|
+
# Recherche des mailles rivières les plus proches
|
915
|
+
# dans la modélisation hydrologique
|
916
|
+
dist, nearest = kdtree.query(xy_selected, k=1)
|
917
|
+
# Récupération des noeuds correspondants aux index fournis par l'objet KDTree
|
918
|
+
nearest:list[Node_Watershed] = [nodes[i] for i in nearest]
|
919
|
+
|
920
|
+
# Surface drainée par les mailles
|
921
|
+
drained_surface = np.array([cur.uparea for cur in nearest])
|
922
|
+
|
923
|
+
# Valeurs de BV uniques
|
924
|
+
unique_area = np.unique(drained_surface)
|
925
|
+
|
926
|
+
# Création de listes contenant les mailles associé à chaque BV
|
927
|
+
list_part = {}
|
928
|
+
for cur_area in unique_area:
|
929
|
+
idx = list(np.where(drained_surface == cur_area)[0])
|
930
|
+
list_part[cur_area] = [xy_selected[i] for i in idx]
|
931
|
+
|
932
|
+
self.lists_part[key_river] = (unique_area, list_part)
|
933
|
+
|
934
|
+
def injections_along(self, along:list[str, str] = None):
|
935
|
+
""" Injections along rivers """
|
936
|
+
|
937
|
+
if along is None:
|
938
|
+
along = self._along
|
939
|
+
|
940
|
+
if along is None:
|
941
|
+
logging.error(_("No along injections defined"))
|
942
|
+
return
|
943
|
+
|
944
|
+
along = along.copy()
|
945
|
+
|
946
|
+
# ## Création de bassins virtuels afin de séparer les hydrogrammes en plusieurs rivières
|
947
|
+
|
948
|
+
# Un sous-BV peut voir son hydrogramme partiel être réparti entre un rivière ou plusieurs rivières.
|
949
|
+
|
950
|
+
# En cas de rivières multiples, la décomposition doit se faire 2 par 2:
|
951
|
+
|
952
|
+
# - rivière principale
|
953
|
+
# - rivière secondaire
|
954
|
+
|
955
|
+
# La rivière secondaire peut également être décomposée en plusieurs selon le même principe.
|
956
|
+
|
957
|
+
# **La procédure de calcul est récursive.**
|
958
|
+
to_split = [cur for cur in along if isinstance(cur[1], list)]
|
959
|
+
|
960
|
+
replace = []
|
961
|
+
for cur in to_split:
|
962
|
+
replace.extend(self._split_hydrographs(cur[0], cur[1]))
|
963
|
+
|
964
|
+
for cur in to_split:
|
965
|
+
along.remove(cur)
|
966
|
+
|
967
|
+
for cur in replace:
|
968
|
+
along.append(cur)
|
969
|
+
|
970
|
+
for cur in along:
|
971
|
+
self._injection_along(cur)
|
972
|
+
|
973
|
+
def _injection_along(self, name_subwatershed_river:tuple[str, str]):
|
974
|
+
|
975
|
+
# Nom de colonne et liste de mailles potentielles à utiliser
|
976
|
+
# pour la répartition
|
977
|
+
|
978
|
+
name_subwatershed, river = name_subwatershed_river
|
979
|
+
|
980
|
+
list_rivers, used_reaches = self.lists_part[river], self._searching[river].downstream_reaches
|
981
|
+
|
982
|
+
# sous-bassin
|
983
|
+
subbasin = self.watershed.get_subwatershed(name_subwatershed)
|
984
|
+
|
985
|
+
# Récupération des surfaces à utiliser pour l'injection en long
|
986
|
+
unique_areas, lists = list_rivers
|
987
|
+
unique_areas.sort()
|
988
|
+
|
989
|
+
# traitement des injections locales si existantes dans le ss-bv
|
990
|
+
# -------------------------------------------------------------
|
991
|
+
|
992
|
+
# Recherche des zones d'injection locales dans le sous-bassin virtuel uniquement
|
993
|
+
local_injections = subbasin.filter_zones(self._locale_injection_zones, force_virtual_if_any=True)
|
994
|
+
|
995
|
+
# surfaces traitées par les injections ponctuelles
|
996
|
+
local_areas = 0.
|
997
|
+
# liste contenant la maille de connection au réseau et la maille d'injection locale
|
998
|
+
to_remove:list[tuple[Node_Watershed, Node_Watershed, float]] = []
|
999
|
+
|
1000
|
+
for cur_locinj in local_injections:
|
1001
|
+
|
1002
|
+
# Recherche du noeud rivière le plus proche de la zone d'injection locale
|
1003
|
+
dist, node_local_injection = self.river_system.get_nearest_nodes(cur_locinj)
|
1004
|
+
|
1005
|
+
if node_local_injection.reach not in used_reaches:
|
1006
|
+
# Recherche de la maille rivière en aval
|
1007
|
+
# qui fait partie de la distribution en long
|
1008
|
+
down = self.river_system.go_downstream_until_reach_found(node_local_injection, used_reaches)
|
1009
|
+
else:
|
1010
|
+
# La zone d'injection ponctuelle est sur un des biefs à traiter
|
1011
|
+
# On cherche la première maille sur laquelle une distribution en long se fera
|
1012
|
+
down = node_local_injection
|
1013
|
+
while down is not None and down.uparea not in unique_areas:
|
1014
|
+
down = down.down
|
1015
|
+
|
1016
|
+
|
1017
|
+
# surface su sous-bassin qui sera injectée localement
|
1018
|
+
local_area = node_local_injection.uparea - subbasin.get_area_outside_sub_if_exists(node_local_injection, node_local_injection.get_up_reaches_same_sub())
|
1019
|
+
|
1020
|
+
# on retient la maille aval et la maille d'injection locale
|
1021
|
+
to_remove.append((down, node_local_injection, local_area))
|
1022
|
+
|
1023
|
+
local_areas += local_area
|
1024
|
+
|
1025
|
+
self._add_local_injecton(cur_locinj, InjectionType.PARTIAL if not subbasin._is_virtual else InjectionType.VIRTUAL, name_subwatershed, local_area / subbasin.area, 0.)
|
1026
|
+
|
1027
|
+
# Incrément de BV à traiter pour l'injection en long
|
1028
|
+
# c-à-d la différence entre la surface drainée du ss-bv et la somme des surfaces locales
|
1029
|
+
delta_area = subbasin.area - local_areas
|
1030
|
+
|
1031
|
+
# aire drainée en aval du sous-bassin
|
1032
|
+
area_max = subbasin.outlet.uparea
|
1033
|
+
|
1034
|
+
# aire drainée à la limite amont du ss-bassin, le long de la distribution en long
|
1035
|
+
up_node = subbasin.get_up_rivernode_outside_sub(subbasin.outlet, used_reaches)
|
1036
|
+
|
1037
|
+
if up_node is None:
|
1038
|
+
starting_node = subbasin.get_list_nodes_river(min(used_reaches))[-1]
|
1039
|
+
area_min = subbasin.get_area_outside_sub_if_exists(starting_node, starting_node.get_up_reaches_same_sub())
|
1040
|
+
else:
|
1041
|
+
area_min = up_node.uparea
|
1042
|
+
|
1043
|
+
# On ne garde que la fraction utile des surfaces à traiter pour le ss-bv
|
1044
|
+
unique_areas = unique_areas[(unique_areas >= area_min) & (unique_areas<=area_max)]
|
1045
|
+
|
1046
|
+
if unique_areas[0] != area_min:
|
1047
|
+
unique_areas = np.insert(unique_areas, 0, area_min)
|
1048
|
+
|
1049
|
+
frac_sum=0.
|
1050
|
+
|
1051
|
+
def area_to_remove(node:Node_Watershed) -> float:
|
1052
|
+
|
1053
|
+
uparea = 0.
|
1054
|
+
|
1055
|
+
# injections locales
|
1056
|
+
for node_down, node_inj, loc_area in to_remove:
|
1057
|
+
if node == node_down:
|
1058
|
+
if node_inj.reach in used_reaches:
|
1059
|
+
uparea += loc_area
|
1060
|
+
else:
|
1061
|
+
uparea += node_inj.uparea
|
1062
|
+
|
1063
|
+
for node_up in node.upriver:
|
1064
|
+
if node_up.sub == node.sub:
|
1065
|
+
if not subbasin.is_in_rivers(node_up):
|
1066
|
+
uparea += node_up.uparea
|
1067
|
+
|
1068
|
+
return uparea
|
1069
|
+
|
1070
|
+
for idx in range(1,len(unique_areas)-1):
|
1071
|
+
|
1072
|
+
up_node = subbasin.get_river_nodes_from_upareas(unique_areas[idx], unique_areas[idx])[0]
|
1073
|
+
delta_loc = unique_areas[idx] - unique_areas[idx-1] - area_to_remove(up_node)
|
1074
|
+
|
1075
|
+
fraction_loc = delta_loc / delta_area
|
1076
|
+
|
1077
|
+
if fraction_loc > 0.:
|
1078
|
+
|
1079
|
+
self._add_along_injection(lists[unique_areas[idx]],
|
1080
|
+
InjectionType.PARTIAL if not subbasin._is_virtual else InjectionType.VIRTUAL,
|
1081
|
+
name_subwatershed,
|
1082
|
+
fraction_loc,
|
1083
|
+
lag =0.)
|
1084
|
+
|
1085
|
+
frac_sum += fraction_loc
|
1086
|
+
elif fraction_loc < 0.:
|
1087
|
+
logging.error(f"Bad fraction {fraction_loc} " + name_subwatershed)
|
1088
|
+
|
1089
|
+
delta_loc = area_max - unique_areas[-2] - area_to_remove(subbasin.outlet)
|
1090
|
+
|
1091
|
+
fraction_loc = delta_loc / delta_area
|
1092
|
+
|
1093
|
+
self._add_along_injection(lists[unique_areas[-1]],
|
1094
|
+
InjectionType.PARTIAL if not subbasin._is_virtual else InjectionType.VIRTUAL,
|
1095
|
+
name_subwatershed,
|
1096
|
+
fraction_loc,
|
1097
|
+
lag =0.)
|
1098
|
+
|
1099
|
+
frac_sum += fraction_loc
|
1100
|
+
|
1101
|
+
if frac_sum > 1.001 or frac_sum < 0.999:
|
1102
|
+
logging.error(f"Bad sum of fractions {frac_sum} " + name_subwatershed)
|
1103
|
+
|
1104
|
+
|
1105
|
+
def create_hydrographs(self):
|
1106
|
+
""" Création des hydrogrammes
|
1107
|
+
|
1108
|
+
Les étapes précédentes ont ajouté à la liste "infiltrations" les éléments suivants:
|
1109
|
+
|
1110
|
+
- l'index de la zone d'infiltration (1-based)
|
1111
|
+
- l'hydrogramme de référence
|
1112
|
+
- la fecteur pondérateur
|
1113
|
+
- le temps de déphasage
|
1114
|
+
|
1115
|
+
Une zone peut contenir plusieurs apports.
|
1116
|
+
|
1117
|
+
Il faut donc parcourir l'ensemble des zones et sommer les contributions.
|
1118
|
+
|
1119
|
+
Le fichier final est ordonné comme la matrice d'infiltration.
|
1120
|
+
|
1121
|
+
Avant de sommer, il faut tout d'abord créer les hydrogrammes associés au BV virtuels (décomposition d'un BV, modélisé comme un tout, en plusieurs rivières distinctes pour la répartition en long)
|
1122
|
+
"""
|
1123
|
+
|
1124
|
+
dt = self.hydrographs_total.index[1] - self.hydrographs_total.index[0]
|
1125
|
+
|
1126
|
+
# Création des hydrogrammes virtuels
|
1127
|
+
# Un hydrograme virtuel ne peut être créé que par des hydrogrammes partiels ou virtuels
|
1128
|
+
df_virtual = pd.DataFrame()
|
1129
|
+
df_virtual.index.name = 'Time'
|
1130
|
+
df_virtual.index = self.hydrographs_total.index
|
1131
|
+
|
1132
|
+
for cur_vrt in self._hydrographs_virtual:
|
1133
|
+
name, src_hydrograph_name, factor, lag = cur_vrt
|
1134
|
+
|
1135
|
+
decal = int(lag / dt)
|
1136
|
+
|
1137
|
+
if src_hydrograph_name in self.hydrographs_local.columns:
|
1138
|
+
df_virtual[name] = self.hydrographs_local.shift(decal, fill_value=0.)[src_hydrograph_name] * factor
|
1139
|
+
|
1140
|
+
elif src_hydrograph_name in df_virtual.columns:
|
1141
|
+
df_virtual[name] = df_virtual.shift(decal, fill_value=0.)[src_hydrograph_name] * factor
|
1142
|
+
|
1143
|
+
df_2d_dict = {}
|
1144
|
+
df_2d_dict['Time'] = self.hydrographs_total.index
|
1145
|
+
|
1146
|
+
nb_infil = max([cur.index for cur in self.infiltrations])
|
1147
|
+
|
1148
|
+
counter = np.asarray([cur.index for cur in self.infiltrations])
|
1149
|
+
counter = [np.count_nonzero(counter == cur) for cur in range(1, nb_infil+1)]
|
1150
|
+
|
1151
|
+
# FIXME : pas optimisé
|
1152
|
+
for i in range(1, nb_infil+1):
|
1153
|
+
# Bouclage sur les zones d'infiltation
|
1154
|
+
|
1155
|
+
loc_count = 0
|
1156
|
+
|
1157
|
+
for cur_infil in self.infiltrations:
|
1158
|
+
# Bouclage sur les infiltrations
|
1159
|
+
# En effet, plusieurs hydrogrammes peuvent être associés à une même zone
|
1160
|
+
|
1161
|
+
idx, type_name, col_name, factor, lag = cur_infil.index, cur_infil.type, cur_infil.colref, cur_infil.factor, cur_infil.lagtime
|
1162
|
+
|
1163
|
+
if idx == i:
|
1164
|
+
|
1165
|
+
decal = int(lag / dt)
|
1166
|
+
loc_count += 1
|
1167
|
+
|
1168
|
+
if type_name == InjectionType.GLOBAL:
|
1169
|
+
|
1170
|
+
if loc_count == 1:
|
1171
|
+
df_2d_dict[idx] = self.hydrographs_total.shift(decal, fill_value = 0.)[col_name] * factor
|
1172
|
+
else:
|
1173
|
+
df_2d_dict[idx] += self.hydrographs_total.shift(decal, fill_value = 0.)[col_name] * factor
|
1174
|
+
|
1175
|
+
elif type_name == InjectionType.PARTIAL:
|
1176
|
+
|
1177
|
+
if loc_count == 1:
|
1178
|
+
df_2d_dict[idx] = self.hydrographs_local.shift(decal, fill_value = 0.)[col_name] * factor
|
1179
|
+
else:
|
1180
|
+
df_2d_dict[idx] += self.hydrographs_local.shift(decal, fill_value = 0.)[col_name] * factor
|
1181
|
+
|
1182
|
+
elif type_name == InjectionType.ANTHROPOGENIC:
|
1183
|
+
|
1184
|
+
if loc_count == 1:
|
1185
|
+
df_2d_dict[idx] = self.hydrographs_total.shift(decal, fill_value = 0.)[col_name] * factor
|
1186
|
+
else:
|
1187
|
+
df_2d_dict[idx] += self.hydrographs_total.shift(decal, fill_value = 0.)[col_name] * factor
|
1188
|
+
|
1189
|
+
elif type_name == InjectionType.VIRTUAL:
|
1190
|
+
|
1191
|
+
if loc_count == 1:
|
1192
|
+
df_2d_dict[idx] = df_virtual.shift(decal, fill_value = 0.)[col_name] * factor
|
1193
|
+
else:
|
1194
|
+
df_2d_dict[idx] += df_virtual.shift(decal, fill_value = 0.)[col_name] * factor
|
1195
|
+
|
1196
|
+
elif type_name == InjectionType.CONSTANT:
|
1197
|
+
|
1198
|
+
if loc_count == 1:
|
1199
|
+
df_2d_dict[idx] = np.asarray([col_name]*len(df_2d_dict[('Time')])) * factor
|
1200
|
+
else:
|
1201
|
+
df_2d_dict[idx] += col_name * factor
|
1202
|
+
|
1203
|
+
else:
|
1204
|
+
logging.error(f"Unknown type {type_name}")
|
1205
|
+
|
1206
|
+
if loc_count != counter[i-1]:
|
1207
|
+
logging.error(f"Bad count for {i}")
|
1208
|
+
|
1209
|
+
self.df_2d = pd.DataFrame(df_2d_dict)
|
1210
|
+
self.df_2d.set_index('Time', inplace=True)
|
1211
|
+
|
1212
|
+
|
1213
|
+
def save_hydrographs(self, dirout:Path, name:str):
|
1214
|
+
""" Write the hydrographs"""
|
1215
|
+
|
1216
|
+
with open(dirout / (name + f'_infiltration_zones.txt'), 'w') as f:
|
1217
|
+
f.write("Zone\tType\tColonne\tFacteur\tLag\n")
|
1218
|
+
for cur in self.infiltrations:
|
1219
|
+
idx, type_name, col_name, factor, lag = cur.index, cur.type.value, cur.colref, cur.factor, cur.lagtime
|
1220
|
+
f.write(f"{idx}\t{type_name}\t{col_name}\t{factor}\t{lag}\n")
|
1221
|
+
|
1222
|
+
if self.df_2d is None:
|
1223
|
+
logging.error("No hydrographs created")
|
1224
|
+
return
|
1225
|
+
|
1226
|
+
self.df_2d.to_csv(dirout / name, sep='\t', decimal='.', encoding='latin1')
|