-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProfil.py
More file actions
342 lines (306 loc) · 16.7 KB
/
Profil.py
File metadata and controls
342 lines (306 loc) · 16.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# coding: utf-8
# Imports généraux
from sortedcontainers import SortedKeyList
import pandas as pd
# Imports spécifiques
from utils import abs_path, dic_remove
from Geometrie import *
"""
Test pour les projections dans COMPAT
PBa 2024
"""
class Profil(Tracable):
""" Profil de vitesse, donné dans un système cartésien orthonormé.
Classe générique, pour laquelle il convient de spécialiser le chargement des données du profil.
"""
class EnsembleList(SortedKeyList):
""" Sous-classe utilitaire: conteneur des ensembles, liste ordonnée par ses clés """
def __init__(self):
""" Constructeur, qui spécifie le critère de tri: par tuples xy croissants """
super().__init__(key = lambda ens: ens.xy)
def get(self, xy):
""" Renvoyer l'élément correspondant au tuple xy;
si pas de correspondance, on en ajoute un et on le renvoie.
:param xy: tuple (x, y) position 2d de l'Ensemble
:return: Ensemble renvoyé
"""
lst_ens = [ens for ens in self if ens.xy == xy] # Ne devrait renvoyer qu'un élément ou aucun
if len(lst_ens) == 0:
ens = Ensemble(xy)
self.add(ens)
return ens
elif len(lst_ens) == 1:
return lst_ens[0]
else:
raise ValueError(f"Erreur: EnsembleList.get, incohérence. Plusieurs 'Ensemble' à la même position: {self}")
def __init__(self, **kwargs):
""" Constructeur générique; à appeler en début de constructeur des classes filles.
Chaque classe fille charge les données (de format spécifique) du profil selon sa spécialisation.
:param kwargs: éventuels arguments communs
"""
# TODO Réflexion: j'utilise une description du profil par des ensembles contenant des cellules, mais
# TODO il serait plus simple d'avoir directement des éléments regroupant un Point3d et un vecteur vitesse...
# TODO Voir le besoin réel des ensembles.
self.lst_ens = Profil.EnsembleList() # Liste des ensembles, pour les données du profil de vitesse
self.nom = None
self.point_axe = None # Point 3d, à la cote max des ensembles
self.vect_n = None # Vecteur 3d unitaire, normal au profil
# Récupérer les éventuelles propriétés
self.set_properties(**kwargs)
def set_properties(self, **kwargs):
""" Récupérer les propriétés communes, passées dans le constructeur; à appeler en fin de constructeur des classes filles.
:param kwargs: arguments communs aux classes filles
"""
if 'nom' in kwargs:
self.nom = kwargs['nom']
if 'axe' in kwargs:
axe = kwargs['axe'] # Segment ou TODO polyligne
point_axe = self._croiser(axe) # Point 2d
z = max([ens.max_z() for ens in self.lst_ens]) if len(self.lst_ens) != 0 else 0
self.point_axe = point_axe.make3d(z) # Point 3d
vect_n = Vect.from_segment(axe)
vect_n = vect_n.make3d(0)
self.vect_n = Vect.normaliser(vect_n) # Vect 3d unitaire
if 'point_axe' in kwargs:
point_axe = kwargs['point_axe'] # Point récupéré, 2d ou 3d
if len(point_axe) != 3:
z = max([ens.max_z() for ens in self.lst_ens]) if len(self.lst_ens) != 0 else 0
point_axe = point_axe.make3d(z) # Point 3d
self.point_axe = point_axe # Point 3d
if 'vect_n' in kwargs:
vect_n = kwargs['vect_n']
if len(vect_n) == 2:
vect_n = vect_n.make3d(0)
self.vect_n = Vect.normaliser(vect_n) # Vect 3d unitaire
if 'lst_ens' in kwargs:
self.lst_ens = kwargs['lst_ens']
z = max([ens.max_z() for ens in self.lst_ens]) if len(self.lst_ens) != 0 else 0
point_axe = self.point_axe.make2d() if len(self.point_axe) == 3 else self.point_axe
self.point_axe = point_axe.make3d(z) # Point 3d
#@staticmethod
def convertir(self) -> 'Profil':
""" Convertir le profil vers un nouveau système cartésien orthonormé.
TODO utiliser pyproj ?
TODO gérer aussi les repères locaux comme le système de coordonnées du labo: utiliser ChangeurRepere ?
"""
pass
def aplatir(self, vect_proj: Vect, nom: str = None) -> 'Profil':
""" Projeter le profil sur un plan plat, perpendiculaire au vecteur de projection, passant par le point 2d axe;
cela modifie la position des ensembles, mais pas les vecteurs vitesse.
:param vect_proj: vecteur de projection 2d, orthogonal à la surface de projection
:param nom: nom optionnel du profil aplati
:return: nouveau profil
"""
if not isinstance(self.vect_n, Vect) or not isinstance(self.point_axe, Point):
raise ValueError(f"Erreur: Profil.aplatir, le profil {self} doit avoir un vecteur normal et un point d'axe")
z = self.point_axe.get_coord()[2]
proj = ProjecteurPlan(vect_proj=vect_proj, point_surface=self.point_axe, make3d=True, z=z) # Pour être plus générique, projection 3d
profil_new = Profil(point_axe=self.point_axe, vect_n=vect_proj, nom=nom)
for ens in self.lst_ens:
profil_new.lst_ens.add(proj.projeter_ensemble(ens))
return profil_new
def deplacer(self, r_cib: Repere, nom: str = None) -> 'Profil':
""" Déplacer un profil depuis son repère 'ori' vers le repère 'cib', ses coordonnées restant exprimées dans le repère 'ref'
:param r_cib: repère cible
:param nom: nom optionnel du profil déplacé
:return: nouveau profil
"""
if not isinstance(self.point_axe, Point) or not isinstance(self.vect_n, Vect):
raise ValueError(f"Erreur: Profil.deplacer, le profil {self} doit avoir un vecteur normal et un point d'axe")
z = self.point_axe.get_coord()[2]
r_ori = Repere(ori=self.point_axe, v_x=self.vect_n, make3d=True, z=z) # Repère 3d, construit pour être orthonormal
chgr = ChangeurRepere(r_ori=r_ori, r_cib=r_cib, make3d=True, z=z) # Le déplacement d'un profil est forcément 3d
lst_ens_cib = Profil.EnsembleList() # Liste des ensembles
for ens in self.lst_ens:
ens_cib = chgr.deplacer_ens(ens)
lst_ens_cib.add(ens_cib)
p_cib = chgr.deplacer_point(self.point_axe)
v_cib = chgr.deplacer_vect(self.vect_n)
profil_new = Profil(lst_ens=lst_ens_cib, point_axe=p_cib, vect_n=v_cib, nom=nom)
return profil_new
def add_cellule(self, x: float, y: float, z: float, u: float, v: float, w: float):
""" Ajouter une cellule de vitesse au profil, dans le système de coordonnées du profil.
:param x: position x
:param y: position y
:param z: position z
:param u: composante de la vitesse selon x
:param v: composante de la vitesse selon y
:param w: composante de la vitesse selon z
:return: mise à jour de self.lst_ens
"""
xy = (x, y)
ens = self.lst_ens.get(xy)
ens.add_cellule(z, Vect([u, v, w]))
def _croiser(self, axe: Segm) -> Point:
""" Renvoyer le point de croisement du profil avec l'axe
:param axe: segment 2d, axe hydraulique
:return: point d'intersection 2d entre le profil et le segment; None si pas d'intersection
"""
p_prec = None
for ens in self.lst_ens:
p_cour = ens.get_point()
if p_prec is not None:
seg_prf = Segm(p_prec, p_cour)
p_inter = Segm.croiser2d(seg_prf, axe)
if p_inter is not None:
return p_inter # Intersection trouvée
p_prec = p_cour # Initialiser pour la prochaine itération
return None # Pas d'intersection
def plot(self, fig: go.Figure, tracer: bool = True, mode: str = 'v', **kwargs) -> pbt.BaseTraceType:
""" Ajouter un tracé sur une figure Plotly.
:param fig: figure à compléter
:param tracer: indique s'il faut effectivement ajouter la trace à la figure
:param mode: 'v' pour tracé vecteur, 'c' pour contour
:param kwargs: arguments supplémentaires pour le formatage du graphe
:return: objet à tracer, si besoin de le récupérer par ailleurs
"""
# Préparer les tableaux pour les tracés
tab_ens_x, tab_ens_y, tab_ens_zmin, tab_ens_zmax = [], [], [], [] # Ensembles
tab_x, tab_y, tab_z = [], [], [] # Points
tab_u, tab_v, tab_w, tab_norme = [], [], [], [] # Vitesses
tab_ctr_x, tab_ctr_y, tab_ctr_z = [], [], [] # Contours
for ens in self.lst_ens:
tab_ens_z = [] # Liste locale des z pour chaque ensemble
for cel in ens.lst_cel:
tab_x.append(ens.xy[0]) # Coordonnée x, récupérée dans l'ensemble
tab_y.append(ens.xy[1]) # Coordonnée y, récupérée dans l'ensemble
tab_z.append(cel.z) # Coordonnée z, récupérée dans la cellule
tab_ens_z.append(cel.z)
tab_u.append(cel.v.get_coord()[0]) # Composante u, récupérée dans la cellule
tab_v.append(cel.v.get_coord()[1]) # Composante v, récupérée dans la cellule
tab_w.append(cel.v.get_coord()[2]) # Composante w, récupérée dans la cellule
tab_norme.append((cel.v.get_coord()[0]**2 + cel.v.get_coord()[1]**2 + cel.v.get_coord()[2]**2)**0.5)
tab_ens_x.append(ens.xy[0])
tab_ens_y.append(ens.xy[1])
tab_ens_zmin.append(min(tab_ens_z))
tab_ens_zmax.append(max(tab_ens_z))
for x, y, zmin in zip(tab_ens_x, tab_ens_y, tab_ens_zmin):
# Bas du contour à l'aller
tab_ctr_x.append(x)
tab_ctr_y.append(y)
tab_ctr_z.append(zmin)
for x, y, zmin, zmax in zip(reversed(tab_ens_x), reversed(tab_ens_y), reversed(tab_ens_zmin), reversed(tab_ens_zmax)):
# Haut du contour au retour, avec les montants
tab_ctr_x.append(x)
tab_ctr_y.append(y)
tab_ctr_z.append(zmax)
tab_ctr_x.append(x)
tab_ctr_y.append(y)
tab_ctr_z.append(zmin)
tab_ctr_x.append(x)
tab_ctr_y.append(y)
tab_ctr_z.append(zmax)
# Générer les tracés
if 'v' in mode:
# Champ de vecteurs
kw = dic_remove(kwargs, ['marker_color', 'marker_colorscale', 'line_color', 'line_colorscale', 'marker_size']) # Supprimer les propriétés incompatibles
trace = go.Cone(
x=tab_x, y=tab_y, z=tab_z,
u=tab_u, v=tab_v, w=tab_w,
anchor = 'tail', sizemode='scaled',
name=self.nom, legendgroup=self.nom, **kw)
if tracer:
fig.add_trace(trace)
if 'c' in mode:
# Contour
kw = dic_remove(kwargs, ['colorscale', 'colorbar']) # Supprimer les propriétés incompatibles
trace = go.Scatter3d(
x=tab_ctr_x, y=tab_ctr_y, z=tab_ctr_z, mode='lines',
name=self.nom, legendgroup=self.nom, **kw)
if tracer:
fig.add_trace(trace)
if 's' in mode:
# Surface; pour mémoire car fonctionne mal
kw = dic_remove(kwargs) # Supprimer les propriétés incompatibles
trace = go.Mesh3d(
x=tab_x, y=tab_y, z=tab_z,
alphahull=0, facecolor=tab_norme,
name=self.nom, **kw)
if tracer:
fig.add_trace(trace)
if self.point_axe is not None and tracer:
# Point de l'axe hydraulique
kw = dic_remove(kwargs) # Supprimer les propriétés incompatibles
nom_axe = self.point_axe.nom if self.point_axe.nom is not None else f"Axe {self.nom}"
z_axe = max(tab_ens_zmax) if len(self.point_axe) != 3 else self.point_axe.coord[2]
point_axe = Point([self.point_axe.coord[0], self.point_axe.coord[1], z_axe], nom=nom_axe)
point_axe.plot(fig, showlegend=False, legendgroup=self.nom, **kw)
if self.vect_n is not None:
# Vecteur normal au profil
kw = dic_remove(kwargs) # Supprimer les propriétés incompatibles
nom_vect = self.vect_n.nom if self.vect_n.nom is not None else f"Vnormal {self.nom}"
vect = self.vect_n.make3d(0, nom_vect) if len(self.vect_n) != 3 else self.vect_n
self.vect_n.plot(fig, ori=point_axe, showlegend=False, legendgroup=self.nom, **kw)
return trace
class ProfilAdcpLabo(Profil):
""" Profil de vitesse, spécialisation de la classe Profil.
ADCP Ubertone du labo; données fournies sous la forme d'un fichier csv.
"""
cfg = {'col_x': 'X_ADCP_RN', 'col_y': 'Y_ADCP_RN', 'col_z': 'Z_RN',
'col_u': 'Vu_RN', 'col_v': 'Vv_RN', 'col_w': 'Vw_RN'}
def __init__(self, fic_csv: str, **kwargs):
""" Constructeur générique, à spécialiser dans les classes filles;
le but est de charger les données du profil en fonction de chaque type de source disponible.
Il est possible (et recommandé) d'appeler le constructeur de cette classe mère pour les traitements communs.
:parap fic_csv: nom long, absolu ou relatif, du fichier csv où récupérer les données
:param kwargs: arguments communs aux classes filles
"""
# Appeler le constructeur de la classe mère (sans argument; ils seront traités en fin de ce constructeur)
super().__init__()
# Charger les données spécifiques
try:
df = pd.read_csv(abs_path(fic_csv), sep=';')
dic_idx = {k: df.columns.get_loc(v)+1 for k, v in ProfilAdcpLabo.cfg.items()}
for row in df.itertuples(): #
self.add_cellule(
row[dic_idx['col_x']], row[dic_idx['col_y']], row[dic_idx['col_z']],
row[dic_idx['col_u']], row[dic_idx['col_v']], row[dic_idx['col_w']])
pass
except Exception as e:
print(f"")
raise
# Récupérer les propriétés communes
super().set_properties(**kwargs)
if __name__ == "__main__":
# Section de test, appel seulement si run en tant que script (par opposition à un appel externe de lib)
# Croisement de segments
#p_i = Segm.croiser([[0, 0], [2, 2]])
#print(p_i)
# Définition d'un profil
s_axe = Segm(Point([791800, 225300]), Point([792000, 225400]), 'Axe hydraulique') # Segment 2d
# prf_pt1001 = ProfilAdcpLabo('.\\Exemples\\ADCPlabo_CAL_2018.09.27_3_PT1001.csv', nom='ADCP Labo PT1001', axe=s_axe)
prf_pt1001 = ProfilAdcpLabo('.\\Exemples\\ADCPlabo_Test.csv', nom='ADCPlabo test', axe=s_axe)
v_axe = Vect.from_segment(s_axe)
# Aplatissement d'un profil: le plan de projection est vertical et il passe par le point d'axe du profil
v_orth = Vect([30, 80]) # Vect 2d de projection, orthogonal au plan de projection
prf_pt1001_plat = prf_pt1001.aplatir(v_orth, nom='Profil aplati')
fig0 = go.Figure()
prf_pt1001.plot(fig0, mode='cv', marker_color='red', line_color='red', colorscale='Hot_r', marker_size=3, colorbar=dict(x=-0.1))
prf_pt1001_plat.plot(fig0, mode='cv', marker_color='blue', line_color='blue', colorscale='Ice_r', marker_size=3, colorbar=dict(x=-0.05))
fig0.update_layout(scene_camera_eye=dict(x=0, y=0, z=2), scene_aspectmode='cube')
fig0.show()
# Changement de repère pour un profil
p_cib = Point([791960, 225390])
v_cib = Vect.normaliser(Vect([30, 80]))
r_cib = Repere(p_cib, v_cib)
prf_pt1001_cib = prf_pt1001.deplacer(r_cib, 'Profil déplacé')
fig = go.Figure()
prf_pt1001.plot(fig, mode='cv', marker_color='red', line_color='red', colorscale='Hot_r', marker_size=3, colorbar=dict(x=-0.1))
prf_pt1001_cib.plot(fig, mode='cv', marker_color='blue', line_color='blue', colorscale='Ice_r', marker_size=3, colorbar=dict(x=-0.05))
fig.update_layout(scene_camera_eye=dict(x=0, y=0, z=2), scene_aspectmode='cube')
fig.show()
#e0 = Ensemble((1234, 4321))
#print(f"e0 = {e0}")
#e0.add_cellule(5.2, v0)
#e0.add_cellule(5.1, v1)
#e0.add_cellule(5.1, v1)
#e0.add_cellule(5.3, v2)
#print(f"v0={v0}; c0={c0}")
#print(f"(c0 < c1) = {c0 < c1}")
#print(f"e0 = {e0}, len(e0) = {len(e0)}")
#e1 = Ensemble((1235, 4321))
#prf = Profil()
#prf.add_cellule(1234, 4321, 5.1, 1, 1, 1)
#prf.add_cellule(1235, 4321, 5, 1, 1, 1)
#prf.add_cellule(1234, 4321, 5, 1, 1, 1)
#prf.add_cellule(1235, 4321, 4.9, 1, 1, 1)