wolfhece 2.2.33__py3-none-any.whl → 2.2.35__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/wolf_zi_db.py CHANGED
@@ -17,11 +17,15 @@ from enum import Enum
17
17
  from pathlib import Path
18
18
  from typing import Literal, Union
19
19
  import wx
20
+ from tqdm import tqdm
21
+ import re
20
22
 
21
23
  from shapely.geometry import Polygon
22
24
 
23
25
  from .PyVertexvectors import Zones, zone, vector, wolfvertex
26
+ from .textpillow import Font_Priority
24
27
  from .wolf_texture import genericImagetexture
28
+ from .PyPictures import PictureCollection
25
29
  from .PyTranslate import _
26
30
 
27
31
  class ColNames_PlansTerriers(Enum):
@@ -38,6 +42,100 @@ class ColNames_PlansTerriers(Enum):
38
42
  LOWRES = 'Acces2'
39
43
  RIVER = 'River'
40
44
 
45
+ class ColNames_Ouvrages(Enum):
46
+ """ Enum for the column names in the database """
47
+
48
+ KEY = 'Clé primaire'
49
+ X1 = 'X Lambert gauche'
50
+ X2 = 'X Lambert droit'
51
+ Y1 = 'Y Lambert gauche'
52
+ Y2 = 'Y Lambert droit'
53
+ REMARK = 'Remarques'
54
+ RIVER = 'Lieu'
55
+ PHOTO1 = 'Photo1'
56
+ PHOTO2 = 'Photo2'
57
+ PHOTO3 = 'Photo3'
58
+ PHOTO4 = 'Photo4'
59
+ PHOTO5 = 'Photo5'
60
+ PHOTO6 = 'Photo6'
61
+ PHOTO7 = 'Photo7'
62
+ PHOTO8 = 'Photo8'
63
+ PHOTO9 = 'Photo9'
64
+ PHOTO10 = 'Photo10'
65
+ DATE = 'Date'
66
+
67
+ class ColNames_Particularites(Enum):
68
+ """ Enum for the column names in the database """
69
+
70
+ KEY = 'Clé primaire'
71
+ X = 'Xlambert'
72
+ Y = 'Ylambert'
73
+ REMARK = 'Commentaires'
74
+ RIVER = 'Rivière'
75
+ PHOTO1 = 'Photo 1'
76
+ PHOTO2 = 'Photo 2'
77
+ PHOTO3 = 'Photo 3'
78
+ PHOTO4 = 'Photo 4'
79
+ PHOTO5 = 'Photo 5'
80
+ ORIENTATION = 'Orientation'
81
+ DATE = 'Date'
82
+
83
+ class ColNames_Enquetes(Enum):
84
+ """ Enum for the column names in the database """
85
+
86
+ KEY = 'Clé primaire'
87
+ X = 'XLambert'
88
+ Y = 'YLambert'
89
+ RIVER = 'Rivière'
90
+ PHOTO = 'Photo'
91
+ ORIENTATION = 'Orientation'
92
+ DATE = 'Date'
93
+
94
+ class ColNames_Profils(Enum):
95
+ """ Enum for the column names in the database """
96
+
97
+ KEY = 'Clé primaire'
98
+ X = 'XLambert'
99
+ Y = 'YLambert'
100
+ PHOTO = 'FichierImage'
101
+ RIVER = 'Rivière'
102
+ DATE = 'DateModif'
103
+
104
+ def _test_bounds(x:float, y:float, bounds:list[list[float, float], list[float, float]]) -> bool:
105
+ """ Test if the coordinates are inside the bounds
106
+
107
+ :param x: The x coordinate
108
+ :type x: float
109
+ :param y: The y coordinate
110
+ :type y: float
111
+ :param bounds: The bounds to test against - [ [xmin, xmax], [ymin, ymax] ]
112
+ :type bounds: list[list[float, float], list[float, float]]
113
+ :return: True if the coordinates are inside the bounds, False otherwise
114
+ :rtype: bool
115
+ """
116
+
117
+ if bounds is None:
118
+ return True
119
+
120
+ xmin, xmax = bounds[0]
121
+ ymin, ymax = bounds[1]
122
+
123
+ return xmin <= x <= xmax and ymin <= y <= ymax
124
+
125
+
126
+ def _sanitize_legendtext(text:str) -> str:
127
+ """ Sanitize the legend text by replacing newlines and special characters
128
+
129
+ :param text: The text to sanitize
130
+ :type text: str
131
+ :return: The sanitized text
132
+ :rtype: str
133
+ """
134
+ text = str(text)
135
+ # replace newlines and special characters
136
+ text = re.sub(r'(_x000D_\n|\n)', ' - ', text)
137
+
138
+ return text.strip()
41
139
  class ZI_Databse_Elt():
42
140
  """ Class to store the database elements """
43
141
 
@@ -211,7 +309,9 @@ class PlansTerrier(Zones):
211
309
  logging.error('No file selected or the file does not exist.')
212
310
  return
213
311
 
312
+ logging.info(f'Reading database from {self.filename}')
214
313
  self.db = pd.read_excel(self.filename, sheet_name='Plans_Terriers')
314
+ logging.info(f'Database read successfully from {self.filename}')
215
315
 
216
316
  rivers = list(self.db[ColNames_PlansTerriers.RIVER.value].unique())
217
317
  rivers.sort()
@@ -289,7 +389,7 @@ class PlansTerrier(Zones):
289
389
  curvector.add_vertex(wolfvertex(x=curelt.origx, y=curelt.endy))
290
390
  curvector.close_force()
291
391
  else:
292
- logging.error(f'File {fullpath} does not exist')
392
+ logging.debug(f'File {fullpath} does not exist')
293
393
 
294
394
  break
295
395
 
@@ -348,4 +448,643 @@ class PlansTerrier(Zones):
348
448
  super().plot(sx, sy, xmin, ymin, xmax, ymax, size)
349
449
 
350
450
  for curtexture in self.textures.values():
351
- curtexture.plot(sx, sy, xmin, ymin, xmax, ymax, size)
451
+ curtexture.plot(sx, sy, xmin, ymin, xmax, ymax, size)
452
+
453
+
454
+ class Ouvrages(PictureCollection):
455
+ """ Class to handle the "Ouvrages" -- Pictures of the structures in the ZI. """
456
+
457
+ def __init__(self, parent=None, idx: str = '', plotted: bool = True, mapviewer=None, rivers:list[str] = None) -> None:
458
+ """
459
+ Constructor for the Ouvrages class.
460
+
461
+ :param parent: The wx parent of the object
462
+ :type parent: wx.Window
463
+ :param idx: The index of the object
464
+ :type idx: str
465
+ :param plotted: If the object is plotted
466
+ :type plotted: bool
467
+ :param mapviewer: The mapviewer object
468
+ :type mapviewer: MapViewer
469
+ :param rivers: The list of rivers to display
470
+ :type rivers: list[str]
471
+ """
472
+
473
+ super().__init__(parent = parent, idx = idx, plotted = plotted, mapviewer = mapviewer)
474
+
475
+ self.wx_exists = wx.GetApp() is not None
476
+ self.db = None
477
+ self.rivers = rivers
478
+ self.initialized = False
479
+
480
+ self._columns = ColNames_Ouvrages
481
+
482
+ def check_plot(self):
483
+ """ Activate the plot if the object is initialized """
484
+
485
+ if self.initialized:
486
+ # Ask if the user wants to reload the database
487
+ if self.wx_exists:
488
+ dlg = wx.MessageDialog(None, _("Do you want to reload the database?"), _("Reload Database"),
489
+ wx.YES_NO | wx.ICON_QUESTION)
490
+ ret = dlg.ShowModal()
491
+ if ret == wx.ID_YES:
492
+ self.initialized = False
493
+ dlg.Destroy()
494
+
495
+ if not self.initialized:
496
+
497
+ # try to get the filename from the parent mapviewer
498
+ if self.mapviewer is not None:
499
+ self.filename = self.mapviewer.default_hece_database
500
+ bounds = self.mapviewer.get_bounds()
501
+
502
+ if 'bridge' in self.idx.lower() or 'pont' in self.idx.lower():
503
+ self.read_db(self.filename, sel_rivers=self.rivers, sheet_name='Ponts', bounds=bounds)
504
+ elif 'weir' in self.idx.lower() or 'seuil' in self.idx.lower():
505
+ self.read_db(self.filename, sel_rivers=self.rivers, sheet_name='Seuils', bounds=bounds)
506
+ elif 'survey' in self.idx.lower() or 'enquete' in self.idx.lower():
507
+ self.read_db(self.filename, sel_rivers=self.rivers, sheet_name='Photos', bounds=bounds)
508
+ elif 'features' in self.idx.lower() or 'particularit' in self.idx.lower():
509
+ self.read_db(self.filename, sel_rivers=self.rivers, sheet_name='Particularités', bounds=bounds)
510
+ elif 'cross' in self.idx.lower() or 'section' in self.idx.lower():
511
+ self.read_db(self.filename, sel_rivers=self.rivers, sheet_name='Sections transversales scannées', bounds=bounds)
512
+
513
+ if self.initialized:
514
+ super().check_plot()
515
+
516
+ def read_db(self, filename:str | Path,
517
+ sel_rivers: list[str] = None,
518
+ sheet_name: str = 'Ponts',
519
+ bounds: list[list[float, float], list[float, float]] = None):
520
+ """ Read the database (Excel file) and create the zones and the vectors.
521
+
522
+ The user will be prompted to select the rivers to display.
523
+
524
+ :param filename: The path to the Excel file containing the database
525
+ :type filename: str | Path
526
+ :param sel_rivers: The list of rivers to display, if None, the user will be prompted to select the rivers
527
+ :type sel_rivers: list[str] | None
528
+ :param sheet_name: The name of the sheet in the Excel file to read
529
+ :type sheet_name: str
530
+ :param bounds: The bounds of the area to display, if None, no test on coordinates will be done - [ [xmin, xmax], [ymin, ymax] ]
531
+ :type bounds: list[list[float, float], list[float, float]] | None
532
+ """
533
+
534
+ self.filename = Path(filename)
535
+
536
+ if not self.filename.exists() or filename == '':
537
+
538
+ if self.wx_exists:
539
+
540
+ dlg= wx.FileDialog(None, _("Choose a file"), defaultDir= "", wildcard="Excel (*.xlsx)|*.xlsx", style = wx.FD_OPEN)
541
+ ret = dlg.ShowModal()
542
+ if ret == wx.ID_OK:
543
+ self.filename = Path(dlg.GetPath())
544
+ dlg.Destroy()
545
+ else:
546
+ logging.error('No file selected')
547
+ dlg.Destroy()
548
+ return
549
+
550
+ else:
551
+ logging.error('No file selected or the file does not exist.')
552
+ return
553
+
554
+ try:
555
+ logging.info(f'Reading database from {self.filename}')
556
+ self.db = pd.read_excel(self.filename, sheet_name=sheet_name)
557
+ logging.info(f'Database read successfully from {self.filename}')
558
+ except ValueError as e:
559
+ logging.error(f"Error reading the Excel file: {e}")
560
+ return
561
+
562
+ rivers = list(self.db[ColNames_Ouvrages.RIVER.value].unique())
563
+ rivers.sort()
564
+
565
+ self.rivers = []
566
+
567
+ if sel_rivers is None and self.wx_exists:
568
+
569
+ with wx.MessageDialog(None, _("Choose the rivers to display"), _("Rivers"), wx.YES_NO | wx.ICON_QUESTION) as dlg:
570
+
571
+ if dlg.ShowModal() == wx.ID_YES:
572
+
573
+ with wx.MultiChoiceDialog(None, _("Choose the rivers to display"), _("Rivers"), rivers) as dlg_river:
574
+ ret = dlg_river.ShowModal()
575
+
576
+ if ret == wx.ID_OK:
577
+ for curidx in dlg_river.GetSelections():
578
+ self.rivers.append(rivers[curidx])
579
+ else:
580
+ self.rivers = rivers
581
+
582
+ elif sel_rivers is not None:
583
+
584
+ for curruver in sel_rivers:
585
+ if curruver in rivers:
586
+ self.rivers.append(curruver)
587
+ else:
588
+ logging.error(f'River {curruver} not found in the database -- Ignoring !')
589
+
590
+ self._filter_db(bounds)
591
+
592
+ self.initialized = True
593
+
594
+ def _filter_db(self, bounds: list[list[float, float], list[float, float]] = None):
595
+ """ Filter the database based on the selected rivers and bounds.
596
+
597
+ :param bounds: The bounds of the area to display, if None, no test on coordinates will be done - [ [xmin, xmax], [ymin, ymax] ]
598
+ :type bounds: list[list[float, float], list[float, float]] | None
599
+ """
600
+
601
+ if len(self.rivers) == 0:
602
+ locdb = self.db
603
+ else:
604
+ locdb = self.db[self.db[ColNames_Ouvrages.RIVER.value].isin(self.rivers)]
605
+
606
+ for id, curline in tqdm(locdb.iterrows()):
607
+ river = curline[ColNames_Ouvrages.RIVER.value]
608
+
609
+ paths = []
610
+ for col in [ColNames_Ouvrages.PHOTO1,
611
+ ColNames_Ouvrages.PHOTO2,
612
+ ColNames_Ouvrages.PHOTO3,
613
+ ColNames_Ouvrages.PHOTO4,
614
+ ColNames_Ouvrages.PHOTO5,
615
+ ColNames_Ouvrages.PHOTO6,
616
+ ColNames_Ouvrages.PHOTO7,
617
+ ColNames_Ouvrages.PHOTO8,
618
+ ColNames_Ouvrages.PHOTO9,
619
+ ColNames_Ouvrages.PHOTO10]:
620
+
621
+ fullpath = curline[col.value]
622
+
623
+ fullpath = fullpath.replace(r'\\192.168.2.185\Intranet\Data\Données et Photos de crues\Ouvrages',
624
+ str(self.filename.parent) +r'\Ouvrages')
625
+ if fullpath == '0':
626
+ break
627
+
628
+ fullpath = Path(fullpath)
629
+
630
+ if fullpath.exists():
631
+ paths.append(fullpath)
632
+ else:
633
+ logging.debug(f'File {fullpath} does not exist')
634
+
635
+ if not paths:
636
+ logging.debug(f'No valid paths found for river {river} in the database')
637
+ continue
638
+
639
+ nb = len(paths)
640
+ x1 = curline[ColNames_Ouvrages.X1.value]
641
+ x2 = curline[ColNames_Ouvrages.X2.value]
642
+ y1 = curline[ColNames_Ouvrages.Y1.value]
643
+ y2 = curline[ColNames_Ouvrages.Y2.value]
644
+
645
+ keyzone = river.strip() + '_' + paths[0].stem
646
+ # make a mosaic - max 3 pictures per row
647
+
648
+ xref = (x1 + x2) / 2
649
+ yref = (y1 + y2) / 2
650
+
651
+ if bounds is not None and not _test_bounds(xref, yref, bounds):
652
+ logging.debug(f'Coordinates are out of bounds -- Skipping line {id}')
653
+ continue
654
+
655
+ for i in range(nb):
656
+ picture = paths[i]
657
+
658
+ x = xref + (i % 3) * self._default_size
659
+ y = yref + (i // 3) * self._default_size
660
+
661
+ if x < 1000. and y < 1000.:
662
+ logging.error(f'Coordinates for river {river} are not set -- Skipping picture {picture}')
663
+ continue
664
+
665
+ self.add_picture(picture, x=x, y=y, name=picture.stem, keyzone=keyzone)
666
+
667
+ pic = self[(keyzone, picture.stem)]
668
+ pic.myprop.legendtext = _sanitize_legendtext(curline[ColNames_Ouvrages.REMARK.value])
669
+ pic.myprop.legendx = pic.centroid.x
670
+ pic.myprop.legendy = pic.centroid.y
671
+ pic.myprop.legendpriority = Font_Priority.WIDTH
672
+ pic.myprop.legendlength = 100
673
+
674
+ self.find_minmax(True)
675
+
676
+ class Particularites(Ouvrages):
677
+ """ Class to handle the "Particularités" -- Pictures of the particularities in the ZI. """
678
+
679
+ def __init__(self, parent=None, idx = '', plotted = True, mapviewer=None, rivers = None):
680
+ super().__init__(parent = parent, idx = idx, plotted = plotted, mapviewer = mapviewer, rivers = rivers)
681
+
682
+ self._columns = ColNames_Particularites
683
+
684
+ def read_db(self, filename:str | Path,
685
+ sel_rivers: list[str] = None,
686
+ sheet_name: str = 'Particularités',
687
+ bounds: list[list[float, float], list[float, float]] = None):
688
+ """ Read the database (Excel file) and create the zones and the vectors.
689
+
690
+ The user will be prompted to select the rivers to display.
691
+
692
+ :param filename: The path to the Excel file containing the database
693
+ :type filename: str | Path
694
+ :param sel_rivers: The list of rivers to display, if None, the user will be prompted to select the rivers
695
+ :type sel_rivers: list[str] | None
696
+ :param sheet_name: The name of the sheet in the Excel file to read
697
+ :type sheet_name: str
698
+ :param bounds: The bounds of the area to display, if None, no test on coordinates will be done - [ [xmin, xmax], [ymin, ymax] ]
699
+ :type bounds: list[list[float, float], list[float, float]] | None
700
+ """
701
+
702
+ self.filename = Path(filename)
703
+
704
+ if not self.filename.exists() or filename == '':
705
+
706
+ if self.wx_exists:
707
+
708
+ dlg= wx.FileDialog(None, _("Choose a file"), defaultDir= "", wildcard="Excel (*.xlsx)|*.xlsx", style = wx.FD_OPEN)
709
+ ret = dlg.ShowModal()
710
+ if ret == wx.ID_OK:
711
+ self.filename = Path(dlg.GetPath())
712
+ dlg.Destroy()
713
+ else:
714
+ logging.error('No file selected')
715
+ dlg.Destroy()
716
+ return
717
+
718
+ else:
719
+ logging.error('No file selected or the file does not exist.')
720
+ return
721
+
722
+ try:
723
+ logging.info(f'Reading database from {self.filename}')
724
+ self.db = pd.read_excel(self.filename, sheet_name=sheet_name)
725
+ logging.info(f'Database read successfully from {self.filename}')
726
+ except ValueError as e:
727
+ logging.error(f"Error reading the Excel file: {e}")
728
+ return
729
+
730
+ rivers = list(self.db[ColNames_Particularites.RIVER.value].unique())
731
+ rivers.sort()
732
+
733
+ self.rivers = []
734
+
735
+ if sel_rivers is None and self.wx_exists:
736
+
737
+ with wx.MessageDialog(None, _("Choose the rivers to display"), _("Rivers"), wx.YES_NO | wx.ICON_QUESTION) as dlg:
738
+
739
+ if dlg.ShowModal() == wx.ID_YES:
740
+
741
+ with wx.MultiChoiceDialog(None, _("Choose the rivers to display"), _("Rivers"), rivers) as dlg_river:
742
+ ret = dlg_river.ShowModal()
743
+
744
+ if ret == wx.ID_OK:
745
+ for curidx in dlg_river.GetSelections():
746
+ self.rivers.append(rivers[curidx])
747
+ else:
748
+ self.rivers = rivers
749
+
750
+ elif sel_rivers is not None:
751
+
752
+ for curruver in sel_rivers:
753
+ if curruver in rivers:
754
+ self.rivers.append(curruver)
755
+ else:
756
+ logging.error(f'River {curruver} not found in the database -- Ignoring !')
757
+
758
+ self._filter_db(bounds)
759
+
760
+ self.initialized = True
761
+
762
+ def _filter_db(self, bounds: list[list[float, float], list[float, float]] = None):
763
+ """ Filter the database based on the selected rivers and bounds.
764
+
765
+ :param bounds: The bounds of the area to display, if None, no test on coordinates will be done - [ [xmin, xmax], [ymin, ymax] ]
766
+ :type bounds: list[list[float, float], list[float, float]] |
767
+ """
768
+
769
+ if len(self.rivers) == 0:
770
+ locdb = self.db
771
+ else:
772
+ locdb = self.db[self.db[ColNames_Particularites.RIVER.value].isin(self.rivers)]
773
+
774
+ for id, curline in tqdm(locdb.iterrows()):
775
+ river = curline[ColNames_Particularites.RIVER.value]
776
+
777
+ paths = []
778
+ for col in [ColNames_Particularites.PHOTO1,
779
+ ColNames_Particularites.PHOTO2,
780
+ ColNames_Particularites.PHOTO3,
781
+ ColNames_Particularites.PHOTO4,
782
+ ColNames_Particularites.PHOTO5]:
783
+
784
+ fullpath = curline[col.value]
785
+
786
+ fullpath = fullpath.replace(r'\\192.168.2.185\Intranet\Data\Données et Photos de crues',
787
+ str(self.filename.parent))
788
+ fullpath = Path(fullpath)
789
+
790
+ if fullpath.exists():
791
+ paths.append(fullpath)
792
+ else:
793
+ logging.debug(f'File {fullpath} does not exist')
794
+
795
+ if not paths:
796
+ logging.debug(f'No valid paths found for river {river} in the database')
797
+ continue
798
+
799
+ nb = len(paths)
800
+ xref = curline[ColNames_Particularites.X.value]
801
+ yref = curline[ColNames_Particularites.Y.value]
802
+
803
+ keyzone = river.strip() + '_' + paths[0].stem
804
+ # make a mosaic - max 3 pictures per row
805
+
806
+ if bounds is not None and not _test_bounds(xref, yref, bounds):
807
+ logging.info(f'Coordinates are out of bounds -- Skipping line {id}')
808
+ continue
809
+
810
+ for i in range(nb):
811
+ picture = paths[i]
812
+ x = xref + (i % 3) * self._default_size
813
+ y = yref + (i // 3) * self._default_size
814
+
815
+ self.add_picture(picture, x=x, y=y, name=picture.stem, keyzone=keyzone)
816
+
817
+ pic = self[(keyzone, picture.stem)]
818
+ pic.myprop.legendtext = _sanitize_legendtext(curline[ColNames_Particularites.REMARK.value])
819
+ pic.myprop.legendx = pic.centroid.x
820
+ pic.myprop.legendy = pic.centroid.y
821
+ pic.myprop.legendpriority = Font_Priority.WIDTH
822
+ pic.myprop.legendlength = 100
823
+
824
+
825
+ self.find_minmax(True)
826
+
827
+ class Enquetes(Ouvrages):
828
+ """ Class to handle the "Enquêtes" -- Pictures of the surveys in the ZI. """
829
+ def __init__(self, parent=None, idx = '', plotted = True, mapviewer=None, rivers = None):
830
+ super().__init__(parent = parent, idx = idx, plotted = plotted, mapviewer = mapviewer, rivers = rivers)
831
+
832
+ self._columns = ColNames_Enquetes
833
+
834
+ def read_db(self, filename:str | Path,
835
+ sel_rivers: list[str] = None,
836
+ sheet_name: str = 'Photos',
837
+ bounds: list[list[float, float], list[float, float]] = None):
838
+ """ Read the database (Excel file) and create the zones and the vectors.
839
+
840
+ The user will be prompted to select the rivers to display.
841
+
842
+ :param filename: The path to the Excel file containing the database
843
+ :type filename: str | Path
844
+ :param sel_rivers: The list of rivers to display, if None, the user will be prompted to select the rivers
845
+ :type sel_rivers: list[str] | None
846
+ :param sheet_name: The name of the sheet in the Excel file to read
847
+ :type sheet_name: str
848
+ :param bounds: The bounds of the area to display, if None, no test on coordinates will be done - [ [xmin, xmax], [ymin, ymax] ]
849
+ :type bounds: list[list[float, float], list[float, float]] |
850
+ """
851
+
852
+ self.filename = Path(filename)
853
+
854
+ if not self.filename.exists() or filename == '':
855
+
856
+ if self.wx_exists:
857
+
858
+ dlg= wx.FileDialog(None, _("Choose a file"), defaultDir= "", wildcard="Excel (*.xlsx)|*.xlsx", style = wx.FD_OPEN)
859
+ ret = dlg.ShowModal()
860
+ if ret == wx.ID_OK:
861
+ self.filename = Path(dlg.GetPath())
862
+ dlg.Destroy()
863
+ else:
864
+ logging.error('No file selected')
865
+ dlg.Destroy()
866
+ return
867
+
868
+ else:
869
+ logging.error('No file selected or the file does not exist.')
870
+ return
871
+
872
+ try:
873
+ logging.info(f'Reading database from {self.filename}')
874
+ self.db = pd.read_excel(self.filename, sheet_name=sheet_name)
875
+ logging.info(f'Database read successfully from {self.filename}')
876
+ except ValueError as e:
877
+ logging.error(f"Error reading the Excel file: {e}")
878
+ return
879
+
880
+ rivers = list(self.db[ColNames_Enquetes.RIVER.value].unique())
881
+ rivers.sort()
882
+
883
+ self.rivers = []
884
+
885
+ if sel_rivers is None and self.wx_exists:
886
+
887
+ with wx.MessageDialog(None, _("Choose the rivers to display"), _("Rivers"), wx.YES_NO | wx.ICON_QUESTION) as dlg:
888
+
889
+ if dlg.ShowModal() == wx.ID_YES:
890
+
891
+ with wx.MultiChoiceDialog(None, _("Choose the rivers to display"), _("Rivers"), rivers) as dlg_river:
892
+ ret = dlg_river.ShowModal()
893
+
894
+ if ret == wx.ID_OK:
895
+ for curidx in dlg_river.GetSelections():
896
+ self.rivers.append(rivers[curidx])
897
+ else:
898
+ self.rivers = rivers
899
+
900
+ elif sel_rivers is not None:
901
+
902
+ for curruver in sel_rivers:
903
+ if curruver in rivers:
904
+ self.rivers.append(curruver)
905
+ else:
906
+ logging.error(f'River {curruver} not found in the database -- Ignoring !')
907
+
908
+ self._filter_db(bounds)
909
+
910
+ self.initialized = True
911
+
912
+ def _filter_db(self, bounds: list[list[float, float], list[float, float]] = None):
913
+ """ Filter the database based on the selected rivers and bounds.
914
+
915
+ :param bounds: The bounds of the area to display, if None, no test on coordinates will be done - [ [xmin, xmax], [ymin, ymax] ]
916
+ :type bounds: list[list[float, float], list[float, float]] |
917
+ """
918
+
919
+ if len(self.rivers) == 0:
920
+ locdb = self.db
921
+ else:
922
+ locdb = self.db[self.db[ColNames_Enquetes.RIVER.value].isin(self.rivers)]
923
+
924
+ for id, curline in tqdm(locdb.iterrows()):
925
+ river = curline[ColNames_Enquetes.RIVER.value]
926
+
927
+ fullpath = curline[ColNames_Enquetes.PHOTO.value]
928
+
929
+ fullpath = fullpath.replace(r'\\192.168.2.185\Intranet\Data\Données et Photos de crues',
930
+ str(self.filename.parent))
931
+ fullpath = Path(fullpath)
932
+
933
+ if not fullpath.exists():
934
+ logging.debug(f'File {fullpath} does not exist')
935
+ continue
936
+
937
+ x = curline[ColNames_Enquetes.X.value]
938
+ y = curline[ColNames_Enquetes.Y.value]
939
+
940
+ if bounds is not None and not _test_bounds(x, y, bounds):
941
+ logging.info(f'Coordinates are out of bounds -- Skipping line {id}')
942
+ continue
943
+
944
+ keyzone = river.strip()
945
+
946
+ picture = fullpath
947
+ self.add_picture(picture, x=x, y=y, name=picture.stem, keyzone=keyzone)
948
+
949
+ pic = self[(keyzone, picture.stem)]
950
+ pic.myprop.legendtext = _sanitize_legendtext(curline[ColNames_Enquetes.DATE.value])
951
+ pic.myprop.legendx = pic.centroid.x
952
+ pic.myprop.legendy = pic.centroid.y
953
+ pic.myprop.legendpriority = Font_Priority.WIDTH
954
+ pic.myprop.legendlength = 100
955
+
956
+
957
+ self.find_minmax(True)
958
+
959
+ class Profils(Ouvrages):
960
+ """ Class to handle the "Profils en travers" -- Pictures of the corss-sections in the ZI. """
961
+
962
+ def __init__(self, parent=None, idx = '', plotted = True, mapviewer=None, rivers = None):
963
+ super().__init__(parent = parent, idx = idx, plotted = plotted, mapviewer = mapviewer, rivers = rivers)
964
+
965
+ self._columns = ColNames_Profils
966
+
967
+ def read_db(self, filename:str | Path,
968
+ sel_rivers: list[str] = None,
969
+ sheet_name: str = 'Sections transversales scannées',
970
+ bounds: list[list[float, float], list[float, float]] = None):
971
+ """ Read the database (Excel file) and create the zones and the vectors.
972
+
973
+ The user will be prompted to select the rivers to display.
974
+
975
+ :param filename: The path to the Excel file containing the database
976
+ :type filename: str | Path
977
+ :param sel_rivers: The list of rivers to display, if None, the user will be prompted to select the rivers
978
+ :type sel_rivers: list[str] | None
979
+ :param sheet_name: The name of the sheet in the Excel file to read
980
+ :type sheet_name: str
981
+ :param bounds: The bounds of the area to display, if None, no test on coordinates will be done - [ [xmin, xmax], [ymin, ymax] ]
982
+ :type bounds: list[list[float, float], list[float, float]] |
983
+ """
984
+
985
+ self.filename = Path(filename)
986
+
987
+ if not self.filename.exists() or filename == '':
988
+
989
+ if self.wx_exists:
990
+
991
+ dlg= wx.FileDialog(None, _("Choose a file"), defaultDir= "", wildcard="Excel (*.xlsx)|*.xlsx", style = wx.FD_OPEN)
992
+ ret = dlg.ShowModal()
993
+ if ret == wx.ID_OK:
994
+ self.filename = Path(dlg.GetPath())
995
+ dlg.Destroy()
996
+ else:
997
+ logging.error('No file selected')
998
+ dlg.Destroy()
999
+ return
1000
+
1001
+ else:
1002
+ logging.error('No file selected or the file does not exist.')
1003
+ return
1004
+
1005
+ try:
1006
+ logging.info(f'Reading database from {self.filename}')
1007
+ self.db = pd.read_excel(self.filename, sheet_name=sheet_name)
1008
+ logging.info(f'Database read successfully from {self.filename}')
1009
+ except ValueError as e:
1010
+ logging.error(f"Error reading the Excel file: {e}")
1011
+ return
1012
+
1013
+ rivers = list(self.db[ColNames_Profils.RIVER.value].unique())
1014
+ rivers.sort()
1015
+
1016
+ self.rivers = []
1017
+
1018
+ if sel_rivers is None and self.wx_exists:
1019
+
1020
+ with wx.MessageDialog(None, _("Choose the rivers to display"), _("Rivers"), wx.YES_NO | wx.ICON_QUESTION) as dlg:
1021
+
1022
+ if dlg.ShowModal() == wx.ID_YES:
1023
+
1024
+ with wx.MultiChoiceDialog(None, _("Choose the rivers to display"), _("Rivers"), rivers) as dlg_river:
1025
+ ret = dlg_river.ShowModal()
1026
+
1027
+ if ret == wx.ID_OK:
1028
+ for curidx in dlg_river.GetSelections():
1029
+ self.rivers.append(rivers[curidx])
1030
+ else:
1031
+ self.rivers = rivers
1032
+
1033
+ elif sel_rivers is not None:
1034
+
1035
+ for curruver in sel_rivers:
1036
+ if curruver in rivers:
1037
+ self.rivers.append(curruver)
1038
+ else:
1039
+ logging.error(f'River {curruver} not found in the database -- Ignoring !')
1040
+
1041
+ self._filter_db(bounds)
1042
+
1043
+ self.initialized = True
1044
+
1045
+ def _filter_db(self, bounds: list[list[float, float], list[float, float]] = None):
1046
+ """ Filter the database based on the selected rivers and bounds.
1047
+
1048
+ :param bounds: The bounds of the area to display, if None, no test on coordinates will be done - [ [xmin, xmax], [ymin, ymax] ]
1049
+ :type bounds: list[list[float, float], list[float, float]] |
1050
+ """
1051
+
1052
+ if len(self.rivers) == 0:
1053
+ locdb = self.db
1054
+ else:
1055
+ locdb = self.db[self.db[ColNames_Profils.RIVER.value].isin(self.rivers)]
1056
+
1057
+ for id, curline in tqdm(locdb.iterrows()):
1058
+ river = curline[ColNames_Profils.RIVER.value]
1059
+
1060
+ fullpath = curline[ColNames_Profils.PHOTO.value]
1061
+
1062
+ fullpath = fullpath.replace(r'\\192.168.2.185\Intranet\Data\Données et Photos de crues\Données Profils',
1063
+ str(self.filename.parent) + r'\Profils')
1064
+ fullpath = Path(fullpath)
1065
+
1066
+ if not fullpath.exists():
1067
+ logging.debug(f'File {fullpath} does not exist')
1068
+ continue
1069
+
1070
+ x = curline[ColNames_Profils.X.value]
1071
+ y = curline[ColNames_Profils.Y.value]
1072
+
1073
+ if bounds is not None and not _test_bounds(x, y, bounds):
1074
+ logging.info(f'Coordinates are out of bounds -- Skipping line {id}')
1075
+ continue
1076
+
1077
+ keyzone = river.strip()
1078
+
1079
+ picture = fullpath
1080
+ self.add_picture(picture, x=x, y=y, name=picture.stem, keyzone=keyzone)
1081
+
1082
+ pic = self[(keyzone, picture.stem)]
1083
+ pic.myprop.legendtext = _sanitize_legendtext(curline[ColNames_Profils.DATE.value])
1084
+ pic.myprop.legendx = pic.centroid.x
1085
+ pic.myprop.legendy = pic.centroid.y
1086
+ pic.myprop.legendpriority = Font_Priority.WIDTH
1087
+ pic.myprop.legendlength = 100
1088
+
1089
+
1090
+ self.find_minmax(True)