wolfhece 2.2.42__py3-none-any.whl → 2.2.44__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/ChatwWOLF.py +200 -0
- wolfhece/Model1D.py +1785 -11
- wolfhece/PyCrosssections.py +1536 -699
- wolfhece/PyDraw.py +61 -8
- wolfhece/PyVertexvectors.py +64 -22
- wolfhece/RatingCurve_xml.py +15 -1
- wolfhece/analyze_poly.py +198 -4
- wolfhece/apps/version.py +1 -1
- wolfhece/dike.py +265 -19
- wolfhece/eikonal.py +1 -0
- wolfhece/pywalous.py +146 -2
- wolfhece/wolf_array.py +242 -29
- {wolfhece-2.2.42.dist-info → wolfhece-2.2.44.dist-info}/METADATA +2 -2
- {wolfhece-2.2.42.dist-info → wolfhece-2.2.44.dist-info}/RECORD +17 -16
- {wolfhece-2.2.42.dist-info → wolfhece-2.2.44.dist-info}/WHEEL +0 -0
- {wolfhece-2.2.42.dist-info → wolfhece-2.2.44.dist-info}/entry_points.txt +0 -0
- {wolfhece-2.2.42.dist-info → wolfhece-2.2.44.dist-info}/top_level.txt +0 -0
wolfhece/PyCrosssections.py
CHANGED
@@ -12,7 +12,7 @@ from numpy import asarray,ndarray,arange,zeros,linspace,concatenate,unique,amin,
|
|
12
12
|
import math
|
13
13
|
import matplotlib.pyplot as plt
|
14
14
|
from shapely.geometry import LineString,MultiLineString,Point,Polygon,CAP_STYLE,Point
|
15
|
-
from shapely
|
15
|
+
from shapely import prepare, is_prepared, destroy_prepared
|
16
16
|
from shapely.ops import nearest_points,substring
|
17
17
|
from OpenGL.GL import *
|
18
18
|
import numpy as np
|
@@ -28,7 +28,7 @@ import wx
|
|
28
28
|
from typing import Union, Literal
|
29
29
|
from pathlib import Path
|
30
30
|
import pandas as pd
|
31
|
-
|
31
|
+
from numba import njit
|
32
32
|
|
33
33
|
from .PyTranslate import _
|
34
34
|
from .drawing_obj import Element_To_Draw
|
@@ -72,7 +72,8 @@ example_diffsect1="""0 10
|
|
72
72
|
42 7
|
73
73
|
50 10"""
|
74
74
|
|
75
|
-
|
75
|
+
@njit
|
76
|
+
def INTERSEC(x1:float, y1:float, x2:float, y2:float, el:float):
|
76
77
|
"""Procédure de calcul de l'abscisse d'intersection d'une altitude donnée el
|
77
78
|
dans un segment défini par ses coordonnées (x1,y1) et (x2,y2)"""
|
78
79
|
xx=1.0e10
|
@@ -90,14 +91,19 @@ def INTERSEC(x1,y1,x2,y2,el):
|
|
90
91
|
xx=(el-b)/a
|
91
92
|
return xx
|
92
93
|
|
93
|
-
def
|
94
|
+
def _shift_np(base:np.ndarray, x:float, y:float):
|
95
|
+
""" Copy a Numpy array and translate it by (x,y) in the XY plane """
|
94
96
|
|
95
97
|
copie = base.copy()
|
96
98
|
copie[:,0] += x
|
97
99
|
copie[:,1] += y
|
98
|
-
return
|
100
|
+
return copie
|
101
|
+
|
102
|
+
@njit
|
103
|
+
def _find_shift_at_xy(section:np.ndarray, x3:float, y3:float):
|
104
|
+
""" Find the Delta X and Delta Y to shift a section
|
105
|
+
to be parallel to itself at point (x3,y3) """
|
99
106
|
|
100
|
-
def find_xy(section,x3,y3):
|
101
107
|
x1 = section[0,0]
|
102
108
|
y1 = section[0,1]
|
103
109
|
x2 = section[-1,0]
|
@@ -110,9 +116,9 @@ def find_xy(section,x3,y3):
|
|
110
116
|
a = (y2-y1)/(x2-x1)
|
111
117
|
b = y1-(a*x1)
|
112
118
|
|
113
|
-
vecteur = ([1,a])
|
114
|
-
normale = ([-a,1])
|
115
|
-
normale_opposée = ([a,-1])
|
119
|
+
# vecteur = ([1,a])
|
120
|
+
# normale = ([-a,1])
|
121
|
+
# normale_opposée = ([a,-1])
|
116
122
|
|
117
123
|
c = -1/a
|
118
124
|
d = y3-(c*x3)
|
@@ -135,19 +141,40 @@ class postype(Enum):
|
|
135
141
|
|
136
142
|
class profile(vector):
|
137
143
|
"""
|
138
|
-
|
144
|
+
Subclass of a vector to define a river profile/cross-section.
|
145
|
+
|
146
|
+
Some attributes are added to manage specific points of the profile (banks, bed).
|
147
|
+
The bankleft, bankright, bed, bankleft_down, bankright_down attributes can be wolfvertex,
|
148
|
+
index of vertex or s3D position depending on banksbed_postype.
|
149
|
+
|
150
|
+
If a profile is part of a croossection object, the parent attribute points to it.
|
151
|
+
If the profiles are ordered along a river, the up and down attributes point to the upstream and downstream profiles.
|
139
152
|
|
140
|
-
|
153
|
+
sdatum and zdatum are offsets to be added to the curvilinear abscissa and altitudes of the profile.
|
154
|
+
|
155
|
+
The stored coordinates are in 3D (x, y, z). If data_sect is provided at initialization, we expect (X, Z) data.
|
156
|
+
The x coordinate is the curvilinear abscissa along the section and z is the altitude (y is set to 0).
|
157
|
+
|
158
|
+
ATTENTION:
|
159
|
+
Normally, a profile is a succession of points in a vertical plane, aligned along a trace.
|
160
|
+
In this implementation, the points can be in 3D space, not necessarily aligned.
|
161
|
+
Some routines have a 'cumul' parameter to compute the curvilinear abscissa along the section.
|
162
|
+
If 'cumul' is True, the real curvilinear abscissa is computed.
|
163
|
+
If 'cumul' is False, the distance **between each point and the first point** is computed.
|
141
164
|
"""
|
142
165
|
|
143
|
-
def __init__(self, name, data_sect='',parent=None) -> None:
|
166
|
+
def __init__(self, name:str, data_sect:str | np.ndarray | list | vector = '', parent = None) -> None:
|
167
|
+
""" Initialization of a profile
|
168
|
+
|
169
|
+
:param name: name of the profile
|
170
|
+
:param data_sect: section data as a string (x z) separated by spaces and line breaks
|
171
|
+
:param parent: parent object (crosssections)
|
172
|
+
"""
|
173
|
+
|
144
174
|
super().__init__(name=name)
|
145
175
|
|
146
|
-
if data_sect
|
147
|
-
|
148
|
-
values=curline.split(' ')
|
149
|
-
curvert=wolfvertex(float(values[0]),0.,float(values[1]))
|
150
|
-
self.add_vertex(curvert)
|
176
|
+
if not isinstance(data_sect, (str, np.ndarray, list, vector)):
|
177
|
+
raise TypeError(_('data_sect must be a string, numpy array or list'))
|
151
178
|
|
152
179
|
# Les positions de référence sont intanciées à None
|
153
180
|
# Elles sont accessibles comme "property" de l'objet --> valeur "brute"
|
@@ -156,9 +183,6 @@ class profile(vector):
|
|
156
183
|
# - bankleft_s3D, bankright_s3D, bed_s3D qui retournent la position curvi 3D quel que soit le format de stockage
|
157
184
|
# - bankleft_sz, bankright_sz, bed_sz qui retournent un couple (s,z) quel que soit le format de stockage
|
158
185
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
186
|
self._bankleft=None
|
163
187
|
self._bankright=None
|
164
188
|
self._bed=None
|
@@ -174,27 +198,85 @@ class profile(vector):
|
|
174
198
|
self.up:profile = None
|
175
199
|
self.down:profile = None
|
176
200
|
|
177
|
-
self.laz=False
|
201
|
+
self.laz = False # if True, points from LAZ file are loaded around the section
|
178
202
|
|
179
203
|
if parent is not None:
|
180
204
|
assert isinstance(parent,crosssections), _('Bad type of parent object')
|
205
|
+
|
181
206
|
self.parent=parent
|
182
207
|
|
183
|
-
self.zdatum = 0.
|
184
|
-
self.add_zdatum=False
|
208
|
+
# self.zdatum = 0.
|
209
|
+
# self.add_zdatum = False
|
185
210
|
|
186
|
-
self.sdatum = 0.
|
187
|
-
self.add_sdatum=False
|
211
|
+
# self.sdatum = 0.
|
212
|
+
# self.add_sdatum = False
|
188
213
|
|
189
214
|
self.orient = None
|
190
215
|
|
191
216
|
self.sz = None
|
192
217
|
self.sz_bankbed = None
|
193
218
|
self.s3d_bankbed = None
|
194
|
-
self.prepared=False # if True, one can call self.sz instead of self.get_sz
|
219
|
+
self.prepared = False # if True, one can call self.sz instead of self.get_sz
|
220
|
+
|
221
|
+
self.smin = -99999
|
222
|
+
self.smax = -99999
|
223
|
+
self.zmin = -99999
|
224
|
+
self.zmax = -99999
|
225
|
+
|
226
|
+
# Uniform discharge
|
227
|
+
self.q_slope = None # Based on imposed slope and roughness
|
228
|
+
self.q_down = 0. # Based on downstream slope (if oriented sections exist) and roughness
|
229
|
+
self.q_up = 0. # Based on upstream slope (if oriented sections exist) and roughness
|
230
|
+
self.q_centered = 0. # Based on centered slope (if oriented sections exist) and roughness
|
231
|
+
|
232
|
+
self.wetarea = None
|
233
|
+
self.wetperimeter = None
|
234
|
+
self.hydraulicradius = None
|
235
|
+
self.waterdepth = None
|
236
|
+
self.localwidth = None
|
237
|
+
self.criticaldischarge = None
|
238
|
+
|
239
|
+
self._linestring_sz = None
|
240
|
+
self._linestring_s3dz = None
|
241
|
+
|
242
|
+
if isinstance(data_sect, str):
|
243
|
+
if data_sect != '':
|
244
|
+
for curline in data_sect.splitlines():
|
245
|
+
values=curline.split(' ')
|
246
|
+
curvert=wolfvertex(float(values[0]), 0., float(values[1]))
|
247
|
+
self.add_vertex(curvert)
|
248
|
+
self.prepare()
|
249
|
+
else:
|
250
|
+
logging.debug(_('Empty data_sect string -- no vertex added'))
|
251
|
+
elif isinstance(data_sect, np.ndarray):
|
252
|
+
if data_sect.shape[1]==2:
|
253
|
+
for cur in data_sect:
|
254
|
+
curvert=wolfvertex(float(cur[0]), 0., float(cur[1]))
|
255
|
+
self.add_vertex(curvert)
|
256
|
+
self.prepare()
|
257
|
+
else:
|
258
|
+
logging.error(_('Bad shape of input array in data_sect -- no vertex added'))
|
259
|
+
elif isinstance(data_sect, list):
|
260
|
+
if all(isinstance(item, (list, tuple, np.ndarray)) and len(item)==2 for item in data_sect):
|
261
|
+
for cur in data_sect:
|
262
|
+
curvert=wolfvertex(float(cur[0]), 0., float(cur[1]))
|
263
|
+
self.add_vertex(curvert)
|
264
|
+
self.prepare()
|
265
|
+
elif all(isinstance(item, wolfvertex) for item in data_sect):
|
266
|
+
self.add_vertex(data_sect)
|
267
|
+
self.prepare()
|
268
|
+
else:
|
269
|
+
logging.error(_('Bad shape of input list in data_sect -- no vertex added'))
|
270
|
+
elif isinstance(data_sect, vector):
|
271
|
+
self.add_vertex(data_sect.myvertices.copy())
|
272
|
+
self.prepare()
|
195
273
|
|
196
274
|
@property
|
197
275
|
def linked_arrays(self):
|
276
|
+
""" Return the linked arrays from parent crosssection if any.
|
277
|
+
Useful to plot associated data along the section.
|
278
|
+
"""
|
279
|
+
|
198
280
|
if self.parent is not None:
|
199
281
|
return self.parent.get_linked_arrays()
|
200
282
|
else:
|
@@ -202,46 +284,60 @@ class profile(vector):
|
|
202
284
|
|
203
285
|
@property
|
204
286
|
def bankleft(self):
|
287
|
+
""" Return the bankleft reference point in its raw format (wolfvertex, index or s3D) """
|
205
288
|
return self._bankleft
|
206
289
|
|
207
290
|
@bankleft.setter
|
208
291
|
def bankleft(self,value):
|
209
|
-
|
292
|
+
""" Set the bankleft reference point in its raw format (wolfvertex, index or s3D) """
|
293
|
+
self._bankleft = value
|
210
294
|
|
211
295
|
@property
|
212
296
|
def bankright(self):
|
297
|
+
""" Return the bankright reference point in its raw format (wolfvertex, index or s3D) """
|
213
298
|
return self._bankright
|
214
299
|
|
215
300
|
@bankright.setter
|
216
301
|
def bankright(self,value):
|
217
|
-
|
302
|
+
""" Set the bankright reference point in its raw format (wolfvertex, index or s3D) """
|
303
|
+
self._bankright = value
|
218
304
|
|
219
|
-
@
|
305
|
+
@property
|
220
306
|
def bankleft_down(self):
|
307
|
+
""" Return the bankleft_down reference point in its raw format (wolfvertex, index or s3D) """
|
221
308
|
return self._bankleft_down
|
222
309
|
|
223
310
|
@bankleft_down.setter
|
224
311
|
def bankleft_down(self,value):
|
225
|
-
|
312
|
+
""" Set the bankleft_down reference point in its raw format (wolfvertex, index or s3D) """
|
313
|
+
self._bankleft_down = value
|
226
314
|
|
227
315
|
@property
|
228
316
|
def bankright_down(self):
|
317
|
+
""" Return the bankright_down reference point in its raw format (wolfvertex, index or s3D) """
|
229
318
|
return self._bankright_down
|
230
319
|
|
231
320
|
@bankright_down.setter
|
232
321
|
def bankright_down(self,value):
|
233
|
-
|
322
|
+
""" Set the bankright_down reference point in its raw format (wolfvertex, index or s3D) """
|
323
|
+
self._bankright_down = value
|
234
324
|
|
235
325
|
@property
|
236
326
|
def bed(self):
|
327
|
+
""" Return the bed reference point in its raw format (wolfvertex, index or s3D) """
|
237
328
|
return self._bed
|
238
329
|
|
239
330
|
@bed.setter
|
240
331
|
def bed(self,value):
|
241
|
-
|
332
|
+
""" Set the bed reference point in its raw format (wolfvertex, index or s3D) """
|
333
|
+
self._bed = value
|
242
334
|
|
243
335
|
@property
|
244
336
|
def bankleft_vertex(self):
|
337
|
+
""" Return the bankleft reference point as a wolfvertex whatever the storage format """
|
338
|
+
if self._bankleft is None:
|
339
|
+
return None
|
340
|
+
|
245
341
|
if self.banksbed_postype == postype.BY_VERTEX:
|
246
342
|
return self._bankleft
|
247
343
|
elif self.banksbed_postype == postype.BY_INDEX:
|
@@ -251,6 +347,10 @@ class profile(vector):
|
|
251
347
|
|
252
348
|
@property
|
253
349
|
def bankright_vertex(self):
|
350
|
+
""" Return the bankright reference point as a wolfvertex whatever the storage format """
|
351
|
+
if self._bankright is None:
|
352
|
+
return None
|
353
|
+
|
254
354
|
if self.banksbed_postype == postype.BY_VERTEX:
|
255
355
|
return self._bankright
|
256
356
|
elif self.banksbed_postype == postype.BY_INDEX:
|
@@ -260,6 +360,10 @@ class profile(vector):
|
|
260
360
|
|
261
361
|
@property
|
262
362
|
def bankleft_down_vertex(self):
|
363
|
+
""" Return the bankleft_down reference point as a wolfvertex whatever the storage format """
|
364
|
+
if self._bankleft_down is None:
|
365
|
+
return None
|
366
|
+
|
263
367
|
if self.banksbed_postype == postype.BY_VERTEX:
|
264
368
|
return self._bankleft_down
|
265
369
|
elif self.banksbed_postype == postype.BY_INDEX:
|
@@ -269,6 +373,10 @@ class profile(vector):
|
|
269
373
|
|
270
374
|
@property
|
271
375
|
def bankright_down_vertex(self):
|
376
|
+
""" Return the bankright_down reference point as a wolfvertex whatever the storage format """
|
377
|
+
if self._bankright_down is None:
|
378
|
+
return None
|
379
|
+
|
272
380
|
if self.banksbed_postype == postype.BY_VERTEX:
|
273
381
|
return self._bankright_down
|
274
382
|
elif self.banksbed_postype == postype.BY_INDEX:
|
@@ -278,6 +386,10 @@ class profile(vector):
|
|
278
386
|
|
279
387
|
@property
|
280
388
|
def bed_vertex(self):
|
389
|
+
""" Return the bed reference point as a wolfvertex whatever the storage format """
|
390
|
+
if self._bed is None:
|
391
|
+
return None
|
392
|
+
|
281
393
|
if self.banksbed_postype == postype.BY_VERTEX:
|
282
394
|
return self._bed
|
283
395
|
elif self.banksbed_postype == postype.BY_INDEX:
|
@@ -287,6 +399,7 @@ class profile(vector):
|
|
287
399
|
|
288
400
|
@property
|
289
401
|
def bankleft_s3D(self):
|
402
|
+
""" Return the bankleft reference point as a s3D position whatever the storage format """
|
290
403
|
if self.banksbed_postype == postype.BY_S3D:
|
291
404
|
return self._bankleft
|
292
405
|
else:
|
@@ -294,8 +407,8 @@ class profile(vector):
|
|
294
407
|
# --> projection x,y sur la trace --> récupération de 's'
|
295
408
|
# --> calcul de la distance s3D via shapely LineString sz en projetant '(s,z)'
|
296
409
|
if self.bankleft_vertex is not None:
|
297
|
-
ls2d = self.
|
298
|
-
lssz = self.
|
410
|
+
ls2d = self.linestring
|
411
|
+
lssz = self.linestring_sz
|
299
412
|
curvert = self.bankleft_vertex
|
300
413
|
s = ls2d.project(Point(curvert.x, curvert.y))
|
301
414
|
s3d = lssz.project(Point(s,curvert.z))
|
@@ -305,6 +418,7 @@ class profile(vector):
|
|
305
418
|
|
306
419
|
@property
|
307
420
|
def bankright_s3D(self):
|
421
|
+
""" Return the bankright reference point as a s3D position whatever the storage format """
|
308
422
|
if self.banksbed_postype == postype.BY_S3D:
|
309
423
|
return self._bankright
|
310
424
|
else:
|
@@ -312,8 +426,8 @@ class profile(vector):
|
|
312
426
|
# --> projection x,y sur la trace --> récupération de 's'
|
313
427
|
# --> calcul de la distance s3D via shapely LineString sz en projetant '(s,z)'
|
314
428
|
if self.bankright_vertex is not None:
|
315
|
-
ls2d = self.
|
316
|
-
lssz = self.
|
429
|
+
ls2d = self.linestring
|
430
|
+
lssz = self.linestring_sz
|
317
431
|
curvert = self.bankright_vertex
|
318
432
|
s = ls2d.project(Point(curvert.x, curvert.y))
|
319
433
|
s3d = lssz.project(Point(s,curvert.z))
|
@@ -323,6 +437,7 @@ class profile(vector):
|
|
323
437
|
|
324
438
|
@property
|
325
439
|
def bankleft_down_s3D(self):
|
440
|
+
""" Return the bankleft_down reference point as a s3D position whatever the storage format """
|
326
441
|
if self.banksbed_postype == postype.BY_S3D:
|
327
442
|
return self._bankleft_down
|
328
443
|
else:
|
@@ -330,8 +445,8 @@ class profile(vector):
|
|
330
445
|
# --> projection x,y sur la trace --> récupération de 's'
|
331
446
|
# --> calcul de la distance s3D via shapely LineString sz en projetant '(s,z)'
|
332
447
|
if self.bankleft_down_vertex is not None:
|
333
|
-
ls2d = self.
|
334
|
-
lssz = self.
|
448
|
+
ls2d = self.linestring
|
449
|
+
lssz = self.linestring_sz
|
335
450
|
curvert = self.bankleft_down_vertex
|
336
451
|
s = ls2d.project(Point(curvert.x, curvert.y))
|
337
452
|
s3d = lssz.project(Point(s,curvert.z))
|
@@ -341,6 +456,7 @@ class profile(vector):
|
|
341
456
|
|
342
457
|
@property
|
343
458
|
def bankright_down_s3D(self):
|
459
|
+
""" Return the bankright_down reference point as a s3D position whatever the storage format """
|
344
460
|
if self.banksbed_postype == postype.BY_S3D:
|
345
461
|
return self._bankright_down
|
346
462
|
else:
|
@@ -348,8 +464,8 @@ class profile(vector):
|
|
348
464
|
# --> projection x,y sur la trace --> récupération de 's'
|
349
465
|
# --> calcul de la distance s3D via shapely LineString sz en projetant '(s,z)'
|
350
466
|
if self.bankright_down_vertex is not None:
|
351
|
-
ls2d = self.
|
352
|
-
lssz = self.
|
467
|
+
ls2d = self.linestring
|
468
|
+
lssz = self.linestring_sz
|
353
469
|
curvert = self.bankright_down_vertex
|
354
470
|
s = ls2d.project(Point(curvert.x, curvert.y))
|
355
471
|
s3d = lssz.project(Point(s,curvert.z))
|
@@ -359,6 +475,7 @@ class profile(vector):
|
|
359
475
|
|
360
476
|
@property
|
361
477
|
def bed_s3D(self):
|
478
|
+
""" Return the bed reference point as a s3D position whatever the storage format """
|
362
479
|
if self.banksbed_postype == postype.BY_S3D:
|
363
480
|
return self._bed
|
364
481
|
else:
|
@@ -366,8 +483,8 @@ class profile(vector):
|
|
366
483
|
# --> projection x,y sur la trace --> récupération de 's'
|
367
484
|
# --> calcul de la distance s3D via shapely LineString sz en projetant '(s,z)'
|
368
485
|
if self.bed_vertex is not None:
|
369
|
-
ls2d = self.
|
370
|
-
lssz = self.
|
486
|
+
ls2d = self.linestring
|
487
|
+
lssz = self.linestring_sz
|
371
488
|
curvert = self.bed_vertex
|
372
489
|
s = ls2d.project(Point(curvert.x, curvert.y))
|
373
490
|
s3d = lssz.project(Point(s,curvert.z))
|
@@ -377,35 +494,40 @@ class profile(vector):
|
|
377
494
|
|
378
495
|
@property
|
379
496
|
def bankleft_sz(self):
|
380
|
-
|
497
|
+
""" Return the bankleft reference point as a (s,z) tuple whatever the storage format """
|
498
|
+
ls2d = self.linestring
|
381
499
|
curvert = self.bankleft_vertex
|
382
500
|
s = ls2d.project(Point(curvert.x, curvert.y))
|
383
501
|
return s, curvert.z
|
384
502
|
|
385
503
|
@property
|
386
504
|
def bankright_sz(self):
|
387
|
-
|
505
|
+
""" Return the bankright reference point as a (s,z) tuple whatever the storage format """
|
506
|
+
ls2d = self.linestring
|
388
507
|
curvert = self.bankright_vertex
|
389
508
|
s = ls2d.project(Point(curvert.x, curvert.y))
|
390
509
|
return s, curvert.z
|
391
510
|
|
392
511
|
@property
|
393
512
|
def bed_sz(self):
|
394
|
-
|
513
|
+
""" Return the bed reference point as a (s,z) tuple whatever the storage format """
|
514
|
+
ls2d = self.linestring
|
395
515
|
curvert = self.bed_vertex
|
396
516
|
s = ls2d.project(Point(curvert.x, curvert.y))
|
397
517
|
return s, curvert.z
|
398
518
|
|
399
519
|
@property
|
400
520
|
def bankleft_down_sz(self):
|
401
|
-
|
521
|
+
""" Return the bankleft_down reference point as a (s,z) tuple whatever the storage format """
|
522
|
+
ls2d = self.linestring
|
402
523
|
curvert = self.bankleft_down_vertex
|
403
524
|
s = ls2d.project(Point(curvert.x, curvert.y))
|
404
525
|
return s, curvert.z
|
405
526
|
|
406
527
|
@property
|
407
528
|
def bankright_down_sz(self):
|
408
|
-
|
529
|
+
""" Return the bankright_down reference point as a (s,z) tuple whatever the storage format """
|
530
|
+
ls2d = self.linestring
|
409
531
|
curvert = self.bankright_down_vertex
|
410
532
|
s = ls2d.project(Point(curvert.x, curvert.y))
|
411
533
|
return s, curvert.z
|
@@ -414,17 +536,21 @@ class profile(vector):
|
|
414
536
|
|
415
537
|
self.update_lengths()
|
416
538
|
|
417
|
-
# for curl in self._lengthparts3D
|
418
539
|
|
419
|
-
def triangulation_gltf(self, zmin):
|
540
|
+
def triangulation_gltf(self, zmin:float):
|
420
541
|
"""
|
421
542
|
Génération d'un info de triangulation pour sortie au format GLTF --> Blender
|
422
|
-
|
543
|
+
|
544
|
+
La triangulation est constituée de la section et d'une base plane à l'altitude zmin, a priori sous le lit de la rivière.
|
545
|
+
|
546
|
+
:param zmin: position d'altitude minimale de la triangulation
|
423
547
|
"""
|
548
|
+
|
424
549
|
section = self.asnparray3d()
|
425
550
|
base = section.copy()
|
426
551
|
base[:,2] = zmin
|
427
|
-
|
552
|
+
|
553
|
+
points = np.concatenate((section,base), axis=0)
|
428
554
|
triangles=[]
|
429
555
|
nb=self.nbvertices
|
430
556
|
for j in range (nb-1):
|
@@ -436,15 +562,20 @@ class profile(vector):
|
|
436
562
|
c = b
|
437
563
|
b = a+1
|
438
564
|
|
439
|
-
return points,triangles
|
565
|
+
return points, triangles
|
440
566
|
|
441
|
-
def triangulation_ponts(self,x,y, zmax):
|
567
|
+
def triangulation_ponts(self, x:float ,y:float, zmax:float):
|
442
568
|
"""
|
443
569
|
Triangulation d'une section de pont
|
570
|
+
|
571
|
+
:param x: coordonnée X du point de référence du pont
|
572
|
+
:param y: coordonnée Y du point de référence du pont
|
573
|
+
:param zmax: altitude du tablier du pont
|
444
574
|
"""
|
575
|
+
|
445
576
|
section = self.asnparray3d()
|
446
|
-
x1,y1=
|
447
|
-
parallele =
|
577
|
+
x1,y1=_find_shift_at_xy(section,x,y)
|
578
|
+
parallele = _shift_np(section,x1,y1)
|
448
579
|
|
449
580
|
base1 = section.copy()
|
450
581
|
base1[:,2]= zmax
|
@@ -490,29 +621,60 @@ class profile(vector):
|
|
490
621
|
triangles.append([len(section)-1,len(section)*2-1,len(section)*4-1])
|
491
622
|
return(np.asarray([[curpt[0],curpt[2],-curpt[1]] for curpt in points],dtype=np.float32),np.asarray(triangles,dtype=np.uint32))
|
492
623
|
|
493
|
-
def
|
624
|
+
def _set_orient(self):
|
494
625
|
"""
|
495
626
|
Calcul du vecteur directeur de la section sur base des points extrêmes
|
496
627
|
"""
|
628
|
+
|
629
|
+
if self.nbvertices<2:
|
630
|
+
logging.error(_('Not enough vertices to define an orientation'))
|
631
|
+
self.orient = np.zeros(2)
|
632
|
+
return
|
633
|
+
|
497
634
|
self.orient = asarray([self.myvertices[-1].x-self.myvertices[0].x,
|
498
635
|
self.myvertices[-1].y-self.myvertices[0].y])
|
499
|
-
self.orient = self.orient /np.linalg.norm(self.orient)
|
500
636
|
|
501
|
-
|
637
|
+
dist = np.linalg.norm(self.orient)
|
638
|
+
if dist==0.:
|
639
|
+
logging.error(_('Bad vertices to define an orientation -- identical points'))
|
640
|
+
self.orient = np.zeros(2)
|
641
|
+
return
|
642
|
+
|
643
|
+
self.orient = self.orient / np.linalg.norm(self.orient)
|
644
|
+
|
645
|
+
@property
|
646
|
+
def trace_increments(self):
|
647
|
+
""" Return the orientation vector of the section (unit vector in the XY plane)
|
648
|
+
"""
|
649
|
+
if self.orient is None:
|
650
|
+
self._set_orient()
|
651
|
+
return self.orient
|
652
|
+
|
653
|
+
def get_xy_from_s(self, s:float) -> tuple[float, float]:
|
502
654
|
"""
|
503
655
|
Récupération d'un tuple (x,y) sur base d'une distance 2D 's' orientée dans l'axe de la section
|
656
|
+
|
657
|
+
:param s: distance curviligne 2D le long de la section supposée plane étant donné l'utilisation de l'orientation
|
504
658
|
"""
|
659
|
+
|
505
660
|
if self.orient is None:
|
506
|
-
self.
|
661
|
+
self._set_orient()
|
507
662
|
|
508
663
|
return self.myvertices[0].x+self.orient[0]*s,self.myvertices[0].y+self.orient[1]*s
|
509
664
|
|
510
|
-
def set_vertices_sz_orient(self, sz:np.ndarray, xy1:np.ndarray, xy2:np.ndarray):
|
665
|
+
def set_vertices_sz_orient(self, sz:np.ndarray | tuple | list, xy1:np.ndarray | list | tuple, xy2:np.ndarray | list | tuple):
|
511
666
|
"""
|
512
667
|
Ajout de vertices depuis :
|
513
|
-
- une matrice numpy (s,z) -- shape = (nb_vert,2)
|
514
|
-
-
|
515
|
-
-
|
668
|
+
- une matrice numpy (s,z) -- shape = (nb_vert,2) ou une liste ou un tuple convertibles en numpy array
|
669
|
+
- un point source [x,y]
|
670
|
+
- un point visé [x,y]
|
671
|
+
|
672
|
+
Le point source correspond au vertex de s=0.
|
673
|
+
Le point visé sert uniquement à définir l'orientation de la section.
|
674
|
+
|
675
|
+
:param sz: matrice numpy (s,z) -- shape = (nb_vert,2)
|
676
|
+
:param xy1: point source [x,y]
|
677
|
+
:param xy2: point visé [x,y]
|
516
678
|
"""
|
517
679
|
|
518
680
|
if isinstance(sz,list):
|
@@ -539,19 +701,36 @@ class profile(vector):
|
|
539
701
|
for cur in sz:
|
540
702
|
x, y = xy1[0] + dx*cur[0], xy1[1] + dy*cur[0]
|
541
703
|
self.add_vertex(wolfvertex(x, y, float(cur[1])))
|
704
|
+
else:
|
705
|
+
logging.error(_('Bad input points in set_vertices_sz_orient -- identical points'))
|
706
|
+
else:
|
707
|
+
logging.error(_('Bad shape of input arrays in set_vertices_sz_orient'))
|
542
708
|
|
543
|
-
def get_laz_around(self,length_buffer=10.):
|
709
|
+
def get_laz_around(self, length_buffer:float = 10.):
|
544
710
|
"""
|
545
711
|
Récupération de points LAZ autour de la section
|
712
|
+
|
713
|
+
:param length_buffer: distance latérale [m] autour de la section pour la récupération des points LAZ
|
546
714
|
"""
|
547
|
-
|
548
|
-
|
549
|
-
|
715
|
+
|
716
|
+
self.laz = False
|
717
|
+
|
718
|
+
if self.parent is None:
|
719
|
+
logging.error(_('No parent crosssection object -- cannot get LAZ points'))
|
720
|
+
return
|
721
|
+
|
722
|
+
if self.parent.gridlaz is None:
|
723
|
+
logging.error(_('No LAZ grid in parent crosssection object -- cannot get LAZ points'))
|
724
|
+
return
|
725
|
+
|
726
|
+
mypoly = self.linestring.buffer(length_buffer, cap_style=CAP_STYLE.square)
|
727
|
+
mybounds = ((mypoly.bounds[0], mypoly.bounds[2]), (mypoly.bounds[1], mypoly.bounds[3]))
|
550
728
|
|
551
729
|
myxyz = self.parent.gridlaz.scan(mybounds)
|
552
730
|
|
553
|
-
prep_poly =
|
731
|
+
prep_poly = prepare(mypoly)
|
554
732
|
mytests = [prep_poly.contains(Point(cur[:3])) for cur in myxyz]
|
733
|
+
destroy_prepared(prep_poly)
|
555
734
|
|
556
735
|
self.usedlaz = np.asarray(myxyz[mytests])
|
557
736
|
|
@@ -586,13 +765,23 @@ class profile(vector):
|
|
586
765
|
|
587
766
|
self.laz=True
|
588
767
|
|
589
|
-
def plot_laz(self,length_buffer=5.,fig:Figure=None,ax:Axes=None,show=False):
|
768
|
+
def plot_laz(self, length_buffer:float = 5., fig:Figure=None, ax:Axes=None, show=False):
|
590
769
|
"""
|
591
770
|
Dessin des points LAZ sur le graphique Matplotlib
|
771
|
+
|
772
|
+
:param length_buffer: distance latérale [m] autour de la section pour la récupération des points LAZ
|
773
|
+
:param fig: figure Matplotlib
|
774
|
+
:param ax: axes Matplotlib
|
775
|
+
:param show: if True, show the figure
|
592
776
|
"""
|
777
|
+
|
593
778
|
if not self.laz:
|
594
779
|
self.get_laz_around(length_buffer)
|
595
780
|
|
781
|
+
if not self.laz:
|
782
|
+
logging.error(_('No LAZ points to plot'))
|
783
|
+
return None
|
784
|
+
|
596
785
|
if ax is None:
|
597
786
|
fig = plt.figure()
|
598
787
|
ax=fig.add_subplot(111)
|
@@ -606,14 +795,17 @@ class profile(vector):
|
|
606
795
|
|
607
796
|
return np.min(self.usedlaz[:,2]),np.max(self.usedlaz[:,2])
|
608
797
|
|
609
|
-
def slide_vertex(self,s):
|
798
|
+
def slide_vertex(self, s:float):
|
610
799
|
"""
|
611
800
|
Glissement des vertices d'une constante 's'
|
801
|
+
|
802
|
+
:param s: distance de glissement dans l'axe de la section
|
612
803
|
"""
|
613
804
|
|
614
805
|
if self.orient is None:
|
615
|
-
self.
|
806
|
+
self._set_orient()
|
616
807
|
|
808
|
+
# increments de position
|
617
809
|
dx = self.orient[0] *s
|
618
810
|
dy = self.orient[1] *s
|
619
811
|
|
@@ -621,11 +813,15 @@ class profile(vector):
|
|
621
813
|
curv.x +=dx
|
622
814
|
curv.y +=dy
|
623
815
|
|
624
|
-
def movebankbed_index(self,
|
816
|
+
def movebankbed_index(self,
|
817
|
+
which:Literal['left', 'right', 'bed', 'left_down', 'right_down'],
|
625
818
|
orientation:Literal['left', 'right']) -> None:
|
626
819
|
"""
|
627
|
-
Déplacement des points de référence sur base d'un index
|
628
|
-
Le cas échéant, adaptation du mode de stockage
|
820
|
+
Déplacement des points de référence sur base d'un index.
|
821
|
+
Le cas échéant, adaptation du mode de stockage.
|
822
|
+
|
823
|
+
:param which: point à déplacer ('left', 'right', 'bed', 'left_down', 'right_down')
|
824
|
+
:param orientation: direction du déplacement ('left' ou 'right')
|
629
825
|
"""
|
630
826
|
if self.banksbed_postype == postype.BY_VERTEX:
|
631
827
|
if which=='left':
|
@@ -707,9 +903,11 @@ class profile(vector):
|
|
707
903
|
self.sz_bankbed = self.get_sz_banksbed(force=True)
|
708
904
|
self.s3d_bankbed = self.get_s3d_banksbed(force=True)
|
709
905
|
|
710
|
-
def update_sdatum(self, new_sdatum):
|
906
|
+
def update_sdatum(self, new_sdatum:float):
|
711
907
|
"""
|
712
|
-
|
908
|
+
Mise à jour de la position de la section selon sa trace
|
909
|
+
|
910
|
+
:param new_sdatum: nouvelle valeur de sdatum
|
713
911
|
"""
|
714
912
|
|
715
913
|
sdatum_prev = self.sdatum
|
@@ -718,14 +916,16 @@ class profile(vector):
|
|
718
916
|
self.add_sdatum = True
|
719
917
|
|
720
918
|
if self.prepared:
|
721
|
-
delta = new_sdatum-sdatum_prev
|
919
|
+
delta = new_sdatum - sdatum_prev
|
722
920
|
self.sz[:,0] += delta
|
723
921
|
self.smin += delta
|
724
922
|
self.smax += delta
|
725
923
|
|
726
|
-
def update_zdatum(self, new_zdatum):
|
924
|
+
def update_zdatum(self, new_zdatum:float):
|
727
925
|
"""
|
728
|
-
|
926
|
+
Mise à jour de l'altitude de référence de la section
|
927
|
+
|
928
|
+
:param new_zdatum: nouvelle valeur de zdatum
|
729
929
|
"""
|
730
930
|
|
731
931
|
zdatum_prev = self.zdatum
|
@@ -734,21 +934,28 @@ class profile(vector):
|
|
734
934
|
self.add_zdatum = True
|
735
935
|
|
736
936
|
if self.prepared:
|
737
|
-
delta = new_zdatum-zdatum_prev
|
738
|
-
self.sz[:,1] +=
|
937
|
+
delta = new_zdatum - zdatum_prev
|
938
|
+
self.sz[:,1] += delta
|
739
939
|
self.zmin += delta
|
740
940
|
self.zmax += delta
|
741
941
|
|
742
|
-
def update_banksbed_from_s3d(self,
|
942
|
+
def update_banksbed_from_s3d(self,
|
943
|
+
which:Literal['left', 'right', 'bed', 'left_down', 'right_down'],
|
944
|
+
s:float):
|
743
945
|
"""
|
744
|
-
|
946
|
+
Mise à jour des points de référence depuis une coordonnée curvi 3D
|
947
|
+
|
948
|
+
:param which: point à modifier ('left', 'right', 'bed', 'left_down', 'right_down')
|
949
|
+
:param s: nouvelle position curvi 3D du point
|
745
950
|
"""
|
746
951
|
|
747
952
|
if self.banksbed_postype != postype.BY_S3D:
|
748
|
-
s1, s2, s3 = self.get_s3d_banksbed()
|
953
|
+
s1, s2, s3, s4, s5 = self.get_s3d_banksbed()
|
749
954
|
self.bankleft = s1
|
750
|
-
self.
|
751
|
-
self.
|
955
|
+
self.bankleft_down = s2
|
956
|
+
self.bed = s3
|
957
|
+
self.bankright_down = s4
|
958
|
+
self.bankright = s5
|
752
959
|
|
753
960
|
self.banksbed_postype = postype.BY_S3D
|
754
961
|
|
@@ -762,20 +969,34 @@ class profile(vector):
|
|
762
969
|
self.bankright = s
|
763
970
|
|
764
971
|
if self.prepared:
|
765
|
-
self.s3d_bankbed[
|
972
|
+
self.s3d_bankbed[4]=s
|
766
973
|
|
767
974
|
elif which=='bed':
|
768
975
|
self.bed = s
|
769
976
|
|
977
|
+
if self.prepared:
|
978
|
+
self.s3d_bankbed[2]=s
|
979
|
+
|
980
|
+
elif which=='left_down':
|
981
|
+
self.bankleft_down = s
|
982
|
+
|
770
983
|
if self.prepared:
|
771
984
|
self.s3d_bankbed[1]=s
|
772
985
|
|
986
|
+
elif which=='right_down':
|
987
|
+
self.bankright_down = s
|
988
|
+
|
989
|
+
if self.prepared:
|
990
|
+
self.s3d_bankbed[3]=s
|
991
|
+
|
773
992
|
if self.prepared:
|
774
993
|
self.sz_bankbed = self.get_sz_banksbed(force=True)
|
775
994
|
|
776
|
-
def save(self,f):
|
995
|
+
def save(self, f):
|
777
996
|
"""
|
778
997
|
Surcharge de l'opération d'écriture
|
998
|
+
|
999
|
+
:param f: fichier ouvert en écriture
|
779
1000
|
"""
|
780
1001
|
|
781
1002
|
if self.parent.forcesuper:
|
@@ -789,68 +1010,76 @@ class profile(vector):
|
|
789
1010
|
which = "LEFT"
|
790
1011
|
elif curvert is self.bankright:
|
791
1012
|
which = "RIGHT"
|
1013
|
+
elif curvert is self.bankleft_down:
|
1014
|
+
which = "LEFT_DOWN"
|
1015
|
+
elif curvert is self.bankright_down:
|
1016
|
+
which = "RIGHT_DOWN"
|
792
1017
|
|
793
|
-
f.write("{0}\t{1}\t{2}\t{3}\t{4}\n".format(self.myname,curvert.x,curvert.y,which,curvert.z))
|
1018
|
+
f.write("{0}\t{1}\t{2}\t{3}\t{4}\n".format(self.myname, curvert.x, curvert.y, which, curvert.z))
|
794
1019
|
|
795
|
-
def get_s_from_xy(self, xy:wolfvertex) -> float:
|
1020
|
+
def get_s_from_xy(self, xy:wolfvertex, cumul:bool = False) -> float:
|
796
1021
|
"""
|
797
|
-
Retourne la
|
1022
|
+
Retourne la coordonnée curviligne (ou distance euclidienne selon l'orientation de la section)
|
1023
|
+
du point xy donné en tant qu'objet wolfvertex.
|
1024
|
+
|
1025
|
+
Dans cette routine, 'cumul' est à False par défaut pour être rétro-compatible avec les versions précédentes.
|
1026
|
+
ATTENTION: Cette valeur par défaut n'est pas identique à celle utilisée dans 'get_sz'.
|
1027
|
+
|
1028
|
+
:param xy: vertex dont on veut la coordonnée curvi 2D
|
1029
|
+
:param cumul: si True, la coordonnée curvi 2D est cumulée (distance le long de la section)
|
1030
|
+
si False, la coordonnée curvi 2D est la distance euclidienne au point de départ
|
798
1031
|
"""
|
799
1032
|
|
1033
|
+
if cumul:
|
1034
|
+
s = self.linestring.project(Point(xy.x, xy.y))
|
1035
|
+
return s
|
1036
|
+
|
800
1037
|
x1 = self.myvertices[0].x
|
801
1038
|
y1 = self.myvertices[0].y
|
802
1039
|
length = math.sqrt((xy.x-x1)**2.+(xy.y-y1)**2.)
|
803
1040
|
|
804
1041
|
return length
|
805
1042
|
|
806
|
-
def get_sz(self, cumul=True) -> tuple[np.ndarray, np.ndarray]:
|
807
|
-
|
808
|
-
|
809
|
-
"""
|
1043
|
+
# def get_sz(self, cumul=True) -> tuple[np.ndarray, np.ndarray]:
|
1044
|
+
# """
|
1045
|
+
# Surcharge de la méthode get_sz de la classe mère wolfsection pour ajouter les datums s et z.
|
810
1046
|
|
811
|
-
|
812
|
-
|
1047
|
+
# :param cumul: si True, la coordonnée curvi 2D est cumulée (distance le long de la section)
|
1048
|
+
# si False, la coordonnée curvi 2D est la distance euclidienne au point de départ
|
1049
|
+
# :return: tuple (s,z) avec s la coordonnée curvi 2D
|
1050
|
+
# """
|
813
1051
|
|
814
|
-
|
1052
|
+
# if self.prepared:
|
1053
|
+
# return self.sz[:,0], self.sz[:,1]
|
815
1054
|
|
816
|
-
|
817
|
-
z+=self.zdatum
|
818
|
-
|
819
|
-
nb = len(z)
|
820
|
-
s = zeros(nb)
|
1055
|
+
# s, z = super().get_sz(cumul=cumul)
|
821
1056
|
|
822
|
-
|
823
|
-
|
824
|
-
y1 = self.myvertices[0].y
|
825
|
-
for i in range(nb-1):
|
826
|
-
x2 = self.myvertices[i+1].x
|
827
|
-
y2 = self.myvertices[i+1].y
|
1057
|
+
# if self.add_zdatum:
|
1058
|
+
# z += self.zdatum
|
828
1059
|
|
829
|
-
|
830
|
-
|
1060
|
+
# if self.add_sdatum:
|
1061
|
+
# s += self.sdatum
|
831
1062
|
|
832
|
-
|
833
|
-
y1=y2
|
834
|
-
else:
|
835
|
-
for i in range(nb):
|
836
|
-
s[i] = self.myvertices[0].dist2D(self.myvertices[i])
|
1063
|
+
# return s,z
|
837
1064
|
|
838
|
-
|
839
|
-
|
1065
|
+
def set_sz(self, sz:np.ndarray, trace:list[tuple[float, float]]):
|
1066
|
+
"""
|
1067
|
+
Calcule les positions des vertices sur base d'une matrice sz et d'une trace.
|
840
1068
|
|
841
|
-
|
1069
|
+
:param s: colonne 0
|
1070
|
+
:param z: colonne 1
|
1071
|
+
:param trace: liste de 2 couples xy -> [[x1,y1], [x2,y2]]
|
842
1072
|
|
843
|
-
|
844
|
-
"""
|
845
|
-
Calcule les positions des vertices sur base d'une matrice sz et d'une trace
|
846
|
-
s : colonne 0
|
847
|
-
z : colonne 1
|
848
|
-
trace : liste de 2 couples xy -> [[x1,y1], [x2,y2]]
|
1073
|
+
FIXME: Vérifier la possibilité de fusionner avec set_vertices_sz_orient
|
849
1074
|
"""
|
850
1075
|
|
851
1076
|
orig = trace[0]
|
852
1077
|
end = trace[1]
|
853
1078
|
|
1079
|
+
if np.all(np.asarray(orig)==np.asarray(end)):
|
1080
|
+
logging.error(_('Bad input points in set_sz -- identical points'))
|
1081
|
+
return
|
1082
|
+
|
854
1083
|
vec = np.asarray(end)-np.asarray(orig)
|
855
1084
|
vec = vec/np.linalg.norm(vec)
|
856
1085
|
|
@@ -863,7 +1092,11 @@ class profile(vector):
|
|
863
1092
|
def get_sz_banksbed(self, cumul=True, force:bool=False) -> tuple[float, float, float, float, float, float, float, float, float, float]:
|
864
1093
|
"""
|
865
1094
|
Retourne les positions des points de référence mais avec la coordonnée curvi 2D
|
866
|
-
- (sleft, sbed, sright, zleft, zbed, zright)
|
1095
|
+
- (sleft, sbed, sright, zleft, zbed, zright, sbankleft_down, sbankright_down, zbankleft_down, zbankright_down)
|
1096
|
+
|
1097
|
+
:param cumul: si True, la coordonnée curvi 2D est cumulée (distance le long de la section)
|
1098
|
+
si False, la coordonnée curvi 2D est la distance euclidienne au point de départ
|
1099
|
+
:param force: si True, force le recalcul même si la section est déjà préparée
|
867
1100
|
"""
|
868
1101
|
|
869
1102
|
if self.prepared and not force:
|
@@ -952,16 +1185,16 @@ class profile(vector):
|
|
952
1185
|
|
953
1186
|
return sleft,sbed,sright,zleft,zbed,zright, sbankleft_down, sbankright_down, zbankleft_down, zbankright_down
|
954
1187
|
|
955
|
-
def get_s3d_banksbed(self, force:bool=False)-> tuple[float, float, float]:
|
1188
|
+
def get_s3d_banksbed(self, force:bool=False)-> tuple[float, float, float, float, float]:
|
956
1189
|
"""
|
957
1190
|
Retourne les coordonnée curvi 3D des points de référence
|
958
|
-
- (sleft, sbed, sright)
|
1191
|
+
- (sleft, sleft_down, sbed, sright_down, sright)
|
959
1192
|
"""
|
960
1193
|
|
961
1194
|
if self.prepared and not force:
|
962
1195
|
return self.s3d_bankbed
|
963
1196
|
|
964
|
-
return self.bankleft_s3D, self.bed_s3D, self.bankright_s3D
|
1197
|
+
return self.bankleft_s3D, self.bankleft_down_s3D, self.bed_s3D, self.bankright_down_s3D, self.bankright_s3D
|
965
1198
|
|
966
1199
|
def asshapely_sz(self) -> LineString:
|
967
1200
|
"""
|
@@ -971,6 +1204,19 @@ class profile(vector):
|
|
971
1204
|
s,z = self.get_sz()
|
972
1205
|
return LineString(np.asarray([[curs, curz] for curs, curz in zip(s,z)]))
|
973
1206
|
|
1207
|
+
@property
|
1208
|
+
def linestring_sz(self) -> LineString:
|
1209
|
+
"""
|
1210
|
+
Retroune la section comme objet shapely - polyligne selon la trace avec altitudes
|
1211
|
+
"""
|
1212
|
+
|
1213
|
+
if self._linestring_sz is not None:
|
1214
|
+
return self._linestring_sz
|
1215
|
+
|
1216
|
+
self._linestring_sz = self.asshapely_sz()
|
1217
|
+
prepare(self._linestring_sz)
|
1218
|
+
return self._linestring_sz
|
1219
|
+
|
974
1220
|
def asshapely_s3dz(self) -> LineString:
|
975
1221
|
"""
|
976
1222
|
Retroune la section comme objet shapely - polyligne selon la trace 3D avec altitudes
|
@@ -980,9 +1226,32 @@ class profile(vector):
|
|
980
1226
|
z = [cur.z for cur in self.myvertices]
|
981
1227
|
return LineString(np.asarray([[curs, curz] for curs, curz in zip(s,z)]))
|
982
1228
|
|
1229
|
+
@property
|
1230
|
+
def s3dz(self) -> np.ndarray:
|
1231
|
+
"""
|
1232
|
+
Retroune la section comme matrice numpy (s3d,z)
|
1233
|
+
"""
|
1234
|
+
|
1235
|
+
s = self.get_s3d()
|
1236
|
+
z = [cur.z for cur in self.myvertices]
|
1237
|
+
return np.column_stack([s,z])
|
1238
|
+
|
1239
|
+
@property
|
1240
|
+
def linestring_s3dz(self) -> LineString:
|
1241
|
+
"""
|
1242
|
+
Retroune la section comme objet shapely - polyligne selon la trace 3D avec altitudes
|
1243
|
+
"""
|
1244
|
+
|
1245
|
+
if self._linestring_s3dz is not None:
|
1246
|
+
return self._linestring_s3dz
|
1247
|
+
|
1248
|
+
self._linestring_s3dz = self.asshapely_s3dz()
|
1249
|
+
prepare(self._linestring_s3dz)
|
1250
|
+
return self._linestring_s3dz
|
1251
|
+
|
983
1252
|
def prepare(self,cumul=True):
|
984
1253
|
"""
|
985
|
-
Pre-Compute sz and
|
1254
|
+
Pre-Compute sz, sz_banked and shapely objects to avoid multiple computation
|
986
1255
|
"""
|
987
1256
|
|
988
1257
|
self.reset_prepare()
|
@@ -994,12 +1263,30 @@ class profile(vector):
|
|
994
1263
|
self.zmin = min(y)
|
995
1264
|
self.zmax = max(y)
|
996
1265
|
|
997
|
-
self.prepare_shapely()
|
1266
|
+
self.prepare_shapely(linestring=True, polygon=False)
|
998
1267
|
self.sz_bankbed = list(self.get_sz_banksbed(cumul))
|
999
1268
|
self.s3d_bankbed = list(self.get_s3d_banksbed())
|
1000
1269
|
|
1001
1270
|
self.prepared=True
|
1002
1271
|
|
1272
|
+
def __del__(self):
|
1273
|
+
|
1274
|
+
self.reset_prepare()
|
1275
|
+
super().__del__()
|
1276
|
+
|
1277
|
+
def _reset_linestring_sz_s3dz(self):
|
1278
|
+
|
1279
|
+
if self._linestring_sz is not None:
|
1280
|
+
if is_prepared(self._linestring_sz):
|
1281
|
+
destroy_prepared(self._linestring_sz)
|
1282
|
+
self._linestring_sz=None
|
1283
|
+
|
1284
|
+
if self._linestring_s3dz is not None:
|
1285
|
+
if is_prepared(self._linestring_s3dz):
|
1286
|
+
destroy_prepared(self._linestring_s3dz)
|
1287
|
+
self._linestring_s3dz=None
|
1288
|
+
|
1289
|
+
|
1003
1290
|
def reset_prepare(self):
|
1004
1291
|
"""
|
1005
1292
|
Réinitialisation de la préparation de la section
|
@@ -1013,173 +1300,35 @@ class profile(vector):
|
|
1013
1300
|
self.sz_bankbed = None
|
1014
1301
|
self.s3d_bankbed = None
|
1015
1302
|
self.reset_linestring()
|
1303
|
+
self._reset_linestring_sz_s3dz()
|
1016
1304
|
|
1017
1305
|
self.prepared=False
|
1018
1306
|
|
1019
|
-
def get_min(self):
|
1307
|
+
def get_min(self) -> wolfvertex:
|
1308
|
+
""" Return the vertex with minimum elevation """
|
1020
1309
|
return sorted(self.myvertices,key=lambda x:x.z)[0]
|
1021
1310
|
|
1022
|
-
def get_max(self):
|
1311
|
+
def get_max(self) -> wolfvertex:
|
1312
|
+
""" Return the vertex with maximum elevation """
|
1023
1313
|
return sorted(self.myvertices,key=lambda x:x.z)[-1]
|
1024
1314
|
|
1025
|
-
def get_minz(self):
|
1315
|
+
def get_minz(self) -> float:
|
1316
|
+
""" Return the minimum elevation """
|
1026
1317
|
return amin(list(x.z for x in self.myvertices))
|
1027
1318
|
|
1028
|
-
def get_maxz(self):
|
1319
|
+
def get_maxz(self) -> float:
|
1320
|
+
""" Return the maximum elevation """
|
1029
1321
|
return amax(list(x.z for x in self.myvertices))
|
1030
1322
|
|
1031
|
-
def
|
1032
|
-
|
1033
|
-
colors=['red','blue','green']
|
1034
|
-
|
1035
|
-
k=0
|
1036
|
-
for curlabel, curarray in linked_arrays.items():
|
1037
|
-
if curarray.plotted:
|
1038
|
-
myls = self.asshapely_ls()
|
1039
|
-
|
1040
|
-
length = myls.length
|
1041
|
-
ds = min(curarray.dx,curarray.dy)
|
1042
|
-
nb = int(np.ceil(length/ds*2))
|
1043
|
-
|
1044
|
-
alls = np.linspace(0,int(length),nb)
|
1045
|
-
|
1046
|
-
pts = [myls.interpolate(curs) for curs in alls]
|
1047
|
-
|
1048
|
-
allz = [curarray.get_value(curpt.x,curpt.y) for curpt in pts]
|
1049
|
-
|
1050
|
-
if np.max(allz)>-99999:
|
1051
|
-
ax.plot(alls,allz,
|
1052
|
-
color=colors[np.mod(k,3)],
|
1053
|
-
lw=2.0,
|
1054
|
-
label=curlabel)
|
1055
|
-
k+=1
|
1056
|
-
|
1057
|
-
def _plot_only_cs(self,fig:Figure=None,ax:Axes=None,label='',alpha=0.8,lw=1.,style: str ='dashed',centerx=0.,centery=0.,grid=True, col_ax: str = 'black'):
|
1058
|
-
# plot
|
1059
|
-
x,y=self.get_sz()
|
1060
|
-
|
1061
|
-
sl,sb,sr,yl,yb,yr, sld, srd, yld, yrd = self.get_sz_banksbed()
|
1062
|
-
|
1063
|
-
if centerx >0. and sb!=-99999.:
|
1064
|
-
decal = centerx-sb
|
1065
|
-
x+=decal
|
1066
|
-
sl+=decal
|
1067
|
-
sb+=decal
|
1068
|
-
sr+=decal
|
1069
|
-
|
1070
|
-
if centery >0. and yb!=-99999.:
|
1071
|
-
decal = centery-yb
|
1072
|
-
y+=decal
|
1073
|
-
yl+=decal
|
1074
|
-
yb+=decal
|
1075
|
-
yr+=decal
|
1076
|
-
|
1077
|
-
ax.plot(x,y,color=col_ax,
|
1078
|
-
lw=lw,
|
1079
|
-
linestyle=style,
|
1080
|
-
alpha=alpha,
|
1081
|
-
label=label)
|
1082
|
-
|
1083
|
-
curtick=ax.get_xticks()
|
1084
|
-
ax.set_xticks(np.arange(min(curtick[0],(x[0]//2)*2),max(curtick[-1],(x[-1]//2)*2),2))
|
1085
|
-
|
1086
|
-
if sl != -99999.:
|
1087
|
-
ax.plot(sl,yl,'or',alpha=alpha)
|
1088
|
-
if sb != -99999.:
|
1089
|
-
ax.plot(sb,yb,'ob',alpha=alpha)
|
1090
|
-
if sr != -99999.:
|
1091
|
-
ax.plot(sr,yr,'og',alpha=alpha)
|
1092
|
-
if sld != -99999.:
|
1093
|
-
ax.plot(sld,yld,'*r',alpha=alpha)
|
1094
|
-
if srd != -99999.:
|
1095
|
-
ax.plot(srd,yrd,'*g',alpha=alpha)
|
1096
|
-
|
1097
|
-
def plot_cs(self, fwl=None, show=False, forceaspect=True, fig:Figure=None, ax:Axes=None, plotlaz=True, clear=True, linked_arrays:dict={}):
|
1098
|
-
# plot
|
1099
|
-
x,y=self.get_sz()
|
1100
|
-
|
1101
|
-
sl,sb,sr,yl,yb,yr, sld, srd, yld, yrd = self.get_sz_banksbed()
|
1102
|
-
|
1103
|
-
xmin=x[0]
|
1104
|
-
xmax=x[-1]
|
1105
|
-
ymin=self.get_minz()
|
1106
|
-
ymax=self.get_maxz()
|
1323
|
+
def relation_oneh(self, cury:float, x:float = None, y:float = None):
|
1324
|
+
""" Compute the hydraulic characteristics for a given water elevation 'cury'
|
1107
1325
|
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
if ax is None:
|
1113
|
-
redraw=False
|
1114
|
-
fig = plt.figure()
|
1115
|
-
ax=fig.add_subplot(111)
|
1116
|
-
else:
|
1117
|
-
redraw=True
|
1118
|
-
if clear:
|
1119
|
-
ax.cla()
|
1120
|
-
|
1121
|
-
if np.min(y) != -99999. and np.max(y) != -99999.:
|
1122
|
-
ax.plot(x,y,color='black',
|
1123
|
-
lw=2.0,
|
1124
|
-
label='Profil')
|
1125
|
-
|
1126
|
-
if self.parent is not None:
|
1127
|
-
if plotlaz and self.parent.gridlaz is not None:
|
1128
|
-
minlaz=ymin
|
1129
|
-
maxlaz=ymax
|
1130
|
-
minlaz,maxlaz=self.plot_laz(fig=fig,ax=ax)
|
1131
|
-
if np.min(y) != -99999. and np.max(y) != -99999.:
|
1132
|
-
ymin = min(ymin,minlaz)
|
1133
|
-
ymax = max(ymax,maxlaz)
|
1134
|
-
else:
|
1135
|
-
ymin = minlaz
|
1136
|
-
ymax = maxlaz
|
1137
|
-
|
1138
|
-
self.plot_linked(fig,ax,linked_arrays)
|
1139
|
-
|
1140
|
-
if fwl is not None:
|
1141
|
-
ax.fill_between(x,y,fwl,where=y<=fwl,facecolor='cyan',alpha=0.3,interpolate=True)
|
1142
|
-
|
1143
|
-
if sl != -99999.:
|
1144
|
-
ax.plot(sl,yl,'or')
|
1145
|
-
if sb != -99999.:
|
1146
|
-
ax.plot(sb,yb,'ob')
|
1147
|
-
if sr != -99999.:
|
1148
|
-
ax.plot(sr,yr,'og')
|
1149
|
-
if sld != -99999.:
|
1150
|
-
ax.plot(sld,yld,'*r')
|
1151
|
-
if srd != -99999.:
|
1152
|
-
ax.plot(srd,yrd,'*g')
|
1153
|
-
|
1154
|
-
ax.set_title(self.myname)
|
1155
|
-
ax.set_xlabel(_('Distance [m]'))
|
1156
|
-
ax.set_ylabel('Elevation [EL.m]')
|
1157
|
-
ax.legend()
|
1158
|
-
|
1159
|
-
tol=(xmax-xmin)/10.
|
1160
|
-
ax.set_xlim(xmin-tol,xmax+tol)
|
1161
|
-
ax.set_ylim(ymin,ymax)
|
1162
|
-
|
1163
|
-
nbticks = 20
|
1164
|
-
dzticks = max((((ymax-ymin)/nbticks) // .25) *.25,.25)
|
1165
|
-
|
1166
|
-
ax.set_yticks(np.arange((ymin//.25)*.25,(ymax//.25)*.25,dzticks))
|
1167
|
-
ax.grid()
|
1168
|
-
|
1169
|
-
if forceaspect:
|
1170
|
-
aspect=1.0*(ymax-ymin)/(xmax-xmin)*(ax.get_xlim()[1] - ax.get_xlim()[0]) / (ax.get_ylim()[1] - ax.get_ylim()[0])
|
1171
|
-
ax.set_aspect(aspect)
|
1172
|
-
|
1173
|
-
if show:
|
1174
|
-
fig.show()
|
1175
|
-
|
1176
|
-
if redraw:
|
1177
|
-
fig.canvas.draw()
|
1178
|
-
|
1179
|
-
return sl,sb,sr,yl,yb,yr
|
1180
|
-
|
1181
|
-
def relation_oneh(self,cury,x=None,y=None):
|
1326
|
+
:param cury: water elevation
|
1327
|
+
:param x: optional x coordinates of the section (if None, use the section's coordinates)
|
1328
|
+
:param y: optional y coordinates of the section (if None, use the section's coordinates)
|
1329
|
+
:return: a,s,w,r (wetted area, wetted perimeter, top width, hydraulic radius)
|
1182
1330
|
|
1331
|
+
"""
|
1183
1332
|
if x is None and y is None:
|
1184
1333
|
x,y=self.get_sz()
|
1185
1334
|
|
@@ -1224,7 +1373,7 @@ class profile(vector):
|
|
1224
1373
|
else:
|
1225
1374
|
r=0.
|
1226
1375
|
|
1227
|
-
return a,s,w,r
|
1376
|
+
return float(a), float(s), float(w), float(r)
|
1228
1377
|
|
1229
1378
|
def relations(self, discretize: int = 100, plot = True):
|
1230
1379
|
"""
|
@@ -1232,6 +1381,7 @@ class profile(vector):
|
|
1232
1381
|
of a specific hydraulic characteristic with respect to the water depth in the profile
|
1233
1382
|
(wetted area, wetted perimeter, top width, water detph, hydraulic radius, critical discharge).
|
1234
1383
|
"""
|
1384
|
+
|
1235
1385
|
x,y=self.get_sz()
|
1236
1386
|
|
1237
1387
|
ymin=min(y)
|
@@ -1263,7 +1413,7 @@ class profile(vector):
|
|
1263
1413
|
if plot: # In order to allow other usages apart from Graphprofile.
|
1264
1414
|
self.wetarea = a
|
1265
1415
|
self.wetperimeter = s
|
1266
|
-
self.
|
1416
|
+
self.hydraulicradius = r
|
1267
1417
|
self.waterdepth=h
|
1268
1418
|
self.localwidth=w
|
1269
1419
|
self.criticaldischarge = qcr
|
@@ -1271,6 +1421,11 @@ class profile(vector):
|
|
1271
1421
|
return a,s,r,h,w,qcr
|
1272
1422
|
|
1273
1423
|
def slopes(self):
|
1424
|
+
""" Compute the slopes of the section with respect to the upstream and downstream sections """
|
1425
|
+
|
1426
|
+
if self.up is None or self.down is None:
|
1427
|
+
logging.error(_('No upstream or downstream section defined for section {}').format(self.myname))
|
1428
|
+
return 0.,0.,0.
|
1274
1429
|
|
1275
1430
|
slopedown = (self.get_minz() - self.down.get_minz()) / abs(self.down.s - self.s+1.e-10)
|
1276
1431
|
slopeup = (self.up.get_minz() - self.get_minz()) / abs(self.s - self.up.s+1.e-10)
|
@@ -1278,30 +1433,52 @@ class profile(vector):
|
|
1278
1433
|
|
1279
1434
|
return slopeup,slopecentered,slopedown
|
1280
1435
|
|
1281
|
-
def ManningStrickler_Q(self,slope=1.e-3,nManning=0.,KStrickler=0.):
|
1282
|
-
"""Procédure générique pour obtenir une relation uniforme Q-H sur base
|
1283
|
-
|
1284
|
-
|
1436
|
+
def ManningStrickler_Q(self, slope:float = 1.e-3, nManning:float = 0., KStrickler:float = 0.):
|
1437
|
+
"""Procédure générique pour obtenir une relation uniforme Q-H sur base d'une pente et d'un coefficient de frottement.
|
1438
|
+
|
1439
|
+
:param slope: une pente
|
1440
|
+
:param nManning: un coefficient de frottement
|
1441
|
+
:param KStrickler: un coefficient de frottement (if nManning > 0.0, KStrickler is ignored)
|
1285
1442
|
"""
|
1286
1443
|
|
1444
|
+
if slope <= 0.:
|
1445
|
+
logging.error(_('No positive slope provided for section {}').format(self.myname))
|
1446
|
+
return
|
1447
|
+
|
1448
|
+
# Calcul des caractéristiques hydrauliques si pas encore faites
|
1449
|
+
|
1450
|
+
if self.waterdepth is None:
|
1451
|
+
self.relations(plot = True)
|
1452
|
+
|
1287
1453
|
if nManning==0. and KStrickler==0.:
|
1454
|
+
logging.error(_('No friction coefficient provided for section {}').format(self.myname))
|
1288
1455
|
return
|
1289
|
-
elif nManning>0.:
|
1290
|
-
coeff=1
|
1456
|
+
elif nManning > 0.:
|
1457
|
+
coeff = 1. / nManning
|
1291
1458
|
elif KStrickler>0.:
|
1292
1459
|
coeff = KStrickler
|
1293
1460
|
|
1294
|
-
nn=len(self.waterdepth)
|
1295
|
-
sqrtslope=math.sqrt(slope)
|
1461
|
+
nn = len(self.waterdepth)
|
1462
|
+
sqrtslope = math.sqrt(slope)
|
1463
|
+
|
1464
|
+
self.q_slope = asarray([coeff * self.hydraulicradius[k]**(2./3.)* sqrtslope * self.wetarea[k] for k in range(nn)])
|
1296
1465
|
|
1297
|
-
|
1466
|
+
return self.q_slope
|
1298
1467
|
|
1299
|
-
def ManningStrickler_oneQ(self,slope=1.e-3,nManning=0.,KStrickler=0.,cury=0.):
|
1468
|
+
def ManningStrickler_oneQ(self, slope:float = 1.e-3, nManning:float = 0., KStrickler:float = 0., cury:float = 0.):
|
1300
1469
|
"""Procédure générique pour obtenir une relation uniforme Q-H sur base
|
1301
|
-
|
1302
|
-
|
1470
|
+
|
1471
|
+
:param slope: une pente
|
1472
|
+
:param nManning: un coefficient de frottement
|
1473
|
+
:param KStrickler: un coefficient de frottement (if nManning > 0.0, KStrickler is ignored)
|
1474
|
+
:param cury: une hauteur d'eau
|
1475
|
+
:return: le débit Q
|
1303
1476
|
"""
|
1304
1477
|
|
1478
|
+
if slope <= 0.:
|
1479
|
+
logging.error(_('No positive slope provided for section {}').format(self.myname))
|
1480
|
+
return
|
1481
|
+
|
1305
1482
|
if nManning==0. and KStrickler==0.:
|
1306
1483
|
return
|
1307
1484
|
elif nManning>0.:
|
@@ -1317,16 +1494,19 @@ class profile(vector):
|
|
1317
1494
|
|
1318
1495
|
return q
|
1319
1496
|
|
1320
|
-
|
1321
|
-
def deepcopy_profile(self, name: str = None):
|
1497
|
+
def deepcopy_profile(self, name: str = None, appendstr: str = '_copy'):
|
1322
1498
|
"""
|
1323
1499
|
This method returns a deepcopy of the active profile.
|
1324
1500
|
The profile features are individually transferred, therefore,
|
1325
1501
|
only the necessary features are copied.
|
1502
|
+
|
1503
|
+
:param name: name of the copied profile (if None, the current profile's name + appendstr is used)
|
1504
|
+
:param appendstr: string to append to the current profile's name if no new name is given (default: '_copy')
|
1505
|
+
:return: the copied profile
|
1326
1506
|
"""
|
1327
1507
|
# if a new name is not given, we add _copy to the current profile's name.
|
1328
1508
|
if name is None:
|
1329
|
-
name = self.myname +
|
1509
|
+
name = self.myname + appendstr
|
1330
1510
|
|
1331
1511
|
# We create the new profile (copy).
|
1332
1512
|
copied_profile = profile(name)
|
@@ -1339,21 +1519,31 @@ class profile(vector):
|
|
1339
1519
|
copied_profile.bankright = copy.deepcopy(self.bankright)
|
1340
1520
|
copied_profile.bed = copy.deepcopy(self.bed)
|
1341
1521
|
copied_profile.banksbed_postype = copy.deepcopy(self.banksbed_postype)
|
1522
|
+
copied_profile.bankleft_down = copy.deepcopy(self.bankleft_down)
|
1523
|
+
copied_profile.bankright_down = copy.deepcopy(self.bankright_down)
|
1342
1524
|
|
1343
1525
|
return copied_profile
|
1344
1526
|
|
1345
|
-
def color_active_profile(self, width: float = 3., color: list = [255, 0, 0], plot_opengl = True):
|
1527
|
+
def color_active_profile(self, width: float = 3., color: list = [255, 0, 0], plot_opengl:bool = True):
|
1346
1528
|
"""
|
1347
1529
|
This method colors and thickens the active profile
|
1348
1530
|
(default width : 3, default color: red).
|
1531
|
+
|
1532
|
+
:param width: width of the profile
|
1533
|
+
:param color: color of the profile as a list of RGB values (0-255)
|
1534
|
+
:param plot_opengl: if True, update the OpenGL plot of the parent zone
|
1349
1535
|
"""
|
1536
|
+
|
1350
1537
|
self.myprop.width = width
|
1351
1538
|
self.myprop.color = getIfromRGB(color)
|
1539
|
+
|
1352
1540
|
if plot_opengl:
|
1353
|
-
self.parentzone
|
1541
|
+
if self.parentzone is not None:
|
1542
|
+
self.parentzone.plot(True) # FIXME (Parent zone)
|
1543
|
+
|
1544
|
+
def highlighting(self, width: float = 3., color: list = [255, 0, 0] , plot_opengl:bool = True):
|
1545
|
+
""" Alias for color_active_profile """
|
1354
1546
|
|
1355
|
-
def highlightning(self, width: float = 3., color: list = [255, 0, 0] , plot_opengl = True):
|
1356
|
-
"""Alias for color_active_profile"""
|
1357
1547
|
self.color_active_profile(width, color)
|
1358
1548
|
|
1359
1549
|
def uncolor_active_profile(self, plot_opengl = True):
|
@@ -1364,28 +1554,40 @@ class profile(vector):
|
|
1364
1554
|
self.myprop.color = getIfromRGB([0, 0, 0])
|
1365
1555
|
|
1366
1556
|
if plot_opengl:
|
1367
|
-
self.parentzone
|
1557
|
+
if self.parentzone is not None:
|
1558
|
+
self.parentzone.plot(True)
|
1368
1559
|
|
1369
|
-
def withdrawing(self
|
1560
|
+
def withdrawing(self, plot_opengl:bool = True):
|
1370
1561
|
"""Alias for uncolor_active_profile"""
|
1562
|
+
|
1371
1563
|
self.uncolor_active_profile(plot_opengl)
|
1372
1564
|
|
1373
1565
|
def ManningStrickler_profile(self, slope: float =1.e-3, nManning: float =0., KStrickler: float=0.):
|
1374
1566
|
"""
|
1375
|
-
Procédure générique pour obtenir une relation uniforme Q-H d'un profile
|
1376
|
-
|
1377
|
-
|
1567
|
+
Procédure générique pour obtenir une relation uniforme Q-H d'un profile
|
1568
|
+
|
1569
|
+
:param slope: une pente
|
1570
|
+
:param nManning: un coefficient de frottement
|
1571
|
+
:param KStrickler: un coefficient de frottement (if nManning > 0.0, KStrickler is ignored)
|
1378
1572
|
|
1379
1573
|
ainsi que les relations correspondant aux pentes aval(slope down), amont(slopeup), et amont-aval (centered).
|
1574
|
+
|
1575
|
+
:return: le débit Q maximum parmi les 4 relations
|
1380
1576
|
"""
|
1381
1577
|
if self.down is not None and self.up is not None:
|
1382
1578
|
slopeup,slopecentered,slopedown = self.slopes()
|
1579
|
+
# slopes are not necessarily positive --> subsequent tests
|
1580
|
+
|
1581
|
+
slopeup = max(slopeup, 0.)
|
1582
|
+
slopecentered = max(slopecentered, 0.)
|
1583
|
+
slopedown = max(slopedown, 0.)
|
1383
1584
|
else:
|
1384
|
-
slopecentered = 0
|
1385
|
-
slopedown = 0
|
1386
|
-
slopeup = 0
|
1585
|
+
slopecentered = 0.
|
1586
|
+
slopedown = 0.
|
1587
|
+
slopeup = 0.
|
1387
1588
|
|
1388
1589
|
if nManning==0. and KStrickler==0.:
|
1590
|
+
logging.error(_('No friction coefficient provided for section {}').format(self.myname))
|
1389
1591
|
return
|
1390
1592
|
elif nManning>0.:
|
1391
1593
|
coeff=1./nManning
|
@@ -1396,40 +1598,31 @@ class profile(vector):
|
|
1396
1598
|
|
1397
1599
|
|
1398
1600
|
sqrtslope = math.sqrt(slope)
|
1399
|
-
|
1400
|
-
|
1401
|
-
|
1402
|
-
sqrtslopedown = 0.
|
1601
|
+
sqrtslopedown= math.sqrt(slopedown)
|
1602
|
+
sqrtslopecentered= math.sqrt(slopecentered)
|
1603
|
+
sqrtslopeup= math.sqrt(slopeup)
|
1403
1604
|
|
1404
|
-
|
1405
|
-
|
1406
|
-
|
1407
|
-
|
1408
|
-
if slopeup > 0:
|
1409
|
-
sqrtslopeup= math.sqrt(slopeup)
|
1410
|
-
else:
|
1411
|
-
sqrtslopeup = 0.
|
1605
|
+
self.q_slope = asarray([coeff*(self.hydraulicradius[k]**(2/3))*sqrtslope*self.wetarea[k] for k in range (nn)])
|
1606
|
+
self.q_down=asarray([coeff * self.hydraulicradius[k]**(2./3.)*sqrtslopedown * self.wetarea[k] for k in range(nn)])
|
1607
|
+
self.q_up=asarray([coeff * self.hydraulicradius[k]**(2./3.)*sqrtslopeup * self.wetarea[k] for k in range(nn)])
|
1608
|
+
self.q_centered=asarray([coeff * self.hydraulicradius[k]**(2./3.)*sqrtslopecentered * self.wetarea[k] for k in range(nn)])
|
1412
1609
|
|
1413
|
-
self.q_slope = asarray([coeff*(self.hyrdaulicradius[k]**(2/3))*sqrtslope*self.wetarea[k] for k in range (nn)])
|
1414
|
-
self.q_down=asarray([coeff * self.hyrdaulicradius[k]**(2./3.)*sqrtslopedown * self.wetarea[k] for k in range(nn)])
|
1415
|
-
self.q_up=asarray([coeff * self.hyrdaulicradius[k]**(2./3.)*sqrtslopeup * self.wetarea[k] for k in range(nn)])
|
1416
|
-
self.q_centered=asarray([coeff * self.hyrdaulicradius[k]**(2./3.)*sqrtslopecentered * self.wetarea[k] for k in range(nn)])
|
1417
1610
|
return max(max(self.q_slope), max(self.q_down), max(self.q_up), max(self.q_centered), max(self.criticaldischarge))
|
1418
1611
|
|
1419
1612
|
def plotcs_profile(self,
|
1420
1613
|
fig: Figure = None,
|
1421
1614
|
ax: Axes = None,
|
1422
|
-
compare = None,
|
1615
|
+
compare:"profile" = None,
|
1423
1616
|
vecs : list = [],
|
1424
1617
|
col_structure: str ='none',
|
1425
1618
|
fwl: float = None,
|
1426
1619
|
fwd: float = None,
|
1427
1620
|
simuls:list=None,
|
1428
|
-
show = False,
|
1429
|
-
forceaspect= True,
|
1430
|
-
plotlaz= True,
|
1431
|
-
clear = True,
|
1432
|
-
redraw=True ):
|
1621
|
+
show:bool = False,
|
1622
|
+
forceaspect:bool = True,
|
1623
|
+
plotlaz:bool = True,
|
1624
|
+
clear:bool = True,
|
1625
|
+
redraw:bool = True ):
|
1433
1626
|
"""
|
1434
1627
|
This method plots the physical geometry of the current cross section (profile).
|
1435
1628
|
|
@@ -1439,6 +1632,21 @@ class profile(vector):
|
|
1439
1632
|
- idsimul: list of available numerical simulations containing this profile,
|
1440
1633
|
- zsimul: list of water level in the simulations.
|
1441
1634
|
- col_structure colors the structure displayed.
|
1635
|
+
|
1636
|
+
:param fig: Matplotlib figure
|
1637
|
+
:param ax: Matplotlib axes
|
1638
|
+
:param compare: reference profile to compare with
|
1639
|
+
:param vecs: list of vector objects to plot on the section (e.g. levees, dikes, ...)
|
1640
|
+
:param col_structure: color of the structures (default: 'none')
|
1641
|
+
:param fwl: water level to plot (if None, no water level is plotted)
|
1642
|
+
:param fwd: water depth to plot (if None, no water level is plotted)
|
1643
|
+
:param simuls: list of simulations containing the profile (id, name, date, time, water level)
|
1644
|
+
:param show: if True, show the figure
|
1645
|
+
:param forceaspect: if True, force aspect ratio
|
1646
|
+
:param plotlaz: if True, plot LAZ points if available
|
1647
|
+
:param clear: if True, clear the axis before plotting
|
1648
|
+
:param redraw: if True, redraw the figure after plotting
|
1649
|
+
|
1442
1650
|
"""
|
1443
1651
|
|
1444
1652
|
idsimul=None
|
@@ -1648,10 +1856,11 @@ class profile(vector):
|
|
1648
1856
|
fwd: float = None,
|
1649
1857
|
fwq: float = None,
|
1650
1858
|
simuls:list=None,
|
1651
|
-
show = False,
|
1652
|
-
clear = True,
|
1653
|
-
labels = True,
|
1654
|
-
redraw =True
|
1859
|
+
show:bool = False,
|
1860
|
+
clear:bool = True,
|
1861
|
+
labels:bool = True,
|
1862
|
+
redraw:bool =True,
|
1863
|
+
force_label_to_right:bool = True):
|
1655
1864
|
"""
|
1656
1865
|
This method plots the discharges relationship computed
|
1657
1866
|
with the methods: relations and ManningStrcikler_profile.
|
@@ -1662,8 +1871,22 @@ class profile(vector):
|
|
1662
1871
|
- idsimul: list of available numerical models.
|
1663
1872
|
- qsimul: list of discharges in the available numerical models,
|
1664
1873
|
- hsimul: list of water depth in the available numerical models.
|
1874
|
+
|
1875
|
+
:param fig: Matplotlib figure
|
1876
|
+
:param ax: Matplotlib axes
|
1877
|
+
:param fwl: water level to plot (if None, no water level is plotted)
|
1878
|
+
:param fwd: water depth to plot (if None, no water level is plotted)
|
1879
|
+
:param fwq: discharge to plot (if None, no discharge is plotted)
|
1880
|
+
:param simuls: list of simulations containing the profile (id, name, date, time, discharge, water level)
|
1881
|
+
:param show: if True, show the figure
|
1882
|
+
:param clear: if True, clear the axis before plotting
|
1883
|
+
:param labels: if True, show the legend
|
1884
|
+
:param redraw: if True, redraw the figure after plotting
|
1665
1885
|
"""
|
1666
1886
|
|
1887
|
+
if self.q_slope is None:
|
1888
|
+
raise Exception(_('Please compute the discharge relationship first using the method "ManningStrickler_profile"'))
|
1889
|
+
|
1667
1890
|
hsimul = None
|
1668
1891
|
qsimul = None
|
1669
1892
|
idsimul = None
|
@@ -1681,12 +1904,18 @@ class profile(vector):
|
|
1681
1904
|
if fwd is None or fwd <= 0:
|
1682
1905
|
fwd =0
|
1683
1906
|
fwl= self.zmin+ fwd
|
1907
|
+
plot_wl = False
|
1684
1908
|
elif fwd > 0 :
|
1685
1909
|
fwl = self.zmin + fwd
|
1910
|
+
plot_wl = True
|
1686
1911
|
|
1687
1912
|
elif fwl is not None:
|
1688
1913
|
fwl = fwl
|
1689
1914
|
fwd = fwl-self.zmin
|
1915
|
+
plot_wl = True
|
1916
|
+
|
1917
|
+
if fig is None:
|
1918
|
+
fig, ax = plt.subplots()
|
1690
1919
|
|
1691
1920
|
# Creation of a new ax for cleaning purposes in the wolfhece.Graphprofile.
|
1692
1921
|
myax2 = ax
|
@@ -1721,7 +1950,8 @@ class profile(vector):
|
|
1721
1950
|
myax2.axhline(y=zb, color=_('blue'), alpha=1, lw=1, label =_('Bed'), ls =_('dotted'))
|
1722
1951
|
|
1723
1952
|
# Desired water depth
|
1724
|
-
|
1953
|
+
if plot_wl:
|
1954
|
+
myax2.axhspan(ymin= self.zmin, ymax =fwl,color=_('cyan'), alpha=0.2, lw=2, label =_('Desired water depth'))
|
1725
1955
|
|
1726
1956
|
myax2.set_xlabel(_('Discharge - Q ($m^3/s$)'), size=12)
|
1727
1957
|
myax2.set_ylim(self.zmin, self.zmax+1)
|
@@ -1729,7 +1959,10 @@ class profile(vector):
|
|
1729
1959
|
if qsimul is not None:
|
1730
1960
|
myax2.set_xlim(0., max(max(self.q_down),max(self.q_up),max(self.q_centered),max(self.q_slope), max(self.criticaldischarge),max(qsimul)))
|
1731
1961
|
else:
|
1732
|
-
|
1962
|
+
if self and self.down and self.up:
|
1963
|
+
myax2.set_xlim(0., max(max(self.q_down),max(self.q_up),max(self.q_centered),max(self.q_slope), max(self.criticaldischarge)))
|
1964
|
+
else:
|
1965
|
+
myax2.set_xlim(0., max(max(self.q_slope), max(self.criticaldischarge)))
|
1733
1966
|
|
1734
1967
|
# Conversion methods for the second matplotlib ax
|
1735
1968
|
def alt_to_depth(y):
|
@@ -1737,7 +1970,7 @@ class profile(vector):
|
|
1737
1970
|
def depth_to_alt(y):
|
1738
1971
|
return y+self.zmin
|
1739
1972
|
|
1740
|
-
secax = myax2.secondary_yaxis(
|
1973
|
+
secax = myax2.secondary_yaxis('right', functions=(alt_to_depth,depth_to_alt))
|
1741
1974
|
#secax.set_yticks(y)
|
1742
1975
|
myax2.yaxis.tick_left()
|
1743
1976
|
#myax2.set_yticks(alt_to_depth(self.waterdepth))
|
@@ -1750,106 +1983,351 @@ class profile(vector):
|
|
1750
1983
|
|
1751
1984
|
|
1752
1985
|
if labels:
|
1753
|
-
myax2.set_ylabel(_('
|
1754
|
-
|
1755
|
-
|
1986
|
+
myax2.set_ylabel(_('Altitude - Z ($m$)'), size=12, rotation=270,labelpad=35)
|
1987
|
+
if force_label_to_right:
|
1988
|
+
myax2.yaxis.set_label_position('right')
|
1989
|
+
secax.set_ylabel(_('Water depth - h ($m$)'), size=12, labelpad=10)
|
1756
1990
|
fig.suptitle('Discharges C.S. - %s'%(self.myname), size=15)
|
1757
1991
|
|
1758
1992
|
|
1759
|
-
if show:
|
1760
|
-
fig.show()
|
1993
|
+
if show:
|
1994
|
+
fig.show()
|
1995
|
+
|
1996
|
+
def plot_discharges_UnCr(self,
|
1997
|
+
fig: Figure = None,
|
1998
|
+
ax: Axes = None,
|
1999
|
+
fwl: float = None,
|
2000
|
+
fwd: float = None,
|
2001
|
+
fwq: float = None,
|
2002
|
+
simuls:list=None,
|
2003
|
+
show:bool = False,
|
2004
|
+
clear:bool = True,
|
2005
|
+
labels:bool = True,
|
2006
|
+
redraw:bool =True,
|
2007
|
+
force_label_to_right:bool = False):
|
2008
|
+
|
2009
|
+
self.plotcs_discharges(fig, ax, fwl, fwd, fwq, simuls, show, clear, labels, redraw, force_label_to_right)
|
2010
|
+
|
2011
|
+
def plotcs_hspw(self,
|
2012
|
+
fig:Figure = None, ax:Axes = None,
|
2013
|
+
fwl:float = None, fwd:float = None,
|
2014
|
+
fwq:float = None,
|
2015
|
+
show:bool = False, clear:bool = True,
|
2016
|
+
labels:bool = True, redraw:bool =True,
|
2017
|
+
force_label_to_right:bool = True):
|
2018
|
+
|
2019
|
+
"""
|
2020
|
+
This method plots the hydraulic geometries computed by the relations method
|
2021
|
+
(Hydraulic radius, wetted area, wetted perimeter, Top width).
|
2022
|
+
|
2023
|
+
- fwl: for water level,
|
2024
|
+
- fwd: for water depth,
|
2025
|
+
- fwq: for water discharge.
|
2026
|
+
|
2027
|
+
:param fig: Matplotlib figure
|
2028
|
+
:param ax: Matplotlib axes
|
2029
|
+
:param fwl: water level to plot (if None, no water level is plotted)
|
2030
|
+
:param fwd: water depth to plot (if None, no water level is plotted)
|
2031
|
+
:param fwq: discharge to plot (if None, no discharge is plotted)
|
2032
|
+
:param show: if True, show the figure
|
2033
|
+
:param clear: if True, clear the axis before plotting
|
2034
|
+
:param labels: if True, show the legend
|
2035
|
+
:param redraw: if True, redraw the figure after plotting
|
2036
|
+
|
2037
|
+
"""
|
2038
|
+
if self.waterdepth is None:
|
2039
|
+
self.relations()
|
2040
|
+
|
2041
|
+
if fig is None and ax is None:
|
2042
|
+
fig, ax = plt.subplots()
|
2043
|
+
|
2044
|
+
sl,sb,sr,zl,zb,zr, sld, srd, zld, zrd = self.get_sz_banksbed()
|
2045
|
+
x,y = self.get_sz()
|
2046
|
+
|
2047
|
+
if fwl is None:
|
2048
|
+
if fwd is None or fwd <= 0:
|
2049
|
+
fwd =0
|
2050
|
+
fwl= self.zmin+ fwd
|
2051
|
+
plot_wl = False
|
2052
|
+
elif fwd > 0 :
|
2053
|
+
fwl = self.zmin + fwd
|
2054
|
+
plot_wl = True
|
2055
|
+
|
2056
|
+
elif fwl is not None:
|
2057
|
+
fwl = fwl
|
2058
|
+
fwd = fwl-self.zmin
|
2059
|
+
plot_wl = True
|
2060
|
+
|
2061
|
+
# Creation of a new ax for cleaning purposes in the wolfhece.Graphprofile.
|
2062
|
+
myax3 = ax
|
2063
|
+
axt3 = ax
|
2064
|
+
if redraw:
|
2065
|
+
if clear:
|
2066
|
+
axt3.clear()
|
2067
|
+
# myax3.clear()
|
2068
|
+
|
2069
|
+
# Plots
|
2070
|
+
# FIXME (Clearing issues) a second x axis for the Hydraulic radius to provide more clarity.
|
2071
|
+
axt3.plot(self.hydraulicradius,self.waterdepth + self.zmin,color=_('green'),lw=2,label=_('H - Hydraulic radius($m$)'), ls= _('--')) #Plot the evaluation of the hydraulic radius as function of water depth
|
2072
|
+
axt3.set_xlim(0, max(self.hydraulicradius))
|
2073
|
+
|
2074
|
+
myax3.plot(self.wetarea,self.waterdepth + self.zmin,color=_('black'),lw=2.0,label=_('S - Wet area ($m^2$)')) #Plot the wetted area as function of water depth
|
2075
|
+
myax3.plot(self.wetperimeter,self.waterdepth + self.zmin,color=_('magenta'),lw=1,label=_('P - Wet perimeter($m$)')) #Plot the wetted perimeter as function of water depth
|
2076
|
+
myax3.plot(self.localwidth,self.waterdepth + self.zmin,color=_('red'),lw=1,label=_('W - Top Width ($m$)')) #Plot the evalution of the water table as function of water depth
|
2077
|
+
|
2078
|
+
# Selection of hydraulic geometries based on their index
|
2079
|
+
if fwq is not None and fwq > 0.:
|
2080
|
+
# First, We select the closest critical discharge to the user's input.
|
2081
|
+
mask = np.absolute(self.criticaldischarge - fwq)
|
2082
|
+
index = np.argmin(mask)
|
2083
|
+
# Second, since the matrices have the same shapes and their elements are sorted likewise,
|
2084
|
+
# we use the index of the selected critical discharge to find the corresponding hydraulic geometries.
|
2085
|
+
cr_wetarea = self.wetarea[index]
|
2086
|
+
cr_wetperimeter = self.wetperimeter[index]
|
2087
|
+
cr_width = self.localwidth[index]
|
2088
|
+
cr_radius = self.hydraulicradius[index]
|
2089
|
+
cr_h = self.waterdepth[index]
|
2090
|
+
myax3.plot(cr_wetarea,cr_h + self.zmin,'ok' )
|
2091
|
+
myax3.annotate(_('$Critical$ $characteristics$ \nH: %s $m$ \nS: %s $m^2$ \nP: %s $m$ \nW: %s $m$ \n \n')% (round(cr_radius,2),round(cr_wetarea,2),round(cr_wetperimeter,2),round(cr_width,2)),\
|
2092
|
+
(cr_wetarea, cr_h + self.zmin), alpha= 1, fontsize = _('x-small'),color =_('black'))
|
2093
|
+
|
2094
|
+
#Finally, we plot the critical hydraulic geometries as dots.
|
2095
|
+
myax3.plot(cr_wetperimeter,cr_h + self.zmin,_('om') )
|
2096
|
+
myax3.plot(cr_width,cr_h + self.zmin,_('or') )
|
2097
|
+
myax3.plot(cr_radius,cr_h + self.zmin,_('og') )
|
2098
|
+
|
2099
|
+
#Displayed water depths and banks
|
2100
|
+
if plot_wl:
|
2101
|
+
myax3.axhspan(ymin= self.zmin, ymax =fwd + self.zmin,color=_('cyan'), alpha=0.2, lw=2, label =_('Desired water depth'))
|
2102
|
+
|
2103
|
+
if zl != -99999.:
|
2104
|
+
myax3.axhline(y=zl, color=_('magenta'), alpha=1, lw=1, label =_('Left bank'), ls =_('dotted') )
|
2105
|
+
if zr != -99999.:
|
2106
|
+
myax3.axhline(y=zr, color=_('green'), alpha=1, lw=1, label =_('right bank'), ls =_('dotted') )
|
2107
|
+
if zb != -99999.:
|
2108
|
+
myax3.axhline(y=zb, color=_('blue'), alpha=1, lw=1, label =_(' bed '), ls =_('dotted'))
|
2109
|
+
|
2110
|
+
#Limits and labels
|
2111
|
+
myax3.set_ylim(self.zmin,self.zmax+1)
|
2112
|
+
myax3.set_xlim(0., max(max(self.wetarea), max(self.wetperimeter), max(self.localwidth)))
|
2113
|
+
myax3.set_xlabel(_('S - P - W'), size=12)
|
2114
|
+
myax3.set_ylabel(_('Altitude - Z ($m$)'), size=12,rotation=270, labelpad=50)
|
2115
|
+
|
2116
|
+
#Conversion methods for the second y axis
|
2117
|
+
def alt_to_depth(y):
|
2118
|
+
return y-self.zmin
|
2119
|
+
def depth_to_alt(y):
|
2120
|
+
return y+self.zmin
|
2121
|
+
secax = myax3.secondary_yaxis('right', functions=(alt_to_depth,depth_to_alt))
|
2122
|
+
|
2123
|
+
myax3.yaxis.tick_left()
|
2124
|
+
if force_label_to_right:
|
2125
|
+
myax3.yaxis.set_label_position('right')
|
2126
|
+
if labels:
|
2127
|
+
secax.set_ylabel(_('Water depth - h ($m$)'), size=12, rotation=270, labelpad=20)
|
2128
|
+
myax3.grid()
|
2129
|
+
|
2130
|
+
if show:
|
2131
|
+
fig.show()
|
2132
|
+
|
2133
|
+
def plotcs_relations(self,
|
2134
|
+
fig:Figure = None, ax:Axes = None,
|
2135
|
+
fwl:float = None, fwd:float = None,
|
2136
|
+
fwq:float = None,
|
2137
|
+
show:bool = False, clear:bool = True,
|
2138
|
+
labels:bool = True, redraw:bool =True,
|
2139
|
+
force_label_to_right:bool = False
|
2140
|
+
):
|
2141
|
+
"""
|
2142
|
+
This method plots the cross section relations (geometry, hydraulic geometries)
|
2143
|
+
|
2144
|
+
It is the same than 'plotcs_hspw' but with (force_label_to_right = False) by default
|
2145
|
+
"""
|
2146
|
+
|
2147
|
+
self.plotcs_hspw(fig=fig, ax=ax,
|
2148
|
+
fwl=fwl, fwd=fwd,
|
2149
|
+
fwq=fwq,
|
2150
|
+
show=show, clear=clear,
|
2151
|
+
labels=labels, redraw=redraw,
|
2152
|
+
force_label_to_right=force_label_to_right)
|
2153
|
+
|
2154
|
+
|
2155
|
+
def plot_linked(self, fig:Figure, ax:Axes, linked_arrays:dict, colors:list=['red','blue','green']):
|
2156
|
+
""" Plot linked arrays on the section plot """
|
2157
|
+
|
2158
|
+
from .wolf_array import WolfArray
|
2159
|
+
|
2160
|
+
# colors=['red','blue','green']
|
2161
|
+
nb_colors = len(colors)
|
2162
|
+
|
2163
|
+
k=0
|
2164
|
+
for curlabel, curarray in linked_arrays.items():
|
2165
|
+
|
2166
|
+
curarray:WolfArray
|
2167
|
+
if curarray.plotted:
|
2168
|
+
|
2169
|
+
length = self.linestring.length
|
2170
|
+
ds = min(curarray.dx,curarray.dy)
|
2171
|
+
nb = int(np.ceil(length/ds*2))
|
2172
|
+
|
2173
|
+
alls = np.linspace(0,int(length),nb)
|
2174
|
+
|
2175
|
+
pts = [self.linestring.interpolate(curs) for curs in alls]
|
2176
|
+
|
2177
|
+
allz = [curarray.get_value(curpt.x,curpt.y) for curpt in pts]
|
2178
|
+
|
2179
|
+
if np.max(allz)>-99999:
|
2180
|
+
ax.plot(alls,allz,
|
2181
|
+
color=colors[np.mod(k,nb_colors)],
|
2182
|
+
lw=2.0,
|
2183
|
+
label=curlabel)
|
2184
|
+
k+=1
|
2185
|
+
|
2186
|
+
def _plot_only_cs(self,
|
2187
|
+
fig:Figure=None, ax:Axes=None,
|
2188
|
+
label='', alpha=0.8, lw=1., style: str ='dashed',
|
2189
|
+
centerx=0., centery=0., grid=True, col_ax: str = 'black'):
|
2190
|
+
""" Plot only the cross section line on an existing axis """
|
2191
|
+
|
2192
|
+
x,y=self.get_sz()
|
2193
|
+
|
2194
|
+
sl,sb,sr,yl,yb,yr, sld, srd, yld, yrd = self.get_sz_banksbed()
|
2195
|
+
|
2196
|
+
if centerx >0. and sb!=-99999.:
|
2197
|
+
decal = centerx-sb
|
2198
|
+
x+=decal
|
2199
|
+
sl+=decal
|
2200
|
+
sb+=decal
|
2201
|
+
sr+=decal
|
2202
|
+
|
2203
|
+
if centery >0. and yb!=-99999.:
|
2204
|
+
decal = centery-yb
|
2205
|
+
y+=decal
|
2206
|
+
yl+=decal
|
2207
|
+
yb+=decal
|
2208
|
+
yr+=decal
|
2209
|
+
|
2210
|
+
ax.plot(x,y,color=col_ax,
|
2211
|
+
lw=lw,
|
2212
|
+
linestyle=style,
|
2213
|
+
alpha=alpha,
|
2214
|
+
label=label)
|
2215
|
+
|
2216
|
+
curtick=ax.get_xticks()
|
2217
|
+
ax.set_xticks(np.arange(min(curtick[0],(x[0]//2)*2),max(curtick[-1],(x[-1]//2)*2),2))
|
1761
2218
|
|
1762
|
-
|
2219
|
+
if sl != -99999.:
|
2220
|
+
ax.plot(sl,yl,'or',alpha=alpha)
|
2221
|
+
if sb != -99999.:
|
2222
|
+
ax.plot(sb,yb,'ob',alpha=alpha)
|
2223
|
+
if sr != -99999.:
|
2224
|
+
ax.plot(sr,yr,'og',alpha=alpha)
|
2225
|
+
if sld != -99999.:
|
2226
|
+
ax.plot(sld,yld,'*r',alpha=alpha)
|
2227
|
+
if srd != -99999.:
|
2228
|
+
ax.plot(srd,yrd,'*g',alpha=alpha)
|
1763
2229
|
|
2230
|
+
def plot_cs(self,
|
2231
|
+
fwl:float = None, show:bool = False, forceaspect:bool = True,
|
2232
|
+
fig:Figure = None, ax:Axes = None,
|
2233
|
+
plotlaz:bool = True, clear:bool = True,
|
2234
|
+
linked_arrays:dict = {}):
|
2235
|
+
""" Plot the cross section with matplotlib - with options
|
2236
|
+
|
2237
|
+
:param fwl: fill water level - if None, no filling
|
2238
|
+
:param show: if True, show the figure
|
2239
|
+
:param forceaspect: if True, force aspect ratio
|
2240
|
+
:param fig: figure Matplotlib
|
2241
|
+
:param ax: axes Matplotlib
|
2242
|
+
:param plotlaz: if True, plot LAZ points if available
|
2243
|
+
:param clear: if True, clear the axis before plotting
|
2244
|
+
:param linked_arrays: dictionary of linked arrays to plot on the section
|
1764
2245
|
"""
|
1765
|
-
This method plots the hydraulic geometries computed by the relations method
|
1766
|
-
(Hydraulic radius, wetted area, wetted perimeter, Top width).
|
1767
2246
|
|
1768
|
-
|
1769
|
-
|
1770
|
-
- fwq: for water discharge.
|
2247
|
+
if linked_arrays is None:
|
2248
|
+
linked_arrays = {}
|
1771
2249
|
|
1772
|
-
|
1773
|
-
sl,sb,sr,zl,zb,zr, sld, srd, zld, zrd = self.get_sz_banksbed()
|
1774
|
-
x,y = self.get_sz()
|
2250
|
+
x,y=self.get_sz()
|
1775
2251
|
|
1776
|
-
|
1777
|
-
if fwd is None or fwd <= 0:
|
1778
|
-
fwd =0
|
1779
|
-
fwl= self.zmin+ fwd
|
1780
|
-
elif fwd > 0 :
|
1781
|
-
fwl = self.zmin + fwd
|
2252
|
+
sl,sb,sr,yl,yb,yr, sld, srd, yld, yrd = self.get_sz_banksbed()
|
1782
2253
|
|
1783
|
-
|
1784
|
-
|
1785
|
-
|
2254
|
+
xmin=x[0]
|
2255
|
+
xmax=x[-1]
|
2256
|
+
ymin=self.get_minz()
|
2257
|
+
ymax=self.get_maxz()
|
1786
2258
|
|
1787
|
-
|
1788
|
-
|
1789
|
-
|
1790
|
-
|
2259
|
+
dy=ymax-ymin
|
2260
|
+
ymin-=dy/4.
|
2261
|
+
ymax+=dy/4.
|
2262
|
+
|
2263
|
+
if ax is None:
|
2264
|
+
redraw=False
|
2265
|
+
fig = plt.figure()
|
2266
|
+
ax=fig.add_subplot(111)
|
2267
|
+
else:
|
2268
|
+
redraw=True
|
1791
2269
|
if clear:
|
1792
|
-
|
1793
|
-
# myax3.clear()
|
2270
|
+
ax.cla()
|
1794
2271
|
|
1795
|
-
|
1796
|
-
|
1797
|
-
|
1798
|
-
|
2272
|
+
if np.min(y) != -99999. and np.max(y) != -99999.:
|
2273
|
+
ax.plot(x,y,color='black',
|
2274
|
+
lw=2.0,
|
2275
|
+
label='Profil')
|
1799
2276
|
|
1800
|
-
|
1801
|
-
|
1802
|
-
|
2277
|
+
if self.parent is not None:
|
2278
|
+
if plotlaz and self.parent.gridlaz is not None:
|
2279
|
+
minlaz=ymin
|
2280
|
+
maxlaz=ymax
|
2281
|
+
minlaz,maxlaz=self.plot_laz(fig=fig,ax=ax)
|
2282
|
+
if np.min(y) != -99999. and np.max(y) != -99999.:
|
2283
|
+
ymin = min(ymin,minlaz)
|
2284
|
+
ymax = max(ymax,maxlaz)
|
2285
|
+
else:
|
2286
|
+
ymin = minlaz
|
2287
|
+
ymax = maxlaz
|
1803
2288
|
|
1804
|
-
|
1805
|
-
if fwq is not None and fwq > 0.:
|
1806
|
-
# First, We select the closest critical discharge to the user's input.
|
1807
|
-
mask = np.absolute(self.criticaldischarge - fwq)
|
1808
|
-
index = np.argmin(mask)
|
1809
|
-
# Second, since the matrices have the same shapes and their elements are sorted likewise,
|
1810
|
-
# we use the index of the selected critical discharge to find the corresponding hydraulic geometries.
|
1811
|
-
cr_wetarea = self.wetarea[index]
|
1812
|
-
cr_wetperimeter = self.wetperimeter[index]
|
1813
|
-
cr_width = self.localwidth[index]
|
1814
|
-
cr_radius = self.hyrdaulicradius[index]
|
1815
|
-
cr_h = self.waterdepth[index]
|
1816
|
-
myax3.plot(cr_wetarea,cr_h + self.zmin,'ok' )
|
1817
|
-
myax3.annotate(_('$Critical$ $characteristics$ \nH: %s $m$ \nS: %s $m^2$ \nP: %s $m$ \nW: %s $m$ \n \n')% (round(cr_radius,2),round(cr_wetarea,2),round(cr_wetperimeter,2),round(cr_width,2)),\
|
1818
|
-
(cr_wetarea, cr_h + self.zmin), alpha= 1, fontsize = _('x-small'),color =_('black'))
|
2289
|
+
self.plot_linked(fig,ax,linked_arrays)
|
1819
2290
|
|
1820
|
-
|
1821
|
-
|
1822
|
-
myax3.plot(cr_width,cr_h + self.zmin,_('or') )
|
1823
|
-
myax3.plot(cr_radius,cr_h + self.zmin,_('og') )
|
2291
|
+
if fwl is not None:
|
2292
|
+
ax.fill_between(x,y,fwl,where=y<=fwl,facecolor='cyan',alpha=0.3,interpolate=True)
|
1824
2293
|
|
1825
|
-
|
1826
|
-
|
1827
|
-
|
1828
|
-
|
1829
|
-
|
2294
|
+
if sl != -99999.:
|
2295
|
+
ax.plot(sl,yl,'or')
|
2296
|
+
if sb != -99999.:
|
2297
|
+
ax.plot(sb,yb,'ob')
|
2298
|
+
if sr != -99999.:
|
2299
|
+
ax.plot(sr,yr,'og')
|
2300
|
+
if sld != -99999.:
|
2301
|
+
ax.plot(sld,yld,'*r')
|
2302
|
+
if srd != -99999.:
|
2303
|
+
ax.plot(srd,yrd,'*g')
|
1830
2304
|
|
1831
|
-
|
1832
|
-
|
1833
|
-
|
1834
|
-
|
1835
|
-
myax3.set_ylabel(_('Water depth - h\n($m$)'), size=12,rotation=270, labelpad=50)
|
2305
|
+
ax.set_title(self.myname)
|
2306
|
+
ax.set_xlabel(_('Distance [m]'))
|
2307
|
+
ax.set_ylabel('Elevation [EL.m]')
|
2308
|
+
ax.legend()
|
1836
2309
|
|
1837
|
-
|
1838
|
-
|
1839
|
-
|
1840
|
-
def depth_to_alt(y):
|
1841
|
-
return y+self.zmin
|
1842
|
-
secax = myax3.secondary_yaxis(_('right'), functions=(alt_to_depth,depth_to_alt))
|
2310
|
+
tol=(xmax-xmin)/10.
|
2311
|
+
ax.set_xlim(xmin-tol,xmax+tol)
|
2312
|
+
ax.set_ylim(ymin,ymax)
|
1843
2313
|
|
1844
|
-
|
1845
|
-
|
1846
|
-
|
1847
|
-
|
1848
|
-
|
2314
|
+
nbticks = 20
|
2315
|
+
dzticks = max((((ymax-ymin)/nbticks) // .25) *.25,.25)
|
2316
|
+
|
2317
|
+
ax.set_yticks(np.arange((ymin//.25)*.25,(ymax//.25)*.25,dzticks))
|
2318
|
+
ax.grid()
|
2319
|
+
|
2320
|
+
if forceaspect:
|
2321
|
+
aspect=1.0*(ymax-ymin)/(xmax-xmin)*(ax.get_xlim()[1] - ax.get_xlim()[0]) / (ax.get_ylim()[1] - ax.get_ylim()[0])
|
2322
|
+
ax.set_aspect(aspect)
|
1849
2323
|
|
1850
2324
|
if show:
|
1851
2325
|
fig.show()
|
1852
2326
|
|
2327
|
+
if redraw:
|
2328
|
+
fig.canvas.draw()
|
2329
|
+
|
2330
|
+
return sl,sb,sr,yl,yb,yr
|
1853
2331
|
|
1854
2332
|
|
1855
2333
|
class crosssections(Element_To_Draw):
|
@@ -1860,6 +2338,7 @@ class crosssections(Element_To_Draw):
|
|
1860
2338
|
- SPW_2025 --> format ='2025_xlsx'
|
1861
2339
|
- WOLF vecz --> format ='vecz'
|
1862
2340
|
- WOLF sxy --> format ='sxy'
|
2341
|
+
- WOLF zones --> format ='zones'
|
1863
2342
|
|
1864
2343
|
L'objet stocke ses informations dans un dictionnaire : self.myprofiles
|
1865
2344
|
Les clés de chaque entrée sont:
|
@@ -1884,22 +2363,54 @@ class crosssections(Element_To_Draw):
|
|
1884
2363
|
|
1885
2364
|
"""
|
1886
2365
|
|
1887
|
-
myprofiles:dict
|
2366
|
+
myprofiles:dict['cs':profile, 'index':int, 'left':wolfvertex, 'bed':wolfvertex, 'right':wolfvertex, 'left_down':wolfvertex, 'right_down':wolfvertex]
|
1888
2367
|
mygenprofiles:dict
|
1889
2368
|
|
1890
2369
|
def __init__(self,
|
1891
|
-
|
1892
|
-
format:typing.Literal['2000','2022', '2025_xlsx','vecz','sxy']='2022',
|
2370
|
+
fn_or_Zones:str | Path | Zones = '',
|
2371
|
+
format:typing.Literal['2000','2022', '2025_xlsx','vecz','sxy', 'zones']='2022',
|
1893
2372
|
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',
|
1894
2373
|
mapviewer = None,
|
1895
2374
|
idx='',
|
1896
|
-
plotted=True
|
2375
|
+
plotted=True,
|
2376
|
+
from_zones:Zones = None,
|
2377
|
+
force_unique_name:bool = False) -> None:
|
2378
|
+
"""
|
2379
|
+
Constructor of the crosssections class
|
2380
|
+
|
2381
|
+
ATTENTION:
|
2382
|
+
- If you use "from_zones", the cross-sections will be read from the provided Zones instance, overriding fn_or_Zones and format.
|
2383
|
+
- If you use "from_zones", the cross-sections will be added only if "used" in the Zones instance.
|
2384
|
+
- If you use "fn_or_Zones" as an instance of Zones, the format must be "zones". In this case, only the first zone will be used and cross-sections will be added even if not "used".
|
2385
|
+
|
2386
|
+
:param fn_or_Zones: filename (str or Path) or instance of Zones containing the sections
|
2387
|
+
:param format: format of the sections file - '2000','2022','2025_xlsx','vecz','sxy', 'zones'
|
2388
|
+
:param dirlaz: directory containing LAZ files or instance of xyz_laz_grids
|
2389
|
+
:param mapviewer: instance of MapViewer (if None, no mapviewer is used)
|
2390
|
+
:param idx: index of the crosssections instance
|
2391
|
+
:param plotted: if True, the crosssections will be plotted in the mapviewer (if provided)
|
2392
|
+
:param from_zones: if not None, the sections will be read from the provided Zones instance (overrides fn_or_Zones and format)
|
2393
|
+
:param force_unique_name: if True, force unique names for each section (useful when reading from Zones)
|
2394
|
+
:return: None
|
1897
2395
|
|
1898
|
-
|
2396
|
+
"""
|
2397
|
+
|
2398
|
+
assert format in ['2000','2022','2025_xlsx','vecz','sxy', 'zones'], _('Format %s not supported!')%format
|
1899
2399
|
|
1900
2400
|
super().__init__(idx=idx, plotted= plotted, mapviewer=mapviewer, need_for_wx=False)
|
1901
2401
|
|
1902
|
-
|
2402
|
+
if from_zones is not None:
|
2403
|
+
assert isinstance(from_zones, Zones), _('from_zones must be an instance of Zones!')
|
2404
|
+
self.filename = ''
|
2405
|
+
format=''
|
2406
|
+
elif isinstance(fn_or_Zones, Zones):
|
2407
|
+
assert format=='zones', _('If fn_or_Zones is an instance of Zones, format must be "zones"!')
|
2408
|
+
self.filename = fn_or_Zones.filename
|
2409
|
+
elif isinstance(fn_or_Zones, (str, Path)):
|
2410
|
+
self.filename = fn_or_Zones
|
2411
|
+
else:
|
2412
|
+
raise Exception(_('fn_or_Zones must be a string, Path or an instance of Zones!'))
|
2413
|
+
|
1903
2414
|
self.myzones=None
|
1904
2415
|
self.myzone=None
|
1905
2416
|
|
@@ -1920,25 +2431,25 @@ class crosssections(Element_To_Draw):
|
|
1920
2431
|
self.linked_zones=None
|
1921
2432
|
|
1922
2433
|
if format in ['2000','2022','sxy']:
|
1923
|
-
self.filename=
|
1924
|
-
if Path(
|
1925
|
-
f=open(
|
2434
|
+
self.filename=fn_or_Zones
|
2435
|
+
if Path(fn_or_Zones).exists() and fn_or_Zones!='':
|
2436
|
+
f=open(fn_or_Zones,'r')
|
1926
2437
|
lines=f.read().splitlines()
|
1927
2438
|
f.close()
|
1928
2439
|
elif format=='2025_xlsx':
|
1929
2440
|
# For the 2025_xlsx format, we need to read the file using pandas
|
1930
|
-
if Path(
|
2441
|
+
if Path(fn_or_Zones).exists() and fn_or_Zones!='':
|
1931
2442
|
# read the first sheet of the excel file
|
1932
2443
|
# Note: header=1 means that the first row is not the header, but the second row is.
|
1933
|
-
logging.info(_('Reading cross section data from %s')%
|
2444
|
+
logging.info(_('Reading cross section data from %s')%fn_or_Zones)
|
1934
2445
|
try:
|
1935
|
-
lines = pd.read_excel(
|
1936
|
-
logging.info(_('Cross section data read successfully from %s')%
|
2446
|
+
lines = pd.read_excel(fn_or_Zones, sheet_name=0, header=1)
|
2447
|
+
logging.info(_('Cross section data read successfully from %s')%fn_or_Zones)
|
1937
2448
|
except Exception as e:
|
1938
|
-
logging.error(_('Error reading the file %s: %s')%(
|
2449
|
+
logging.error(_('Error reading the file %s: %s')%(fn_or_Zones, str(e)))
|
1939
2450
|
lines = pd.DataFrame()
|
1940
2451
|
else:
|
1941
|
-
logging.error(_('File %s does not exist!')%
|
2452
|
+
logging.error(_('File %s does not exist!')%fn_or_Zones)
|
1942
2453
|
lines = []
|
1943
2454
|
# For other formats (e.g. vecz)
|
1944
2455
|
else:
|
@@ -2232,23 +2743,60 @@ class crosssections(Element_To_Draw):
|
|
2232
2743
|
curdict['right']=cursect.bankright
|
2233
2744
|
|
2234
2745
|
|
2235
|
-
|
2746
|
+
elif from_zones is not None:
|
2747
|
+
|
2748
|
+
for curzone in from_zones.myzones:
|
2749
|
+
for curvec in curzone.myvectors:
|
2750
|
+
if curvec.used:
|
2751
|
+
|
2752
|
+
if curvec.myname in self.myprofiles.keys():
|
2753
|
+
if force_unique_name:
|
2754
|
+
logging.info(_('Profile %s already exists in cross-sections. We change its name to %s.')%(curvec.myname, f"{curvec.myname}_{len(self.myprofiles)}"))
|
2755
|
+
curvec.myname = f"{curvec.myname}_{len(self.myprofiles)}"
|
2756
|
+
else:
|
2757
|
+
logging.warning(_('Profile %s already exists in cross-sections. Previous will be overwritten.')%curvec.myname)
|
2758
|
+
|
2759
|
+
self.myprofiles[curvec.myname]={}
|
2760
|
+
curdict=self.myprofiles[curvec.myname]
|
2761
|
+
|
2762
|
+
curdict['index']=len(self.myprofiles)
|
2763
|
+
curdict['left']=None
|
2764
|
+
curdict['bed']=None
|
2765
|
+
curdict['right']=None
|
2766
|
+
curdict['left_down'] = None
|
2767
|
+
curdict['right_down'] = None
|
2768
|
+
|
2769
|
+
curdict['cs'] = profile(name=curvec.myname, parent=self)
|
2770
|
+
cursect:profile
|
2771
|
+
cursect=curdict['cs']
|
2772
|
+
|
2773
|
+
cursect.myvertices = curvec.myvertices.copy()
|
2774
|
+
|
2236
2775
|
elif len(lines)==0:
|
2776
|
+
|
2237
2777
|
if format=='vecz' or format=='zones':
|
2238
2778
|
|
2239
|
-
if isinstance(
|
2240
|
-
self.filename=
|
2241
|
-
tmpzones=
|
2242
|
-
|
2243
|
-
|
2244
|
-
|
2779
|
+
if isinstance(fn_or_Zones, Zones):
|
2780
|
+
self.filename=fn_or_Zones.filename
|
2781
|
+
tmpzones = fn_or_Zones
|
2782
|
+
|
2783
|
+
elif isinstance(fn_or_Zones, (str, Path)):
|
2784
|
+
self.filename=fn_or_Zones
|
2785
|
+
tmpzones = Zones(fn_or_Zones, find_minmax=False)
|
2245
2786
|
|
2246
2787
|
curzone:zone
|
2247
2788
|
curvec:vector
|
2248
|
-
curzone=tmpzones.myzones[0]
|
2789
|
+
curzone = tmpzones.myzones[0]
|
2249
2790
|
index=0
|
2250
2791
|
for curvec in curzone.myvectors:
|
2251
2792
|
|
2793
|
+
if curvec.myname in self.myprofiles.keys():
|
2794
|
+
if force_unique_name:
|
2795
|
+
logging.info(_('Profile %s already exists in cross-sections. We change its name to %s.')%(curvec.myname, f"{curvec.myname}_{index}"))
|
2796
|
+
curvec.myname = f"{curvec.myname}_{index}"
|
2797
|
+
else:
|
2798
|
+
logging.warning(_('Profile %s already exists in cross-sections. Previous will be overwritten.')%curvec.myname)
|
2799
|
+
|
2252
2800
|
self.myprofiles[curvec.myname]={}
|
2253
2801
|
curdict=self.myprofiles[curvec.myname]
|
2254
2802
|
|
@@ -2256,18 +2804,47 @@ class crosssections(Element_To_Draw):
|
|
2256
2804
|
curdict['left']=None
|
2257
2805
|
curdict['bed']=None
|
2258
2806
|
curdict['right']=None
|
2807
|
+
curdict['left_down'] = None
|
2808
|
+
curdict['right_down'] = None
|
2259
2809
|
|
2260
2810
|
index+=1
|
2261
|
-
curdict['cs']=profile(name=curvec.myname,parent=self)
|
2811
|
+
curdict['cs']=profile(name=curvec.myname, parent=self)
|
2262
2812
|
cursect:profile
|
2263
2813
|
cursect=curdict['cs']
|
2264
2814
|
|
2265
|
-
cursect.myvertices = curvec.myvertices
|
2815
|
+
cursect.myvertices = curvec.myvertices.copy()
|
2266
2816
|
|
2267
2817
|
self.verif_bed()
|
2268
2818
|
self.find_minmax(True)
|
2269
2819
|
self.init_cloud()
|
2270
2820
|
|
2821
|
+
|
2822
|
+
@property
|
2823
|
+
def nb_profiles(self):
|
2824
|
+
""" Return the number of profiles in the cross-sections. """
|
2825
|
+
return len(self.myprofiles)
|
2826
|
+
|
2827
|
+
def __getitem__(self, key):
|
2828
|
+
""" Get a profile by its name, index or (x, y) coordinates.
|
2829
|
+
|
2830
|
+
:param key: The name or index of the profile or coordinates where the search is performed.
|
2831
|
+
:type key: str | int | tuple | list
|
2832
|
+
:return: The profile corresponding to the key.
|
2833
|
+
:rtype: profile
|
2834
|
+
"""
|
2835
|
+
|
2836
|
+
if isinstance(key, str):
|
2837
|
+
return self.myprofiles[key]['cs']
|
2838
|
+
elif isinstance(key, int):
|
2839
|
+
return self.myprofiles[list(self.myprofiles.keys())[key]]['cs']
|
2840
|
+
elif isinstance(key, tuple | list):
|
2841
|
+
if len(key) == 2:
|
2842
|
+
x, y = key
|
2843
|
+
assert isinstance(x, float) and isinstance(y, float), _('Coordinates must be floats.')
|
2844
|
+
return self.select_profile(x, y)
|
2845
|
+
else:
|
2846
|
+
raise KeyError(_('Key must be a string or an integer or a tuple/list of (float, float).'))
|
2847
|
+
|
2271
2848
|
def init_cloud(self):
|
2272
2849
|
""" Initialiaze cloud points for cross-sections. """
|
2273
2850
|
|
@@ -2435,7 +3012,7 @@ class crosssections(Element_To_Draw):
|
|
2435
3012
|
curlinkprop = myvec.myname
|
2436
3013
|
|
2437
3014
|
myvecls = myvec.asshapely_ls()
|
2438
|
-
prepls=
|
3015
|
+
prepls=prepare(myvecls)
|
2439
3016
|
|
2440
3017
|
for cursname in self.myprofiles.values():
|
2441
3018
|
curs:profile
|
@@ -2464,6 +3041,7 @@ class crosssections(Element_To_Draw):
|
|
2464
3041
|
curs.refpoints[curlinkprop]=myvert
|
2465
3042
|
|
2466
3043
|
cursname[curlinkprop]=myvert
|
3044
|
+
destroy_prepared(prepls)
|
2467
3045
|
|
2468
3046
|
self.update_cloud()
|
2469
3047
|
|
@@ -2628,12 +3206,12 @@ class crosssections(Element_To_Draw):
|
|
2628
3206
|
for curprof in self.myprofiles.keys():
|
2629
3207
|
curdict=self.myprofiles[curprof]
|
2630
3208
|
curvec=curdict['cs']
|
2631
|
-
curvec:
|
3209
|
+
curvec:profile
|
2632
3210
|
if curvec.used:
|
2633
3211
|
self.myzone.add_vector(curvec, forceparent=True)
|
2634
3212
|
|
2635
3213
|
if self.plotted:
|
2636
|
-
self._prep_listogl()
|
3214
|
+
self._prep_listogl()
|
2637
3215
|
|
2638
3216
|
def showstructure(self, parent=None, forceupdate=False):
|
2639
3217
|
""" Show the structure of the cross-sections in the zones. """
|
@@ -2641,7 +3219,7 @@ class crosssections(Element_To_Draw):
|
|
2641
3219
|
self.set_zones()
|
2642
3220
|
self.myzones.showstructure(parent, forceupdate)
|
2643
3221
|
|
2644
|
-
def get_upstream(self) -> dict:
|
3222
|
+
def get_upstream(self) -> dict['cs':profile, 'index':int, 'left':wolfvertex | None, 'bed':wolfvertex | None, 'right':wolfvertex | None, 'left_down':wolfvertex | None, 'right_down':wolfvertex | None]:
|
2645
3223
|
""" Get the upstream profile of the cross-sections."""
|
2646
3224
|
curprof:profile
|
2647
3225
|
curprof=self.myprofiles[list(self.myprofiles.keys())[0]]['cs']
|
@@ -2651,7 +3229,7 @@ class crosssections(Element_To_Draw):
|
|
2651
3229
|
|
2652
3230
|
return self.myprofiles[curprof.myname]
|
2653
3231
|
|
2654
|
-
def get_downstream(self) -> dict:
|
3232
|
+
def get_downstream(self) -> dict['cs':profile, 'index':int, 'left':wolfvertex | None, 'bed':wolfvertex | None, 'right':wolfvertex | None, 'left_down':wolfvertex | None, 'right_down':wolfvertex | None]:
|
2655
3233
|
""" Get the downstream profile of the cross-sections. """
|
2656
3234
|
curprof:profile
|
2657
3235
|
curprof=self.myprofiles[list(self.myprofiles.keys())[0]]['cs']
|
@@ -2860,7 +3438,7 @@ class crosssections(Element_To_Draw):
|
|
2860
3438
|
mysorted = curdict['sorted'] = []
|
2861
3439
|
length = vecsupport.length
|
2862
3440
|
|
2863
|
-
prepsup=
|
3441
|
+
prepsup=prepare(vecsupport) #Prepare le vecteur support aux opérations récurrentes
|
2864
3442
|
curvect:profile
|
2865
3443
|
for idx,curv in self.myprofiles.items():
|
2866
3444
|
#bouclage sur les sections
|
@@ -2878,6 +3456,7 @@ class crosssections(Element_To_Draw):
|
|
2878
3456
|
curvect.s = length - mydist
|
2879
3457
|
else:
|
2880
3458
|
curvect.s = mydist
|
3459
|
+
destroy_prepared(prepsup)
|
2881
3460
|
|
2882
3461
|
#on trie le résultat en place
|
2883
3462
|
mysorted.sort(key=lambda x:x.s)
|
@@ -2929,7 +3508,7 @@ class crosssections(Element_To_Draw):
|
|
2929
3508
|
self.set_zones()
|
2930
3509
|
self.myzones.saveas(filename=filename)
|
2931
3510
|
|
2932
|
-
def select_profile(self, x:float, y:float):
|
3511
|
+
def select_profile(self, x:float, y:float) -> profile:
|
2933
3512
|
""" Select the profile closest to the given coordinates (x, y).
|
2934
3513
|
|
2935
3514
|
:param x: X coordinate of the point.
|
@@ -2965,271 +3544,426 @@ class Interpolator():
|
|
2965
3544
|
|
2966
3545
|
"""
|
2967
3546
|
|
2968
|
-
def __init__(self, vec1:vector,
|
2969
|
-
|
3547
|
+
def __init__(self, vec1:vector,
|
3548
|
+
vec2:vector,
|
3549
|
+
supports:list[vector],
|
3550
|
+
ds:float = 1.,
|
3551
|
+
epsilon:float = 5e-2) -> None:
|
3552
|
+
"""
|
3553
|
+
Initialize the Interpolator with two vectors and a list of support vectors.
|
3554
|
+
|
3555
|
+
Not all supports need to intersect both sections.
|
3556
|
+
First, we check if each support intersects the sections.
|
3557
|
+
If a support does not intersect both sections, it will be ignored.
|
3558
|
+
|
3559
|
+
Support vectors must have Z coordinates.
|
3560
|
+
|
3561
|
+
If the Z-coordinates at the intersection points between the supports and the sections are not identical,
|
3562
|
+
"alpha" values are computed for interpolation:
|
3563
|
+
|
3564
|
+
alpha = Z_support / Z_section
|
3565
|
+
|
3566
|
+
The Z-coordinate of each interpolated point is then:
|
3567
|
+
|
3568
|
+
Z_interp = Z_section * alpha
|
3569
|
+
|
3570
|
+
Alpha is linearly interpolated between the two sections according to
|
3571
|
+
the curvilinear abscissa. The same applies to the Z-coordinates of the supports and the sections.
|
3572
|
+
|
3573
|
+
If the sections are not straight lines, a "trace" is defined by the intersection points
|
3574
|
+
between the supports and the sections. This trace is used to pre-compute the interpolation points.
|
3575
|
+
Then, deltas (in X and Y) are added to "deform" the local sections.
|
3576
|
+
These deltas are linearly interpolated between the two sections.
|
3577
|
+
|
3578
|
+
:param vec1: The first vector representing the first section.
|
3579
|
+
:type vec1: vector | profile
|
3580
|
+
:param vec2: The second vector representing the second section.
|
3581
|
+
:type vec2: vector | profile
|
3582
|
+
:param supports: A list of support vectors used for interpolation. This should be a list of vectors, not a zone.
|
3583
|
+
:type supports: list[vector]
|
3584
|
+
:param ds: The step size for interpolation, default is 1.0 [m].
|
3585
|
+
:type ds: float
|
3586
|
+
:param epsilon: The tolerance for intersection checks, default is 5e-2 [m] (5 cm).
|
3587
|
+
:type epsilon: float
|
3588
|
+
"""
|
2970
3589
|
|
2971
3590
|
self.interpolants=[]
|
2972
3591
|
|
2973
|
-
sect1={}
|
2974
|
-
sect2={}
|
3592
|
+
sect1 = {} # local dict to store section 1 data
|
3593
|
+
sect2 = {} # local dict to store section 2 data
|
3594
|
+
|
3595
|
+
used_supports:dict[int: dict['vec':vector, int:float]] = {}
|
2975
3596
|
|
2976
3597
|
#Linestrings shapely des sections 1 et 2
|
2977
3598
|
#ATTENTION, SHAPELY est une librairie géométrique 2D --> des outils spécifiques ont été programmés pour faire l'interpolation 3D
|
2978
|
-
s1=sect1['ls']=vec1.
|
2979
|
-
s2=sect2['ls']=vec2.
|
3599
|
+
s1 = sect1['ls'] = vec1.linestring
|
3600
|
+
s2 = sect2['ls'] = vec2.linestring
|
3601
|
+
|
3602
|
+
eps = epsilon # Tolérance pour vérifier l'intersection
|
3603
|
+
|
3604
|
+
# Check if sections intersect the supports at their endpoints, considering a small epsilon (eps) for precision issues
|
3605
|
+
def check_intersection(section_ls:LineString, vec:profile, myls:LineString, eps:float):
|
3606
|
+
loc = None
|
3607
|
+
# Try intersection at start
|
3608
|
+
pt = Point(section_ls.xy[0][0], section_ls.xy[1][0])
|
3609
|
+
pt_proj = myls.interpolate(myls.project(pt))
|
3610
|
+
length = pt_proj.distance(pt)
|
3611
|
+
intersected = length < eps
|
3612
|
+
if intersected:
|
3613
|
+
vec.myvertices[0].x = pt_proj.xy[0][0]
|
3614
|
+
vec.myvertices[0].y = pt_proj.xy[1][0]
|
3615
|
+
section_ls = vec.linestring
|
3616
|
+
loc = 'first'
|
3617
|
+
|
3618
|
+
# Try intersection at end if not found at start
|
3619
|
+
if not intersected:
|
3620
|
+
pt = Point(section_ls.xy[0][-1], section_ls.xy[1][-1])
|
3621
|
+
pt_proj = myls.interpolate(myls.project(pt))
|
3622
|
+
length = pt_proj.distance(pt)
|
3623
|
+
intersected = length < eps
|
3624
|
+
if intersected:
|
3625
|
+
vec.myvertices[-1].x = pt_proj.xy[0][-1]
|
3626
|
+
vec.myvertices[-1].y = pt_proj.xy[1][-1]
|
3627
|
+
section_ls = vec.linestring
|
3628
|
+
loc = 'last'
|
3629
|
+
return intersected, section_ls, loc
|
3630
|
+
|
3631
|
+
for cur_trace in supports:
|
3632
|
+
# RRecherche des distances des intersections des sections sur le support
|
3633
|
+
|
3634
|
+
# linestring du support courant
|
3635
|
+
cur_support_ls = cur_trace.linestring
|
2980
3636
|
|
2981
|
-
|
2982
|
-
|
2983
|
-
|
2984
|
-
eps=5.e-2
|
3637
|
+
#intersections du vecteur support avec les sections
|
3638
|
+
i1 = cur_support_ls.intersects(s1) # check is intersects section 1 --> return True or False -- see https://shapely.readthedocs.io/en/2.1.1/reference/shapely.intersects.html
|
2985
3639
|
|
2986
|
-
|
2987
|
-
#
|
2988
|
-
#
|
2989
|
-
|
2990
|
-
myls=curvec.asshapely_ls()
|
3640
|
+
# If the section does not intersect the support, two possibilities:
|
3641
|
+
# 1. The first vertex of the section is on/near the support line
|
3642
|
+
# 2. The last vertex of the section is on/near the support line
|
3643
|
+
# If neither is the case, we ignore this support.
|
2991
3644
|
|
2992
|
-
#intersections du vecteur support avec les sections
|
2993
|
-
i1=myls.intersects(s1)
|
2994
3645
|
if not i1:
|
2995
|
-
|
2996
|
-
|
2997
|
-
|
2998
|
-
i1 = length<eps
|
2999
|
-
|
3000
|
-
if i1:
|
3001
|
-
vec1.myvertices[0].x=pt1.xy[0][0]
|
3002
|
-
vec1.myvertices[0].y=pt1.xy[1][0]
|
3003
|
-
s1=vec1.asshapely_ls()
|
3004
|
-
|
3005
|
-
if not i1:
|
3006
|
-
pt=Point(s1.xy[0][-1],s1.xy[1][-1])
|
3007
|
-
pt1=myls.interpolate(myls.project(pt))
|
3008
|
-
length = pt1.distance(pt)
|
3009
|
-
i1 = length<eps
|
3010
|
-
if i1:
|
3011
|
-
vec1.myvertices[-1].x=pt1.xy[0][-1]
|
3012
|
-
vec1.myvertices[-1].y=pt1.xy[1][-1]
|
3013
|
-
s1=vec1.asshapely_ls()
|
3014
|
-
|
3015
|
-
i2=myls.intersects(s2)
|
3646
|
+
i1, s1, _loc = check_intersection(s1, vec1, cur_support_ls, eps)
|
3647
|
+
|
3648
|
+
i2 = cur_support_ls.intersects(s2)
|
3016
3649
|
if not i2:
|
3017
|
-
|
3018
|
-
pt2=myls.interpolate(myls.project(pt))
|
3019
|
-
length = pt2.distance(Point(s2.xy[0][0],s2.xy[1][0]))
|
3020
|
-
i2 = length<eps
|
3021
|
-
|
3022
|
-
if i2:
|
3023
|
-
vec2.myvertices[0].x=pt2.xy[0][0]
|
3024
|
-
vec2.myvertices[0].y=pt2.xy[1][0]
|
3025
|
-
s2=vec2.asshapely_ls()
|
3026
|
-
|
3027
|
-
if not i2:
|
3028
|
-
pt=Point(s2.xy[0][-1],s2.xy[1][-1])
|
3029
|
-
pt2=myls.interpolate(myls.project(pt))
|
3030
|
-
length = pt2.distance(pt)
|
3031
|
-
i2 = length<eps
|
3032
|
-
if i2:
|
3033
|
-
vec2.myvertices[-1].x=pt2.xy[0][-1]
|
3034
|
-
vec2.myvertices[-1].y=pt2.xy[1][-1]
|
3035
|
-
s2=vec2.asshapely_ls()
|
3650
|
+
i2, s2, _loc = check_intersection(s2, vec2, cur_support_ls, eps)
|
3036
3651
|
|
3652
|
+
# Si le support intersecte les sections, on le conserve
|
3653
|
+
# et on le stocke dans le dictionnaire used_supports
|
3037
3654
|
if i1 and i2:
|
3038
|
-
|
3039
|
-
|
3040
|
-
|
3041
|
-
|
3655
|
+
loc_sup = used_supports[len(used_supports)] = {}
|
3656
|
+
loc_sup['vec'] = cur_trace
|
3657
|
+
|
3658
|
+
|
3659
|
+
# Need to reset the PREPARED linestrings
|
3660
|
+
# to ensure they are recalculated with the new coordinates
|
3661
|
+
# of the sections after the intersection checks.
|
3662
|
+
# This is necessary because the coordinates may have been adjusted
|
3663
|
+
# to ensure they are close to the support lines.
|
3664
|
+
# Otherwise, the linestrings may not reflect the actual geometry.
|
3665
|
+
vec1.reset_linestring()
|
3666
|
+
vec2.reset_linestring()
|
3667
|
+
|
3668
|
+
s1 = vec1.linestring
|
3669
|
+
s2 = vec2.linestring
|
3042
3670
|
|
3043
3671
|
#bouclage sur les vecteurs supports pour trouver les intersections avec les sections
|
3044
3672
|
# - trouver la portion utile entre intersections des supports
|
3045
3673
|
# - trouver les distances sur les sections de ces intersections
|
3046
|
-
for k in range(
|
3047
|
-
#linestring du support courant
|
3048
|
-
#distances des intersections des sections sur le support
|
3049
|
-
myls:LineString
|
3050
|
-
myls=supls[k]['ls']
|
3674
|
+
for k in range(len(used_supports)):
|
3051
3675
|
|
3052
|
-
#intersections
|
3676
|
+
# distances des intersections des sections sur le support
|
3677
|
+
cur_trace:vector
|
3678
|
+
cur_trace = used_supports[k]['vec']
|
3679
|
+
|
3680
|
+
cur_support_ls:LineString
|
3681
|
+
cur_support_ls = cur_trace.linestring
|
3682
|
+
|
3683
|
+
# Coordonnées d'intersection du vecteur support avec les sections
|
3684
|
+
# On est certain que cela s'intersecte puisque l'on a vérifié juste avant
|
3685
|
+
|
3686
|
+
# Section "amont"
|
3687
|
+
i1 = cur_support_ls.intersection(s1) # --> return a Point or MultiPoint -- see https://shapely.readthedocs.io/en/stable/reference/shapely.intersection.html
|
3688
|
+
if not i1:
|
3689
|
+
i1, s1, loc1 = check_intersection(s1, vec1, cur_support_ls, eps)
|
3690
|
+
if loc1=='first':
|
3691
|
+
i1 = Point(s1.xy[0][0], s1.xy[1][0])
|
3692
|
+
elif loc1=='last':
|
3693
|
+
i1 = Point(s1.xy[0][-1], s1.xy[1][-1])
|
3053
3694
|
|
3054
|
-
|
3055
|
-
i1=myls.intersection(s1)
|
3056
|
-
if i1.geom_type=='MultiPoint':
|
3695
|
+
elif i1.geom_type=='MultiPoint':
|
3057
3696
|
i1=i1.geoms[0]
|
3058
3697
|
logging.debug('MultiPoint -- use first point or debug')
|
3059
3698
|
|
3060
|
-
|
3061
|
-
|
3062
|
-
|
3699
|
+
|
3700
|
+
# Section "aval"
|
3701
|
+
i2 = cur_support_ls.intersection(s2)
|
3702
|
+
if not i2:
|
3703
|
+
i2, s2, loc2 = check_intersection(s2, vec2, cur_support_ls, eps)
|
3704
|
+
if loc2=='first':
|
3705
|
+
i2 = Point(s2.xy[0][0], s2.xy[1][0])
|
3706
|
+
elif loc2=='last':
|
3707
|
+
i2 = Point(s2.xy[0][-1], s2.xy[1][-1])
|
3708
|
+
|
3709
|
+
elif i2.geom_type=='MultiPoint':
|
3063
3710
|
i2=i2.geoms[0]
|
3064
3711
|
logging.debug('MultiPoint -- use first point or debug')
|
3065
3712
|
|
3066
|
-
#Les distances, sur les sections, sont calculées
|
3067
|
-
|
3068
|
-
|
3069
|
-
|
3070
|
-
|
3071
|
-
|
3072
|
-
|
3073
|
-
|
3074
|
-
|
3075
|
-
|
3076
|
-
|
3077
|
-
|
3078
|
-
|
3079
|
-
|
3080
|
-
|
3081
|
-
|
3082
|
-
|
3083
|
-
|
3084
|
-
|
3085
|
-
|
3086
|
-
|
3087
|
-
|
3088
|
-
|
3089
|
-
|
3090
|
-
|
3091
|
-
|
3092
|
-
|
3093
|
-
|
3094
|
-
|
3095
|
-
|
3713
|
+
# Les distances curvilignes, sur les sections, sont calculées
|
3714
|
+
# en projetant l'intersection du vecteur support et des sections
|
3715
|
+
sect1[k] = s1.project(i1)
|
3716
|
+
sect2[k] = s2.project(i2)
|
3717
|
+
|
3718
|
+
# Les distances, sur le support, sont calculées en projetant
|
3719
|
+
# l'intersection du vecteur support et des sections
|
3720
|
+
used_supports[k][1] = cur_support_ls.project(i1)
|
3721
|
+
if used_supports[k][1] == -1.:
|
3722
|
+
# Problème de précision de calcul à gérer
|
3723
|
+
# Même type de problème que lors de la vérification de l'intersection
|
3724
|
+
|
3725
|
+
# We test the distance to the first and last points of the section
|
3726
|
+
# to ensure we have a valid projection.
|
3727
|
+
# We project on the support AND the section and store the result
|
3728
|
+
# in the used_supports and sect1/sect2 dictionaries.
|
3729
|
+
|
3730
|
+
pt_first = Point(vec1[0].x, vec1[0].y)
|
3731
|
+
pt_last = Point(vec1[-1].x, vec1[-1].y)
|
3732
|
+
|
3733
|
+
if cur_support_ls.distance(pt_first) < eps:
|
3734
|
+
used_supports[k][1] = cur_support_ls.project(pt_first)
|
3735
|
+
sect1[k] = s1.project(pt_first)
|
3736
|
+
|
3737
|
+
elif cur_support_ls.distance(pt_last) < eps:
|
3738
|
+
used_supports[k][1] = cur_support_ls.project(pt_last)
|
3739
|
+
sect1[k] = s1.project(pt_last)
|
3740
|
+
|
3741
|
+
used_supports[k][2] = cur_support_ls.project(i2)
|
3742
|
+
if used_supports[k][2] == -1.:
|
3743
|
+
# Problème de précision de calcul -- see previous comment
|
3744
|
+
pt_first = Point(vec2[0].x, vec2[0].y)
|
3745
|
+
pt_last = Point(vec2[-1].x, vec2[-1].y)
|
3746
|
+
|
3747
|
+
if cur_support_ls.distance(pt_first) < eps:
|
3748
|
+
used_supports[k][2] = cur_support_ls.project(pt_first)
|
3749
|
+
sect2[k] = s2.project(pt_first)
|
3750
|
+
|
3751
|
+
elif cur_support_ls.distance(pt_last) < eps:
|
3752
|
+
used_supports[k][2] = cur_support_ls.project(pt_last)
|
3753
|
+
sect2[k] = s2.project(pt_last)
|
3754
|
+
|
3755
|
+
# Replace the vector by the portion of the support vector between the two intersections
|
3756
|
+
used_supports[k]['vec'] = cur_trace.substring(used_supports[k][1], # curvilinear position of the intersection with section 1
|
3757
|
+
used_supports[k][2], # curvilinear position of the intersection with section 2
|
3758
|
+
is3D = False, # Compute in 2D, not 3D
|
3759
|
+
adim = False # In real coordinates, not adimensional
|
3760
|
+
)
|
3761
|
+
|
3762
|
+
|
3763
|
+
# Bouclage sur les intervalles --> nb_supports-1
|
3764
|
+
for k in range(len(used_supports)-1):
|
3765
|
+
|
3096
3766
|
interpolant=[]
|
3097
3767
|
self.interpolants.append(interpolant)
|
3098
3768
|
|
3099
|
-
|
3100
|
-
|
3769
|
+
cur_support_left:vector
|
3770
|
+
cur_support_right:vector
|
3101
3771
|
curvec1:vector
|
3102
3772
|
curvec2:vector
|
3103
3773
|
|
3104
|
-
#
|
3774
|
+
# Morceaux de sections entre intersections avec le support.
|
3775
|
+
# Comme les distances ont été calculées avec Shapely,
|
3776
|
+
# on doit trouver les morceaux en 2D et non 3D.
|
3105
3777
|
curvec1=sect1['sub'+str(k)]=vec1.substring(sect1[k],sect1[k+1],is3D=False,adim=False)
|
3106
3778
|
curvec2=sect2['sub'+str(k)]=vec2.substring(sect2[k],sect2[k+1],is3D=False,adim=False)
|
3107
3779
|
|
3108
|
-
#
|
3109
|
-
|
3110
|
-
|
3780
|
+
# Pointeurs vers les morceaux des supports
|
3781
|
+
cur_support_left = used_supports[k]['vec']
|
3782
|
+
cur_support_right = used_supports[k+1]['vec']
|
3111
3783
|
|
3112
|
-
#MAJ des longueurs 2D
|
3113
|
-
|
3114
|
-
|
3784
|
+
# MAJ des longueurs 2D ET 3D
|
3785
|
+
cur_support_left.update_lengths()
|
3786
|
+
cur_support_right.update_lengths()
|
3115
3787
|
curvec1.update_lengths()
|
3116
3788
|
curvec2.update_lengths()
|
3117
3789
|
|
3118
|
-
#Trouve la liste des distances à traiter pour le maillage
|
3119
|
-
|
3120
|
-
|
3121
|
-
|
3122
|
-
|
3123
|
-
|
3124
|
-
|
3125
|
-
|
3126
|
-
|
3127
|
-
|
3128
|
-
|
3129
|
-
|
3130
|
-
|
3131
|
-
|
3132
|
-
|
3133
|
-
|
3134
|
-
|
3135
|
-
|
3136
|
-
|
3137
|
-
|
3138
|
-
|
3139
|
-
|
3140
|
-
|
3141
|
-
|
3142
|
-
|
3143
|
-
|
3144
|
-
|
3145
|
-
#
|
3146
|
-
|
3147
|
-
|
3148
|
-
#
|
3149
|
-
|
3150
|
-
#
|
3151
|
-
|
3152
|
-
|
3153
|
-
|
3154
|
-
|
3155
|
-
|
3156
|
-
|
3157
|
-
|
3158
|
-
|
3159
|
-
|
3160
|
-
|
3161
|
-
|
3162
|
-
|
3163
|
-
|
3164
|
-
|
3165
|
-
|
3166
|
-
|
3167
|
-
|
3168
|
-
|
3169
|
-
|
3170
|
-
|
3171
|
-
|
3172
|
-
|
3790
|
+
# Trouve la liste des distances à traiter pour le maillage
|
3791
|
+
|
3792
|
+
# Nombre de points (entier) à évaluer pour s'approcher de la distance ds souhaitée
|
3793
|
+
# On divise la longueur 3D maximales des sections par la taille souhaitée
|
3794
|
+
# et on arrondit à l'entier supérieur
|
3795
|
+
nbi_sect = np.ceil(max(curvec1.length3D, curvec2.length3D) / ds)
|
3796
|
+
if nbi_sect == 0:
|
3797
|
+
logging.warning(_('No points to interpolate between sections {s1} and {s2} with distance {d} m.').format(s1=curvec1.myname, s2=curvec2.myname, d=ds))
|
3798
|
+
else:
|
3799
|
+
locds = 1./float(nbi_sect) # nouvelle distance de calcul
|
3800
|
+
|
3801
|
+
# Create a list of adimensional distances where to interpolate
|
3802
|
+
# We use :
|
3803
|
+
# - the cumulative length of the sections to create a list of distances
|
3804
|
+
# - a range from 0 to 1 with the step size locds
|
3805
|
+
dist3d = np.concatenate([np.arange(0.,1.,locds),
|
3806
|
+
np.cumsum(curvec1._lengthparts3D) / curvec1.length3D,
|
3807
|
+
np.cumsum(curvec2._lengthparts3D) / curvec2.length3D])
|
3808
|
+
|
3809
|
+
# Remove duplicates AND sort the distances
|
3810
|
+
dist3d = np.unique(dist3d)
|
3811
|
+
|
3812
|
+
# Nombre de points à traiter sur les supports.
|
3813
|
+
# On divise la longueur 3D maximale des supports par la taille souhaitée
|
3814
|
+
# et on arrondi à l'entier supérieur
|
3815
|
+
nbi_support = int(np.ceil(max(cur_support_left.length3D, cur_support_right.length3D) / ds))
|
3816
|
+
|
3817
|
+
# Nouvelle distance de calcul
|
3818
|
+
locds = 1./float(nbi_support)
|
3819
|
+
|
3820
|
+
# Wolfvertex des points d'intersection des sections avec les supports
|
3821
|
+
|
3822
|
+
# section amont/support gauche
|
3823
|
+
pt1_left = curvec1.interpolate(s= 0., is3D = True, adim = True)
|
3824
|
+
# section amont/support droit
|
3825
|
+
pt1_right = curvec1.interpolate(s= 1., is3D = True, adim = True)
|
3826
|
+
|
3827
|
+
# section aval/support gauche
|
3828
|
+
pt2_left = curvec2.interpolate(s= 0., is3D = True, adim = True)
|
3829
|
+
# section aval/support droit
|
3830
|
+
pt2_right = curvec2.interpolate(s= 1., is3D = True, adim = True)
|
3831
|
+
|
3832
|
+
# Vecteurs temporaires pour les sections.
|
3833
|
+
# Ces nouveaux vecteurs sont des segments de droite entre les intersections
|
3834
|
+
# des sections avec les supports.
|
3835
|
+
# Cela définit ainsi la trace des sections, même si en réalité tous leurs
|
3836
|
+
# points ne sont pas alignés.
|
3837
|
+
|
3838
|
+
# Rappel : Cette procédure a au départ été écrite pour de vraies sections en travers.
|
3839
|
+
# Elle a été adaptée pour fonctionner avec des sections 3D de forme quelconque.
|
3840
|
+
|
3841
|
+
# Passage via vector pour conversion vers LineString
|
3842
|
+
# NDLR : on aurait sans doute pu faire plus simple ;-)
|
3843
|
+
s1_trace = vector(name= 'temp1')
|
3844
|
+
s2_trace = vector(name= 'temp2')
|
3845
|
+
|
3846
|
+
s1_trace.add_vertex([pt1_left, pt1_right])
|
3847
|
+
s2_trace.add_vertex([pt2_left, pt2_right])
|
3848
|
+
|
3849
|
+
# Convert to LineString for Shapely operations
|
3850
|
+
s1_trace = s1_trace.linestring
|
3851
|
+
s2_trace = s2_trace.linestring
|
3852
|
+
|
3853
|
+
sloc = 0. # position adimensionnelle le long du support
|
3854
|
+
# On a calculé la distance utile de discrétisation "locds".
|
3855
|
+
# On va donc évoluer le long des supports en incrémentant "sloc" progressivement.
|
3856
|
+
|
3857
|
+
# Compute alpha coefficients for left and right endpoints of the support vectors
|
3858
|
+
def safe_div(a, b):
|
3859
|
+
return a / b if b != 0. else 0.
|
3860
|
+
|
3861
|
+
for s_along_sup in range(nbi_support + 1):
|
3862
|
+
|
3863
|
+
logging.debug(str(s_along_sup))
|
3864
|
+
|
3865
|
+
# Interpolation 3D sur les 2 supports
|
3866
|
+
#
|
3867
|
+
# Si le vecteur support est 3D, on souhaite utiliser les positions
|
3868
|
+
# relatives des points des sections par rapport aux points du support
|
3869
|
+
# pour calculer les positions des points interpolés.
|
3870
|
+
|
3871
|
+
l1 = cur_support_left.interpolate(s= sloc, is3D= True, adim= True)
|
3872
|
+
l2 = cur_support_right.interpolate(s= sloc, is3D= True, adim= True)
|
3873
|
+
|
3874
|
+
# Trace de la future section interpolée.
|
3875
|
+
# Tout comme "s1_trace" et "s2_trace"", c'est un segment de droite
|
3876
|
+
# qui servira de position de départ à laquelle des incréments seront ajoutés.
|
3877
|
+
# Cela définira les points de la section interpolée.
|
3878
|
+
#
|
3879
|
+
# On garde ici l'objet de type "vector"
|
3880
|
+
cur_trace=vector(name='loc')
|
3881
|
+
cur_trace.add_vertex([l1, l2])
|
3882
|
+
all_points_interp = []
|
3883
|
+
|
3884
|
+
if l1.z != 0. and l1.z != -99999.:
|
3885
|
+
# Le support est 3D
|
3886
|
+
alpha1_left = safe_div(l1.z, pt1_left.z)
|
3887
|
+
alpha2_left = safe_div(l1.z, pt2_left.z)
|
3173
3888
|
else:
|
3174
|
-
|
3175
|
-
|
3176
|
-
if l2.z!=0.:
|
3177
|
-
if pt1r.z==0.:
|
3178
|
-
alpha1r = 0.
|
3179
|
-
else:
|
3180
|
-
alpha1r = l2.z/pt1r.z
|
3889
|
+
alpha1_left = 0.
|
3890
|
+
alpha2_left = 0.
|
3181
3891
|
|
3182
|
-
|
3183
|
-
|
3184
|
-
|
3185
|
-
|
3892
|
+
if l2.z != 0. and l2.z != -99999.:
|
3893
|
+
# Le support est 3D
|
3894
|
+
alpha1_right = safe_div(l2.z, pt1_right.z)
|
3895
|
+
alpha2_right = safe_div(l2.z, pt2_right.z)
|
3186
3896
|
else:
|
3187
|
-
|
3188
|
-
|
3897
|
+
alpha1_right = 0.
|
3898
|
+
alpha2_right = 0.
|
3189
3899
|
|
3900
|
+
# Bouclage sur tous les points d'interpolation
|
3901
|
+
# sur les sections
|
3190
3902
|
for curdist in dist3d:
|
3191
3903
|
|
3192
|
-
#
|
3193
|
-
cur1 = curvec1.interpolate(curdist,True,True)
|
3194
|
-
cur2 = curvec2.interpolate(curdist,True,True)
|
3195
|
-
|
3196
|
-
|
3197
|
-
|
3198
|
-
|
3199
|
-
|
3200
|
-
|
3201
|
-
|
3202
|
-
|
3203
|
-
|
3204
|
-
|
3205
|
-
|
3206
|
-
|
3207
|
-
|
3208
|
-
|
3209
|
-
|
3210
|
-
|
3211
|
-
|
3212
|
-
|
3213
|
-
|
3214
|
-
#
|
3215
|
-
#
|
3216
|
-
|
3217
|
-
|
3218
|
-
|
3219
|
-
|
3220
|
-
|
3221
|
-
#
|
3904
|
+
# Interpolation 3D dans les 2 sections
|
3905
|
+
cur1 = curvec1.interpolate(s= curdist, is3D = True, adim = True)
|
3906
|
+
cur2 = curvec2.interpolate(s= curdist, is3D = True, adim = True)
|
3907
|
+
|
3908
|
+
# Pondération linéaire des alphas
|
3909
|
+
alpha1 = alpha1_left * (1.-curdist) + alpha1_right*curdist
|
3910
|
+
alpha2 = alpha2_left * (1.-curdist) + alpha2_right*curdist
|
3911
|
+
|
3912
|
+
# Projection 2D des points sur les traces des sections
|
3913
|
+
# En fonction de la forme réelle de la section, ces projections
|
3914
|
+
# pourraient ne pas être situées selon une abscisse curviligne
|
3915
|
+
# strictement croissante le long de la section.
|
3916
|
+
s_proj1 = s1_trace.project(Point(cur1.x, cur1.y))
|
3917
|
+
s_proj2 = s2_trace.project(Point(cur2.x, cur2.y))
|
3918
|
+
|
3919
|
+
# Interpolation des points sur les traces des sections
|
3920
|
+
proj_1 = s1_trace.interpolate(s_proj1)
|
3921
|
+
proj_2 = s2_trace.interpolate(s_proj2)
|
3922
|
+
|
3923
|
+
s_proj1 /= s1_trace.length # adimensional position along the section 1
|
3924
|
+
s_proj2 /= s2_trace.length # adimensional position along the section 2
|
3925
|
+
|
3926
|
+
# Relative position of the points on the support.
|
3927
|
+
# We calculate the difference between the projected points and the original points.
|
3928
|
+
dx1 = cur1.x - proj_1.x
|
3929
|
+
dy1 = cur1.y - proj_1.y
|
3930
|
+
dx2 = cur2.x - proj_2.x
|
3931
|
+
dy2 = cur2.y - proj_2.y
|
3932
|
+
|
3933
|
+
# Linear ponderation of the differences
|
3934
|
+
dx = dx1*(1.-sloc) + dx2*sloc
|
3935
|
+
dy = dy1*(1.-sloc) + dy2*sloc
|
3936
|
+
s = s_proj1*(1.-sloc) + s_proj2*sloc
|
3937
|
+
|
3938
|
+
# !! Interpolation en 2D !! pour être cohérent avec le "s" des traces.
|
3939
|
+
pt = cur_trace.interpolate(s, is3D = False, adim = True)
|
3940
|
+
|
3941
|
+
# Pondération des altitudes
|
3222
3942
|
zloc = cur1.z*alpha1 + sloc * (cur2.z*alpha2-cur1.z*alpha1)
|
3223
3943
|
|
3224
|
-
|
3225
|
-
|
3944
|
+
# Ajout du point
|
3945
|
+
all_points_interp.append(wolfvertex(pt.x+dx, pt.y+dy, zloc))
|
3946
|
+
|
3947
|
+
# ajout de la liste des points interpolés dans la section
|
3948
|
+
interpolant.append(all_points_interp)
|
3949
|
+
|
3950
|
+
# mise à jour de la position le long de la section
|
3951
|
+
sloc += locds
|
3226
3952
|
|
3227
|
-
|
3953
|
+
# Limite la position à 1.0 pour éviter les débordements.
|
3954
|
+
# La sommation des distances ne garantit pas que la position
|
3955
|
+
# le long de la section soit strictement inférieure à 1.0.
|
3956
|
+
sloc = min(sloc,1.)
|
3228
3957
|
|
3229
|
-
sloc+=locds
|
3230
|
-
sloc=min(sloc,1.)
|
3231
3958
|
|
3232
|
-
def get_xyz_for_viewer(self,nbsub=10):
|
3959
|
+
def get_xyz_for_viewer(self, nbsub=10):
|
3960
|
+
""" Get the XYZ coordinates for the viewer.
|
3961
|
+
|
3962
|
+
:param nbsub: Number of subdivisions for each segment, default is 10.
|
3963
|
+
:type nbsub: int
|
3964
|
+
:return: A numpy array of shape [(nb-1)*nbsub, 4] containing the XYZ coordinates.
|
3965
|
+
:rtype: np.ndarray
|
3966
|
+
"""
|
3233
3967
|
|
3234
3968
|
pts=self.interpolants
|
3235
3969
|
|
@@ -3273,7 +4007,12 @@ class Interpolator():
|
|
3273
4007
|
|
3274
4008
|
return xyz
|
3275
4009
|
|
3276
|
-
def add_triangles_to_zone(self,myzone:zone):
|
4010
|
+
def add_triangles_to_zone(self, myzone:zone):
|
4011
|
+
""" Add triangles to the specified zone based on the interpolants.
|
4012
|
+
|
4013
|
+
:param myzone: The zone to which the triangles will be added.
|
4014
|
+
:type myzone: zone
|
4015
|
+
"""
|
3277
4016
|
|
3278
4017
|
nb=0
|
3279
4018
|
npt=1
|
@@ -3297,8 +4036,17 @@ class Interpolator():
|
|
3297
4036
|
npt+=1
|
3298
4037
|
nb+=(len(curpt)-1)*(len(curl)-1)*3+(len(curl)-1)+(len(curpt))
|
3299
4038
|
|
3300
|
-
def get_triangles(self,forgltf=True):
|
4039
|
+
def get_triangles(self, forgltf=True):
|
4040
|
+
""" Get the triangles and points for the interpolants.
|
3301
4041
|
|
4042
|
+
ATTENTION : gltf format coordinates as [x, z, -y] and "forgltf" is True by default.
|
4043
|
+
It is not the case in "get_points" where forgltf is False by default.
|
4044
|
+
|
4045
|
+
:param forgltf: If True, formats the points for glTF export, default is True.
|
4046
|
+
:type forgltf: bool
|
4047
|
+
:return: A tuple containing the number of points, points array, and triangles array.
|
4048
|
+
:rtype: tuple[int, np.ndarray, np.ndarray]
|
4049
|
+
"""
|
3302
4050
|
points=[]
|
3303
4051
|
triangles=[]
|
3304
4052
|
nbpts=0
|
@@ -3334,11 +4082,22 @@ class Interpolator():
|
|
3334
4082
|
|
3335
4083
|
|
3336
4084
|
if len(np.argwhere(np.isnan(points)==True))>0:
|
3337
|
-
|
4085
|
+
logging.error(_('NaN values found in points array.'))
|
4086
|
+
raise ValueError(_('NaN values found in points array.'))
|
3338
4087
|
|
3339
4088
|
return nbpts,points,triangles
|
3340
4089
|
|
3341
|
-
def get_points(self,forgltf=False):
|
4090
|
+
def get_points(self, forgltf=False):
|
4091
|
+
""" Get the points for the interpolants.
|
4092
|
+
|
4093
|
+
ATTENTION : gltf format coordinates as [x, z, -y] and "forgltf" is False by default.
|
4094
|
+
It is not the case in "get_triangles" where forgltf is True by default.
|
4095
|
+
|
4096
|
+
:param forgltf: If True, formats the points for glTF export, default is False.
|
4097
|
+
:type forgltf: bool
|
4098
|
+
:return: A tuple containing the number of points and the points array.
|
4099
|
+
:rtype: tuple[int, np.ndarray]
|
4100
|
+
"""
|
3342
4101
|
|
3343
4102
|
points=[]
|
3344
4103
|
nbpts=0
|
@@ -3362,7 +4121,19 @@ class Interpolator():
|
|
3362
4121
|
return nbpts,points
|
3363
4122
|
|
3364
4123
|
def export_gltf(self, points=None, triangles=None, fn:str | Path = None):
|
3365
|
-
|
4124
|
+
""" Export the interpolated sections as a glTF file.
|
4125
|
+
|
4126
|
+
GLTF can be opened in many 3D viewers, including Blender, Cesium, and others.
|
4127
|
+
|
4128
|
+
:param points: Optional points array, if None, it will be generated from the interpolants.
|
4129
|
+
:type points: np.ndarray | None
|
4130
|
+
:param triangles: Optional triangles array, if None, it will be generated from the interpolants.
|
4131
|
+
:type triangles: np.ndarray | None
|
4132
|
+
:param fn: The filename to save the glTF file, if None, a file dialog will be shown.
|
4133
|
+
:type fn: str | Path | None
|
4134
|
+
:return: None
|
4135
|
+
:rtype: None
|
4136
|
+
"""
|
3366
4137
|
if points is None and triangles is None:
|
3367
4138
|
points,triangles= self.get_triangles()
|
3368
4139
|
|
@@ -3441,56 +4212,58 @@ class Interpolator():
|
|
3441
4212
|
gltf.save(fn)
|
3442
4213
|
|
3443
4214
|
def get_xy_z_for_griddata(self):
|
3444
|
-
|
4215
|
+
"""
|
4216
|
+
Get the XY and Z coordinates for griddata interpolation (Scipy).
|
4217
|
+
"""
|
3445
4218
|
xy = np.asarray([[curvertex.x,curvertex.y] for curpt in self.interpolants for curl in curpt for curvertex in curl])
|
3446
4219
|
z = np.asarray([curvertex.z for curpt in self.interpolants for curl in curpt for curvertex in curl])
|
3447
4220
|
|
3448
|
-
# if len(np.argwhere(np.isnan(z)))>0:
|
3449
|
-
# test=1
|
3450
|
-
# if len(np.argwhere(np.isneginf(z)))>0:
|
3451
|
-
# test=1
|
3452
|
-
# if len(np.argwhere(np.isposinf(z)))>0:
|
3453
|
-
# test=1
|
3454
4221
|
return xy,z
|
3455
4222
|
|
3456
4223
|
class Interpolators():
|
3457
4224
|
"""
|
3458
|
-
Classe de gestion des interpolations sur sections en travers
|
4225
|
+
Classe de gestion des interpolations sur sections en travers.
|
4226
|
+
|
4227
|
+
La préparation de la triangulation est faite sur base de vecteurs supports (instance de type "Zones")
|
4228
|
+
et de sections en travers stockées dans une instance de type "crosssections".
|
3459
4229
|
"""
|
3460
4230
|
|
3461
|
-
def __init__(self, banks:Zones, cs:crosssections, ds=1.) -> None:
|
4231
|
+
def __init__(self, banks:Zones, cs:crosssections, ds:float = 1.) -> None:
|
3462
4232
|
"""
|
3463
|
-
Constructeur de la classe Interpolators
|
4233
|
+
Constructeur de la classe Interpolators.
|
3464
4234
|
|
3465
|
-
:param banks: Zones contenant les vecteurs supports
|
4235
|
+
:param banks: Zones contenant les vecteurs supports (pay attention to the used property for zone and vectors)
|
3466
4236
|
:param cs: objet 'crosssections' contenant les sections en travers --> voir PyCrosssections
|
4237
|
+
:param ds: distance souhaitée de discrétisation [m]
|
3467
4238
|
"""
|
3468
4239
|
|
3469
|
-
self.points
|
3470
|
-
self.triangles = None
|
4240
|
+
self.points = None # Points for triangulation
|
4241
|
+
self.triangles = None # Triangles for triangulation
|
3471
4242
|
|
3472
|
-
|
4243
|
+
# Convert the supports/banks to a list of vectors
|
4244
|
+
# All vectors in the zones will be used (independent of the zone name)
|
4245
|
+
# If a zone/vector is not used, it will not be included in the list
|
4246
|
+
self.mybanks = [curv for curzone in banks.myzones if curzone.used for curv in curzone.myvectors if curv.used]
|
3473
4247
|
|
3474
|
-
cs.set_zones()
|
3475
|
-
self.mycs = cs.myzones
|
4248
|
+
cs.set_zones() # Force the cross-sections to be set up as a Zones object
|
3476
4249
|
|
3477
|
-
self.myinterp:list[Interpolator]=[]
|
4250
|
+
self.myinterp:list[Interpolator]=[] # List of Interpolator objects
|
3478
4251
|
|
3479
|
-
|
3480
|
-
zonecs =
|
4252
|
+
# self.mycs = cs.myzones # pointer to the zones of the cross-sections
|
4253
|
+
zonecs = cs.myzones.myzones[0]
|
3481
4254
|
|
3482
4255
|
if zonecs.myvectors[0].up is not None:
|
3483
4256
|
# Les sections ont été triées sur base d'un vecteur support
|
3484
4257
|
# On les traite dans l'ordre du tri
|
3485
4258
|
cs1:profile
|
3486
|
-
cs1=cs.get_upstream()['cs']
|
4259
|
+
cs1 = cs.get_upstream()['cs']
|
3487
4260
|
|
3488
4261
|
while cs1.down is not cs1:
|
3489
4262
|
cs2=cs1.down
|
3490
4263
|
|
3491
4264
|
logging.info('{} - {}'.format(cs1.myname,cs2.myname))
|
3492
4265
|
|
3493
|
-
myinterp=Interpolator(cs1,cs2,self.mybanks,ds)
|
4266
|
+
myinterp = Interpolator(cs1, cs2, self.mybanks, ds)
|
3494
4267
|
|
3495
4268
|
if len(myinterp.interpolants)>0:
|
3496
4269
|
self.myinterp.append(myinterp)
|
@@ -3499,13 +4272,18 @@ class Interpolators():
|
|
3499
4272
|
|
3500
4273
|
cs1=cs2
|
3501
4274
|
else:
|
3502
|
-
# Les sections n'ont pas été triées
|
4275
|
+
# Les sections n'ont pas été triées
|
4276
|
+
# --> on les traite dans l'ordre d'énumération
|
3503
4277
|
for i in range(zonecs.nbvectors-1):
|
3504
|
-
cs1
|
3505
|
-
cs2
|
4278
|
+
cs1:vector
|
4279
|
+
cs2:vector
|
4280
|
+
cs1 = zonecs.myvectors[i] # Cross-section 1
|
4281
|
+
cs2 = zonecs.myvectors[i+1] # Cross-section 2
|
3506
4282
|
|
3507
4283
|
logging.info('{} - {}'.format(cs1.myname,cs2.myname))
|
3508
|
-
|
4284
|
+
|
4285
|
+
myinterp = Interpolator(cs1, cs2, self.mybanks, ds)
|
4286
|
+
|
3509
4287
|
if len(myinterp.interpolants)>0:
|
3510
4288
|
self.myinterp.append(myinterp)
|
3511
4289
|
else:
|
@@ -3552,6 +4330,7 @@ class Interpolators():
|
|
3552
4330
|
|
3553
4331
|
|
3554
4332
|
def viewer_interpolator(self):
|
4333
|
+
""" Display the interpolated sections in a viewer. """
|
3555
4334
|
|
3556
4335
|
xyz=[]
|
3557
4336
|
for curinterp in self.myinterp:
|
@@ -3561,9 +4340,34 @@ class Interpolators():
|
|
3561
4340
|
|
3562
4341
|
myviewer(xyz,0)
|
3563
4342
|
|
4343
|
+
@property
|
4344
|
+
def points_xyz(self):
|
4345
|
+
""" Get the XYZ coordinates of the interpolated points.
|
4346
|
+
|
4347
|
+
Points are stored as [x,z,-y] for glTF compatibility.
|
4348
|
+
Convert them to [x,y,z] for other uses.
|
4349
|
+
"""
|
4350
|
+
pts = self.points.reshape([-1,3])[:,[0,2,1]].copy() # Convert from [x,z,-y] to [x,y,z]
|
4351
|
+
pts[:,1] = -pts[:,1] # Convert from -y to y
|
4352
|
+
return pts
|
4353
|
+
|
3564
4354
|
def interp_on_array(self, myarray,
|
3565
4355
|
method:Literal["nearest", "linear", "cubic"],
|
3566
|
-
use_cloud:bool
|
4356
|
+
use_cloud:bool=True,
|
4357
|
+
mask_outside_polygons:bool=False):
|
4358
|
+
""" Interpolate the sections on a WolfArray.
|
4359
|
+
|
4360
|
+
:param myarray: The WolfArray to interpolate on.
|
4361
|
+
:type myarray: WolfArray
|
4362
|
+
:param method: The interpolation method to use, default is "linear".
|
4363
|
+
:type method: str
|
4364
|
+
:param use_cloud: If True, uses cloud interpolation, otherwise triangulation, default is True.
|
4365
|
+
:type use_cloud: bool
|
4366
|
+
"""
|
4367
|
+
from .wolf_array import WolfArray
|
4368
|
+
from .PyVertexvectors import Triangulation
|
4369
|
+
|
4370
|
+
myarray:WolfArray
|
3567
4371
|
|
3568
4372
|
if use_cloud:
|
3569
4373
|
xy=[]
|
@@ -3576,7 +4380,40 @@ class Interpolators():
|
|
3576
4380
|
xy = np.concatenate(xy)
|
3577
4381
|
z = np.concatenate(z)
|
3578
4382
|
myarray.interpolate_on_cloud(xy,z,method)
|
4383
|
+
|
4384
|
+
if mask_outside_polygons:
|
4385
|
+
|
4386
|
+
# Mask points outside the polygons defined by the triangulation
|
4387
|
+
# Do not solve the problem of points alongside the polygons or on the vertices
|
4388
|
+
# observed in some cases if we use triangulation.
|
4389
|
+
#
|
4390
|
+
# Should be improved by searching the contour of the polygons
|
4391
|
+
# like the concatenation of external supports (left + last_section + right_inverted + first_section_inverted)
|
4392
|
+
|
4393
|
+
tmp_tri = Triangulation(pts = self.points_xyz, tri=self.triangles)
|
4394
|
+
ij = myarray.get_ij_inside_listofpolygons(tmp_tri._get_polygons())
|
4395
|
+
myarray.array.mask[:,:] = True # Mask all points
|
4396
|
+
myarray.array.mask[ij[:,0], ij[:,1]] = False # Unmask
|
4397
|
+
myarray.set_nullvalue_in_mask()
|
3579
4398
|
else:
|
3580
4399
|
for interp in self.myinterp:
|
3581
4400
|
n, pts, tri = interp.get_triangles(forgltf=False)
|
3582
|
-
myarray.interpolate_on_triangulation(pts, tri, interp_method= 'scipy')
|
4401
|
+
myarray.interpolate_on_triangulation(pts, tri, interp_method= 'scipy')
|
4402
|
+
|
4403
|
+
def saveas(self, fn: str | Path):
|
4404
|
+
""" Save the interpolators to files.
|
4405
|
+
|
4406
|
+
Each interpolator will be saved as a separate file with a .tri extension.
|
4407
|
+
|
4408
|
+
:param fn: The filename to save the interpolators.
|
4409
|
+
:type fn: str | Path
|
4410
|
+
"""
|
4411
|
+
from .PyVertexvectors import Triangulation
|
4412
|
+
|
4413
|
+
if isinstance(fn, str):
|
4414
|
+
fn = Path(fn)
|
4415
|
+
|
4416
|
+
for i, interp in enumerate(self.myinterp):
|
4417
|
+
n, pts, tri = interp.get_triangles(forgltf=False)
|
4418
|
+
tmp_tri = Triangulation(pts = pts, tri = tri)
|
4419
|
+
tmp_tri.saveas(fn.parent / (fn.stem + f'_interp{i+1}.tri'))
|