wolfhece 2.2.34__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.
@@ -27,6 +27,8 @@ import logging
27
27
  import wx
28
28
  from typing import Union, Literal
29
29
  from pathlib import Path
30
+ import pandas as pd
31
+
30
32
 
31
33
  from .PyTranslate import _
32
34
  from .drawing_obj import Element_To_Draw
@@ -130,6 +132,7 @@ class postype(Enum):
130
132
  BY_VERTEX = 0 # sur base de coordonnées (wolfvertex)
131
133
  BY_S3D = 1 # sur base d'une position curviligne 3D (le long de la trace de la section)
132
134
  BY_INDEX = 2 # sur base d'un index de vertex
135
+
133
136
  class profile(vector):
134
137
  """
135
138
  Surcharge d'un vecteur en vue de définir un profil de rivière
@@ -156,6 +159,9 @@ class profile(vector):
156
159
  self._bankright=None
157
160
  self._bed=None
158
161
 
162
+ self._bankleft_down=None
163
+ self._bankright_down=None
164
+
159
165
  self.banksbed_postype = postype.BY_VERTEX
160
166
 
161
167
  self.refpoints={}
@@ -206,6 +212,22 @@ class profile(vector):
206
212
  def bankright(self,value):
207
213
  self._bankright=value
208
214
 
215
+ @ property
216
+ def bankleft_down(self):
217
+ return self._bankleft_down
218
+
219
+ @bankleft_down.setter
220
+ def bankleft_down(self,value):
221
+ self._bankleft_down=value
222
+
223
+ @property
224
+ def bankright_down(self):
225
+ return self._bankright_down
226
+
227
+ @bankright_down.setter
228
+ def bankright_down(self,value):
229
+ self._bankright_down=value
230
+
209
231
  @property
210
232
  def bed(self):
211
233
  return self._bed
@@ -232,6 +254,24 @@ class profile(vector):
232
254
  else:
233
255
  return self.interpolate(self._bankright, adim=False)
234
256
 
257
+ @property
258
+ def bankleft_down_vertex(self):
259
+ if self.banksbed_postype == postype.BY_VERTEX:
260
+ return self._bankleft_down
261
+ elif self.banksbed_postype == postype.BY_INDEX:
262
+ return self.myvertices[self._bankleft_down]
263
+ else:
264
+ return self.interpolate(self._bankleft_down, adim=False)
265
+
266
+ @property
267
+ def bankright_down_vertex(self):
268
+ if self.banksbed_postype == postype.BY_VERTEX:
269
+ return self._bankright_down
270
+ elif self.banksbed_postype == postype.BY_INDEX:
271
+ return self.myvertices[self._bankright_down]
272
+ else:
273
+ return self.interpolate(self._bankright_down, adim=False)
274
+
235
275
  @property
236
276
  def bed_vertex(self):
237
277
  if self.banksbed_postype == postype.BY_VERTEX:
@@ -277,6 +317,42 @@ class profile(vector):
277
317
  s3d = self.length3D
278
318
  return s3d
279
319
 
320
+ @property
321
+ def bankleft_down_s3D(self):
322
+ if self.banksbed_postype == postype.BY_S3D:
323
+ return self._bankleft_down
324
+ else:
325
+ # on dispose d'un vertex 3D et on doit retnvoyer une position s3D
326
+ # --> projection x,y sur la trace --> récupération de 's'
327
+ # --> calcul de la distance s3D via shapely LineString sz en projetant '(s,z)'
328
+ if self.bankleft_down_vertex is not None:
329
+ ls2d = self.asshapely_ls()
330
+ lssz = self.asshapely_sz()
331
+ curvert = self.bankleft_down_vertex
332
+ s = ls2d.project(Point(curvert.x, curvert.y))
333
+ s3d = lssz.project(Point(s,curvert.z))
334
+ else:
335
+ s3d = 0.
336
+ return s3d
337
+
338
+ @property
339
+ def bankright_down_s3D(self):
340
+ if self.banksbed_postype == postype.BY_S3D:
341
+ return self._bankright_down
342
+ else:
343
+ # on dispose d'un vertex 3D et on doit retnvoyer une position s3D
344
+ # --> projection x,y sur la trace --> récupération de 's'
345
+ # --> calcul de la distance s3D via shapely LineString sz en projetant '(s,z)'
346
+ if self.bankright_down_vertex is not None:
347
+ ls2d = self.asshapely_ls()
348
+ lssz = self.asshapely_sz()
349
+ curvert = self.bankright_down_vertex
350
+ s = ls2d.project(Point(curvert.x, curvert.y))
351
+ s3d = lssz.project(Point(s,curvert.z))
352
+ else:
353
+ s3d = self.length3D
354
+ return s3d
355
+
280
356
  @property
281
357
  def bed_s3D(self):
282
358
  if self.banksbed_postype == postype.BY_S3D:
@@ -527,7 +603,8 @@ class profile(vector):
527
603
  curv.x +=dx
528
604
  curv.y +=dy
529
605
 
530
- def movebankbed_index(self,which,orientation):
606
+ def movebankbed_index(self, which:Literal['left', 'right', 'bed', 'left_down', 'right_down'],
607
+ orientation:Literal['left', 'right']) -> None:
531
608
  """
532
609
  Déplacement des points de référence sur base d'un index
533
610
  Le cas échéant, adaptation du mode de stockage
@@ -539,6 +616,10 @@ class profile(vector):
539
616
  k = self.myvertices.index(self._bankright)
540
617
  elif which=='bed':
541
618
  k = self.myvertices.index(self._bed)
619
+ elif which=='left_down':
620
+ k = self.myvertices.index(self._bankleft_down)
621
+ elif which=='right_down':
622
+ k = self.myvertices.index(self._bankright_down)
542
623
 
543
624
  if orientation=='left':
544
625
  k=max(0,k-1)
@@ -551,6 +632,11 @@ class profile(vector):
551
632
  self._bankright=k
552
633
  elif which=='bed':
553
634
  self._bed=k
635
+ elif which=='left_down':
636
+ self._bankleft_down=k
637
+ elif which=='right_down':
638
+ self._bankright_down=k
639
+
554
640
  elif self.banksbed_postype == postype.BY_S3D:
555
641
  if which=='left':
556
642
  k = 0
@@ -561,6 +647,14 @@ class profile(vector):
561
647
  elif which=='bed':
562
648
  k = int(self.nbvertices/2)
563
649
  self._bed=k
650
+ elif which=='left_down':
651
+ k = 1
652
+ self._bankleft_down=k
653
+ elif which=='right_down':
654
+ k = self.nbvertices-2
655
+ self._bankright_down=k
656
+
657
+
564
658
  if self.banksbed_postype == postype.BY_INDEX:
565
659
  if which=='left':
566
660
  k = self._bankleft
@@ -568,6 +662,10 @@ class profile(vector):
568
662
  k = self._bankright
569
663
  elif which=='bed':
570
664
  k = self._bed
665
+ elif which=='left_down':
666
+ k = self._bankleft_down
667
+ elif which=='right_down':
668
+ k = self._bankright_down
571
669
 
572
670
  if orientation=='left':
573
671
  k=max(0,k-1)
@@ -580,6 +678,10 @@ class profile(vector):
580
678
  self._bankright=k
581
679
  elif which=='bed':
582
680
  self._bed=k
681
+ elif which=='left_down':
682
+ self._bankleft_down=k
683
+ elif which=='right_down':
684
+ self._bankright_down=k
583
685
 
584
686
  self.banksbed_postype = postype.BY_INDEX
585
687
 
@@ -862,7 +964,7 @@ class profile(vector):
862
964
  self.zmax = -99999
863
965
  self.sz_bankbed = None
864
966
  self.s3d_bankbed = None
865
- self.linestring = None
967
+ self.reset_linestring()
866
968
 
867
969
  self.prepared=False
868
970
 
@@ -1695,6 +1797,7 @@ class crosssections(Element_To_Draw):
1695
1797
  Gestion de sections en travers pour différents formats
1696
1798
  - SPW 2000 --> format ='2000'
1697
1799
  - SPW 2022 --> format ='2022'
1800
+ - SPW_2025 --> format ='2025_xlsx'
1698
1801
  - WOLF vecz --> format ='vecz'
1699
1802
  - WOLF sxy --> format ='sxy'
1700
1803
 
@@ -1704,6 +1807,8 @@ class crosssections(Element_To_Draw):
1704
1807
  ['left'] : wolfvertex
1705
1808
  ['bed'] : wolfvertex
1706
1809
  ['right'] : wolfvertex
1810
+ ['left_down'] : wolfvertex
1811
+ ['right_down']: wolfvertex
1707
1812
  ['cs'] : profile (surcharge de vector)
1708
1813
 
1709
1814
  Pour le moment, il est possible de lire les fichiers et d'effectuer cerrains traitements (tri selon vecteur, export gltf...).
@@ -1715,7 +1820,7 @@ class crosssections(Element_To_Draw):
1715
1820
  - cloud
1716
1821
  - cloud_all
1717
1822
 
1718
- :remark !! La classe n'est pas encore prévue pour créer des sections en travers!!
1823
+ :attention: !! La classe n'est pas encore prévue pour créer des sections en travers!!
1719
1824
 
1720
1825
  """
1721
1826
 
@@ -1724,12 +1829,13 @@ class crosssections(Element_To_Draw):
1724
1829
 
1725
1830
  def __init__(self,
1726
1831
  myfile:str = '',
1727
- format:typing.Literal['2000','2022','vecz','sxy']='2022',
1832
+ format:typing.Literal['2000','2022', '2025_xlsx','vecz','sxy']='2022',
1728
1833
  dirlaz:typing.Union[str, xyz_laz_grids] =r'D:\OneDrive\OneDrive - Universite de Liege\Crues\2021-07 Vesdre\CSC - Convention - ARNE\Data\LAZ_Vesdre\2023',
1729
1834
  mapviewer = None,
1730
1835
  idx='',
1731
1836
  plotted=True) -> None:
1732
1837
 
1838
+ assert format in ['2000','2022','2025_xlsx','vecz','sxy'], _('Format %s not supported!')%format
1733
1839
 
1734
1840
  super().__init__(idx=idx, plotted= plotted, mapviewer=mapviewer, need_for_wx=False)
1735
1841
 
@@ -1759,6 +1865,21 @@ class crosssections(Element_To_Draw):
1759
1865
  f=open(myfile,'r')
1760
1866
  lines=f.read().splitlines()
1761
1867
  f.close()
1868
+ elif format=='2025_xlsx':
1869
+ # For the 2025_xlsx format, we need to read the file using pandas
1870
+ if Path(myfile).exists() and myfile!='':
1871
+ # read the first sheet of the excel file
1872
+ # Note: header=1 means that the first row is not the header, but the second row is.
1873
+ logging.info(_('Reading cross section data from %s')%myfile)
1874
+ try:
1875
+ lines = pd.read_excel(myfile, sheet_name=0, header=1)
1876
+ logging.info(_('Cross section data read successfully from %s')%myfile)
1877
+ except Exception as e:
1878
+ logging.error(_('Error reading the file %s: %s')%(myfile, str(e)))
1879
+ lines = pd.DataFrame()
1880
+ else:
1881
+ logging.error(_('File %s does not exist!')%myfile)
1882
+ lines = []
1762
1883
  # For other formats (e.g. vecz)
1763
1884
  else:
1764
1885
  lines=[]
@@ -1773,7 +1894,80 @@ class crosssections(Element_To_Draw):
1773
1894
  self.sorted = {}
1774
1895
  self.plotted = False
1775
1896
 
1776
- if len(lines)>0:
1897
+ if isinstance(lines, pd.DataFrame):
1898
+
1899
+ self.format='2025_xlsx'
1900
+
1901
+ # We attend these columns:
1902
+ # 'Num', 'X', 'Y', 'Z', 'Code'
1903
+ if 'Num' not in lines.columns or 'X' not in lines.columns or 'Y' not in lines.columns or 'Z' not in lines.columns or 'Code' not in lines.columns:
1904
+ logging.error(_('The file %s does not contain the required columns: Num, X, Y, Z, Code')%self.filename)
1905
+ return
1906
+
1907
+ nbsects = int(lines['Num'].max())
1908
+
1909
+ # Convert X and Y to float
1910
+ lines['X'] = lines['X'].apply(lambda x: float(x.replace('.', '').replace(',', '.')) if isinstance(x, str) else float(x))
1911
+ lines['Y'] = lines['Y'].apply(lambda x: float(x.replace('.', '').replace(',', '.')) if isinstance(x, str) else float(x))
1912
+ # Convert Z to float and in meters
1913
+ # Note: The Z values are in mm, so we divide by 1000 to convert to meters.
1914
+ lines['Z'] = lines['Z'].apply(lambda x: float(x.replace('.', '').replace(',', '.'))/1000. if isinstance(x, str) else float(x)/1000.)
1915
+
1916
+ for index in range(1, nbsects + 1):
1917
+ #création d'un nouveau dictionnaire
1918
+ name = str(index)
1919
+ curdict = self.myprofiles[name] = {}
1920
+ curdict['index'] = index
1921
+ curdict['cs'] = profile(name=name, parent=self)
1922
+ cursect:profile
1923
+ cursect = curdict['cs']
1924
+
1925
+ df = lines[lines['Num'] == index]
1926
+
1927
+ for ___, row in df.iterrows():
1928
+ x = row['X']
1929
+ y = row['Y']
1930
+ z = row['Z']
1931
+ label = row['Code']
1932
+
1933
+ # Add vertex to the current section
1934
+ curvertex=wolfvertex(x,y,z)
1935
+ cursect.add_vertex(curvertex)
1936
+
1937
+ if label == 'HBG':
1938
+ if cursect.bankleft is None:
1939
+ cursect.bankleft = wolfvertex(x,y,z)
1940
+ curdict['left'] = cursect.bankleft
1941
+ else:
1942
+ logging.debug(name)
1943
+ elif label == 'THA':
1944
+ if cursect.bed is None:
1945
+ cursect.bed=wolfvertex(x,y,z)
1946
+ curdict['bed']=cursect.bed
1947
+ else:
1948
+ logging.debug(name)
1949
+ elif label == 'HBD':
1950
+ if cursect.bankright is None:
1951
+ cursect.bankright=wolfvertex(x,y,z)
1952
+ curdict['right']=cursect.bankright
1953
+ else:
1954
+ logging.debug(name)
1955
+ elif label == 'BBG':
1956
+ # This is a special case for the 2025 format, where the bank left down is defined as BBG (Bas Berge Gauche in French).
1957
+ if cursect.bankleft_down is None:
1958
+ cursect.bankleft_down = wolfvertex(x,y,z)
1959
+ curdict['left_down'] = cursect.bankleft_down
1960
+ else:
1961
+ logging.debug(name)
1962
+ elif label == 'BBD':
1963
+ # This is a special case for the 2025 format, where the bank right down is defined as BBD (Bas Berge Droite in French).
1964
+ if cursect.bankright_down is None:
1965
+ cursect.bankright_down = wolfvertex(x,y,z)
1966
+ curdict['right_down'] = cursect.bankright_down
1967
+ else:
1968
+ logging.debug(name)
1969
+
1970
+ elif len(lines)>0:
1777
1971
  if format=='2000':
1778
1972
  self.format='2000'
1779
1973
  lines.pop(0)
@@ -1953,6 +2147,7 @@ class crosssections(Element_To_Draw):
1953
2147
  cursect.bankright=wolfvertex(rbs,rbz)
1954
2148
  curdict['right']=cursect.bankright
1955
2149
 
2150
+
1956
2151
  # To make a distinction between cases for vecz
1957
2152
  elif len(lines)==0:
1958
2153
  if format=='vecz' or format=='zones':
@@ -1990,6 +2185,7 @@ class crosssections(Element_To_Draw):
1990
2185
  self.init_cloud()
1991
2186
 
1992
2187
  def init_cloud(self):
2188
+ """ Initialiaze cloud points for cross-sections. """
1993
2189
 
1994
2190
  self.cloud = cloud_vertices()
1995
2191
  self.cloud_all = cloud_vertices()
@@ -2001,6 +2197,11 @@ class crosssections(Element_To_Draw):
2001
2197
  self.cloud_all.myprop.width=4
2002
2198
 
2003
2199
  def add(self, newprofile:profile | vector):
2200
+ """ Add a new profile or vector to the cross-sections.
2201
+
2202
+ :param newprofile: A profile or vector to be added.
2203
+ :type newprofile: profile | vector
2204
+ """
2004
2205
 
2005
2206
  if isinstance(newprofile, profile):
2006
2207
  curvec = newprofile
@@ -2008,9 +2209,11 @@ class crosssections(Element_To_Draw):
2008
2209
 
2009
2210
  curdict['index']=len(self.myprofiles)
2010
2211
  curdict['cs'] = newprofile
2011
- curdict['left']= newprofile.bankleft_vertex.copy()
2012
- curdict['bed']= newprofile.bed_vertex.copy()
2013
- curdict['right']=newprofile.bankright_vertex.copy()
2212
+ curdict['left']= newprofile.bankleft_vertex.copy() if newprofile.bankleft_vertex else None
2213
+ curdict['bed']= newprofile.bed_vertex.copy() if newprofile.bed_vertex else None
2214
+ curdict['right']=newprofile.bankright_vertex.copy() if newprofile.bankright_vertex else None
2215
+ curdict['left_down'] = newprofile.bankleft_down_vertex.copy() if newprofile.bankleft_down_vertex else None
2216
+ curdict['right_down'] = newprofile.bankright_down_vertex.copy() if newprofile.bankright_down_vertex else None
2014
2217
 
2015
2218
  elif isinstance(newprofile, vector):
2016
2219
  curvec = newprofile
@@ -2020,6 +2223,8 @@ class crosssections(Element_To_Draw):
2020
2223
  curdict['left']=None
2021
2224
  curdict['bed']=None
2022
2225
  curdict['right']=None
2226
+ curdict['left_down'] = None
2227
+ curdict['right_down'] = None
2023
2228
 
2024
2229
  cursect = curdict['cs'] = profile(name=curvec.myname,parent=self)
2025
2230
  cursect.myvertices = curvec.myvertices
@@ -2035,7 +2240,12 @@ class crosssections(Element_To_Draw):
2035
2240
 
2036
2241
  def get_profile(self, which_prof, which_dict:str=None):
2037
2242
  """
2038
- Recherche et renvoi d'un profil sur base du nom ou de son index et éventuellement de la liste triée
2243
+ Recherche et renvoi d'un profil sur base du nom ou de son index et éventuellement de la liste triée.
2244
+
2245
+ :param which_prof: Nom du profil ou index du profil à rechercher.
2246
+ :type which_prof: str | int
2247
+ :param which_dict: Nom du dictionnaire trié à utiliser, si applicable.
2248
+ :type which_dict: str | None
2039
2249
  """
2040
2250
  if which_dict is not None:
2041
2251
  # on travaille sur les vecteurs triés
@@ -2059,6 +2269,7 @@ class crosssections(Element_To_Draw):
2059
2269
  return None
2060
2270
 
2061
2271
  def fillin_cloud_all(self):
2272
+ """ Fill the cloud_all with all vertices from all profiles. """
2062
2273
 
2063
2274
  curprof:profile
2064
2275
  for idx,vect in self.myprofiles.items():
@@ -2069,6 +2280,7 @@ class crosssections(Element_To_Draw):
2069
2280
  self.cloud_all.find_minmax()
2070
2281
 
2071
2282
  def update_cloud(self):
2283
+ """ Update the cloud with vertices from all profiles. """
2072
2284
 
2073
2285
  curprof:profile
2074
2286
  for idx,vect in self.myprofiles.items():
@@ -2089,6 +2301,7 @@ class crosssections(Element_To_Draw):
2089
2301
  self.cloud.find_minmax(True)
2090
2302
 
2091
2303
  def create_zone_from_banksbed(self):
2304
+ """ Create a zone from the banks and bed of the cross-sections."""
2092
2305
 
2093
2306
  if self.linked_zones is None:
2094
2307
  return
@@ -2112,11 +2325,14 @@ class crosssections(Element_To_Draw):
2112
2325
  newvec.myvertices=right
2113
2326
  newzone.add_vector(newvec)
2114
2327
 
2115
- def link_external_zones(self,mylink:Zones):
2328
+ def link_external_zones(self, mylink:Zones):
2329
+ """ Link the cross-sections to external zones. """
2330
+
2116
2331
  self.linked_zones = mylink
2117
2332
  self.find_intersect_with_link_zones()
2118
2333
 
2119
2334
  def find_intersect_with_link_zones(self):
2335
+ """ Find intersections between the cross-sections and linked zones. """
2120
2336
 
2121
2337
  if self.linked_zones is None:
2122
2338
  return
@@ -2168,6 +2384,7 @@ class crosssections(Element_To_Draw):
2168
2384
  self.update_cloud()
2169
2385
 
2170
2386
  def export_gltf(self,zmin,fn=''):
2387
+ """ Export the cross-sections to a GLTF file. """
2171
2388
 
2172
2389
  points=[]
2173
2390
  triangles=[]
@@ -2249,6 +2466,7 @@ class crosssections(Element_To_Draw):
2249
2466
  gltf.save(fn)
2250
2467
 
2251
2468
  def export_gltf_gen(self,points,triangles,fn=''):
2469
+ """ Export generated cross-sections to a GLTF file. """
2252
2470
 
2253
2471
  triangles_binary_blob = triangles.flatten().tobytes()
2254
2472
  points_binary_blob = points.tobytes()
@@ -2311,6 +2529,8 @@ class crosssections(Element_To_Draw):
2311
2529
  gltf.save(fn)
2312
2530
 
2313
2531
  def set_zones(self, forceupdate:bool=False):
2532
+ """ Set/Prepare the zones for the cross-sections. """
2533
+
2314
2534
  if forceupdate:
2315
2535
  self.myzone=None
2316
2536
  self.myzones=None
@@ -2331,10 +2551,13 @@ class crosssections(Element_To_Draw):
2331
2551
  self._prep_listogl() #FIXME : Does not work in the context of a 1D model
2332
2552
 
2333
2553
  def showstructure(self, parent=None, forceupdate=False):
2554
+ """ Show the structure of the cross-sections in the zones. """
2555
+
2334
2556
  self.set_zones()
2335
2557
  self.myzones.showstructure(parent, forceupdate)
2336
2558
 
2337
2559
  def get_upstream(self) -> dict:
2560
+ """ Get the upstream profile of the cross-sections."""
2338
2561
  curprof:profile
2339
2562
  curprof=self.myprofiles[list(self.myprofiles.keys())[0]]['cs']
2340
2563
 
@@ -2344,6 +2567,7 @@ class crosssections(Element_To_Draw):
2344
2567
  return self.myprofiles[curprof.myname]
2345
2568
 
2346
2569
  def get_downstream(self) -> dict:
2570
+ """ Get the downstream profile of the cross-sections. """
2347
2571
  curprof:profile
2348
2572
  curprof=self.myprofiles[list(self.myprofiles.keys())[0]]['cs']
2349
2573
 
@@ -2352,7 +2576,14 @@ class crosssections(Element_To_Draw):
2352
2576
 
2353
2577
  return self.myprofiles[curprof.myname]
2354
2578
 
2355
- def rename(self,fromidx,updown=True):
2579
+ def rename(self, fromidx:int, updown:bool=True):
2580
+ """" Rename the cross-sections starting from a given index.
2581
+
2582
+ :param fromidx: The index from which to start renaming.
2583
+ :type fromidx: int
2584
+ :param updown: If True, renames upstream sections; if False, renames all sections.
2585
+ :type updown: bool
2586
+ """
2356
2587
 
2357
2588
  idx=fromidx
2358
2589
 
@@ -2383,6 +2614,8 @@ class crosssections(Element_To_Draw):
2383
2614
  self.set_zones(True)
2384
2615
 
2385
2616
  def saveas(self,filename=None):
2617
+ """ Save the cross-sections to a file in the specified format. """
2618
+
2386
2619
  self.forcesuper=False
2387
2620
 
2388
2621
  if filename is not None:
@@ -2395,12 +2628,38 @@ class crosssections(Element_To_Draw):
2395
2628
  if self.filename.endswith('.vecz'):
2396
2629
  self.forcesuper=True
2397
2630
  self.saveas_wolfvec(self.filename)
2631
+
2398
2632
  elif self.format=='2000' or self.format=='2022':
2633
+
2399
2634
  with open(self.filename,'w') as f:
2400
2635
  f.write("Profile\tx\ty\tBerge\tz\n")
2401
2636
  for idx,curvect in self.myprofiles.items():
2402
2637
  curprof=curvect['cs']
2403
2638
  curprof.save(f)
2639
+
2640
+ elif self.format=='2025_xlsx':
2641
+ # For the 2025_xlsx format, we need to save the data using pandas
2642
+ data = []
2643
+ for idx, curvect in self.myprofiles.items():
2644
+ curprof = curvect['cs']
2645
+ for vertex in curprof.myvertices:
2646
+ data.append({
2647
+ 'Num': idx,
2648
+ 'X': vertex.x,
2649
+ 'Y': vertex.y,
2650
+ 'Z': vertex.z,
2651
+ 'Code': curprof.get_label(vertex)
2652
+ })
2653
+
2654
+ df = pd.DataFrame(data)
2655
+ # we need to ensure that the header begins at the second row
2656
+ df = df[['Num', 'X', 'Y', 'Z', 'Code']]
2657
+ # Save to Excel file
2658
+ with pd.ExcelWriter(self.filename, engine='openpyxl') as writer:
2659
+ # Write a blank row first, then the data starting from row 2
2660
+ pd.DataFrame([]).to_excel(writer, sheet_name='Sheet1', index=False, header=False)
2661
+ df.to_excel(writer, sheet_name='Sheet1', index=False, startrow=1)
2662
+
2404
2663
  elif self.format=='sxy':
2405
2664
  with open(self.filename,'w') as f:
2406
2665
  f.write(str(len(self.mygenprofiles)))
@@ -2433,22 +2692,28 @@ class crosssections(Element_To_Draw):
2433
2692
 
2434
2693
  def verif_bed(self):
2435
2694
  """Verification de l'existence du point lit mineur sinon attribution de l'altitude minimale"""
2695
+
2436
2696
  for idx,curvect in self.myprofiles.items():
2437
2697
  curprof=curvect['cs']
2438
2698
  if curprof.bed is None:
2439
2699
  curprof.bed = curprof.get_min()
2440
2700
 
2441
- def get_min(self,whichname='',whichprofile=None):
2701
+ def get_min(self, whichname:str = '', whichprofile:profile = None):
2702
+ """ Get the minimum vertex of a profile or cross-section. """
2703
+
2442
2704
  curvect:profile
2443
2705
  if whichname!='':
2444
2706
  curvect=self.myprofiles[whichname]['cs']
2445
2707
  curvert=curvect.myvertices
2446
- elif not whichprofile is None:
2708
+
2709
+ elif whichprofile is not None:
2447
2710
  curvect=whichprofile['cs']
2448
2711
  curvert=curvect.myvertices
2449
2712
  return sorted(curvert,key=lambda x:x.z)[0]
2450
2713
 
2451
2714
  def asshapely_ls(self):
2715
+ """ Convert the cross-sections to a MultiLineString using Shapely. """
2716
+
2452
2717
  mylines=[]
2453
2718
  curvect:profile
2454
2719
  for idx,curvect in self.myprofiles.items():
@@ -2456,6 +2721,8 @@ class crosssections(Element_To_Draw):
2456
2721
  return MultiLineString(mylines)
2457
2722
 
2458
2723
  def prepare_shapely(self):
2724
+ """ Prepare the cross-sections for Shapely operations. """
2725
+
2459
2726
  self.multils = self.asshapely_ls()
2460
2727
 
2461
2728
  def sort_along(self,vecsupport:LineString,name:str,downfirst=True):
@@ -2502,6 +2769,11 @@ class crosssections(Element_To_Draw):
2502
2769
  return len(mysorted)
2503
2770
 
2504
2771
  def find_minmax(self, update:bool=False):
2772
+ """ Find the minimum and maximum coordinates of the cross-sections.
2773
+
2774
+ :param update: If True, updates the min/max values based on the current profiles.
2775
+ :type update: bool
2776
+ """
2505
2777
 
2506
2778
  if len(self.myprofiles)==0:
2507
2779
  self.xmin = 0
@@ -2509,7 +2781,7 @@ class crosssections(Element_To_Draw):
2509
2781
  self.xmax = 0
2510
2782
  self.ymax = 0
2511
2783
  return
2512
-
2784
+
2513
2785
  if update:
2514
2786
  for idx,vect in self.myprofiles.items():
2515
2787
  vect['cs'].find_minmax(only_firstlast = True)
@@ -2529,11 +2801,16 @@ class crosssections(Element_To_Draw):
2529
2801
  self.myzones.prep_listogl()
2530
2802
 
2531
2803
  def saveas_wolfvec(self,filename:str):
2532
-
2804
+ """ Save the cross-sections as a WOLF vector file. """
2533
2805
  self.set_zones()
2534
2806
  self.myzones.saveas(filename=filename)
2535
2807
 
2536
- def select_profile(self,x,y):
2808
+ def select_profile(self, x:float, y:float):
2809
+ """ Select the profile closest to the given coordinates (x, y).
2810
+
2811
+ :param x: X coordinate of the point.
2812
+ :param y: Y coordinate of the point.
2813
+ """
2537
2814
 
2538
2815
  mypt = Point(x,y)
2539
2816
  distmin=1.e300