From 246b4111144f62a5744af92c12791d7aed9dd2c4 Mon Sep 17 00:00:00 2001 From: Dylan Jeannin Date: Fri, 3 Jul 2026 17:07:34 +0200 Subject: [PATCH] Weather params: Dual tab for parameters with add functionnality --- src/Model/River.py | 18 +- src/Model/WeatherParameters/AirTemperature.py | 406 ------------ .../WeatherParameters/AirTemperatureList.py | 73 --- .../WeatherParameters/WeatherParameters.py | 615 ++++++++++++++++++ .../WeatherParametersList.py | 96 +++ .../WeatherParametersTypes.py | 171 +++++ src/View/MainWindow.py | 6 +- src/View/WeatherParameters/Table.py | 150 +++-- src/View/WeatherParameters/UndoCommand.py | 123 ++-- src/View/WeatherParameters/Window.py | 63 +- src/View/WeatherParameters/translate.py | 22 +- 11 files changed, 1094 insertions(+), 649 deletions(-) delete mode 100644 src/Model/WeatherParameters/AirTemperature.py delete mode 100644 src/Model/WeatherParameters/AirTemperatureList.py create mode 100644 src/Model/WeatherParameters/WeatherParameters.py create mode 100644 src/Model/WeatherParameters/WeatherParametersList.py create mode 100644 src/Model/WeatherParameters/WeatherParametersTypes.py diff --git a/src/Model/River.py b/src/Model/River.py index 7eeafa8d..70b625d2 100644 --- a/src/Model/River.py +++ b/src/Model/River.py @@ -66,8 +66,8 @@ from Model.InitialConditionsTemperature.InitialConditionsTemperatureList \ from Model.BoundaryConditionsTemperature.BoundaryConditionsTemperatureList \ import BoundaryConditionsTemperatureList -from Model.WeatherParameters.AirTemperatureList \ - import AirTemperatureList +from Model.WeatherParameters.WeatherParametersList \ + import WeatherParametersList from Model.GeoTIFF.GeoTIFFList import GeoTIFFList from Model.Results.Results import Results @@ -487,7 +487,7 @@ class River(Graph): DIFAdisTSList, InitialConditionsTemperatureList, BoundaryConditionsTemperatureList, - AirTemperatureList, + WeatherParametersList, GeoTIFFList, Results ] @@ -531,7 +531,7 @@ class River(Graph): self._BoundaryConditionsTemperature = ( BoundaryConditionsTemperatureList(status=self._status) ) - self._AirTemperature = (AirTemperatureList(status=self._status)) + self._WeatherParameters = (WeatherParametersList(status=self._status)) self._geotiff = GeoTIFFList(status=self._status) @@ -653,7 +653,7 @@ class River(Graph): new._BoundaryConditionsTemperature = \ BoundaryConditionsTemperatureList._db_load(execute, data) - new._AirTemperature = AirTemperatureList._db_load(execute, data) + new._WeatherParameters = WeatherParametersList._db_load(execute, data) new._geotiff = GeoTIFFList._db_load(execute, data) @@ -692,7 +692,7 @@ class River(Graph): objs.append(self._InitialConditionsTemperature) objs.append(self._BoundaryConditionsTemperature) - objs.append(self._AirTemperature) + objs.append(self._WeatherParameters) objs.append(self._geotiff) @@ -774,7 +774,7 @@ class River(Graph): self._D90AdisTS, self._DIFAdisTS, self._InitialConditionsTemperature, self._BoundaryConditionsTemperature, - self._AirTemperature, + self._WeatherParameters, self._geotiff, ] @@ -917,8 +917,8 @@ Last export at: @date.""" return self._BoundaryConditionsTemperature @property - def air_temperature(self): - return self._AirTemperature + def weather_parameters(self): + return self._WeatherParameters def get_params(self, solver): if solver in self._parameters: diff --git a/src/Model/WeatherParameters/AirTemperature.py b/src/Model/WeatherParameters/AirTemperature.py deleted file mode 100644 index 212ea52c..00000000 --- a/src/Model/WeatherParameters/AirTemperature.py +++ /dev/null @@ -1,406 +0,0 @@ -# AirTemperature.py -- Pamhyr -# Copyright (C) 2023-2025 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 . - -# -*- coding: utf-8 -*- - -import logging - -from tools import ( - trace, timer, - old_pamhyr_date_to_timestamp, - date_iso_to_timestamp, - date_dmy_to_timestamp, -) - -from Model.Tools.PamhyrDB import SQLSubModel -from Model.Except import NotImplementedMethodeError -from Model.Scenario import Scenario - -logger = logging.getLogger() - - -class Data(SQLSubModel): - _sub_classes = [] - - def __init__(self, - data0, data1, - id: int = -1, - types=[float, float], - status=None, - owner_scenario=-1): - super(Data, self).__init__( - id=id, status=status, - owner_scenario=owner_scenario - ) - - self._types = types - self._data = [data0, data1] - - @classmethod - def _db_create(cls, execute, ext=""): - execute(f""" - CREATE TABLE air_temperature_data{ext}( - {cls.create_db_add_pamhyr_id()}, - deleted BOOLEAN NOT NULL DEFAULT FALSE, - data0 TEXT NOT NULL, - data1 TEXT NOT NULL, - {Scenario.create_db_add_scenario()}, - {Scenario.create_db_add_scenario_fk()}, - ) - """) - - @classmethod - def _db_update(cls, execute, version, data=None): - major, minor, release = version.strip().split(".") - created = False - - if major == "0" and int(minor) < 2: - if cls.is_table_exists( - execute, "air_temperature_data"): - cls._db_update_to_0_2_0(execute, data) - else: - cls._db_create(execute) - - return True - - @classmethod - def _db_load(cls, execute, data=None): - new = [] - status = data['status'] - scenario = data["scenario"] - loaded = data['loaded_pid'] - - if scenario is None: - return new - - table = execute( - "SELECT pamhyr_id, deleted, data0, data1, scenario " + - "FROM air_temperature_data " + - f"WHERE scenario = {scenario.id} " + - f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) " - ) - - if table is not None: - for row in table: - it = iter(row) - - pid = next(it) - deleted = (next(it) == 1) - data0 = next(it) - data1 = next(it) - owner_scenario = next(it) - - lc = cls( - data0, data1, - id=pid, status=status, - owner_scenario=owner_scenario - ) - if deleted: - lc.set_as_deleted() - - loaded.add(pid) - new.append(lc) - - data["scenario"] = scenario.parent - new += cls._db_load(execute, data) - data["scenario"] = scenario - - return new - - def _db_save(self, execute, data=None): - if not self.must_be_saved(): - return True - - execute( - "INSERT INTO " + - "air_temperature_data(pamhyr_id, deleted," + - "data0, data1, scenario) " + - "VALUES (" + - f"{self.id}, {self._db_format(self.is_deleted())}, " + - f"{self._data[0]}, {self._data[1]}, " + - f"{self._status.scenario_id}" + - ")" - ) - - return True - - def __getitem__(self, key): - return self._types[key](self._data[key]) - - def __setitem__(self, key, value): - self._data[key] = self._types[key](value) - - -class AirTemperature(SQLSubModel): - _sub_classes = [Data] - - def __init__(self, id: int = -1, int = -1, - name: str = "", status=None, - owner_scenario=-1): - super(AirTemperature, self).__init__( - id=id, status=status, - owner_scenario=owner_scenario - ) - - self._status = status - - self._reach = None - self._begin_rk = 0.0 - self._end_rk = 0.0 - self._data = [] - self._header = ["time", "rate"] - self._types = [self.time_convert, float] - - @classmethod - def _db_create(cls, execute, ext=""): - execute(f""" - CREATE TABLE air_temperature{ext}( - {cls.create_db_add_pamhyr_id()}, - deleted BOOLEAN NOT NULL DEFAULT FALSE, - reach INTEGER NOT NULL, - begin_rk REAL NOT NULL, - end_rk REAL NOT NULL, - {Scenario.create_db_add_scenario()}, - {Scenario.create_db_add_scenario_fk()}, - FOREIGN KEY(reach) REFERENCES river_reach(pamhyr_id) - ) - """) - - if ext != "": - return True - - return cls._create_submodel(execute) - - @classmethod - def _db_update(cls, execute, version, data=None): - major, minor, release = version.strip().split(".") - created = False - - if major == "0" and int(minor) < 2: - if cls.is_table_exists(execute, "air_temperature"): - cls._db_update_to_0_2_0(execute, data) - else: - cls._db_create(execute) - created = True - - if not created: - return cls._update_submodel(execute, version, data) - - return True - - @classmethod - def _db_load(cls, execute, data=None): - new = [] - status = data['status'] - scenario = data["scenario"] - loaded = data['loaded_pid'] - - if scenario is None: - return new - - table = execute( - "SELECT pamhyr_id, deleted, reach, " + - "begin_rk, end_rk, scenario " + - "FROM air_temperature " + - f"WHERE scenario = {scenario.id} " + - f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) " - ) - - if table is not None: - for row in table: - it = iter(row) - - pid = next(it) - deleted = (next(it) == 1) - reach = next(it) - brk = next(it) - erk = next(it) - owner_scenario = next(it) - - data["scenario"] = scenario.parent - new += cls._db_load(execute, data) - data["scenario"] = scenario - - return new - - def _db_save(self, execute, data=None): - if not self.must_be_saved(): - return True - - execute( - f"DELETE FROM air_temperature " + - f"WHERE pamhyr_id = {self.id} " + - f"AND scenario = {self._status.scenario_id}" - ) - execute( - f"DELETE FROM air_temperature_data " + - f"WHERE scenario = {self._status.scenario_id}" - ) - - execute( - "INSERT INTO " + - "air_temperature(pamhyr_id, deleted, " + - "reach, begin_rk, end_rk, scenario) " + - "VALUES (" + - f"{self.id}, {self._db_format(self.is_deleted())}, {self.reach}, " + - f"{self._begin_rk}, {self._end_rk}, " + - f"{self._status.scenario_id}" + - ")" - ) - ind = 0 - for d in self._data: - data["ind"] = ind - - d._db_save(execute, data) - - ind += 1 - - return True - - def _data_traversal(self, - predicate=lambda obj, data: True, - modifier=lambda obj, data: None, - data={}): - if predicate(self, data): - modifier(self, data) - - for d in self._data: - d._data_traversal(predicate, modifier, data) - - def __len__(self): - return len( - list( - filter( - lambda el: el is not None and not el.is_deleted(), - self._data - ) - ) - ) - - @classmethod - def time_convert(cls, data): - if type(data) is str: - if data.count("-") == 2: - return date_iso_to_timestamp(data) - if data.count("/") == 2: - return date_dmy_to_timestamp(data) - if data.count(":") == 3: - return old_pamhyr_date_to_timestamp(data) - if data.count(":") == 2: - return old_pamhyr_date_to_timestamp("00:" + data) - if data.count(".") == 1: - return round(float(data)) - - return int(data) - - @property - def reach(self): - return self._reach - - @reach.setter - def reach(self, reach): - self._reach = reach - self.modified() - - @property - def header(self): - return self._header.copy() - - @header.setter - def header(self, header): - self._header = header - self.modified() - - @property - def data(self): - return list( - filter( - lambda el: el is not None and not el.is_deleted(), - self._data - ) - ) - - @property - def begin_rk(self): - return self._begin_rk - - @begin_rk.setter - def begin_rk(self, begin_rk): - self._begin_rk = begin_rk - self.modified() - - @property - def end_rk(self): - return self._end_rk - - @end_rk.setter - def end_rk(self, end_rk): - self._end_rk = end_rk - self.modified() - - @property - def _default_0(self): - return self._types[0](0) - - @property - def _default_1(self): - return self._types[1](0.0) - - def add(self, index: int): - value = Data(self._default_0, self._default_1, status=self._status) - self._data.insert(index, value) - self.modified() - return value - - def insert(self, index: int, data: Data): - self._data.insert(index, data) - self.modified() - - def delete_i(self, indexes): - self._data = list( - map( - lambda e: e[1].set_as_deleted(), - filter( - lambda e: e[0] not in indexes, - enumerate(self.data) - ) - ) - ) - self.modified() - - def index(self, bc): - self._data.index(bc) - - def get_i(self, index: int): - return self._data[index] - - def get_range(self, _range): - lst = [] - for r in _range: - lst.append(r) - return lst - - def _set_i_c_v(self, index: int, column: int, value): - v = self._data[index] - v[column] = self._types[column](value) - self._data[index] = v - self.modified() - - def set_i_0(self, index: int, value): - self._set_i_c_v(index, 0, value) - - def set_i_1(self, index: int, value): - self._set_i_c_v(index, 1, value) diff --git a/src/Model/WeatherParameters/AirTemperatureList.py b/src/Model/WeatherParameters/AirTemperatureList.py deleted file mode 100644 index 3875125d..00000000 --- a/src/Model/WeatherParameters/AirTemperatureList.py +++ /dev/null @@ -1,73 +0,0 @@ -# AirTemperatureList.py -- Pamhyr -# Copyright (C) 2023-2025 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 . - -# -*- coding: utf-8 -*- - -from copy import copy -from tools import trace, timer - -from Model.Tools.PamhyrListExt import PamhyrModelList -from Model.Except import NotImplementedMethodeError - -from Model.WeatherParameters.AirTemperature \ - import AirTemperature - - -class AirTemperatureList(PamhyrModelList): - _sub_classes = [ - AirTemperature, - ] - - @classmethod - def _db_load(cls, execute, data=None): - new = cls(status=data['status']) - - if data is None: - data = {} - - new._lst = AirTemperature._db_load( - execute, data - ) - - return new - - def _db_save(self, execute, data=None): - execute( - "DELETE FROM air_temperature " + - f"WHERE scenario = {self._status.scenario_id}" - ) - execute( - "DELETE FROM air_temperature_data " + - f"WHERE scenario = {self._status.scenario_id}" - ) - - if data is None: - data = {} - - for lc in self._lst: - lc._db_save(execute, data=data) - - return True - - def new(self, index, pollutant): - n = AirTemperature(pollutant=pollutant, status=self._status) - self._lst.insert(index, n) - self._status.modified() - return n - - @property - def Air_Temperature_List(self): - return self.lst diff --git a/src/Model/WeatherParameters/WeatherParameters.py b/src/Model/WeatherParameters/WeatherParameters.py new file mode 100644 index 00000000..8fac0025 --- /dev/null +++ b/src/Model/WeatherParameters/WeatherParameters.py @@ -0,0 +1,615 @@ +# WeatherParameters.py -- Pamhyr +# Copyright (C) 2023-2025 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 . + +# -*- coding: utf-8 -*- + +import logging + +from tools import ( + trace, timer, + old_pamhyr_date_to_timestamp, + date_iso_to_timestamp, + date_dmy_to_timestamp, +) + +from Model.Tools.PamhyrDB import SQLSubModel +from Model.Except import NotImplementedMethodeError +from Model.Scenario import Scenario + +logger = logging.getLogger() + + +class Data(SQLSubModel): + _sub_classes = [] + + def __init__(self, + data0, data1, + id: int = -1, + types=[float, float], + status=None, + owner_scenario=-1): + super(Data, self).__init__( + id=id, status=status, + owner_scenario=owner_scenario + ) + + self._types = types + self._data = [data0, data1] + + @classmethod + def _db_create(cls, execute, ext=""): + execute(f""" + CREATE TABLE weather_parameters_data{ext} ( + {cls.create_db_add_pamhyr_id()}, + deleted BOOLEAN NOT NULL DEFAULT FALSE, + ind INTEGER NOT NULL, + data0 TEXT NOT NULL, + data1 TEXT NOT NULL, + wp INTEGER, + {Scenario.create_db_add_scenario()}, + {Scenario.create_db_add_scenario_fk()}, + FOREIGN KEY(wp) REFERENCES weather_parameters(pamhyr_id), + PRIMARY KEY(pamhyr_id, scenario) + ) + """) + + return cls._create_submodel(execute) + + @classmethod + def _db_update(cls, execute, version, data=None): + major, minor, release = version.strip().split(".") + + if major == "0" and (int(minor) < 2 or + (int(minor) == 2 and int(release) < 7)): + if not cls.is_table_exists(execute, "air_temperature_data"): + cls._db_create(execute) + + return cls._update_submodel(execute, version, data) + + @classmethod + def _db_load(cls, execute, data=None): + new = [] + wp = data["wp"] + status = data['status'] + scenario = data["scenario"] + loaded = data['loaded_pid'] + + if scenario is None: + return new + + values = execute( + "SELECT pamhyr_id, deleted, " + + "data0, data1, scenario " + + "FROM weather_parameters_data " + + f"WHERE wp = {wp._pamhyr_id} " + + f"AND scenario = {scenario.id} " + + f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) " + + "ORDER BY ind ASC" + ) + + for v in values: + it = iter(v) + + pid = next(it) + deleted = next(it) + data0 = wp._types[0](next(it)) + data1 = wp._types[1](next(it)) + owner_scenario = next(it) + + nd = cls( + data0, data1, + id=pid, + types=wp._types, + status=status, + owner_scenario=owner_scenario + ) + if deleted: + nd.set_as_deleted() + + loaded.add(pid) + new.append(nd) + + data["scenario"] = scenario.parent + new += cls._db_load(execute, data) + data["scenario"] = scenario + + return new + + def _db_save(self, execute, data=None): + pid = self._pamhyr_id + ind = data["ind"] + data0 = self._db_format(str(self[0])) + data1 = self._db_format(str(self[1])) + wp = data["wp"] + + execute( + "INSERT INTO " + + "weather_parameters_data (pamhyr_id, deleted, ind, " + + "data0, data1, wp, scenario) " + + f"VALUES ({pid}, {self._db_format(self.is_deleted())}, " + + f"{ind}, '{self._db_format(data0)}', {self._db_format(data1)}, " + + f"{wp._pamhyr_id}, {self._status.scenario_id}" + + ")" + ) + + return True + + def __getitem__(self, key): + return self._types[key](self._data[key]) + + def __setitem__(self, key, value): + self._data[key] = self._types[key](value) + + +class WeatherParameters(SQLSubModel): + _sub_classes = [Data] + + def __init__(self, id: int = -1, + name: str = "", + status=None, owner_scenario=-1): + super(WeatherParameters, self).__init__( + id=id, status=status, + owner_scenario=owner_scenario + ) + + self._name = name + self._type = "" + self._reach = None + self._begin_section = None + self._end_section = None + self._data = [] + self._header = [] + self._types = [float, float] + + @classmethod + def _db_create(cls, execute, ext=""): + execute(f""" + CREATE TABLE weather_parameters{ext}( + {cls.create_db_add_pamhyr_id()}, + deleted BOOLEAN NOT NULL DEFAULT FALSE, + name TEXT NOT NULL, + type TEXT NOT NULL, + reach INTEGER, + begin_section INTEGER, + end_section INTEGER, + {Scenario.create_db_add_scenario()}, + {Scenario.create_db_add_scenario_fk()}, + FOREIGN KEY(reach) REFERENCES river_reach(pamhyr_id), + FOREIGN KEY(begin_section) + REFERENCES geometry_profileXYZ(pamhyr_id), + FOREIGN KEY(end_section) + REFERENCES geometry_profileXYZ(pamhyr_id), + PRIMARY KEY(pamhyr_id, scenario) + ) + """) + + if ext == "_tmp": + return True + + return cls._create_submodel(execute) + + @classmethod + def _get_ctor_from_type(cls, t): + from Model.WeatherParameters.WeatherParametersTypes import ( + NotDefined, WeatherParam, AirTemperature, SpecificHumidity, + GlobalRadiation, ReferenceWindSpeed, GroundwaterTemperature, + GroundwaterFlowRate, Albedo, ShadingCoefficient, CloudCoverFraction + ) + + res = NotDefined + if t == "WP": + res = WeatherParam + elif t == "AT": + res = AirTemperature + elif t == "SH": + res = SpecificHumidity + elif t == "GR": + res = GlobalRadiation + elif t == "RWS": + res = ReferenceWindSpeed + elif t == "GT": + res = GroundwaterTemperature + elif t == "GFR": + res = GroundwaterFlowRate + elif t == "ALB": + res = Albedo + elif t == "SC": + res = ShadingCoefficient + elif t == "CCF": + res = CloudCoverFraction + else: + res = NotDefined + return res + + @classmethod + def _db_update(cls, execute, version, data=None): + major, minor, release = version.strip().split(".") + created = False + + if major == "0" and (int(minor) < 2 or + (int(minor) == 2 and int(release) < 7)): + if not cls.is_table_exists(execute, "air_temperature"): + cls._db_create(execute) + created = True + + if not created: + return cls._update_submodel(execute, version, data) + + return True + + @classmethod + def _db_load(cls, execute, data=None): + new = [] + status = data['status'] + edges = data["edges"] + scenario = data["scenario"] + loaded = data['loaded_pid'] + + if scenario is None: + return new + + table = execute( + "SELECT pamhyr_id, deleted, name, type, " + + "reach, begin_section, end_section, scenario " + + "FROM weather_parameters " + + f"WHERE scenario = {scenario.id} " + + f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) " + ) + + for row in table: + it = iter(row) + + pid = next(it) + deleted = next(it) + name = next(it) + t = next(it) + reach = next(it) + b_section = next(it) + e_section = next(it) + owner_scenario = next(it) + + ctor = cls._get_ctor_from_type(t) + wp = ctor( + id=pid, name=name, + status=status, + owner_scenario=owner_scenario + ) + wp.reach = None + wp._begin_section = None + wp._end_section = None + + if row[3] != -1: + wp.reach = next( + filter( + lambda e: e.id == reach, + edges + ) + ) + wp._begin_section = next( + filter( + lambda p: p.id == b_section, + wp.reach.reach.profiles + ) + ) + wp._end_section = next( + filter( + lambda p: p.id == e_section, + wp.reach.reach.profiles + ) + ) + + data["wp"] = wp + wp._data = Data._db_load(execute, data=data) + + loaded.add(pid) + new.append(wp) + + data["scenario"] = scenario.parent + new += cls._db_load(execute, data) + data["scenario"] = scenario + + return new + + def _db_save(self, execute, data=None): + if not self.must_be_saved(): + return True + + data["wp"] = self + + execute( + "DELETE FROM weather_parameters_data " + + f"WHERE wp = {self._pamhyr_id} " + + f"AND scenario = {self._status.scenario_id}" + ) + + reach = -1 + begin_section = -1 + end_section = -1 + + if self._reach is not None: + reach = self._reach._pamhyr_id + begin_section = self._begin_section._pamhyr_id + end_section = self._end_section._pamhyr_id + + execute( + "INSERT INTO " + + "weather_parameters(" + + "pamhyr_id, deleted, name, type, " + + "reach, begin_section, end_section, scenario) " + + "VALUES (" + + f"{self.id}, {self._db_format(self.is_deleted())}, " + + f"'{self._db_format(self._name)}', " + + f"'{self._db_format(self._type)}', {reach}, " + + f"{begin_section}, {end_section}, " + + f"{self._status.scenario_id}" + + ")" + ) + + ind = 0 + for d in self._data: + data["ind"] = ind + + d._db_save(execute, data) + + ind += 1 + + return True + + def _data_traversal(self, + predicate=lambda obj, data: True, + modifier=lambda obj, data: None, + data={}): + if predicate(self, data): + modifier(self, data) + + for d in self._data: + d._data_traversal(predicate, modifier, data) + + def __len__(self): + return len( + list( + filter( + lambda el: not el.is_deleted(), + self._data + ) + ) + ) + + @classmethod + def compatibility(cls): + return ["liquid", "solid", "suspenssion"] + + @classmethod + def time_convert(cls, data): + if type(data) is str: + if data.count("-") == 2: + return date_iso_to_timestamp(data) + if data.count("/") == 2: + return date_dmy_to_timestamp(data) + if data.count(":") == 3: + return old_pamhyr_date_to_timestamp(data) + if data.count(":") == 2: + return old_pamhyr_date_to_timestamp("00:" + data) + if data.count(".") == 1: + return round(float(data)) + + return int(data) + + @property + def name(self): + if self._name == "": + return f"LC #{self.id}" + + return self._name + + @name.setter + def name(self, name): + self._name = name + self.modified() + + @property + def wptype(self): + return self._type + + @property + def reach(self): + return self._reach + + @reach.setter + def reach(self, reach): + self._reach = reach + if reach is not None: + self._begin_section = self._reach.reach.profiles[0] + self._end_section = self._reach.reach.profiles[-1] + + self.modified() + + def has_reach(self): + return self._reach is not None + + @property + def begin_rk(self): + if self._begin_section is None: + return 0 + + return self._begin_section.rk + + @property + def begin_section(self): + return self._begin_section + + @begin_section.setter + def begin_section(self, section): + self._begin_section = section + self.modified() + + @property + def end_rk(self): + if self._end_section is None: + return 0 + + return self._end_section.rk + + @property + def end_section(self): + return self._end_section + + @end_section.setter + def end_section(self, section): + self._end_section = section + self.modified() + + @property + def header(self): + return self._header.copy() + + @property + def data(self): + return list( + filter( + lambda el: not el.is_deleted(), + self._data + ) + ) + + def get_type_column(self, column): + if 0 <= column < 2: + return self._types[column] + return None + + @property + def _default_0(self): + return self._types[0](0) + + @property + def _default_1(self): + return self._types[1](0.0) + + def is_define(self): + return self._data is not None + + def new_from_data(self, header, data): + new_0 = self._default_0 + new_1 = self._default_1 + + if len(header) != 0: + for i in [0, 1]: + for j in range(len(header)): + if self._header[i] == header[j]: + if i == 0: + new_0 = self._types[i](data[j].replace(",", ".")) + else: + new_1 = self._types[i](data[j].replace(",", ".")) + else: + new_0 = self._types[0](data[0].replace(",", ".")) + new_1 = self._types[1](data[1].replace(",", ".")) + + return Data(new_0, new_1, status=self._status) + + def add(self, index: int): + value = Data(self._default_0, self._default_1, status=self._status) + self._data.insert(index, value) + self.modified() + return value + + def insert(self, index: int, value): + self._data.insert(index, value) + self.modified() + + def delete_i(self, indexes): + self._data = list( + map( + lambda e: e[1], + filter( + lambda e: e[0] not in indexes, + enumerate(self.data) + ) + ) + ) + self.modified() + + def delete(self, els): + self._data = list( + filter( + lambda e: e not in els, + self.data + ) + ) + self.modified() + + def sort(self, _reverse=False, key=None): + if key is None: + self._data.sort(reverse=_reverse) + else: + self._data.sort(reverse=_reverse, key=key) + self.modified() + + def get_i(self, index): + return self.data[index] + + def get_range(self, _range): + lst = [] + for r in _range: + lst.append(r) + return lst + + def _set_i_c_v(self, index, column, value): + v = self._data[index] + v[column] = self._types[column](value) + self._data[index] = v + self.modified() + + def set_i_0(self, index: int, value): + self._set_i_c_v(index, 0, value) + + def set_i_1(self, index: int, value): + self._set_i_c_v(index, 1, value) + + @timer + def convert(self, cls): + new = cls(name=self.name, status=self._status) + new.reach = self.reach + new.begin_section = self.begin_section + new.end_section = self.end_section + + for i, _ in enumerate(self.data): + new.add(i) + + for i in [0, 1]: + for j in [0, 1]: + if self._header[i] == new.header[j]: + for ind, v in self.data: + try: + new._set_i_c_v(ind, j, v[i]) + except Exception as e: + logger.info(e) + + self.modified() + return new + + def move_up(self, index): + if index < len(self): + next = index - 1 + d = self._data + d[index], d[next] = d[next], d[index] + self.modified() + + def move_down(self, index): + if index >= 0: + prev = index + 1 + d = self._data + d[index], d[prev] = d[prev], d[index] + self.modified() diff --git a/src/Model/WeatherParameters/WeatherParametersList.py b/src/Model/WeatherParameters/WeatherParametersList.py new file mode 100644 index 00000000..a7e2f215 --- /dev/null +++ b/src/Model/WeatherParameters/WeatherParametersList.py @@ -0,0 +1,96 @@ +# WeatherParametersList.py -- Pamhyr +# Copyright (C) 2023-2025 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 . + +# -*- coding: utf-8 -*- + +from copy import copy +from tools import trace, timer + +from Model.Tools.PamhyrListExt import PamhyrModelList +from Model.Except import NotImplementedMethodeError + +from Model.WeatherParameters.WeatherParameters import WeatherParameters +from Model.WeatherParameters.WeatherParametersTypes import ( + NotDefined, WeatherParam, AirTemperature, SpecificHumidity, + GlobalRadiation, ReferenceWindSpeed, GroundwaterTemperature, + GroundwaterFlowRate, Albedo, ShadingCoefficient, CloudCoverFraction +) + + +class WeatherParametersList(PamhyrModelList): + _sub_classes = [ + WeatherParameters, + ] + + @classmethod + def _db_load(cls, execute, data=None): + new = cls(status=data['status']) + + if data is None: + data = {} + + new._lst = WeatherParameters._db_load( + execute, data + ) + + return new + + def _db_save(self, execute, data=None): + execute( + "DELETE FROM weather_parameters " + + f"WHERE scenario = {self._status.scenario_id}" + ) + execute( + "DELETE FROM weather_parameters_data " + + f"WHERE scenario = {self._status.scenario_id}" + ) + + if data is None: + data = {} + + for wp in self._lst: + wp._db_save(execute, data=data) + + return True + + def new(self, index, type="ND"): + if type == "AT": + n = AirTemperature(status=self._status) + elif type == "SH": + n = SpecificHumidity(status=self._status) + elif type == "GR": + n = GlobalRadiation(status=self._status) + elif type == "RWS": + n = ReferenceWindSpeed(status=self._status) + elif type == "GT": + n = GroundwaterTemperature(status=self._status) + elif type == "GFR": + n = GroundwaterFlowRate(status=self._status) + elif type == "ALB": + n = Albedo(status=self._status) + elif type == "SC": + n = ShadingCoefficient(status=self._status) + elif type == "CCF": + n = CloudCoverFraction(status=self._status) + else: + n = NotDefined(status=self._status) + self._lst.insert(index, n) + self._status.modified() + return n + + @property + def Weather_Parameters_List(self): + return self.lst diff --git a/src/Model/WeatherParameters/WeatherParametersTypes.py b/src/Model/WeatherParameters/WeatherParametersTypes.py new file mode 100644 index 00000000..3db87fd6 --- /dev/null +++ b/src/Model/WeatherParameters/WeatherParametersTypes.py @@ -0,0 +1,171 @@ +# LateralContributionTypes.py -- Pamhyr +# Copyright (C) 2023-2025 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 . + +# -*- coding: utf-8 -*- + +from Model.Except import NotImplementedMethodeError + +from Model.WeatherParameters.WeatherParameters import WeatherParameters + + +class NotDefined(WeatherParameters): + def __init__(self, id: int = -1, name: str = "", + status=None, owner_scenario=-1): + super(NotDefined, self).__init__( + id=id, name=name, status=status, + owner_scenario=owner_scenario + ) + + self._type = "ND" + self._header = ["x", "y"] + + @property + def _default_0(self): + return 0.0 + + +class WeatherParam(WeatherParameters): + def __init__(self, id: int = -1, name: str = "", + status=None, owner_scenario=-1): + super(WeatherParam, self).__init__( + id=id, name=name, status=status, + owner_scenario=owner_scenario + ) + + self._type = "WP" + self._header = ["time", "value"] + self._types = [WeatherParam.time_convert, float] + + @classmethod + def compatibility(cls): + return ["liquid"] + + +class AirTemperature(WeatherParameters): + def __init__(self, id: int = -1, name: str = "", + status=None, owner_scenario=-1): + super(AirTemperature, self).__init__( + id=id, name=name, status=status, + owner_scenario=owner_scenario + ) + + self._type = "AT" + self._header = ["time", "temperature"] + self._types = [AirTemperature.time_convert, float] + + +class SpecificHumidity(WeatherParameters): + def __init__(self, id: int = -1, name: str = "", + status=None, owner_scenario=-1): + super(SpecificHumidity, self).__init__( + id=id, name=name, status=status, + owner_scenario=owner_scenario + ) + + self._type = "SH" + self._header = ["time", "discharge"] + self._types = [SpecificHumidity.time_convert, float] + + +class GlobalRadiation(WeatherParameters): + def __init__(self, id: int = -1, name: str = "", + status=None, owner_scenario=-1): + super(GlobalRadiation, self).__init__( + id=id, name=name, status=status, + owner_scenario=owner_scenario + ) + + self._type = "GR" + self._header = ["time", "discharge"] + self._types = [GlobalRadiation.time_convert, float] + + +class ReferenceWindSpeed(WeatherParameters): + def __init__(self, id: int = -1, name: str = "", + status=None, owner_scenario=-1): + super(ReferenceWindSpeed, self).__init__( + id=id, name=name, status=status, + owner_scenario=owner_scenario + ) + + self._type = "RWS" + self._header = ["time", "discharge"] + self._types = [ReferenceWindSpeed.time_convert, float] + + +class GroundwaterTemperature(WeatherParameters): + def __init__(self, id: int = -1, name: str = "", + status=None, owner_scenario=-1): + super(GroundwaterTemperature, self).__init__( + id=id, name=name, status=status, + owner_scenario=owner_scenario + ) + + self._type = "GT" + self._header = ["time", "discharge"] + self._types = [GroundwaterTemperature.time_convert, float] + + +class GroundwaterFlowRate(WeatherParameters): + def __init__(self, id: int = -1, name: str = "", + status=None, owner_scenario=-1): + super(GroundwaterFlowRate, self).__init__( + id=id, name=name, status=status, + owner_scenario=owner_scenario + ) + + self._type = "GFR" + self._header = ["time", "discharge"] + self._types = [GroundwaterFlowRate.time_convert, float] + + +class Albedo(WeatherParameters): + def __init__(self, id: int = -1, name: str = "", + status=None, owner_scenario=-1): + super(Albedo, self).__init__( + id=id, name=name, status=status, + owner_scenario=owner_scenario + ) + + self._type = "ALB" + self._header = ["time", "discharge"] + self._types = [Albedo.time_convert, float] + + +class ShadingCoefficient(WeatherParameters): + def __init__(self, id: int = -1, name: str = "", + status=None, owner_scenario=-1): + super(ShadingCoefficient, self).__init__( + id=id, name=name, status=status, + owner_scenario=owner_scenario + ) + + self._type = "SC" + self._header = ["time", "discharge"] + self._types = [ShadingCoefficient.time_convert, float] + + +class CloudCoverFraction(WeatherParameters): + def __init__(self, id: int = -1, name: str = "", + status=None, owner_scenario=-1): + super(CloudCoverFraction, self).__init__( + id=id, name=name, status=status, + owner_scenario=owner_scenario + ) + + self._type = "CCF" + self._header = ["time", "discharge"] + self._types = [CloudCoverFraction.time_convert, float] diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index 104f5c50..4105fd2b 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -1045,16 +1045,18 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): def open_weather_parameters(self): river = self._study.river - + weather_param = river.weather_parameters + if self.sub_window_exists( WeatherParametersWindow, - data=[self._study, None] + data=[self._study, None, weather_param] ): return bound = WeatherParametersWindow( study=self._study, parent=self, + data=weather_param ) bound.show() diff --git a/src/View/WeatherParameters/Table.py b/src/View/WeatherParameters/Table.py index bc884ac7..1e2e8486 100644 --- a/src/View/WeatherParameters/Table.py +++ b/src/View/WeatherParameters/Table.py @@ -36,8 +36,8 @@ from PyQt5.QtWidgets import ( from View.Tools.PamhyrTable import PamhyrTableModel from View.WeatherParameters.UndoCommand import ( - SetCommand, AddCommand, SetCommandSpec, - DelCommand, + SetCommand, AddCommand, DelCommand, + SetEdgeCommand, SetBeginCommand, SetEndCommand, ) logger = logging.getLogger() @@ -46,43 +46,50 @@ _translate = QCoreApplication.translate class ComboBoxDelegate(QItemDelegate): - def __init__(self, data=None, _weather_param_lst=None, + def __init__(self, data=None, weather_param_lst=None, trad=None, parent=None, mode="reaches"): super(ComboBoxDelegate, self).__init__(parent) self._data = data self._mode = mode self._trad = trad - # self._weather_param_lst = _weather_param_lst + self._weather_param_lst = weather_param_lst + + @property + def weather_param_lst(self): + return self._weather_param_lst + + @weather_param_lst.setter + def weather_param_lst(self, weather_param_lst): + self._weather_param_lst = weather_param_lst + # self.modified() def createEditor(self, parent, option, index): self.editor = QComboBox(parent) - val = [] if self._mode == "rk": - reach_id = self._weather_param_lst[index.row()].reach - - reach = next(filter(lambda edge: edge.id == reach_id, - self._data.edges()), None) - - if reach_id is not None: - val = list( - map( - lambda rk: str(rk), reach.reach.get_rk() + wp = None + if self._weather_param_lst is not None: + wp = self._weather_param_lst.get(index.row()) + if wp is None or wp.reach is None: + self.editor.addItems( + ["-"] + ) + else: + self.editor.addItems( + list( + map( + lambda p: p.display_name(), + wp.reach.reach.profiles + ) ) ) else: - val = list( - map( - lambda n: n.name, self._data.edges() - ) + self.editor.addItems( + [self._trad['not_associated']] + + self._data.edges_names() ) - self.editor.addItems( - [self._trad['not_associated']] + - val - ) - self.editor.setCurrentText(str(index.data(Qt.DisplayRole))) return self.editor @@ -92,7 +99,24 @@ class ComboBoxDelegate(QItemDelegate): def setModelData(self, editor, model, index): text = str(editor.currentText()) - model.setData(index, text) + + if self._mode == "rk": + value = None + wp = None + if self._weather_param_lst is not None: + wp = self._weather_param_lst.get(index.row()) + if wp is not None and wp.reach is not None: + profiles = list( + filter( + lambda p: p.display_name() == text, + wp.reach.reach.profiles + ) + ) + value = profiles[0].pamhyr_id if len(profiles) > 0 else None + else: + value = text + + model.setData(index, value) editor.close() editor.deleteLater() @@ -115,16 +139,16 @@ class WeatherParametersTableModel(PamhyrTableModel): super(WeatherParametersTableModel, self).__init__(data=data, **kwargs) self._data = data - print("self._data: ", self._data) + self._type = "ND" def _setup_lst(self): - # self._lst = list( - # filter( - # lambda ica: ica._deleted is False, - # self._data._data - # ) - # ) - self._lst = self._data._data + self._lst = self._data.lst + + def update_tab_spec(self, type="ND"): + # self._data = data + self._type = type + self._setup_lst() + self.layoutChanged.emit() def rowCount(self, parent): return len(self._lst) @@ -136,34 +160,21 @@ class WeatherParametersTableModel(PamhyrTableModel): row = index.row() column = index.column() - if self._headers[column] == "name": - n = self._lst[row].name - if n is None or n == "": + if self._headers[column] == "reach": + reach = self._lst[row].reach + if reach is None: return self._trad['not_associated'] - return n - elif self._headers[column] == "reach": - n = self._lst[row].reach - if n is None: - return self._trad['not_associated'] - return next(filter( - lambda edge: edge.id == n, self._river.edges() - )).name + return reach.name elif self._headers[column] == "start_rk": - n = self._lst[row].start_rk - if n is None: + section = self._lst[row].begin_section + if section is None: return self._trad['not_associated'] - return n + return section.display_name() elif self._headers[column] == "end_rk": - n = self._lst[row].end_rk - if n is None: + section = self._lst[row].end_section + if section is None: return self._trad['not_associated'] - return n - elif self._headers[column] == "value": - n = self._lst[row].value - if n is None: - return self._trad['not_associated'] - return n - return n + return section.display_name() return QVariant() @@ -178,17 +189,32 @@ class WeatherParametersTableModel(PamhyrTableModel): column = index.column() try: - if self._headers[column] != "reach": + if self._headers[column] == "start_rk": + _edge = self._data.get(row).reach + _begin_rk = next( + p for p in _edge.reach.profiles + if p.pamhyr_id == value + ) self._undo.push( - SetCommandSpec( - self._lst, row, self._headers[column], value + SetBeginCommand( + self._data, row, _begin_rk + ) + ) + elif self._headers[column] == "end_rk": + _edge = self._data.get(row).reach + _end_rk = next( + p for p in _edge.reach.profiles + if p.pamhyr_id == value + ) + self._undo.push( + SetEndCommand( + self._data, row, _end_rk ) ) elif self._headers[column] == "reach": self._undo.push( - SetCommandSpec( - self._lst, row, self._headers[column], - self._river.edge(value).id + SetEdgeCommand( + self._data, row, self._river.edge(value) ) ) except Exception as e: @@ -203,7 +229,7 @@ class WeatherParametersTableModel(PamhyrTableModel): self._undo.push( AddCommand( - self._data, self._lst, row + self._data, self._type, self._lst, row ) ) diff --git a/src/View/WeatherParameters/UndoCommand.py b/src/View/WeatherParameters/UndoCommand.py index 984be6e7..85c628cb 100644 --- a/src/View/WeatherParameters/UndoCommand.py +++ b/src/View/WeatherParameters/UndoCommand.py @@ -28,6 +28,8 @@ from Model.InitialConditionsAdisTS.InitialConditionsAdisTS \ from Model.InitialConditionsAdisTS.InitialConditionsAdisTSList \ import InitialConditionsAdisTSList +from Model.River import RiverReach + class SetCommand(QUndoCommand): def __init__(self, data, row, column, new_value): @@ -79,88 +81,61 @@ class SetCommand(QUndoCommand): self._data[self._row].ed = self._new -class SetCommandSpec(QUndoCommand): - def __init__(self, data, row, column, new_value): +class SetBeginCommand(QUndoCommand): + def __init__(self, wps, index, new_value): QUndoCommand.__init__(self) - self._data = data - self._row = row - self._column = column - - if self._column == "name": - self._old = self._data[self._row].name - elif self._column == "reach": - self._old = self._data[self._row].reach - elif self._column == "start_rk": - self._old = self._data[self._row].start_rk - elif self._column == "end_rk": - self._old = self._data[self._row].end_rk - elif self._column == "concentration": - self._old = self._data[self._row].concentration - elif self._column == "eg": - self._old = self._data[self._row].eg - elif self._column == "em": - self._old = self._data[self._row].em - elif self._column == "ed": - self._old = self._data[self._row].ed - elif self._column == "rate": - self._old = self._data[self._row].rate - - _type = float - if column == "name": - _type = str - elif column == "reach": - _type = int - - self._new = _type(new_value) + self._wps = wps + self._index = index + self._old = self._wps.get(self._index).begin_section + self._new = new_value def undo(self): - if self._column == "name": - self._data[self._row].name = self._old - elif self._column == "reach": - self._data[self._row].reach = self._old - elif self._column == "start_rk": - self._data[self._row].start_rk = self._old - elif self._column == "end_rk": - self._data[self._row].end_rk = self._old - elif self._column == "concentration": - self._data[self._row].concentration = self._old - elif self._column == "eg": - self._data[self._row].eg = self._old - elif self._column == "em": - self._data[self._row].em = self._old - elif self._column == "ed": - self._data[self._row].ed = self._old - elif self._column == "rate": - self._data[self._row].rate = self._old + self._wps.get(self._index).begin_section = self._old def redo(self): - if self._column == "name": - self._data[self._row].name = self._new - elif self._column == "reach": - self._data[self._row].reach = self._new - elif self._column == "start_rk": - self._data[self._row].start_rk = self._new - elif self._column == "end_rk": - self._data[self._row].end_rk = self._new - elif self._column == "concentration": - self._data[self._row].concentration = self._new - elif self._column == "eg": - self._data[self._row].eg = self._new - elif self._column == "em": - self._data[self._row].em = self._new - elif self._column == "ed": - self._data[self._row].ed = self._new - elif self._column == "rate": - self._data[self._row].rate = self._new + self._wps.get(self._index).begin_section = self._new + + +class SetEndCommand(QUndoCommand): + def __init__(self, wps, index, new_value): + QUndoCommand.__init__(self) + + self._wps = wps + self._index = index + self._old = self._wps.get(self._index).end_section + self._new = new_value + + def undo(self): + self._wps.get(self._index).end_section = self._old + + def redo(self): + self._wps.get(self._index).end_section = self._new + + +class SetEdgeCommand(QUndoCommand): + def __init__(self, wps, index, edge): + QUndoCommand.__init__(self) + + self._wps = wps + self._index = index + self._old = self._wps.get(self._index).reach + self._new = edge + + def undo(self): + self._wps.get(self._index).reach = self._old + + def redo(self): + self._wps.get(self._index).reach = self._new class AddCommand(QUndoCommand): - def __init__(self, data, ics_spec, index): + def __init__(self, data, type, wps_spec, index): QUndoCommand.__init__(self) self._data = data - self._ics_spec = ics_spec + self._type = type + self._wps_spec = wps_spec self._index = index self._new = None @@ -169,22 +144,22 @@ class AddCommand(QUndoCommand): def redo(self): if self._new is None: - self._new = self._data.new(self._index) + self._new = self._data.new(self._index, self._type) else: self._data.insert(self._index, self._new) class DelCommand(QUndoCommand): - def __init__(self, data, ics_spec, rows): + def __init__(self, data, wps_spec, rows): QUndoCommand.__init__(self) self._data = data - self._ics_spec = ics_spec + self._wps_spec = wps_spec self._rows = rows self._ic = [] for row in rows: - self._ic.append((row, self._ics_spec[row])) + self._ic.append((row, self._wps_spec[row])) self._ic.sort() def undo(self): diff --git a/src/View/WeatherParameters/Window.py b/src/View/WeatherParameters/Window.py index 4aa0c7ab..4a721691 100644 --- a/src/View/WeatherParameters/Window.py +++ b/src/View/WeatherParameters/Window.py @@ -68,15 +68,14 @@ class WeatherParametersWindow(PamhyrWindow): _pamhyr_ui = "WeatherParameters" _pamhyr_name = "Weather parameters" - def __init__(self, data=None, study=None, config=None, parent=None): - self._data = [] - # self._data.append(data) + # self._data = [] + self._data = data trad = WeatherParametersTranslate() - self.weather_params = [trad.get_dict("weather_parameters")[k] for k in trad.get_dict("weather_parameters").keys()] - print(f"Weather parameters: {self.weather_params}") + self.weather_params = [trad.get_dict("weather_parameters")[k] for + k in trad.get_dict("weather_parameters").keys()] name = ( trad[self._pamhyr_name] + " - " + study.name @@ -92,14 +91,15 @@ class WeatherParametersWindow(PamhyrWindow): self._hash_data.append(data) - # self._ics_adists_lst = study.river.ic_adists - + self._wp_acronyms = [ + "AT", "SH", "GR", "RWS", "GT", "GFR", "ALB", "SC", "CCF" + ] self.setup_table() def setup_table(self): path_icons = os.path.join(self._get_ui_directory(), f"ressources") - table_default = self.find(QTableView, f"tableView") + self.table_default = self.find(QTableView, f"tableView") if self._study.is_editable(): editable_headers = self._trad.get_dict("table_headers") @@ -107,7 +107,7 @@ class WeatherParametersWindow(PamhyrWindow): editable_headers = [] self._table = WeatherParametersTableDefaultModel( - table_view=table_default, + table_view=self.table_default, table_headers=self._trad.get_dict("table_headers"), editable_headers=editable_headers, delegates={}, @@ -116,12 +116,15 @@ class WeatherParametersWindow(PamhyrWindow): trad=self._trad ) - table_default.setModel(self._table) - table_default.setSelectionBehavior(QAbstractItemView.SelectRows) - table_default.horizontalHeader().setSectionResizeMode( + self.table_default.setModel(self._table) + self.table_default.setSelectionBehavior(QAbstractItemView.SelectRows) + self.table_default.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch ) - table_default.setAlternatingRowColors(True) + self.table_default.setAlternatingRowColors(True) + self.table_default.selectionModel().selectionChanged.connect( + self.on_table_default_selection_changed + ) layout = self.find(QVBoxLayout, f"verticalLayout_1") toolBar = QToolBar() @@ -147,12 +150,14 @@ class WeatherParametersWindow(PamhyrWindow): self._delegate_reach = ComboBoxDelegate( trad=self._trad, data=self._study.river, + weather_param_lst=None, parent=self, mode="reaches" ) self._delegate_rk = ComboBoxDelegate( trad=self._trad, data=self._study.river, + weather_param_lst=self._study.river.weather_parameters, parent=self, mode="rk" ) @@ -171,7 +176,7 @@ class WeatherParametersWindow(PamhyrWindow): "start_rk": self._delegate_rk, "end_rk": self._delegate_rk }, - data=self._data, + data=self._study.river.weather_parameters, undo=self._undo_stack, trad=self._trad, river=self._study.river @@ -195,6 +200,25 @@ class WeatherParametersWindow(PamhyrWindow): ) self.table_spec.scrollTo(index) + def on_table_default_selection_changed(self, selected, deselected): + rows = self.table_default.selectionModel().selectedRows() + if len(rows) == 0: + self._selected_default_row_index = None + return None + + self._selected_default_row_index = rows[0].row() + + self._table_spec.update_tab_spec( + type=self._wp_acronyms[ + self._selected_default_row_index + ] + ) + # self._data = self._study.river.weather_parameters + # self._delegate_rk.weather_param_lst = ( + # self._study.river.weather_parameters) + + return self._selected_default_row_index + def index_selected_row(self): # table = self.find(QTableView, f"tableView") table = self.table_spec @@ -208,7 +232,7 @@ class WeatherParametersWindow(PamhyrWindow): def index_selected_rows(self): # table = self.find(QTableView, f"tableView") - table = self.table_spec + table = self.table_default return list( # Delete duplicate set( @@ -301,10 +325,11 @@ class WeatherParametersWindow(PamhyrWindow): def add(self): rows = self.index_selected_rows() - if len(self._data[0]._data) == 0 or len(rows) == 0: - self._table_spec.add(0) - else: - self._table_spec.add(rows[0]) + if rows is not None and len(rows) > 0: + if len(self._data.lst) == 0 or len(rows) == 0: + self._table_spec.add(0) + else: + self._table_spec.add(rows[0]) def delete(self): rows = self.index_selected_rows() diff --git a/src/View/WeatherParameters/translate.py b/src/View/WeatherParameters/translate.py index 4da9de4f..ca72e52d 100644 --- a/src/View/WeatherParameters/translate.py +++ b/src/View/WeatherParameters/translate.py @@ -38,13 +38,27 @@ class WeatherParametersTranslate(MainTranslate): } self._sub_dict["table_headers_spec"] = { - "name": self._dict["name"], "reach": self._dict["reach"], "start_rk": _translate("Unit", "Start_RK (m)"), "end_rk": _translate("Unit", "End_RK (m)"), - "value": self._dict["value"] } self._sub_dict["weather_parameters"] = { - "air_temperature": _translate("WeatherParameters", "Air temperature") - } \ No newline at end of file + "air_temperature": _translate("WeatherParameters", + "Air Temperature (°C)"), + "specific_humidity": _translate("WeatherParameters", + "Specific Humidity (%)"), + "global_radiation": _translate("WeatherParameters", + "Global Radiation (W/m²)"), + "reference_wind_speed": _translate("WeatherParameters", + "Reference Wind Speed (m/s)"), + "surface_temperature": _translate("WeatherParameters", + "Groundwater Temperature (°C)"), + "surface_flow_rate": _translate("WeatherParameters", + "Groundwater Flow Rate (m³/s)"), + "albedo": _translate("WeatherParameters", "Albedo"), + "shading_coefficient": _translate("WeatherParameters", + "Shading Coefficient"), + "cloud_cover_fraction": _translate("WeatherParameters", + "Cloud Cover Fraction"), + }