wolfhece 2.2.37__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.
- wolfhece/Coordinates_operations.py +5 -0
- wolfhece/GraphNotebook.py +72 -1
- wolfhece/GraphProfile.py +1 -1
- wolfhece/MulticriteriAnalysis.py +1579 -0
- wolfhece/PandasGrid.py +62 -1
- wolfhece/PyCrosssections.py +194 -43
- wolfhece/PyDraw.py +891 -73
- wolfhece/PyGui.py +913 -72
- wolfhece/PyGuiHydrology.py +528 -74
- wolfhece/PyPalette.py +26 -4
- wolfhece/PyParams.py +33 -0
- wolfhece/PyPictures.py +2 -2
- wolfhece/PyVertex.py +32 -0
- wolfhece/PyVertexvectors.py +147 -75
- wolfhece/PyWMS.py +52 -36
- wolfhece/acceptability/acceptability.py +15 -8
- wolfhece/acceptability/acceptability_gui.py +507 -360
- wolfhece/acceptability/func.py +80 -183
- wolfhece/apps/version.py +1 -1
- wolfhece/compare_series.py +480 -0
- wolfhece/drawing_obj.py +12 -1
- wolfhece/hydrology/Catchment.py +228 -162
- wolfhece/hydrology/Internal_variables.py +43 -2
- wolfhece/hydrology/Models_characteristics.py +69 -67
- wolfhece/hydrology/Optimisation.py +893 -182
- wolfhece/hydrology/PyWatershed.py +267 -165
- wolfhece/hydrology/SubBasin.py +185 -140
- wolfhece/hydrology/climate_data.py +334 -0
- wolfhece/hydrology/constant.py +11 -0
- wolfhece/hydrology/cst_exchanges.py +76 -1
- wolfhece/hydrology/forcedexchanges.py +413 -49
- wolfhece/hydrology/hyetograms.py +2095 -0
- wolfhece/hydrology/read.py +65 -5
- wolfhece/hydrometry/kiwis.py +42 -26
- wolfhece/hydrometry/kiwis_gui.py +7 -2
- wolfhece/insyde_be/INBE_func.py +746 -0
- wolfhece/insyde_be/INBE_gui.py +1776 -0
- wolfhece/insyde_be/__init__.py +3 -0
- wolfhece/interpolating_raster.py +366 -0
- wolfhece/irm_alaro.py +1457 -0
- wolfhece/irm_qdf.py +889 -57
- wolfhece/lifewatch.py +6 -3
- wolfhece/picc.py +124 -8
- wolfhece/pyLandUseFlanders.py +146 -0
- wolfhece/pydownloader.py +2 -1
- wolfhece/pywalous.py +225 -31
- wolfhece/toolshydrology_dll.py +149 -0
- wolfhece/wolf_array.py +63 -25
- {wolfhece-2.2.37.dist-info → wolfhece-2.2.39.dist-info}/METADATA +3 -1
- {wolfhece-2.2.37.dist-info → wolfhece-2.2.39.dist-info}/RECORD +53 -42
- {wolfhece-2.2.37.dist-info → wolfhece-2.2.39.dist-info}/WHEEL +0 -0
- {wolfhece-2.2.37.dist-info → wolfhece-2.2.39.dist-info}/entry_points.txt +0 -0
- {wolfhece-2.2.37.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,
|
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
|
340
|
-
|
341
|
-
|
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(
|
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.
|
357
|
-
|
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
|
-
|
389
|
-
|
390
|
-
|
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
|
-
|
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
|
-
|
640
|
+
if self.mycatchment.nbSubBasin == 1:
|
641
|
+
cur_zone = delimit.myzones[0]
|
642
|
+
cur_vect = cur_zone.myvectors[0]
|
403
643
|
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
431
|
-
|
701
|
+
def is_new_sim(self) -> bool:
|
702
|
+
"""
|
703
|
+
Check if the hydrology model is a new simulation.
|
432
704
|
|
433
|
-
|
434
|
-
|
705
|
+
Returns:
|
706
|
+
bool: True if it is a new simulation, False otherwise.
|
707
|
+
"""
|
435
708
|
|
436
|
-
|
437
|
-
|
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
|
-
|
440
|
-
|
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
|
-
|
443
|
-
|
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
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
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
|
|