wolfhece 2.2.38__py3-none-any.whl → 2.2.39__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 (49) 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/lifewatch.py +6 -3
  39. wolfhece/picc.py +124 -8
  40. wolfhece/pyLandUseFlanders.py +146 -0
  41. wolfhece/pydownloader.py +2 -1
  42. wolfhece/pywalous.py +225 -31
  43. wolfhece/toolshydrology_dll.py +149 -0
  44. wolfhece/wolf_array.py +63 -25
  45. {wolfhece-2.2.38.dist-info → wolfhece-2.2.39.dist-info}/METADATA +3 -1
  46. {wolfhece-2.2.38.dist-info → wolfhece-2.2.39.dist-info}/RECORD +49 -40
  47. {wolfhece-2.2.38.dist-info → wolfhece-2.2.39.dist-info}/WHEEL +0 -0
  48. {wolfhece-2.2.38.dist-info → wolfhece-2.2.39.dist-info}/entry_points.txt +0 -0
  49. {wolfhece-2.2.38.dist-info → wolfhece-2.2.39.dist-info}/top_level.txt +0 -0
wolfhece/PyGui.py CHANGED
@@ -13,6 +13,8 @@ except ImportError as e:
13
13
  print(f"Import Error: {e} - GDAL")
14
14
  print("Please install GDAL for your Python version.")
15
15
 
16
+ import logging
17
+
16
18
  try:
17
19
  from os import scandir, getcwd, makedirs
18
20
  from os.path import exists, join, isdir, isfile, dirname, normpath, splitext
@@ -32,7 +34,7 @@ except ImportError as e:
32
34
  print("Please install the required modules using 'pip install -r requirements.txt'")
33
35
 
34
36
  try:
35
- from .wolf_array import WOLF_ARRAY_FULL_LOGICAL, WOLF_ARRAY_MB_SINGLE, WolfArray, getkeyblock, WOLF_ARRAY_FULL_INTEGER16, WOLF_ARRAY_MB_INTEGER
37
+ from .wolf_array import WOLF_ARRAY_FULL_LOGICAL, WOLF_ARRAY_MB_SINGLE, WolfArray, getkeyblock, WOLF_ARRAY_FULL_INTEGER16, WOLF_ARRAY_MB_INTEGER, header_wolf
36
38
  except ImportError as e:
37
39
  print(f"Import Error: {e} - WolfArray")
38
40
  print("Please install the required modules using 'pip install -r requirements.txt'")
@@ -80,6 +82,11 @@ except ImportError as e:
80
82
  print(f"Import Error: {e} - RatingCurve, mesh2d, Results2DGPU, PyGuiHydrology, RatingCurve, hydrology, PyParams, picc, wolf_zi_db, CpGrid")
81
83
  print("Please install the required modules using 'pip install -r requirements.txt'")
82
84
 
85
+ try:
86
+ from .toolshydrology_dll import ToolsHydrologyFortran
87
+ except ImportError as e:
88
+ print(f"Import Error: {e} - ToolsHydrologyFortran")
89
+
83
90
 
84
91
  GEOM_GROUP_NAME = _('Block geometry')
85
92
  MAGN_GROUP_NAME = _('Magnetic grid')
@@ -315,6 +322,34 @@ class MapManager(GenMapManager):
315
322
  # self.mapviewer.findminmax(True)
316
323
  # self.mapviewer.Autoscale(False)
317
324
 
325
+ DEBUG_HYDROLOGY_DLL = False
326
+
327
+ HYDROLOGY_ARRAYS = {'Characteristic_maps':[
328
+ ('.b','Raw elevation [m]'), # extension, name for GUI
329
+ ('corr.b','Corrected elevation [m]'),
330
+ ('diff.b','Corrections (corr-raw) [m]'),
331
+ ('.nap','Mask [-]'),
332
+ ('.sub','SubBasin index [-]'),
333
+ ('.cnv','Accumulation [km²]'),
334
+ ('.time','Total time [s]'),
335
+ ('.coeff','RunOff coeff [-]'),
336
+ ('.slope','Slope [-]'),
337
+ ('.reachs','Reach index [-]'),
338
+ ('.strahler','Strahler index [-]'),
339
+ ('.reachlevel','Reach accumulation [-]'),
340
+ ('.landuse1','Woodlands [m²]'),
341
+ ('.landuse2','Pastures [m²]'),
342
+ ('.landuse3','Cultivated [m²]'),
343
+ ('.landuse4','Pavements [m²]'),
344
+ ('.landuse5','Water [m²]'),
345
+ ('.landuse6','River [m²]'),
346
+ ('.landuse_cropped','LandUse Cropped'),
347
+ # ('.principal_landuse_cropped','Principal landuse [-]'),
348
+ ('_encode.sub','Coded index SubB [-]')]}
349
+
350
+ HYDROLOGY_VECTORS = {'Characteristic_vectors':[('.delimit.vec','Watershed')], # extension, name for GUI
351
+ 'Whole_basin':[('Rain_basin_geom.vec','Rain geom'), # file, name for GUI
352
+ ('Evap_basin_geom.vec','Evapotranspiration geom')]}
318
353
  class HydrologyModel(GenMapManager):
319
354
 
320
355
  mydir:str
@@ -329,20 +364,50 @@ class HydrologyModel(GenMapManager):
329
364
  mycatchment:Catchment
330
365
  myexchanges:forced_exchanges
331
366
 
332
- def __init__(self,dir:str='', splash=True, *args, **kw):
367
+ def __init__(self, directory:str = '', splash:bool = True, *args, **kw):
333
368
 
334
369
  self.wx_exists = wx.App.Get() is not None # test if wx App is running
335
370
 
336
- self.SPWstations=SPWMIGaugingStations()
337
- self.DCENNstations=SPWDCENNGaugingStations()
371
+ self.SPWstations = SPWMIGaugingStations()
372
+ self.DCENNstations= SPWDCENNGaugingStations()
338
373
 
339
- if dir=='':
340
- idir=wx.DirDialog(None,"Choose Directory")
341
- if idir.ShowModal() == wx.ID_CANCEL:
374
+ if directory=='':
375
+ if self.wx_exists:
376
+ idir = wx.DirDialog(None,"Choose Directory")
377
+ if idir.ShowModal() == wx.ID_CANCEL:
378
+ idir.Destroy()
379
+ logging.error("Directory selection cancelled. Cannot proceed with HydrologyModel initialization.")
380
+ return
381
+
382
+ self.mydir =idir.GetPath()
383
+ idir.Destroy()
384
+ else:
385
+ logging.error("No directory selected. Cannot proceed with HydrologyModel initialization.")
342
386
  return
343
- self.mydir =idir.GetPath()
344
387
  else:
345
- self.mydir=normpath(dir)
388
+ self.mydir = normpath(directory)
389
+
390
+ # Test if the directory is void
391
+ if not exists(self.mydir):
392
+ if self.wx_exists:
393
+ dlg = wx.MessageDialog(None,
394
+ _("The directory you selected does not exist. Please select a valid directory."),
395
+ _("Invalid Directory"),
396
+ wx.OK | wx.ICON_ERROR)
397
+ dlg.ShowModal()
398
+ dlg.Destroy()
399
+ return
400
+ else:
401
+ raise FileNotFoundError(f"The directory {self.mydir} does not exist.")
402
+
403
+ logging.info(_("Loading is on going - Please wait..."))
404
+
405
+ self._dlltools = ToolsHydrologyFortran(self.mydir, debugmode=DEBUG_HYDROLOGY_DLL)
406
+
407
+ new_sim = self.is_new_sim()
408
+ if new_sim is None:
409
+ logging.error("No new simulation defined. Cannot proceed with HydrologyModel initialization.")
410
+ return
346
411
 
347
412
  if self.wx_exists:
348
413
  super(HydrologyModel, self).__init__(splash=splash, *args, **kw)
@@ -350,104 +415,880 @@ class HydrologyModel(GenMapManager):
350
415
  if "splash" in kw and kw["splash"]:
351
416
  raise Exception("You can't have the splash screen outside a GUI")
352
417
 
353
- self.mydircharact=join(self.mydir,'Characteristic_maps\\Drainage_basin')
354
- self.mydirwhole=join(self.mydir,'Whole_basin\\')
418
+ self.mydircharact= join(self.mydir,'Characteristic_maps\\Drainage_basin')
419
+ self.mydirwhole = join(self.mydir,'Whole_basin\\')
420
+
421
+ if new_sim:
422
+ self.mycatchment = None
423
+ else:
424
+ self.mycatchment = Catchment('Mysim', self.mydir, False, True)
425
+
426
+ #Fichiers de paramètres
427
+ self.mainparams=Wolf_Param(self.mapviewer,
428
+ filename=self.mydir+'\\Main_model.param',
429
+ title="Model parameters",
430
+ DestroyAtClosing=False)
355
431
 
356
- self.mycatchment = Catchment('Mysim',self.mydir,False,True)
357
- self.myexchanges = forced_exchanges(self.mydir)
432
+ self.basinparams=Wolf_Param(self.mapviewer,
433
+ filename=self.mydircharact+'.param',
434
+ title="Basin parameters",
435
+ DestroyAtClosing=False)
436
+
437
+ self.mainparams.Hide()
438
+ self.basinparams.Hide()
358
439
 
359
440
  if self.wx_exists:
360
441
  self.mapviewer=GuiHydrology(title='Model : '+self.mydir, wolfparent=self, wxlogging=self.mylogs)
361
442
  # self.setup_mapviewer(title='Wolf - Hydrology model', wolfparent=self)
362
443
 
444
+ if self.mainparams[('Forced Exchanges','Directory')] is not None:
445
+ self.myexchanges = forced_exchanges(Path(self.mydir) / self.mainparams[('Forced Exchanges','Directory')],
446
+ fname = self.mainparams[('Forced Exchanges','Filename')],
447
+ mapviewer=self.mapviewer)
448
+ else:
449
+ self.myexchanges = forced_exchanges(Path(self.mydir),
450
+ fname = 'Coupled_pairs.txt',
451
+ mapviewer=self.mapviewer)
363
452
 
364
- self.files_hydrology_array={'Characteristic_maps':[
365
- ('.b','Raw elevation [m]'),
366
- ('corr.b','Corrected elevation [m]'),
367
- #('diff.b','Corrections (corr-raw) [m]'),
368
- ('.nap','Mask [-]'),
369
- ('.sub','SubBasin index [-]'),
370
- ('.cnv','Accumulation [km²]'),
371
- ('.time','Total time [s]'),
372
- ('.coeff','RunOff coeff [-]'),
373
- ('.slope','Slope [-]'),
374
- ('.reachs','Reach index [-]'),
375
- ('.strahler','Strahler index [-]'),
376
- ('.reachlevel','Reach accumulation [-]'),
377
- ('.landuse1','Woodlands [m²]'),
378
- ('.landuse2','Pastures [m²]'),
379
- ('.landuse3','Cultivated [m²]'),
380
- ('.landuse4','Pavements [m²]'),
381
- ('.landuse5','Water [m²]'),
382
- ('.landuse6','River [m²]'),
383
- ('.landuse_limited_area','LandUse Verif'),
384
- ('.principal_landuse_cropped','Principal landuse [-]'),
385
- ('_encode.sub','Coded index SubB [-]')]}
386
453
 
454
+ self.files_hydrology_array = HYDROLOGY_ARRAYS
455
+ self.files_hydrology_vectors = HYDROLOGY_VECTORS
456
+
457
+ for curfile in self.files_hydrology_array['Characteristic_maps']:
458
+ curext=curfile[0]
459
+ curidx=curfile[1]
460
+ fn = Path(self.mydircharact + curext)
461
+ if fn.exists():
462
+ a = WolfArray(fn)
463
+ self.mapviewer.add_object(which='array',
464
+ newobj=a,
465
+ id= curidx,
466
+ ToCheck= False,
467
+ )
468
+ self._impose_palette(a)
469
+
470
+ for curfile in self.files_hydrology_vectors['Characteristic_vectors']:
471
+ curext=curfile[0]
472
+ curidx=curfile[1]
473
+ fn = Path(self.mydircharact + curext)
474
+ if fn.exists():
475
+ delimit = Zones(filename=fn, mapviewer=self.mapviewer, parent=self.mapviewer)
476
+
477
+ if self.mycatchment.nbSubBasin == 1:
478
+ cur_zone = delimit.myzones[0]
479
+ cur_vect = cur_zone.myvectors[0]
480
+
481
+ cur_zone.myname = _('Watershed')
482
+ cur_vect.set_legend_to_centroid(str(1), visible=True)
483
+ cur_vect.myprop.legendfontsize = 12
484
+ else:
485
+ if self.mycatchment.nbSubBasin == delimit.nbzones:
486
+ for idx, cur_zone in enumerate(delimit.myzones):
487
+ cur_sub = self.mycatchment.get_subBasin(idx+1)
488
+ cur_zone.myname = cur_sub.name
489
+ cur_vect = cur_zone.myvectors[0]
490
+ cur_vect.set_legend_to_centroid(cur_sub.name + ' - ' + str(cur_sub.iDSorted), visible=True)
491
+ cur_vect.myprop.legendfontsize = 12
492
+ else:
493
+ logging.warning(_("The number of sub-basins in the delimit file does not match the number of sub-basins in the catchment."))
494
+ logging.warning(_("Please check the delimit file and the catchment parameters."))
495
+
496
+ delimit.reset_listogl()
497
+
498
+ self.mapviewer.add_object(which='vector',
499
+ newobj = delimit,
500
+ id=curidx,
501
+ ToCheck=True)
502
+
503
+ for curfile in self.files_hydrology_vectors['Whole_basin']:
504
+ curext=curfile[0]
505
+ curidx=curfile[1]
506
+
507
+ fn = Path(self.mydirwhole + curext)
508
+ if fn.exists():
509
+ self.mapviewer.add_object(which='vector',
510
+ filename=fn,
511
+ id=curidx,
512
+ ToCheck=False)
513
+
514
+ # ------------------------------------------------
515
+ # FORCED EXCHANGES
516
+ if self.myexchanges is not None:
517
+ self.mapviewer.add_object(which='vector',
518
+ newobj=self.myexchanges._mysegs,
519
+ id='Forced exchanges',
520
+ ToCheck=False)
521
+ self.mapviewer.add_object(which='cloud',
522
+ newobj=self.myexchanges._mycloudup,
523
+ id='Up nodes - FE',
524
+ ToCheck=False)
525
+ self.myexchanges._mycloudup.set_mapviewer(self.mapviewer)
526
+
527
+ self.mapviewer.add_object(which='cloud',
528
+ newobj=self.myexchanges._myclouddown,
529
+ id='Down nodes - FE',
530
+ ToCheck=False)
531
+
532
+ self.myexchanges._myclouddown.set_mapviewer(self.mapviewer)
533
+ # END FORCED EXCHANGES
534
+ # ------------------------------------------------
535
+
536
+ if self.mycatchment is not None:
537
+
538
+ zones_RT = self.mycatchment.get_retentionbasin_zones()
539
+ zones_RT.parent = self
540
+ self.mapviewer.add_object(which='vector',
541
+ newobj=zones_RT,
542
+ id='Anthropic links',
543
+ ToCheck=False)
544
+
545
+ self.mapviewer.add_object(which='cloud',
546
+ newobj=self.mycatchment.subBasinCloud,
547
+ id='Local outlets',
548
+ ToCheck=False)
549
+
550
+ self.mapviewer.add_object(which='cloud',
551
+ newobj=self.mycatchment.retentionBasinCloud,
552
+ id='Anthropic inlets/outlets',
553
+ ToCheck=False)
554
+
555
+ self.mycatchment.subBasinCloud.set_mapviewer(self.mapviewer)
556
+ self.mycatchment.retentionBasinCloud.set_mapviewer(self.mapviewer)
557
+
558
+
559
+ self.mapviewer.add_object(which='other',
560
+ newobj=self.SPWstations,
561
+ ToCheck=False,
562
+ id='SPW-MI stations')
563
+
564
+ self.mapviewer.add_object(which='other',
565
+ newobj=self.DCENNstations,
566
+ ToCheck=False,
567
+ id='SPW-DCENN stations')
568
+
569
+ self.mapviewer.add_grid()
570
+ self.mapviewer.add_WMS()
571
+
572
+ self.mapviewer.active_array = None
573
+ self.mapviewer.active_vector = None
574
+ self.mapviewer.active_zone = None
575
+ self.mapviewer.active_zones = None
576
+ self.mapviewer.active_cloud = None
577
+
578
+ self.mapviewer.findminmax(True)
579
+ self.mapviewer.Autoscale(False)
580
+
581
+ logging.info("Loading hydrology model complete.")
582
+
583
+ def _impose_palette(self, array:"WolfArray"):
584
+ """ Impose a palette to the array.
585
+
586
+ This is used to ensure that the array has a consistent color palette.
587
+ """
588
+
589
+ if array is not None:
590
+ if array.idx == "Corrections (corr-raw) [m]".lower():
591
+ # Corrections array
592
+ array.mypal.default_difference3()
593
+ elif array.idx == 'Accumulation [km²]'.lower():
594
+ array.mypal.defaultblue3()
595
+ array.mypal.set_values([0., 0.1, 10.])
596
+ elif array.idx == 'LandUse Cropped'.lower():
597
+ from .pywalous import get_palette_walous_ocs
598
+ array.mypal = get_palette_walous_ocs()
599
+
600
+ def reload(self):
601
+ """ Reload the hydrology model parameters and data. """
602
+
603
+ new_sim = self.is_new_sim()
604
+ if new_sim is None:
605
+ logging.error("No new simulation defined. Cannot reload hydrology model.")
606
+ return
607
+
608
+ self.mycatchment = Catchment('Mysim', self.mydir, False, True)
387
609
 
388
- self.files_hydrology_vectors={'Characteristic_vectors':[('.delimit.vec','Watershed')],
389
- 'Whole_basin':[('Rain_basin_geom.vec','Rain geom'),
390
- ('Evap_basin_geom.vec','Evapotranspiration geom')]}
610
+ if self.mapviewer is not None:
611
+ all_arrays = self.mapviewer.get_list_ids(drawing_type= draw_type.ARRAYS, checked_state= None)
612
+ all_checked_arrays = self.mapviewer.get_list_ids(drawing_type= draw_type.ARRAYS, checked_state= True)
391
613
 
392
614
  for curfile in self.files_hydrology_array['Characteristic_maps']:
393
615
  curext=curfile[0]
394
616
  curidx=curfile[1]
395
- self.mapviewer.add_object(which='array',filename=self.mydircharact+curext,id=curidx,ToCheck=False)
617
+ fn = Path(self.mydircharact + curext)
396
618
 
619
+ if fn.exists() and curidx.lower() not in all_arrays:
620
+ self.mapviewer.add_object(which='array',
621
+ filename=fn,
622
+ id= curidx,
623
+ ToCheck= False,
624
+ )
625
+ elif fn.exists() and curidx.lower() in all_arrays:
626
+ # force to reload the array
627
+ obj:WolfArray
628
+ obj = self.mapviewer.get_obj_from_id(curidx, drawing_type= draw_type.ARRAYS)
629
+ obj._reload()
630
+
631
+ all_vectors = self.mapviewer.get_list_ids(drawing_type= draw_type.VECTORS, checked_state= None)
397
632
 
398
633
  for curfile in self.files_hydrology_vectors['Characteristic_vectors']:
399
634
  curext=curfile[0]
400
635
  curidx=curfile[1]
636
+ fn = Path(self.mydircharact + curext)
637
+ if fn.exists():
638
+ delimit = Zones(filename=fn, mapviewer=self.mapviewer, parent=self.mapviewer, idx = curidx.lower())
401
639
 
402
- delimit = Zones(filename=self.mydircharact+curext, mapviewer=self.mapviewer, parent = self.mapviewer)
640
+ if self.mycatchment.nbSubBasin == 1:
641
+ cur_zone = delimit.myzones[0]
642
+ cur_vect = cur_zone.myvectors[0]
403
643
 
404
- for idx, cur_zone in enumerate(delimit.myzones):
405
- cur_sub = self.mycatchment.get_subBasin(idx+1)
406
- cur_zone.myname = cur_sub.name
407
- cur_vect = cur_zone.myvectors[0]
408
- cur_vect.set_legend_to_centroid(cur_sub.name + ' - ' + str(cur_sub.iDSorted), visible=True)
409
- cur_vect.myprop.legendfontsize = 12
410
-
411
- delimit.reset_listogl()
644
+ cur_zone.myname = _('Watershed')
645
+ cur_vect.set_legend_to_centroid(str(1), visible=True)
646
+ cur_vect.myprop.legendfontsize = 12
647
+ else:
648
+ for idx, cur_zone in enumerate(delimit.myzones):
649
+ cur_sub = self.mycatchment.get_subBasin(idx+1)
650
+ cur_zone.myname = cur_sub.name
651
+ cur_vect = cur_zone.myvectors[0]
652
+ cur_vect.set_legend_to_centroid(cur_sub.name + ' - ' + str(cur_sub.iDSorted), visible=True)
653
+ cur_vect.myprop.legendfontsize = 12
412
654
 
413
- self.mapviewer.add_object(which='vector',newobj = delimit, id=curidx, ToCheck=True)
655
+ if curidx.lower() in all_vectors:
656
+ self.mapviewer.replace_object(curidx, delimit, drawing_type=draw_type.VECTORS)
414
657
 
415
658
  for curfile in self.files_hydrology_vectors['Whole_basin']:
416
659
  curext=curfile[0]
417
660
  curidx=curfile[1]
418
- if exists(self.mydirwhole+curext):
419
- self.mapviewer.add_object(which='vector',filename=self.mydirwhole+curext,id=curidx,ToCheck=False)
420
661
 
421
- self.mapviewer.add_object(which='vector',newobj=self.myexchanges.mysegs,id='Forced exchanges',ToCheck=False)
662
+ fn = Path(self.mydirwhole + curext)
663
+ if fn.exists():
664
+ if curidx.lower() in all_vectors:
665
+ newobj = Zones(filename=fn, mapviewer=self.mapviewer, parent=self.mapviewer)
666
+ self.mapviewer.replace_object(curidx,
667
+ newobj=newobj,
668
+ drawing_type=draw_type.VECTORS)
669
+
670
+ if self.mycatchment is not None:
671
+ all_clouds = self.mapviewer.get_list_ids(drawing_type= draw_type.CLOUD, checked_state= None)
672
+
673
+ if 'Local outlets'.lower() in all_clouds:
674
+ # Replace the local outlets cloud
675
+ self.mapviewer.replace_object('Local outlets',
676
+ newobj=self.mycatchment.subBasinCloud,
677
+ drawing_type=draw_type.CLOUD)
678
+
679
+ if 'Anthropic inlets/outlets'.lower() in all_clouds:
680
+ self.mapviewer.replace_object('Anthropic inlets/outlets',
681
+ newobj=self.mycatchment.retentionBasinCloud,
682
+ drawing_type=draw_type.CLOUD)
683
+
684
+ # self.mapviewer.replace_object('Up nodes - FE',
685
+ # newobj=self.myexchanges._mycloudup,
686
+ # drawing_type=draw_type.CLOUD)
687
+
688
+ # self.mapviewer.replace_object('Down nodes - FE',
689
+ # newobj=self.myexchanges._myclouddown,
690
+ # drawing_type=draw_type.CLOUD)
691
+
692
+ self.mycatchment.subBasinCloud.set_mapviewer(self.mapviewer)
693
+ self.mycatchment.retentionBasinCloud.set_mapviewer(self.mapviewer)
694
+ # self.myexchanges._myclouddown.set_mapviewer(self.mapviewer)
695
+ # self.myexchanges._mycloudup.set_mapviewer(self.mapviewer)
422
696
 
423
- zones_RT = self.mycatchment.get_retentionbasin_zones()
424
- zones_RT.parent = self
425
- self.mapviewer.add_object(which='vector',newobj=zones_RT,id='Anthropic links',ToCheck=False)
697
+ self.mapviewer.Refresh()
426
698
 
427
- self.mapviewer.add_object(which='cloud',newobj=self.mycatchment.subBasinCloud,id='Local outlets',ToCheck=False)
428
- self.mapviewer.add_object(which='cloud',newobj=self.mycatchment.retentionBasinCloud,id='Anthropic inlets/outlets',ToCheck=False)
699
+ logging.info("Hydrology model reloaded successfully.")
429
700
 
430
- self.mycatchment.subBasinCloud.set_mapviewer(self.mapviewer)
431
- self.mycatchment.retentionBasinCloud.set_mapviewer(self.mapviewer)
701
+ def is_new_sim(self) -> bool:
702
+ """
703
+ Check if the hydrology model is a new simulation.
432
704
 
433
- self.mapviewer.add_object(which='cloud',newobj=self.myexchanges.mycloudup,id='Up nodes',ToCheck=False)
434
- self.mapviewer.add_object(which='cloud',newobj=self.myexchanges.myclouddown,id='Down nodes',ToCheck=False)
705
+ Returns:
706
+ bool: True if it is a new simulation, False otherwise.
707
+ """
435
708
 
436
- self.mapviewer.add_object(which='other',newobj=self.SPWstations,ToCheck=False,id='SPW-MI stations')
437
- self.mapviewer.add_object(which='other',newobj=self.DCENNstations,ToCheck=False,id='SPW-DCENN stations')
709
+ nbfiles_in_dir = len([f for f in scandir(self.mydir) if f.is_file()])
710
+ if nbfiles_in_dir == 0:
711
+ # Propose to create a new simulation
712
+ if self.wx_exists:
713
+ dlg = wx.MessageDialog(None,
714
+ _("The directory you selected is empty. Do you want to create a new simulation?"),
715
+ _("Empty Directory"),
716
+ wx.YES_NO | wx.ICON_QUESTION)
717
+ if dlg.ShowModal() == wx.ID_YES:
718
+ self._dlltools.set_directory()
719
+ self._dlltools.create_default_parameters()
720
+ return True
721
+ else:
722
+ dlg.Destroy()
723
+ return None
438
724
 
439
- self.mapviewer.add_grid()
440
- self.mapviewer.add_WMS()
725
+ dlg.Destroy()
726
+ else:
727
+ self._dlltools = ToolsHydrologyFortran(self.mydir, debugmode=DEBUG_HYDROLOGY_DLL)
728
+ self._dlltools.set_directory()
729
+ self._dlltools.create_default_parameters()
730
+ return True
731
+ else:
732
+ return False
441
733
 
442
- self.mapviewer.findminmax(True)
443
- self.mapviewer.Autoscale(False)
734
+ @property
735
+ def header(self) -> header_wolf:
736
+ """ Return the header of the hydrology model. """
737
+
738
+ if self.mycatchment is None:
739
+ fn_dtm = self.path_dtm_raw
740
+ if fn_dtm is not None:
741
+ header = header_wolf.read_header(str(fn_dtm))
742
+ return header
743
+ else:
744
+ return self.mycatchment.charact_watrshd.header
444
745
 
445
- #Fichiers de paramètres
446
- self.mainparams=Wolf_Param(self.mapviewer,filename=self.mydir+'\\Main_model.param',title="Model parameters",DestroyAtClosing=False)
447
- self.basinparams=Wolf_Param(self.mapviewer,filename=self.mydircharact+'.param',title="Basin parameters",DestroyAtClosing=False)
448
- self.mainparams.Hide()
449
- self.basinparams.Hide()
746
+ # If no catchment is defined, return None
747
+ # This is a fallback in case the catchment is not set or the DTM file
748
+ logging.error("No catchment defined. Cannot retrieve header.")
749
+ return None
750
+
751
+ @property
752
+ def path_dtm_raw(self) -> Path:
753
+ """ Return the raw Digital Terrain Model (DTM) as a Path. """
754
+
755
+ fn = Path(self.mydircharact + '.b')
756
+ if fn.exists():
757
+ return fn
758
+ else:
759
+ logging.error(f"DTM file {fn} does not exist.")
760
+ return None
761
+
762
+ @property
763
+ def path_dtm_corrected(self) -> Path:
764
+ """ Return the corrected Digital Terrain Model (DTM) as a Path. """
450
765
 
766
+ fn = Path(self.mydircharact + 'corr.b')
767
+ if fn.exists():
768
+ return fn
769
+ else:
770
+ logging.error(f"Corrected DTM file {fn} does not exist.")
771
+ return None
772
+
773
+ @property
774
+ def path_mask(self) -> Path:
775
+ """ Return the mask of the Digital Terrain Model (DTM) as a Path. """
776
+
777
+ fn = Path(self.mydircharact + '.nap')
778
+ if fn.exists():
779
+ return fn
780
+ else:
781
+ logging.error(f"Mask DTM file {fn} does not exist.")
782
+ return None
783
+
784
+ @property
785
+ def path_subbasin(self) -> Path:
786
+ """ Return the subbasin index as a Path. """
787
+
788
+ fn = Path(self.mydircharact + '.sub')
789
+ if fn.exists():
790
+ return fn
791
+ else:
792
+ logging.error(f"Subbasin DTM file {fn} does not exist.")
793
+ return None
794
+
795
+ @property
796
+ def path_accumulation(self) -> Path:
797
+ """ Return the accumulation as a Path. """
798
+
799
+ fn = Path(self.mydircharact + '.cnv')
800
+ if fn.exists():
801
+ return fn
802
+ else:
803
+ logging.error(f"Accumulation DTM file {fn} does not exist.")
804
+ return None
805
+
806
+ @property
807
+ def path_total_time(self) -> Path:
808
+ """ Return the total time as a Path. """
809
+
810
+ fn = Path(self.mydircharact + '.time')
811
+ if fn.exists():
812
+ return fn
813
+ else:
814
+ logging.error(f"Total time DTM file {fn} does not exist.")
815
+ return None
816
+
817
+ @property
818
+ def path_runoff_coeff(self) -> Path:
819
+ """ Return the runoff coefficient as a Path. """
820
+
821
+ fn = Path(self.mydircharact + '.coeff')
822
+ if fn.exists():
823
+ return fn
824
+ else:
825
+ logging.error(f"Runoff coefficient DTM file {fn} does not exist.")
826
+ return None
827
+
828
+ @property
829
+ def path_slope(self) -> Path:
830
+ """ Return the slope of the Digital Terrain Model (DTM) as a Path. """
831
+
832
+ fn = Path(self.mydircharact + '.slope')
833
+ if fn.exists():
834
+ return fn
835
+ else:
836
+ logging.error(f"Slope DTM file {fn} does not exist.")
837
+ return None
838
+
839
+ @property
840
+ def path_reachs(self) -> Path:
841
+ """ Return the reach file as a Path. """
842
+
843
+ fn = Path(self.mydircharact + '.reach')
844
+ if fn.exists():
845
+ return fn
846
+ else:
847
+ logging.error(f"Reach DTM file {fn} does not exist.")
848
+ return None
849
+
850
+ def set_outlet(self, x:float, y:float):
851
+ """
852
+ Set the outlet coordinates for the hydrology model.
853
+ """
854
+ if self.mainparams is not None:
855
+ self.mainparams[('Outlet Coordinates', 'X')] = x
856
+ self.mainparams[('Outlet Coordinates', 'Y')] = y
857
+ self.mainparams.Save()
858
+ logging.info(f"Outlet set to coordinates: ({x}, {y})")
859
+ else:
860
+ logging.error("Main parameters not loaded. Cannot set outlet coordinates.")
861
+
862
+ def set_active_array_as_dtm(self, active_array:WolfArray):
863
+ """
864
+ Set the active array as the Digital Terrain Model (DTM) for the hydrology model.
865
+
866
+ Implications :
867
+ - Update the mesh size in the main parameters.
868
+ - Saving the active array as the '.b' file in the characteristic maps directory.
869
+ """
870
+
871
+ assert active_array.dx == active_array.dy, "The active array must have equal dx and dy for DTM."
872
+
873
+ if self.mainparams is not None:
874
+ self.mainparams[('Topographical mesh', 'Space step')] = active_array.dx
875
+ self.mainparams[('Preprocessing', 'Cropping topo')] = 0
876
+
877
+ fn = Path(self.mydircharact + '.b')
878
+ self.mainparams[('Topography', 'Directory')] = fn.parent
879
+ self.mainparams[('Topography', 'Filename')] = fn.name
880
+
881
+ active_array.write_all(fn)
882
+ self.mainparams.Save()
883
+
884
+ # self.myexchanges.save()
885
+
886
+ # self.mainparams[('Forced Exchanges', 'Directory')] = self.mydir
887
+ # self.mainparams[('Forced Exchanges', 'Filename')] = 'Coupled_pairs.txt'
888
+
889
+ # with open(Path(self.mydir) / 'Coupled_pairs.txt', 'w') as f:
890
+ # f.write(f"{active_array.dx} {active_array.dy}\n")
891
+
892
+ logging.info(f"Active array set as DTM: {active_array.filename}")
893
+ else:
894
+ logging.error("Main parameters not loaded. Cannot set active array as DTM.")
895
+
896
+ def set_only_preprocess_data(self):
897
+ """ Set the hydrology model to only preprocess data. """
898
+ from datetime import timedelta, datetime as dt
899
+
900
+
901
+ if self.mainparams is not None:
902
+ self.mainparams[('Preprocessing', 'Cropping topo')] = 0
903
+ self.mainparams[('Preprocessing', 'Quit immediatly after')] = 1
904
+ self.mainparams[('Preprocessing', 'Delimiting subbasin')] = 1
905
+ self.mainparams[('Preprocessing', 'Whole basin (atmospheric geometry & data)')] = 1
906
+ self.mainparams[('Preprocessing', 'Subbasin (atmospheric geometry & data)')] = 1
907
+ self.mainparams[('Preprocessing', 'Lumped input')] = 1
908
+
909
+ self.mainparams[('Atmospheric data', 'Type of source')] = -1 # To ignore atmospheric data
910
+ self.mainparams[('Atmospheric data', 'Time step')] = 1
911
+
912
+ self.mainparams[('Temporal Parameters', 'Start date time')] = dt.now().strftime('%Y%m%d-%H%M%S')
913
+ self.mainparams[('Temporal Parameters', 'End date time')] = (dt.now()+timedelta(seconds=1)).strftime('%Y%m%d-%H%M%S')
914
+ self.mainparams[('Temporal Parameters', 'Time step')] = 1
915
+
916
+ self.mainparams[('Simulation intervals', 'Nb')] = 0 # Preprocess data only
917
+
918
+ self.mainparams[('Measuring stations SPW', 'To read')] = 0
919
+
920
+ self.mainparams.Save()
921
+
922
+ logging.info("Hydrology model set to preprocess data only.")
923
+ else:
924
+ logging.error("Main parameters not loaded. Cannot set model type to preprocess data.")
925
+
926
+ if self.basinparams is not None:
927
+ self.basinparams[('Preprocessing DEM', 'Import flt')] = 0
928
+ if DEBUG_HYDROLOGY_DLL:
929
+ self.basinparams[('Verbosity', 'Code Verbosity')] = 1
930
+ else:
931
+ self.basinparams[('Verbosity', 'Code Verbosity')] = 0
932
+
933
+ if self.is_topo_already_corrected():
934
+ dlg = wx.MessageDialog(None,
935
+ _("The topography is already corrected. Do you want to set the model to not process it?"),
936
+ _("Topography Already Corrected"),
937
+ wx.YES_NO | wx.ICON_QUESTION)
938
+ if dlg.ShowModal() == wx.ID_YES:
939
+ self.basinparams[('Preprocessing DEM', 'Todo DEM correction')] = 0
940
+ else:
941
+ self.basinparams[('Preprocessing DEM', 'Todo DEM correction')] = 1
942
+ dlg.Destroy()
943
+ else:
944
+ self.basinparams[('Preprocessing DEM', 'Todo DEM correction')] = 1 # Fill-in and relief phase
945
+
946
+ self.basinparams[('Preprocessing DEM', 'nb Neighbors')] = 4
947
+ self.basinparams[('Preprocessing DEM', 'indeterminate value')] = self.header.nullvalue
948
+
949
+ if self.path_accumulation.exists():
950
+ dlg = wx.MessageDialog(None,
951
+ _("The accumulation/convergence file already exists. Do you want to set the model to not compute it?"),
952
+ _("Accumulation File Exists"),
953
+ wx.YES_NO | wx.ICON_QUESTION)
954
+ if dlg.ShowModal() == wx.ID_YES:
955
+ self.basinparams[('Preprocessing Flux', 'Todo compute flux')] = 0
956
+ else:
957
+ self.basinparams[('Preprocessing Flux', 'Todo compute flux')] = 1
958
+ dlg.Destroy()
959
+ else:
960
+ self.basinparams[('Preprocessing Flux', 'Todo compute flux')] = 1 # Convergence/Accumulation phase
961
+
962
+ self.basinparams.Save()
963
+
964
+ logging.info("Basin parameters set to not import DEM as flt.")
965
+ else:
966
+ logging.error("Basin parameters not loaded. Cannot set model type to preprocess data.")
967
+
968
+ def is_topo_already_corrected(self) -> bool:
969
+ """ Check if the topography has already been corrected.
970
+
971
+ :return: True if the topography is already corrected, False otherwise.
972
+ :rtype: bool
973
+ """
974
+
975
+ if self.path_dtm_corrected.exists():
976
+ logging.info("Topography is already corrected.")
977
+ return True
978
+ else:
979
+ logging.info("Topography is not corrected yet.")
980
+ return False
981
+
982
+ def set_model_type(self, which_model:int | str):
983
+ """ Set the hydrology model to a lumped model.
984
+
985
+ :param which_model: Model type to set. 1 for lumped model, 2 for semi-distributed model, 3 for meshed.
986
+ """
987
+
988
+ if isinstance(which_model, str):
989
+ if which_model.lower() == 'lumped':
990
+ which_model = 1
991
+ elif which_model.lower() == 'semi-distributed':
992
+ which_model = 2
993
+ elif which_model.lower() == 'meshed':
994
+ which_model = 3
995
+ else:
996
+ logging.error(f"Invalid model type string: {which_model}. Must be 'lumped', 'semi-distributed', or 'meshed'.")
997
+ return
998
+
999
+ if which_model not in [1, 2, 3]:
1000
+ logging.error(f"Invalid model type: {which_model}. Must be 1, 2, or 3.")
1001
+ return
1002
+
1003
+ if self.mainparams is not None:
1004
+ self.mainparams[('Model Type', 'Spatial distribution')] = which_model
1005
+ # self.mainparams[('Preprocessing', 'Delimiting subbasin')] = 1 if which_model == 1 else 1
1006
+ # self.mainparams[('Preprocessing', 'Whole basin (atmospheric geometry & data)')] = 0
1007
+ # self.mainparams[('Preprocessing', 'Subbasin (atmospheric geometry & data)')] = 0
1008
+
1009
+ self.mainparams.Save()
1010
+
1011
+ logging.info("Hydrology model set to lumped model.")
1012
+ else:
1013
+ logging.error("Main parameters not loaded. Cannot set model type to lumped.")
1014
+
1015
+ def _check_files(self):
1016
+ """ Check if the necessary files for the hydrology model exist.
1017
+ """
1018
+
1019
+ tmp_dir = self.mainparams[('Measuring stations SPW', 'Directory')]
1020
+ tmp_fn = self.mainparams[('Measuring stations SPW', 'Filename')]
1021
+
1022
+ # Stations file must exist
1023
+ if not (Path(tmp_dir) / tmp_fn).exists():
1024
+ logging.error(f"SPW measuring stations file not found: {tmp_dir}/{tmp_fn}.")
1025
+
1026
+ dlg = wx.FileDialog(None,
1027
+ message=_("Select the SPW measuring stations file"),
1028
+ defaultDir=tmp_dir,
1029
+ defaultFile=tmp_fn,
1030
+ wildcard="*.txt",
1031
+ style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
1032
+
1033
+ if dlg.ShowModal() == wx.ID_OK:
1034
+ selected_file = Path(dlg.GetPath())
1035
+ if exists(selected_file):
1036
+ self.mainparams[('Measuring stations SPW', 'Directory')] = str(selected_file.parent)
1037
+ self.mainparams[('Measuring stations SPW', 'Filename')] = str(selected_file.name)
1038
+ self.mainparams.Save()
1039
+ logging.info(f"SPW measuring stations file set to: {selected_file}")
1040
+ else:
1041
+ logging.error(f"Selected file does not exist: {selected_file}")
1042
+ dlg.Destroy()
1043
+
1044
+ # Landuse Directory must exist
1045
+ tmp_dir = Path(self.mainparams[('LandUse', 'Directory')])
1046
+ if not tmp_dir.exists() or not tmp_dir.is_dir():
1047
+ logging.error(f"Landuse directory not found: {tmp_dir}.")
1048
+
1049
+ dlg = wx.DirDialog(None,
1050
+ message=_("Select the Landuse directory"),
1051
+ defaultPath=str(tmp_dir),
1052
+ style=wx.DD_DEFAULT_STYLE)
1053
+
1054
+ if dlg.ShowModal() == wx.ID_OK:
1055
+ selected_dir = dlg.GetPath()
1056
+ if exists(selected_dir):
1057
+ self.mainparams[('LandUse', 'Directory')] = str(selected_dir)
1058
+ self.mainparams.Save()
1059
+ logging.info(f"LandUse directory set to: {selected_dir}")
1060
+ else:
1061
+ logging.error(f"Selected directory does not exist: {selected_dir}")
1062
+ dlg.Destroy()
1063
+
1064
+ # Municipality QDF file must exist
1065
+ tmp_dir = Path(self.mainparams[('Municipality QDF', 'Directory')])
1066
+ if not tmp_dir.exists() or not tmp_dir.is_dir():
1067
+ logging.error(f"Municipality directory not found: {tmp_dir}.")
1068
+
1069
+ # dlg = wx.DirDialog(None,
1070
+ # message=_("Select the Municipality directory"),
1071
+ # defaultPath=tmp_dir,
1072
+ # style=wx.DD_DEFAULT_STYLE)
1073
+
1074
+ # if dlg.ShowModal() == wx.ID_OK:
1075
+ # selected_dir = dlg.GetPath()
1076
+ # if exists(selected_dir):
1077
+ # self.mainparams[('Municipality', 'Directory')] = selected_dir
1078
+ # self.mainparams.Save()
1079
+ # logging.info(f"Municipality directory set to: {selected_dir}")
1080
+ # else:
1081
+ # logging.error(f"Selected directory does not exist: {selected_dir}")
1082
+ # dlg.Destroy()
1083
+
1084
+ return True
1085
+
1086
+ def _check_threshold(self):
1087
+ """
1088
+ Check if the threshold for the hydrology model is set correctly.
1089
+ """
1090
+
1091
+ if self.basinparams is not None:
1092
+ threshold = self.basinparams[('Preprocessing Flux', 'threshold convergence')]
1093
+ dtm = self.header
1094
+
1095
+ surface = dtm.nbx * dtm.nby * dtm.dx * dtm.dy
1096
+ surface /= 1000. * 1000. # Convert to km²
1097
+
1098
+ if threshold > surface / 10.:
1099
+ logging.warning(f"Threshold value {threshold} is too high compared to the dtm surface {surface}.")
1100
+ threshold = float(((surface / 10.) //5.) *5.) # rounded to 5 km²
1101
+ self.basinparams[('Preprocessing Flux', 'threshold convergence')] = threshold
1102
+ elif threshold <= 0.:
1103
+ logging.error(f"Threshold value {threshold} is invalid. It must be greater than 0.")
1104
+ logging.info("Setting threshold to 10% of the DTM surface.")
1105
+
1106
+ threshold = float(((surface / 10.) //5.) *5.)
1107
+ self.basinparams[('Preprocessing Flux', 'threshold convergence')] = threshold
1108
+ else:
1109
+ logging.info(f"Threshold value is set to: {threshold}")
1110
+
1111
+ return True
1112
+ else:
1113
+ logging.error("Main parameters not loaded. Cannot check threshold.")
1114
+ return False
1115
+
1116
+ def run_preprocessing(self, verbose:bool = DEBUG_HYDROLOGY_DLL):
1117
+ """ Run the preprocessing of the hydrology model.
1118
+
1119
+ This will create the characteristic maps and the whole basin data.
1120
+ """
1121
+
1122
+ self._check_files()
1123
+ self._check_threshold()
1124
+
1125
+ if self.myexchanges.is_empty():
1126
+ self.mainparams[('Forced Exchanges', 'Directory')] = '.'
1127
+ self.mainparams[('Forced Exchanges', 'Filename')] = 'N-O'
1128
+ else:
1129
+ self.mainparams[('Forced Exchanges', 'Directory')] = Path(str(self.myexchanges._filename)).parent.relative_to(self.mydir)
1130
+ self.mainparams[('Forced Exchanges', 'Filename')] = self.myexchanges._filename.name
1131
+ self.myexchanges.save()
1132
+
1133
+ if self.nb_interior_points() in [0,1]:
1134
+ self.set_model_type('lumped') # Lumped model
1135
+ elif self.nb_interior_points() > 1:
1136
+ self.set_model_type('meshed') # Meshed model
1137
+
1138
+ self.mainparams.compare_active_to_default()
1139
+ self.mainparams.Save()
1140
+
1141
+ self.basinparams.compare_active_to_default()
1142
+ self.basinparams.Save()
1143
+
1144
+ # Create a progress bar with pulse
1145
+ if self.wx_exists:
1146
+ progress_dialog = wx.ProgressDialog(_("Preprocessing Hydrology Model"),
1147
+ _("Running preprocessing..."),
1148
+ maximum=100,
1149
+ parent=self,
1150
+ style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME)
1151
+
1152
+ progress_dialog.Pulse(_("Compute preprocessing..."))
1153
+
1154
+ logging.info("Starting preprocessing of the hydrology model...")
1155
+ self._dlltools.run_preprocessing()
1156
+ logging.info("Finished preprocessing of the hydrology model.")
1157
+
1158
+ if self.wx_exists:
1159
+ # Update the progress dialog
1160
+ progress_dialog.Destroy()
1161
+
1162
+ self.reload()
1163
+
1164
+ def set_runoff_type(self, which_runoff:int | str, timestep:float = 300.):
1165
+ """ Set the runoff type for the hydrology model.
1166
+
1167
+ :param which_runoff: Runoff type to set. ADALI = 3 ; Ven te Chow = 2 ; Froude based = 1
1168
+ """
1169
+
1170
+ if isinstance(which_runoff, str):
1171
+ if which_runoff.lower() == 'adali':
1172
+ which_runoff = 3
1173
+ elif which_runoff.lower() == 'ven te chow':
1174
+ which_runoff = 2
1175
+ elif which_runoff.lower() == 'froude based':
1176
+ which_runoff = 1
1177
+ else:
1178
+ logging.error(f"Invalid runoff type string: {which_runoff}. Must be 'ADALI', 'Ven te Chow', or 'Froude based'.")
1179
+ return
1180
+
1181
+ if which_runoff not in [1, 2, 3]:
1182
+ logging.error(f"Invalid runoff type: {which_runoff}. Must be 1, 2, or 3.")
1183
+ return
1184
+
1185
+ if self.mainparams is not None:
1186
+ self.mainparams[('Runoff', 'How to compute local runoff speed?')] = which_runoff
1187
+ self.mainparams[('Runoff', 'Time step')] = timestep
1188
+ logging.info(f"Runoff type set to {which_runoff}.")
1189
+ else:
1190
+ logging.error("Main parameters not loaded. Cannot set runoff type.")
1191
+
1192
+ def nb_interior_points(self) -> int:
1193
+ """ Get the number of interior points in the hydrology model.
1194
+
1195
+ Returns:
1196
+ int: Number of interior points.
1197
+ """
1198
+ if self.mainparams is not None:
1199
+ nb = self.mainparams[('Semi distributed model', ('How many?'))]
1200
+ logging.info(f"Number of interior points: {nb}")
1201
+ return nb
1202
+ else:
1203
+ logging.error("No catchment defined. Cannot get number of interior points.")
1204
+ return 0
1205
+
1206
+ def get_interior_points(self) -> dict:
1207
+ """ Get the interior points of the hydrology model.
1208
+
1209
+ Returns:
1210
+ list: List of interior points.
1211
+ """
1212
+ if self.mainparams is not None:
1213
+ nb = self.mainparams[('Semi distributed model', ('How many?'))]
1214
+
1215
+ all_ip = {}
1216
+ for i in range(1, nb + 1):
1217
+ ip = self.mainparams[f'Interior point {i}']
1218
+ if ip is None:
1219
+ logging.warning(f"Interior point {i} is not defined.")
1220
+ continue
1221
+
1222
+ x = ip['X']
1223
+ y = ip['Y']
1224
+ type_ip = ip['Which type']
1225
+ active = ip['Active']
1226
+ all_ip[i] = {'X': x, 'Y': y,
1227
+ 'Type': type_ip,
1228
+ 'Active': active
1229
+ }
1230
+ logging.info(f"Found {len(all_ip)} interior points.")
1231
+ return all_ip
1232
+ else:
1233
+ logging.error("No catchment defined. Cannot get interior points.")
1234
+ return {}
1235
+
1236
+ def set_interior_points(self, points:dict):
1237
+ """ Set the interior points of the hydrology model.
1238
+
1239
+ :param points: Dictionary of interior points to set.
1240
+ """
1241
+ if self.mainparams is not None:
1242
+ nb = len(points)
1243
+ self.mainparams[('Semi distributed model', ('How many?'))] = nb
1244
+
1245
+ for i, (key, value) in enumerate(points.items(), start=1):
1246
+ self.mainparams[f'Interior point {i}'] = {
1247
+ 'X': value['X'],
1248
+ 'Y': value['Y'],
1249
+ 'Which type': value['Type'],
1250
+ 'Active': value['Active']
1251
+ }
1252
+ self.mainparams.Save()
1253
+ logging.info(f"Set {nb} interior points.")
1254
+ else:
1255
+ logging.error("No catchment defined. Cannot set interior points.")
1256
+
1257
+ def edit_interior_point(self):
1258
+ """ Edit IP in CpGrid. """
1259
+ from .PandasGrid import DictGrid
1260
+
1261
+ dlg = DictGrid(parent = None,
1262
+ id=_("Edit Interior Points"),
1263
+ data = self.get_interior_points())
1264
+
1265
+ dlg.ShowModal()
1266
+
1267
+ self.set_interior_points(dlg.get_dict())
1268
+ dlg.Destroy()
1269
+
1270
+ def add_interior_point(self, x:float, y:float, type_ip:int = 0, active:bool = True):
1271
+ """ Add an interior point to the hydrology model.
1272
+
1273
+ :param x: X coordinate of the interior point.
1274
+ :param y: Y coordinate of the interior point.
1275
+ :param type_ip: Type of the interior point (default is 1).
1276
+ :param active: Whether the interior point is active (default is True).
1277
+ """
1278
+
1279
+ if self.mainparams is not None:
1280
+ nb = self.mainparams[('Semi distributed model', ('How many?'))] + 1
1281
+ self.mainparams[('Semi distributed model', ('How many?'))] = nb
1282
+
1283
+ self.mainparams[(f'Interior point {nb}', 'X')] = x
1284
+ self.mainparams[(f'Interior point {nb}', 'Y')] = y
1285
+ self.mainparams[(f'Interior point {nb}', 'Which type')] = type_ip
1286
+ self.mainparams[(f'Interior point {nb}', 'Active')] = 1 if active else 0
1287
+
1288
+ self.mainparams.Save()
1289
+ logging.info(f"Added interior point {nb} at ({x}, {y}) with type {type_ip} and active status {active}.")
1290
+ else:
1291
+ logging.error("No catchment defined. Cannot add interior point.")
451
1292
 
452
1293
  class Wolf2DPartArrays():
453
1294