diff --git a/src/Model/Geometry/Geometry.py b/src/Model/Geometry/Geometry.py new file mode 100644 index 0000000000000000000000000000000000000000..ce977efa1dfad3f89db070466b241e657ae98b58 --- /dev/null +++ b/src/Model/Geometry/Geometry.py @@ -0,0 +1,228 @@ +# -*- coding: utf-8 -*- + +import numpy as np + +from time import time +from typing import List +from copy import deepcopy +from operator import itemgetter + +from Model.Geometry.PointXYZ import PointXYZ +from Model.Geometry.ProfileXYZ import ProfileXYZ + +# TypeProfileXYZ = List[ProfileXYZ] + + +class Geometry: + """ + Reach geometry + """ + def __init__(self, parent=None): + self.parent = parent + self._list_profiles: List[ProfileXYZ] = [] + self.file_st = "" + self.__list_copied_profiles = [] + + def __repr__(self): + return f"\n===== Début liste des profils ======> {np.array(self._list_profiles)}" \ + f"\n<====== Fin liste des profils =====" + + @property + def number_profiles(self): + """ + Returns: + Number of profiles + """ + return len(self._list_profiles) + + def get_geometry(self) -> List[ProfileXYZ]: + """ + Returns: + The profiles list. + """ + return self._list_profiles + + def get_profile_i(self, i: int) -> ProfileXYZ: + """ + Args: + i: Index + Returns: + The profile at index i. + """ + try: + return self._list_profiles[i] + except IndexError: + raise IndexError(f"Le bief a moins de {i} profil(s)") + + def import_geometry(self, file_path_name: str): + """Import a geometry from file (.ST or .st) + + Args: + file_path_name: The absolute path of geometry file (.ST or .st) to import. + + Returns: + Nothing. + """ + self.file_st = str(file_path_name) + list_profile, list_header = self.read_file_st() + # print(list_profile, "\n", list_header) + if list_profile and list_header: + for ind, profile in enumerate(list_profile): + prof = ProfileXYZ(*list_header[ind]) + prof.import_points(profile) + self._list_profiles.append(prof) + self._update_profile_numbers() + + else: + print("Fichier introuvable ou non conforme !") + + def add(self): + """Add a new profile at the end of profiles list + + Returns: + Nothing. + """ + nb_profile = self.number_profiles + profile = ProfileXYZ() + profile.num = nb_profile + 1 + self._list_profiles.append(profile) + + def _update_profile_numbers(self): + """Update profiles index + + Returns: + Nothing. + """ + for ind, profile in enumerate(self.get_geometry()): + profile.num = ind + 1 + + def insert(self, index: int): + """Insert new profile in list + + Args: + index: The position of the new profile. + + Returns: + Nothing. + """ + profile = ProfileXYZ() + self._list_profiles.insert(index, profile) + self._update_profile_numbers() + + def delete(self, list_index: list): + """Delete some elements in profile list + + Args: + list_index: The list of element index + + Returns: + Nothing. + """ + try: + if list_index: + indices = sorted(list(set(list_index)), reverse=True) + for idx in indices: + # if idx < len(self._list_profiles) : + try: + self._list_profiles.pop(idx) + self._update_profile_numbers() + except IndexError: + print("Liste vide, rien à supprimer !") + except TypeError: + if isinstance(list_index, int): + self._list_profiles.pop(list_index) + self._update_profile_numbers() + print(f"\nSuppression --> attention !!!!\nL'argument {list_index} doit être une liste!\n") + else: + raise TypeError(f"L'argument {list_index} fourni est de type incorrect") + + def _sort(self, is_reversed: bool = False): + self._list_profiles = sorted( + self._list_profiles, + key=lambda profile: profile.pk, + reverse=is_reversed + ) + + def sort_ascending(self): + """Sort profiles by increasing KP + + Returns: + Nothing. + """ + self._sort(is_reversed=False) + + def sort_descending(self): + """Sort profiles by decreasing KP + + Returns: + Nothing. + """ + self._sort(is_reversed=True) + + def copy(self, index_list: List[int]): + self.__list_copied_profiles.clear() + index_list = list(set(index_list)) # delete duplicate index + for index in index_list: + try: + self.__list_copied_profiles.append(deepcopy(self.get_profile_i(index))) + except IndexError: + raise IndexError(f"Echec de la copie, l'indice {index} n'existe pas !") + + def paste(self): + if self.__list_copied_profiles: + for profile in self.__list_copied_profiles: + self._list_profiles.append(profile) + print("self.__list_copied_profiles", self.__list_copied_profiles, "\n *****") + + def read_file_st(self): + """Read the ST file + + Returns: + List of profiles and list of headers. + """ + t0 = time() + line_is_header = True + list_point_profile = [] + list_profile = [] + list_header = [] + stop_code = "999.999" + try: + with open(self.file_st, encoding="utf-8") as file_st: + for line in file_st: + if not (line.startswith("#") or line.startswith("*")): + line = line.split() + if line_is_header: + if len(line) >= 6: + list_header.append(line[:6]) + elif len(line) == 5: + line.append("") + list_header.append(line) + else: + print(f"Point {line} invalide ==> pas pris en compte") + line_is_header = False + else: + # Read until "999.9990 999.9990" as found + if len(line) == 3: + x, y, z = line + if stop_code in x and stop_code in y: + line_is_header = True + list_profile.append(list_point_profile) + list_point_profile = [] + else: + line.append("") + list_point_profile.append(line) + elif len(line) == 4: + x, y, z, ld = line + if stop_code in x and stop_code in y: + list_profile.append(list_point_profile) + list_point_profile = [] + line_is_header = True + else: + list_point_profile.append(line) + else: + pass + + except FileNotFoundError: + print(f"\n \n %%%%%% Fichier : {self.file_st} introuvable !! %%%%%%") + print("****** Fichier {} lu et traité en {} secondes *******".format(self.file_st, time() - t0)) + return list_profile, list_header diff --git a/src/Model/Geometry/PointXYZ.py b/src/Model/Geometry/PointXYZ.py new file mode 100644 index 0000000000000000000000000000000000000000..14c29bebc55c968d3bd3040e1798b5e402420994 --- /dev/null +++ b/src/Model/Geometry/PointXYZ.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- + +from math import dist +from pandas import isna as pd_is_na + +class PointXYZ: + def __init__(self, x: float, y: float, z: float, point_name: str = ""): + self.x = float(x) + self.y = float(y) + self.z = float(z) + self.name = point_name + self.points = [self.x, self.y, self.z, self.name] + + def __repr__(self): + point_xyz_name = f"({self.x}, {self.y},{self.z}, {self.name})" + return point_xyz_name + + @property + def x(self): + return self._x + + @x.setter + def x(self, value): + self._x = float(value) + + @property + def y(self): + return self._y + + @y.setter + def y(self, value): + self._y = float(value) + + # self.points[1] = self._y + + @property + def z(self): + return self._z + + @z.setter + def z(self, value): + self._z = float(value) + + @property + def name(self): + return self._name + + @name.setter + def name(self, point_name): + self._name = point_name + + def point_is_named(self): + """ + Returns: + True if the point is named. + """ + return len(self.name.strip()) != 0 + + @property + def is_nan(self): + """ + Returns: + True if at least one coordinate is as np.nan + """ + return pd_is_na(self.x) or pd_is_na(self.y) or pd_is_na(self.z) + + @staticmethod + def distance(p1, p2): + """Euclidean distance between p1 and p2. + + Args: + p1: A XYZ Point + p2: A XYZ Point + + Returns: + Euclidean distance between the two points + """ + return dist((p1.x, p1.y, p1.z), (p2.x, p2.y, p2.z)) diff --git a/src/Model/Geometry/ProfileXYZ.py b/src/Model/Geometry/ProfileXYZ.py new file mode 100644 index 0000000000000000000000000000000000000000..f3fc6883b61e8d3f85f392f8fed2901e77f8f1f6 --- /dev/null +++ b/src/Model/Geometry/ProfileXYZ.py @@ -0,0 +1,199 @@ +# -*- coding: utf-8 -*- + +import numpy as np +import pandas as pd +from typing import List + +from Model.Geometry.PointXYZ import PointXYZ + +class ProfileXYZ: + def __init__(self, num: int = 0, code1: int = 0, code2: int = 0, + nb_points: int = 0, kp: float = 0., name: str = ""): + """ProfileXYZ constructor + + Args: + num: The number of this profile + code1: The interpolation code 1 + code2: The interpolation code 2 + nb_points: Number of points + kp: Kilometer point + name: The name of profile + + Returns: + Nothing. + """ + self._num = int(num) + self._code1 = int(code1) + self._code2 = int(code2) + self._nb_points = int(nb_points) + self._kp = float(kp) + self._name = str(name) + self._list_points: List[PointXYZ] = [] + + def __repr__(self): + df = pd.DataFrame(columns=["X", "Y", "Z", "Name"], + data=[[p.x, p.y, p.z, p.name] for p in self._list_points]) + return f"\n{self.header}\n{df}" + + @property + def num(self): + """ + Returns: + Number of profile. + """ + return self._num + + @num.setter + def num(self, value: int): + self._num = int(value) + + @property + def code1(self): + """ + Returns: + Interpolation code 1. + """ + return self._code1 + + @code1.setter + def code1(self, value: int): + self._code1 = int(value) + + @property + def code2(self): + """ + Returns: + Interpolation code 2. + """ + return self._code2 + + @code2.setter + def code2(self, value: int): + self._code2 = int(value) + + @property + def nb_points(self): + return self._nb_points + + # @nb_points.setter + # def nb_points(self, nb: int): + # self._nb_points = int(nb) + + @property + def kp(self): + """ + Returns: + Kilometer point. + """ + return self._kp + + @kp.setter + def kp(self, value: float): + self._kp = float(value) + + @property + def name(self): + """ + Returns: + Profile name. + """ + return self._name + + @name.setter + def name(self, other: str): + self._name = other + + @property + def header(self): + """ + Returns: + Profile header. + """ + return np.array([self._num, self._code1, self._code2, self._nb_points, self.kp, self._name]) + + def import_points(self, list_points: list): + """Import a list of points to profile + + Args: + list_points: Liste of PointXYZ + + Returns: + Nothing. + """ + for point in list_points: + pt = PointXYZ(*point) + self._list_points.append(pt) + + def get_point_i(self, index: int) -> PointXYZ: + """Get point at index. + + Args: + index: Index of point. + + Returns: + The point. + """ + try: + return self._list_points[index] + except IndexError: + raise IndexError(f"Le profil a moins de {index} points !") + + def add(self): + """Add a new PointXYZ to profile. + + Returns: + Nothing. + """ + point_xyz = PointXYZ(0., 0., 0.) + self._list_points.append(point_xyz) + + def delete(self, index: int): + """Delete the point at index + + Args: + index: Index of point. + + Returns: + Nothing. + """ + try: + self._list_points.pop(index) + except IndexError: + raise IndexError(f"Suppression échouée, l'indice {index} n'existe pas !") + + def insert(self, index: int): + """Insert a new profile at index. + + Args: + index: The index of new profile. + + Returns: + Nothing. + """ + profile = ProfileXYZ() + self._list_points.insert(index, profile) + + def delete1(self, list_index: list): + """Delete a list of points + + Args: + list_index: Indexes list. + + Returns: + Nothing. + """ + try: + if list_index: + indices = sorted(list(set(list_index)), reverse=True) + for idx in indices: + # if idx < len(self._list_profiles) : + try: + self._list_points.pop(idx) + except IndexError: + print("Liste vide, rien à supprimer !") + except TypeError: + if isinstance(list_index, int): + self._list_points.pop(list_index) + print(f"\nSuppression --> attention !!!!\nL'argument {list_index} doit être une liste!\n") + else: + raise TypeError(f"L'argument {list_index} fourni est de type incorrect") diff --git a/src/Model/Geometry/Reach.py b/src/Model/Geometry/Reach.py new file mode 100644 index 0000000000000000000000000000000000000000..7fe06ce5318bced623331de884aa66318c90e891 --- /dev/null +++ b/src/Model/Geometry/Reach.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +from Model.Geometry.Geometry import Geometry + +class Reach: + def __init__(self, name: str = "", + upstream_node: str = None, + downstream_node: str = None, + parent=None): + self._name = name + self._name_upstream_node = name_upstream_node + self._name_downstream_node = name_downstream_node + self.parent = parent + self._geometry = Geometry(parent=self) + + def __repr__(self): + return f"Bief : {self._name}\n Nb de sections : {self._geometry.number_profiles}" + + @property + def name(self): + return self._name + + @property + def name_upstream_node(self): + return self._name_upstream_node + + @property + def name_downstream_node(self): + return self._name_downstream_node + + @property + def geometry(self): + return self._geometry