Profile.py 8.35 KiB
# Profile.py -- Pamhyr
# Copyright (C) 2023  INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

# -*- coding: utf-8 -*-

import logging

from tools import timer

from Model.Geometry.Point import Point
from Model.Except import NotImplementedMethodeError

logger = logging.getLogger()


class Profile(object):
    _id_cnt = 0

    def __init__(self, id: int = -1, num: int = 0,
                 kp: float = 0.0, name: str = "",
                 code1: int = 0, code2: int = 0,
                 _type: str = "", reach=None,
                 status=None):
        super(Profile, self).__init__()

        self._status = status

        if id == -1:
            self.id = Profile._id_cnt
        else:
            self.id = id

        Profile._id_cnt = max(self.id, Profile._id_cnt+1)

        self._num = int(num)
        self._code1 = int(code1)
        self._code2 = int(code2)
        self._kp = float(kp)
        self._name = str(name)
        self._reach = reach
        self._sl = None

        self._points: List[Point] = []

        self._profile_type = _type

    def __len__(self):
        return len(self.points)

    @property
    def number_points(self):
        return len(self.points)

    def _get_points_list(self):
        # Points list generator is type (int, Point) with the first
        # element the index of the Point in list
        return list(
            map(
                lambda p: p[1],
                sorted(
                    self._points,
                    key=lambda p: p[0]
                )
            )
        )

    @property
    def points(self):
        if not isinstance(self._points, list):
            self._points = self._get_points_list()

        return self._points

    def point(self, index):
        return self.points[index]

    @property
    def reach(self):
        return self._reach

    @property
    def num(self):
        """
        Returns:
            Number of profile.
        """
        return self._num

    @num.setter
    def num(self, value: int):
        self._num = int(value)
        self._status.modified()

    @property
    def code1(self):
        """
        Returns:
            Interpolation code 1.
        """
        return self._code1

    @code1.setter
    def code1(self, value: int):
        self._code1 = int(value)
        self._status.modified()

    @property
    def code2(self):
        """
        Returns:
            Interpolation code 2.
        """
        return self._code2

    @code2.setter
    def code2(self, value: int):
        self._code2 = int(value)
        self._status.modified()

    @property
    def nb_points(self):
        return len(self.points)

    @property
    def kp(self):
        """
        Returns:
            Kilometer point.
        """
        return self._kp

    @kp.setter
    def kp(self, value: float):
        self._kp = float(value)
        self._status.modified()

    @property
    def name(self):
        """
        Returns:
            Profile name.
        """
        return self._name

    @name.setter
    def name(self, value: str):
        self._name = value.strip()
        self._status.modified()

    @property
    def sl(self):
        """
        Returns:
            Profile sediment layers.
        """
        return self._sl

    @sl.setter
    def sl(self, value):
        self._sl = value
        self._status.modified()

    @property
    def profile_type(self):
        """
        Returns:
            Profile type.
        """
        return self._profile_type

    @profile_type.setter
    def profile_type(self, value: str):
        self._profile_type = value
        self._status.modified()

    def point(self, i: int):
        if i < len(self.points):
            return self.points[i]

        return None

    def named_points(self):
        """List of named point

        Returns:
            The list of named point
        """
        return [point for point in self.points
                if point.point_is_named()]

    def insert_point(self, index: int, point: Point):
        """Insert point at index.

        Args:
            index: The index of new profile.
            point: The point.

        Returns:
            Nothing.
        """
        self.points.insert(index, point)
        self._status.modified()

    def delete(self, indexes: int):
        """Delete points at index

        Args:
            indexes: List of index of points.

        Returns:
            Nothing.
        """
        points = set(
            map(
                lambda e: e[1],
                filter(
                    lambda e: e[0] in indexes,
                    enumerate(self.points)
                )
            )
        )

        self.points = list(
            filter(
                lambda p: p not in points,
                self.points
            )
        )
        self._status.modified()

    def delete_points(self, points):
        """Delete some elements in profile list

        Args:
            points: The list of profile to delete

        Returns:
            Nothing.
        """
        self.points = list(
            filter(
                lambda p: p not in points,
                self.points
            )
        )
        self._status.modified()

    # Move

    def move_up_point(self, index: int):
        if index < len(self.points):
            next = index - 1

            p = self.points
            p[index], p[next] = p[next], p[index]
            self._status.modified()

    def move_down_point(self, index: int):
        if index >= 0:
            prev = index + 1

            p = self.points
            p[index], p[prev] = p[prev], p[index]
            self._status.modified()

    # Sort

    @timer
    def sort(self, column, is_reversed: bool = False):
        def predicate(p): return p.x
        if column == 'y':
            def predicate(p): return p.y
        elif column == 'z':
            def predicate(p): return p.z

        self.points = sorted(
            self.points,
            key=predicate,
            reverse=is_reversed
        )
        self._status.modified()

    @timer
    def sort_with_indexes(self, indexes: list):
        if len(self.points) != len(indexes):
            logger.critical("Indexes list do not correspond to point list")

        self.points = list(
            map(
                lambda x: x[1],
                sorted(
                    enumerate(self.points),
                    key=lambda x: indexes[x[0]]
                )
            )
        )
        self._status.modified()

    # Sediment Layers

    def get_sl(self):
        """Get sediment layer height of points

        Get sediment layer of points (without spesific point sl)

        Returns:
            List of sediment layers height
        """
        res = []
        psl = [point.sl for point in self.points]

        # Compute max number of layers
        sl_max = 0
        for sl in psl:
            n = 0 if sl is None else len(sl)
            sl_max = max(n, sl_max)

        # Create list of height for each sl and each layer
        for i in range(0, sl_max):
            cur = []
            # Compute new layer line for each sl
            for sl in psl:
                if sl is not None and i < len(sl):
                    cur.append(sl.get(i).height)
                else:
                    cur.append(0)
            # Add layer line to result
            res.append(cur)

        return res

    # Abstract method, must be implemented for in non abstract class
    def get_station(self):
        raise NotImplementedMethodeError(self, self.get_station)

    # Computation method

    # Abstract method for width approximation
    def width_approximation(self):
        raise NotImplementedMethodeError(self, self.width_approximation)

    # Abstract method for width approximation
    def get_water_limits(self, z):
        raise NotImplementedMethodeError(self, self.get_water_limits)