wolfhece 2.1.13__py3-none-any.whl → 2.1.14__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.
@@ -6,9 +6,11 @@ from matplotlib.axes import Axes
6
6
  import matplotlib.pyplot as plt
7
7
  import logging
8
8
  from tqdm import tqdm
9
+ import numpy as np
9
10
 
10
11
  from ..PyTranslate import _
11
12
  from ..PyVertex import wolfvertex, cloud_vertices
13
+ from ..PyVertexvectors import Zones, zone, vector
12
14
  from ..wolf_array import *
13
15
  from ..PyCrosssections import crosssections as CrossSections
14
16
  from ..GraphNotebook import PlotNotebook
@@ -57,6 +59,11 @@ class Node_Watershed:
57
59
 
58
60
  flatindex:int = -1 # index de la zone de plat
59
61
 
62
+ def __init__(self):
63
+ self.cums=0.
64
+ self.up=None
65
+ self.down=None
66
+
60
67
  def incr_curvi(self):
61
68
  """Incrémentation de la longueur curviligne"""
62
69
 
@@ -64,8 +71,7 @@ class Node_Watershed:
64
71
  self.cums=0.
65
72
  else:
66
73
  self.cums = self.down.cums+self.incrs
67
-
68
- for curup in self.up:
74
+ for curup in self.up:
69
75
  curup.incr_curvi()
70
76
 
71
77
  def mean_slope_up(self, threshold:float)-> float:
@@ -119,6 +125,95 @@ class Node_Watershed:
119
125
  """
120
126
  self.strahler = strahler
121
127
 
128
+ def distance(self, x:float, y:float) -> float:
129
+ """ Distance euclidienne """
130
+
131
+ return np.sqrt(pow(self.x-x,2)+pow(self.y-y,2))
132
+
133
+ def get_up_nodes_same_sub(self, excluded_node:list["Node_Watershed"]=[]):
134
+ """
135
+ Get all upstream nodes in the same sub-basin
136
+ """
137
+
138
+ all_up = [self]
139
+ all_rivers = [self] if self.river else []
140
+ all_runoff = [] if self.river else [self]
141
+
142
+ for curup in self.up:
143
+
144
+ if curup in excluded_node:
145
+ continue
146
+
147
+ added = False
148
+ if curup.sub == self.sub:
149
+ all_up.append(curup)
150
+ added = True
151
+
152
+ if curup.river:
153
+ all_rivers.append(curup)
154
+ else:
155
+ all_runoff.append(curup)
156
+
157
+ if added:
158
+ up, river, runoff = curup.get_up_nodes_same_sub(excluded_node)
159
+ all_up.extend(up)
160
+ all_rivers.extend(river)
161
+ all_runoff.extend(runoff)
162
+
163
+ return all_up, all_rivers, all_runoff
164
+
165
+ def get_up_runoff_nodes_same_sub(self):
166
+ """
167
+ Get all upstream runoff nodes in the same sub-basin
168
+ """
169
+
170
+ all_up = []
171
+ for curup in self.up:
172
+ if curup.sub == self.sub and not curup.river:
173
+ all_up.append(curup)
174
+
175
+ all_up += curup.get_up_runoff_nodes_same_sub()
176
+
177
+ return all_up
178
+
179
+ def get_up_rivernodes_same_sub(self):
180
+ """
181
+ Get all upstream river nodes in the same sub-basin
182
+ """
183
+
184
+ all_up = []
185
+ for curup in self.upriver:
186
+ if curup.sub == self.sub:
187
+ all_up.append(curup)
188
+
189
+ all_up += curup.get_up_rivernodes_same_sub()
190
+
191
+ return all_up
192
+
193
+ def get_up_reaches_same_sub(self) -> list[int]:
194
+ """
195
+ Get all upstream reaches in the same sub-basin
196
+ """
197
+
198
+ all_up = [self.reach]
199
+ for curup in self.upriver:
200
+ if curup.sub == self.sub:
201
+ all_up += curup.get_up_reaches_same_sub()
202
+
203
+ return np.unique(all_up).tolist()
204
+
205
+ def get_down_reaches_same_sub(self) -> list[int]:
206
+ """
207
+ Get all downstream reaches in the same sub-basin
208
+ """
209
+
210
+ all_down = [self.reach]
211
+ if self.down is not None:
212
+ if self.down.sub == self.sub:
213
+ all_down += self.down.get_down_reaches_same_sub()
214
+
215
+ return np.unique(all_down).tolist()
216
+
122
217
 
123
218
  class RiverSystem:
124
219
  """
@@ -129,8 +224,10 @@ class RiverSystem:
129
224
  # reaches
130
225
  # |__['reaches']
131
226
  # | |__[idx]
132
- # | |__['upstream']
133
- # | |__['baselist']
227
+ # | |__['upstream'] # all reaches in upstream
228
+ # | |__['baselist'] # list of nodes in the reach
229
+ # | |__['up'] # **if upstream** node in upstream
230
+ # | |__['fromuptodown'] # **if upstream** list of nodes from upstream to downstream
134
231
  # |__['indexed']
135
232
  # |__['strahler']
136
233
 
@@ -205,7 +302,7 @@ class RiverSystem:
205
302
  xy = [[curnode.x, curnode.y] for curnode in nodes]
206
303
  self.kdtree = KDTree(xy)
207
304
 
208
- def get_nearest_nodes(self, xy:np.ndarray, nb=int) -> tuple[np.ndarray, list[Node_Watershed]]:
305
+ def get_nearest_nodes(self, xy:np.ndarray | vector, nb:int = 1) -> tuple[np.ndarray | float, list[Node_Watershed] | Node_Watershed]:
209
306
  """
210
307
  Return the distance and the nearest Node_Watershed
211
308
 
@@ -214,10 +311,105 @@ class RiverSystem:
214
311
 
215
312
  return
216
313
  """
314
+
315
+ if isinstance(xy, vector):
316
+ centroid = xy.asshapely_pol().centroid
317
+ xy = np.array([[centroid.x, centroid.y]])
318
+
217
319
  dd, ii = self.kdtree.query(xy, nb)
218
320
 
219
- return dd, [self.all_nodes[curi] for curi in ii]
321
+ if isinstance(ii, int | np.int64):
322
+ return dd, self.all_nodes[ii]
323
+ elif isinstance(ii, np.ndarray | list):
324
+ if len(ii) == 1:
325
+ return dd[0], self.all_nodes[ii[0]]
326
+ else:
327
+ return dd, [self.all_nodes[curi] for curi in ii]
328
+
329
+ def get_nodes_in_reaches(self, reaches:list[int])->list[Node_Watershed]:
330
+ """
331
+ Get nodes in a reaches
332
+ """
333
+ all_nodes = []
334
+ for cur_reach in reaches:
335
+ all_nodes.extend(self.reaches['reaches'][cur_reach]['baselist'])
336
+
337
+ return all_nodes
338
+
339
+ def get_downstream_node_in_reach(self, reach:int)->Node_Watershed:
340
+ """
341
+ Get downstream node in a reach
342
+ """
343
+
344
+ return self.reaches['reaches'][reach]['baselist'][0]
345
+
346
+ def get_upstream_node_in_reach(self, reach:int)->Node_Watershed:
347
+ """
348
+ Get upstream node in a reach
349
+ """
350
+
351
+ return self.reaches['reaches'][reach]['baselist'][-1]
352
+
353
+ def get_downstream_reaches(self, node:Node_Watershed)->list[int]:
354
+ """
355
+ Get index of downstream reaches
356
+ """
220
357
 
358
+ curnode = node
359
+ downreaches = []
360
+ while not curnode is None:
361
+ downreaches.append(curnode.reach)
362
+ curnode = curnode.down
363
+
364
+ return list(np.unique(downreaches))
365
+
366
+ def get_kdtree_downstream(self, node:Node_Watershed)-> tuple[list[Node_Watershed], KDTree]:
367
+ """
368
+ Get KDTree of downstream reaches
369
+ """
370
+
371
+ downreaches = self.get_downstream_reaches(node)
372
+ return self.get_kdtree_from_reaches(downreaches)
373
+
374
+ def get_kdtree_from_reaches(self, reaches:list[int])->tuple[list[Node_Watershed], KDTree]:
375
+ """
376
+ Get KDTree from a list of reaches
377
+ """
378
+
379
+ nodes = self.get_nodes_in_reaches(reaches)
380
+ xy = [[curnode.x, curnode.y] for curnode in nodes]
381
+ return nodes, KDTree(xy)
382
+
383
+ def get_downstream_reaches_excluded(self, node:Node_Watershed, excluded:list[int])->list[int]:
384
+ """
385
+ Get index of downstream reaches, excepted the excluded ones
386
+ """
387
+
388
+ list_reaches = self.get_downstream_reaches(node)
389
+
390
+ for cur in excluded:
391
+ if cur in list_reaches:
392
+ list_reaches.remove(cur)
393
+
394
+ return list_reaches
395
+
396
+ def go_downstream_until_reach_found(self, node:Node_Watershed, reach:int | list[int])->Node_Watershed:
397
+ """ Go downstream until a reach is found """
398
+
399
+ curnode = node
400
+ if isinstance(reach, int):
401
+ while not curnode is None:
402
+ if curnode.reach == reach:
403
+ break
404
+ curnode = curnode.down
405
+ elif isinstance(reach, list):
406
+ while not curnode is None:
407
+ if curnode.reach in reach:
408
+ break
409
+ curnode = curnode.down
410
+
411
+ return curnode
412
+
221
413
  def get_cums(self, whichreach:int=None, whichup:int=None):
222
414
  """
223
415
  Récupération de la position curvi
@@ -298,6 +490,25 @@ class RiverSystem:
298
490
 
299
491
  return slope
300
492
 
493
+ def get_upstreams_coords(self):
494
+ """
495
+ Récupération des coordonnées des amonts
496
+ """
497
+
498
+ xy = [[curnode.x, curnode.y] for curnode in self.upstreams['list']]
499
+ return np.array(xy)
500
+
501
+ def get_nearest_upstream(self, xy:np.ndarray, nb:int) -> tuple[np.ndarray, list[Node_Watershed]]:
502
+ """
503
+ Recherche des amonts les plus proches
504
+ """
505
+
506
+ xy_up = self.get_upstreams_coords()
507
+ loc_kd = KDTree(xy_up)
508
+ dd, ii =loc_kd.query(xy, nb)
509
+
510
+ return dd, [self.upstreams['list'][curi] for curi in ii]
511
+
301
512
  def create_index(self):
302
513
  """
303
514
  Incrément d'index depuis l'amont jusque l'exutoire final
@@ -1132,19 +1343,222 @@ class SubWatershed:
1132
1343
  runoff:list[Node_Watershed],
1133
1344
  rivers:list[Node_Watershed]) -> None:
1134
1345
 
1135
- self.parent = parent
1136
- self.index = idx
1137
- self.name = name
1138
- self.mask = mask
1139
- self.nodes = nodes
1140
- self.rivers = rivers
1141
- self.runoff = runoff
1346
+ self.parent:"Watershed" = parent
1347
+ self.index:int = idx # index of subwatershed - **NOT** sorted like in the array
1348
+ self.name:str = name # name of the subwatershed
1349
+ self.mask:WolfArray = mask # WolfArray of the subwatershed -- All nodes are masked except the subwatershed
1350
+ self.mask.count()
1351
+
1352
+ self.nodes:list[Node_Watershed] = nodes # all nodes in the subwatershed
1353
+ self.rivers:list[Node_Watershed] = rivers # only rivers - sorted by dem value --> outlet is the first one
1354
+ self.runoff:list[Node_Watershed] = runoff
1355
+
1142
1356
  self.idx_reaches = np.unique(np.asarray([x.reach for x in rivers]))
1143
1357
 
1358
+ self._index_sorted = idx
1359
+
1360
+ self._is_virtual:bool = False
1361
+ self._src_sub:"SubWatershed" = None
1362
+
1363
+ @property
1364
+ def is_virtual(self) -> bool:
1365
+ """ Vérification si le sous-bassin est virtuel """
1366
+
1367
+ return self._is_virtual
1368
+
1144
1369
  @property
1145
1370
  def surface(self) -> float:
1371
+ """ Surface du bassin versant en m² """
1146
1372
  return self.mask.nbnotnull * self.mask.dx * self.mask.dy
1373
+
1374
+ @property
1375
+ def area(self) -> float:
1376
+ """ Surface du bassin versant en km² """
1377
+ return self.surface / 1.e6
1378
+
1379
+ @property
1380
+ def area_outlet(self) -> float:
1381
+ """ Surface du bassin à l'exutoire """
1382
+
1383
+ return self.outlet.uparea
1384
+
1385
+ @property
1386
+ def outlet(self) -> Node_Watershed:
1387
+ """ Outlet of the subbasin """
1388
+
1389
+ return self.rivers[0]
1390
+
1391
+ def is_reach_in_sub(self, idx_reach:int) -> bool:
1392
+ """ Vérification si un bief est dans le sous-bassin """
1393
+
1394
+ return idx_reach in self.idx_reaches
1395
+
1396
+ def is_in_rivers(self, node:Node_Watershed) -> bool:
1397
+ """ Vérification si un noeud est dans les rivières """
1398
+
1399
+ return node in self.rivers
1400
+
1401
+ def get_list_nodes_river(self, idx_reach:int) -> list[Node_Watershed]:
1402
+ """
1403
+ Récupération des noeuds d'une rivière
1404
+ """
1405
+
1406
+ return [x for x in self.rivers if x.reach==idx_reach]
1407
+
1408
+ def get_nearest_river(self, x, y) -> Node_Watershed:
1409
+ """
1410
+ Récupération du noeud de rivière le plus proche
1411
+ """
1412
+
1413
+ return min(self.rivers, key=lambda x: x.distance(x,y))
1414
+
1415
+ def get_max_area_in_reach(self, idx_reach:int) -> float:
1416
+ """
1417
+ Récupération de la surface maximale dans un bief
1418
+ """
1419
+
1420
+ return max([x.uparea for x in self.get_list_nodes_river(idx_reach)])
1421
+
1422
+ def get_min_area_in_reach(self, idx_reach:int) -> float:
1423
+ """
1424
+ Récupération de la surface minimale dans un bief
1425
+ """
1426
+
1427
+ return min([x.uparea for x in self.get_list_nodes_river(idx_reach)])
1428
+
1429
+ def get_min_area_along_reaches(self, reaches:list[int], starting_node:Node_Watershed = None) -> float:
1430
+ """ Aire drainée à la limite amont du ss-bassin """
1431
+
1432
+ if starting_node is None:
1433
+ starting_node = self.outlet
1434
+
1435
+ upriver = starting_node.upriver
1436
+
1437
+ area_min = []
1438
+
1439
+ idx_reach = 0
1440
+ for curnode in upriver:
1441
+ if curnode.reach in reaches:
1442
+ idx_reach = curnode.reach
1443
+ area_min.append(self.get_min_area_in_reach(idx_reach))
1444
+
1445
+ if len(area_min) == 0:
1446
+ return None
1447
+ else:
1448
+ return min(area_min)
1449
+
1450
+ def get_up_rivernode_outside_sub(self, starting_node:Node_Watershed, reaches:list[int]) -> Node_Watershed:
1451
+ """ Récupération du noeud de rivière en amont du sous-bassin """
1452
+
1453
+ def up_in_reaches(node:Node_Watershed, reaches:list[int]) -> Node_Watershed:
1454
+
1455
+ if len(node.upriver) == 0:
1456
+ # No upstream node
1457
+ return node
1458
+ else:
1459
+ # Iterate over upriver nodes
1460
+ for curup in node.upriver:
1461
+ if curup.reach in reaches:
1462
+ # If the reach is in the list, return the node
1463
+ return curup
1464
+
1465
+ # No node found in the list of reaches
1466
+ return node
1467
+
1468
+ if self._is_virtual:
1469
+ return self._src_sub.get_up_rivernode_outside_sub(starting_node, reaches)
1470
+
1471
+ up = up_in_reaches(starting_node, reaches)
1472
+ loc_up = None
1473
+ while up is not starting_node and up.sub == starting_node.sub and up is not loc_up:
1474
+ # bouclage parfois utile en fonction de la superposition ou non du tracé
1475
+ # vectoriel de lit mineur vis-à-vis de la discrétsation hydrologique
1476
+ loc_up = up
1477
+ up = up_in_reaches(up, reaches)
1478
+
1479
+ return up if up is not loc_up else None
1480
+
1481
+ def get_area_outside_sub_if_exists(self, starting_node:Node_Watershed, reaches:list[int]) -> float:
1482
+ """ Aire drainée en amont du sous-bassin """
1483
+
1484
+ up_outside = self.get_up_rivernode_outside_sub(starting_node, reaches)
1485
+
1486
+ if up_outside is None:
1487
+ return 0.
1488
+ else:
1489
+ return up_outside.uparea
1490
+
1491
+ def get_river_nodes_from_upareas(self, min_area:float, max_area:float) -> list[Node_Watershed]:
1492
+ """
1493
+ Récupération des noeuds de rivière entre deux surfacesde BV.
1494
+
1495
+ Les surfaces sont exprimées en km².
1496
+
1497
+ Les bornes sont incluses.
1498
+
1499
+ :param min_area: surface minimale
1500
+ :param max_area: surface maximale
1501
+ """
1147
1502
 
1503
+ return [x for x in self.rivers if x.uparea>=min_area and x.uparea<=max_area]
1504
+
1505
+ def is_in_subwatershed(self, vec:vector) -> bool:
1506
+ """ Vérification si un vecteur est dans le sous-bassin """
1507
+
1508
+ centroid = vec.asshapely_pol().centroid
1509
+
1510
+ i, j = self.mask.get_ij_from_xy(centroid.x, centroid.y)
1511
+
1512
+ return self.mask.array.mask[i,j] == False
1513
+
1514
+ def filter_zones(self, zones_to_filter:Zones, force_virtual_if_any:bool = False) -> list[vector]:
1515
+ """
1516
+ Filtrage des zones pour ne garder que celle se trouvant dans le sous-bassin
1517
+ """
1518
+
1519
+ if self._is_virtual and not force_virtual_if_any:
1520
+ return self._src_sub.filter_zones(zones_to_filter)
1521
+
1522
+ return [curvec for curzone in zones_to_filter.myzones for curvec in curzone.myvectors if self.is_in_subwatershed(curvec)]
1523
+
1524
+ def get_virtual_subwatershed(self, outlet:Node_Watershed, excluded_nodes:list[Node_Watershed] = []) -> "SubWatershed":
1525
+ """
1526
+ Création d'un sous-bassin virtuel sur base d'une maille rivière aval
1527
+ """
1528
+
1529
+ if not outlet.river:
1530
+ logging.error(_('The outlet should be a river node'))
1531
+ return None
1532
+
1533
+ mymask = WolfArray(mold = self.mask)
1534
+ mymask.array.mask[:,:] = True
1535
+
1536
+ all, river, runoff = outlet.get_up_nodes_same_sub(excluded_nodes)
1537
+
1538
+ for curnode in all:
1539
+ mymask.array.mask[curnode.i,curnode.j] = False
1540
+
1541
+ newsub = SubWatershed(self.parent,
1542
+ self.name + '_virtual',
1543
+ self.parent.nb_subs + 1,
1544
+ mymask,
1545
+ all,
1546
+ runoff,
1547
+ river,
1548
+ )
1549
+
1550
+ newsub._is_virtual = True
1551
+ newsub._src_sub = self
1552
+
1553
+ return newsub
1554
+
1555
+ def get_downstream_node_in_reach(self, reach:int) -> Node_Watershed:
1556
+ """
1557
+ Récupération du noeud aval dans un bief
1558
+ """
1559
+
1560
+ # rivers are sorted by dem value, so the first one is the outlet
1561
+ return self.get_list_nodes_river(reach)[0]
1148
1562
  class Watershed:
1149
1563
  """Classe bassin versant"""
1150
1564
 
@@ -1164,6 +1578,7 @@ class Watershed:
1164
1578
  couplednodes:list # forced exchanges
1165
1579
 
1166
1580
  subcatchments: list[SubWatershed]
1581
+ virtualcatchments : list[SubWatershed]
1167
1582
  statisticss: dict
1168
1583
 
1169
1584
  couplednodesxy:list[float,float,float,float]
@@ -1188,6 +1603,8 @@ class Watershed:
1188
1603
  dir_mnt_subpixels:str=None,
1189
1604
  *args, **kwargs):
1190
1605
 
1606
+ self.virtualcatchments = []
1607
+
1191
1608
  logging.info(_('Read files...'))
1192
1609
 
1193
1610
  self.dir=os.path.normpath(dir)
@@ -1209,7 +1626,11 @@ class Watershed:
1209
1626
 
1210
1627
  if lines[0]=='COORDINATES':
1211
1628
  for xy in enumerate(lines[1:]):
1212
- xup,yup,xdown,ydown=xy[1].split('\t')
1629
+ xy_split = xy[1].split('\t')
1630
+ if len(xy_split)==4:
1631
+ xup,yup,xdown,ydown=xy_split
1632
+ else:
1633
+ xup,yup,xdown,ydown=xy_split[:4]
1213
1634
  self.couplednodesxy.append([float(xup),float(yup),float(xdown),float(ydown)])
1214
1635
  self.couplednodesij.append([self.subs_array.get_ij_from_xy(float(xup),float(yup)),self.subs_array.get_ij_from_xy(float(xdown),float(ydown))])
1215
1636
 
@@ -1258,6 +1679,71 @@ class Watershed:
1258
1679
  @property
1259
1680
  def resolution(self):
1260
1681
  return self.header.dx
1682
+
1683
+ # def impose_sorted_index_subbasins(self, new_idx=list[int]):
1684
+ # """
1685
+ # Tri des sous-bassins
1686
+ # """
1687
+
1688
+ # for cursub in self.subcatchments:
1689
+ # cursub._index_sorted = new_idx[cursub.index]
1690
+
1691
+ def set_names_subbasins(self, new_names:list[tuple[int,str]]):
1692
+ """
1693
+ Renommage des sous-bassins
1694
+ """
1695
+
1696
+ for cursub, curname in new_names:
1697
+ self.get_subwatershed(cursub).name = curname
1698
+
1699
+ def add_virtual_subwatershed(self, subwater:SubWatershed):
1700
+ """
1701
+ Ajout d'un sous-bassin virtuel
1702
+ """
1703
+
1704
+ self.virtualcatchments.append(subwater)
1705
+
1706
+ subwater.name += str(len(self.virtualcatchments))
1707
+
1708
+ def create_virtual_subwatershed(self, outlet:Node_Watershed, excluded_nodes:list[Node_Watershed] = []):
1709
+ """
1710
+ Création d'un sous-bassin virtuel
1711
+ """
1712
+
1713
+ newsub = self.get_subwatershed(outlet.sub).get_virtual_subwatershed(outlet, excluded_nodes=excluded_nodes)
1714
+
1715
+ self.add_virtual_subwatershed(newsub)
1716
+
1717
+ return newsub
1718
+
1719
+ def get_subwatershed(self, idx_sorted_or_name:int | str) -> SubWatershed:
1720
+ """
1721
+ Récupération d'un sous-bassin sur base de l'index trié
1722
+ """
1723
+
1724
+ if isinstance(idx_sorted_or_name, str):
1725
+ for cur_sub in self.subcatchments:
1726
+ if cur_sub.name == idx_sorted_or_name:
1727
+ return cur_sub
1728
+
1729
+ if len(self.virtualcatchments)>0:
1730
+ for cur_sub in self.virtualcatchments:
1731
+ if cur_sub.name == idx_sorted_or_name:
1732
+ return cur_sub
1733
+
1734
+ elif isinstance(idx_sorted_or_name, int):
1735
+ for cur_sub in self.subcatchments:
1736
+ if cur_sub._index_sorted+1 == idx_sorted_or_name:
1737
+ return cur_sub
1738
+
1739
+ if len(self.virtualcatchments)>0:
1740
+ for cur_sub in self.virtualcatchments:
1741
+ if cur_sub._index_sorted+1 == idx_sorted_or_name:
1742
+ return cur_sub
1743
+ else:
1744
+ logging.error(_('Index must be an integer or a string!'))
1745
+
1746
+ return None
1261
1747
 
1262
1748
  def get_node_from_ij(self, i:int,j:int):
1263
1749
  """
@@ -1498,6 +1984,8 @@ class Watershed:
1498
1984
 
1499
1985
  self.rivers=list(filter(lambda x: x.river,self.nodes))
1500
1986
  self.rivers.sort(key=lambda x: x.dem['dem_after_corr'])
1987
+
1988
+ # FIXME : Caution the following iterative function can induce a RecursionError in Debug for bigger systems
1501
1989
  sys.setrecursionlimit(len(self.nodes))
1502
1990
  self.outlet.incr_curvi()
1503
1991
 
@@ -1980,3 +2468,53 @@ class Watershed:
1980
2468
  cur_node.time = wolf_time[cur_node.i, cur_node.j]
1981
2469
 
1982
2470
  self.to_update_times = False
2471
+
2472
+ def get_subwatershed_from_ij(self, i:int, j:int) -> SubWatershed:
2473
+ """
2474
+ Récupération d'un sous-bassin sur base des indices (i,j)
2475
+
2476
+ :return: SubWatershed : sous-bassin or None
2477
+ """
2478
+
2479
+ if self.subs_array.array.mask[i,j]:
2480
+ return None
2481
+
2482
+ idx_sub = self.subs_array.array[i,j]
2483
+
2484
+ return self.subcatchments[idx_sub-1]
2485
+
2486
+ def get_subwatershed_from_xy(self, x:float, y:float) -> SubWatershed:
2487
+ """
2488
+ Récupération d'un sous-bassin sur base des coordonnées (x,y)
2489
+
2490
+ :return: SubWatershed : sous-bassin or None
2491
+ """
2492
+
2493
+ i,j = self.header.get_ij_from_xy(x,y)
2494
+ return self.get_subwatershed_from_ij(i,j)
2495
+
2496
+ def get_subwatershed_from_vector(self, vec:vector) -> tuple[SubWatershed, bool, list[SubWatershed]]:
2497
+ """
2498
+ Récupération d'un sous-bassin sur base d'un vecteur.
2499
+
2500
+ Recherche sur base du centroid du vecteur
2501
+
2502
+ :param vec: vecteur
2503
+
2504
+ :return: tuple(SubWatershed, bool, list[SubWatershed]) : sous-bassin, entièrement dans le sous-bassin, autres sous-bassins
2505
+ """
2506
+
2507
+ centroid = vec.asshapely_pol().centroid
2508
+
2509
+ sub = self.get_subwatershed_from_xy(centroid.x, centroid.y)
2510
+
2511
+ entirely = True
2512
+ others = []
2513
+ for curvert in vec.myvertices:
2514
+ cursub = self.get_subwatershed_from_xy(curvert.x, curvert.y)
2515
+ if cursub is not None and cursub != sub:
2516
+ logging.warning(_('The vector is not entirely in the same subcatchment'))
2517
+ entirely = False
2518
+ others.append(cursub)
2519
+
2520
+ return sub, entirely, others