mirror of
https://gitlab.com/pamhyr/pamhyr2.git
synced 2026-10-30 03:20:41 +01:00
Merge: branch 'master' into 'scenarios'.
This commit is contained in:
commit
74ab3aa522
312 files changed
+82851
-9744
No files matched your search
@@ -0,0 +1,52 @@
|
||||
# Adists.py -- Pamhyr study checkers
|
||||
# Copyright (C) 2023-2024 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 time
|
||||
import logging
|
||||
from functools import reduce
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
from Modules import Modules
|
||||
from Checker.Checker import AbstractModelChecker, STATUS
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class AdistsOutputRKChecker(AbstractModelChecker):
|
||||
def __init__(self):
|
||||
super(AdistsOutputRKChecker, self).__init__()
|
||||
|
||||
self._name = _translate("Checker", "AdisTS output RK checker")
|
||||
self._description = _translate(
|
||||
"Checker", "Check output RK"
|
||||
)
|
||||
self._modules = Modules.OUTPUT_RK
|
||||
|
||||
def run(self, study):
|
||||
ok = True
|
||||
nerror = 0
|
||||
self._summary = "ok"
|
||||
self._status = STATUS.OK
|
||||
|
||||
if not self.basic_check(study):
|
||||
return False
|
||||
|
||||
return ok
|
||||
+1
-1
@@ -395,7 +395,7 @@ class MeshingWithMageMailleurTT(AMeshingTool):
|
||||
logger.info(
|
||||
f"! {self._exe_path()} " +
|
||||
f"{st_file} {m_file} " +
|
||||
f"update_kp " +
|
||||
f"update_rk " +
|
||||
f"{str(step)} " +
|
||||
f"{limites[0]} {limites[1]} " +
|
||||
f"{directrices[0]} {directrices[1]} " +
|
||||
|
||||
@@ -345,7 +345,7 @@ class BoundaryCondition(SQLSubModel):
|
||||
|
||||
bc.node = None
|
||||
if node != -1:
|
||||
bc.node = next(filter(lambda n: n.id == node, nodes))
|
||||
bc.node = next(filter(lambda n: n.id == node, nodes), None)
|
||||
|
||||
data["bc"] = bc
|
||||
bc._data = Data._db_load(execute, data=data)
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
# BoundaryConditionsAdisTS.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 (
|
||||
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
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class BoundaryConditionAdisTS(SQLSubModel):
|
||||
_sub_classes = []
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int = -1,
|
||||
pollutant: int = -1, status=None):
|
||||
super(BoundaryConditionAdisTS, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = BoundaryConditionAdisTS._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
self._type = ""
|
||||
self._node = None
|
||||
self._pollutant = pollutant
|
||||
self._data = []
|
||||
self._header = []
|
||||
self._types = [self.time_convert, float]
|
||||
|
||||
BoundaryConditionAdisTS._id_cnt = max(
|
||||
BoundaryConditionAdisTS._id_cnt + 1, self.id)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE boundary_condition_adists(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
pollutant INTEGER NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
node INTEGER,
|
||||
FOREIGN KEY(pollutant) REFERENCES Pollutants(id),
|
||||
FOREIGN KEY(node) REFERENCES river_node(id)
|
||||
)
|
||||
""")
|
||||
|
||||
execute("""
|
||||
CREATE TABLE boundary_condition_data_adists(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
data0 TEXT NOT NULL,
|
||||
data1 TEXT NOT NULL,
|
||||
bc INTEGER,
|
||||
FOREIGN KEY(bc) REFERENCES boundary_condition_adists(id)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
|
||||
table = execute(
|
||||
"SELECT id, pollutant, type, node " +
|
||||
"FROM boundary_condition_adists"
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
bc = cls(
|
||||
id=row[0],
|
||||
pollutant=row[1],
|
||||
status=data['status']
|
||||
)
|
||||
|
||||
bc.type = row[2]
|
||||
|
||||
bc.node = None
|
||||
if row[3] != -1:
|
||||
tmp = next(filter(
|
||||
lambda n: n.id == row[3], data["nodes"]),
|
||||
None)
|
||||
if tmp is not None:
|
||||
bc.node = tmp.id
|
||||
else:
|
||||
bc.node = -1
|
||||
|
||||
values = execute(
|
||||
"SELECT data0, data1 FROM " +
|
||||
"boundary_condition_data_adists " +
|
||||
f"WHERE bc = '{bc.id}'"
|
||||
)
|
||||
|
||||
# Write data
|
||||
for v in values:
|
||||
data0 = bc._types[0](v[0])
|
||||
data1 = bc._types[1](v[1])
|
||||
# Replace data at pos ind
|
||||
bc._data.append((data0, data1))
|
||||
|
||||
new.append(bc)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
|
||||
execute(f"DELETE FROM boundary_condition_adists WHERE id = {self.id}")
|
||||
execute(f"DELETE FROM boundary_condition_data_adists" +
|
||||
f" WHERE bc = {self.id}")
|
||||
|
||||
node = -1
|
||||
if self._node is not None:
|
||||
node = self._node
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"boundary_condition_adists(id, pollutant, type, node) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, {self._pollutant}, " +
|
||||
f"'{self._db_format(self._type)}', {node}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
for d in self._data:
|
||||
data0 = self._db_format(str(d[0]))
|
||||
data1 = self._db_format(str(d[1]))
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"boundary_condition_data_adists(data0, data1, bc) " +
|
||||
f"VALUES ('{data0}', {data1}, {self.id})"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
return True
|
||||
|
||||
def __len__(self):
|
||||
return len(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 node(self):
|
||||
return self._node
|
||||
|
||||
@node.setter
|
||||
def node(self, node):
|
||||
self._node = node
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def header(self):
|
||||
return self._header.copy()
|
||||
|
||||
@header.setter
|
||||
def header(self, header):
|
||||
self._header = header
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def pollutant(self):
|
||||
return self._pollutant
|
||||
|
||||
@pollutant.setter
|
||||
def pollutant(self, pollutant):
|
||||
self._pollutant = pollutant
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return self._type
|
||||
|
||||
@type.setter
|
||||
def type(self, type):
|
||||
self._type = type
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data.copy()
|
||||
|
||||
@property
|
||||
def _default_0(self):
|
||||
return self._types[0](0)
|
||||
|
||||
@property
|
||||
def _default_1(self):
|
||||
return self._types[1](0.0)
|
||||
|
||||
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 (new_0, new_1)
|
||||
|
||||
def add(self, index: int):
|
||||
value = (self._default_0, self._default_1)
|
||||
self._data.insert(index, value)
|
||||
self._status.modified()
|
||||
return value
|
||||
|
||||
def insert(self, index: int, value):
|
||||
self._data.insert(index, value)
|
||||
self._status.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._status.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._status.modified()
|
||||
|
||||
def index(self, bc):
|
||||
self._data.index(bc)
|
||||
|
||||
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 = list(self._data[index])
|
||||
v[column] = self._types[column](value)
|
||||
self._data[index] = tuple(v)
|
||||
self._status.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)
|
||||
@@ -0,0 +1,67 @@
|
||||
# BoundaryConditionsAdisTSList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 -*-
|
||||
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
from Model.BoundaryConditionsAdisTS.BoundaryConditionAdisTS \
|
||||
import BoundaryConditionAdisTS
|
||||
|
||||
|
||||
class BoundaryConditionsAdisTSList(PamhyrModelList):
|
||||
_sub_classes = [
|
||||
BoundaryConditionAdisTS,
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = cls(status=data['status'])
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
new._lst = BoundaryConditionAdisTS._db_load(
|
||||
execute, data
|
||||
)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute("DELETE FROM boundary_condition_adists")
|
||||
execute("DELETE FROM boundary_condition_data_adists")
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
for bc in self._lst:
|
||||
bc._db_save(execute, data=data)
|
||||
|
||||
return True
|
||||
|
||||
def new(self, index, pollutant):
|
||||
n = BoundaryConditionAdisTS(pollutant=pollutant, status=self._status)
|
||||
self._lst.insert(index, n)
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
@property
|
||||
def BCs_AdisTS_List(self):
|
||||
return self.lst
|
||||
@@ -0,0 +1,196 @@
|
||||
# D90AdisTS.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 functools import reduce
|
||||
|
||||
from tools import trace, timer, old_pamhyr_date_to_timestamp
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
from Model.D90AdisTS.D90AdisTSSpec import D90AdisTSSpec
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class D90AdisTS(SQLSubModel):
|
||||
_sub_classes = [
|
||||
D90AdisTSSpec,
|
||||
]
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "default",
|
||||
status=None):
|
||||
super(D90AdisTS, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = D90AdisTS._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
self._name = name
|
||||
self._d90 = None
|
||||
self._enabled = True
|
||||
self._data = []
|
||||
|
||||
D90AdisTS._id_cnt = max(
|
||||
D90AdisTS._id_cnt + 1,
|
||||
self.id
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE d90_adists(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
d90 REAL NOT NULL,
|
||||
enabled BOOLEAN NOT NULL
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == 0 and minor < 1:
|
||||
if int(release) < 6:
|
||||
cls._db_create(execute)
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
|
||||
table = execute(
|
||||
"SELECT id, name, d90, enabled " +
|
||||
"FROM d90_adists"
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
d90_id = row[0]
|
||||
name = row[1]
|
||||
d90 = row[2]
|
||||
enabled = (row[3] == 1)
|
||||
|
||||
D90 = cls(
|
||||
id=d90_id,
|
||||
name=name,
|
||||
status=data['status']
|
||||
)
|
||||
|
||||
D90.d90 = d90
|
||||
D90.enabled = enabled
|
||||
|
||||
data['d90_default_id'] = d90_id
|
||||
D90._data = D90AdisTSSpec._db_load(execute, data)
|
||||
|
||||
new.append(D90)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute(f"DELETE FROM d90_adists WHERE id = {self.id}")
|
||||
|
||||
d90 = -1.
|
||||
if self.d90 is not None:
|
||||
d90 = self.d90
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"d90_adists(" +
|
||||
"id, name, d90, enabled" +
|
||||
") " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, '{self._db_format(self._name)}', " +
|
||||
f"{d90}, {self._enabled}" +
|
||||
")"
|
||||
)
|
||||
|
||||
execute(sql)
|
||||
|
||||
data['d90_default_id'] = self.id
|
||||
execute(
|
||||
"DELETE FROM d90_spec " +
|
||||
f"WHERE d90_default = {self.id}"
|
||||
)
|
||||
|
||||
for d90_spec in self._data:
|
||||
d90_spec._db_save(execute, data)
|
||||
|
||||
return True
|
||||
|
||||
def __len__(self):
|
||||
return len(self._data)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def d90(self):
|
||||
return self._d90
|
||||
|
||||
@d90.setter
|
||||
def d90(self, d90):
|
||||
self._d90 = d90
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
return self._enabled
|
||||
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self._status.modified()
|
||||
|
||||
def new(self, index):
|
||||
n = D90AdisTSSpec(status=self._status)
|
||||
self._data.insert(index, n)
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
def delete(self, data):
|
||||
self._data = list(
|
||||
filter(
|
||||
lambda x: x not in data,
|
||||
self._data
|
||||
)
|
||||
)
|
||||
self._status.modified()
|
||||
|
||||
def delete_i(self, indexes):
|
||||
for ind in indexes:
|
||||
del self._data[ind]
|
||||
self._status.modified()
|
||||
|
||||
def insert(self, index, data):
|
||||
self._data.insert(index, data)
|
||||
self._status.modified()
|
||||
@@ -0,0 +1,63 @@
|
||||
# D90AdisTSList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 -*-
|
||||
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.D90AdisTS.D90AdisTS import D90AdisTS
|
||||
|
||||
|
||||
class D90AdisTSList(PamhyrModelList):
|
||||
_sub_classes = [
|
||||
D90AdisTS,
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = cls(status=data['status'])
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
new._lst = D90AdisTS._db_load(
|
||||
execute, data
|
||||
)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute("DELETE FROM d90_adists")
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
for d90 in self._lst:
|
||||
d90._db_save(execute, data=data)
|
||||
|
||||
return True
|
||||
|
||||
def new(self, index):
|
||||
n = D90AdisTS(status=self._status)
|
||||
self._lst.insert(index, n)
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
@property
|
||||
def D90_AdisTS_List(self):
|
||||
return self.lst
|
||||
@@ -0,0 +1,193 @@
|
||||
# D90AdisTSSpec.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 trace, timer
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class D90AdisTSSpec(SQLSubModel):
|
||||
_sub_classes = [
|
||||
]
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None):
|
||||
super(D90AdisTSSpec, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = D90AdisTSSpec._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
self._name_section = name
|
||||
self._reach = None
|
||||
self._start_rk = None
|
||||
self._end_rk = None
|
||||
self._d90 = None
|
||||
self._enabled = True
|
||||
|
||||
D90AdisTSSpec._id_cnt = max(D90AdisTSSpec._id_cnt + 1, self.id)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE d90_spec(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
d90_default INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
reach INTEGER NOT NULL,
|
||||
start_rk REAL NOT NULL,
|
||||
end_rk REAL NOT NULL,
|
||||
d90 REAL NOT NULL,
|
||||
enabled BOOLEAN NOT NULL,
|
||||
FOREIGN KEY(d90_default) REFERENCES d90_adists(id),
|
||||
FOREIGN KEY(reach) REFERENCES river_reach(id)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == 0 and minor < 1:
|
||||
if int(release) < 6:
|
||||
cls._db_create(execute)
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
|
||||
table = execute(
|
||||
"SELECT id, d90_default, name, reach, start_rk, end_rk, " +
|
||||
"d90, enabled " +
|
||||
"FROM d90_spec " +
|
||||
f"WHERE d90_default = {data['d90_default_id']} "
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
id = row[0]
|
||||
name = row[2]
|
||||
reach = row[3]
|
||||
start_rk = row[4]
|
||||
end_rk = row[5]
|
||||
d90 = row[6]
|
||||
enabled = (row[7] == 1)
|
||||
|
||||
new_spec = cls(
|
||||
id=id,
|
||||
name=name,
|
||||
status=data['status']
|
||||
)
|
||||
|
||||
new_spec.reach = reach
|
||||
new_spec.start_rk = start_rk
|
||||
new_spec.end_rk = end_rk
|
||||
new_spec.d90 = d90
|
||||
new_spec.enabled = enabled
|
||||
|
||||
new.append(new_spec)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
d90_default = data['d90_default_id']
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"d90_spec(id, d90_default, name, reach, " +
|
||||
"start_rk, end_rk, d90, enabled) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, " +
|
||||
f"{d90_default}, " +
|
||||
f"'{self._db_format(self._name_section)}', " +
|
||||
f"{self._reach}, " +
|
||||
f"{self._start_rk}, " +
|
||||
f"{self._end_rk}, " +
|
||||
f"{self._d90}, " +
|
||||
f"{self._enabled}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name_section
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name_section = name
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def reach(self):
|
||||
return self._reach
|
||||
|
||||
@reach.setter
|
||||
def reach(self, reach):
|
||||
self._reach = reach
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def start_rk(self):
|
||||
return self._start_rk
|
||||
|
||||
@start_rk.setter
|
||||
def start_rk(self, start_rk):
|
||||
self._start_rk = start_rk
|
||||
self._status.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._status.modified()
|
||||
|
||||
@property
|
||||
def d90(self):
|
||||
return self._d90
|
||||
|
||||
@d90.setter
|
||||
def d90(self, d90):
|
||||
self._d90 = d90
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
return self._enabled
|
||||
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self._status.modified()
|
||||
@@ -0,0 +1,253 @@
|
||||
# DIFAdisTS.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 functools import reduce
|
||||
|
||||
from tools import trace, timer, old_pamhyr_date_to_timestamp
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
from Model.DIFAdisTS.DIFAdisTSSpec import DIFAdisTSSpec
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class DIFAdisTS(SQLSubModel):
|
||||
_sub_classes = [
|
||||
DIFAdisTSSpec,
|
||||
]
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "default",
|
||||
status=None):
|
||||
super(DIFAdisTS, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = DIFAdisTS._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
self._name = name
|
||||
self._method = None
|
||||
self._dif = None
|
||||
self._b = None
|
||||
self._c = None
|
||||
self._enabled = True
|
||||
self._types = ["iwasa", "fisher", "elder", "constante", "generique"]
|
||||
self._data = []
|
||||
|
||||
DIFAdisTS._id_cnt = max(
|
||||
DIFAdisTS._id_cnt + 1,
|
||||
self.id
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE dif_adists(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
method TEXT NOT NULL,
|
||||
dif REAL NOT NULL,
|
||||
b REAL,
|
||||
c REAL,
|
||||
enabled BOOLEAN NOT NULL
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == 0 and minor < 1:
|
||||
if int(release) < 6:
|
||||
cls._db_create(execute)
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
|
||||
table = execute(
|
||||
"SELECT id, name, method, dif, b, c, enabled " +
|
||||
"FROM dif_adists"
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
dif_id = row[0]
|
||||
name = row[1]
|
||||
method = row[2]
|
||||
dif = row[3]
|
||||
b = row[4]
|
||||
c = row[5]
|
||||
enabled = (row[6] == 1)
|
||||
|
||||
DIF = cls(
|
||||
id=dif_id,
|
||||
name=name,
|
||||
status=data['status']
|
||||
)
|
||||
|
||||
DIF.method = method
|
||||
DIF.dif = dif
|
||||
DIF.b = b
|
||||
DIF.c = c
|
||||
DIF.enabled = enabled
|
||||
|
||||
data['dif_default_id'] = dif_id
|
||||
DIF._data = DIFAdisTSSpec._db_load(execute, data)
|
||||
|
||||
new.append(DIF)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute(f"DELETE FROM dif_adists WHERE id = {self.id}")
|
||||
|
||||
method = ""
|
||||
if self.method is not None:
|
||||
method = self.method
|
||||
|
||||
dif = -1.
|
||||
if self.dif is not None:
|
||||
dif = self.dif
|
||||
|
||||
b = -1.
|
||||
if self.b is not None:
|
||||
b = self.b
|
||||
|
||||
c = -1.
|
||||
if self.dif is not None:
|
||||
c = self.c
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"dif_adists(" +
|
||||
"id, name, method, dif, b, c, enabled" +
|
||||
") " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, '{self._db_format(self._name)}', " +
|
||||
f"'{self._db_format(self._method)}', " +
|
||||
f"{dif}, {b}, {c}, {self._enabled}" +
|
||||
")"
|
||||
)
|
||||
|
||||
execute(sql)
|
||||
|
||||
data['dif_default_id'] = self.id
|
||||
execute(
|
||||
"DELETE FROM dif_spec " +
|
||||
f"WHERE dif_default = {self.id}"
|
||||
)
|
||||
|
||||
for dif_spec in self._data:
|
||||
dif_spec._db_save(execute, data)
|
||||
|
||||
return True
|
||||
|
||||
def __len__(self):
|
||||
return len(self._data)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def method(self):
|
||||
return self._method
|
||||
|
||||
@method.setter
|
||||
def method(self, method):
|
||||
self._method = method
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def types(self):
|
||||
return self._types
|
||||
|
||||
@property
|
||||
def dif(self):
|
||||
return self._dif
|
||||
|
||||
@dif.setter
|
||||
def dif(self, dif):
|
||||
self._dif = dif
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def b(self):
|
||||
return self._b
|
||||
|
||||
@b.setter
|
||||
def b(self, b):
|
||||
self._b = b
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def c(self):
|
||||
return self._c
|
||||
|
||||
@c.setter
|
||||
def c(self, c):
|
||||
self._c = c
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
return self._enabled
|
||||
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self._status.modified()
|
||||
|
||||
def new(self, index):
|
||||
n = DIFAdisTSSpec(status=self._status)
|
||||
self._data.insert(index, n)
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
def delete(self, data):
|
||||
self._data = list(
|
||||
filter(
|
||||
lambda x: x not in data,
|
||||
self._data
|
||||
)
|
||||
)
|
||||
self._status.modified()
|
||||
|
||||
def delete_i(self, indexes):
|
||||
for ind in indexes:
|
||||
del self._data[ind]
|
||||
self._status.modified()
|
||||
|
||||
def insert(self, index, data):
|
||||
self._data.insert(index, data)
|
||||
self._status.modified()
|
||||
@@ -0,0 +1,63 @@
|
||||
# DIFAdisTSList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 -*-
|
||||
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.DIFAdisTS.DIFAdisTS import DIFAdisTS
|
||||
|
||||
|
||||
class DIFAdisTSList(PamhyrModelList):
|
||||
_sub_classes = [
|
||||
DIFAdisTS,
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = cls(status=data['status'])
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
new._lst = DIFAdisTS._db_load(
|
||||
execute, data
|
||||
)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute("DELETE FROM dif_adists")
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
for dif in self._lst:
|
||||
dif._db_save(execute, data=data)
|
||||
|
||||
return True
|
||||
|
||||
def new(self, index):
|
||||
n = DIFAdisTS(status=self._status)
|
||||
self._lst.insert(index, n)
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
@property
|
||||
def DIF_AdisTS_List(self):
|
||||
return self.lst
|
||||
@@ -0,0 +1,221 @@
|
||||
# DIFAdisTSSpec.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 trace, timer
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class DIFAdisTSSpec(SQLSubModel):
|
||||
_sub_classes = [
|
||||
]
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int = -1, method: str = "",
|
||||
status=None):
|
||||
super(DIFAdisTSSpec, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = DIFAdisTSSpec._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
self._method = method
|
||||
self._reach = None
|
||||
self._start_rk = None
|
||||
self._end_rk = None
|
||||
self._dif = None
|
||||
self._b = None
|
||||
self._c = None
|
||||
self._enabled = True
|
||||
|
||||
DIFAdisTSSpec._id_cnt = max(DIFAdisTSSpec._id_cnt + 1, self.id)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE dif_spec(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
dif_default INTEGER NOT NULL,
|
||||
method TEXT NOT NULL,
|
||||
reach INTEGER NOT NULL,
|
||||
start_rk REAL NOT NULL,
|
||||
end_rk REAL NOT NULL,
|
||||
dif REAL NOT NULL,
|
||||
b REAL,
|
||||
c REAL,
|
||||
enabled BOOLEAN NOT NULL,
|
||||
FOREIGN KEY(dif_default) REFERENCES dif_adists(id),
|
||||
FOREIGN KEY(reach) REFERENCES river_reach(id)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == 0 and minor < 1:
|
||||
if int(release) < 6:
|
||||
cls._db_create(execute)
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
|
||||
table = execute(
|
||||
"SELECT id, dif_default, method, reach, start_rk, end_rk, " +
|
||||
"dif, b, c, enabled " +
|
||||
"FROM dif_spec " +
|
||||
f"WHERE dif_default = {data['dif_default_id']} "
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
id = row[0]
|
||||
method = row[2]
|
||||
reach = row[3]
|
||||
start_rk = row[4]
|
||||
end_rk = row[5]
|
||||
dif = row[6]
|
||||
b = row[7]
|
||||
c = row[8]
|
||||
enabled = (row[9] == 1)
|
||||
|
||||
new_spec = cls(
|
||||
id=id,
|
||||
method=method,
|
||||
status=data['status']
|
||||
)
|
||||
|
||||
new_spec.reach = reach
|
||||
new_spec.start_rk = start_rk
|
||||
new_spec.end_rk = end_rk
|
||||
new_spec.dif = dif
|
||||
new_spec.b = b
|
||||
new_spec.c = c
|
||||
new_spec.enabled = enabled
|
||||
|
||||
new.append(new_spec)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
dif_default = data['dif_default_id']
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"dif_spec(id, dif_default, method, reach, " +
|
||||
"start_rk, end_rk, dif, b, c, enabled) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, " +
|
||||
f"{dif_default}, " +
|
||||
f"'{self._db_format(self._method)}', " +
|
||||
f"{self._reach}, " +
|
||||
f"{self._start_rk}, " +
|
||||
f"{self._end_rk}, " +
|
||||
f"{self._dif}, " +
|
||||
f"{self._b}, " +
|
||||
f"{self._c}, " +
|
||||
f"{self._enabled}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def method(self):
|
||||
return self._method
|
||||
|
||||
@method.setter
|
||||
def method(self, method):
|
||||
self._method = method
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def reach(self):
|
||||
return self._reach
|
||||
|
||||
@reach.setter
|
||||
def reach(self, reach):
|
||||
self._reach = reach
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def start_rk(self):
|
||||
return self._start_rk
|
||||
|
||||
@start_rk.setter
|
||||
def start_rk(self, start_rk):
|
||||
self._start_rk = start_rk
|
||||
self._status.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._status.modified()
|
||||
|
||||
@property
|
||||
def dif(self):
|
||||
return self._dif
|
||||
|
||||
@dif.setter
|
||||
def dif(self, dif):
|
||||
self._dif = dif
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def b(self):
|
||||
return self._b
|
||||
|
||||
@b.setter
|
||||
def b(self, b):
|
||||
self._b = b
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def c(self):
|
||||
return self._c
|
||||
|
||||
@c.setter
|
||||
def c(self, c):
|
||||
self._c = c
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
return self._enabled
|
||||
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self._status.modified()
|
||||
@@ -264,8 +264,7 @@ class Friction(SQLSubModel):
|
||||
|
||||
def has_coefficient(self):
|
||||
return (
|
||||
self._begin_strickler is not None and
|
||||
self._end_strickler is not None
|
||||
self._begin_strickler is not None
|
||||
)
|
||||
|
||||
def is_full_defined(self):
|
||||
@@ -324,7 +323,8 @@ class Friction(SQLSubModel):
|
||||
|
||||
@property
|
||||
def end_strickler(self):
|
||||
return self._end_strickler
|
||||
# return self._end_strickler
|
||||
return self._begin_strickler
|
||||
|
||||
@end_strickler.setter
|
||||
def end_strickler(self, strickler):
|
||||
@@ -334,13 +334,17 @@ class Friction(SQLSubModel):
|
||||
def get_friction(self, rk):
|
||||
if not self.contains_rk(rk):
|
||||
return None
|
||||
minor = interp(rk,
|
||||
[self.begin_rk, self.end_rk],
|
||||
[self.begin_strickler.minor,
|
||||
self.end_strickler.minor])
|
||||
medium = interp(rk,
|
||||
[self.begin_rk, self.end_rk],
|
||||
[self.begin_strickler.medium,
|
||||
self.end_strickler.medium])
|
||||
|
||||
minor = self.begin_strickler.minor
|
||||
medium = self.begin_strickler.medium
|
||||
|
||||
# minor = interp(rk,
|
||||
# [self.begin_rk, self.end_rk],
|
||||
# [self.begin_strickler.minor,
|
||||
# self.end_strickler.minor])
|
||||
# medium = interp(rk,
|
||||
# [self.begin_rk, self.end_rk],
|
||||
# [self.begin_strickler.medium,
|
||||
# self.end_strickler.medium])
|
||||
|
||||
return minor, medium
|
||||
@@ -20,6 +20,7 @@ import logging
|
||||
import numpy as np
|
||||
from typing import List
|
||||
from functools import reduce
|
||||
from dataclasses import dataclass
|
||||
|
||||
from tools import timer, flatten
|
||||
from shapely import geometry
|
||||
@@ -34,6 +35,13 @@ from Model.Geometry.Vector_1d import Vector1d
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
@dataclass
|
||||
class Tabulation:
|
||||
z: np.array([])
|
||||
A: np.array([])
|
||||
L: np.array([])
|
||||
|
||||
|
||||
class ProfileXYZ(Profile, SQLSubModel):
|
||||
_sub_classes = [
|
||||
PointXYZ,
|
||||
@@ -72,6 +80,14 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self.tab = Tabulation([], [], [])
|
||||
self.tab_up_to_date = False
|
||||
self.time_z = 0.0
|
||||
self.time_A = 0.0
|
||||
self.time_l = 0.0
|
||||
self._station = []
|
||||
self.station_up_to_date = False
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
@@ -252,8 +268,7 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
filter(
|
||||
lambda s: s.id == sl,
|
||||
data["sediment_layers_list"].sediment_layers
|
||||
)
|
||||
)
|
||||
), None)
|
||||
|
||||
data["profile"] = new
|
||||
new._points = PointXYZ._db_load(execute, data.copy())
|
||||
@@ -365,7 +380,13 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
return point
|
||||
|
||||
def display_name(self):
|
||||
return f"{self.name} ({self.rk:.4f})"
|
||||
name = ""
|
||||
if self.name != "":
|
||||
name += f"{self.name} "
|
||||
|
||||
name += f"({self.rk})"
|
||||
|
||||
return name
|
||||
|
||||
def x(self):
|
||||
return [point.x for point in self.points]
|
||||
@@ -409,7 +430,10 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
for point in list_points:
|
||||
pt = PointXYZ(*point, profile=self, status=self._status)
|
||||
self.points.append(pt)
|
||||
|
||||
self.modified()
|
||||
self.tab_up_to_date = False
|
||||
self.station_up_to_date = False
|
||||
|
||||
def get_point_i(self, index: int) -> PointXYZ:
|
||||
"""Get point at index.
|
||||
@@ -470,7 +494,10 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
"""
|
||||
point_xyz = PointXYZ(0., 0., 0., profile=self, status=self._status)
|
||||
self.points.append(point_xyz)
|
||||
|
||||
self.modified()
|
||||
self.tab_up_to_date = False
|
||||
self.station_up_to_date = False
|
||||
|
||||
def insert(self, index: int):
|
||||
"""Insert a new point at index.
|
||||
@@ -483,7 +510,11 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
"""
|
||||
point = PointXYZ(0., 0., 0., profile=self, status=self._status)
|
||||
self.points.insert(index, point)
|
||||
|
||||
self.modified()
|
||||
self.tab_up_to_date = False
|
||||
self.station_up_to_date = False
|
||||
|
||||
return point
|
||||
|
||||
def filter_isnan(self, lst):
|
||||
@@ -515,7 +546,7 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
|
||||
return abs(rg.dist(rd))
|
||||
|
||||
def wet_width(self, z):
|
||||
def compute_wet_width(self, z):
|
||||
start, end = self.get_all_water_limits_ac(z)
|
||||
|
||||
if len(start) == 0:
|
||||
@@ -526,6 +557,17 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
length += abs(s - e)
|
||||
return length
|
||||
|
||||
def wet_width(self, z):
|
||||
if self.tab_up_to_date:
|
||||
if z > self.tab.z[-1]:
|
||||
return self.tab.L[-1]
|
||||
elif z < self.tab.z[0]:
|
||||
return 0.0
|
||||
else:
|
||||
return np.interp(z, self.tab.z, self.tab.L)
|
||||
else:
|
||||
return self.compute_wet_width(z)
|
||||
|
||||
def wet_perimeter(self, z):
|
||||
lines = self.wet_lines(z)
|
||||
|
||||
@@ -537,17 +579,46 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
length += line.length
|
||||
return length
|
||||
|
||||
def wet_area(self, z):
|
||||
lines = self.wet_lines(z)
|
||||
|
||||
if lines is None:
|
||||
return 0
|
||||
|
||||
def compute_wet_area(self, z):
|
||||
area = 0.0
|
||||
for line in lines:
|
||||
if len(line.coords) > 2:
|
||||
poly = geometry.Polygon(line)
|
||||
area += poly.area
|
||||
if len(self.tab.L) > 0:
|
||||
if z < self.tab.z[0]:
|
||||
return 0.0
|
||||
i = np.searchsorted([z], self.tab.z, side='right')[0]
|
||||
for j in range(i-1):
|
||||
area += (self.tab.L[j] + self.tab.L[j+1]) * (
|
||||
self.tab.z[j+1] - self.tab.z[j]) / 2.0
|
||||
area += (self.tab.L[i-1] + self.wet_width(z))
|
||||
else:
|
||||
lines = self.wet_lines(z)
|
||||
|
||||
if lines is None:
|
||||
return 0.0
|
||||
for line in lines:
|
||||
if len(line.coords) > 2:
|
||||
poly = geometry.Polygon(line)
|
||||
area += poly.area
|
||||
return area
|
||||
|
||||
def wet_area(self, z):
|
||||
if self.tab_up_to_date:
|
||||
if z > self.tab.z[-1]:
|
||||
return self.tab.A[-1] + self.tab.L[-1] * (z - self.tab.z[-1])
|
||||
elif z < self.tab.z[0]:
|
||||
return 0.0
|
||||
else:
|
||||
return np.interp(z, self.tab.z, self.tab.A)
|
||||
else:
|
||||
lines = self.wet_lines(z)
|
||||
|
||||
if lines is None:
|
||||
return 0.0
|
||||
|
||||
area = 0.0
|
||||
for line in lines:
|
||||
if len(line.coords) > 2:
|
||||
poly = geometry.Polygon(line)
|
||||
area += poly.area
|
||||
return area
|
||||
|
||||
def wet_radius(self, z):
|
||||
@@ -668,7 +739,7 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
return None
|
||||
|
||||
zz = list(map(lambda p: p.z, points))
|
||||
station = self._get_station(points)
|
||||
station = self.get_station()
|
||||
|
||||
start = []
|
||||
if points[0].z <= z:
|
||||
@@ -756,6 +827,19 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
|
||||
return pt_left, pt_right
|
||||
|
||||
def compute_tabulation(self):
|
||||
sorted_points = sorted(self._points, key=lambda p: p.z)
|
||||
self.tab.z = np.array([p.z for p in sorted_points], np.float64)
|
||||
self.tab.L = np.zeros(len(self.tab.z), np.float64)
|
||||
self.tab.A = np.zeros(len(self.tab.z), np.float64)
|
||||
for i in range(1, len(self.tab.z)):
|
||||
self.tab.L[i] = self.compute_wet_width(self.tab.z[i])
|
||||
dx = (self.tab.L[i] + self.tab.L[i-1])/2
|
||||
dz = self.tab.z[i] - self.tab.z[i-1]
|
||||
self.tab.A[i] = self.tab.A[i-1] + dz * dx
|
||||
|
||||
self.tab_up_to_date = True
|
||||
|
||||
def get_station(self):
|
||||
"""Projection of the points of the profile on a plane.
|
||||
|
||||
@@ -768,7 +852,12 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
if self.nb_points < 2:
|
||||
return [0.0]
|
||||
else:
|
||||
return self._get_station(self.points)
|
||||
if self.station_up_to_date:
|
||||
return self._station
|
||||
else:
|
||||
self._station = self._get_station(self.points)
|
||||
self.station_up_to_date = True
|
||||
return self._station
|
||||
|
||||
@timer
|
||||
def _get_station(self, points):
|
||||
|
||||
@@ -350,6 +350,9 @@ class Reach(SQLSubModel):
|
||||
|
||||
def inter_profiles_rk(self):
|
||||
profiles = sorted(self.profiles, key=lambda p: p.rk)
|
||||
if len(profiles) == 0:
|
||||
return []
|
||||
|
||||
first = profiles[0]
|
||||
last = profiles[-1]
|
||||
|
||||
@@ -613,8 +616,8 @@ class Reach(SQLSubModel):
|
||||
|
||||
try:
|
||||
list_profile, list_header = self.read_file_st(str(file_path_name))
|
||||
profile_header = ["num", "code1",
|
||||
"code2", "nb_point", "rk", "name"]
|
||||
profile_header = ["num", "code1", "code2",
|
||||
"nb_point", "rk", "name"]
|
||||
|
||||
if list_profile and list_header:
|
||||
for ind, profile in enumerate(list_profile):
|
||||
@@ -687,6 +690,15 @@ class Reach(SQLSubModel):
|
||||
line_is_header = True
|
||||
else:
|
||||
list_point_profile.append(line)
|
||||
elif len(line) > 4:
|
||||
x, y, z = line[:3]
|
||||
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[:3])
|
||||
else:
|
||||
pass
|
||||
|
||||
@@ -721,6 +733,9 @@ class Reach(SQLSubModel):
|
||||
cnt += 1
|
||||
|
||||
def get_incline(self):
|
||||
if len(self.profiles) == 0:
|
||||
return 0.0
|
||||
|
||||
first = self.profile(0)
|
||||
last = self.profile(len(self) - 1)
|
||||
|
||||
@@ -738,6 +753,9 @@ class Reach(SQLSubModel):
|
||||
|
||||
def get_incline_mean(self):
|
||||
profiles = self.profiles
|
||||
if len(profiles) == 0:
|
||||
return 0.0
|
||||
|
||||
previous = profiles[0]
|
||||
|
||||
incline_acc = 0
|
||||
@@ -761,6 +779,9 @@ class Reach(SQLSubModel):
|
||||
|
||||
def get_incline_median(self):
|
||||
profiles = self.profiles
|
||||
if len(profiles) == 0:
|
||||
return 0.0
|
||||
|
||||
previous = profiles[0]
|
||||
|
||||
incline_acc = []
|
||||
@@ -785,6 +806,9 @@ class Reach(SQLSubModel):
|
||||
|
||||
def get_incline_median_mean(self):
|
||||
profiles = self.profiles
|
||||
if len(profiles) == 0:
|
||||
return 0.0
|
||||
|
||||
previous = profiles[0]
|
||||
|
||||
incline_acc = []
|
||||
@@ -796,6 +820,9 @@ class Reach(SQLSubModel):
|
||||
z_last = profile.z_min()
|
||||
rk_last = profile.rk
|
||||
|
||||
if (rk_last - rk_first) == 0:
|
||||
continue
|
||||
|
||||
incline_acc += [
|
||||
(z_last - z_first)
|
||||
/
|
||||
|
||||
@@ -87,8 +87,8 @@ class TriangularWeir(BasicHS):
|
||||
self._data = [
|
||||
BHSValue(-1, "elevation", float, 1.0, status=status),
|
||||
BHSValue(-1, "loading_elevation", float, 9999.999, status=status),
|
||||
BHSValue(-1, "discharge_coefficient", float, 0.4, status=status),
|
||||
BHSValue(-1, "half-angle_tangent", float, 0.0, status=status),
|
||||
BHSValue(-1, "discharge_coefficient", float, 0.31, status=status),
|
||||
BHSValue(-1, "half-angle_tangent", float, 1.0, status=status),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -486,24 +486,11 @@ class InitialConditions(SQLSubModel):
|
||||
def get_discharge(self):
|
||||
return self._data_get("discharge")
|
||||
|
||||
def _sort_by_z_and_rk(self, profiles):
|
||||
profiles.sort(
|
||||
reverse=False,
|
||||
key=lambda p: p.rk
|
||||
)
|
||||
|
||||
first_z = profiles[0].z()
|
||||
last_z = profiles[-1].z()
|
||||
|
||||
if first_z > last_z:
|
||||
profiles.sort(
|
||||
reverse=True,
|
||||
key=lambda p: p.rk
|
||||
)
|
||||
|
||||
def generate_growing_constant_depth(self, height: float,
|
||||
compute_discharge: bool):
|
||||
profiles = self._reach.reach.profiles.copy()
|
||||
profiles.reverse()
|
||||
|
||||
previous_elevation = -99999.99
|
||||
|
||||
data_discharge = {}
|
||||
@@ -520,11 +507,15 @@ class InitialConditions(SQLSubModel):
|
||||
self._data = []
|
||||
for profile in reversed(profiles):
|
||||
width = profile.wet_width(profile.z_min() + height)
|
||||
area = profile.wet_area(profile.z_min() + height)
|
||||
radius = profile.wet_radius(profile.z_min() + height)
|
||||
frictions = self._reach.frictions.frictions
|
||||
strickler = None
|
||||
for f in frictions:
|
||||
if f.contains_rk(profile.rk):
|
||||
strickler = f.get_friction(profile.rk)[0]
|
||||
if frictions is not None:
|
||||
if len(frictions) >= 1:
|
||||
for f in frictions:
|
||||
if f.contains_rk(profile.rk):
|
||||
strickler = f.get_friction(profile.rk)[0]
|
||||
if strickler is None:
|
||||
strickler = 25.0
|
||||
|
||||
@@ -532,9 +523,12 @@ class InitialConditions(SQLSubModel):
|
||||
discharge = data_discharge[profile.rk]
|
||||
else:
|
||||
discharge = (
|
||||
((width * 0.8)
|
||||
* strickler
|
||||
* (height ** (5/3))
|
||||
# ((width * 0.8)
|
||||
# * strickler
|
||||
# * (height ** (5/3))
|
||||
# * (abs(incline) ** (0.5)))
|
||||
(area * strickler
|
||||
* (radius ** (2/3))
|
||||
* (abs(incline) ** (0.5)))
|
||||
)
|
||||
|
||||
@@ -560,6 +554,10 @@ class InitialConditions(SQLSubModel):
|
||||
|
||||
def generate_discharge(self, discharge: float, compute_height: bool):
|
||||
profiles = self._reach.reach.profiles.copy()
|
||||
if profiles is None:
|
||||
return None
|
||||
profiles.reverse()
|
||||
|
||||
previous_elevation = -99999.99
|
||||
|
||||
data_height = {}
|
||||
@@ -578,20 +576,25 @@ class InitialConditions(SQLSubModel):
|
||||
width = profile.width_approximation()
|
||||
frictions = self._reach.frictions.frictions
|
||||
strickler = None
|
||||
for f in frictions:
|
||||
if f.contains_rk(profile.rk):
|
||||
strickler = f.get_friction(profile.rk)[0]
|
||||
if frictions is not None:
|
||||
if len(frictions) >= 1:
|
||||
for f in frictions:
|
||||
if f.contains_rk(profile.rk):
|
||||
strickler = f.get_friction(profile.rk)[0]
|
||||
if strickler is None:
|
||||
strickler = 25.0
|
||||
|
||||
if not compute_height:
|
||||
height = data_height[profile.rk]
|
||||
else:
|
||||
height = (
|
||||
discharge
|
||||
/
|
||||
((width * 0.8) * strickler * (abs(incline) ** (0.5)))
|
||||
) ** (0.6)
|
||||
if abs(incline) <= 0:
|
||||
height = 0.0
|
||||
else:
|
||||
height = (
|
||||
discharge
|
||||
/
|
||||
((width * 0.8) * strickler * (abs(incline) ** (0.5)))
|
||||
) ** (0.6)
|
||||
|
||||
elevation = max(
|
||||
profile.z_min() + height,
|
||||
@@ -612,3 +615,36 @@ class InitialConditions(SQLSubModel):
|
||||
self._data.append(new)
|
||||
|
||||
self._data.reverse()
|
||||
|
||||
def generate_height(self,
|
||||
elevation1: float,
|
||||
elevation2: float,
|
||||
compute_discharge: bool,
|
||||
discharge: float):
|
||||
profiles = self._reach.reach.profiles.copy()
|
||||
upstream_rk = profiles[0].rk
|
||||
downstream_rk = profiles[-1].rk
|
||||
data_discharge = {}
|
||||
if not compute_discharge:
|
||||
if len(self._data) == 0:
|
||||
for profile in profiles:
|
||||
data_discharge[profile.rk] = 0.0
|
||||
else:
|
||||
for data in self._data:
|
||||
data_discharge[data["rk"]] = data["discharge"]
|
||||
|
||||
self._data = []
|
||||
for profile in profiles:
|
||||
|
||||
if not compute_discharge:
|
||||
d = data_discharge[profile.rk]
|
||||
else:
|
||||
d = discharge
|
||||
elevation = interp(profile.rk,
|
||||
[upstream_rk, downstream_rk],
|
||||
[elevation1, elevation2])
|
||||
new = Data(reach=self._reach, status=self._status)
|
||||
new["rk"] = profile.rk
|
||||
new["discharge"] = d
|
||||
new["elevation"] = elevation
|
||||
self._data.append(new)
|
||||
@@ -0,0 +1,268 @@
|
||||
# InitialConditionsAdisTS.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 functools import reduce
|
||||
|
||||
from tools import trace, timer, old_pamhyr_date_to_timestamp
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
from Model.InitialConditionsAdisTS.InitialConditionsAdisTSSpec \
|
||||
import ICAdisTSSpec
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class InitialConditionsAdisTS(SQLSubModel):
|
||||
_sub_classes = [
|
||||
ICAdisTSSpec,
|
||||
]
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "default",
|
||||
pollutant: int = -1, status=None):
|
||||
super(InitialConditionsAdisTS, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = InitialConditionsAdisTS._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
self._name = name
|
||||
self._pollutant = pollutant
|
||||
self._concentration = None
|
||||
self._eg = None
|
||||
self._em = None
|
||||
self._ed = None
|
||||
self._enabled = True
|
||||
self._data = []
|
||||
|
||||
InitialConditionsAdisTS._id_cnt = max(
|
||||
InitialConditionsAdisTS._id_cnt + 1,
|
||||
self.id
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE initial_conditions_adists(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
pollutant INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
concentration REAL NOT NULL,
|
||||
eg REAL NOT NULL,
|
||||
em REAL NOT NULL,
|
||||
ed REAL NOT NULL,
|
||||
enabled BOOLEAN NOT NULL,
|
||||
FOREIGN KEY(pollutant) REFERENCES Pollutants(id)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == 0 and minor < 1:
|
||||
if int(release) < 6:
|
||||
cls._db_create(execute)
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
|
||||
table = execute(
|
||||
"SELECT id, pollutant, name, concentration, eg, em, ed, " +
|
||||
"enabled " +
|
||||
"FROM initial_conditions_adists"
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
IC_id = row[0]
|
||||
pollutant = row[1]
|
||||
name = row[2]
|
||||
concentration = row[3]
|
||||
eg = row[4]
|
||||
em = row[5]
|
||||
ed = row[6]
|
||||
enabled = (row[7] == 1)
|
||||
|
||||
IC = cls(
|
||||
id=IC_id,
|
||||
name=name,
|
||||
status=data['status']
|
||||
)
|
||||
|
||||
IC.pollutant = pollutant
|
||||
IC.concentration = concentration
|
||||
IC.eg = eg
|
||||
IC.em = em
|
||||
IC.ed = ed
|
||||
IC.enabled = enabled
|
||||
|
||||
data['ic_default_id'] = IC_id
|
||||
IC._data = ICAdisTSSpec._db_load(execute, data)
|
||||
|
||||
new.append(IC)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute(f"DELETE FROM initial_conditions_adists WHERE id = {self.id}")
|
||||
|
||||
pollutant = -1
|
||||
if self.pollutant is not None:
|
||||
pollutant = self.pollutant
|
||||
|
||||
concentration = -1.
|
||||
if self.concentration is not None:
|
||||
concentration = self.concentration
|
||||
|
||||
eg = -1.
|
||||
if self.eg is not None:
|
||||
eg = self.eg
|
||||
|
||||
em = -1.
|
||||
if self.em is not None:
|
||||
em = self.em
|
||||
|
||||
ed = -1.
|
||||
if self.ed is not None:
|
||||
ed = self.ed
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"initial_conditions_adists(" +
|
||||
"id, pollutant, name, concentration, " +
|
||||
"eg, em, ed, enabled" +
|
||||
") " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, {pollutant}, '{self._db_format(self._name)}', " +
|
||||
f"{concentration}, {eg}, {em}, {ed}, {self._enabled}" +
|
||||
")"
|
||||
)
|
||||
|
||||
execute(sql)
|
||||
|
||||
data['ic_default_id'] = self.id
|
||||
execute(
|
||||
"DELETE FROM initial_conditions_spec " +
|
||||
f"WHERE ic_default = {self.id}"
|
||||
)
|
||||
|
||||
for ic_spec in self._data:
|
||||
ic_spec._db_save(execute, data)
|
||||
|
||||
return True
|
||||
|
||||
def __len__(self):
|
||||
return len(self._data)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def pollutant(self):
|
||||
return self._pollutant
|
||||
|
||||
@pollutant.setter
|
||||
def pollutant(self, pollutant):
|
||||
self._pollutant = pollutant
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def concentration(self):
|
||||
return self._concentration
|
||||
|
||||
@concentration.setter
|
||||
def concentration(self, concentration):
|
||||
self._concentration = concentration
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def eg(self):
|
||||
return self._eg
|
||||
|
||||
@eg.setter
|
||||
def eg(self, eg):
|
||||
self._eg = eg
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def em(self):
|
||||
return self._em
|
||||
|
||||
@em.setter
|
||||
def em(self, em):
|
||||
self._em = em
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def ed(self):
|
||||
return self._ed
|
||||
|
||||
@ed.setter
|
||||
def ed(self, ed):
|
||||
self._ed = ed
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
return self._enabled
|
||||
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self._status.modified()
|
||||
|
||||
def new(self, index):
|
||||
n = ICAdisTSSpec(status=self._status)
|
||||
self._data.insert(index, n)
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
def delete(self, data):
|
||||
self._data = list(
|
||||
filter(
|
||||
lambda x: x not in data,
|
||||
self._data
|
||||
)
|
||||
)
|
||||
self._status.modified()
|
||||
|
||||
def delete_i(self, indexes):
|
||||
for ind in indexes:
|
||||
del self._data[ind]
|
||||
self._status.modified()
|
||||
|
||||
def insert(self, index, data):
|
||||
self._data.insert(index, data)
|
||||
self._status.modified()
|
||||
@@ -0,0 +1,64 @@
|
||||
# InitialConditionsAdisTSList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 -*-
|
||||
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.InitialConditionsAdisTS.InitialConditionsAdisTS \
|
||||
import InitialConditionsAdisTS
|
||||
|
||||
|
||||
class InitialConditionsAdisTSList(PamhyrModelList):
|
||||
_sub_classes = [
|
||||
InitialConditionsAdisTS,
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = cls(status=data['status'])
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
new._lst = InitialConditionsAdisTS._db_load(
|
||||
execute, data
|
||||
)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute("DELETE FROM initial_conditions_adists")
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
for ic in self._lst:
|
||||
ic._db_save(execute, data=data)
|
||||
|
||||
return True
|
||||
|
||||
def new(self, index, pollutant):
|
||||
n = InitialConditionsAdisTS(pollutant=pollutant, status=self._status)
|
||||
self._lst.insert(index, n)
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
@property
|
||||
def Initial_Conditions_List(self):
|
||||
return self.lst
|
||||
@@ -0,0 +1,252 @@
|
||||
# InitialConditionsAdisTSSpec.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 trace, timer
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class ICAdisTSSpec(SQLSubModel):
|
||||
_sub_classes = [
|
||||
]
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None):
|
||||
super(ICAdisTSSpec, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = ICAdisTSSpec._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
self._name_section = name
|
||||
self._reach = None
|
||||
self._start_rk = None
|
||||
self._end_rk = None
|
||||
self._concentration = None
|
||||
self._eg = None
|
||||
self._em = None
|
||||
self._ed = None
|
||||
self._rate = None
|
||||
self._enabled = True
|
||||
|
||||
ICAdisTSSpec._id_cnt = max(ICAdisTSSpec._id_cnt + 1, self.id)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE initial_conditions_spec(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
ic_default INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
reach INTEGER NOT NULL,
|
||||
start_rk REAL NOT NULL,
|
||||
end_rk REAL NOT NULL,
|
||||
concentration REAL NOT NULL,
|
||||
eg REAL NOT NULL,
|
||||
em REAL NOT NULL,
|
||||
ed REAL NOT NULL,
|
||||
rate REAL NOT NULL,
|
||||
enabled BOOLEAN NOT NULL,
|
||||
FOREIGN KEY(ic_default) REFERENCES initial_conditions_adists(id),
|
||||
FOREIGN KEY(reach) REFERENCES river_reach(id)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == 0 and minor < 1:
|
||||
if int(release) < 6:
|
||||
cls._db_create(execute)
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
|
||||
table = execute(
|
||||
"SELECT id, ic_default, name, reach, start_rk, end_rk, " +
|
||||
"concentration, eg, em, ed, rate, enabled " +
|
||||
"FROM initial_conditions_spec " +
|
||||
f"WHERE ic_default = {data['ic_default_id']} "
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
id = row[0]
|
||||
name = row[2]
|
||||
reach = row[3]
|
||||
start_rk = row[4]
|
||||
end_rk = row[5]
|
||||
concentration = row[6]
|
||||
eg = row[7]
|
||||
em = row[8]
|
||||
ed = row[9]
|
||||
rate = row[10]
|
||||
enabled = (row[11] == 1)
|
||||
|
||||
# new_spec = [name, reach, start_rk, end_rk,
|
||||
# concentration, eg, em, ed, rate, enabled]
|
||||
|
||||
new_spec = cls(
|
||||
id=id,
|
||||
name=name,
|
||||
status=data['status']
|
||||
)
|
||||
|
||||
new_spec.reach = reach
|
||||
new_spec.start_rk = start_rk
|
||||
new_spec.end_rk = end_rk
|
||||
new_spec.concentration = concentration
|
||||
new_spec.eg = eg
|
||||
new_spec.em = em
|
||||
new_spec.ed = ed
|
||||
new_spec.rate = rate
|
||||
new_spec.enabled = enabled
|
||||
|
||||
new.append(new_spec)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
ic_default = data['ic_default_id']
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"initial_conditions_spec(id, ic_default, name, reach, " +
|
||||
"start_rk, end_rk, concentration, eg, em, ed, rate, enabled) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, " +
|
||||
f"{ic_default}, " +
|
||||
f"'{self._db_format(self._name_section)}', " +
|
||||
f"{self._reach}, " +
|
||||
f"{self._start_rk}, " +
|
||||
f"{self._end_rk}, " +
|
||||
f"{self._concentration}, " +
|
||||
f"{self._eg}, " +
|
||||
f"{self._em}, " +
|
||||
f"{self._ed}, " +
|
||||
f"{self._rate}, " +
|
||||
f"{self._enabled}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name_section
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name_section = name
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def reach(self):
|
||||
return self._reach
|
||||
|
||||
@reach.setter
|
||||
def reach(self, reach):
|
||||
self._reach = reach
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def start_rk(self):
|
||||
return self._start_rk
|
||||
|
||||
@start_rk.setter
|
||||
def start_rk(self, start_rk):
|
||||
self._start_rk = start_rk
|
||||
self._status.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._status.modified()
|
||||
|
||||
@property
|
||||
def concentration(self):
|
||||
return self._concentration
|
||||
|
||||
@concentration.setter
|
||||
def concentration(self, concentration):
|
||||
self._concentration = concentration
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def eg(self):
|
||||
return self._eg
|
||||
|
||||
@eg.setter
|
||||
def eg(self, eg):
|
||||
self._eg = eg
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def em(self):
|
||||
return self._em
|
||||
|
||||
@em.setter
|
||||
def em(self, em):
|
||||
self._em = em
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def ed(self):
|
||||
return self._ed
|
||||
|
||||
@ed.setter
|
||||
def ed(self, ed):
|
||||
self._ed = ed
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def rate(self):
|
||||
return self._rate
|
||||
|
||||
@rate.setter
|
||||
def rate(self, rate):
|
||||
self._rate = rate
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
return self._enabled
|
||||
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self._status.modified()
|
||||
@@ -0,0 +1,220 @@
|
||||
# LateralContributionAdisTS.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 (
|
||||
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
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class LateralContributionAdisTS(SQLSubModel):
|
||||
_sub_classes = []
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int = -1, pollutant: int = -1,
|
||||
name: str = "", status=None):
|
||||
super(LateralContributionAdisTS, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = LateralContributionAdisTS._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
self._pollutant = pollutant
|
||||
self._edge = None
|
||||
self._begin_rk = 0.0
|
||||
self._end_rk = 0.0
|
||||
self._data = []
|
||||
self._header = ["time", "rate"]
|
||||
self._types = [self.time_convert, float]
|
||||
|
||||
LateralContributionAdisTS._id_cnt = max(
|
||||
LateralContributionAdisTS._id_cnt + 1, self.id)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE lateral_contribution_adists(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
pollutant INTEGER NOT NULL,
|
||||
edge INTEGER NOT NULL,
|
||||
begin_rk REAL NOT NULL,
|
||||
end_rk REAL NOT NULL,
|
||||
FOREIGN KEY(pollutant) REFERENCES Pollutants(id),
|
||||
FOREIGN KEY(edge) REFERENCES river_reach(id)
|
||||
)
|
||||
""")
|
||||
|
||||
execute("""
|
||||
CREATE TABLE lateral_contribution_data_adists(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
data0 TEXT NOT NULL,
|
||||
data1 TEXT NOT NULL,
|
||||
lc INTEGER,
|
||||
FOREIGN KEY(lc) REFERENCES lateral_contribution(id)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
|
||||
table = execute(
|
||||
"SELECT id, pollutant, edge, begin_rk, end_rk " +
|
||||
"FROM lateral_contribution_adists"
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
lc = cls(
|
||||
id=row[0],
|
||||
pollutant=row[1],
|
||||
status=data['status']
|
||||
)
|
||||
|
||||
lc.edge = row[2]
|
||||
lc.begin_rk = row[3]
|
||||
lc.end_rk = row[4]
|
||||
|
||||
values = execute(
|
||||
"SELECT data0," +
|
||||
" data1 FROM lateral_contribution_data_adists " +
|
||||
f"WHERE lc = '{lc.id}'"
|
||||
)
|
||||
|
||||
# Write data
|
||||
for v in values:
|
||||
data0 = lc._types[0](v[0])
|
||||
data1 = lc._types[1](v[1])
|
||||
# Replace data at pos ind
|
||||
lc._data.append((data0, data1))
|
||||
|
||||
new.append(lc)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
|
||||
execute(f"DELETE FROM lateral_contribution_adists" +
|
||||
f" WHERE id = {self.id}")
|
||||
execute(f"DELETE FROM lateral_contribution_data_adists" +
|
||||
f" WHERE lc = {self.id}")
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"lateral_contribution_adists(id, " +
|
||||
"pollutant, edge, begin_rk, end_rk) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, {self._pollutant}, {self.edge}, " +
|
||||
f"{self._begin_rk}, {self._end_rk}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
for d in self._data:
|
||||
data0 = self._db_format(str(d[0]))
|
||||
data1 = self._db_format(str(d[1]))
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"lateral_contribution_data_adists(data0, data1, lc) " +
|
||||
f"VALUES ('{data0}', {data1}, {self.id})"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
return True
|
||||
|
||||
def __len__(self):
|
||||
return len(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 edge(self):
|
||||
return self._edge
|
||||
|
||||
@edge.setter
|
||||
def edge(self, edge):
|
||||
self._edge = edge
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def header(self):
|
||||
return self._header.copy()
|
||||
|
||||
@header.setter
|
||||
def header(self, header):
|
||||
self._header = header
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def pollutant(self):
|
||||
return self._pollutant
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data.copy()
|
||||
|
||||
@property
|
||||
def begin_rk(self):
|
||||
return self._begin_rk
|
||||
|
||||
@begin_rk.setter
|
||||
def begin_rk(self, begin_rk):
|
||||
self._begin_rk = begin_rk
|
||||
self._status.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._status.modified()
|
||||
@@ -0,0 +1,67 @@
|
||||
# LateralContributionsAdisTSList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 -*-
|
||||
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
from Model.LateralContributionsAdisTS.LateralContributionAdisTS \
|
||||
import LateralContributionAdisTS
|
||||
|
||||
|
||||
class LateralContributionsAdisTSList(PamhyrModelList):
|
||||
_sub_classes = [
|
||||
LateralContributionAdisTS,
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = cls(status=data['status'])
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
new._lst = LateralContributionAdisTS._db_load(
|
||||
execute, data
|
||||
)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute("DELETE FROM lateral_contribution_adists")
|
||||
execute("DELETE FROM lateral_condition_data_adists")
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
for lc in self._lst:
|
||||
lc._db_save(execute, data=data)
|
||||
|
||||
return True
|
||||
|
||||
def new(self, index, pollutant):
|
||||
n = LateralContributionAdisTS(pollutant=pollutant, status=self._status)
|
||||
self._lst.insert(index, n)
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
@property
|
||||
def Lat_Cont_List(self):
|
||||
return self.lst
|
||||
@@ -69,6 +69,9 @@ class Edge(SQLSubModel):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
if self._name == "":
|
||||
return f"R{self.id}"
|
||||
|
||||
return self._name
|
||||
|
||||
def is_enable(self):
|
||||
|
||||
@@ -51,6 +51,9 @@ class Node(SQLSubModel):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
if self._name == "":
|
||||
return f"N{self.id}"
|
||||
|
||||
return self._name
|
||||
|
||||
@property
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
# OutputRKAdists.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 (
|
||||
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
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class OutputRKAdists(SQLSubModel):
|
||||
_sub_classes = []
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int = -1, reach=None,
|
||||
rk=None, title: str = "", status=None):
|
||||
super(OutputRKAdists, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = OutputRKAdists._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
self._reach = reach
|
||||
self._rk = rk
|
||||
self._title = str(title)
|
||||
self._enabled = True
|
||||
|
||||
OutputRKAdists._id_cnt = max(
|
||||
OutputRKAdists._id_cnt + 1, self.id)
|
||||
|
||||
@property
|
||||
def reach(self):
|
||||
return self._reach
|
||||
|
||||
@reach.setter
|
||||
def reach(self, reach_id):
|
||||
self._reach = reach_id
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def rk(self):
|
||||
return self._rk
|
||||
|
||||
@rk.setter
|
||||
def rk(self, profile_id):
|
||||
self._rk = profile_id
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return self._title
|
||||
|
||||
@title.setter
|
||||
def title(self, title):
|
||||
self._title = title
|
||||
self._status.modified()
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
|
||||
sql = (
|
||||
"CREATE TABLE OutputRKAdists(" +
|
||||
"id INTEGER NOT NULL PRIMARY KEY, " +
|
||||
"reach INTEGER NOT NULL, " +
|
||||
"rk REAL NOT NULL, " +
|
||||
"title TEXT NOT NULL, " +
|
||||
"FOREIGN KEY(reach) REFERENCES river_reach(id)" +
|
||||
")"
|
||||
)
|
||||
|
||||
execute(sql)
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
|
||||
# reach = data["reach"]
|
||||
# profile = data["profile"]
|
||||
status = data["status"]
|
||||
|
||||
table = execute(
|
||||
"SELECT id, reach, rk, title " +
|
||||
f"FROM OutputRKAdists"
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
id = row[0]
|
||||
id_reach = row[1]
|
||||
id_rk = row[2]
|
||||
title = row[3]
|
||||
|
||||
new_output = cls(
|
||||
id=id, reach=id_reach,
|
||||
rk=id_rk, title=title,
|
||||
status=status
|
||||
)
|
||||
|
||||
new.append(new_output)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
|
||||
execute(f"DELETE FROM OutputRKAdists WHERE id = {self.id}")
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"OutputRKAdists(id, reach, rk, title) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, {self._reach}, {self._rk}, " +
|
||||
f"'{self._db_format(self._title)}'" +
|
||||
")"
|
||||
)
|
||||
|
||||
execute(sql)
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
return self._enabled
|
||||
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self._status.modified()
|
||||
@@ -0,0 +1,56 @@
|
||||
# OutputRKListAdists.py -- Pamhyr
|
||||
# Copyright (C) 2024 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 -*-
|
||||
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.OutputRKAdists.OutputRKAdists import OutputRKAdists
|
||||
|
||||
|
||||
class OutputRKAdistsList(PamhyrModelList):
|
||||
_sub_classes = [OutputRKAdists]
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = cls(status=data["status"])
|
||||
|
||||
new._lst = OutputRKAdists._db_load(execute, data)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
ok = True
|
||||
|
||||
# Delete previous data
|
||||
execute("DELETE FROM OutputRKAdists")
|
||||
|
||||
for sl in self._lst:
|
||||
ok &= sl._db_save(execute, data)
|
||||
|
||||
return ok
|
||||
|
||||
@property
|
||||
def OutputRK_List(self):
|
||||
return self.lst
|
||||
|
||||
def new(self, lst, index):
|
||||
n = OutputRKAdists(status=self._status)
|
||||
self._lst.insert(index, n)
|
||||
self._status.modified()
|
||||
return n
|
||||
@@ -0,0 +1,195 @@
|
||||
# Pollutants.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 (
|
||||
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
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class Pollutants(SQLSubModel):
|
||||
_sub_classes = []
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "", status=None):
|
||||
super(Pollutants, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = Pollutants._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
if name is None or name == "":
|
||||
self.name = f"pol{self.id}"
|
||||
else:
|
||||
self._name = str(name)
|
||||
self._enabled = True
|
||||
|
||||
self._data = []
|
||||
|
||||
Pollutants._id_cnt = max(
|
||||
Pollutants._id_cnt + 1, self.id)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data.copy()
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE Pollutants(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE
|
||||
)
|
||||
""")
|
||||
|
||||
execute("""
|
||||
CREATE TABLE Pollutants_characteristics(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
type INTEGER NOT NULL,
|
||||
diametre REAL NOT NULL,
|
||||
rho REAL NOT NULL,
|
||||
porosity REAL NOT NULL,
|
||||
cdc_riv REAL NOT NULL,
|
||||
cdc_cas REAL NOT NULL,
|
||||
apd REAL NOT NULL,
|
||||
ac REAL NOT NULL,
|
||||
bc REAL NOT NULL,
|
||||
pollutant INTEGER NOT NULL,
|
||||
FOREIGN KEY(pollutant) REFERENCES Pollutants(id)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
|
||||
status = data["status"]
|
||||
|
||||
table = execute(
|
||||
"SELECT id, name " +
|
||||
f"FROM Pollutants"
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
id = row[0]
|
||||
name = row[1]
|
||||
|
||||
new_pollutant = cls(
|
||||
id=id, name=name,
|
||||
status=status
|
||||
)
|
||||
|
||||
new_data = []
|
||||
table = execute(
|
||||
"SELECT * " +
|
||||
"FROM Pollutants_characteristics " +
|
||||
f"WHERE pollutant = {id}"
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for t in table:
|
||||
new_data = t[1:-1]
|
||||
|
||||
new_pollutant._data.append(new_data)
|
||||
|
||||
new.append(new_pollutant)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
|
||||
execute(f"DELETE FROM Pollutants WHERE id = {self.id}")
|
||||
execute(f"DELETE FROM Pollutants_characteristics" +
|
||||
f" WHERE pollutant = {self.id}")
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"Pollutants(id, name) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, " +
|
||||
f"'{self._db_format(self._name)}'" +
|
||||
")"
|
||||
)
|
||||
|
||||
execute(sql)
|
||||
|
||||
for d in self._data:
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"Pollutants_characteristics(type, diametre, rho, porosity, " +
|
||||
"cdc_riv, cdc_cas, apd, ac, bc, pollutant) " +
|
||||
f"VALUES ({d[0]}, {d[1]}, {d[2]},{d[3]}, {d[4]}, "
|
||||
f"{d[5]}, {d[6]}, {d[7]}, {d[8]}, {self.id})"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
return self._enabled
|
||||
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self._status.modified()
|
||||
|
||||
def is_define(self):
|
||||
return len(self._data) != 0
|
||||
|
||||
def new_from_data(self, data):
|
||||
|
||||
try:
|
||||
new = [int(data[0])]
|
||||
new += [float(d) for d in data[1:]]
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
new = None
|
||||
|
||||
return new
|
||||
|
||||
def __len__(self):
|
||||
return len(self._data)
|
||||
@@ -0,0 +1,56 @@
|
||||
# PollutantsList.py -- Pamhyr
|
||||
# Copyright (C) 2024 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 -*-
|
||||
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.Pollutants.Pollutants import Pollutants
|
||||
|
||||
|
||||
class PollutantsList(PamhyrModelList):
|
||||
_sub_classes = [Pollutants]
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = cls(status=data["status"])
|
||||
|
||||
new._lst = Pollutants._db_load(execute, data)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
ok = True
|
||||
|
||||
# Delete previous data
|
||||
execute("DELETE FROM Pollutants")
|
||||
|
||||
for sl in self._lst:
|
||||
ok &= sl._db_save(execute, data)
|
||||
|
||||
return ok
|
||||
|
||||
@property
|
||||
def Pollutants_List(self):
|
||||
return self.lst
|
||||
|
||||
def new(self, lst, index):
|
||||
n = Pollutants(status=self._status)
|
||||
self._lst.insert(index, n)
|
||||
self._status.modified()
|
||||
return n
|
||||
@@ -65,6 +65,10 @@ class Results(SQLSubModel):
|
||||
def set(self, key, value):
|
||||
self._meta_data[key] = value
|
||||
|
||||
@property
|
||||
def is_valid(self):
|
||||
return ("timestamps" in self._meta_data)
|
||||
|
||||
def get(self, key):
|
||||
return self._meta_data[key]
|
||||
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
# Results.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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/>.
|
||||
|
||||
import logging
|
||||
import numpy as np
|
||||
import os
|
||||
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
|
||||
from Model.Results.River.RiverAdisTS import River
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class Results(object):
|
||||
def __init__(self, study=None, solver=None,
|
||||
repertory="", name="0", type_pol=None):
|
||||
self._study = study
|
||||
self._solver = solver
|
||||
self._repertory = repertory
|
||||
self._name = name
|
||||
|
||||
self._river = River(self._study)
|
||||
|
||||
self._meta_data = {
|
||||
# Keep results creation date
|
||||
"creation_date": datetime.now(),
|
||||
}
|
||||
filelist = [f for f in os.listdir(self._repertory)
|
||||
if os.path.isfile(os.path.join(self._repertory, f))
|
||||
]
|
||||
self._pollutants_list = [f[0:-4] for f in filelist if f[-4:] == ".bin"]
|
||||
self._pollutants_list.insert(0, self._pollutants_list.pop(
|
||||
self._pollutants_list.index("total_sediment"))
|
||||
)
|
||||
|
||||
self._phys_var_list = ["C", "G", "M", "D", "L", "N", "R"]
|
||||
|
||||
@property
|
||||
def date(self):
|
||||
date = self._meta_data["creation_date"]
|
||||
return f"{date.isoformat(sep=' ')}"
|
||||
|
||||
@property
|
||||
def river(self):
|
||||
return self._river
|
||||
|
||||
@property
|
||||
def study(self):
|
||||
return self._study
|
||||
|
||||
@property
|
||||
def pollutants_list(self):
|
||||
return self._pollutants_list
|
||||
|
||||
@property
|
||||
def phys_var_list(self):
|
||||
return self._phys_var_list
|
||||
|
||||
@property
|
||||
def nb_pollutants(self):
|
||||
return len(self._pollutants_list)
|
||||
|
||||
def set(self, key, value):
|
||||
self._meta_data[key] = value
|
||||
|
||||
def get(self, key):
|
||||
return self._meta_data[key]
|
||||
|
||||
def reload(self):
|
||||
return self._solver.results(
|
||||
self._study,
|
||||
self._repertory,
|
||||
qlog=None,
|
||||
)
|
||||
@@ -57,7 +57,10 @@ class Profile(object):
|
||||
)
|
||||
|
||||
def get_ts_key(self, timestamp, key):
|
||||
return self._data[timestamp][key]
|
||||
if timestamp in self._data:
|
||||
if key in self._data[timestamp]:
|
||||
return self._data[timestamp][key]
|
||||
return None
|
||||
|
||||
def has_sediment(self):
|
||||
return any(map(lambda ts: "sl" in self._data[ts], self._data))
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
# River.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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/>.
|
||||
|
||||
import logging
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class Profile(object):
|
||||
def __init__(self, profile, study):
|
||||
self._study = study
|
||||
self._profile = profile # Source profile in the study
|
||||
self._data = {} # Dict of dict {<ts>: {<key>: <value>, ...}, ...}
|
||||
|
||||
def __len__(self):
|
||||
return len(self._data)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._profile.name
|
||||
|
||||
@property
|
||||
def rk(self):
|
||||
return self._profile.rk
|
||||
|
||||
@property
|
||||
def geometry(self):
|
||||
return self._profile
|
||||
|
||||
def set(self, timestamp, key, data):
|
||||
if timestamp not in self._data:
|
||||
self._data[timestamp] = {}
|
||||
|
||||
self._data[timestamp][key] = data
|
||||
|
||||
def get_ts(self, timestamp):
|
||||
return self._data[timestamp]
|
||||
|
||||
def get_key(self, key):
|
||||
return list(
|
||||
map(lambda ts: self._data[ts][key], self._data)
|
||||
)
|
||||
|
||||
def get_ts_key(self, timestamp, key):
|
||||
return self._data[timestamp][key]
|
||||
|
||||
def has_sediment(self):
|
||||
return any(map(lambda ts: "sl" in self._data[ts], self._data))
|
||||
|
||||
|
||||
class Reach(object):
|
||||
def __init__(self, reach, study):
|
||||
self._study = study
|
||||
self._reach = reach # Source reach in the study
|
||||
self._profiles = list(
|
||||
map(
|
||||
lambda p: Profile(p, self._study),
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._profiles)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._reach.name
|
||||
|
||||
@property
|
||||
def geometry(self):
|
||||
return self._reach
|
||||
|
||||
@property
|
||||
def profiles(self):
|
||||
return self._profiles.copy()
|
||||
|
||||
def profile(self, id):
|
||||
return self._profiles[id]
|
||||
|
||||
def set(self, profile_id, timestamp, key, data):
|
||||
self._profiles[profile_id].set(timestamp, key, data)
|
||||
|
||||
def has_sediment(self):
|
||||
return any(map(lambda profile: profile.has_sediment(), self._profiles))
|
||||
|
||||
|
||||
class River(object):
|
||||
def __init__(self, study):
|
||||
self._study = study
|
||||
|
||||
# Dict with timestamps as key
|
||||
self._reachs = []
|
||||
|
||||
def __len__(self):
|
||||
return len(self._reachs)
|
||||
|
||||
@property
|
||||
def reachs(self):
|
||||
return self._reachs.copy()
|
||||
|
||||
def reach(self, id):
|
||||
return self._reachs[id]
|
||||
|
||||
def has_reach(self, id):
|
||||
return 0 <= id < len(self._reachs)
|
||||
|
||||
def add(self, reach_id):
|
||||
reachs = self._study.river.enable_edges()
|
||||
|
||||
new = Reach(reachs[reach_id].reach, self._study)
|
||||
|
||||
self._reachs.append(new)
|
||||
return new
|
||||
|
||||
def get_reach_by_geometry(self, geometry_reach):
|
||||
return next(
|
||||
filter(
|
||||
lambda r: r.geometry is geometry_reach,
|
||||
self._reachs
|
||||
)
|
||||
)
|
||||
@@ -48,6 +48,17 @@ from Model.REPLine.REPLineList import REPLineList
|
||||
|
||||
from Solver.Solvers import solver_type_list
|
||||
|
||||
from Model.OutputRKAdists.OutputRKListAdists import OutputRKAdistsList
|
||||
from Model.Pollutants.PollutantsList import PollutantsList
|
||||
from Model.InitialConditionsAdisTS.InitialConditionsAdisTSList \
|
||||
import InitialConditionsAdisTSList
|
||||
from Model.BoundaryConditionsAdisTS.BoundaryConditionsAdisTSList \
|
||||
import BoundaryConditionsAdisTSList
|
||||
from Model.LateralContributionsAdisTS.LateralContributionsAdisTSList \
|
||||
import LateralContributionsAdisTSList
|
||||
from Model.D90AdisTS.D90AdisTSList import D90AdisTSList
|
||||
from Model.DIFAdisTS.DIFAdisTSList import DIFAdisTSList
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
@@ -402,6 +413,13 @@ class River(Graph):
|
||||
HydraulicStructureList,
|
||||
AddFileList,
|
||||
REPLineList,
|
||||
OutputRKAdistsList,
|
||||
PollutantsList,
|
||||
InitialConditionsAdisTSList,
|
||||
BoundaryConditionsAdisTSList,
|
||||
LateralContributionsAdisTSList,
|
||||
D90AdisTSList,
|
||||
DIFAdisTSList,
|
||||
]
|
||||
|
||||
def __init__(self, status=None):
|
||||
@@ -427,6 +445,16 @@ class River(Graph):
|
||||
)
|
||||
self._additional_files = AddFileList(status=self._status)
|
||||
self._rep_lines = REPLineList(status=self._status)
|
||||
self._Output_rk_adists = OutputRKAdistsList(status=self._status)
|
||||
self._Pollutants = PollutantsList(status=self._status)
|
||||
self._InitialConditionsAdisTS = InitialConditionsAdisTSList(
|
||||
status=self._status)
|
||||
self._BoundaryConditionsAdisTS = BoundaryConditionsAdisTSList(
|
||||
status=self._status)
|
||||
self._LateralContributionsAdisTS = LateralContributionsAdisTSList(
|
||||
status=self._status)
|
||||
self._D90AdisTS = D90AdisTSList(status=self._status)
|
||||
self._DIFAdisTS = DIFAdisTSList(status=self._status)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
@@ -500,6 +528,25 @@ class River(Graph):
|
||||
)
|
||||
new._rep_lines = REPLineList._db_load(execute, data)
|
||||
|
||||
new._Output_rk_adists = OutputRKAdistsList._db_load(
|
||||
execute, data
|
||||
)
|
||||
|
||||
new._Pollutants = PollutantsList._db_load(execute, data)
|
||||
|
||||
new._InitialConditionsAdisTS = InitialConditionsAdisTSList._db_load(
|
||||
execute, data)
|
||||
|
||||
new._BoundaryConditionsAdisTS = BoundaryConditionsAdisTSList._db_load(
|
||||
execute, data)
|
||||
|
||||
new._LateralContributionsAdisTS = \
|
||||
LateralContributionsAdisTSList._db_load(execute, data)
|
||||
|
||||
new._D90AdisTS = D90AdisTSList._db_load(execute, data)
|
||||
|
||||
new._DIFAdisTS = DIFAdisTSList._db_load(execute, data)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
@@ -519,6 +566,14 @@ class River(Graph):
|
||||
for solver in self._parameters:
|
||||
objs.append(self._parameters[solver])
|
||||
|
||||
objs.append(self._Output_rk_adists)
|
||||
objs.append(self._Pollutants)
|
||||
objs.append(self._InitialConditionsAdisTS)
|
||||
objs.append(self._BoundaryConditionsAdisTS)
|
||||
objs.append(self._LateralContributionsAdisTS)
|
||||
objs.append(self._D90AdisTS)
|
||||
objs.append(self._DIFAdisTS)
|
||||
|
||||
self._save_submodel(execute, objs, data)
|
||||
return True
|
||||
|
||||
@@ -643,6 +698,34 @@ Last export at: @date."""
|
||||
def parameters(self):
|
||||
return self._parameters
|
||||
|
||||
@property
|
||||
def Output_rk_adists(self):
|
||||
return self._Output_rk_adists
|
||||
|
||||
@property
|
||||
def Pollutants(self):
|
||||
return self._Pollutants
|
||||
|
||||
@property
|
||||
def ic_adists(self):
|
||||
return self._InitialConditionsAdisTS
|
||||
|
||||
@property
|
||||
def boundary_conditions_adists(self):
|
||||
return self._BoundaryConditionsAdisTS
|
||||
|
||||
@property
|
||||
def lateral_contributions_adists(self):
|
||||
return self._LateralContributionsAdisTS
|
||||
|
||||
@property
|
||||
def d90_adists(self):
|
||||
return self._D90AdisTS
|
||||
|
||||
@property
|
||||
def dif_adists(self):
|
||||
return self._DIFAdisTS
|
||||
|
||||
def get_params(self, solver):
|
||||
if solver in self._parameters:
|
||||
return self._parameters[solver]
|
||||
|
||||
@@ -164,10 +164,7 @@ class Stricklers(SQLSubModel):
|
||||
return True
|
||||
|
||||
def __str__(self):
|
||||
if self._name != "":
|
||||
return f"{self._name} ({self._minor}, {self._medium})"
|
||||
|
||||
return f"({self._minor}, {self._medium})"
|
||||
return f"{self.name} ({self._minor}, {self._medium})"
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
||||
+7
-1
@@ -85,7 +85,7 @@ class Study(SQLModel):
|
||||
StudyNetworkReachChecker(),
|
||||
StudyGeometryChecker(),
|
||||
StudyInitialConditionsChecker(),
|
||||
StudyBoundaryConditionChecker(),
|
||||
# StudyBoundaryConditionChecker(),
|
||||
# DummyOK(),
|
||||
# DummyWARNING(),
|
||||
# DummyERROR(),
|
||||
@@ -322,6 +322,12 @@ class Study(SQLModel):
|
||||
sql, fetch_one=False, commit=True
|
||||
)
|
||||
|
||||
version = new.execute(
|
||||
"SELECT value FROM info WHERE key='study_release'"
|
||||
)
|
||||
if version is not None:
|
||||
new.status.version = int(version[0])
|
||||
|
||||
# TODO: Load metadata
|
||||
new.name = new.execute("SELECT value FROM info WHERE key='name'")[0]
|
||||
new.description = new.execute(
|
||||
|
||||
@@ -52,6 +52,7 @@ class SQLModel(SQL):
|
||||
self._create() # Create db
|
||||
# self._save() # Save
|
||||
else:
|
||||
logger.info("Update database")
|
||||
self._update() # Update db scheme if necessary
|
||||
# self._load() # Load data
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ class Modules(IterableFlag):
|
||||
RESERVOIR = auto()
|
||||
SEDIMENT_LAYER = auto()
|
||||
ADDITIONAL_FILES = auto()
|
||||
OUTPUT_RK = auto()
|
||||
|
||||
# Results
|
||||
RESULTS = auto()
|
||||
@@ -79,6 +80,7 @@ class Modules(IterableFlag):
|
||||
cls.ADDITIONAL_FILES,
|
||||
cls.RESULTS,
|
||||
cls.WINDOW_LIST,
|
||||
cls.OUTPUT_RK,
|
||||
]
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -0,0 +1,893 @@
|
||||
# AdisTS.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 os
|
||||
import logging
|
||||
|
||||
import numpy as np
|
||||
|
||||
import shutil
|
||||
|
||||
from tools import (
|
||||
trace, timer, logger_exception,
|
||||
timestamp_to_old_pamhyr_date,
|
||||
old_pamhyr_date_to_timestamp,
|
||||
timestamp_to_old_pamhyr_date_adists
|
||||
)
|
||||
|
||||
from Solver.CommandLine import CommandLineSolver
|
||||
|
||||
from Model.Results.ResultsAdisTS import Results
|
||||
from Model.Results.River.River import River, Reach, Profile
|
||||
|
||||
from Checker.Adists import (
|
||||
AdistsOutputRKChecker,
|
||||
)
|
||||
|
||||
from itertools import chain
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
def adists_file_open(filepath, mode):
|
||||
f = open(filepath, mode)
|
||||
|
||||
if "w" in mode:
|
||||
# Write header
|
||||
comment = "*"
|
||||
if ".ST" in filepath:
|
||||
comment = "#"
|
||||
|
||||
f.write(
|
||||
f"{comment} " +
|
||||
"This file is generated by PAMHYR, please don't modify\n"
|
||||
)
|
||||
|
||||
return f
|
||||
|
||||
|
||||
class AdisTS(CommandLineSolver):
|
||||
_type = "adists"
|
||||
|
||||
def __init__(self, name):
|
||||
super(AdisTS, self).__init__(name)
|
||||
|
||||
self._type = "adists"
|
||||
|
||||
self._cmd_input = ""
|
||||
self._cmd_solver = "@path @input -o @output"
|
||||
self._cmd_output = ""
|
||||
|
||||
@classmethod
|
||||
def default_parameters(cls):
|
||||
lst = super(AdisTS, cls).default_parameters()
|
||||
|
||||
lst += [
|
||||
("adists_implicitation_parameter", "0.5"),
|
||||
("adists_timestep_screen", "60"),
|
||||
("adists_timestep_bin", "60"),
|
||||
("adists_timestep_csv", "60"),
|
||||
("adists_timestep_mage", "60"),
|
||||
("adists_initial_concentration", "60"),
|
||||
]
|
||||
|
||||
return lst
|
||||
|
||||
@classmethod
|
||||
def checkers(cls):
|
||||
lst = [
|
||||
AdistsOutputRKChecker(),
|
||||
]
|
||||
|
||||
return lst
|
||||
|
||||
def cmd_args(self, study):
|
||||
lst = super(AdisTS, self).cmd_args(study)
|
||||
return lst
|
||||
|
||||
def input_param(self):
|
||||
name = self._study.name
|
||||
return f"{name}.REP"
|
||||
|
||||
def log_file(self):
|
||||
name = self._study.name
|
||||
return f"{name}.TRA"
|
||||
|
||||
def _export_REP_additional_lines(self, study, rep_file):
|
||||
lines = filter(
|
||||
lambda line: line.is_enabled(),
|
||||
study.river.rep_lines.lines
|
||||
)
|
||||
|
||||
for line in lines:
|
||||
rep_file.write(line.line)
|
||||
|
||||
def _export_REP(self, study, repertory, mage_rep, files, qlog, name="0"):
|
||||
|
||||
if qlog is not None:
|
||||
qlog.put("Export REP file")
|
||||
|
||||
# Write header
|
||||
with adists_file_open(
|
||||
os.path.join(
|
||||
repertory, f"{name}.REP"
|
||||
), "w+"
|
||||
) as f:
|
||||
path = os.path.join("..", mage_rep, name)
|
||||
f.write(f"NET {path}.NET\n")
|
||||
f.write(f"REP {path}.REP\n")
|
||||
|
||||
for file in files:
|
||||
EXT = file.split('.')[1]
|
||||
f.write(f"{EXT} {file}\n")
|
||||
|
||||
self._export_REP_additional_lines(study, f)
|
||||
|
||||
path_mage_net = os.path.join(os.path.abspath(
|
||||
os.path.join(repertory, os.pardir)
|
||||
), os.path.join(mage_rep, "net"))
|
||||
path_adists_net = os.path.join(repertory, "net")
|
||||
|
||||
if os.path.exists(path_mage_net):
|
||||
shutil.copytree(path_mage_net, path_adists_net, dirs_exist_ok=True)
|
||||
|
||||
@timer
|
||||
def export(self, study, repertory, qlog=None):
|
||||
self._study = study
|
||||
name = study.name.replace(" ", "_")
|
||||
|
||||
self.export_additional_files(study, repertory, qlog, name=name)
|
||||
|
||||
return True
|
||||
|
||||
###########
|
||||
# RESULTS #
|
||||
###########
|
||||
|
||||
def read_bin(self, study, repertory, results, qlog=None, name="0"):
|
||||
return
|
||||
|
||||
@timer
|
||||
def results(self, study, repertory, qlog=None, name="0", type_pol=None):
|
||||
results = Results(
|
||||
study=study,
|
||||
solver=self,
|
||||
repertory=repertory,
|
||||
name=name,
|
||||
type_pol=type_pol,
|
||||
)
|
||||
self.read_bin(study, repertory, results, qlog, name=name)
|
||||
|
||||
return results
|
||||
|
||||
def output_param(self):
|
||||
name = ""
|
||||
return f"{name}"
|
||||
|
||||
_alph = list(
|
||||
map(
|
||||
chr,
|
||||
chain(
|
||||
range(48, 58), # 0..9
|
||||
range(65, 91), # A..Z
|
||||
range(97, 123) # a..z
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
_l_alph = len(_alph)
|
||||
|
||||
_nodes_cnt = 0
|
||||
_nodes_names = {}
|
||||
_nodes_views = set()
|
||||
|
||||
def get_reach_name(self, reach):
|
||||
return f"Reach_{reach.id:>3}".replace(" ", "0")
|
||||
|
||||
def get_node_name(self, node):
|
||||
"""Generate a 3 char name for node
|
||||
|
||||
Args:
|
||||
node: The node
|
||||
|
||||
Returns:
|
||||
A 3 char name string
|
||||
"""
|
||||
n = node.id
|
||||
|
||||
# print("node name id: ", n)
|
||||
|
||||
# if n in self._nodes_names:
|
||||
# return self._nodes_names[n]
|
||||
|
||||
name = ""
|
||||
|
||||
checked_new = False
|
||||
while not checked_new:
|
||||
self._nodes_cnt += 1
|
||||
nc = self._nodes_cnt
|
||||
|
||||
name = "".join(
|
||||
map(
|
||||
lambda i: self._alph[i % self._l_alph],
|
||||
[
|
||||
int(nc / (self._l_alph * self._l_alph)),
|
||||
int(nc / self._l_alph),
|
||||
nc
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
checked_new = name not in self._nodes_views
|
||||
|
||||
self._nodes_views.add(name)
|
||||
self._nodes_names[n] = name
|
||||
|
||||
return name
|
||||
|
||||
#################################
|
||||
# Adis-TS in weak coupling mode #
|
||||
#################################
|
||||
|
||||
|
||||
class AdisTSwc(AdisTS):
|
||||
_type = "adistswc"
|
||||
|
||||
def __init__(self, name):
|
||||
super(AdisTSwc, self).__init__(name)
|
||||
|
||||
self._type = "adistswc"
|
||||
|
||||
@classmethod
|
||||
def default_parameters(cls):
|
||||
lst = super(AdisTSwc, cls).default_parameters()
|
||||
|
||||
# Insert new parameters at specific position
|
||||
names = list(map(lambda t: t[0], lst))
|
||||
|
||||
return lst
|
||||
|
||||
##########
|
||||
# Export #
|
||||
##########
|
||||
|
||||
def cmd_args(self, study):
|
||||
lst = super(AdisTSwc, self).cmd_args(study)
|
||||
|
||||
return lst
|
||||
|
||||
def _export_POLs(self, study, repertory, qlog=None, name="0"):
|
||||
|
||||
files = []
|
||||
|
||||
if qlog is not None:
|
||||
qlog.put("Export POLS files")
|
||||
|
||||
pollutants = study.river.Pollutants.Pollutants_List
|
||||
|
||||
for pollutant in pollutants:
|
||||
name = pollutant.name
|
||||
with adists_file_open(
|
||||
os.path.join(repertory, f"{name}.POL"), "w+"
|
||||
) as f:
|
||||
files.append(f"{name}.POL")
|
||||
f.write(f"name = {name}\n")
|
||||
|
||||
self._export_POL_Characteristics(
|
||||
study, pollutant._data, f, qlog
|
||||
)
|
||||
|
||||
POL_ICs = next(filter(
|
||||
lambda ic: ic.pollutant == pollutant.id,
|
||||
study.river.ic_adists.Initial_Conditions_List
|
||||
))
|
||||
|
||||
if POL_ICs.concentration is not None:
|
||||
f.write(f"file_ini = {name}.INI\n")
|
||||
self._export_ICs_AdisTS(
|
||||
study, repertory, POL_ICs, qlog, name
|
||||
)
|
||||
|
||||
POL_BCs = list(filter(
|
||||
lambda bc: bc.pollutant == pollutant.id,
|
||||
study.river.boundary_conditions_adists.BCs_AdisTS_List
|
||||
))
|
||||
|
||||
if len(POL_BCs) != 0:
|
||||
f.write(f"file_cl = {name}.CDT\n")
|
||||
self._export_BCs_AdisTS(
|
||||
study, repertory, POL_BCs, qlog, name
|
||||
)
|
||||
|
||||
POL_LAT_Cont = list(filter(
|
||||
lambda lc: lc.pollutant == pollutant.id,
|
||||
study.river.lateral_contributions_adists.Lat_Cont_List
|
||||
))
|
||||
|
||||
if len(POL_LAT_Cont) != 0:
|
||||
f.write(f"file_ald = {name}.ALD\n")
|
||||
f.write(f"*\n")
|
||||
self._export_Lat_AdisTS(
|
||||
study, repertory, POL_LAT_Cont, qlog, name
|
||||
)
|
||||
|
||||
return files
|
||||
|
||||
def _export_Lat_AdisTS(self, study, repertory, POL_LC, qlog, POL_name):
|
||||
|
||||
if qlog is not None:
|
||||
qlog.put("Export POL LCs files")
|
||||
|
||||
with adists_file_open(
|
||||
os.path.join(repertory, f"{POL_name}.ALD"), "w+"
|
||||
) as f:
|
||||
for LC in POL_LC:
|
||||
reach = next(filter(
|
||||
lambda edge: edge.id == LC.edge, study.river.edges()
|
||||
)) # .name
|
||||
reach_name = self.get_reach_name(self, reach)
|
||||
f.write(f"${reach_name} {LC.begin_rk} {LC.end_rk}\n")
|
||||
f.write(f"*temps |débit massique (kg/s)\n")
|
||||
f.write(f"*---------++++++++++\n")
|
||||
|
||||
for LC_data in LC._data:
|
||||
tmp = timestamp_to_old_pamhyr_date_adists(int(LC_data[0]))
|
||||
f.write(" ".join((f"{tmp}",
|
||||
f"{LC_data[1]}\n")))
|
||||
f.write(f"*\n")
|
||||
|
||||
return True
|
||||
|
||||
def _export_BCs_AdisTS(self, study, repertory, POL_BC, qlog, POL_name):
|
||||
|
||||
if qlog is not None:
|
||||
qlog.put("Export POL BCs files")
|
||||
|
||||
with adists_file_open(os.path.join(
|
||||
repertory, f"{POL_name}.CDT"), "w+"
|
||||
) as f:
|
||||
for BC in POL_BC:
|
||||
node = next(filter(
|
||||
lambda x: x.id == BC.node, study.river._nodes
|
||||
))
|
||||
node_name = f"{node.id:3}".replace(" ", "x")
|
||||
f.write(f"${node_name}\n")
|
||||
|
||||
if BC.type == "Concentration":
|
||||
f.write(f"*temps |concentration\n")
|
||||
f.write(f"*JJ:HH:MM | (g/L)\n")
|
||||
f.write(f"*---------++++++++++\n")
|
||||
else:
|
||||
f.write(f"*temps |rate\n")
|
||||
f.write(f"*JJ:HH:MM | (kg/s)\n")
|
||||
f.write(f"*---------++++++++++\n")
|
||||
|
||||
for BC_data in BC._data:
|
||||
tmp = timestamp_to_old_pamhyr_date_adists(int(BC_data[0]))
|
||||
f.write(" ".join((f"{tmp}",
|
||||
f"{BC_data[1]}\n")))
|
||||
f.write(f"*\n")
|
||||
|
||||
return True
|
||||
|
||||
def _export_ICs_AdisTS(self, study, repertory,
|
||||
POL_IC_default, qlog, POL_name):
|
||||
|
||||
if qlog is not None:
|
||||
qlog.put("Export POL ICs files")
|
||||
|
||||
with adists_file_open(os.path.join(
|
||||
repertory, f"{POL_name}.INI"
|
||||
), "w+") as f:
|
||||
f.write(f"*État initial pour le polluant {POL_name}\n")
|
||||
f.write(" ".join(("DEFAULT =",
|
||||
f"{POL_IC_default.concentration}",
|
||||
f"{POL_IC_default.eg}",
|
||||
f"{POL_IC_default.em}",
|
||||
f"{POL_IC_default.ed}\n")))
|
||||
|
||||
if len(POL_IC_default._data) != 0:
|
||||
self._export_ICs_AdisTS_Spec(
|
||||
study, POL_IC_default._data, f, qlog
|
||||
)
|
||||
|
||||
def _export_ICs_AdisTS_Spec(self, study, pol_ics_spec_data,
|
||||
f, qlog, name="0"):
|
||||
|
||||
edges = study.river.enable_edges()
|
||||
for ic_spec in pol_ics_spec_data:
|
||||
id_reach = ic_spec.reach
|
||||
reach = next((x for x in edges if x.id == id_reach), None)
|
||||
if reach is None:
|
||||
continue
|
||||
|
||||
f.write(" ".join((f"{ic_spec.name}",
|
||||
"=",
|
||||
f"{study.river.get_edge_id(reach)+1}",
|
||||
f"{ic_spec.start_rk}",
|
||||
f"{ic_spec.end_rk}",
|
||||
f"{ic_spec.concentration}",
|
||||
f"{ic_spec.eg}",
|
||||
f"{ic_spec.em}",
|
||||
f"{ic_spec.ed}",
|
||||
f"{ic_spec.rate}\n")))
|
||||
|
||||
return True
|
||||
|
||||
def _export_POL_Characteristics(self, study, pol_data, f, qlog, name="0"):
|
||||
|
||||
list_characteristics = ["type", "diametre", "rho", "porosity",
|
||||
"cdc_riv", "cdc_cas", "apd", "ac", "bc"]
|
||||
|
||||
if len(list_characteristics) <= (len(pol_data[0])):
|
||||
for i in range(len(list_characteristics)):
|
||||
f.write(f"{list_characteristics[i]} = {pol_data[0][i]}\n")
|
||||
|
||||
def _export_D90(self, study, repertory, qlog=None, name="0"):
|
||||
|
||||
files = []
|
||||
|
||||
if qlog is not None:
|
||||
qlog.put("Export D90 file")
|
||||
|
||||
with adists_file_open(
|
||||
os.path.join(repertory, f"{name}.D90"), "w+"
|
||||
) as f:
|
||||
files.append(f"{name}.D90")
|
||||
|
||||
f.write(f"*Diamètres caractéristiques du fond stable\n")
|
||||
|
||||
d90AdisTS = study.river.d90_adists.D90_AdisTS_List
|
||||
|
||||
f.write(f"DEFAULT = {d90AdisTS[0].d90}\n")
|
||||
|
||||
self._export_d90_spec(study, d90AdisTS[0]._data, f, qlog)
|
||||
|
||||
return files
|
||||
|
||||
def _export_d90_spec(self, study, d90_spec_data, f, qlog, name="0"):
|
||||
|
||||
for d90_spec in d90_spec_data:
|
||||
if (d90_spec.name is None
|
||||
or d90_spec.reach is None
|
||||
or d90_spec.start_rk is None
|
||||
or d90_spec.end_rk is None
|
||||
or d90_spec.d90 is None):
|
||||
return
|
||||
|
||||
edges = study.river.enable_edges()
|
||||
|
||||
id_edges = list(map(lambda x: x.id, edges))
|
||||
|
||||
id_reach = d90_spec.reach
|
||||
reach = next((x for x in edges if x.id == id_reach), None)
|
||||
|
||||
if reach is None:
|
||||
return
|
||||
|
||||
f.write(" ".join((f"{d90_spec.name}",
|
||||
"=",
|
||||
f"{study.river.get_edge_id(reach)+1}",
|
||||
f"{d90_spec.start_rk}",
|
||||
f"{d90_spec.end_rk}",
|
||||
f"{d90_spec.d90}\n")))
|
||||
|
||||
def _export_DIF(self, study, repertory, qlog=None, name="0"):
|
||||
|
||||
files = []
|
||||
|
||||
if qlog is not None:
|
||||
qlog.put("Export DIF file")
|
||||
|
||||
with adists_file_open(
|
||||
os.path.join(repertory, f"{name}.DIF"), "w+"
|
||||
) as f:
|
||||
files.append(f"{name}.DIF")
|
||||
|
||||
f.write(f"*Définition des paramètres des fonctions de calcul du\n")
|
||||
f.write(f"*coefficient de diffusion\n")
|
||||
|
||||
difAdisTS = study.river.dif_adists.DIF_AdisTS_List
|
||||
|
||||
if difAdisTS[0].method != "generique":
|
||||
f.write(" ".join((f"defaut = ",
|
||||
f"{difAdisTS[0].method}",
|
||||
f"{difAdisTS[0].dif}\n")))
|
||||
else:
|
||||
f.write(" ".join((f"defaut ="
|
||||
f"{difAdisTS[0].method}",
|
||||
f"{difAdisTS[0].dif}",
|
||||
f"{difAdisTS[0].b}",
|
||||
f"{difAdisTS[0].c}\n")))
|
||||
|
||||
self._export_dif_spec(study, difAdisTS[0]._data, f, qlog)
|
||||
|
||||
return files
|
||||
|
||||
def _export_dif_spec(self, study, dif_spec_data, f, qlog, name="0"):
|
||||
|
||||
for dif_spec in dif_spec_data:
|
||||
if (dif_spec.reach is None
|
||||
or dif_spec.start_rk is None
|
||||
or dif_spec.end_rk is None
|
||||
or dif_spec.dif is None
|
||||
or dif_spec.b is None
|
||||
or dif_spec.c is None):
|
||||
return
|
||||
|
||||
edges = study.river.enable_edges()
|
||||
|
||||
id_reach = dif_spec.reach
|
||||
reach = next((x for x in edges if x.id == id_reach), None)
|
||||
|
||||
if reach is None:
|
||||
return
|
||||
|
||||
if dif_spec.method != "generique":
|
||||
f.write(" ".join((f"{dif_spec.method}",
|
||||
"=",
|
||||
f"{study.river.get_edge_id(reach)+1}",
|
||||
f"{dif_spec.start_rk}",
|
||||
f"{dif_spec.end_rk}",
|
||||
f"{dif_spec.dif}\n")))
|
||||
else:
|
||||
f.write(" ".join((f"{dif_spec.method}",
|
||||
f"=" f"{study.river.get_edge_id(reach)+1}",
|
||||
f"{dif_spec.start_rk}",
|
||||
f"{dif_spec.end_rk}",
|
||||
f"{dif_spec.dif}",
|
||||
f"{dif_spec.b}",
|
||||
f"{dif_spec.c}\n")))
|
||||
|
||||
def _export_NUM(self, study, repertory, qlog=None, name="0"):
|
||||
|
||||
dict_names = {"init_time": "start_date",
|
||||
"final_time": "end_date",
|
||||
"timestep": "dt0",
|
||||
"implicitation_parameter": "theta",
|
||||
"timestep_screen": "dtscr",
|
||||
"timestep_bin": "dtbin",
|
||||
"timestep_csv": "dtcsv",
|
||||
"timestep_mage": "dtMage",
|
||||
"initial_concentration": "c_initiale"}
|
||||
files = []
|
||||
|
||||
if qlog is not None:
|
||||
qlog.put("Export NUM file")
|
||||
|
||||
with adists_file_open(
|
||||
os.path.join(repertory, f"{name}.NUM"), "w+"
|
||||
) as f:
|
||||
files.append(f"{name}.NUM")
|
||||
|
||||
params = study.river.get_params(self.type).parameters
|
||||
for p in params:
|
||||
name = p.name\
|
||||
.replace("all_", "")\
|
||||
.replace("adists_", "")
|
||||
value = p.value
|
||||
|
||||
logger.debug(
|
||||
f"export: NUM: {name}: {value} ({p.value})"
|
||||
)
|
||||
|
||||
if name != "command_line_arguments":
|
||||
f.write(f"{dict_names[name]} = {value}\n")
|
||||
|
||||
outputrks = study.river.Output_rk_adists.OutputRK_List
|
||||
|
||||
for outputrk in outputrks:
|
||||
self._export_outputrk(study, outputrk, f, qlog)
|
||||
|
||||
return files
|
||||
|
||||
def _export_outputrk(self, study, outputrk, f, qlog, name="0"):
|
||||
if (outputrk.reach is None
|
||||
or outputrk.rk is None
|
||||
or outputrk.title is None):
|
||||
return
|
||||
|
||||
edges = study.river.enable_edges()
|
||||
|
||||
id_edges = list(map(lambda x: x.id, edges))
|
||||
|
||||
id_reach = outputrk.reach
|
||||
reach = next((x for x in edges if x.id == id_reach), None)
|
||||
rk = outputrk.rk
|
||||
title = outputrk.title
|
||||
|
||||
if reach is None:
|
||||
return
|
||||
|
||||
f.write(f"output = {study.river.get_edge_id(reach)+1} {rk} {title}\n")
|
||||
|
||||
@timer
|
||||
def read_bin(self, study, repertory, results, qlog=None, name="0"):
|
||||
|
||||
filelist = [f for f in os.listdir(repertory)
|
||||
if os.path.isfile(os.path.join(repertory, f))
|
||||
]
|
||||
files_bin_names = [f for f in filelist if f[-4:] == ".bin"]
|
||||
files_bin_names.insert(0, files_bin_names.pop(
|
||||
files_bin_names.index("total_sediment.bin"))
|
||||
)
|
||||
|
||||
ifilename = os.path.join(repertory, files_bin_names[0])
|
||||
|
||||
logger.info(f"read_bin: Start reading '{ifilename}' ...")
|
||||
|
||||
with open(ifilename, 'rb') as f:
|
||||
# header
|
||||
# first line
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (start)
|
||||
data = np.fromfile(f, dtype=np.int32, count=3)
|
||||
ibmax = data[0] # number of reaches
|
||||
ismax = data[1] # total number of cross sections
|
||||
kbl = data[2] * -1 # block size for .BIN header
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (end)
|
||||
# second line
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (start)
|
||||
ibu = np.fromfile(f, dtype=np.int32, count=ibmax)
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (end)
|
||||
# third line
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (start)
|
||||
data = np.fromfile(f, dtype=np.int32, count=2 * ibmax)
|
||||
is1 = np.zeros(ibmax, dtype=np.int32)
|
||||
is2 = np.zeros(ibmax, dtype=np.int32)
|
||||
|
||||
logger.debug(f"read_bin: nb_reach = {ibmax}")
|
||||
logger.debug(f"read_bin: nb_profile = {ismax}")
|
||||
|
||||
results.set("nb_reach", f"{ibmax}")
|
||||
results.set("nb_profile", f"{ismax}")
|
||||
|
||||
reachs = []
|
||||
iprofiles = {}
|
||||
reach_offset = {}
|
||||
|
||||
for i in range(ibmax):
|
||||
# Add results reach to reach list
|
||||
r = results.river.add(i)
|
||||
reachs.append(r)
|
||||
|
||||
is1[i] = data[2 * i] - 1 # first section of reach i
|
||||
is2[i] = data[2 * i + 1] - 1 # last section of reach i
|
||||
|
||||
key = (is1[i], is2[i])
|
||||
iprofiles[key] = r
|
||||
|
||||
reach_offset[r] = is1[i]
|
||||
|
||||
logger.debug(f"read_bin: iprofiles = {iprofiles}")
|
||||
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (end)
|
||||
# fourth line
|
||||
pk = np.zeros(ismax, dtype=np.float32)
|
||||
for k in range(0, ismax, kbl):
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (start)
|
||||
pk[k:min(k + kbl, ismax)] = np.fromfile(f,
|
||||
dtype=np.float32,
|
||||
count=min(
|
||||
k + kbl, ismax
|
||||
) - k)
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (end)
|
||||
|
||||
# fifth line (useless)
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (start)
|
||||
zmin_OLD = np.fromfile(f, dtype=np.float32, count=1)[0]
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (end)
|
||||
# sixth line
|
||||
zf = np.zeros(ismax, dtype=np.float32)
|
||||
z = np.zeros(ismax * 3, dtype=np.float32)
|
||||
for k in range(0, ismax, kbl):
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (start)
|
||||
z[3 * k:3 * min(k + kbl, ismax)] = \
|
||||
np.fromfile(f,
|
||||
dtype=np.float32,
|
||||
count=3 * (min(k + kbl, ismax) - k)
|
||||
)
|
||||
# z[i*3+1] and z[i*3+2] are useless
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (end)
|
||||
zf = [z[i * 3] for i in range(ismax)]
|
||||
# seventh line (useless)
|
||||
for k in range(0, ismax, kbl):
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (start)
|
||||
zero = np.fromfile(f, dtype=np.int32, count=ismax)
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (end)
|
||||
# end header
|
||||
|
||||
def ip_to_r(i):
|
||||
return iprofiles[
|
||||
next(
|
||||
filter(
|
||||
lambda k: k[0] <= i <= k[1],
|
||||
iprofiles
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
def ip_to_ri(r, i): return i - reach_offset[r]
|
||||
|
||||
path_files = map(lambda file: os.path.join(
|
||||
repertory, file), files_bin_names)
|
||||
|
||||
data_tmp = {}
|
||||
|
||||
for file_bin in path_files:
|
||||
key_pol = os.path.basename(file_bin)[0:-4]
|
||||
data_tmp[key_pol] = {}
|
||||
with open(file_bin, 'rb') as f:
|
||||
# header
|
||||
# first line
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (start)
|
||||
data = np.fromfile(f, dtype=np.int32, count=3)
|
||||
ibmax = data[0] # number of reaches
|
||||
ismax = data[1] # total number of cross sections
|
||||
kbl = data[2] * -1 # block size for .BIN header
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (end)
|
||||
# second line
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (start)
|
||||
ibu = np.fromfile(f, dtype=np.int32, count=ibmax)
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (end)
|
||||
# third line
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (start)
|
||||
data = np.fromfile(f, dtype=np.int32, count=2 * ibmax)
|
||||
is1 = np.zeros(ibmax, dtype=np.int32)
|
||||
is2 = np.zeros(ibmax, dtype=np.int32)
|
||||
for i in range(ibmax):
|
||||
# first section of reach i (FORTRAN numbering)
|
||||
is1[i] = data[2 * i]
|
||||
# last section of reach i (FORTRAN numbering)
|
||||
is2[i] = data[2 * i + 1]
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (end)
|
||||
# fourth line
|
||||
pk = np.zeros(ismax, dtype=np.float32)
|
||||
for k in range(0, ismax, kbl):
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (start)
|
||||
pk[k:min(k + kbl, ismax)] = np.fromfile(
|
||||
f, dtype=np.float32, count=min(k + kbl, ismax) - k
|
||||
)
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (end)
|
||||
# fifth line (useless)
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (start)
|
||||
zmin_OLD = np.fromfile(f, dtype=np.float32, count=1)[0]
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (end)
|
||||
# sixth line
|
||||
zf = np.zeros(ismax, dtype=np.float32)
|
||||
z = np.zeros(ismax * 3, dtype=np.float32)
|
||||
for k in range(0, ismax, kbl):
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (start)
|
||||
z[3 * k:3 * min(k + kbl, ismax)] = np.fromfile(
|
||||
f, dtype=np.float32,
|
||||
count=3 * (min(k + kbl, ismax) - k)
|
||||
)
|
||||
# z[i*3+1] and z[i*3+2] are useless
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (end)
|
||||
zf = [z[i * 3] for i in range(ismax)]
|
||||
# seventh line (useless)
|
||||
for k in range(0, ismax, kbl):
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (start)
|
||||
zero = np.fromfile(f, dtype=np.int32, count=ismax)
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (end)
|
||||
# end header
|
||||
# data
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (start)
|
||||
while data.size > 0:
|
||||
ismax = np.fromfile(f, dtype=np.int32, count=1)[0]
|
||||
t = np.fromfile(f, dtype=np.float64, count=1)[0]
|
||||
if t not in data_tmp[key_pol]:
|
||||
data_tmp[key_pol][t] = {}
|
||||
c = np.fromfile(f, dtype=np.byte, count=1)
|
||||
# possible values :
|
||||
# sediment : C, G, M, D, L, N, R
|
||||
# polutant : C
|
||||
phys_var = bytearray(c).decode()
|
||||
data_tmp[key_pol][t][phys_var] = {}
|
||||
real_data = np.fromfile(f, dtype=np.float32, count=ismax)
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (end)
|
||||
data_tmp[key_pol][t][phys_var] = real_data
|
||||
data = np.fromfile(f, dtype=np.int32, count=1) # (start)
|
||||
# end data
|
||||
|
||||
pollutants_keys = list(data_tmp.keys())
|
||||
timestamps_keys = list(data_tmp[pollutants_keys[0]].keys())
|
||||
phys_data_names = list(data_tmp[pollutants_keys[0]]
|
||||
[timestamps_keys[0]].keys())
|
||||
type_pol_index = len(phys_data_names)
|
||||
|
||||
for r, reach in enumerate(reachs):
|
||||
for i in range(is1[r]-1, is2[r]):
|
||||
p_i = ip_to_ri(reach, i)
|
||||
|
||||
for t_data in timestamps_keys:
|
||||
pol_view = []
|
||||
for pol in pollutants_keys:
|
||||
pol_view.append(tuple(list(map(
|
||||
lambda data_el: data_el[i],
|
||||
list(data_tmp[pol][t_data].values())
|
||||
))))
|
||||
|
||||
reach.set(p_i, t_data, "pols", pol_view)
|
||||
|
||||
results.set("timestamps", set(timestamps_keys))
|
||||
|
||||
@timer
|
||||
def results(self, study, repertory, qlog=None, name=None):
|
||||
self._study = study
|
||||
if name is None:
|
||||
name = study.name.replace(" ", "_")
|
||||
|
||||
results = super(AdisTSwc, self).results(study,
|
||||
repertory,
|
||||
qlog,
|
||||
name=name)
|
||||
|
||||
return results
|
||||
|
||||
def export_func_dict(self):
|
||||
return [
|
||||
self._export_NUM,
|
||||
self._export_DIF,
|
||||
self._export_D90,
|
||||
self._export_POLs,
|
||||
]
|
||||
|
||||
def rm_previous_results(self, study, repertory, qlog):
|
||||
|
||||
if "resultats" in os.listdir(repertory):
|
||||
repertory_results = os.path.join(repertory, "resultats")
|
||||
else:
|
||||
repertory_results = os.path.normpath(repertory)
|
||||
|
||||
if not os.path.isdir(repertory_results):
|
||||
return
|
||||
|
||||
filelist = [f for f in os.listdir(repertory_results)
|
||||
if os.path.isfile(os.path.join(repertory_results, f))
|
||||
]
|
||||
files_bin_names = [f for f in filelist if f[-4:] == ".bin"]
|
||||
if len(files_bin_names) < 1:
|
||||
return
|
||||
|
||||
for el in files_bin_names:
|
||||
os.remove(os.path.join(repertory_results, el))
|
||||
|
||||
@timer
|
||||
def export(self, study, repertory, mage_rep, qlog=None, name="0"):
|
||||
logger.debug(f"cmd solver adistswc : {self._cmd_solver}")
|
||||
self._study = study
|
||||
name = study.name.replace(" ", "_")
|
||||
|
||||
# Generate files
|
||||
files = []
|
||||
|
||||
self.rm_previous_results(study, repertory, qlog)
|
||||
|
||||
try:
|
||||
for func in self.export_func_dict():
|
||||
files = files + func(study, repertory, qlog, name=name)
|
||||
|
||||
self.export_additional_files(study, repertory, qlog, name=name)
|
||||
self._export_REP(study, repertory, mage_rep,
|
||||
files, qlog, name=name)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to export study to {self._type}")
|
||||
logger_exception(e)
|
||||
return False
|
||||
@@ -99,7 +99,10 @@ class CommandLineSolver(AbstractSolver):
|
||||
params = study.river.get_params(self.type)
|
||||
args = params.get_by_key("all_command_line_arguments")
|
||||
|
||||
return args.split(" ")
|
||||
if args is None:
|
||||
return []
|
||||
else:
|
||||
return args.split(" ")
|
||||
|
||||
def input_param(self):
|
||||
"""Return input command line parameter(s)
|
||||
|
||||
+83
-25
@@ -70,7 +70,7 @@ class Mage(CommandLineSolver):
|
||||
lst += [
|
||||
("mage_min_timestep", "1.0"),
|
||||
("mage_timestep_tra", "3600"),
|
||||
("mage_timestep_bin", "0"),
|
||||
("mage_timestep_bin", "3600"),
|
||||
# ("mage_timestep_melissa", "0"),
|
||||
("mage_implicitation", "0.70"),
|
||||
("mage_continuity_discretization", "S"),
|
||||
@@ -80,11 +80,11 @@ class Mage(CommandLineSolver):
|
||||
("mage_smooth_coef", "0"),
|
||||
("mage_cfl_max", "-1."),
|
||||
("mage_min_height", "0.1"),
|
||||
("mage_max_niter", "10"),
|
||||
("mage_max_niter", "99"),
|
||||
("mage_timestep_reduction_factor", "2"),
|
||||
("mage_precision_reduction_factor_Z", "1"),
|
||||
("mage_precision_reduction_factor_Q", "1"),
|
||||
("mage_niter_max_precision", "99"),
|
||||
("mage_niter_max_precision", "10"),
|
||||
("mage_niter_before_switch", "99"),
|
||||
("mage_max_froude", "1.5"),
|
||||
("mage_diffluence_node_height_balance", "-1"),
|
||||
@@ -195,7 +195,7 @@ class Mage(CommandLineSolver):
|
||||
)
|
||||
|
||||
for line in lines:
|
||||
rep_file.write(line.line)
|
||||
rep_file.write(line.line+"\n")
|
||||
|
||||
def _export_ST(self, study, repertory, qlog, name="0"):
|
||||
files = []
|
||||
@@ -662,29 +662,50 @@ class Mage(CommandLineSolver):
|
||||
if qlog is not None:
|
||||
qlog.put("Export VAR file")
|
||||
|
||||
with mage_file_open(os.path.join(repertory, f"{name}.VAR"), "w+") as f:
|
||||
files.append(f"{name}.VAR")
|
||||
nb_cv = 0
|
||||
for hs in hydraulic_structures:
|
||||
if hs.input_reach is None:
|
||||
continue
|
||||
|
||||
for hs in hydraulic_structures:
|
||||
if hs.input_reach is None:
|
||||
continue
|
||||
if not hs.input_reach.is_enable():
|
||||
continue
|
||||
|
||||
if not hs.input_reach.is_enable():
|
||||
continue
|
||||
if not hs.enabled:
|
||||
continue
|
||||
|
||||
for bhs in hs.basic_structures:
|
||||
for bhs in hs.basic_structures:
|
||||
if bhs.enabled:
|
||||
logger.info(bhs._type)
|
||||
if bhs._type != "CV":
|
||||
if bhs._type == "CV":
|
||||
nb_cv += 1
|
||||
|
||||
if nb_cv != 0:
|
||||
with mage_file_open(os.path.join(
|
||||
repertory, f"{name}.VAR"), "w+") as f:
|
||||
files.append(f"{name}.VAR")
|
||||
|
||||
for hs in hydraulic_structures:
|
||||
if hs.input_reach is None:
|
||||
continue
|
||||
|
||||
name = bhs.name
|
||||
if name == "":
|
||||
name = f"HS_{bhs.id:>3}".replace(" ", "0")
|
||||
if not hs.input_reach.is_enable():
|
||||
continue
|
||||
|
||||
f.write(
|
||||
f"${name} clapet"
|
||||
)
|
||||
if not hs.enabled:
|
||||
continue
|
||||
|
||||
for bhs in hs.basic_structures:
|
||||
logger.info(bhs._type)
|
||||
if bhs._type != "CV":
|
||||
continue
|
||||
|
||||
name = bhs.name
|
||||
if name == "":
|
||||
name = f"HS_{bhs.id:>3}".replace(" ", "0")
|
||||
|
||||
f.write(
|
||||
f"${name} clapet"
|
||||
)
|
||||
return files
|
||||
|
||||
def _export_DEV(self, study, repertory, qlog, name="0"):
|
||||
@@ -759,7 +780,7 @@ class Mage(CommandLineSolver):
|
||||
# RESULTS #
|
||||
###########
|
||||
|
||||
def read_bin(self, study, repertory, results, qlog=None, name="0"):
|
||||
def read_bin(self, study, fname, results, qlog=None, name="0"):
|
||||
return
|
||||
|
||||
@timer
|
||||
@@ -770,7 +791,16 @@ class Mage(CommandLineSolver):
|
||||
repertory=repertory,
|
||||
name=name,
|
||||
)
|
||||
self.read_bin(study, repertory, results, qlog, name=name)
|
||||
fname = os.path.join(repertory, f"{name}.BIN")
|
||||
if not os.path.isfile(fname):
|
||||
logger.info(f"Result file {name}.BIN does not exist")
|
||||
return None
|
||||
try:
|
||||
self.read_bin(study, fname, results, qlog, name=name)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to read results")
|
||||
logger_exception(e)
|
||||
return None
|
||||
|
||||
return results
|
||||
|
||||
@@ -974,8 +1004,7 @@ class Mage8(Mage):
|
||||
###########
|
||||
|
||||
@timer
|
||||
def read_bin(self, study, repertory, results, qlog=None, name="0"):
|
||||
fname = os.path.join(repertory, f"{name}.BIN")
|
||||
def read_bin(self, study, fname, results, qlog=None, name="0"):
|
||||
logger.info(f"read_bin: Start reading '{fname}' ...")
|
||||
|
||||
with mage_file_open(fname, "r") as f:
|
||||
@@ -1026,6 +1055,7 @@ class Mage8(Mage):
|
||||
|
||||
reachs = []
|
||||
iprofiles = {}
|
||||
profile_len = []
|
||||
reach_offset = {}
|
||||
|
||||
data = read_int(2*nb_reach)
|
||||
@@ -1045,6 +1075,7 @@ class Mage8(Mage):
|
||||
|
||||
# Profile ID offset
|
||||
reach_offset[r] = i1
|
||||
profile_len.append(i2-i1+1)
|
||||
|
||||
logger.debug(f"read_bin: iprofiles = {iprofiles}")
|
||||
|
||||
@@ -1073,6 +1104,15 @@ class Mage8(Mage):
|
||||
]
|
||||
def ip_to_ri(r, i): return i - reach_offset[r]
|
||||
|
||||
# check results and geometry consistency
|
||||
for i, r in enumerate(reachs):
|
||||
lp = len(r.profiles)
|
||||
pl = profile_len[i]
|
||||
if lp != pl:
|
||||
logger.warning(f"geometry reach {i} has {lp} profiles")
|
||||
logger.warning(f"results reach {i} has {pl} values")
|
||||
return
|
||||
|
||||
ts = set()
|
||||
end = False
|
||||
while not end:
|
||||
@@ -1082,7 +1122,7 @@ class Mage8(Mage):
|
||||
f, dtype=np.byte, count=1)).decode()
|
||||
data = read_float(n)
|
||||
|
||||
logger.debug(f"read_bin: timestamp = {timestamp} sec")
|
||||
# logger.debug(f"read_bin: timestamp = {timestamp} sec")
|
||||
ts.add(timestamp)
|
||||
|
||||
if key in ["Z", "Q"]:
|
||||
@@ -1106,8 +1146,24 @@ class Mage8(Mage):
|
||||
endline()
|
||||
end = newline().size <= 0
|
||||
|
||||
logger.debug(reachs[0].profiles[0]._data)
|
||||
results.set("timestamps", ts)
|
||||
ts_list = sorted(ts)
|
||||
logger.info(f"compute tab...")
|
||||
for r in reachs:
|
||||
for p in r.profiles:
|
||||
if not p.geometry.tab_up_to_date:
|
||||
p.geometry.compute_tabulation()
|
||||
|
||||
logger.info(f"compute velocily...")
|
||||
|
||||
for r in reachs:
|
||||
for t in ts_list:
|
||||
for i, p in enumerate(r.profiles):
|
||||
v = p.geometry.speed(
|
||||
p.get_ts_key(t, "Q"),
|
||||
p.get_ts_key(t, "Z")
|
||||
)
|
||||
r.set(i, t, "V", v)
|
||||
logger.info(f"read_bin: ... end with {len(ts)} timestamp read")
|
||||
|
||||
@timer
|
||||
@@ -1288,6 +1344,8 @@ class Mage8(Mage):
|
||||
name = study.name.replace(" ", "_")
|
||||
|
||||
results = super(Mage8, self).results(study, repertory, qlog, name=name)
|
||||
if results is None:
|
||||
return None
|
||||
if with_gra:
|
||||
self.read_gra(study, repertory, results, qlog, name=name)
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ from Solver.GenericSolver import GenericSolver
|
||||
from Solver.Mage import (
|
||||
Mage7, Mage8, MageFake7,
|
||||
)
|
||||
from Solver.AdisTS import AdisTSwc
|
||||
from Solver.RubarBE import Rubar3, RubarBE
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
@@ -31,6 +32,7 @@ solver_long_name = {
|
||||
# "mage7": "Mage v7",
|
||||
"mage8": "Mage v8",
|
||||
# "mage_fake7": "Mage fake v7",
|
||||
"adistswc": "Adis-TS_WC",
|
||||
# "rubarbe": "RubarBE",
|
||||
# "rubar3": "Rubar3",
|
||||
}
|
||||
@@ -40,6 +42,7 @@ solver_type_list = {
|
||||
# "mage7": Mage7,
|
||||
"mage8": Mage8,
|
||||
# "mage_fake7": MageFake7,
|
||||
"adistswc": AdisTSwc,
|
||||
# "rubarbe": RubarBE,
|
||||
# "rubar3": Rubar3,
|
||||
}
|
||||
@@ -55,6 +55,7 @@ class AboutWindow(PamhyrDialog):
|
||||
|
||||
label = self.get_label_text("label_version")
|
||||
label = label.replace("@version", version)
|
||||
label = label.replace("@codename", "(Adis-TS)")
|
||||
self.set_label_text("label_version", label)
|
||||
|
||||
# Authors
|
||||
|
||||
@@ -232,8 +232,11 @@ class EditBoundaryConditionWindow(PamhyrWindow):
|
||||
self._d50sigma.setEnabled(False)
|
||||
|
||||
def setup_dialog(self):
|
||||
reach = self._data.reach(self._study.river)[0]
|
||||
self.slope_value = abs(reach.get_incline_median_mean())
|
||||
if len(self._data.reach(self._study.river)) > 0:
|
||||
reach = self._data.reach(self._study.river)[0]
|
||||
self.slope_value = abs(reach.get_incline_median_mean())
|
||||
else:
|
||||
self.slope_value = 0.0
|
||||
|
||||
def d50_changed(self, value):
|
||||
self._undo_stack.push(
|
||||
@@ -352,6 +355,14 @@ class EditBoundaryConditionWindow(PamhyrWindow):
|
||||
if node is None:
|
||||
return
|
||||
reach = self._data.reach(self._study.river)[0]
|
||||
if len(reach.profiles) == 0:
|
||||
self.message_box(
|
||||
window_title=self._trad["Warning"],
|
||||
text=self._trad["title_need_geometry"],
|
||||
informative_text=self._trad["msg_need_geometry"]
|
||||
)
|
||||
return
|
||||
|
||||
profile = reach.profiles[-1]
|
||||
dlg = GenerateDialog(self.slope_value,
|
||||
reach,
|
||||
@@ -369,8 +380,12 @@ class EditBoundaryConditionWindow(PamhyrWindow):
|
||||
if strickler is None:
|
||||
strickler = 25.0
|
||||
height = [(i)*(z_max-z_min)/50 for i in range(51)]
|
||||
q = [((profile.wet_width(z_min + h) * 0.8) * strickler
|
||||
* (h ** (5/3)) * (abs(self.slope_value) ** (0.5)))
|
||||
# q = [((profile.wet_width(z_min + h) * 0.8) * strickler
|
||||
# * (h ** (5/3)) * (abs(self.slope_value) ** (0.5)))
|
||||
# for h in height]
|
||||
q = [profile.wet_area(z_min + h) * strickler
|
||||
* (profile.wet_radius(z_min + h) ** (2/3))
|
||||
* (abs(self.slope_value) ** (0.5))
|
||||
for h in height]
|
||||
for i in range(len(height)):
|
||||
height[i] += z_min
|
||||
@@ -384,6 +399,14 @@ class EditBoundaryConditionWindow(PamhyrWindow):
|
||||
if node is None:
|
||||
return
|
||||
reach = self._data.reach(self._study.river)[0]
|
||||
if len(reach.profiles) == 0:
|
||||
self.message_box(
|
||||
window_title=self._trad["Warning"],
|
||||
text=self._trad["title_need_geometry"],
|
||||
informative_text=self._trad["msg_need_geometry"]
|
||||
)
|
||||
return
|
||||
|
||||
profile = reach.profiles[-1]
|
||||
z_min = profile.z_min()
|
||||
z_max = profile.z_max()
|
||||
|
||||
@@ -44,3 +44,14 @@ class BCETranslate(BCTranslate):
|
||||
"z": self._dict["unit_elevation"],
|
||||
"solid": _translate("BoundaryCondition", "Solid (kg/s)"),
|
||||
}
|
||||
|
||||
self._dict["title_need_geometry"] = _translate(
|
||||
"BoundaryCondition", "No geometry"
|
||||
)
|
||||
self._dict["msg_need_geometry"] = _translate(
|
||||
"BoundaryCondition",
|
||||
"No geometry found for this reach.\n"
|
||||
"This feature requires a reach with a geometry."
|
||||
)
|
||||
self._dict["Warning"] = _translate("BoundaryCondition",
|
||||
"Warning")
|
||||
@@ -97,7 +97,7 @@ class BoundaryConditionWindow(PamhyrWindow):
|
||||
else:
|
||||
editable_headers = []
|
||||
|
||||
for t in ["liquid", "solid", "suspenssion"]:
|
||||
for t in ["liquid", "solid"]:
|
||||
self._delegate_type = ComboBoxDelegate(
|
||||
trad=self._trad,
|
||||
data=self._study.river,
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
# Plot.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 datetime import datetime
|
||||
|
||||
from tools import timer, trace
|
||||
from View.Tools.PamhyrPlot import PamhyrPlot
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
QCoreApplication
|
||||
)
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class Plot(PamhyrPlot):
|
||||
def __init__(self, mode="time", data=None,
|
||||
trad=None, canvas=None, toolbar=None,
|
||||
parent=None):
|
||||
super(Plot, self).__init__(
|
||||
canvas=canvas,
|
||||
trad=trad,
|
||||
data=data,
|
||||
toolbar=toolbar,
|
||||
parent=parent
|
||||
)
|
||||
|
||||
self._table_headers = self._trad.get_dict("table_headers")
|
||||
|
||||
header = self.data.header
|
||||
|
||||
self.label_x = self._table_headers[header[0]]
|
||||
self.label_y = self._table_headers[header[1]]
|
||||
|
||||
self._mode = mode
|
||||
self._isometric_axis = False
|
||||
|
||||
self._auto_relim_update = True
|
||||
self._autoscale_update = True
|
||||
|
||||
def custom_ticks(self):
|
||||
if self.data.header[0] != "time":
|
||||
return
|
||||
|
||||
self.set_ticks_time_formater()
|
||||
|
||||
@timer
|
||||
def draw(self):
|
||||
self.init_axes()
|
||||
|
||||
if len(self.data) == 0:
|
||||
self._init = False
|
||||
return
|
||||
|
||||
self.draw_data()
|
||||
self.custom_ticks()
|
||||
|
||||
self.idle()
|
||||
self._init = True
|
||||
|
||||
def draw_data(self):
|
||||
# Plot data
|
||||
x = list(map(lambda v: v[0], self.data.data))
|
||||
y = list(map(lambda v: v[1], self.data.data))
|
||||
|
||||
self._line, = self.canvas.axes.plot(
|
||||
x, y,
|
||||
color=self.color_plot,
|
||||
**self.plot_default_kargs
|
||||
)
|
||||
|
||||
@timer
|
||||
def update(self, ind=None):
|
||||
if not self._init:
|
||||
self.draw()
|
||||
return
|
||||
|
||||
self.update_data()
|
||||
|
||||
self.update_idle()
|
||||
|
||||
def update_data(self):
|
||||
x = list(map(lambda v: v[0], self.data.data))
|
||||
y = list(map(lambda v: v[1], self.data.data))
|
||||
|
||||
self._line.set_data(x, y)
|
||||
@@ -0,0 +1,157 @@
|
||||
# Table.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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
|
||||
import traceback
|
||||
|
||||
from datetime import date, time, datetime, timedelta
|
||||
|
||||
from tools import (
|
||||
trace, timer,
|
||||
timestamp_to_old_pamhyr_date,
|
||||
old_pamhyr_date_to_timestamp
|
||||
)
|
||||
|
||||
from View.Tools.PamhyrTable import PamhyrTableModel
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
Qt, QVariant, QAbstractTableModel,
|
||||
QCoreApplication, QModelIndex, pyqtSlot,
|
||||
QRect, QTime, QDateTime,
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QTableView, QAbstractItemView, QSpinBox,
|
||||
QTimeEdit, QDateTimeEdit, QItemDelegate,
|
||||
)
|
||||
|
||||
from View.BoundaryConditionsAdisTS.Edit.UndoCommand import (
|
||||
AddCommand, DelCommand, SetDataCommand, PasteCommand, SortCommand,
|
||||
)
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class TableModel(PamhyrTableModel):
|
||||
def data(self, index, role):
|
||||
if role == Qt.TextAlignmentRole:
|
||||
return Qt.AlignHCenter | Qt.AlignVCenter
|
||||
|
||||
if role != Qt.ItemDataRole.DisplayRole:
|
||||
return QVariant()
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
value = QVariant()
|
||||
|
||||
if 0 <= column < 2:
|
||||
v = self._data._data[row][column]
|
||||
if self._data._types[column] == float:
|
||||
value = f"{v:.4f}"
|
||||
elif self._data.header[column] == "time":
|
||||
if self._opt_data == "time":
|
||||
value = timestamp_to_old_pamhyr_date(int(v))
|
||||
else:
|
||||
value = str(datetime.fromtimestamp(v))
|
||||
else:
|
||||
value = f"{v}"
|
||||
|
||||
return value
|
||||
|
||||
def setData(self, index, value, role=Qt.EditRole):
|
||||
if not index.isValid() or role != Qt.EditRole:
|
||||
return False
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
try:
|
||||
self._undo.push(
|
||||
SetDataCommand(
|
||||
self._data, row, column, value
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.info(e)
|
||||
logger.debug(traceback.format_exc())
|
||||
|
||||
self.dataChanged.emit(index, index)
|
||||
return True
|
||||
|
||||
def add(self, row, parent=QModelIndex()):
|
||||
self.beginInsertRows(parent, row, row - 1)
|
||||
|
||||
self._undo.push(
|
||||
AddCommand(
|
||||
self._data, row
|
||||
)
|
||||
)
|
||||
|
||||
self.endInsertRows()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def delete(self, rows, parent=QModelIndex()):
|
||||
self.beginRemoveRows(parent, rows[0], rows[-1])
|
||||
|
||||
self._undo.push(
|
||||
DelCommand(
|
||||
self._data, rows
|
||||
)
|
||||
)
|
||||
|
||||
self.endRemoveRows()
|
||||
|
||||
def sort(self, _reverse, parent=QModelIndex()):
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
|
||||
self._undo.push(
|
||||
SortCommand(
|
||||
self._data, _reverse
|
||||
)
|
||||
)
|
||||
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self.update()
|
||||
|
||||
def paste(self, row, header, data):
|
||||
if len(data) == 0:
|
||||
return
|
||||
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
|
||||
self._undo.push(
|
||||
PasteCommand(
|
||||
self._data, row,
|
||||
list(
|
||||
map(
|
||||
lambda d: self._data.new_from_data(header, d),
|
||||
data
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
# self.auto_sort()
|
||||
self.layoutChanged.emit()
|
||||
@@ -0,0 +1,138 @@
|
||||
# UndoCommand.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 copy import deepcopy
|
||||
from tools import trace, timer
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QMessageBox, QUndoCommand, QUndoStack,
|
||||
)
|
||||
|
||||
from Model.BoundaryConditionsAdisTS.BoundaryConditionAdisTS \
|
||||
import BoundaryConditionAdisTS
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class SetDataCommand(QUndoCommand):
|
||||
def __init__(self, data, index, column, new_value):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._data = data
|
||||
self._index = index
|
||||
self._column = column
|
||||
self._old = self._data.get_i(self._index)[self._column]
|
||||
self._new = new_value
|
||||
|
||||
def undo(self):
|
||||
self._data._set_i_c_v(self._index, self._column, self._old)
|
||||
|
||||
def redo(self):
|
||||
self._data._set_i_c_v(self._index, self._column, self._new)
|
||||
|
||||
|
||||
class AddCommand(QUndoCommand):
|
||||
def __init__(self, data, index):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._data = data
|
||||
self._index = index
|
||||
self._new = None
|
||||
|
||||
def undo(self):
|
||||
del self._data._data[self._index]
|
||||
|
||||
def redo(self):
|
||||
if self._new is None:
|
||||
self._new = self._data.insert(self._index, (
|
||||
self._data._types[0](0), self._data._types[1](0.0)
|
||||
))
|
||||
else:
|
||||
self._data.insert(self._index, self._new)
|
||||
|
||||
|
||||
class DelCommand(QUndoCommand):
|
||||
def __init__(self, data, rows):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._data = data
|
||||
self._rows = rows
|
||||
|
||||
self._bc = []
|
||||
for row in rows:
|
||||
self._bc.append((row, self._data._data[row]))
|
||||
self._bc.sort()
|
||||
|
||||
def undo(self):
|
||||
for row, el in self._bc:
|
||||
self._data.insert(row, el)
|
||||
|
||||
def redo(self):
|
||||
self._data.delete_i(self._rows)
|
||||
|
||||
|
||||
class PasteCommand(QUndoCommand):
|
||||
def __init__(self, data, row, bcs):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._data = data
|
||||
self._row = row
|
||||
self._bcs = bcs
|
||||
self._bcs.reverse()
|
||||
|
||||
def undo(self):
|
||||
self._data.delete_i(
|
||||
range(self._row, self._row + len(self._bcs))
|
||||
)
|
||||
|
||||
def redo(self):
|
||||
for bc in self._bcs:
|
||||
self._data.insert(self._row, bc)
|
||||
|
||||
|
||||
class SortCommand(QUndoCommand):
|
||||
def __init__(self, data, _reverse):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._data = data
|
||||
self._reverse = _reverse
|
||||
|
||||
self._old = self._data.data
|
||||
self._indexes = None
|
||||
|
||||
def undo(self):
|
||||
ll = self._data.data
|
||||
self._data.sort(
|
||||
key=lambda x: self._indexes[ll.index(x)]
|
||||
)
|
||||
|
||||
def redo(self):
|
||||
self._data.sort(
|
||||
_reverse=self._reverse,
|
||||
key=lambda x: x[0]
|
||||
)
|
||||
if self._indexes is None:
|
||||
self._indexes = list(
|
||||
map(
|
||||
lambda p: self._old.index(p),
|
||||
self._data.data
|
||||
)
|
||||
)
|
||||
self._old = None
|
||||
@@ -0,0 +1,225 @@
|
||||
# Window.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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, trace
|
||||
|
||||
from View.Tools.PamhyrWindow import PamhyrWindow
|
||||
from View.Tools.PamhyrWidget import PamhyrWidget
|
||||
from View.Tools.PamhyrDelegate import PamhyrExTimeDelegate
|
||||
|
||||
from PyQt5.QtGui import (
|
||||
QKeySequence,
|
||||
)
|
||||
|
||||
from PyQt5 import QtCore
|
||||
from PyQt5.QtCore import (
|
||||
Qt, QVariant, QAbstractTableModel, QCoreApplication,
|
||||
pyqtSlot, pyqtSignal,
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QPushButton, QLineEdit,
|
||||
QFileDialog, QTableView, QAbstractItemView,
|
||||
QUndoStack, QShortcut, QAction, QItemDelegate,
|
||||
QHeaderView, QDoubleSpinBox, QVBoxLayout,
|
||||
)
|
||||
|
||||
from View.Tools.Plot.PamhyrCanvas import MplCanvas
|
||||
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
|
||||
|
||||
from View.BoundaryConditionsAdisTS.Edit.translate import BCETranslate
|
||||
from View.BoundaryConditionsAdisTS.Edit.Table import TableModel
|
||||
from View.BoundaryConditionsAdisTS.Edit.Plot import Plot
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class EditBoundaryConditionWindow(PamhyrWindow):
|
||||
_pamhyr_ui = "EditBoundaryConditionsAdisTS"
|
||||
_pamhyr_name = "Edit Boundary Conditions AdisTS"
|
||||
|
||||
def __init__(self, data=None, study=None, config=None, parent=None):
|
||||
self._data = data
|
||||
trad = BCETranslate()
|
||||
|
||||
name = trad[self._pamhyr_name]
|
||||
|
||||
super(EditBoundaryConditionWindow, self).__init__(
|
||||
title=name,
|
||||
study=study,
|
||||
config=config,
|
||||
trad=trad,
|
||||
parent=parent
|
||||
)
|
||||
|
||||
if self._data is not None:
|
||||
n = self._data.node
|
||||
node_name = next(filter(
|
||||
lambda x: x.id == n, self._study.river._nodes
|
||||
)).name
|
||||
name += (
|
||||
f" - {study.name} " +
|
||||
f"({node_name})"
|
||||
)
|
||||
|
||||
self._hash_data.append(data)
|
||||
|
||||
self.setup_table()
|
||||
self.setup_plot()
|
||||
self.setup_connections()
|
||||
|
||||
def setup_table(self):
|
||||
table_headers = self._trad.get_dict("table_headers")
|
||||
if self._data.type == "Concentration":
|
||||
self._data.header = ["time", "concentration"]
|
||||
else:
|
||||
self._data.header = ["time", "mass"]
|
||||
|
||||
headers = {}
|
||||
for h in self._data.header:
|
||||
headers[h] = table_headers[h]
|
||||
|
||||
self._delegate_time = PamhyrExTimeDelegate(
|
||||
data=self._data,
|
||||
mode=self._study.time_system,
|
||||
parent=self
|
||||
)
|
||||
|
||||
table = self.find(QTableView, "tableView")
|
||||
self._table = TableModel(
|
||||
table_view=table,
|
||||
table_headers=headers,
|
||||
editable_headers=self._data.header,
|
||||
delegates={
|
||||
# "time": self._delegate_time,
|
||||
},
|
||||
data=self._data,
|
||||
undo=self._undo_stack,
|
||||
opt_data=self._study.time_system
|
||||
)
|
||||
|
||||
table.setModel(self._table)
|
||||
table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||||
table.setAlternatingRowColors(True)
|
||||
|
||||
def setup_plot(self):
|
||||
self.canvas = MplCanvas(width=5, height=4, dpi=100)
|
||||
self.canvas.setObjectName("canvas")
|
||||
self.toolbar = PamhyrPlotToolbar(
|
||||
self.canvas, self
|
||||
)
|
||||
self.verticalLayout.addWidget(self.toolbar)
|
||||
self.verticalLayout.addWidget(self.canvas)
|
||||
|
||||
self.plot = Plot(
|
||||
canvas=self.canvas,
|
||||
data=self._data,
|
||||
mode=self._study.time_system,
|
||||
trad=self._trad,
|
||||
toolbar=self.toolbar,
|
||||
)
|
||||
self.plot.draw()
|
||||
|
||||
def setup_connections(self):
|
||||
self.find(QAction, "action_add").triggered.connect(self.add)
|
||||
self.find(QAction, "action_del").triggered.connect(self.delete)
|
||||
self.find(QAction, "action_sort").triggered.connect(self.sort)
|
||||
self._table.dataChanged.connect(self.update)
|
||||
|
||||
def update(self):
|
||||
self.plot.update()
|
||||
|
||||
def index_selected_row(self):
|
||||
table = self.find(QTableView, "tableView")
|
||||
return table.selectionModel()\
|
||||
.selectedRows()[0]\
|
||||
.row()
|
||||
|
||||
def index_selected_rows(self):
|
||||
table = self.find(QTableView, "tableView")
|
||||
return list(
|
||||
# Delete duplicate
|
||||
set(
|
||||
map(
|
||||
lambda i: i.row(),
|
||||
table.selectedIndexes()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def add(self):
|
||||
rows = self.index_selected_rows()
|
||||
if len(self._data) == 0 or len(rows) == 0:
|
||||
self._table.add(0)
|
||||
else:
|
||||
self._table.add(rows[0])
|
||||
|
||||
self.plot.update()
|
||||
|
||||
def delete(self):
|
||||
rows = self.index_selected_rows()
|
||||
if len(rows) == 0:
|
||||
return
|
||||
|
||||
self._table.delete(rows)
|
||||
self.plot.update()
|
||||
|
||||
def sort(self):
|
||||
self._table.sort(False)
|
||||
self.plot.update()
|
||||
|
||||
def _copy(self):
|
||||
rows = self.index_selected_rows()
|
||||
|
||||
table = []
|
||||
table.append(self._data.header)
|
||||
|
||||
data = self._data.data
|
||||
for row in rows:
|
||||
table.append(list(data[row]))
|
||||
|
||||
self.copyTableIntoClipboard(table)
|
||||
|
||||
def _paste(self):
|
||||
header, data = self.parseClipboardTable()
|
||||
|
||||
logger.debug(f"paste: h:{header}, d:{data}")
|
||||
|
||||
if len(data) == 0:
|
||||
return
|
||||
|
||||
row = 0
|
||||
rows = self.index_selected_rows()
|
||||
if len(rows) != 0:
|
||||
row = rows[0]
|
||||
|
||||
self._table.paste(row, header, data)
|
||||
self.plot.update()
|
||||
|
||||
def _undo(self):
|
||||
self._table.undo()
|
||||
self.plot.update()
|
||||
|
||||
def _redo(self):
|
||||
self._table.redo()
|
||||
self.plot.update()
|
||||
@@ -0,0 +1,42 @@
|
||||
# translate.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 -*-
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
from View.Translate import MainTranslate
|
||||
|
||||
from View.BoundaryCondition.translate import BCTranslate
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
|
||||
class BCETranslate(BCTranslate):
|
||||
def __init__(self):
|
||||
super(BCETranslate, self).__init__()
|
||||
|
||||
self._dict["Edit Boundary Conditions AdisTS"] = _translate(
|
||||
"BoundaryConditionsAdisTS", "Edit boundary conditions AdisTS"
|
||||
)
|
||||
|
||||
self._sub_dict["table_headers"] = {
|
||||
"time": self._dict["time"],
|
||||
"date": self._dict["date"],
|
||||
"mass": _translate("BoundaryConditionsAdisTS", "Mass"),
|
||||
"concentration": _translate("BoundaryConditionsAdisTS",
|
||||
"Concentration"),
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
# Table.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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
|
||||
import traceback
|
||||
|
||||
from tools import trace, timer
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
Qt, QVariant, QAbstractTableModel,
|
||||
QCoreApplication, QModelIndex, pyqtSlot,
|
||||
QRect,
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QPushButton, QLineEdit,
|
||||
QFileDialog, QTableView, QAbstractItemView,
|
||||
QUndoStack, QShortcut, QAction, QItemDelegate,
|
||||
QComboBox,
|
||||
)
|
||||
|
||||
from Model.BoundaryCondition.BoundaryConditionTypes import (
|
||||
NotDefined, PonctualContribution,
|
||||
TimeOverZ, TimeOverDischarge, ZOverDischarge
|
||||
)
|
||||
|
||||
from View.Tools.PamhyrTable import PamhyrTableModel
|
||||
|
||||
from View.BoundaryConditionsAdisTS.UndoCommand import (
|
||||
SetNodeCommand, SetTypeCommand,
|
||||
AddCommand, DelCommand, SetPolCommand
|
||||
)
|
||||
from View.BoundaryCondition.translate import BC_types
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
|
||||
class ComboBoxDelegate(QItemDelegate):
|
||||
def __init__(self, data=None, mode="type", tab="",
|
||||
trad=None, parent=None):
|
||||
super(ComboBoxDelegate, self).__init__(parent)
|
||||
|
||||
self._data = data
|
||||
self._mode = mode
|
||||
self._tab = tab
|
||||
self._trad = trad
|
||||
|
||||
self._long_types = {}
|
||||
if self._trad is not None:
|
||||
self._long_types = self._trad.get_dict("long_types")
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
self.editor = QComboBox(parent)
|
||||
|
||||
if self._mode == "type":
|
||||
lst = [self._trad["not_associated"], "Concentration", "Mass"]
|
||||
self.editor.addItems(
|
||||
lst
|
||||
)
|
||||
elif self._mode == "node":
|
||||
self.editor.addItems(
|
||||
[self._trad["not_associated"]] +
|
||||
self._data.nodes_names()
|
||||
)
|
||||
elif self._mode == "pol":
|
||||
lst = [p.name for p in self._data._Pollutants.Pollutants_List]
|
||||
self.editor.addItems(
|
||||
[self._trad["not_associated"]] +
|
||||
lst
|
||||
)
|
||||
|
||||
self.editor.setCurrentText(index.data(Qt.DisplayRole))
|
||||
return self.editor
|
||||
|
||||
def setEditorData(self, editor, index):
|
||||
value = index.data(Qt.DisplayRole)
|
||||
self.editor.currentTextChanged.connect(self.currentItemChanged)
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
text = str(editor.currentText())
|
||||
model.setData(index, text)
|
||||
editor.close()
|
||||
editor.deleteLater()
|
||||
|
||||
def updateEditorGeometry(self, editor, option, index):
|
||||
r = QRect(option.rect)
|
||||
if self.editor.windowFlags() & Qt.Popup:
|
||||
if editor.parent() is not None:
|
||||
r.setTopLeft(self.editor.parent().mapToGlobal(r.topLeft()))
|
||||
editor.setGeometry(r)
|
||||
|
||||
@pyqtSlot()
|
||||
def currentItemChanged(self):
|
||||
self.commitData.emit(self.sender())
|
||||
|
||||
|
||||
class TableModel(PamhyrTableModel):
|
||||
def __init__(self, pollutant=None, bc_list=None, trad=None, **kwargs):
|
||||
self._trad = trad
|
||||
self._bc_list = bc_list
|
||||
self._pollutant = pollutant
|
||||
|
||||
super(TableModel, self).__init__(trad=trad, **kwargs)
|
||||
|
||||
def rowCount(self, parent):
|
||||
return len(self._bc_list)
|
||||
|
||||
def data(self, index, role):
|
||||
|
||||
if role != Qt.ItemDataRole.DisplayRole:
|
||||
return QVariant()
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
if self._headers[column] == "type":
|
||||
n = self._bc_list.get(row).type
|
||||
if n is None or n == "":
|
||||
return self._trad["not_associated"]
|
||||
return n
|
||||
elif self._headers[column] == "node":
|
||||
n = self._bc_list.get(row).node
|
||||
if n is None:
|
||||
return self._trad["not_associated"]
|
||||
tmp = next(filter(lambda x: x.id == n, self._data._nodes), None)
|
||||
if tmp is not None:
|
||||
return tmp.name
|
||||
else:
|
||||
return self._trad["not_associated"]
|
||||
elif self._headers[column] == "pol":
|
||||
n = self._bc_list.get(row).pollutant
|
||||
if n is None or n == "not_associated" or n == "":
|
||||
return self._trad["not_associated"]
|
||||
tmp = next(filter(lambda x: x.id == n,
|
||||
self._data._Pollutants.Pollutants_List
|
||||
),
|
||||
None)
|
||||
if tmp is not None:
|
||||
return tmp.name
|
||||
else:
|
||||
return self._trad["not_associated"]
|
||||
|
||||
return QVariant()
|
||||
|
||||
def setData(self, index, value, role=Qt.EditRole):
|
||||
if not index.isValid() or role != Qt.EditRole:
|
||||
return False
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
try:
|
||||
if self._headers[column] == "type":
|
||||
self._undo.push(
|
||||
SetTypeCommand(
|
||||
self._bc_list, row, value
|
||||
)
|
||||
)
|
||||
elif self._headers[column] == "node":
|
||||
self._undo.push(
|
||||
SetNodeCommand(
|
||||
self._bc_list, row, self._data.node(value)
|
||||
)
|
||||
)
|
||||
elif self._headers[column] == "pol":
|
||||
if value == self._trad["not_associated"]:
|
||||
self._undo.push(
|
||||
SetPolCommand(
|
||||
self._bc_list, row, None
|
||||
)
|
||||
)
|
||||
else:
|
||||
pol = next(filter(lambda x: x.name == value,
|
||||
self._data._Pollutants.Pollutants_List)
|
||||
)
|
||||
self._undo.push(
|
||||
SetPolCommand(
|
||||
self._bc_list, row, pol.id
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.info(e)
|
||||
logger.debug(traceback.format_exc())
|
||||
|
||||
self.dataChanged.emit(index, index)
|
||||
return True
|
||||
|
||||
def add(self, row, parent=QModelIndex()):
|
||||
self.beginInsertRows(parent, row, row - 1)
|
||||
|
||||
self._undo.push(
|
||||
AddCommand(
|
||||
self._pollutant, self._bc_list, row
|
||||
)
|
||||
)
|
||||
|
||||
self.endInsertRows()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def delete(self, rows, parent=QModelIndex()):
|
||||
self.beginRemoveRows(parent, rows[0], rows[-1])
|
||||
|
||||
self._undo.push(
|
||||
DelCommand(
|
||||
self._bc_list, rows
|
||||
)
|
||||
)
|
||||
|
||||
self.endRemoveRows()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def undo(self):
|
||||
self._undo.undo()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def redo(self):
|
||||
self._undo.redo()
|
||||
self.layoutChanged.emit()
|
||||
@@ -0,0 +1,117 @@
|
||||
# UndoCommand.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 -*-
|
||||
|
||||
from copy import deepcopy
|
||||
from tools import trace, timer
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QMessageBox, QUndoCommand, QUndoStack,
|
||||
)
|
||||
|
||||
from Model.BoundaryConditionsAdisTS.BoundaryConditionAdisTS \
|
||||
import BoundaryConditionAdisTS
|
||||
from Model.BoundaryConditionsAdisTS.BoundaryConditionsAdisTSList \
|
||||
import BoundaryConditionsAdisTSList
|
||||
|
||||
|
||||
class SetNodeCommand(QUndoCommand):
|
||||
def __init__(self, bcs, index, node):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._bcs = bcs
|
||||
self._index = index
|
||||
self._old = self._bcs.get(self._index).node
|
||||
self._new = node.id
|
||||
|
||||
def undo(self):
|
||||
self._bcs.get(self._index).node = self._old
|
||||
|
||||
def redo(self):
|
||||
self._bcs.get(self._index).node = self._new
|
||||
|
||||
|
||||
class SetTypeCommand(QUndoCommand):
|
||||
def __init__(self, bcs, index, _type):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._bcs = bcs
|
||||
self._index = index
|
||||
self._type = _type
|
||||
self._old = self._bcs.get(self._index).type
|
||||
self._new = self._type
|
||||
|
||||
def undo(self):
|
||||
self._bcs.get(self._index).type = self._old
|
||||
|
||||
def redo(self):
|
||||
self._bcs.get(self._index).type = self._new
|
||||
|
||||
|
||||
class SetPolCommand(QUndoCommand):
|
||||
def __init__(self, bcs, index, pollutant):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._bcs = bcs
|
||||
self._index = index
|
||||
self._old = self._bcs.get(self._index).pollutant
|
||||
self._new = pollutant
|
||||
|
||||
def undo(self):
|
||||
self._bcs.get(self._index).pollutant = self._old
|
||||
|
||||
def redo(self):
|
||||
self._bcs.get(self._index).pollutant = self._new
|
||||
|
||||
|
||||
class AddCommand(QUndoCommand):
|
||||
def __init__(self, pollutant, bcs, index):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._bcs = bcs
|
||||
self._pollutant = pollutant
|
||||
self._index = index
|
||||
self._new = None
|
||||
|
||||
def undo(self):
|
||||
self._bcs.delete_i([self._index])
|
||||
|
||||
def redo(self):
|
||||
if self._new is None:
|
||||
self._new = self._bcs.new(self._index, self._pollutant)
|
||||
else:
|
||||
self._bcs.insert(self._index, self._new)
|
||||
|
||||
|
||||
class DelCommand(QUndoCommand):
|
||||
def __init__(self, bcs, rows):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._bcs = bcs
|
||||
self._rows = rows
|
||||
|
||||
self._bc = []
|
||||
for row in rows:
|
||||
self._bc.append((row, self._bcs.get(row)))
|
||||
self._bc.sort()
|
||||
|
||||
def undo(self):
|
||||
for row, el in self._bc:
|
||||
self._bcs.insert(row, el)
|
||||
|
||||
def redo(self):
|
||||
self._bcs.delete_i(self._rows)
|
||||
@@ -0,0 +1,199 @@
|
||||
# Window.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 trace, timer, logger_exception
|
||||
|
||||
from View.Tools.PamhyrWindow import PamhyrWindow
|
||||
|
||||
from PyQt5.QtGui import (
|
||||
QKeySequence,
|
||||
)
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
Qt, QVariant, QAbstractTableModel,
|
||||
QCoreApplication, QModelIndex, pyqtSlot,
|
||||
QRect,
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QPushButton, QLineEdit,
|
||||
QFileDialog, QTableView, QAbstractItemView,
|
||||
QUndoStack, QShortcut, QAction, QItemDelegate,
|
||||
QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from View.BoundaryConditionsAdisTS.Table import (
|
||||
TableModel, ComboBoxDelegate
|
||||
)
|
||||
|
||||
from View.Network.GraphWidget import GraphWidget
|
||||
from View.BoundaryConditionsAdisTS.translate import BCAdisTSTranslate
|
||||
from View.BoundaryConditionsAdisTS.Edit.Window \
|
||||
import EditBoundaryConditionWindow
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class BoundaryConditionAdisTSWindow(PamhyrWindow):
|
||||
_pamhyr_ui = "BoundaryConditionsAdisTS"
|
||||
_pamhyr_name = "Boundary conditions AdisTS"
|
||||
|
||||
def __init__(self, study=None, config=None, parent=None):
|
||||
trad = BCAdisTSTranslate()
|
||||
name = (
|
||||
trad[self._pamhyr_name] +
|
||||
" - " + study.name
|
||||
)
|
||||
|
||||
super(BoundaryConditionAdisTSWindow, self).__init__(
|
||||
title=name,
|
||||
study=study,
|
||||
config=config,
|
||||
trad=trad,
|
||||
parent=parent
|
||||
)
|
||||
|
||||
self._pollutants_lst = self._study._river._Pollutants
|
||||
self._bcs = self._study.river.boundary_conditions_adists
|
||||
|
||||
self.setup_graph()
|
||||
self.setup_table()
|
||||
self.setup_connections()
|
||||
|
||||
self.ui.setWindowTitle(self._title)
|
||||
|
||||
def setup_table(self):
|
||||
self._delegate_type = ComboBoxDelegate(
|
||||
trad=self._trad,
|
||||
data=self._study.river,
|
||||
mode="type",
|
||||
parent=self
|
||||
)
|
||||
self._delegate_node = ComboBoxDelegate(
|
||||
trad=self._trad,
|
||||
data=self._study.river,
|
||||
mode="node",
|
||||
parent=self
|
||||
)
|
||||
self._delegate_pol = ComboBoxDelegate(
|
||||
trad=self._trad,
|
||||
data=self._study.river,
|
||||
mode="pol",
|
||||
parent=self
|
||||
)
|
||||
|
||||
table = self.find(QTableView, f"tableView")
|
||||
self._table = TableModel(
|
||||
table_view=table,
|
||||
table_headers=self._trad.get_dict("table_headers"),
|
||||
editable_headers=["type", "node", "pol"],
|
||||
delegates={
|
||||
"type": self._delegate_type,
|
||||
"node": self._delegate_node,
|
||||
"pol": self._delegate_pol,
|
||||
},
|
||||
trad=self._trad,
|
||||
bc_list=self._study.river.boundary_conditions_adists,
|
||||
undo=self._undo_stack,
|
||||
data=self._study.river
|
||||
)
|
||||
table.setModel(self._table)
|
||||
table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||||
table.setAlternatingRowColors(True)
|
||||
|
||||
def setup_graph(self):
|
||||
self.graph_widget = GraphWidget(
|
||||
self._study.river,
|
||||
min_size=None, size=(200, 200),
|
||||
only_display=True,
|
||||
parent=self
|
||||
)
|
||||
self.graph_layout = self.find(QVBoxLayout, "verticalLayout")
|
||||
self.graph_layout.addWidget(self.graph_widget)
|
||||
|
||||
def setup_connections(self):
|
||||
self.find(QAction, "action_add").triggered.connect(self.add)
|
||||
self.find(QAction, "action_del").triggered.connect(self.delete)
|
||||
self.find(QAction, "action_edit").triggered.connect(self.edit)
|
||||
|
||||
def index_selected_row(self):
|
||||
table = self.find(QTableView, f"tableView")
|
||||
return table.selectionModel()\
|
||||
.selectedRows()[0]\
|
||||
.row()
|
||||
|
||||
def index_selected_rows(self):
|
||||
table = self.find(QTableView, f"tableView")
|
||||
return list(
|
||||
# Delete duplicate
|
||||
set(
|
||||
map(
|
||||
lambda i: i.row(),
|
||||
table.selectedIndexes()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def add(self):
|
||||
self._table.add(len(self._bcs))
|
||||
|
||||
def delete(self):
|
||||
rows = self.index_selected_rows()
|
||||
if len(rows) == 0:
|
||||
return
|
||||
|
||||
self._table.delete(rows)
|
||||
|
||||
def _copy(self):
|
||||
logger.info("TODO: copy")
|
||||
|
||||
def _paste(self):
|
||||
logger.info("TODO: paste")
|
||||
|
||||
def _undo(self):
|
||||
self._table.undo()
|
||||
|
||||
def _redo(self):
|
||||
self._table.redo()
|
||||
|
||||
def edit(self):
|
||||
rows = self.index_selected_rows()
|
||||
for row in rows:
|
||||
data = self._bcs.get(row)
|
||||
|
||||
if data.node is None:
|
||||
continue
|
||||
|
||||
if self.sub_window_exists(
|
||||
EditBoundaryConditionWindow,
|
||||
data=[self._study, None, data]
|
||||
):
|
||||
continue
|
||||
|
||||
win = EditBoundaryConditionWindow(
|
||||
data=data,
|
||||
study=self._study,
|
||||
parent=self
|
||||
)
|
||||
win.show()
|
||||
@@ -0,0 +1,38 @@
|
||||
# translate.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 -*-
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
from View.Translate import MainTranslate
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
|
||||
class BCAdisTSTranslate(MainTranslate):
|
||||
def __init__(self):
|
||||
super(BCAdisTSTranslate, self).__init__()
|
||||
|
||||
self._dict["Boundary conditions AdisTS"] = _translate(
|
||||
"BoundaryConditionsAdisTS", "Boundary conditions AdisTS"
|
||||
)
|
||||
|
||||
self._sub_dict["table_headers"] = {
|
||||
"type": self._dict["type"],
|
||||
"node": _translate("BoundaryCondition", "Node"),
|
||||
"pol": _translate("BoundaryCondition", "Pollutant")
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
# WindowAdisTS.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 -*-
|
||||
|
||||
from tools import trace, timer
|
||||
|
||||
from View.Tools.PamhyrWindow import PamhyrWindow
|
||||
|
||||
from PyQt5.QtGui import (
|
||||
QKeySequence,
|
||||
)
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
Qt, QVariant, QAbstractTableModel,
|
||||
QCoreApplication, QModelIndex, QRect, QThread,
|
||||
pyqtSlot, pyqtSignal,
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QPushButton, QLineEdit,
|
||||
QFileDialog, QTableView, QAbstractItemView,
|
||||
QUndoStack, QShortcut, QAction, QItemDelegate,
|
||||
QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
|
||||
QProgressBar, QLabel,
|
||||
)
|
||||
|
||||
from View.CheckList.Table import TableModel
|
||||
from View.CheckList.Worker import Worker
|
||||
from View.CheckList.Translate import CheckListTranslate
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
|
||||
class CheckListWindowAdisTS(PamhyrWindow):
|
||||
_pamhyr_ui = "CheckList"
|
||||
_pamhyr_name = "Check list"
|
||||
|
||||
signalStatus = pyqtSignal(str)
|
||||
|
||||
def __init__(self, autorun: bool = True,
|
||||
study=None, config=None,
|
||||
solver=None, parent=None, mage_rep=None):
|
||||
trad = CheckListTranslate()
|
||||
|
||||
self._autorun = autorun
|
||||
self._solver = solver
|
||||
self._mage_rep = mage_rep
|
||||
|
||||
name = trad[self._pamhyr_name] + " - " + study.name
|
||||
|
||||
super(CheckListWindowAdisTS, self).__init__(
|
||||
title=name,
|
||||
study=study,
|
||||
config=config,
|
||||
trad=trad,
|
||||
options=[],
|
||||
parent=parent
|
||||
)
|
||||
|
||||
# Add solver to hash computation data
|
||||
self._hash_data.append(self._solver)
|
||||
|
||||
self._checker_list = (
|
||||
self._study.checkers() +
|
||||
self._solver.checkers()
|
||||
)
|
||||
|
||||
self.setup_table()
|
||||
self.setup_progress_bar()
|
||||
self.setup_connections()
|
||||
self.setup_thread()
|
||||
self.setup_statusbar()
|
||||
|
||||
def setup_table(self):
|
||||
table = self.find(QTableView, f"tableView")
|
||||
self._table = TableModel(
|
||||
table_view=table,
|
||||
table_headers=self._trad.get_dict("table_headers"),
|
||||
data=self._checker_list,
|
||||
)
|
||||
|
||||
def setup_progress_bar(self):
|
||||
self._progress = self.find(QProgressBar, f"progressBar")
|
||||
self._p = 0 # Progress current step
|
||||
|
||||
self._progress.setRange(0, len(self._checker_list))
|
||||
self._progress.setValue(self._p)
|
||||
|
||||
def setup_connections(self):
|
||||
self.find(QPushButton, "pushButton_ok").clicked.connect(self.accept)
|
||||
self.find(QPushButton, "pushButton_retry").clicked.connect(self.retry)
|
||||
self.find(QPushButton, "pushButton_cancel")\
|
||||
.clicked.connect(self.reject)
|
||||
|
||||
def setup_thread(self):
|
||||
self._worker = Worker(self._study, self._checker_list)
|
||||
self._worker_thread = QThread()
|
||||
self._worker.moveToThread(self._worker_thread)
|
||||
|
||||
# Connect any worker signals
|
||||
self._worker.signalStatus.connect(self.update)
|
||||
self._worker_thread.started.connect(self._worker.process)
|
||||
|
||||
self._worker_thread.start()
|
||||
|
||||
def retry(self):
|
||||
self._worker_thread.terminate()
|
||||
self._worker_thread.wait()
|
||||
|
||||
self.find(QPushButton, "pushButton_retry").setEnabled(False)
|
||||
self.find(QPushButton, "pushButton_ok").setEnabled(False)
|
||||
|
||||
self.setup_thread()
|
||||
|
||||
def _compute_status(self):
|
||||
ok = len(list(filter(lambda c: c.is_ok(), self._checker_list)))
|
||||
warning = len(
|
||||
list(filter(lambda c: c.is_warning(), self._checker_list)))
|
||||
error = len(list(filter(lambda c: c.is_error(), self._checker_list)))
|
||||
|
||||
return ok, warning, error
|
||||
|
||||
def _compute_status_label(self):
|
||||
ok, warning, error = self._compute_status()
|
||||
return (f"<font color=\"Green\">Ok: {ok} </font> |" +
|
||||
f"<font color=\"Orange\">Warning: {warning} </font> |" +
|
||||
f"<font color=\"Red\">Error: {error}</font>")
|
||||
|
||||
def setup_statusbar(self):
|
||||
txt = self._compute_status_label()
|
||||
self._status_label = QLabel(txt)
|
||||
self.statusbar.addPermanentWidget(self._status_label)
|
||||
|
||||
def update_statusbar(self):
|
||||
txt = self._compute_status_label()
|
||||
self._status_label.setText(txt)
|
||||
|
||||
def progress(self):
|
||||
self._p += 1
|
||||
self._progress.setValue(self._p)
|
||||
self._table.update()
|
||||
|
||||
def start_compute(self):
|
||||
self._p = 0
|
||||
self._progress.setValue(self._p)
|
||||
|
||||
def info_compute(self, str):
|
||||
self.statusbar.showMessage(str, 3000)
|
||||
|
||||
def end_compute(self):
|
||||
self._table.layoutChanged.emit()
|
||||
self.find(QPushButton, "pushButton_retry").setEnabled(True)
|
||||
|
||||
errors = any(filter(lambda c: c.is_error(), self._checker_list))
|
||||
if not errors:
|
||||
self.find(QPushButton, "pushButton_ok").setEnabled(True)
|
||||
if self._autorun:
|
||||
self._parent.solver_log_adists(self._solver, self._mage_rep)
|
||||
self.end()
|
||||
|
||||
self.update_statusbar()
|
||||
|
||||
def update(self, key: str):
|
||||
if key == "start":
|
||||
self.start_compute()
|
||||
self.info_compute("Starting ...")
|
||||
elif key == "end":
|
||||
self.info_compute("Finish")
|
||||
self.end_compute()
|
||||
elif key == "progress":
|
||||
self.progress()
|
||||
else:
|
||||
self.info_compute(key)
|
||||
|
||||
self.update_statusbar()
|
||||
|
||||
def end(self):
|
||||
# self._worker.join()b
|
||||
self.close()
|
||||
|
||||
def reject(self):
|
||||
self.end()
|
||||
|
||||
def accept(self):
|
||||
self._parent.solver_log_adists(self._solver, self._mage_rep)
|
||||
# self.end()
|
||||
@@ -0,0 +1,221 @@
|
||||
# Table.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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
|
||||
import traceback
|
||||
from tools import trace, timer
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
Qt, QVariant, QAbstractTableModel,
|
||||
QCoreApplication, QModelIndex, pyqtSlot,
|
||||
QRect,
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QPushButton, QLineEdit,
|
||||
QFileDialog, QTableView, QAbstractItemView,
|
||||
QUndoStack, QShortcut, QAction, QItemDelegate,
|
||||
QComboBox,
|
||||
)
|
||||
|
||||
from View.Tools.PamhyrTable import PamhyrTableModel
|
||||
|
||||
from View.D90AdisTS.UndoCommand import (
|
||||
SetCommand, AddCommand, SetCommandSpec,
|
||||
DelCommand,
|
||||
)
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
|
||||
class ComboBoxDelegate(QItemDelegate):
|
||||
def __init__(self, data=None, ic_spec_lst=None,
|
||||
trad=None, parent=None, mode="reaches"):
|
||||
super(ComboBoxDelegate, self).__init__(parent)
|
||||
|
||||
self._data = data
|
||||
self._mode = mode
|
||||
self._trad = trad
|
||||
self._ic_spec_lst = ic_spec_lst
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
self.editor = QComboBox(parent)
|
||||
|
||||
val = []
|
||||
if self._mode == "rk":
|
||||
reach_id = self._ic_spec_lst[index.row()].reach
|
||||
|
||||
reach = next(filter(
|
||||
lambda edge: edge.id == reach_id, self._data.edges()
|
||||
))
|
||||
|
||||
if reach_id is not None:
|
||||
val = list(
|
||||
map(
|
||||
lambda rk: str(rk), reach.reach.get_rk()
|
||||
)
|
||||
)
|
||||
else:
|
||||
val = list(
|
||||
map(
|
||||
lambda n: n.name, self._data.edges()
|
||||
)
|
||||
)
|
||||
|
||||
self.editor.addItems(
|
||||
[self._trad['not_associated']] +
|
||||
val
|
||||
)
|
||||
|
||||
self.editor.setCurrentText(str(index.data(Qt.DisplayRole)))
|
||||
return self.editor
|
||||
|
||||
def setEditorData(self, editor, index):
|
||||
value = index.data(Qt.DisplayRole)
|
||||
self.editor.currentTextChanged.connect(self.currentItemChanged)
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
text = str(editor.currentText())
|
||||
model.setData(index, text)
|
||||
editor.close()
|
||||
editor.deleteLater()
|
||||
|
||||
def updateEditorGeometry(self, editor, option, index):
|
||||
r = QRect(option.rect)
|
||||
if self.editor.windowFlags() & Qt.Popup:
|
||||
if editor.parent() is not None:
|
||||
r.setTopLeft(self.editor.parent().mapToGlobal(r.topLeft()))
|
||||
editor.setGeometry(r)
|
||||
|
||||
@pyqtSlot()
|
||||
def currentItemChanged(self):
|
||||
self.commitData.emit(self.sender())
|
||||
|
||||
|
||||
class D90TableModel(PamhyrTableModel):
|
||||
def __init__(self, river=None, data=None, **kwargs):
|
||||
self._river = river
|
||||
|
||||
super(D90TableModel, self).__init__(data=data, **kwargs)
|
||||
|
||||
self._data = data
|
||||
|
||||
def _setup_lst(self):
|
||||
self._lst = self._data._data
|
||||
|
||||
def rowCount(self, parent):
|
||||
return len(self._lst)
|
||||
|
||||
def data(self, index, role):
|
||||
if role != Qt.ItemDataRole.DisplayRole:
|
||||
return QVariant()
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
if self._headers[column] == "name":
|
||||
n = self._lst[row].name
|
||||
if n is None or n == "":
|
||||
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
|
||||
elif self._headers[column] == "start_rk":
|
||||
n = self._lst[row].start_rk
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
elif self._headers[column] == "end_rk":
|
||||
n = self._lst[row].end_rk
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
elif self._headers[column] == "d90":
|
||||
n = self._lst[row].d90
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
|
||||
return QVariant()
|
||||
|
||||
def setData(self, index, value, role=Qt.EditRole):
|
||||
if not index.isValid() or role != Qt.EditRole:
|
||||
return False
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
try:
|
||||
if self._headers[column] != "reach":
|
||||
self._undo.push(
|
||||
SetCommandSpec(
|
||||
self._lst, row, self._headers[column], value
|
||||
)
|
||||
)
|
||||
elif self._headers[column] == "reach":
|
||||
self._undo.push(
|
||||
SetCommandSpec(
|
||||
self._lst, row, self._headers[column],
|
||||
self._river.edge(value).id
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.info(e)
|
||||
logger.debug(traceback.format_exc())
|
||||
|
||||
self.dataChanged.emit(index, index)
|
||||
return True
|
||||
|
||||
def add(self, row, parent=QModelIndex()):
|
||||
self.beginInsertRows(parent, row, row - 1)
|
||||
|
||||
self._undo.push(
|
||||
AddCommand(
|
||||
self._data, self._lst, row
|
||||
)
|
||||
)
|
||||
|
||||
self.endInsertRows()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def delete(self, rows, parent=QModelIndex()):
|
||||
self.beginRemoveRows(parent, rows[0], rows[-1])
|
||||
|
||||
self._undo.push(
|
||||
DelCommand(
|
||||
self._data, self._lst, rows
|
||||
)
|
||||
)
|
||||
|
||||
self.endRemoveRows()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def undo(self):
|
||||
self._undo.undo()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def redo(self):
|
||||
self._undo.redo()
|
||||
self.layoutChanged.emit()
|
||||
@@ -0,0 +1,95 @@
|
||||
# Table.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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
|
||||
import traceback
|
||||
from tools import trace, timer
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
Qt, QVariant, QAbstractTableModel,
|
||||
QCoreApplication, QModelIndex, pyqtSlot,
|
||||
QRect,
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QPushButton, QLineEdit,
|
||||
QFileDialog, QTableView, QAbstractItemView,
|
||||
QUndoStack, QShortcut, QAction, QItemDelegate,
|
||||
QComboBox,
|
||||
)
|
||||
|
||||
from View.Tools.PamhyrTable import PamhyrTableModel
|
||||
|
||||
from View.D90AdisTS.UndoCommand import (
|
||||
SetCommand,
|
||||
)
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
|
||||
class D90TableDefaultModel(PamhyrTableModel):
|
||||
def __init__(self, **kwargs):
|
||||
super(D90TableDefaultModel, self).__init__(**kwargs)
|
||||
|
||||
def data(self, index, role):
|
||||
if role != Qt.ItemDataRole.DisplayRole:
|
||||
return QVariant()
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
if self._headers[column] is "name":
|
||||
return self._data[row].name
|
||||
elif self._headers[column] is "d90":
|
||||
n = self._data[row].d90
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
|
||||
return QVariant()
|
||||
|
||||
def setData(self, index, value, role=Qt.EditRole):
|
||||
if not index.isValid() or role != Qt.EditRole:
|
||||
return False
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
try:
|
||||
if self._headers[column] is not None:
|
||||
self._undo.push(
|
||||
SetCommand(
|
||||
self._data, row, self._headers[column], value
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.info(e)
|
||||
logger.debug(traceback.format_exc())
|
||||
|
||||
self.dataChanged.emit(index, index)
|
||||
return True
|
||||
|
||||
def undo(self):
|
||||
self._undo.undo()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def redo(self):
|
||||
self._undo.redo()
|
||||
self.layoutChanged.emit()
|
||||
@@ -0,0 +1,152 @@
|
||||
# UndoCommand.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 -*-
|
||||
|
||||
from copy import deepcopy
|
||||
from tools import trace, timer
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QMessageBox, QUndoCommand, QUndoStack,
|
||||
)
|
||||
|
||||
from Model.D90AdisTS.D90AdisTS import D90AdisTS
|
||||
from Model.D90AdisTS.D90AdisTSList import D90AdisTSList
|
||||
|
||||
|
||||
class SetCommand(QUndoCommand):
|
||||
def __init__(self, data, row, column, 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 == "d90":
|
||||
self._old = self._data[self._row].d90
|
||||
|
||||
_type = float
|
||||
if column == "name":
|
||||
_type = str
|
||||
|
||||
self._new = _type(new_value)
|
||||
|
||||
def undo(self):
|
||||
if self._column == "name":
|
||||
self._data[self._row].name = self._old
|
||||
elif self._column == "d90":
|
||||
self._data[self._row].d90 = self._old
|
||||
|
||||
def redo(self):
|
||||
if self._column == "name":
|
||||
self._data[self._row].name = self._new
|
||||
elif self._column == "d90":
|
||||
self._data[self._row].d90 = self._new
|
||||
|
||||
|
||||
class SetCommandSpec(QUndoCommand):
|
||||
def __init__(self, data, row, column, 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 == "d90":
|
||||
self._old = self._data[self._row].d90
|
||||
|
||||
_type = float
|
||||
if column == "name":
|
||||
_type = str
|
||||
elif column == "reach":
|
||||
_type = int
|
||||
|
||||
self._new = _type(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 == "d90":
|
||||
self._data[self._row].d90 = 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 == "d90":
|
||||
self._data[self._row].d90 = self._new
|
||||
|
||||
|
||||
class AddCommand(QUndoCommand):
|
||||
def __init__(self, data, ics_spec, index):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._data = data
|
||||
self._ics_spec = ics_spec
|
||||
self._index = index
|
||||
self._new = None
|
||||
|
||||
def undo(self):
|
||||
self._data.delete_i([self._index])
|
||||
|
||||
def redo(self):
|
||||
if self._new is None:
|
||||
self._new = self._data.new(self._index)
|
||||
else:
|
||||
self._data.insert(self._index, self._new)
|
||||
|
||||
|
||||
class DelCommand(QUndoCommand):
|
||||
def __init__(self, data, ics_spec, rows):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._data = data
|
||||
self._ics_spec = ics_spec
|
||||
self._rows = rows
|
||||
# self._data = data
|
||||
|
||||
self._ic = []
|
||||
for row in rows:
|
||||
self._ic.append((row, self._ics_spec[row]))
|
||||
self._ic.sort()
|
||||
|
||||
def undo(self):
|
||||
for row, el in self._ic:
|
||||
self._data.insert(row, el)
|
||||
|
||||
def redo(self):
|
||||
self._data.delete_i(self._rows)
|
||||
@@ -0,0 +1,285 @@
|
||||
# Window.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 os
|
||||
import logging
|
||||
|
||||
from tools import trace, timer, logger_exception
|
||||
|
||||
from View.Tools.PamhyrWindow import PamhyrWindow
|
||||
|
||||
from PyQt5.QtGui import (
|
||||
QKeySequence, QIcon,
|
||||
)
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
Qt, QVariant, QAbstractTableModel,
|
||||
QCoreApplication, QModelIndex, pyqtSlot,
|
||||
QRect, QItemSelectionModel,
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QPushButton, QLineEdit,
|
||||
QFileDialog, QTableView, QAbstractItemView,
|
||||
QUndoStack, QShortcut, QAction, QItemDelegate,
|
||||
QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
|
||||
QVBoxLayout, QToolBar, QAction, QToolButton,
|
||||
)
|
||||
|
||||
from Modules import Modules
|
||||
|
||||
from View.InitialConditionsAdisTS.UndoCommand import (
|
||||
SetCommand,
|
||||
)
|
||||
|
||||
from View.D90AdisTS.TableDefault import (
|
||||
D90TableDefaultModel,
|
||||
)
|
||||
|
||||
from View.D90AdisTS.Table import (
|
||||
D90TableModel, ComboBoxDelegate,
|
||||
)
|
||||
|
||||
from View.D90AdisTS.translate import D90AdisTSTranslate
|
||||
|
||||
from Solver.Mage import Mage8
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class D90AdisTSWindow(PamhyrWindow):
|
||||
_pamhyr_ui = "D90AdisTS"
|
||||
_pamhyr_name = "D90 AdisTS"
|
||||
|
||||
def __init__(self, data=None, study=None, config=None, parent=None):
|
||||
self._data = []
|
||||
self._data.append(data)
|
||||
trad = D90AdisTSTranslate()
|
||||
|
||||
name = (
|
||||
trad[self._pamhyr_name] +
|
||||
" - " + study.name +
|
||||
" - " + self._data[0].name
|
||||
)
|
||||
|
||||
super(D90AdisTSWindow, self).__init__(
|
||||
title=name,
|
||||
study=study,
|
||||
config=config,
|
||||
trad=trad,
|
||||
parent=parent
|
||||
)
|
||||
|
||||
self._hash_data.append(data)
|
||||
|
||||
self._d90_adists_lst = study.river.d90_adists
|
||||
|
||||
self.setup_table()
|
||||
|
||||
self.ui.setWindowTitle(self._title)
|
||||
|
||||
def setup_table(self):
|
||||
|
||||
path_icons = os.path.join(self._get_ui_directory(), f"ressources")
|
||||
|
||||
table_default = self.find(QTableView, f"tableView")
|
||||
|
||||
self._table = D90TableDefaultModel(
|
||||
table_view=table_default,
|
||||
table_headers=self._trad.get_dict("table_headers"),
|
||||
editable_headers=["name", "d90"],
|
||||
delegates={},
|
||||
data=self._data,
|
||||
undo=self._undo_stack,
|
||||
trad=self._trad
|
||||
)
|
||||
|
||||
table_default.setModel(self._table)
|
||||
table_default.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
table_default.horizontalHeader().setSectionResizeMode(
|
||||
QHeaderView.Stretch
|
||||
)
|
||||
table_default.setAlternatingRowColors(True)
|
||||
|
||||
layout = self.find(QVBoxLayout, f"verticalLayout_1")
|
||||
toolBar = QToolBar()
|
||||
layout.addWidget(toolBar)
|
||||
|
||||
action_add = QAction(self)
|
||||
action_add.setIcon(QIcon(os.path.join(path_icons, f"add.png")))
|
||||
action_add.triggered.connect(self.add)
|
||||
action_delete = QAction(self)
|
||||
action_delete.setIcon(QIcon(os.path.join(path_icons, f"del.png")))
|
||||
action_delete.triggered.connect(self.delete)
|
||||
|
||||
toolBar.addAction(action_add)
|
||||
toolBar.addAction(action_delete)
|
||||
|
||||
self.table_spec = QTableView()
|
||||
layout.addWidget(self.table_spec)
|
||||
|
||||
self._delegate_reach = ComboBoxDelegate(
|
||||
trad=self._trad,
|
||||
data=self._study.river,
|
||||
ic_spec_lst=self._data[0]._data,
|
||||
parent=self,
|
||||
mode="reaches"
|
||||
)
|
||||
self._delegate_rk = ComboBoxDelegate(
|
||||
trad=self._trad,
|
||||
data=self._study.river,
|
||||
ic_spec_lst=self._data[0]._data,
|
||||
parent=self,
|
||||
mode="rk"
|
||||
)
|
||||
|
||||
self._table_spec = D90TableModel(
|
||||
table_view=self.table_spec,
|
||||
table_headers=self._trad.get_dict("table_headers_spec"),
|
||||
editable_headers=["name", "reach", "start_rk", "end_rk", "d90"],
|
||||
delegates={
|
||||
"reach": self._delegate_reach,
|
||||
"start_rk": self._delegate_rk,
|
||||
"end_rk": self._delegate_rk
|
||||
},
|
||||
data=self._data[0],
|
||||
undo=self._undo_stack,
|
||||
trad=self._trad,
|
||||
river=self._study.river
|
||||
)
|
||||
|
||||
self.table_spec.setModel(self._table_spec)
|
||||
self.table_spec.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.table_spec.horizontalHeader().setSectionResizeMode(
|
||||
QHeaderView.Stretch
|
||||
)
|
||||
self.table_spec.setAlternatingRowColors(True)
|
||||
|
||||
selectionModel = self.table_spec.selectionModel()
|
||||
index = self.table_spec.model().index(0, 0)
|
||||
|
||||
selectionModel.select(
|
||||
index,
|
||||
QItemSelectionModel.Rows |
|
||||
QItemSelectionModel.ClearAndSelect |
|
||||
QItemSelectionModel.Select
|
||||
)
|
||||
self.table_spec.scrollTo(index)
|
||||
|
||||
def index_selected_row(self):
|
||||
# table = self.find(QTableView, f"tableView")
|
||||
table = self.table_spec
|
||||
rows = table.selectionModel()\
|
||||
.selectedRows()
|
||||
|
||||
if len(rows) == 0:
|
||||
return 0
|
||||
|
||||
return rows[0].row()
|
||||
|
||||
def index_selected_rows(self):
|
||||
# table = self.find(QTableView, f"tableView")
|
||||
table = self.table_spec
|
||||
return list(
|
||||
# Delete duplicate
|
||||
set(
|
||||
map(
|
||||
lambda i: i.row(),
|
||||
table.selectedIndexes()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def move_up(self):
|
||||
row = self.index_selected_row()
|
||||
self._table.move_up(row)
|
||||
self._update()
|
||||
|
||||
def move_down(self):
|
||||
row = self.index_selected_row()
|
||||
self._table.move_down(row)
|
||||
self._update()
|
||||
|
||||
def _copy(self):
|
||||
rows = list(
|
||||
map(
|
||||
lambda row: row.row(),
|
||||
self.tableView.selectionModel().selectedRows()
|
||||
)
|
||||
)
|
||||
|
||||
table = list(
|
||||
map(
|
||||
lambda eic: list(
|
||||
map(
|
||||
lambda k: eic[1][k],
|
||||
["rk", "discharge", "elevation"]
|
||||
)
|
||||
),
|
||||
filter(
|
||||
lambda eic: eic[0] in rows,
|
||||
enumerate(self._ics.lst())
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
self.copyTableIntoClipboard(table)
|
||||
|
||||
def _paste(self):
|
||||
header, data = self.parseClipboardTable()
|
||||
|
||||
if len(data) + len(header) == 0:
|
||||
return
|
||||
|
||||
logger.debug(
|
||||
"D90: Paste: " +
|
||||
f"header = {header}, " +
|
||||
f"data = {data}"
|
||||
)
|
||||
|
||||
try:
|
||||
row = self.index_selected_row()
|
||||
# self._table.paste(row, header, data)
|
||||
self._table.paste(row, [], data)
|
||||
except Exception as e:
|
||||
logger_exception(e)
|
||||
|
||||
self._update()
|
||||
|
||||
def _undo(self):
|
||||
self._table.undo()
|
||||
self._update()
|
||||
|
||||
def _redo(self):
|
||||
self._table.redo()
|
||||
self._update()
|
||||
|
||||
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])
|
||||
|
||||
def delete(self):
|
||||
rows = self.index_selected_rows()
|
||||
if len(rows) == 0:
|
||||
return
|
||||
self._table_spec.delete(rows)
|
||||
@@ -0,0 +1,46 @@
|
||||
# translate.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 -*-
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
from View.Translate import MainTranslate
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
|
||||
class D90AdisTSTranslate(MainTranslate):
|
||||
def __init__(self):
|
||||
super(D90AdisTSTranslate, self).__init__()
|
||||
|
||||
self._dict["D90 AdisTS"] = _translate(
|
||||
"D90AdisTS", "D90 AdisTS")
|
||||
|
||||
self._dict["rk"] = self._dict["unit_rk"]
|
||||
|
||||
self._sub_dict["table_headers"] = {
|
||||
"name": self._dict["name"],
|
||||
"d90": _translate("Unit", "D90"),
|
||||
}
|
||||
|
||||
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)"),
|
||||
"d90": _translate("Unit", "D90"),
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
# Table.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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
|
||||
import traceback
|
||||
from tools import trace, timer
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
Qt, QVariant, QAbstractTableModel,
|
||||
QCoreApplication, QModelIndex, pyqtSlot,
|
||||
QRect,
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QPushButton, QLineEdit,
|
||||
QFileDialog, QTableView, QAbstractItemView,
|
||||
QUndoStack, QShortcut, QAction, QItemDelegate,
|
||||
QComboBox,
|
||||
)
|
||||
|
||||
from View.Tools.PamhyrTable import PamhyrTableModel
|
||||
|
||||
from View.DIFAdisTS.UndoCommand import (
|
||||
SetCommand, AddCommand, SetCommandSpec,
|
||||
DelCommand,
|
||||
)
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
|
||||
class ComboBoxDelegate(QItemDelegate):
|
||||
def __init__(self, data=None, ic_spec_lst=None,
|
||||
trad=None, parent=None, mode="reaches"):
|
||||
super(ComboBoxDelegate, self).__init__(parent)
|
||||
|
||||
self._data = data
|
||||
self._mode = mode
|
||||
self._trad = trad
|
||||
self._ic_spec_lst = ic_spec_lst
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
self.editor = QComboBox(parent)
|
||||
|
||||
val = []
|
||||
if self._mode == "rk":
|
||||
reach_id = self._ic_spec_lst[index.row()].reach
|
||||
|
||||
reach = next(filter(
|
||||
lambda edge: edge.id == reach_id, self._data.edges()
|
||||
))
|
||||
|
||||
if reach_id is not None:
|
||||
val = list(
|
||||
map(
|
||||
lambda rk: str(rk), reach.reach.get_rk()
|
||||
)
|
||||
)
|
||||
elif self._mode == "method":
|
||||
val = self._data.dif_adists.lst[0].types
|
||||
else:
|
||||
val = list(
|
||||
map(
|
||||
lambda n: n.name, self._data.edges()
|
||||
)
|
||||
)
|
||||
|
||||
self.editor.addItems(
|
||||
[self._trad['not_associated']] +
|
||||
val
|
||||
)
|
||||
|
||||
self.editor.setCurrentText(str(index.data(Qt.DisplayRole)))
|
||||
return self.editor
|
||||
|
||||
def setEditorData(self, editor, index):
|
||||
value = index.data(Qt.DisplayRole)
|
||||
self.editor.currentTextChanged.connect(self.currentItemChanged)
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
text = str(editor.currentText())
|
||||
model.setData(index, text)
|
||||
editor.close()
|
||||
editor.deleteLater()
|
||||
|
||||
def updateEditorGeometry(self, editor, option, index):
|
||||
r = QRect(option.rect)
|
||||
if self.editor.windowFlags() & Qt.Popup:
|
||||
if editor.parent() is not None:
|
||||
r.setTopLeft(self.editor.parent().mapToGlobal(r.topLeft()))
|
||||
editor.setGeometry(r)
|
||||
|
||||
@pyqtSlot()
|
||||
def currentItemChanged(self):
|
||||
self.commitData.emit(self.sender())
|
||||
|
||||
|
||||
class DIFTableModel(PamhyrTableModel):
|
||||
def __init__(self, river=None, data=None, **kwargs):
|
||||
self._river = river
|
||||
|
||||
super(DIFTableModel, self).__init__(data=data, **kwargs)
|
||||
|
||||
self._data = data
|
||||
|
||||
def _setup_lst(self):
|
||||
self._lst = self._data._data
|
||||
|
||||
def rowCount(self, parent):
|
||||
return len(self._lst)
|
||||
|
||||
def data(self, index, role):
|
||||
if role != Qt.ItemDataRole.DisplayRole:
|
||||
return QVariant()
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
if self._headers[column] == "method":
|
||||
n = self._lst[row].method
|
||||
if n is None or n == "":
|
||||
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
|
||||
elif self._headers[column] == "start_rk":
|
||||
n = self._lst[row].start_rk
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
elif self._headers[column] == "end_rk":
|
||||
n = self._lst[row].end_rk
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
elif self._headers[column] == "dif":
|
||||
n = self._lst[row].dif
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
elif self._headers[column] == "b":
|
||||
n = self._lst[row].b
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
elif self._headers[column] == "c":
|
||||
n = self._lst[row].c
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
|
||||
return QVariant()
|
||||
|
||||
def setData(self, index, value, role=Qt.EditRole):
|
||||
if not index.isValid() or role != Qt.EditRole:
|
||||
return False
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
try:
|
||||
if self._headers[column] != "reach":
|
||||
self._undo.push(
|
||||
SetCommandSpec(
|
||||
self._lst, row, self._headers[column], value
|
||||
)
|
||||
)
|
||||
elif self._headers[column] == "reach":
|
||||
self._undo.push(
|
||||
SetCommandSpec(
|
||||
self._lst,
|
||||
row,
|
||||
self._headers[column],
|
||||
self._river.edge(value).id
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.info(e)
|
||||
logger.debug(traceback.format_exc())
|
||||
|
||||
self.dataChanged.emit(index, index)
|
||||
return True
|
||||
|
||||
def add(self, row, parent=QModelIndex()):
|
||||
self.beginInsertRows(parent, row, row - 1)
|
||||
|
||||
self._undo.push(
|
||||
AddCommand(
|
||||
self._data, self._lst, row
|
||||
)
|
||||
)
|
||||
|
||||
self.endInsertRows()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def delete(self, rows, parent=QModelIndex()):
|
||||
self.beginRemoveRows(parent, rows[0], rows[-1])
|
||||
|
||||
self._undo.push(
|
||||
DelCommand(
|
||||
self._data, self._lst, rows
|
||||
)
|
||||
)
|
||||
|
||||
self.endRemoveRows()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def undo(self):
|
||||
self._undo.undo()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def redo(self):
|
||||
self._undo.redo()
|
||||
self.layoutChanged.emit()
|
||||
@@ -0,0 +1,108 @@
|
||||
# Table.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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
|
||||
import traceback
|
||||
from tools import trace, timer
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
Qt, QVariant, QAbstractTableModel,
|
||||
QCoreApplication, QModelIndex, pyqtSlot,
|
||||
QRect,
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QPushButton, QLineEdit,
|
||||
QFileDialog, QTableView, QAbstractItemView,
|
||||
QUndoStack, QShortcut, QAction, QItemDelegate,
|
||||
QComboBox,
|
||||
)
|
||||
|
||||
from View.Tools.PamhyrTable import PamhyrTableModel
|
||||
|
||||
from View.DIFAdisTS.UndoCommand import (
|
||||
SetCommand,
|
||||
)
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
|
||||
class DIFTableDefaultModel(PamhyrTableModel):
|
||||
def __init__(self, **kwargs):
|
||||
super(DIFTableDefaultModel, self).__init__(**kwargs)
|
||||
|
||||
def data(self, index, role):
|
||||
if role != Qt.ItemDataRole.DisplayRole:
|
||||
return QVariant()
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
if self._headers[column] == "method":
|
||||
n = self._data[row].method
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
elif self._headers[column] == "dif":
|
||||
n = self._data[row].dif
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
elif self._headers[column] == "b":
|
||||
n = self._data[row].b
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
elif self._headers[column] == "c":
|
||||
n = self._data[row].c
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
|
||||
return QVariant()
|
||||
|
||||
def setData(self, index, value, role=Qt.EditRole):
|
||||
if not index.isValid() or role != Qt.EditRole:
|
||||
return False
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
try:
|
||||
if self._headers[column] is not None:
|
||||
self._undo.push(
|
||||
SetCommand(
|
||||
self._data, row, self._headers[column], value
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.info(e)
|
||||
logger.debug(traceback.format_exc())
|
||||
|
||||
self.dataChanged.emit(index, index)
|
||||
return True
|
||||
|
||||
def undo(self):
|
||||
self._undo.undo()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def redo(self):
|
||||
self._undo.redo()
|
||||
self.layoutChanged.emit()
|
||||
@@ -0,0 +1,176 @@
|
||||
# UndoCommand.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 -*-
|
||||
|
||||
from copy import deepcopy
|
||||
from tools import trace, timer
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QMessageBox, QUndoCommand, QUndoStack,
|
||||
)
|
||||
|
||||
from Model.DIFAdisTS.DIFAdisTS import DIFAdisTS
|
||||
from Model.DIFAdisTS.DIFAdisTSList import DIFAdisTSList
|
||||
|
||||
|
||||
class SetCommand(QUndoCommand):
|
||||
def __init__(self, data, row, column, new_value):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._data = data
|
||||
self._row = row
|
||||
self._column = column
|
||||
|
||||
if self._column == "method":
|
||||
self._old = self._data[self._row].method
|
||||
elif self._column == "dif":
|
||||
self._old = self._data[self._row].dif
|
||||
elif self._column == "b":
|
||||
self._old = self._data[self._row].b
|
||||
elif self._column == "c":
|
||||
self._old = self._data[self._row].c
|
||||
|
||||
_type = float
|
||||
if column == "method":
|
||||
_type = str
|
||||
|
||||
self._new = _type(new_value)
|
||||
|
||||
def undo(self):
|
||||
if self._column == "method":
|
||||
self._data[self._row].method = self._old
|
||||
elif self._column == "dif":
|
||||
self._data[self._row].dif = self._old
|
||||
elif self._column == "b":
|
||||
self._data[self._row].b = self._old
|
||||
elif self._column == "c":
|
||||
self._data[self._row].c = self._old
|
||||
|
||||
def redo(self):
|
||||
if self._column == "method":
|
||||
self._data[self._row].method = self._new
|
||||
elif self._column == "dif":
|
||||
self._data[self._row].dif = self._new
|
||||
elif self._column == "b":
|
||||
self._data[self._row].b = self._new
|
||||
elif self._column == "c":
|
||||
self._data[self._row].c = self._new
|
||||
|
||||
|
||||
class SetCommandSpec(QUndoCommand):
|
||||
def __init__(self, data, row, column, new_value):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._data = data
|
||||
self._row = row
|
||||
self._column = column
|
||||
|
||||
if self._column == "method":
|
||||
self._old = self._data[self._row].method
|
||||
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 == "dif":
|
||||
self._old = self._data[self._row].dif
|
||||
elif self._column == "b":
|
||||
self._old = self._data[self._row].b
|
||||
elif self._column == "c":
|
||||
self._old = self._data[self._row].c
|
||||
|
||||
_type = float
|
||||
if column == "method":
|
||||
_type = str
|
||||
elif column == "reach":
|
||||
_type = int
|
||||
|
||||
self._new = _type(new_value)
|
||||
|
||||
def undo(self):
|
||||
if self._column == "method":
|
||||
self._data[self._row].method = 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 == "dif":
|
||||
self._data[self._row].dif = self._old
|
||||
elif self._column == "b":
|
||||
self._data[self._row].b = self._old
|
||||
elif self._column == "c":
|
||||
self._data[self._row].c = self._old
|
||||
|
||||
def redo(self):
|
||||
if self._column == "method":
|
||||
self._data[self._row].method = 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 == "dif":
|
||||
self._data[self._row].dif = self._new
|
||||
elif self._column == "b":
|
||||
self._data[self._row].b = self._new
|
||||
elif self._column == "c":
|
||||
self._data[self._row].c = self._new
|
||||
|
||||
|
||||
class AddCommand(QUndoCommand):
|
||||
def __init__(self, data, ics_spec, index):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._data = data
|
||||
self._ics_spec = ics_spec
|
||||
self._index = index
|
||||
self._new = None
|
||||
|
||||
def undo(self):
|
||||
self._data.delete_i([self._index])
|
||||
|
||||
def redo(self):
|
||||
if self._new is None:
|
||||
self._new = self._data.new(self._index)
|
||||
else:
|
||||
self._data.insert(self._index, self._new)
|
||||
|
||||
|
||||
class DelCommand(QUndoCommand):
|
||||
def __init__(self, data, ics_spec, rows):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._data = data
|
||||
self._ics_spec = ics_spec
|
||||
self._rows = rows
|
||||
# self._data = data
|
||||
|
||||
self._ic = []
|
||||
for row in rows:
|
||||
self._ic.append((row, self._ics_spec[row]))
|
||||
self._ic.sort()
|
||||
|
||||
def undo(self):
|
||||
for row, el in self._ic:
|
||||
self._data.insert(row, el)
|
||||
|
||||
def redo(self):
|
||||
self._data.delete_i(self._rows)
|
||||
@@ -0,0 +1,293 @@
|
||||
# Window.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 os
|
||||
import logging
|
||||
|
||||
from tools import trace, timer, logger_exception
|
||||
|
||||
from View.Tools.PamhyrWindow import PamhyrWindow
|
||||
|
||||
from PyQt5.QtGui import (
|
||||
QKeySequence, QIcon,
|
||||
)
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
Qt, QVariant, QAbstractTableModel,
|
||||
QCoreApplication, QModelIndex, pyqtSlot,
|
||||
QRect, QItemSelectionModel,
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QPushButton, QLineEdit,
|
||||
QFileDialog, QTableView, QAbstractItemView,
|
||||
QUndoStack, QShortcut, QAction, QItemDelegate,
|
||||
QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
|
||||
QVBoxLayout, QToolBar, QAction, QToolButton,
|
||||
)
|
||||
|
||||
from Modules import Modules
|
||||
|
||||
from View.DIFAdisTS.TableDefault import (
|
||||
DIFTableDefaultModel,
|
||||
)
|
||||
|
||||
from View.DIFAdisTS.Table import (
|
||||
DIFTableModel, ComboBoxDelegate,
|
||||
)
|
||||
|
||||
from View.DIFAdisTS.translate import DIFAdisTSTranslate
|
||||
|
||||
from Solver.Mage import Mage8
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class DIFAdisTSWindow(PamhyrWindow):
|
||||
_pamhyr_ui = "DIFAdisTS"
|
||||
_pamhyr_name = "DIF AdisTS"
|
||||
|
||||
def __init__(self, data=None, study=None, config=None, parent=None):
|
||||
self._data = []
|
||||
self._data.append(data)
|
||||
trad = DIFAdisTSTranslate()
|
||||
|
||||
name = (
|
||||
trad[self._pamhyr_name] +
|
||||
" - " + study.name +
|
||||
" - " + self._data[0].name
|
||||
)
|
||||
|
||||
super(DIFAdisTSWindow, self).__init__(
|
||||
title=name,
|
||||
study=study,
|
||||
config=config,
|
||||
trad=trad,
|
||||
parent=parent
|
||||
)
|
||||
|
||||
self._hash_data.append(data)
|
||||
|
||||
self._dif_adists_lst = study.river.dif_adists
|
||||
|
||||
self.setup_table()
|
||||
|
||||
self.ui.setWindowTitle(self._title)
|
||||
|
||||
def setup_table(self):
|
||||
|
||||
path_icons = os.path.join(self._get_ui_directory(), f"ressources")
|
||||
|
||||
table_default = self.find(QTableView, f"tableView")
|
||||
|
||||
self._delegate_method = ComboBoxDelegate(
|
||||
trad=self._trad,
|
||||
data=self._study.river,
|
||||
ic_spec_lst=self._data[0]._data,
|
||||
parent=self,
|
||||
mode="method"
|
||||
)
|
||||
|
||||
self._table = DIFTableDefaultModel(
|
||||
table_view=table_default,
|
||||
table_headers=self._trad.get_dict("table_headers"),
|
||||
editable_headers=["method", "dif", "b", "c"],
|
||||
delegates={
|
||||
"method": self._delegate_method
|
||||
},
|
||||
data=self._data,
|
||||
undo=self._undo_stack,
|
||||
trad=self._trad
|
||||
)
|
||||
|
||||
table_default.setModel(self._table)
|
||||
table_default.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
table_default.horizontalHeader().setSectionResizeMode(
|
||||
QHeaderView.Stretch
|
||||
)
|
||||
table_default.setAlternatingRowColors(True)
|
||||
|
||||
layout = self.find(QVBoxLayout, f"verticalLayout_1")
|
||||
toolBar = QToolBar()
|
||||
layout.addWidget(toolBar)
|
||||
|
||||
action_add = QAction(self)
|
||||
action_add.setIcon(QIcon(os.path.join(path_icons, f"add.png")))
|
||||
action_add.triggered.connect(self.add)
|
||||
action_delete = QAction(self)
|
||||
action_delete.setIcon(QIcon(os.path.join(path_icons, f"del.png")))
|
||||
action_delete.triggered.connect(self.delete)
|
||||
|
||||
toolBar.addAction(action_add)
|
||||
toolBar.addAction(action_delete)
|
||||
|
||||
self.table_spec = QTableView()
|
||||
layout.addWidget(self.table_spec)
|
||||
|
||||
self._delegate_reach = ComboBoxDelegate(
|
||||
trad=self._trad,
|
||||
data=self._study.river,
|
||||
ic_spec_lst=self._data[0]._data,
|
||||
parent=self,
|
||||
mode="reaches"
|
||||
)
|
||||
self._delegate_rk = ComboBoxDelegate(
|
||||
trad=self._trad,
|
||||
data=self._study.river,
|
||||
ic_spec_lst=self._data[0]._data,
|
||||
parent=self,
|
||||
mode="rk"
|
||||
)
|
||||
|
||||
self._table_spec = DIFTableModel(
|
||||
table_view=self.table_spec,
|
||||
table_headers=self._trad.get_dict("table_headers_spec"),
|
||||
editable_headers=["method", "reach", "start_rk",
|
||||
"end_rk", "dif", "b", "c"],
|
||||
delegates={
|
||||
"method": self._delegate_method,
|
||||
"reach": self._delegate_reach,
|
||||
"start_rk": self._delegate_rk,
|
||||
"end_rk": self._delegate_rk
|
||||
},
|
||||
data=self._data[0],
|
||||
undo=self._undo_stack,
|
||||
trad=self._trad,
|
||||
river=self._study.river
|
||||
)
|
||||
|
||||
self.table_spec.setModel(self._table_spec)
|
||||
self.table_spec.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.table_spec.horizontalHeader().setSectionResizeMode(
|
||||
QHeaderView.Stretch
|
||||
)
|
||||
self.table_spec.setAlternatingRowColors(True)
|
||||
|
||||
selectionModel = self.table_spec.selectionModel()
|
||||
index = self.table_spec.model().index(0, 0)
|
||||
|
||||
selectionModel.select(
|
||||
index,
|
||||
QItemSelectionModel.Rows |
|
||||
QItemSelectionModel.ClearAndSelect |
|
||||
QItemSelectionModel.Select
|
||||
)
|
||||
self.table_spec.scrollTo(index)
|
||||
|
||||
def index_selected_row(self):
|
||||
# table = self.find(QTableView, f"tableView")
|
||||
table = self.table_spec
|
||||
rows = table.selectionModel()\
|
||||
.selectedRows()
|
||||
|
||||
if len(rows) == 0:
|
||||
return 0
|
||||
|
||||
return rows[0].row()
|
||||
|
||||
def index_selected_rows(self):
|
||||
# table = self.find(QTableView, f"tableView")
|
||||
table = self.table_spec
|
||||
return list(
|
||||
# Delete duplicate
|
||||
set(
|
||||
map(
|
||||
lambda i: i.row(),
|
||||
table.selectedIndexes()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def move_up(self):
|
||||
row = self.index_selected_row()
|
||||
self._table.move_up(row)
|
||||
self._update()
|
||||
|
||||
def move_down(self):
|
||||
row = self.index_selected_row()
|
||||
self._table.move_down(row)
|
||||
self._update()
|
||||
|
||||
def _copy(self):
|
||||
rows = list(
|
||||
map(
|
||||
lambda row: row.row(),
|
||||
self.tableView.selectionModel().selectedRows()
|
||||
)
|
||||
)
|
||||
|
||||
table = list(
|
||||
map(
|
||||
lambda eic: list(
|
||||
map(
|
||||
lambda k: eic[1][k],
|
||||
["rk", "discharge", "elevation"]
|
||||
)
|
||||
),
|
||||
filter(
|
||||
lambda eic: eic[0] in rows,
|
||||
enumerate(self._ics.lst())
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
self.copyTableIntoClipboard(table)
|
||||
|
||||
def _paste(self):
|
||||
header, data = self.parseClipboardTable()
|
||||
|
||||
if len(data) + len(header) == 0:
|
||||
return
|
||||
|
||||
logger.debug(
|
||||
"DIF: Paste: " +
|
||||
f"header = {header}, " +
|
||||
f"data = {data}"
|
||||
)
|
||||
|
||||
try:
|
||||
row = self.index_selected_row()
|
||||
# self._table.paste(row, header, data)
|
||||
self._table.paste(row, [], data)
|
||||
except Exception as e:
|
||||
logger_exception(e)
|
||||
|
||||
self._update()
|
||||
|
||||
def _undo(self):
|
||||
self._table.undo()
|
||||
self._update()
|
||||
|
||||
def _redo(self):
|
||||
self._table.redo()
|
||||
self._update()
|
||||
|
||||
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])
|
||||
|
||||
def delete(self):
|
||||
rows = self.index_selected_rows()
|
||||
if len(rows) == 0:
|
||||
return
|
||||
self._table_spec.delete(rows)
|
||||
@@ -0,0 +1,50 @@
|
||||
# translate.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 -*-
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
from View.Translate import MainTranslate
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
|
||||
class DIFAdisTSTranslate(MainTranslate):
|
||||
def __init__(self):
|
||||
super(DIFAdisTSTranslate, self).__init__()
|
||||
|
||||
self._dict["DIF AdisTS"] = _translate(
|
||||
"DIFAdisTS", "DIF AdisTS")
|
||||
|
||||
self._dict["rk"] = self._dict["unit_rk"]
|
||||
|
||||
self._sub_dict["table_headers"] = {
|
||||
"method": self._dict["method"],
|
||||
"dif": _translate("Unit", "DIF"),
|
||||
"b": _translate("Unit", "Coeff b"),
|
||||
"c": _translate("Unit", "Coeff c"),
|
||||
}
|
||||
|
||||
self._sub_dict["table_headers_spec"] = {
|
||||
"method": self._dict["method"],
|
||||
"reach": self._dict["reach"],
|
||||
"start_rk": _translate("Unit", "Start_RK (m)"),
|
||||
"end_rk": _translate("Unit", "End_RK (m)"),
|
||||
"dif": _translate("Unit", "DIF"),
|
||||
"b": _translate("Unit", "Coeff b"),
|
||||
"c": _translate("Unit", "Coeff c"),
|
||||
}
|
||||
@@ -48,3 +48,6 @@ class PlotRKZ(PlotRKC):
|
||||
|
||||
def onpick(self, event):
|
||||
return
|
||||
|
||||
def draw_hs(self):
|
||||
pass
|
||||
@@ -217,9 +217,7 @@ class FrictionsWindow(PamhyrWindow):
|
||||
self.update_plot(highlight)
|
||||
|
||||
def update_plot(self, highlight=None):
|
||||
if highlight is not None:
|
||||
self.plot.highlight = highlight
|
||||
self.plot.draw()
|
||||
self.plot.draw(highlight)
|
||||
|
||||
if highlight is not None:
|
||||
self.plot_2.highlight = highlight
|
||||
|
||||
@@ -44,6 +44,6 @@ class FrictionsTranslate(MainTranslate):
|
||||
# "edge": self._dict["reach"],
|
||||
"begin_rk": _translate("Frictions", "Start (m)"),
|
||||
"end_rk": _translate("Frictions", "End (m)"),
|
||||
"begin_strickler": _translate("Frictions", "Start coefficient"),
|
||||
"begin_strickler": _translate("Frictions", "Coefficient"),
|
||||
# "end_strickler": _translate("Frictions", "End coefficient"),
|
||||
}
|
||||
@@ -50,6 +50,8 @@ class MeshingDialog(PamhyrDialog):
|
||||
self._init_default_values()
|
||||
|
||||
def _init_default_values(self):
|
||||
gl, _ = self._reach.compute_guidelines()
|
||||
self._gl = list(gl)
|
||||
self._space_step = 50.0
|
||||
self._lplan = False
|
||||
self._lm = "3"
|
||||
@@ -58,6 +60,14 @@ class MeshingDialog(PamhyrDialog):
|
||||
self._end_cs = -1
|
||||
self._begin_dir = "un"
|
||||
self._end_dir = "np"
|
||||
|
||||
lower_gl = list(map(str.lower, self._gl))
|
||||
for i, gl in enumerate(lower_gl):
|
||||
if gl == "rg":
|
||||
self._begin_dir = self._gl[i]
|
||||
elif gl == "rd":
|
||||
self._end_dir = self._gl[i]
|
||||
|
||||
self._origin = self._reach.profile(0)
|
||||
|
||||
self._init_default_values_profiles()
|
||||
|
||||
@@ -51,6 +51,11 @@ class PlotAC(PamhyrPlot):
|
||||
self.plot_selected = None
|
||||
self.next_plot_selected = None
|
||||
|
||||
@timer
|
||||
def redraw(self, data):
|
||||
self._data = data
|
||||
self.draw()
|
||||
|
||||
@timer
|
||||
def draw(self):
|
||||
self.init_axes()
|
||||
|
||||
@@ -52,7 +52,7 @@ class PlotRKZ(PamhyrPlot):
|
||||
self.line_rk_zmin_zmax_highlight = None
|
||||
|
||||
self.label_x = self._trad["unit_rk"]
|
||||
self.label_y = self._trad["unit_height"]
|
||||
self.label_y = self._trad["unit_depth"]
|
||||
|
||||
self.before_plot_selected = None
|
||||
self.plot_selected = None
|
||||
@@ -163,6 +163,11 @@ class PlotRKZ(PamhyrPlot):
|
||||
)
|
||||
self._table.scrollTo(self._table.model().index(ind2, 0))
|
||||
|
||||
@timer
|
||||
def redraw(self, data):
|
||||
self._data = data
|
||||
self.draw()
|
||||
|
||||
@timer
|
||||
def draw(self):
|
||||
self.init_axes()
|
||||
@@ -198,7 +203,7 @@ class PlotRKZ(PamhyrPlot):
|
||||
z_min = self.data.get_z_min()
|
||||
z_max = self.data.get_z_max()
|
||||
|
||||
self._colors, self._style = self.color_hightlight()
|
||||
self._colors, self._style = self.color_highlight()
|
||||
|
||||
self.line_rk_zmin_zmax = self.canvas.axes.vlines(
|
||||
x=rk, ymin=z_min, ymax=z_max,
|
||||
@@ -208,7 +213,7 @@ class PlotRKZ(PamhyrPlot):
|
||||
picker=10,
|
||||
)
|
||||
|
||||
def color_hightlight(self):
|
||||
def color_highlight(self):
|
||||
rows = sorted(list(
|
||||
set(
|
||||
(i.row() for i in self.parent.tableView.selectedIndexes())
|
||||
@@ -292,6 +297,8 @@ class PlotRKZ(PamhyrPlot):
|
||||
)
|
||||
|
||||
for hs in lhs:
|
||||
if not hs.enabled:
|
||||
continue
|
||||
x = hs.input_rk
|
||||
if x is not None:
|
||||
z_min = reach.get_z_min()
|
||||
@@ -328,7 +335,7 @@ class PlotRKZ(PamhyrPlot):
|
||||
|
||||
def update_current(self):
|
||||
if self._current_data_update:
|
||||
self._colors, self._style = self.color_hightlight()
|
||||
self._colors, self._style = self.color_highlight()
|
||||
self.line_rk_zmin_zmax.set_colors(self._colors)
|
||||
self.line_rk_zmin_zmax.set_linestyle(self._style)
|
||||
|
||||
@@ -345,7 +352,7 @@ class PlotRKZ(PamhyrPlot):
|
||||
# TODO comprendre à quoi sert ce bout de code
|
||||
# ========>
|
||||
# self.line_rk_zmin_zmax.remove()
|
||||
# self._colors, self._style = self.color_hightlight()
|
||||
# self._colors, self._style = self.color_highlight()
|
||||
# self.line_rk_zmin_zmax = self.canvas.axes.vlines(
|
||||
# x=rk,
|
||||
# ymin=z_min,
|
||||
|
||||
@@ -162,6 +162,11 @@ class PlotXY(PamhyrPlot):
|
||||
)
|
||||
self._table.scrollTo(self._table.model().index(ind2, 0))
|
||||
|
||||
@timer
|
||||
def redraw(self, data):
|
||||
self._data = data
|
||||
self.draw()
|
||||
|
||||
@timer
|
||||
def draw(self):
|
||||
self.init_axes()
|
||||
|
||||
@@ -66,11 +66,9 @@ class Plot(PamhyrPlot):
|
||||
self._isometric_axis = False
|
||||
|
||||
self.hl_points = []
|
||||
self.highlight = (
|
||||
[], # Points list to highlight
|
||||
None # Hydrolic values (z, wet_area,
|
||||
# wet_preimeter, water_width)
|
||||
)
|
||||
self.highlight = None # (z, wet_area, wet_preimeter, water_width)
|
||||
self._colors = []
|
||||
|
||||
self._onpickevent = None
|
||||
self._rect_select = RectangleSelector(
|
||||
ax=self.canvas.axes,
|
||||
@@ -87,36 +85,25 @@ class Plot(PamhyrPlot):
|
||||
# we need to do that to prevent conflicst
|
||||
# between onpick and rect_select_callback
|
||||
modifiers = QApplication.keyboardModifiers()
|
||||
points, hyd = self.highlight
|
||||
if self._onpickevent is not None:
|
||||
ind, point = self._closest_point(self._onpickevent)
|
||||
if modifiers == Qt.ControlModifier:
|
||||
rows = self._parent.index_selected_rows()
|
||||
if ind in rows:
|
||||
rows.remove(ind)
|
||||
del (points[ind])
|
||||
self.highlight = (points, hyd)
|
||||
self._select_in_table(rows)
|
||||
else:
|
||||
self.highlight = (points+[point], hyd)
|
||||
self._select_in_table(rows+[ind])
|
||||
elif modifiers == Qt.ShiftModifier:
|
||||
rows = self._parent.index_selected_rows()
|
||||
if len(rows) > 0:
|
||||
i1 = min(rows[0], rows[-1], ind)
|
||||
i2 = max(rows[0], rows[-1], ind)
|
||||
p = [
|
||||
[self.data.points[i].x, self.data.points[i].y]
|
||||
for i in range(i1, i2)
|
||||
]
|
||||
else:
|
||||
i1 = ind
|
||||
i2 = ind
|
||||
p = [point]
|
||||
self.highlight = (p, hyd)
|
||||
self._select_range_in_table(i1, i2)
|
||||
else:
|
||||
self.highlight = ([point], hyd)
|
||||
self._select_in_table([ind])
|
||||
|
||||
self._onpickevent = None
|
||||
@@ -142,41 +129,26 @@ class Plot(PamhyrPlot):
|
||||
if event.button.value == 1:
|
||||
return
|
||||
|
||||
points, _ = self.highlight
|
||||
|
||||
z = self._get_z_from_click(event)
|
||||
if z < self.data.z_min() or event.button.value == 2:
|
||||
self.highlight = (points, None)
|
||||
self.update()
|
||||
self.highlight = None
|
||||
self.draw_highligth()
|
||||
self.update_idle()
|
||||
return
|
||||
|
||||
a, p, w = self._compute_hydraulics(z)
|
||||
|
||||
logger.debug(f"{z, a, p, w}")
|
||||
|
||||
self.highlight = (points, (z, a, p, w))
|
||||
self.highlight = (z, a, p, w)
|
||||
|
||||
self.update()
|
||||
self.draw_highligth()
|
||||
self.update_idle()
|
||||
return
|
||||
|
||||
def select_points_from_indices(self, indices):
|
||||
data = self.data
|
||||
_, hyd = self.highlight
|
||||
|
||||
points = list(
|
||||
map(
|
||||
lambda e: e[1],
|
||||
filter(
|
||||
lambda e: e[0] in indices,
|
||||
enumerate(
|
||||
zip(data.get_station(), data.z())
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
self.highlight = (points, hyd)
|
||||
self.update()
|
||||
self.draw_highligth()
|
||||
# self.update()
|
||||
|
||||
def _select_in_table(self, ind):
|
||||
if self._table is not None:
|
||||
@@ -224,11 +196,6 @@ class Plot(PamhyrPlot):
|
||||
|
||||
x = event.artist.get_xdata()
|
||||
y = event.artist.get_ydata()
|
||||
|
||||
# points = filter(
|
||||
# lambda e: e[0] in points_ind,
|
||||
# enumerate(zip(x, y))
|
||||
# )
|
||||
points = enumerate(zip(x, y))
|
||||
|
||||
mx = event.mouseevent.xdata
|
||||
@@ -250,7 +217,7 @@ class Plot(PamhyrPlot):
|
||||
|
||||
def rect_select_callback(self, eclick, erelease):
|
||||
|
||||
points, hyd = self.highlight
|
||||
hyd = self.highlight
|
||||
x1, y1 = eclick.xdata, eclick.ydata
|
||||
x2, y2 = erelease.xdata, erelease.ydata
|
||||
|
||||
@@ -268,14 +235,13 @@ class Plot(PamhyrPlot):
|
||||
if all(i in rows for i in inds):
|
||||
for ind in sorted(inds, reverse=True):
|
||||
rows.remove(ind)
|
||||
del (points[ind])
|
||||
self.highlight = (points, hyd)
|
||||
self.highlight = hyd
|
||||
self._select_in_table(rows)
|
||||
else:
|
||||
self.highlight = (points+points2, hyd)
|
||||
self.highlight = hyd
|
||||
self._select_in_table(rows+inds)
|
||||
else:
|
||||
self.highlight = (points2, hyd)
|
||||
self.highlight = hyd
|
||||
self._select_in_table(inds)
|
||||
return
|
||||
|
||||
@@ -319,13 +285,16 @@ class Plot(PamhyrPlot):
|
||||
picker=10
|
||||
)
|
||||
|
||||
self.draw_annotation(x, y)
|
||||
self.draw_annotation()
|
||||
self.highlight = None
|
||||
self.draw_highligth()
|
||||
|
||||
self.idle()
|
||||
|
||||
def draw_annotation(self, x, y):
|
||||
def draw_annotation(self):
|
||||
gl = map(lambda p: p.name, self.data.points)
|
||||
x = self.data.get_station()
|
||||
y = self.data.z()
|
||||
|
||||
# Add label on graph
|
||||
self.annotation = []
|
||||
@@ -363,16 +332,18 @@ class Plot(PamhyrPlot):
|
||||
self.canvas.figure.patch.set_facecolor('white')
|
||||
|
||||
def draw_highligth(self):
|
||||
points, hyd = self.highlight
|
||||
hyd = self.highlight
|
||||
for p in self.hl_points:
|
||||
p[0].set_data([], [])
|
||||
|
||||
self.hl_points = []
|
||||
x = self.data.get_station()
|
||||
y = self.data.z()
|
||||
|
||||
for x, y in points:
|
||||
for i in self._parent.index_selected_rows():
|
||||
self.hl_points.append(
|
||||
self.canvas.axes.plot(
|
||||
x, y,
|
||||
x[i], y[i],
|
||||
color=self.color_plot_highlight,
|
||||
lw=1.5, markersize=7, marker='+',
|
||||
)
|
||||
@@ -383,8 +354,9 @@ class Plot(PamhyrPlot):
|
||||
else:
|
||||
if self._z_note is not None:
|
||||
self._z_note.set_visible(False)
|
||||
self._z_line[0].set_visible(False)
|
||||
self._z_line.set_visible(False)
|
||||
self._z_fill_between.set_visible(False)
|
||||
self.idle()
|
||||
|
||||
def draw_highligth_z_line(self, z, a, p, w):
|
||||
text = (
|
||||
@@ -396,40 +368,37 @@ class Plot(PamhyrPlot):
|
||||
|
||||
x = self.data.get_station()
|
||||
xlim = (x[0], x[-1])
|
||||
ylim = self.canvas.axes.get_ylim()
|
||||
pos = (
|
||||
xlim[0] + (abs(xlim[0] - xlim[1]) * 0.05),
|
||||
z + 0.8
|
||||
z + + (abs(ylim[0] - ylim[1]) * 0.08)
|
||||
)
|
||||
y = self.data.z()
|
||||
|
||||
if self._z_note is None:
|
||||
self.draw_highligth_z_line_fill(x, y, z)
|
||||
if self._z_note is not None:
|
||||
self._z_note.remove()
|
||||
if self._z_line is not None:
|
||||
self._z_line.remove()
|
||||
self.draw_highligth_z_line_fill(x, y, z)
|
||||
|
||||
self._z_line = self.canvas.axes.plot(
|
||||
xlim, [z, z],
|
||||
color=self.color_plot_river_water
|
||||
)
|
||||
self._z_line[0].set_visible(True)
|
||||
self._z_line, = self.canvas.axes.plot(
|
||||
xlim, [z, z],
|
||||
color=self.color_plot_river_water
|
||||
)
|
||||
self._z_line.set_visible(True)
|
||||
self._z_fill_between.set_visible(True)
|
||||
|
||||
self._z_note = self.canvas.axes.annotate(
|
||||
text, pos,
|
||||
horizontalalignment='left',
|
||||
verticalalignment='top',
|
||||
annotation_clip=True,
|
||||
color=self.color_plot_river_water,
|
||||
fontsize=9,
|
||||
fontweight='bold',
|
||||
alpha=0.7
|
||||
)
|
||||
self._z_note.set_visible(True)
|
||||
else:
|
||||
self.draw_highligth_z_line_fill(x, y, z)
|
||||
|
||||
self._z_line[0].set_data(xlim, [z, z])
|
||||
self._z_note.set_position(pos)
|
||||
self._z_note.set_text(text)
|
||||
self._z_line[0].set_visible(True)
|
||||
self._z_note.set_visible(True)
|
||||
self._z_note = self.canvas.axes.annotate(
|
||||
text, pos,
|
||||
horizontalalignment='left',
|
||||
verticalalignment='top',
|
||||
annotation_clip=True,
|
||||
color=self.color_plot_river_water,
|
||||
fontsize=9,
|
||||
fontweight='bold',
|
||||
alpha=0.7
|
||||
)
|
||||
self._z_note.set_visible(True)
|
||||
|
||||
def draw_highligth_z_line_fill(self, x, y, z):
|
||||
if self._z_fill_between is not None:
|
||||
@@ -444,6 +413,6 @@ class Plot(PamhyrPlot):
|
||||
|
||||
@timer
|
||||
def update(self):
|
||||
self.draw_highligth()
|
||||
self.draw()
|
||||
|
||||
self.update_idle()
|
||||
@@ -42,9 +42,11 @@ class SetXCommand(SetDataCommand):
|
||||
|
||||
def undo(self):
|
||||
self._profile.point(self._index).x = self._old
|
||||
self._profile.modified()
|
||||
|
||||
def redo(self):
|
||||
self._profile.point(self._index).x = self._new
|
||||
self._profile.modified()
|
||||
|
||||
|
||||
class SetYCommand(SetDataCommand):
|
||||
@@ -54,9 +56,11 @@ class SetYCommand(SetDataCommand):
|
||||
|
||||
def undo(self):
|
||||
self._profile.point(self._index).y = self._old
|
||||
self._profile.modified()
|
||||
|
||||
def redo(self):
|
||||
self._profile.point(self._index).y = self._new
|
||||
self._profile.modified()
|
||||
|
||||
|
||||
class SetZCommand(SetDataCommand):
|
||||
@@ -66,9 +70,11 @@ class SetZCommand(SetDataCommand):
|
||||
|
||||
def undo(self):
|
||||
self._profile.point(self._index).z = self._old
|
||||
self._profile.modified()
|
||||
|
||||
def redo(self):
|
||||
self._profile.point(self._index).z = self._new
|
||||
self._profile.modified()
|
||||
|
||||
|
||||
class SetNameCommand(SetDataCommand):
|
||||
@@ -93,13 +99,15 @@ class AddCommand(QUndoCommand):
|
||||
self._point = None
|
||||
|
||||
def undo(self):
|
||||
self._profile.delete_point([self._point])
|
||||
self._profile.delete_i([self._index])
|
||||
self._profile.modified()
|
||||
|
||||
def redo(self):
|
||||
if self._point is None:
|
||||
self._point = self._profile.insert(self._index)
|
||||
else:
|
||||
self._profile.insert_point(self._index, self._point)
|
||||
self._profile.modified()
|
||||
|
||||
|
||||
class DelCommand(QUndoCommand):
|
||||
@@ -114,10 +122,13 @@ class DelCommand(QUndoCommand):
|
||||
self._points.append(self._profile.point(row))
|
||||
|
||||
def undo(self):
|
||||
self._profile.undelete(self._points)
|
||||
for row, point in self._points:
|
||||
self._profile.insert_point(row, point)
|
||||
self._profile.modified()
|
||||
|
||||
def redo(self):
|
||||
self._profile.delete(self._points)
|
||||
self._profile.delete_i(self._rows)
|
||||
self._profile.modified()
|
||||
|
||||
|
||||
class SortCommand(QUndoCommand):
|
||||
@@ -141,9 +152,11 @@ class SortCommand(QUndoCommand):
|
||||
|
||||
def undo(self):
|
||||
self._profile.sort_with_indexes(self._indexes)
|
||||
self._profile.modified()
|
||||
|
||||
def redo(self):
|
||||
self._profile.sort(self._column, self._reverse)
|
||||
self._profile.modified()
|
||||
|
||||
|
||||
class MoveCommand(QUndoCommand):
|
||||
@@ -159,12 +172,14 @@ class MoveCommand(QUndoCommand):
|
||||
self._profile.move_up_point(self._i)
|
||||
else:
|
||||
self._profile.move_down_point(self._i)
|
||||
self._profile.modified()
|
||||
|
||||
def redo(self):
|
||||
if self._up:
|
||||
self._profile.move_up_point(self._i)
|
||||
else:
|
||||
self._profile.move_down_point(self._i)
|
||||
self._profile.modified()
|
||||
|
||||
|
||||
class ReverseCommand(QUndoCommand):
|
||||
@@ -175,9 +190,11 @@ class ReverseCommand(QUndoCommand):
|
||||
|
||||
def undo(self):
|
||||
self._profile.reverse()
|
||||
self._profile.modified()
|
||||
|
||||
def redo(self):
|
||||
self._profile.reverse()
|
||||
self._profile.modified()
|
||||
|
||||
|
||||
class PurgeCommand(QUndoCommand):
|
||||
@@ -190,9 +207,11 @@ class PurgeCommand(QUndoCommand):
|
||||
|
||||
def undo(self):
|
||||
self._profile._points = self._old.copy()
|
||||
self._profile.modified()
|
||||
|
||||
def redo(self):
|
||||
self._profile.purge(self._np_purge)
|
||||
self._profile.modified()
|
||||
|
||||
|
||||
class PasteCommand(QUndoCommand):
|
||||
@@ -208,7 +227,9 @@ class PasteCommand(QUndoCommand):
|
||||
def undo(self):
|
||||
for ind in range(len(self._points)):
|
||||
self._profile.delete_i([self._row])
|
||||
self._profile.modified()
|
||||
|
||||
def redo(self):
|
||||
for point in self._points:
|
||||
self._profile.insert_point(self._row, point)
|
||||
self._profile.modified()
|
||||
@@ -169,7 +169,7 @@ class GeometryReachTableModel(PamhyrTableModel):
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def move_down(self, row, parent=QModelIndex()):
|
||||
if row > self._data.number_profiles:
|
||||
if row >= self._data.number_profiles-1:
|
||||
return
|
||||
|
||||
target = row
|
||||
@@ -276,3 +276,12 @@ class GeometryReachTableModel(PamhyrTableModel):
|
||||
)
|
||||
)
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def change_reach(self, new_reach, parent):
|
||||
|
||||
self._undo.push(
|
||||
ChangeReachCommand(
|
||||
new_reach,
|
||||
parent
|
||||
)
|
||||
)
|
||||
@@ -22,7 +22,7 @@ from copy import deepcopy
|
||||
from tools import trace, timer, logger_exception
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QMessageBox, QUndoCommand, QUndoStack,
|
||||
QMessageBox, QUndoCommand, QUndoStack, QTableView
|
||||
)
|
||||
|
||||
from Model.Geometry import Reach
|
||||
@@ -202,19 +202,29 @@ class ImportCommand(QUndoCommand):
|
||||
self._row = row
|
||||
self._filename = filename
|
||||
self._profiles = None
|
||||
self._old_profiles = []
|
||||
for row in range(len(self._reach)):
|
||||
self._old_profiles.append((self._reach.profile(row)))
|
||||
self._old_profiles.reverse()
|
||||
|
||||
def undo(self):
|
||||
self._reach.delete_profiles(self._profiles)
|
||||
for profile in self._old_profiles:
|
||||
self._reach.insert_profile(self._row, profile)
|
||||
|
||||
def redo(self):
|
||||
if self._profiles is None:
|
||||
self._reach.delete_profiles(self._old_profiles)
|
||||
try:
|
||||
self._profiles = self._reach.import_geometry(self._filename)
|
||||
self._profiles.reverse()
|
||||
except Exception as e:
|
||||
for profile in self._old_profiles:
|
||||
self._reach.insert_profile(self._row, profile)
|
||||
logger_exception(e)
|
||||
exception_message_box(e)
|
||||
else:
|
||||
self._reach.delete_profiles(self._old_profiles)
|
||||
for profile in self._profiles:
|
||||
self._reach.insert_profile(self._row, profile)
|
||||
|
||||
@@ -281,6 +291,35 @@ class PurgeCommand(QUndoCommand):
|
||||
profile.purge(self._np_purge)
|
||||
|
||||
|
||||
class ChangeReachCommand(QUndoCommand):
|
||||
def __init__(self, new_reach, parent):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._old_reach = parent._study.river.current_reach()
|
||||
self._new_reach = new_reach
|
||||
self._parent = parent
|
||||
|
||||
def undo(self):
|
||||
p = self._parent
|
||||
p._reach = self._old_reach.reach
|
||||
p._study.river.set_current_reach(self._old_reach)
|
||||
p.setup_table()
|
||||
p.update_redraw()
|
||||
p.find(QTableView, "tableView").selectionModel()\
|
||||
.selectionChanged\
|
||||
.connect(p.select_current_profile)
|
||||
|
||||
def redo(self):
|
||||
p = self._parent
|
||||
p._reach = self._new_reach.reach
|
||||
p._study.river.set_current_reach(self._new_reach)
|
||||
p.setup_table()
|
||||
p.update_redraw()
|
||||
p.find(QTableView, "tableView").selectionModel()\
|
||||
.selectionChanged\
|
||||
.connect(p.select_current_profile)
|
||||
|
||||
|
||||
class ShiftCommand(QUndoCommand):
|
||||
def __init__(self, reach, rows, dx, dy, dz):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
@@ -58,9 +58,9 @@ class UpdateRKDialog(PamhyrDialog):
|
||||
|
||||
lower_gl = list(map(str.lower, self._gl))
|
||||
for i, gl in enumerate(lower_gl):
|
||||
if gl == "rd":
|
||||
if gl == "rg":
|
||||
self._begin_dir = self._gl[i]
|
||||
elif gl == "rg":
|
||||
elif gl == "rd":
|
||||
self._end_dir = self._gl[i]
|
||||
|
||||
self._orientation = 0
|
||||
|
||||
@@ -47,6 +47,8 @@ from View.Tools.PamhyrWindow import PamhyrWindow
|
||||
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
|
||||
from View.Tools.Plot.PamhyrCanvas import MplCanvas
|
||||
|
||||
from View.SelectReach.Window import SelectReachWindow
|
||||
|
||||
from Meshing.Mage import (
|
||||
MeshingWithMage, MeshingWithMageMailleurTT
|
||||
)
|
||||
@@ -207,6 +209,7 @@ class GeometryWindow(PamhyrWindow):
|
||||
"action_update_rk": self.update_rk,
|
||||
"action_purge": self.purge,
|
||||
"action_shift": self.shift,
|
||||
"action_select_reach": self.select_reach,
|
||||
}
|
||||
|
||||
for action in actions:
|
||||
@@ -228,9 +231,9 @@ class GeometryWindow(PamhyrWindow):
|
||||
|
||||
def _update(self, redraw=False, propagate=True):
|
||||
if redraw:
|
||||
self._plot_xy.draw()
|
||||
self._plot_rkc.draw()
|
||||
self._plot_ac.draw()
|
||||
self._plot_xy.redraw(data=self._reach)
|
||||
self._plot_rkc.redraw(data=self._reach)
|
||||
self._plot_ac.redraw(data=self._reach)
|
||||
|
||||
self.select_current_profile()
|
||||
|
||||
@@ -547,6 +550,20 @@ class GeometryWindow(PamhyrWindow):
|
||||
logger_exception(e)
|
||||
return
|
||||
|
||||
def select_reach(self):
|
||||
try:
|
||||
dlg = SelectReachWindow(
|
||||
study=self._study,
|
||||
trad=self._trad,
|
||||
parent=self
|
||||
)
|
||||
if dlg.exec():
|
||||
self._table.change_reach(dlg.reach, self)
|
||||
|
||||
except Exception as e:
|
||||
logger_exception(e)
|
||||
return
|
||||
|
||||
def duplicate(self):
|
||||
rows = [
|
||||
row.row() for row in
|
||||
|
||||
@@ -28,7 +28,7 @@ class BasicHydraulicStructuresTranslate(MainTranslate):
|
||||
super(BasicHydraulicStructuresTranslate, self).__init__()
|
||||
|
||||
self._dict["Basic Hydraulic Structures"] = _translate(
|
||||
"BasicHydraulicStructures", "Basic Hydraulic Structure"
|
||||
"BasicHydraulicStructures", "Basic Hydraulic Structures"
|
||||
)
|
||||
|
||||
self._dict['msg_type_change_title'] = _translate(
|
||||
|
||||
@@ -165,7 +165,8 @@ class BasicHydraulicStructuresWindow(PamhyrWindow):
|
||||
profiles = reach.reach.get_profiles_from_rk(float(profile_rk))
|
||||
else:
|
||||
profiles = None
|
||||
if profiles is not None:
|
||||
|
||||
if profiles is not None and len(profiles) != 0:
|
||||
profile = profiles[0]
|
||||
else:
|
||||
profile = None
|
||||
|
||||
@@ -118,3 +118,4 @@ class PlotAC(PamhyrPlot):
|
||||
def update_clear(self):
|
||||
if self.line_rk is not None:
|
||||
self.line_rk.set_data([], [])
|
||||
self.update_idle()
|
||||
@@ -52,6 +52,7 @@ class PlotRKC(PamhyrPlot):
|
||||
self._auto_relim_update = True
|
||||
self._autoscale_update = True
|
||||
self.parent = parent
|
||||
self.anotate_lst = []
|
||||
|
||||
@property
|
||||
def river(self):
|
||||
@@ -69,23 +70,23 @@ class PlotRKC(PamhyrPlot):
|
||||
self.profile = None
|
||||
self.line_rk_zmin_zmax = None
|
||||
self.line_rk_zmin = None
|
||||
self.hs_vlines = None
|
||||
return
|
||||
|
||||
if self._current_reach is None:
|
||||
self.profile = None
|
||||
self.line_rk_zmin_zmax = None
|
||||
self.line_rk_zmin = None
|
||||
self.hs_vlines = None
|
||||
return
|
||||
|
||||
self.draw_data()
|
||||
self.draw_current()
|
||||
self.draw_data(highlight)
|
||||
|
||||
self.idle()
|
||||
self._init = True
|
||||
|
||||
def draw_data(self):
|
||||
def draw_data(self, highlight):
|
||||
reach = self._current_reach
|
||||
|
||||
rk = reach.reach.get_rk()
|
||||
z_min = reach.reach.get_z_min()
|
||||
z_max = reach.reach.get_z_max()
|
||||
@@ -107,25 +108,76 @@ class PlotRKC(PamhyrPlot):
|
||||
self.line_rk_zmin_zmax = self.canvas.axes.vlines(
|
||||
x=rk,
|
||||
ymin=z_min, ymax=z_max,
|
||||
color=self.color_plot,
|
||||
color=self.color_highlight(highlight),
|
||||
lw=1.,
|
||||
picker=10
|
||||
)
|
||||
|
||||
def draw_current(self):
|
||||
if self._current_profile is None:
|
||||
self.profile = None
|
||||
else:
|
||||
rk = [self._current_profile.rk,
|
||||
self._current_profile.rk]
|
||||
min_max = [self._current_profile.z_min(),
|
||||
self._current_profile.z_max()]
|
||||
self.draw_hs()
|
||||
# Draw HS
|
||||
|
||||
self.profile = self.canvas.axes.plot(
|
||||
rk, min_max,
|
||||
color=self.color_plot_current,
|
||||
lw=1.
|
||||
def draw_hs(self):
|
||||
reach = self._current_reach
|
||||
rk = reach.reach.get_rk()
|
||||
z_min = reach.reach.get_z_min()
|
||||
z_max = reach.reach.get_z_max()
|
||||
lhs = filter(
|
||||
lambda hs: hs._input_reach is reach,
|
||||
filter(
|
||||
lambda hs: hs._input_reach is not None,
|
||||
self.data.hydraulic_structures.lst
|
||||
)
|
||||
)
|
||||
|
||||
vx = []
|
||||
vymin = []
|
||||
vymax = []
|
||||
self.anotate_lst = []
|
||||
hs_color = []
|
||||
index = self.parent.tableView.selectedIndexes()
|
||||
for i, hs in enumerate(lhs):
|
||||
if i == index[0].row():
|
||||
hs_color.append("blue")
|
||||
elif hs.enabled:
|
||||
hs_color.append("red")
|
||||
else:
|
||||
hs_color.append("darkgrey")
|
||||
x = hs.input_rk
|
||||
if x is not None:
|
||||
a = self.canvas.axes.annotate(
|
||||
" > " + hs.name,
|
||||
(x, max(z_max)),
|
||||
horizontalalignment='left',
|
||||
verticalalignment='top',
|
||||
annotation_clip=True,
|
||||
fontsize=9,
|
||||
color=hs_color[-1],
|
||||
)
|
||||
self.anotate_lst.append(a)
|
||||
vx.append(x)
|
||||
vymin.append(min(z_min))
|
||||
vymax.append(max(z_max))
|
||||
|
||||
self.hs_vlines = self.canvas.axes.vlines(
|
||||
x=vx, ymin=vymin, ymax=vymax,
|
||||
linestyle="--",
|
||||
lw=1.,
|
||||
color=hs_color,
|
||||
)
|
||||
|
||||
def color_highlight(self, highlight):
|
||||
|
||||
reach = self._current_reach
|
||||
colors = [self.color_plot_river_bottom] * reach.reach.number_profiles
|
||||
|
||||
if highlight is not None:
|
||||
rk = reach.reach.get_rk()
|
||||
rows = [i for i in range(len(rk))
|
||||
if (rk[i] >= highlight[0] and rk[i] <= highlight[1])]
|
||||
if len(rows) > 0:
|
||||
for row in rows:
|
||||
colors[row] = "blue"
|
||||
return colors
|
||||
|
||||
def set_reach(self, reach):
|
||||
self._current_reach = reach
|
||||
@@ -166,11 +218,19 @@ class PlotRKC(PamhyrPlot):
|
||||
if self.line_rk_zmin is not None:
|
||||
self.line_rk_zmin.set_data([], [])
|
||||
|
||||
if self.hs_vlines is not None:
|
||||
self.hs_vlines.remove()
|
||||
self.hs_vlines = None
|
||||
|
||||
for a in self.anotate_lst:
|
||||
a.remove()
|
||||
self.anotate_lst = []
|
||||
|
||||
self.canvas.figure.canvas.draw_idle()
|
||||
|
||||
def clear_profile(self):
|
||||
if self.profile is not None:
|
||||
self.profile.set_data([], [])
|
||||
self.profile[0].set_data([], [])
|
||||
|
||||
self.canvas.figure.canvas.draw_idle()
|
||||
|
||||
|
||||
@@ -96,15 +96,18 @@ class ComboBoxDelegate(QItemDelegate):
|
||||
reach = self._data.hydraulic_structures\
|
||||
.get(index.row())\
|
||||
.input_reach
|
||||
val = reduce(
|
||||
lambda acc, p: p if text == p.display_name() else acc,
|
||||
reach.reach.profiles,
|
||||
None
|
||||
profiles = list(
|
||||
filter(
|
||||
lambda p: p.display_name() == text,
|
||||
reach.reach.profiles
|
||||
)
|
||||
)
|
||||
else:
|
||||
val = text
|
||||
|
||||
model.setData(index, val)
|
||||
value = profiles[0].rk if len(profiles) > 0 else None
|
||||
else:
|
||||
value = text
|
||||
|
||||
model.setData(index, value)
|
||||
editor.close()
|
||||
editor.deleteLater()
|
||||
|
||||
|
||||
@@ -77,29 +77,29 @@ class PlotDRK(PamhyrPlot):
|
||||
rk = self.data.get_rk()
|
||||
elevation = self.data.get_elevation()
|
||||
|
||||
sorted_rk, sorted_elevation = zip(
|
||||
*sorted(zip(rk, elevation))
|
||||
)
|
||||
|
||||
self.line_rk_elevation = self.canvas.axes.plot(
|
||||
rk, elevation,
|
||||
sorted_rk, sorted_elevation,
|
||||
color=self.color_plot_river_water,
|
||||
**self.plot_default_kargs
|
||||
)
|
||||
|
||||
z_min = self.data.reach.reach.get_z_min()
|
||||
geometry_rk = self.data.reach.reach.get_rk()
|
||||
|
||||
filtred_elevation = list(
|
||||
map(
|
||||
lambda x: elevation[x[0]],
|
||||
filter(
|
||||
lambda x: x[1] in geometry_rk,
|
||||
enumerate(rk)
|
||||
)
|
||||
)
|
||||
sorted_geometry_rk, sorted_z_min = zip(
|
||||
*sorted(zip(geometry_rk, z_min), reverse=True)
|
||||
)
|
||||
|
||||
self.collection = self.canvas.axes.fill_between(
|
||||
geometry_rk, z_min, filtred_elevation,
|
||||
poly_x = sorted_rk + sorted_geometry_rk
|
||||
poly_y = sorted_elevation + sorted_z_min
|
||||
|
||||
self.collection = self.canvas.axes.fill(
|
||||
poly_x, poly_y,
|
||||
color=self.color_plot_river_water_zone,
|
||||
alpha=0.7, interpolate=True
|
||||
alpha=0.7,
|
||||
)
|
||||
|
||||
@timer
|
||||
|
||||
@@ -39,7 +39,7 @@ from View.Tools.PamhyrTable import PamhyrTableModel
|
||||
from View.InitialConditions.UndoCommand import (
|
||||
SetCommand, AddCommand, DelCommand,
|
||||
SortCommand, MoveCommand, InsertCommand,
|
||||
DuplicateCommand, GenerateCommand,
|
||||
DuplicateCommand, GenerateCommand, ReplaceDataCommand,
|
||||
)
|
||||
|
||||
logger = logging.getLogger()
|
||||
@@ -111,20 +111,15 @@ class InitialConditionTableModel(PamhyrTableModel):
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
if self._headers[column] == "rk":
|
||||
val = self._lst.get(row)
|
||||
section = val[self._headers[column]]
|
||||
if section is None:
|
||||
return ""
|
||||
|
||||
return section.display_name()
|
||||
elif self._headers[column] == "speed":
|
||||
if self._headers[column] == "velocity":
|
||||
z = self._lst.get(row)["elevation"]
|
||||
q = self._lst.get(row)["discharge"]
|
||||
profile = self._lst.get(row)["rk"]
|
||||
if profile is not None:
|
||||
speed = profile.speed(q, z)
|
||||
return f"{speed:.4f}"
|
||||
profile = self._reach.reach.get_profiles_from_rk(
|
||||
self._lst.get(row)["rk"]
|
||||
)
|
||||
if len(profile) >= 1:
|
||||
velocity = profile[0].speed(q, z)
|
||||
return f"{velocity:.4f}"
|
||||
|
||||
return ""
|
||||
elif self._headers[column] not in ["name", "comment"]:
|
||||
@@ -279,8 +274,8 @@ class InitialConditionTableModel(PamhyrTableModel):
|
||||
)
|
||||
|
||||
self._undo.push(
|
||||
InsertCommand(
|
||||
self._lst, index,
|
||||
ReplaceDataCommand(
|
||||
self._lst,
|
||||
list(
|
||||
map(
|
||||
lambda d: self._lst.new_from_data(*d),
|
||||
|
||||
@@ -205,3 +205,24 @@ class GenerateCommand(QUndoCommand):
|
||||
self._param[1],
|
||||
self._option,
|
||||
self._param[2])
|
||||
|
||||
|
||||
class ReplaceDataCommand(QUndoCommand):
|
||||
def __init__(self, ics, new_data):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._ics = ics
|
||||
self._copy = self._ics.data
|
||||
self._new_data = new_data
|
||||
self._rows = list(range(len(ics.data)))
|
||||
self._new_rows = list(range(len(new_data)))
|
||||
|
||||
def undo(self):
|
||||
self._ics.delete_i(self._new_rows)
|
||||
for row, el in enumerate(self._copy):
|
||||
self._ics.insert(row, el)
|
||||
|
||||
def redo(self):
|
||||
self._ics.delete_i(self._rows)
|
||||
for row, el in enumerate(self._new_data):
|
||||
self._ics.insert(row, el)
|
||||
@@ -23,6 +23,7 @@ from tools import trace, timer, logger_exception
|
||||
|
||||
from View.Tools.PamhyrWindow import PamhyrWindow
|
||||
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtGui import (
|
||||
QKeySequence,
|
||||
)
|
||||
@@ -30,7 +31,7 @@ from PyQt5.QtGui import (
|
||||
from PyQt5.QtCore import (
|
||||
Qt, QVariant, QAbstractTableModel,
|
||||
QCoreApplication, QModelIndex, pyqtSlot,
|
||||
QRect,
|
||||
QRect, QSettings, QItemSelectionModel,
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
@@ -265,27 +266,38 @@ class InitialConditionsWindow(PamhyrWindow):
|
||||
self._update()
|
||||
|
||||
def import_from_file(self):
|
||||
workdir = os.path.dirname(self._study.filename)
|
||||
|
||||
return self.file_dialog(
|
||||
select_file="ExistingFile",
|
||||
callback=lambda d: self._import_from_file(d[0]),
|
||||
directory=workdir,
|
||||
default_suffix=".BIN",
|
||||
file_filter=["Mage (*.BIN)"],
|
||||
options = QFileDialog.Options()
|
||||
settings = QSettings(QSettings.IniFormat,
|
||||
QSettings.UserScope, 'MyOrg', )
|
||||
options |= QFileDialog.DontUseNativeDialog
|
||||
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
|
||||
self,
|
||||
self._trad["open_file"],
|
||||
"",
|
||||
";; ".join(["Mage (*.BIN)"]),
|
||||
options=options
|
||||
)
|
||||
|
||||
if filename != "":
|
||||
size = os.stat(filename).st_size
|
||||
# self._table.import_geometry(0, filename)
|
||||
self._import_from_file(filename)
|
||||
|
||||
def _import_from_file(self, file_name):
|
||||
solver = Mage8("dummy")
|
||||
name = os.path.basename(file_name)\
|
||||
.replace(".BIN", "")
|
||||
|
||||
def reading():
|
||||
self._tmp_results = solver.results(
|
||||
self._study,
|
||||
os.path.dirname(file_name),
|
||||
name=name
|
||||
)
|
||||
try:
|
||||
self._tmp_results = solver.results(
|
||||
self._study,
|
||||
os.path.dirname(file_name),
|
||||
name=name
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to open results")
|
||||
logger_exception(e)
|
||||
|
||||
dlg = WaitingDialog(
|
||||
payload_fn=reading,
|
||||
@@ -294,7 +306,8 @@ class InitialConditionsWindow(PamhyrWindow):
|
||||
)
|
||||
dlg.exec_()
|
||||
results = self._tmp_results
|
||||
self._import_from_results(results)
|
||||
if results.is_valid:
|
||||
self._import_from_results(results)
|
||||
|
||||
def _import_from_results(self, results):
|
||||
logger.debug(f"import from results: {results}")
|
||||
@@ -367,40 +380,43 @@ class InitialConditionsWindow(PamhyrWindow):
|
||||
self._update()
|
||||
|
||||
def generate_growing_constant_depth(self):
|
||||
dlg = DepthDialog(self.depth_value,
|
||||
self.depth_option,
|
||||
trad=self._trad,
|
||||
parent=self)
|
||||
if dlg.exec():
|
||||
self.depth_value = dlg.value
|
||||
self.depth_option = dlg.option
|
||||
self._table.generate("growing",
|
||||
self.depth_value,
|
||||
self.depth_option)
|
||||
self._update()
|
||||
|
||||
def generate_discharge(self):
|
||||
dlg = DischargeDialog(self.discharge_value,
|
||||
self.discharge_option,
|
||||
if self._reach.reach.number_profiles > 0:
|
||||
dlg = DepthDialog(self.depth_value,
|
||||
self.depth_option,
|
||||
trad=self._trad,
|
||||
parent=self)
|
||||
if dlg.exec():
|
||||
self.discharge_value = dlg.value
|
||||
self.discharge_option = dlg.option
|
||||
self._table.generate("discharge",
|
||||
self.discharge_value,
|
||||
self.discharge_option)
|
||||
self._update()
|
||||
if dlg.exec():
|
||||
self.depth_value = dlg.value
|
||||
self.depth_option = dlg.option
|
||||
self._table.generate("growing",
|
||||
self.depth_value,
|
||||
self.depth_option)
|
||||
self._update()
|
||||
|
||||
def generate_discharge(self):
|
||||
if self._reach.reach.number_profiles > 1:
|
||||
dlg = DischargeDialog(self.discharge_value,
|
||||
self.discharge_option,
|
||||
trad=self._trad,
|
||||
parent=self)
|
||||
if dlg.exec():
|
||||
self.discharge_value = dlg.value
|
||||
self.discharge_option = dlg.option
|
||||
self._table.generate("discharge",
|
||||
self.discharge_value,
|
||||
self.discharge_option)
|
||||
self._update()
|
||||
|
||||
def generate_height(self):
|
||||
dlg = HeightDialog(self.height_values,
|
||||
self.height_option,
|
||||
trad=self._trad,
|
||||
parent=self)
|
||||
if dlg.exec():
|
||||
self.height_values = dlg.values
|
||||
self.height_option = dlg.option
|
||||
self._table.generate("height",
|
||||
self.height_values,
|
||||
self.height_option)
|
||||
self._update()
|
||||
if self._reach.reach.number_profiles > 0:
|
||||
dlg = HeightDialog(self.height_values,
|
||||
self.height_option,
|
||||
trad=self._trad,
|
||||
parent=self)
|
||||
if dlg.exec():
|
||||
self.height_values = dlg.values
|
||||
self.height_option = dlg.option
|
||||
self._table.generate("height",
|
||||
self.height_values,
|
||||
self.height_option)
|
||||
self._update()
|
||||
@@ -39,7 +39,7 @@ class ICTranslate(MainTranslate):
|
||||
"rk": self._dict["unit_rk"],
|
||||
"discharge": self._dict["unit_discharge"],
|
||||
"elevation": self._dict["unit_elevation"],
|
||||
"height": self._dict["unit_height"],
|
||||
"speed": self._dict["unit_speed"],
|
||||
"height": self._dict["unit_depth"],
|
||||
"velocity": self._dict["unit_velocity"],
|
||||
# "comment": _translate("InitialCondition", "Comment"),
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
# Table.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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
|
||||
import traceback
|
||||
from tools import trace, timer
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
Qt, QVariant, QAbstractTableModel,
|
||||
QCoreApplication, QModelIndex, pyqtSlot,
|
||||
QRect,
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QPushButton, QLineEdit,
|
||||
QFileDialog, QTableView, QAbstractItemView,
|
||||
QUndoStack, QShortcut, QAction, QItemDelegate,
|
||||
QComboBox,
|
||||
)
|
||||
|
||||
from View.Tools.PamhyrTable import PamhyrTableModel
|
||||
|
||||
from View.InitialConditionsAdisTS.UndoCommand import (
|
||||
SetCommand, AddCommand, SetCommandSpec,
|
||||
DelCommand,
|
||||
)
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
|
||||
class ComboBoxDelegate(QItemDelegate):
|
||||
def __init__(self, data=None, ic_spec_lst=None,
|
||||
trad=None, parent=None, mode="reaches"):
|
||||
super(ComboBoxDelegate, self).__init__(parent)
|
||||
|
||||
self._data = data
|
||||
self._mode = mode
|
||||
self._trad = trad
|
||||
self._ic_spec_lst = ic_spec_lst
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
self.editor = QComboBox(parent)
|
||||
|
||||
val = []
|
||||
if self._mode == "rk":
|
||||
reach_id = self._ic_spec_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()
|
||||
)
|
||||
)
|
||||
else:
|
||||
val = list(
|
||||
map(
|
||||
lambda n: n.name, self._data.edges()
|
||||
)
|
||||
)
|
||||
|
||||
self.editor.addItems(
|
||||
[self._trad['not_associated']] +
|
||||
val
|
||||
)
|
||||
|
||||
self.editor.setCurrentText(str(index.data(Qt.DisplayRole)))
|
||||
return self.editor
|
||||
|
||||
def setEditorData(self, editor, index):
|
||||
value = index.data(Qt.DisplayRole)
|
||||
self.editor.currentTextChanged.connect(self.currentItemChanged)
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
text = str(editor.currentText())
|
||||
model.setData(index, text)
|
||||
editor.close()
|
||||
editor.deleteLater()
|
||||
|
||||
def updateEditorGeometry(self, editor, option, index):
|
||||
r = QRect(option.rect)
|
||||
if self.editor.windowFlags() & Qt.Popup:
|
||||
if editor.parent() is not None:
|
||||
r.setTopLeft(self.editor.parent().mapToGlobal(r.topLeft()))
|
||||
editor.setGeometry(r)
|
||||
|
||||
@pyqtSlot()
|
||||
def currentItemChanged(self):
|
||||
self.commitData.emit(self.sender())
|
||||
|
||||
|
||||
class InitialConditionTableModel(PamhyrTableModel):
|
||||
def __init__(self, river=None, data=None, **kwargs):
|
||||
self._river = river
|
||||
|
||||
super(InitialConditionTableModel, self).__init__(data=data, **kwargs)
|
||||
|
||||
self._data = data
|
||||
|
||||
def _setup_lst(self):
|
||||
self._lst = self._data._data
|
||||
|
||||
def rowCount(self, parent):
|
||||
return len(self._lst)
|
||||
|
||||
def data(self, index, role):
|
||||
if role != Qt.ItemDataRole.DisplayRole:
|
||||
return QVariant()
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
if self._headers[column] == "name":
|
||||
n = self._lst[row].name
|
||||
if n is None or n == "":
|
||||
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
|
||||
elif self._headers[column] == "start_rk":
|
||||
n = self._lst[row].start_rk
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
elif self._headers[column] == "end_rk":
|
||||
n = self._lst[row].end_rk
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
elif self._headers[column] == "concentration":
|
||||
n = self._lst[row].concentration
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
elif self._headers[column] == "eg":
|
||||
n = self._lst[row].eg
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
elif self._headers[column] == "em":
|
||||
n = self._lst[row].em
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
elif self._headers[column] == "ed":
|
||||
n = self._lst[row].ed
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
elif self._headers[column] == "rate":
|
||||
n = self._lst[row].rate
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
|
||||
return QVariant()
|
||||
|
||||
def setData(self, index, value, role=Qt.EditRole):
|
||||
if not index.isValid() or role != Qt.EditRole:
|
||||
return False
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
try:
|
||||
if self._headers[column] != "reach":
|
||||
self._undo.push(
|
||||
SetCommandSpec(
|
||||
self._lst, row, self._headers[column], value
|
||||
)
|
||||
)
|
||||
elif self._headers[column] == "reach":
|
||||
self._undo.push(
|
||||
SetCommandSpec(
|
||||
self._lst, row, self._headers[column],
|
||||
self._river.edge(value).id
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.info(e)
|
||||
logger.debug(traceback.format_exc())
|
||||
|
||||
self.dataChanged.emit(index, index)
|
||||
return True
|
||||
|
||||
def add(self, row, parent=QModelIndex()):
|
||||
self.beginInsertRows(parent, row, row - 1)
|
||||
|
||||
self._undo.push(
|
||||
AddCommand(
|
||||
self._data, self._lst, row
|
||||
)
|
||||
)
|
||||
|
||||
self.endInsertRows()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def delete(self, rows, parent=QModelIndex()):
|
||||
self.beginRemoveRows(parent, rows[0], rows[-1])
|
||||
|
||||
self._undo.push(
|
||||
DelCommand(
|
||||
self._data, self._lst, rows
|
||||
)
|
||||
)
|
||||
|
||||
self.endRemoveRows()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def undo(self):
|
||||
self._undo.undo()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def redo(self):
|
||||
self._undo.redo()
|
||||
self.layoutChanged.emit()
|
||||
@@ -0,0 +1,110 @@
|
||||
# Table.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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
|
||||
import traceback
|
||||
from tools import trace, timer
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
Qt, QVariant, QAbstractTableModel,
|
||||
QCoreApplication, QModelIndex, pyqtSlot,
|
||||
QRect,
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QPushButton, QLineEdit,
|
||||
QFileDialog, QTableView, QAbstractItemView,
|
||||
QUndoStack, QShortcut, QAction, QItemDelegate,
|
||||
QComboBox,
|
||||
)
|
||||
|
||||
from View.Tools.PamhyrTable import PamhyrTableModel
|
||||
|
||||
from View.InitialConditionsAdisTS.UndoCommand import (
|
||||
SetCommand,
|
||||
)
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
|
||||
class InitialConditionTableDefaultModel(PamhyrTableModel):
|
||||
def __init__(self, **kwargs):
|
||||
super(InitialConditionTableDefaultModel, self).__init__(**kwargs)
|
||||
|
||||
def data(self, index, role):
|
||||
if role != Qt.ItemDataRole.DisplayRole:
|
||||
return QVariant()
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
if self._headers[column] == "name":
|
||||
return self._data[row].name
|
||||
elif self._headers[column] == "concentration":
|
||||
n = self._data[row].concentration
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
elif self._headers[column] == "eg":
|
||||
n = self._data[row].eg
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
elif self._headers[column] == "em":
|
||||
n = self._data[row].em
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
elif self._headers[column] == "ed":
|
||||
n = self._data[row].ed
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return n
|
||||
|
||||
return QVariant()
|
||||
|
||||
def setData(self, index, value, role=Qt.EditRole):
|
||||
if not index.isValid() or role != Qt.EditRole:
|
||||
return False
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
try:
|
||||
if self._headers[column] is not None:
|
||||
self._undo.push(
|
||||
SetCommand(
|
||||
self._data, row, self._headers[column], value
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.info(e)
|
||||
logger.debug(traceback.format_exc())
|
||||
|
||||
self.dataChanged.emit(index, index)
|
||||
return True
|
||||
|
||||
def undo(self):
|
||||
self._undo.undo()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def redo(self):
|
||||
self._undo.redo()
|
||||
self.layoutChanged.emit()
|
||||
@@ -0,0 +1,196 @@
|
||||
# UndoCommand.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 -*-
|
||||
|
||||
from copy import deepcopy
|
||||
from tools import trace, timer
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QMessageBox, QUndoCommand, QUndoStack,
|
||||
)
|
||||
|
||||
from Model.InitialConditionsAdisTS.InitialConditionsAdisTS \
|
||||
import InitialConditionsAdisTS
|
||||
from Model.InitialConditionsAdisTS.InitialConditionsAdisTSList \
|
||||
import InitialConditionsAdisTSList
|
||||
|
||||
|
||||
class SetCommand(QUndoCommand):
|
||||
def __init__(self, data, row, column, 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 == "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
|
||||
|
||||
_type = float
|
||||
if column == "name":
|
||||
_type = str
|
||||
|
||||
self._new = _type(new_value)
|
||||
|
||||
def undo(self):
|
||||
if self._column == "name":
|
||||
self._data[self._row].name = 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
|
||||
|
||||
def redo(self):
|
||||
if self._column == "name":
|
||||
self._data[self._row].name = 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
|
||||
|
||||
|
||||
class SetCommandSpec(QUndoCommand):
|
||||
def __init__(self, data, row, column, 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)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
||||
class AddCommand(QUndoCommand):
|
||||
def __init__(self, data, ics_spec, index):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._data = data
|
||||
self._ics_spec = ics_spec
|
||||
self._index = index
|
||||
self._new = None
|
||||
|
||||
def undo(self):
|
||||
self._data.delete_i([self._index])
|
||||
|
||||
def redo(self):
|
||||
if self._new is None:
|
||||
self._new = self._data.new(self._index)
|
||||
else:
|
||||
self._data.insert(self._index, self._new)
|
||||
|
||||
|
||||
class DelCommand(QUndoCommand):
|
||||
def __init__(self, data, ics_spec, rows):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._data = data
|
||||
self._ics_spec = ics_spec
|
||||
self._rows = rows
|
||||
# self._data = data
|
||||
|
||||
self._ic = []
|
||||
for row in rows:
|
||||
self._ic.append((row, self._ics_spec[row]))
|
||||
self._ic.sort()
|
||||
|
||||
def undo(self):
|
||||
for row, el in self._ic:
|
||||
self._data.insert(row, el)
|
||||
|
||||
def redo(self):
|
||||
self._data.delete_i(self._rows)
|
||||
@@ -0,0 +1,287 @@
|
||||
# Window.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 os
|
||||
import logging
|
||||
|
||||
from tools import trace, timer, logger_exception
|
||||
|
||||
from View.Tools.PamhyrWindow import PamhyrWindow
|
||||
|
||||
from PyQt5.QtGui import (
|
||||
QKeySequence, QIcon,
|
||||
)
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
Qt, QVariant, QAbstractTableModel,
|
||||
QCoreApplication, QModelIndex, pyqtSlot,
|
||||
QRect, QItemSelectionModel,
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QPushButton, QLineEdit,
|
||||
QFileDialog, QTableView, QAbstractItemView,
|
||||
QUndoStack, QShortcut, QAction, QItemDelegate,
|
||||
QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
|
||||
QVBoxLayout, QToolBar, QAction, QToolButton,
|
||||
)
|
||||
|
||||
from Modules import Modules
|
||||
|
||||
from View.InitialConditionsAdisTS.UndoCommand import (
|
||||
SetCommand,
|
||||
)
|
||||
|
||||
from View.InitialConditionsAdisTS.TableDefault import (
|
||||
InitialConditionTableDefaultModel,
|
||||
)
|
||||
|
||||
from View.InitialConditionsAdisTS.Table import (
|
||||
InitialConditionTableModel, ComboBoxDelegate,
|
||||
)
|
||||
|
||||
from View.InitialConditionsAdisTS.translate import IcAdisTSTranslate
|
||||
|
||||
from Solver.Mage import Mage8
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class InitialConditionsAdisTSWindow(PamhyrWindow):
|
||||
_pamhyr_ui = "InitialConditionsAdisTS"
|
||||
_pamhyr_name = "Initial condition AdisTS"
|
||||
|
||||
def __init__(self, data=None, study=None, config=None, parent=None):
|
||||
self._data = []
|
||||
self._data.append(data)
|
||||
trad = IcAdisTSTranslate()
|
||||
|
||||
name = (
|
||||
trad[self._pamhyr_name] +
|
||||
" - " + study.name +
|
||||
" - " + self._data[0].name
|
||||
)
|
||||
|
||||
super(InitialConditionsAdisTSWindow, self).__init__(
|
||||
title=name,
|
||||
study=study,
|
||||
config=config,
|
||||
trad=trad,
|
||||
parent=parent
|
||||
)
|
||||
|
||||
self._hash_data.append(data)
|
||||
|
||||
self._ics_adists_lst = study.river.ic_adists
|
||||
|
||||
self.setup_table()
|
||||
|
||||
self.ui.setWindowTitle(self._title)
|
||||
|
||||
def setup_table(self):
|
||||
|
||||
path_icons = os.path.join(self._get_ui_directory(), f"ressources")
|
||||
|
||||
table_default = self.find(QTableView, f"tableView")
|
||||
|
||||
self._table = InitialConditionTableDefaultModel(
|
||||
table_view=table_default,
|
||||
table_headers=self._trad.get_dict("table_headers"),
|
||||
editable_headers=["name", "concentration", "eg", "em", "ed"],
|
||||
delegates={},
|
||||
data=self._data,
|
||||
undo=self._undo_stack,
|
||||
trad=self._trad
|
||||
)
|
||||
|
||||
table_default.setModel(self._table)
|
||||
table_default.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
table_default.horizontalHeader().setSectionResizeMode(
|
||||
QHeaderView.Stretch
|
||||
)
|
||||
table_default.setAlternatingRowColors(True)
|
||||
|
||||
layout = self.find(QVBoxLayout, f"verticalLayout_1")
|
||||
toolBar = QToolBar()
|
||||
layout.addWidget(toolBar)
|
||||
|
||||
action_add = QAction(self)
|
||||
action_add.setIcon(QIcon(os.path.join(path_icons, f"add.png")))
|
||||
action_add.triggered.connect(self.add)
|
||||
action_delete = QAction(self)
|
||||
action_delete.setIcon(QIcon(os.path.join(path_icons, f"del.png")))
|
||||
action_delete.triggered.connect(self.delete)
|
||||
|
||||
toolBar.addAction(action_add)
|
||||
toolBar.addAction(action_delete)
|
||||
|
||||
self.table_spec = QTableView()
|
||||
layout.addWidget(self.table_spec)
|
||||
|
||||
self._delegate_reach = ComboBoxDelegate(
|
||||
trad=self._trad,
|
||||
data=self._study.river,
|
||||
ic_spec_lst=self._data[0]._data,
|
||||
parent=self,
|
||||
mode="reaches"
|
||||
)
|
||||
self._delegate_rk = ComboBoxDelegate(
|
||||
trad=self._trad,
|
||||
data=self._study.river,
|
||||
ic_spec_lst=self._data[0]._data,
|
||||
parent=self,
|
||||
mode="rk"
|
||||
)
|
||||
|
||||
self._table_spec = InitialConditionTableModel(
|
||||
table_view=self.table_spec,
|
||||
table_headers=self._trad.get_dict("table_headers_spec"),
|
||||
editable_headers=["name", "reach", "start_rk",
|
||||
"end_rk", "concentration",
|
||||
"eg", "em", "ed", "rate"],
|
||||
delegates={
|
||||
"reach": self._delegate_reach,
|
||||
"start_rk": self._delegate_rk,
|
||||
"end_rk": self._delegate_rk
|
||||
},
|
||||
data=self._data[0],
|
||||
undo=self._undo_stack,
|
||||
trad=self._trad,
|
||||
river=self._study.river
|
||||
)
|
||||
|
||||
self.table_spec.setModel(self._table_spec)
|
||||
self.table_spec.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.table_spec.horizontalHeader().setSectionResizeMode(
|
||||
QHeaderView.Stretch
|
||||
)
|
||||
self.table_spec.setAlternatingRowColors(True)
|
||||
|
||||
selectionModel = self.table_spec.selectionModel()
|
||||
index = self.table_spec.model().index(0, 0)
|
||||
|
||||
selectionModel.select(
|
||||
index,
|
||||
QItemSelectionModel.Rows |
|
||||
QItemSelectionModel.ClearAndSelect |
|
||||
QItemSelectionModel.Select
|
||||
)
|
||||
self.table_spec.scrollTo(index)
|
||||
|
||||
def index_selected_row(self):
|
||||
# table = self.find(QTableView, f"tableView")
|
||||
table = self.table_spec
|
||||
rows = table.selectionModel()\
|
||||
.selectedRows()
|
||||
|
||||
if len(rows) == 0:
|
||||
return 0
|
||||
|
||||
return rows[0].row()
|
||||
|
||||
def index_selected_rows(self):
|
||||
# table = self.find(QTableView, f"tableView")
|
||||
table = self.table_spec
|
||||
return list(
|
||||
# Delete duplicate
|
||||
set(
|
||||
map(
|
||||
lambda i: i.row(),
|
||||
table.selectedIndexes()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def move_up(self):
|
||||
row = self.index_selected_row()
|
||||
self._table.move_up(row)
|
||||
self._update()
|
||||
|
||||
def move_down(self):
|
||||
row = self.index_selected_row()
|
||||
self._table.move_down(row)
|
||||
self._update()
|
||||
|
||||
def _copy(self):
|
||||
rows = list(
|
||||
map(
|
||||
lambda row: row.row(),
|
||||
self.tableView.selectionModel().selectedRows()
|
||||
)
|
||||
)
|
||||
|
||||
table = list(
|
||||
map(
|
||||
lambda eic: list(
|
||||
map(
|
||||
lambda k: eic[1][k],
|
||||
["rk", "discharge", "elevation"]
|
||||
)
|
||||
),
|
||||
filter(
|
||||
lambda eic: eic[0] in rows,
|
||||
enumerate(self._ics.lst())
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
self.copyTableIntoClipboard(table)
|
||||
|
||||
def _paste(self):
|
||||
header, data = self.parseClipboardTable()
|
||||
|
||||
if len(data) + len(header) == 0:
|
||||
return
|
||||
|
||||
logger.debug(
|
||||
"IC: Paste: " +
|
||||
f"header = {header}, " +
|
||||
f"data = {data}"
|
||||
)
|
||||
|
||||
try:
|
||||
row = self.index_selected_row()
|
||||
# self._table.paste(row, header, data)
|
||||
self._table.paste(row, [], data)
|
||||
except Exception as e:
|
||||
logger_exception(e)
|
||||
|
||||
self._update()
|
||||
|
||||
def _undo(self):
|
||||
self._table.undo()
|
||||
self._update()
|
||||
|
||||
def _redo(self):
|
||||
self._table.redo()
|
||||
self._update()
|
||||
|
||||
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])
|
||||
|
||||
def delete(self):
|
||||
rows = self.index_selected_rows()
|
||||
if len(rows) == 0:
|
||||
return
|
||||
self._table_spec.delete(rows)
|
||||
@@ -0,0 +1,53 @@
|
||||
# translate.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 -*-
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
from View.Translate import MainTranslate
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
|
||||
class IcAdisTSTranslate(MainTranslate):
|
||||
def __init__(self):
|
||||
super(IcAdisTSTranslate, self).__init__()
|
||||
|
||||
self._dict["Initial condition AdisTS"] = _translate(
|
||||
"InitialConditionAdisTS", "Initial condition AdisTS")
|
||||
|
||||
self._dict["rk"] = self._dict["unit_rk"]
|
||||
|
||||
self._sub_dict["table_headers"] = {
|
||||
"name": self._dict["name"],
|
||||
"concentration": self._dict["unit_concentration"],
|
||||
"eg": _translate("Unit", "EG (m)"),
|
||||
"em": _translate("Unit", "EM (m)"),
|
||||
"ed": _translate("Unit", "ED (m)"),
|
||||
}
|
||||
|
||||
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)"),
|
||||
"concentration": self._dict["unit_concentration"],
|
||||
"eg": _translate("Unit", "EG (m)"),
|
||||
"em": _translate("Unit", "EM (m)"),
|
||||
"ed": _translate("Unit", "ED (m)"),
|
||||
"rate": _translate("Unit", "Rate"),
|
||||
}
|
||||
@@ -60,7 +60,7 @@ class PlotXY(PamhyrPlot):
|
||||
def onpick(self, event):
|
||||
if event.mouseevent.inaxes != self.canvas.axes:
|
||||
return
|
||||
if event.mouseevent.button.value not in [1, 3]:
|
||||
if event.mouseevent.button not in [1, 3]:
|
||||
return
|
||||
|
||||
closest = self._closest_section(event)
|
||||
@@ -70,9 +70,9 @@ class PlotXY(PamhyrPlot):
|
||||
index = tableView.selectedIndexes()
|
||||
rk = self.data.get_rk()
|
||||
if self.parent._table is not None:
|
||||
if event.mouseevent.button.value == 1:
|
||||
if event.mouseevent.button == 1:
|
||||
table.setData(index[3], rk[closest[0]])
|
||||
if event.mouseevent.button.value == 3:
|
||||
if event.mouseevent.button == 3:
|
||||
table.setData(index[4], rk[closest[0]])
|
||||
|
||||
return
|
||||
@@ -141,7 +141,8 @@ class PlotXY(PamhyrPlot):
|
||||
if self._highlight_data is not None:
|
||||
rk_min, rk_max = self._highlight_data
|
||||
|
||||
colors = [self.color_plot for row in range(len(self._data))]
|
||||
colors = [self.color_plot_river_bottom
|
||||
for row in range(len(self._data))]
|
||||
for i, rk in enumerate(self.data.get_rk_complete_profiles()):
|
||||
if rk_min <= rk <= rk_max:
|
||||
colors[i] = self.color_plot_current
|
||||
|
||||
@@ -114,16 +114,20 @@ class ComboBoxDelegate(QItemDelegate):
|
||||
self.editor.currentTextChanged.connect(self.currentItemChanged)
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
value = str(editor.currentText())
|
||||
text = str(editor.currentText())
|
||||
|
||||
if self._mode == "rk":
|
||||
value = next(
|
||||
profiles = list(
|
||||
filter(
|
||||
lambda p: p.display_name() == value,
|
||||
lambda p: p.display_name() == text,
|
||||
self._data.reach.profiles
|
||||
)
|
||||
)
|
||||
|
||||
value = profiles[0].rk if len(profiles) > 0 else None
|
||||
else:
|
||||
value = text
|
||||
|
||||
model.setData(index, value)
|
||||
editor.close()
|
||||
editor.deleteLater()
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
# Plot.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 datetime import datetime
|
||||
|
||||
from tools import timer, trace
|
||||
from View.Tools.PamhyrPlot import PamhyrPlot
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
QCoreApplication
|
||||
)
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class Plot(PamhyrPlot):
|
||||
def __init__(self, mode="time", data=None,
|
||||
trad=None, canvas=None, toolbar=None,
|
||||
parent=None):
|
||||
super(Plot, self).__init__(
|
||||
canvas=canvas,
|
||||
trad=trad,
|
||||
data=data,
|
||||
toolbar=toolbar,
|
||||
parent=parent
|
||||
)
|
||||
|
||||
self._table_headers = self._trad.get_dict("table_headers")
|
||||
|
||||
header = self.data.header
|
||||
self.label_x = self._table_headers[header[0]]
|
||||
self.label_y = self._table_headers[header[1]]
|
||||
|
||||
self._mode = mode
|
||||
self._isometric_axis = False
|
||||
|
||||
self._auto_relim_update = True
|
||||
self._autoscale_update = True
|
||||
|
||||
def custom_ticks(self):
|
||||
if self.data.header[0] != "time":
|
||||
return
|
||||
|
||||
self.set_ticks_time_formater()
|
||||
|
||||
@timer
|
||||
def draw(self):
|
||||
self.init_axes()
|
||||
|
||||
if len(self.data) == 0:
|
||||
self._init = False
|
||||
return
|
||||
|
||||
self.draw_data()
|
||||
self.custom_ticks()
|
||||
|
||||
self.idle()
|
||||
self._init = True
|
||||
|
||||
def draw_data(self):
|
||||
# Plot data
|
||||
x = list(map(lambda v: v[0], self.data.data))
|
||||
y = list(map(lambda v: v[1], self.data.data))
|
||||
|
||||
self._line, = self.canvas.axes.plot(
|
||||
x, y,
|
||||
color=self.color_plot,
|
||||
**self.plot_default_kargs
|
||||
)
|
||||
|
||||
@timer
|
||||
def update(self, ind=None):
|
||||
if not self._init:
|
||||
self.draw()
|
||||
return
|
||||
|
||||
self.update_data()
|
||||
|
||||
self.update_idle()
|
||||
|
||||
def update_data(self):
|
||||
x = list(map(lambda v: v[0], self.data.data))
|
||||
y = list(map(lambda v: v[1], self.data.data))
|
||||
|
||||
self._line.set_data(x, y)
|
||||
@@ -0,0 +1,122 @@
|
||||
# Table.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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
|
||||
import traceback
|
||||
from datetime import date, time, datetime, timedelta
|
||||
|
||||
from tools import (
|
||||
trace, timer,
|
||||
timestamp_to_old_pamhyr_date,
|
||||
old_pamhyr_date_to_timestamp,
|
||||
timestamp_to_old_pamhyr_date_adists
|
||||
)
|
||||
|
||||
from View.Tools.PamhyrTable import PamhyrTableModel
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
Qt, QVariant, QAbstractTableModel,
|
||||
QCoreApplication, QModelIndex, pyqtSlot,
|
||||
QRect, QTime, QDateTime,
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QTableView, QAbstractItemView, QSpinBox,
|
||||
QTimeEdit, QDateTimeEdit, QItemDelegate,
|
||||
)
|
||||
|
||||
|
||||
from View.LateralContributionsAdisTS.Edit.UndoCommand import (
|
||||
SetDataCommand, AddCommand, DelCommand,
|
||||
)
|
||||
from View.LateralContributionsAdisTS.Edit.translate import *
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class TableModel(PamhyrTableModel):
|
||||
def data(self, index, role):
|
||||
if role == Qt.TextAlignmentRole:
|
||||
return Qt.AlignHCenter | Qt.AlignVCenter
|
||||
|
||||
if role != Qt.ItemDataRole.DisplayRole:
|
||||
return QVariant()
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
value = QVariant()
|
||||
|
||||
if 0 <= column < 2:
|
||||
v = self._data._data[row][column]
|
||||
if self._data._types[column] == float:
|
||||
value = f"{v:.4f}"
|
||||
elif self._data.header[column] == "time":
|
||||
if self._opt_data == "time":
|
||||
value = timestamp_to_old_pamhyr_date_adists(int(v))
|
||||
else:
|
||||
value = str(datetime.fromtimestamp(v))
|
||||
else:
|
||||
value = f"{v}"
|
||||
|
||||
return value
|
||||
|
||||
def setData(self, index, value, role=Qt.EditRole):
|
||||
if not index.isValid() or role != Qt.EditRole:
|
||||
return False
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
try:
|
||||
self._undo.push(
|
||||
SetDataCommand(
|
||||
self._data, row, column, value
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.info(e)
|
||||
logger.debug(traceback.format_exc())
|
||||
|
||||
self.dataChanged.emit(index, index)
|
||||
return True
|
||||
|
||||
def add(self, row, parent=QModelIndex()):
|
||||
self.beginInsertRows(parent, row, row - 1)
|
||||
|
||||
self._undo.push(
|
||||
AddCommand(
|
||||
self._data, row
|
||||
)
|
||||
)
|
||||
|
||||
self.endInsertRows()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def delete(self, rows, parent=QModelIndex()):
|
||||
self.beginRemoveRows(parent, rows[0], rows[-1])
|
||||
|
||||
self._undo.push(
|
||||
DelCommand(
|
||||
self._data, rows
|
||||
)
|
||||
)
|
||||
|
||||
self.endRemoveRows()
|
||||
@@ -0,0 +1,102 @@
|
||||
# UndoCommand.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 -*-
|
||||
|
||||
from copy import deepcopy
|
||||
from tools import trace, timer
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QMessageBox, QUndoCommand, QUndoStack,
|
||||
)
|
||||
|
||||
from Model.LateralContributionsAdisTS.LateralContributionAdisTS \
|
||||
import LateralContributionAdisTS
|
||||
|
||||
|
||||
class SetDataCommand(QUndoCommand):
|
||||
def __init__(self, data, index, column, new_value):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._data = data
|
||||
self._index = index
|
||||
self._column = column
|
||||
self._old = self._data._data[self._index][self._column]
|
||||
_type = self._data._types[self._column]
|
||||
self._new = _type(new_value)
|
||||
|
||||
def undo(self):
|
||||
if self._column == 0:
|
||||
self._data._data[self._index] = (
|
||||
self._old, self._data._data[self._index][1]
|
||||
)
|
||||
else:
|
||||
self._data._data[self._index] = (
|
||||
self._data._data[self._index][0], self._old
|
||||
)
|
||||
|
||||
def redo(self):
|
||||
if self._column == 0:
|
||||
self._data._data[self._index] = (
|
||||
self._new, self._data._data[self._index][1]
|
||||
)
|
||||
else:
|
||||
self._data._data[self._index] = (
|
||||
self._data._data[self._index][0], self._new
|
||||
)
|
||||
|
||||
|
||||
class AddCommand(QUndoCommand):
|
||||
def __init__(self, data, index):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._data = data
|
||||
self._index = index
|
||||
self._new = None
|
||||
|
||||
def undo(self):
|
||||
del self._data._data[self._index]
|
||||
|
||||
def redo(self):
|
||||
if self._new is None:
|
||||
self._new = self._data._data.insert(
|
||||
self._index, (
|
||||
self._data._types[0](0), self._data._types[1](0.0)
|
||||
)
|
||||
)
|
||||
else:
|
||||
self._data._data.insert(self._index, self._new)
|
||||
|
||||
|
||||
class DelCommand(QUndoCommand):
|
||||
def __init__(self, data, rows):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._data = data
|
||||
self._rows = rows
|
||||
|
||||
self._lc = []
|
||||
for row in rows:
|
||||
self._lc.append((row, self._data._data[row]))
|
||||
self._lc.sort()
|
||||
|
||||
def undo(self):
|
||||
for row, el in self._lc:
|
||||
self._data._data.insert(row, el)
|
||||
|
||||
def redo(self):
|
||||
for row in self._rows:
|
||||
del self._data._data[row]
|
||||
@@ -0,0 +1,189 @@
|
||||
# Window.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 -*-
|
||||
|
||||
from tools import timer, trace
|
||||
|
||||
from View.Tools.PamhyrWindow import PamhyrWindow
|
||||
from View.Tools.PamhyrDelegate import PamhyrExTimeDelegate
|
||||
|
||||
from PyQt5.QtGui import (
|
||||
QKeySequence,
|
||||
)
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
Qt, QVariant, QAbstractTableModel, QCoreApplication,
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QPushButton, QLineEdit,
|
||||
QFileDialog, QTableView, QAbstractItemView,
|
||||
QUndoStack, QShortcut, QAction, QItemDelegate,
|
||||
QHeaderView,
|
||||
)
|
||||
|
||||
from View.Tools.Plot.PamhyrCanvas import MplCanvas
|
||||
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
|
||||
|
||||
from View.LateralContributionsAdisTS.Edit.translate import LCETranslate
|
||||
from View.LateralContributionsAdisTS.Edit.Table import TableModel
|
||||
from View.LateralContributionsAdisTS.Edit.Plot import Plot
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
|
||||
class EditLateralContributionAdisTSWindow(PamhyrWindow):
|
||||
_pamhyr_ui = "EditLateralContributionAdisTS"
|
||||
_pamhyr_name = "Edit lateral contribution AdisTS"
|
||||
|
||||
def __init__(self, data=None,
|
||||
study=None, config=None,
|
||||
parent=None):
|
||||
self._data = data
|
||||
|
||||
trad = LCETranslate()
|
||||
name = trad[self._pamhyr_name]
|
||||
|
||||
super(EditLateralContributionAdisTSWindow, self).__init__(
|
||||
title=name,
|
||||
study=study,
|
||||
config=config,
|
||||
trad=trad,
|
||||
parent=parent
|
||||
)
|
||||
|
||||
if self._data is not None:
|
||||
if self._data.edge is not None:
|
||||
edge_name = next(filter(
|
||||
lambda edge: edge.id == self._data.edge,
|
||||
self._study.river.edges()
|
||||
)).name
|
||||
else:
|
||||
edge_name = trad['not_associated']
|
||||
|
||||
name += (
|
||||
f"{study.name} - " +
|
||||
f"{edge_name})"
|
||||
)
|
||||
|
||||
self._hash_data.append(data)
|
||||
|
||||
self.setup_table()
|
||||
self.setup_plot()
|
||||
self.setup_connections()
|
||||
|
||||
def setup_table(self):
|
||||
self._delegate_time = PamhyrExTimeDelegate(
|
||||
data=self._data,
|
||||
mode=self._study.time_system,
|
||||
parent=self
|
||||
)
|
||||
|
||||
headers = {}
|
||||
table_headers = self._trad.get_dict("table_headers")
|
||||
for h in self._data.header:
|
||||
headers[h] = table_headers[h]
|
||||
|
||||
table = self.find(QTableView, "tableView")
|
||||
self._table = TableModel(
|
||||
table_view=table,
|
||||
table_headers=headers,
|
||||
editable_headers=self._data.header,
|
||||
delegates={
|
||||
# "time": self._delegate_time,
|
||||
},
|
||||
data=self._data,
|
||||
undo=self._undo_stack,
|
||||
trad=self._trad,
|
||||
opt_data=self._study.time_system
|
||||
)
|
||||
|
||||
table.setModel(self._table)
|
||||
table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||||
table.setAlternatingRowColors(True)
|
||||
|
||||
def setup_plot(self):
|
||||
self.canvas = MplCanvas(width=5, height=4, dpi=100)
|
||||
self.canvas.setObjectName("canvas")
|
||||
self.toolbar = PamhyrPlotToolbar(
|
||||
self.canvas, self
|
||||
)
|
||||
self.verticalLayout.addWidget(self.toolbar)
|
||||
self.verticalLayout.addWidget(self.canvas)
|
||||
|
||||
self.plot = Plot(
|
||||
canvas=self.canvas,
|
||||
data=self._data,
|
||||
mode=self._study.time_system,
|
||||
toolbar=self.toolbar,
|
||||
trad=self._trad,
|
||||
parent=self
|
||||
)
|
||||
self.plot.draw()
|
||||
|
||||
def setup_connections(self):
|
||||
self.find(QAction, "action_add").triggered.connect(self.add)
|
||||
self.find(QAction, "action_del").triggered.connect(self.delete)
|
||||
|
||||
self._table.dataChanged.connect(self.update)
|
||||
|
||||
def update(self):
|
||||
self.plot.update()
|
||||
|
||||
def index_selected_row(self):
|
||||
table = self.find(QTableView, "tableView")
|
||||
return table.selectionModel()\
|
||||
.selectedRows()[0]\
|
||||
.row()
|
||||
|
||||
def index_selected_rows(self):
|
||||
table = self.find(QTableView, "tableView")
|
||||
return list(
|
||||
# Delete duplicate
|
||||
set(
|
||||
map(
|
||||
lambda i: i.row(),
|
||||
table.selectedIndexes()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def add(self):
|
||||
rows = self.index_selected_rows()
|
||||
if len(self._data) == 0 or len(rows) == 0:
|
||||
self._table.add(0)
|
||||
else:
|
||||
self._table.add(rows[0])
|
||||
|
||||
self.plot.update()
|
||||
|
||||
def delete(self):
|
||||
rows = self.index_selected_rows()
|
||||
if len(rows) == 0:
|
||||
return
|
||||
|
||||
self._table.delete(rows)
|
||||
self.plot.update()
|
||||
|
||||
def _undo(self):
|
||||
self._table.undo()
|
||||
self.plot.update()
|
||||
|
||||
def _redo(self):
|
||||
self._table.redo()
|
||||
self.plot.update()
|
||||
@@ -0,0 +1,38 @@
|
||||
# translate.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 -*-
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
from View.Translate import MainTranslate
|
||||
from View.LateralContributionsAdisTS.translate import LCTranslate
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
|
||||
class LCETranslate(LCTranslate):
|
||||
def __init__(self):
|
||||
super(LCETranslate, self).__init__()
|
||||
self._dict["Edit lateral contribution AdisTS"] = _translate(
|
||||
"LateralContributionAdisTS", "Edit lateral contribution AdisTS"
|
||||
)
|
||||
|
||||
self._sub_dict["table_headers"] = {
|
||||
"time": self._dict["time"],
|
||||
"date": self._dict["date"],
|
||||
"rate": _translate("LateralContributionAdisTS", "Mass Flow"),
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
# Table.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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
|
||||
import traceback
|
||||
|
||||
from tools import trace, timer
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
Qt, QVariant, QAbstractTableModel,
|
||||
QCoreApplication, QModelIndex, pyqtSlot,
|
||||
QRect,
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QPushButton, QLineEdit,
|
||||
QFileDialog, QTableView, QAbstractItemView,
|
||||
QUndoStack, QShortcut, QAction, QItemDelegate,
|
||||
QComboBox,
|
||||
)
|
||||
|
||||
from View.LateralContributionsAdisTS.UndoCommand import (
|
||||
SetEdgeCommand,
|
||||
SetBeginCommand, SetEndCommand,
|
||||
AddCommand, DelCommand,
|
||||
)
|
||||
|
||||
from View.Tools.PamhyrTable import PamhyrTableModel
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
|
||||
class ComboBoxDelegate(QItemDelegate):
|
||||
def __init__(self, data=None, mode="edge",
|
||||
trad=None, parent=None):
|
||||
super(ComboBoxDelegate, self).__init__(parent)
|
||||
|
||||
self._data = data
|
||||
self._mode = mode
|
||||
self._trad = trad
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data
|
||||
|
||||
@data.setter
|
||||
def data(self, data):
|
||||
self._data = data
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
self.editor = QComboBox(parent)
|
||||
|
||||
if self._mode == "rk":
|
||||
if self._data is None:
|
||||
self.editor.addItems(
|
||||
["0"]
|
||||
)
|
||||
else:
|
||||
self.editor.addItems(
|
||||
list(
|
||||
map(str, self._data.reach.get_rk())
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.editor.addItems(
|
||||
[self._trad['not_associated']] +
|
||||
self._data.edges_names()
|
||||
)
|
||||
|
||||
self.editor.setCurrentText(index.data(Qt.DisplayRole))
|
||||
return self.editor
|
||||
|
||||
def setEditorData(self, editor, index):
|
||||
value = index.data(Qt.DisplayRole)
|
||||
self.editor.currentTextChanged.connect(self.currentItemChanged)
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
text = str(editor.currentText())
|
||||
model.setData(index, text)
|
||||
editor.close()
|
||||
editor.deleteLater()
|
||||
|
||||
def updateEditorGeometry(self, editor, option, index):
|
||||
r = QRect(option.rect)
|
||||
if self.editor.windowFlags() & Qt.Popup:
|
||||
if editor.parent() is not None:
|
||||
r.setTopLeft(self.editor.parent().mapToGlobal(r.topLeft()))
|
||||
editor.setGeometry(r)
|
||||
|
||||
@pyqtSlot()
|
||||
def currentItemChanged(self):
|
||||
self.commitData.emit(self.sender())
|
||||
|
||||
|
||||
class TableModel(PamhyrTableModel):
|
||||
def __init__(self, pollutant=None, lcs_list=None, trad=None, **kwargs):
|
||||
self._trad = trad
|
||||
self._lcs_list = lcs_list
|
||||
self._pollutant = pollutant
|
||||
|
||||
super(TableModel, self).__init__(trad=trad, **kwargs)
|
||||
|
||||
def _setup_lst(self):
|
||||
self._lst = self._data.lateral_contributions_adists.lst
|
||||
self._tab = self._opt_data
|
||||
self._long_types = self._trad.get_dict("long_types")
|
||||
|
||||
def rowCount(self, parent):
|
||||
return len(self._lst)
|
||||
|
||||
def data(self, index, role):
|
||||
if role != Qt.ItemDataRole.DisplayRole:
|
||||
return QVariant()
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
if self._headers[column] == "edge":
|
||||
n = self._lst[row].edge
|
||||
if n is None:
|
||||
return self._trad['not_associated']
|
||||
return next(filter(
|
||||
lambda edge: edge.id == n, self._data.edges()
|
||||
)).name
|
||||
elif self._headers[column] == "begin_rk":
|
||||
return str(self._lst[row].begin_rk)
|
||||
elif self._headers[column] == "end_rk":
|
||||
return str(self._lst[row].end_rk)
|
||||
|
||||
return QVariant()
|
||||
|
||||
def setData(self, index, value, role=Qt.EditRole):
|
||||
if not index.isValid() or role != Qt.EditRole:
|
||||
return False
|
||||
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
try:
|
||||
if self._headers[column] == "edge":
|
||||
self._undo.push(
|
||||
SetEdgeCommand(
|
||||
self._lcs_list, self._lst,
|
||||
row,
|
||||
self._data.edge(value).id
|
||||
)
|
||||
)
|
||||
elif self._headers[column] == "begin_rk":
|
||||
self._undo.push(
|
||||
SetBeginCommand(
|
||||
self._lcs_list, self._lst, row, value
|
||||
)
|
||||
)
|
||||
elif self._headers[column] == "end_rk":
|
||||
self._undo.push(
|
||||
SetEndCommand(
|
||||
self._lcs_list, self._lst, row, value
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.info(e)
|
||||
logger.debug(traceback.format_exc())
|
||||
|
||||
self.dataChanged.emit(index, index)
|
||||
return True
|
||||
|
||||
def add(self, row, parent=QModelIndex()):
|
||||
self.beginInsertRows(parent, row, row - 1)
|
||||
|
||||
self._undo.push(
|
||||
AddCommand(
|
||||
self._pollutant, self._lcs_list, self._lst, row
|
||||
)
|
||||
)
|
||||
|
||||
self.endInsertRows()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def delete(self, rows, parent=QModelIndex()):
|
||||
self.beginRemoveRows(parent, rows[0], rows[-1])
|
||||
|
||||
self._undo.push(
|
||||
DelCommand(
|
||||
self._lst, rows
|
||||
)
|
||||
)
|
||||
|
||||
self.endRemoveRows()
|
||||
self.layoutChanged.emit()
|
||||
Loaded 100 of 312 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user