Compare commits

..
147 Commits
Author SHA1 Message Date
Theophile Terraz 8913a774ea force Mage sulver type in open from file 2025-01-15 14:50:06 +01:00
Theophile Terraz 36c3785cde Merge branch 'master' into compare_results 2025-01-15 14:27:58 +01:00
Theophile Terraz f75a25d010 debug 2025-01-15 14:26:31 +01:00
Theophile Terraz 5ed905df71 Merge branch 'master' into compare_results 2025-01-15 14:25:12 +01:00
Theophile Terraz ea4fa81cc4 debug 2025-01-15 14:13:46 +01:00
Theophile Terraz 8862be991e Merge branch 'master' into compare_results 2025-01-15 14:08:02 +01:00
Theophile Terraz 8aea55909e debug 2025-01-15 14:07:19 +01:00
Theophile Terraz 1289967abb debug 2025-01-15 14:05:11 +01:00
Theophile Terraz 41d1fb9d59 put compare solvers in results window 2025-01-10 16:54:02 +01:00
Theophile Terraz 8d9a196059 merge 2025-01-09 13:47:26 +01:00
Theophile Terraz 9db90debeb translation 2024-12-20 17:13:44 +01:00
Theophile Terraz 2333ab540e debug 2024-12-20 15:54:25 +01:00
Theophile Terraz c81d65f527 rm test case 2024-12-20 15:49:30 +01:00
Theophile Terraz bf44f5c027 rm test case 2024-12-20 15:47:48 +01:00
Theophile Terraz d1400f12a6 debug 2024-12-20 15:41:19 +01:00
Theophile Terraz c610717220 pep8 2024-12-20 10:47:08 +01:00
Theophile Terraz 5edcf15072 Export Adis to csv 2024-12-20 10:05:56 +01:00
Theophile Terraz e4bffce6f4 debug node name in adis BC 2024-12-19 11:24:30 +01:00
Theophile Terraz 54fe032791 pep8 + colors + debug 2024-12-18 16:19:41 +01:00
Theophile Terraz c668dddcfd more work on adis results 2024-12-18 15:17:13 +01:00
Theophile Terraz bdc975f280 work on plot multiple lines 2024-12-17 16:22:07 +01:00
Theophile Terraz 4315fb94f3 debug last solver 2024-12-16 15:34:19 +01:00
Theophile Terraz eb1163f712 debug ci 2024-12-11 11:26:46 +01:00
Theophile Terraz 42f3c02c3e work on release 2024-12-09 17:17:28 +01:00
Theophile Terraz 31ebcf07e0 debug 2024-12-09 14:06:01 +01:00
Theophile Terraz 95612e8ce9 debug 2024-12-09 14:04:48 +01:00
Theophile Terraz e56d88937a pep8 2024-12-09 09:28:00 +01:00
Theophile Terraz 8c697a9621 debug incomplete database 2024-12-09 09:03:02 +01:00
Theophile Terraz 2d3747b3d3 add adists default path 2024-12-05 17:26:47 +01:00
Theophile Terraz b38e252746 cleaning 2024-12-05 16:50:10 +01:00
Theophile Terraz f1d6eb8990 more work on adists results 2024-12-05 16:03:40 +01:00
Theophile Terraz c2c77304d7 remove suspension BC in main window 2024-12-05 14:30:22 +01:00
Theophile Terraz 0b051ad3c6 debug adis BC 2024-12-05 13:38:22 +01:00
Theophile Terraz b72ffe03c3 more work on adis results 2024-12-04 16:09:33 +01:00
Theophile Terraz 01de0cfe93 remove empty tab 2024-12-04 12:01:46 +01:00
Theophile Terraz d6f152958f work on results adis 2024-12-04 11:59:02 +01:00
Theophile Terraz b85962bfcd work on results, type_pol 2024-12-03 14:59:38 +01:00
Theophile Terraz a1ae3f42f4 work on adis results 2024-12-02 15:07:50 +01:00
Theophile Terraz 21876586df debug read_bin 2024-12-02 13:48:57 +01:00
Theophile Terraz a317cd849f more pep8 2024-11-29 11:52:06 +01:00
Theophile Terraz 3b8a49d429 add src/View/ui/OutputRKAdisTS.ui 2024-11-29 09:31:03 +01:00
Theophile Terraz 872235e94f pep8 Model 2024-11-29 09:30:37 +01:00
Theophile Terraz da7ef60bef pep8 src/Solver/AdisTS.py 2024-11-28 14:32:45 +01:00
Theophile Terraz 824669f70f debug 2024-11-28 11:46:20 +01:00
Theophile Terraz 558085705f start to work on compare results 2024-11-28 09:20:20 +01:00
Youcef AOUAD 19a3f3d310 mass zero results fixed 2024-11-28 08:31:40 +01:00
Youcef AOUAD 49aab685fb to fix mass zero 2024-11-27 14:50:44 +01:00
Youcef AOUAD 4383599d30 node name fixed 2024-11-12 09:01:25 +01:00
Youcef AOUAD ae6bca3033 display results from main complete 2024-11-08 09:36:03 +01:00
Theophile Terraz 1266884f93 Merge branch 'terraz_dev' into adists_release 2024-11-07 16:29:19 +01:00
Theophile Terraz f91527464d Merge branch 'master' into terraz_dev 2024-11-07 16:28:24 +01:00
Youcef AOUAD 97f8a3ccde correction of results 2024-11-07 14:20:58 +01:00
Theophile Terraz cef67540c2 debug 2024-11-06 13:40:16 +01:00
Theophile Terraz ff38571933 merge 2024-11-05 13:29:12 +01:00
Theophile Terraz 4f69553bd3 merge 2024-10-31 14:02:38 +01:00
Theophile Terraz 55da743977 Merge branch 'master' into terraz_dev 2024-10-30 10:31:23 +01:00
Youcef AOUAD e711f0fb6f results from main solver type 2024-10-24 10:40:13 +02:00
Youcef AOUAD 36fd8cdc8a display adists results from main 2024-10-22 11:35:29 +02:00
Theophile Terraz 4a24e39224 Merge branch 'adists_release' into terraz_dev 2024-10-21 14:27:14 +02:00
Youcef AOUAD 97c161fc3f correct names nodes reaches 2024-10-21 14:21:25 +02:00
Theophile Terraz 00c43d91cd Merge branch 'master' of gitlab-ssh.irstea.fr:theophile.terraz/pamhyr into terraz_dev 2024-10-21 14:18:50 +02:00
Youcef AOUAD 100cfd095e db version fixed for adists files 2024-10-17 10:36:25 +02:00
Youcef AOUAD 5047dbc85e tables load fixed 2024-10-16 11:15:27 +02:00
Theophile Terraz c7eb10884a remove solver 2024-10-14 14:46:20 +02:00
Theophile Terraz 0ec1a39328 adistslc => adistswc 2024-10-14 14:44:26 +02:00
Theophile Terraz 92c9b106e3 add time slider (results) 2024-10-11 08:57:40 +02:00
Theophile Terraz 91e780432d Merge branch 'master' of gitlab-ssh.irstea.fr:theophile.terraz/pamhyr into terraz_dev 2024-10-08 10:59:13 +02:00
Theophile Terraz 81f15208cf debug 2024-10-07 16:55:24 +02:00
Theophile Terraz c9c5bfe093 Merge branch 'master' of gitlab-ssh.irstea.fr:theophile.terraz/pamhyr into terraz_dev 2024-10-07 15:35:53 +02:00
Theophile Terraz 0483a2463b Merge branch 'master' of gitlab-ssh.irstea.fr:theophile.terraz/pamhyr into terraz_dev 2024-10-07 15:30:06 +02:00
Theophile Terraz 9364b99158 Merge branch 'master' of gitlab-ssh.irstea.fr:theophile.terraz/pamhyr into terraz_dev 2024-10-07 14:27:42 +02:00
Theophile Terraz fa2cc303cd Merge branch 'master' of gitlab-ssh.irstea.fr:theophile.terraz/pamhyr into terraz_dev 2024-10-07 14:25:58 +02:00
Theophile Terraz 2b85f221bb Merge branch 'master' of gitlab-ssh.irstea.fr:theophile.terraz/pamhyr into terraz_dev 2024-10-04 10:16:12 +02:00
Theophile Terraz 55d96a3767 Merge branch 'test_merge_master' into terraz_dev 2024-10-02 10:58:55 +02:00
Theophile Terraz 6000a91e74 Merge branch 'master' of gitlab-ssh.irstea.fr:theophile.terraz/pamhyr into terraz_dev 2024-10-02 10:57:25 +02:00
Theophile Terraz bd6e200a70 merge 2024-10-02 10:57:17 +02:00
Theophile Terraz 143f468c26 kp to rk 2024-09-25 12:03:48 +02:00
Theophile Terraz 334633f0fb replace kp with rk 2024-09-24 16:28:23 +02:00
Theophile Terraz a52e6b86f4 merge master 2024-09-24 16:04:22 +02:00
Youcef AOUAD 9ae16ca6ec delete page from ui resultsadists 2024-09-17 11:04:39 +02:00
Youcef AOUAD c7406d3bce raw data done for one timestamp 2024-09-17 10:23:41 +02:00
Youcef AOUAD cb8ecc752b raw data for adists 2024-09-16 16:33:02 +02:00
Youcef AOUAD 4c522ae050 plot tabs tot sediments done with limit effect 2024-09-13 18:21:38 +02:00
Youcef AOUAD 689d5a7592 plot tot sediments done 2024-09-13 09:40:57 +02:00
Youcef AOUAD c118d4fae8 tot sed plots 2024-09-12 15:27:52 +02:00
Youcef AOUAD 1baf2e8dd5 plot c m done 2024-09-12 11:05:19 +02:00
Youcef AOUAD 1fcdaa8b81 pollutant selection done 2024-09-11 15:14:39 +02:00
Youcef AOUAD 71729578dd reload plot H 2024-09-11 10:29:56 +02:00
Youcef AOUAD abe7bf969a delete sliders 2024-09-10 11:44:31 +02:00
Youcef AOUAD 673ae3908c var phys 2024-09-09 11:44:17 +02:00
Youcef AOUAD 1a31c9122e first plot complete 2024-09-05 12:35:16 +02:00
Youcef AOUAD c53d7d9c25 bug results fixed for data 2024-09-03 14:03:42 +02:00
Youcef AOUAD 02f14ffdf1 debug results profiles read from bin 2024-09-03 11:43:13 +02:00
Youcef AOUAD 84fd841f86 results adists pollutants table view 2024-09-02 15:12:07 +02:00
Youcef AOUAD 629b856788 data view complete pols 2024-09-02 11:42:51 +02:00
Youcef AOUAD f3007e2fe2 adists results data phys variables complete 2024-08-30 12:08:43 +02:00
Youcef AOUAD a7a1a4ddd1 adists results read bin data 2024-08-29 17:26:53 +02:00
Youcef AOUAD 0719db17ed results adists tables 2024-08-26 16:02:25 +02:00
Youcef AOUAD 018192714c windiow results adists mage 2024-08-23 14:59:28 +02:00
Theophile Terraz e1f69e41b0 pep8 2024-08-21 15:48:11 +02:00
Youcef AOUAD 501000fa6b display message run adists 2024-07-24 14:25:29 +02:00
Youcef AOUAD 75149000db rep mage select fixed 2024-07-22 15:22:55 +02:00
Youcef AOUAD 3c82148f44 select adists fixed 2024-07-17 14:24:26 +02:00
Youcef AOUAD 7f2ff9d341 time display ald fixed 2024-07-15 10:06:03 +02:00
Youcef AOUAD 57be80bcd4 cancel merge with master and update 2024-07-12 11:26:28 +02:00
Youcef AOUAD 29b5c97345 Solver adists done 2024-06-28 11:40:57 +02:00
Youcef AOUAD b171eb7d32 Solver connect 2024-06-27 15:46:27 +02:00
Youcef AOUAD 0e18d43496 Export Files 2 2024-06-26 17:04:14 +02:00
Youcef AOUAD bd4222adfa Export files 1 2024-06-26 12:55:07 +02:00
Youcef AOUAD 1e4761609d DIF Complete 2024-06-24 15:55:42 +02:00
Youcef AOUAD de72e2e96b DIF Model 2024-06-24 11:23:09 +02:00
Youcef AOUAD 95a0dabe68 d90 Complete 2024-06-21 15:46:41 +02:00
Youcef AOUAD 9f53ad685e d90 Model 2024-06-21 11:39:04 +02:00
Youcef AOUAD 02c597d109 LCS Complete 2024-06-20 11:59:57 +02:00
Youcef AOUAD 510bee115b LCS -data 2024-06-20 11:12:14 +02:00
Youcef AOUAD ed250f836d LC View Tab 2024-06-19 16:01:58 +02:00
Youcef AOUAD a47410a0f1 LC Model 2024-06-19 11:10:09 +02:00
Youcef AOUAD 704130966e BC AdisTS Complete 2024-06-18 15:40:06 +02:00
Youcef AOUAD b927aa81b3 BC AdisTS View Table 2024-06-17 16:34:41 +02:00
Youcef AOUAD d154ac96c2 BC AdisTS View1 2024-06-17 12:02:07 +02:00
Youcef AOUAD 6067ba601e BC Model 2024-06-14 14:52:29 +02:00
Youcef AOUAD 716d0af96f INI GOOD 2024-06-13 14:35:40 +02:00
Youcef AOUAD f917dd9284 INISPEC DEL 2024-06-12 16:56:05 +02:00
Youcef AOUAD cd68bebf03 INI SPEC Table 2024-06-12 15:06:28 +02:00
Youcef AOUAD 35cc348f03 INI SPEC 2024-06-11 16:22:07 +02:00
Youcef AOUAD 6f3e1e31f0 INI table db 2024-06-11 11:43:04 +02:00
Youcef AOUAD 7df0750c58 INI Default 2024-06-10 16:26:56 +02:00
Youcef AOUAD bcf825a4f7 ini view 2024-06-07 16:34:28 +02:00
Youcef AOUAD 3afd9f5025 IC model 2024-06-06 15:26:55 +02:00
Youcef AOUAD 3cb47ee5c8 ic 2024-06-06 12:00:52 +02:00
Youcef AOUAD 1535247e1b pollutants edit db 2024-06-05 11:35:41 +02:00
Youcef AOUAD 37dd4ebaef characteristics pollutant 2024-06-04 16:29:03 +02:00
Youcef AOUAD 1e8949067b pollutant characteristics 2024-06-03 16:23:39 +02:00
Youcef AOUAD 1945e94190 pollutants names 2024-06-03 12:01:41 +02:00
Youcef AOUAD 1ba971af24 adists run 2024-05-31 15:12:40 +02:00
Youcef AOUAD 0b5357d12a adisTS NUM file 1 2024-05-30 16:03:44 +02:00
Youcef AOUAD 3be361b03c adistTS NUM file 2024-05-30 16:01:13 +02:00
Youcef AOUAD 7681b5d326 adists export 2024-05-29 16:19:46 +02:00
Youcef AOUAD 0a35d387f7 adists check list 2024-05-28 16:39:11 +02:00
Youcef AOUAD b40efc2426 output kp 2024-05-27 16:48:03 +02:00
Youcef AOUAD 59aae7fd40 work 6 2024-05-24 16:07:33 +02:00
Youcef AOUAD a633e4bccf work 5 2024-05-24 15:59:25 +02:00
Youcef AOUAD b56b8099ab work 5 2024-05-23 16:48:31 +02:00
Youcef AOUAD f9fe5c9b6c work 4 2024-05-23 16:38:32 +02:00
Youcef AOUAD 13b954d580 work 3 2024-05-22 16:13:18 +02:00
Youcef AOUAD f53289ef1b output table 2024-05-21 16:29:41 +02:00
Youcef AOUAD 1b3ce91582 adding adists solver 1 2024-05-17 16:43:38 +02:00
129 changed files with 16042 additions and 251 deletions

No files matched your search

+2
View File
@@ -9,6 +9,8 @@
*.class *.class
tmp tmp
mage8
adists
*.tar *.tar
*.tar.* *.tar.*
+35
View File
@@ -83,6 +83,34 @@ dl-mage8-windows:
- mage8-windows/mailleurTT.exe - mage8-windows/mailleurTT.exe
- mage8-windows/libbief.dll - mage8-windows/libbief.dll
dl-adists-linux:
stage: downloads
tags:
- linux
rules:
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
script:
- mkdir -p adists-linux
- cd adists-linux
- curl -L -o adists https://gitlab.irstea.fr/jean-baptiste.faure/adists/-/releases/v1.0.1/downloads/executables/adists
artifacts:
paths:
- adists-linux/adists
dl-adists-windows:
stage: downloads
tags:
- linux
rules:
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
script:
- mkdir -p adists-windows
- cd adists-windows
- curl -L -o adists.exe https://gitlab.irstea.fr/jean-baptiste.faure/adists/-/releases/v1.0.1/downloads/executables/adists_win64.exe.exe
artifacts:
paths:
- adists-windows/adists.exe
############# #############
# CONFIGURE # # CONFIGURE #
############# #############
@@ -224,6 +252,8 @@ build-linux:
rules: rules:
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG - if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
needs: needs:
- job: dl-adists-linux
artifacts: true
- job: dl-mage8-linux - job: dl-mage8-linux
artifacts: true artifacts: true
- job: dl-mage8-doc - job: dl-mage8-doc
@@ -260,6 +290,9 @@ build-linux:
# Copy mage # Copy mage
- mkdir -p pamhyr/mage8 - mkdir -p pamhyr/mage8
- cp -v ../mage8-linux/* pamhyr/mage8/ - cp -v ../mage8-linux/* pamhyr/mage8/
# Copy adists
- mkdir -p pamhyr/adists
- cp -v ../adists-linux/* pamhyr/adists/
# Copy Pamhyr # Copy Pamhyr
- cp -r dist/pamhyr/* pamhyr/ - cp -r dist/pamhyr/* pamhyr/
# Pamhyr script to force x11 # Pamhyr script to force x11
@@ -317,6 +350,8 @@ build-windows:
needs: needs:
- job: dl-mage8-windows - job: dl-mage8-windows
artifacts: true artifacts: true
- job: dl-adists-windows
artifacts: true
- job: dl-mage8-doc - job: dl-mage8-doc
artifacts: true artifacts: true
- job: set-version - job: set-version
+3
View File
@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml
+6
View File
@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.10 (pamhyr)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (pamhyr)" project-jdk-type="Python SDK" />
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/pamhyr.iml" filepath="$PROJECT_DIR$/.idea/pamhyr.iml" />
</modules>
</component>
</project>
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.10 (pamhyr)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+4
View File
@@ -46,6 +46,10 @@ copy /y ..\mage8-windows\mage_extraire.exe pamhyr\mage8\
copy /y ..\mage8-windows\mailleurTT.exe pamhyr\mage8\ copy /y ..\mage8-windows\mailleurTT.exe pamhyr\mage8\
copy /y ..\mage8-windows\libbief.dll pamhyr\mage8\ copy /y ..\mage8-windows\libbief.dll pamhyr\mage8\
rem adists
mkdir pamhyr\adists
copy /y ..\adists-windows\adists.exe pamhyr\adists\
rem Copy tests_cases rem Copy tests_cases
mkdir pamhyr\tests_cases mkdir pamhyr\tests_cases
mkdir pamhyr\tests_cases\Saar mkdir pamhyr\tests_cases\Saar
BIN
View File
Binary file not shown.
+52
View File
@@ -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
+2 -2
View File
@@ -395,7 +395,7 @@ class MeshingWithMageMailleurTT(AMeshingTool):
logger.info( logger.info(
f"! {self._exe_path()} " + f"! {self._exe_path()} " +
f"{st_file} {m_file} " + f"{st_file} {m_file} " +
f"update_kp " + f"update_rk " +
f"{str(step)} " + f"{str(step)} " +
f"{limites[0]} {limites[1]} " + f"{limites[0]} {limites[1]} " +
f"{directrices[0]} {directrices[1]} " + f"{directrices[0]} {directrices[1]} " +
@@ -410,7 +410,7 @@ class MeshingWithMageMailleurTT(AMeshingTool):
str, str,
[ [
st_file, m_file, st_file, m_file,
"update_kp", step, "update_rk", step,
limites[0], limites[1], limites[0], limites[1],
directrices[0], directrices[1], directrices[0], directrices[1],
orientation, lm, linear, origin, origin_value orientation, lm, linear, origin, origin_value
@@ -128,7 +128,8 @@ class BoundaryCondition(SQLSubModel):
bc.node = None bc.node = None
if row[3] != -1: if row[3] != -1:
bc.node = next(filter(lambda n: n.id == row[3], data["nodes"])) bc.node = next(filter(lambda n: n.id == row[3], data["nodes"]),
None)
values = execute( values = execute(
"SELECT ind, data0, data1 FROM boundary_condition_data " + "SELECT ind, data0, data1 FROM boundary_condition_data " +
@@ -0,0 +1,224 @@
# 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()
@@ -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
+196
View File
@@ -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()
+63
View File
@@ -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
+193
View File
@@ -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()
+253
View File
@@ -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()
+63
View File
@@ -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
+221
View File
@@ -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()
@@ -64,6 +64,7 @@ class HydraulicStructure(SQLSubModel):
@classmethod @classmethod
def _db_create(cls, execute): def _db_create(cls, execute):
execute(""" execute("""
CREATE TABLE hydraulic_structures( CREATE TABLE hydraulic_structures(
id INTEGER NOT NULL PRIMARY KEY, id INTEGER NOT NULL PRIMARY KEY,
@@ -154,6 +155,7 @@ class HydraulicStructure(SQLSubModel):
return new return new
def _db_save(self, execute, data=None): def _db_save(self, execute, data=None):
print("save hs unit")
execute(f"DELETE FROM hydraulic_structures WHERE id = {self.id}") execute(f"DELETE FROM hydraulic_structures WHERE id = {self.id}")
input_reach_id = -1 input_reach_id = -1
@@ -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
+159
View File
@@ -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
+181
View File
@@ -0,0 +1,181 @@
# 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
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:]
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 __len__(self):
return len(self._data)
+56
View File
@@ -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
+88
View File
@@ -0,0 +1,88 @@
# 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
import glob
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(),
}
repertory_results = os.path.join(repertory, "resultats")
self._pollutants_list = [
el.split("/")[-1][0:-4]
for el in glob.glob(repertory_results + "/*.bin")
]
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,
)
+136
View File
@@ -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
)
)
+83
View File
@@ -45,6 +45,17 @@ from Model.REPLine.REPLineList import REPLineList
from Solver.Solvers import solver_type_list 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
class RiverNode(Node, SQLSubModel): class RiverNode(Node, SQLSubModel):
_sub_classes = [] _sub_classes = []
@@ -228,6 +239,13 @@ class River(Graph, SQLSubModel):
HydraulicStructureList, HydraulicStructureList,
AddFileList, AddFileList,
REPLineList, REPLineList,
OutputRKAdistsList,
PollutantsList,
InitialConditionsAdisTSList,
BoundaryConditionsAdisTSList,
LateralContributionsAdisTSList,
D90AdisTSList,
DIFAdisTSList,
] ]
def __init__(self, status=None): def __init__(self, status=None):
@@ -251,6 +269,16 @@ class River(Graph, SQLSubModel):
) )
self._additional_files = AddFileList(status=self._status) self._additional_files = AddFileList(status=self._status)
self._rep_lines = REPLineList(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 @classmethod
def _db_create(cls, execute): def _db_create(cls, execute):
@@ -325,6 +353,25 @@ class River(Graph, SQLSubModel):
) )
new._rep_lines = REPLineList._db_load(execute, data) 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 return new
def _db_save(self, execute, data=None): def _db_save(self, execute, data=None):
@@ -344,6 +391,14 @@ class River(Graph, SQLSubModel):
for solver in self._parameters: for solver in self._parameters:
objs.append(self._parameters[solver]) 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) self._save_submodel(execute, objs, data)
return True return True
@@ -466,6 +521,34 @@ Last export at: @date."""
def parameters(self): def parameters(self):
return self._parameters 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): def get_params(self, solver):
if solver in self._parameters: if solver in self._parameters:
return self._parameters[solver] return self._parameters[solver]
+1 -1
View File
@@ -72,7 +72,7 @@ class Study(SQLModel):
StudyNetworkReachChecker(), StudyNetworkReachChecker(),
StudyGeometryChecker(), StudyGeometryChecker(),
StudyInitialConditionsChecker(), StudyInitialConditionsChecker(),
StudyBoundaryConditionChecker(), # StudyBoundaryConditionChecker(),
# DummyOK(), # DummyOK(),
# DummyWARNING(), # DummyWARNING(),
# DummyERROR(), # DummyERROR(),
+1
View File
@@ -50,6 +50,7 @@ class SQLModel(SQL):
self._create() # Create db self._create() # Create db
# self._save() # Save # self._save() # Save
else: else:
logger.info("Update database")
self._update() # Update db scheme if necessary self._update() # Update db scheme if necessary
# self._load() # Load data # self._load() # Load data
+2
View File
@@ -54,6 +54,7 @@ class Modules(IterableFlag):
RESERVOIR = auto() RESERVOIR = auto()
SEDIMENT_LAYER = auto() SEDIMENT_LAYER = auto()
ADDITIONAL_FILES = auto() ADDITIONAL_FILES = auto()
OUTPUT_RK = auto()
# Results # Results
RESULTS = auto() RESULTS = auto()
@@ -77,6 +78,7 @@ class Modules(IterableFlag):
cls.ADDITIONAL_FILES, cls.ADDITIONAL_FILES,
cls.RESULTS, cls.RESULTS,
cls.WINDOW_LIST, cls.WINDOW_LIST,
cls.OUTPUT_RK,
] ]
@classmethod @classmethod
+875
View File
@@ -0,0 +1,875 @@
# 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 glob
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:
f.write(f"NET ../{mage_rep}/{name}.NET\n")
f.write(f"REP ../{mage_rep}/{name}.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)
), f"{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"):
for ic_spec in pol_ics_spec_data:
f.write(" ".join((f"{ic_spec.name}",
"=",
f"{ic_spec.reach}",
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])-1):
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
if id_reach not in id_edges:
return
f.write(" ".join((f"{d90_spec.name}",
"=",
f"{id_reach}",
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_edges = list(map(lambda x: x.id, edges))
id_reach = dif_spec.reach
if id_reach not in id_edges:
return
if dif_spec.method != "generique":
f.write(" ".join((f"{dif_spec.method}",
"=",
f"{id_reach}",
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"{id_reach}",
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
rk = outputrk.rk
title = outputrk.title
if id_reach not in id_edges:
return
f.write(f"output = {id_reach} {rk} {title}\n")
@timer
def read_bin(self, study, repertory, results, qlog=None, name="0"):
repertory_results = os.path.join(repertory, "resultats")
files_bin_names = [el.split("/")[-1]
for el in glob.glob(repertory_results+"/*.bin")]
print("files names resultats: ", files_bin_names)
ifilename = os.path.join(repertory_results, files_bin_names[0])
logger.info(f"read_bin: Start reading '{ifilename}' ...")
print("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)
print("nombre de biefs : ", ibmax)
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_results, file), files_bin_names)
data_tmp = {}
for file_bin in path_files:
key_pol = file_bin.split("/")[-1][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)
# ##print("pol keys: ", pollutants_keys)
# ##print("t keys: ", timestamps_keys)
# ##print("phys var: ", phys_data_names)
# ##print("phys data names mass zero:",
# list(data_tmp[pollutants_keys[1]][timestamps_keys[0]].keys()))
# print("set timestamps keys: ", set(timestamps_keys))
# print("isma")
# ##print("iprofiles: ", iprofiles)
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(" ", "_")
print("adist ts results repertory: ", repertory)
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,
]
@timer
def export(self, study, repertory, mage_rep, qlog=None, name="0"):
print("cmd solver adistswc : ", self._cmd_solver)
self._study = study
name = study.name.replace(" ", "_")
# Generate files
files = []
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
+4
View File
@@ -1001,6 +1001,7 @@ class Mage8(Mage):
# Add profile id correspondance to reach # Add profile id correspondance to reach
key = (i1, i2) key = (i1, i2)
print("mage keys reachs: ", key)
iprofiles[key] = r iprofiles[key] = r
# Profile ID offset # Profile ID offset
@@ -1033,6 +1034,8 @@ class Mage8(Mage):
] ]
def ip_to_ri(r, i): return i - reach_offset[r] def ip_to_ri(r, i): return i - reach_offset[r]
print("mage iprofiles: ", iprofiles)
ts = set() ts = set()
end = False end = False
while not end: while not end:
@@ -1068,6 +1071,7 @@ class Mage8(Mage):
logger.debug(reachs[0].profiles[0]._data) logger.debug(reachs[0].profiles[0]._data)
results.set("timestamps", ts) results.set("timestamps", ts)
print("set timestamps mage: ", ts)
logger.info(f"read_bin: ... end with {len(ts)} timestamp read") logger.info(f"read_bin: ... end with {len(ts)} timestamp read")
@timer @timer
+3
View File
@@ -22,6 +22,7 @@ from Solver.GenericSolver import GenericSolver
from Solver.Mage import ( from Solver.Mage import (
Mage7, Mage8, MageFake7, Mage7, Mage8, MageFake7,
) )
from Solver.AdisTS import AdisTSwc
from Solver.RubarBE import Rubar3, RubarBE from Solver.RubarBE import Rubar3, RubarBE
_translate = QCoreApplication.translate _translate = QCoreApplication.translate
@@ -31,6 +32,7 @@ solver_long_name = {
# "mage7": "Mage v7", # "mage7": "Mage v7",
"mage8": "Mage v8", "mage8": "Mage v8",
# "mage_fake7": "Mage fake v7", # "mage_fake7": "Mage fake v7",
"adistswc": "Adis-TS_WC",
# "rubarbe": "RubarBE", # "rubarbe": "RubarBE",
# "rubar3": "Rubar3", # "rubar3": "Rubar3",
} }
@@ -40,6 +42,7 @@ solver_type_list = {
# "mage7": Mage7, # "mage7": Mage7,
"mage8": Mage8, "mage8": Mage8,
# "mage_fake7": MageFake7, # "mage_fake7": MageFake7,
"adistswc": AdisTSwc,
# "rubarbe": RubarBE, # "rubarbe": RubarBE,
# "rubar3": Rubar3, # "rubar3": Rubar3,
} }
+1 -1
View File
@@ -94,7 +94,7 @@ class BoundaryConditionWindow(PamhyrWindow):
def setup_table(self): def setup_table(self):
self._table = {} self._table = {}
for t in ["liquid", "solid", "suspenssion"]: for t in ["liquid", "solid"]:
self._delegate_type = ComboBoxDelegate( self._delegate_type = ComboBoxDelegate(
trad=self._trad, trad=self._trad,
data=self._study.river, 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,120 @@
# 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,
)
_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()
@@ -0,0 +1,104 @@
# 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._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._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._data.insert(row, el)
def redo(self):
for row in self._rows:
del self._data._data[row]
@@ -0,0 +1,209 @@
# 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):
if self._data.type == "Concentration":
self._data.header = ["time", "concentration"]
else:
self._data.header = ["time", "rate"]
headers = {}
table_headers = self._trad.get_dict("table_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._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 _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(
"BoundaryConditionAdisTS", "Edit boundary conditions AdisTS"
)
self._sub_dict["table_headers"] = {
"time": self._dict["time"],
"date": self._dict["date"],
"rate": _translate("BoundaryConditionAdisTS", "Rate"),
"concentration": _translate("BoundaryConditionAdisTS",
"Concentration"),
}
+228
View File
@@ -0,0 +1,228 @@
# 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", "Rate"]
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 _setup_lst(self):
self._lst = self._bc_list.lst
def rowCount(self, parent):
return len(self._lst)
def data(self, index, role):
if len(self._lst) != 0:
data = self._lst
else:
data = []
if role != Qt.ItemDataRole.DisplayRole:
return QVariant()
row = index.row()
column = index.column()
if self._headers[column] == "type":
n = data[row].type
if n is None or n == "":
return self._trad["not_associated"]
return n
elif self._headers[column] == "node":
n = data[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 = data[row].pollutant
if n is None or n == "not_associated" or n == "":
return self._trad["not_associated"]
return self._data._Pollutants.Pollutants_List[n].name
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._lst, row, value
)
)
elif self._headers[column] == "node":
self._undo.push(
SetNodeCommand(
self._lst, row, self._data.node(value)
)
)
elif self._headers[column] == "pol":
pol = next(filter(lambda x: x.name == value,
self._data._Pollutants.Pollutants_List))
self._undo.push(
SetPolCommand(
self._lst, 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, 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()
def undo(self):
self._undo.undo()
self.layoutChanged.emit()
def redo(self):
self._undo.redo()
self.layoutChanged.emit()
@@ -0,0 +1,120 @@
# 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[self._index].node
self._new = node.id
def undo(self):
self._bcs[self._index].node = self._old
def redo(self):
self._bcs[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[self._index].type
self._new = self._type
def undo(self):
self._bcs[self._index].type = self._old
def redo(self):
self._bcs[self._index].type = self._new
class SetPolCommand(QUndoCommand):
def __init__(self, bcs, index, pollutant):
QUndoCommand.__init__(self)
self._bcs = bcs
self._index = index
self._pollutant = pollutant
self._old = self._bcs[self._index].pollutant
self._new = self._pollutant
def undo(self):
self._bcs[self._index].pollutant = self._old
def redo(self):
self._bcs[self._index].pollutant = self._new
class AddCommand(QUndoCommand):
def __init__(self, pollutant, bcs_list, bcs, index):
QUndoCommand.__init__(self)
self._bcs = bcs
self._bc_list = bcs_list
self._pollutant = pollutant
self._index = index
self._new = None
def undo(self):
del self._bcs[self._index]
def redo(self):
if self._new is None:
self._new = self._bc_list.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[row]))
self._bc.sort()
def undo(self):
for row, el in self._bc:
self._bcs.insert(row, el)
def redo(self):
for row in self._rows:
del self._bcs[row]
+201
View File
@@ -0,0 +1,201 @@
# 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):
rows = self.index_selected_rows()
if len(self._bcs) == 0 or len(rows) == 0:
self._table.add(0)
else:
self._table.add(rows[0])
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.lst[row]
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")
}
+200
View File
@@ -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()
+222
View File
@@ -0,0 +1,222 @@
# 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] is "name":
n = self._lst[row].name
if n is None or n == "":
return self._trad['not_associated']
return n
elif self._headers[column] is "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] is "start_rk":
n = self._lst[row].start_rk
if n is None:
return self._trad['not_associated']
return n
elif self._headers[column] is "end_rk":
n = self._lst[row].end_rk
if n is None:
return self._trad['not_associated']
return n
elif self._headers[column] is "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":
print(self._river.edge(value).id)
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()
+95
View File
@@ -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()
+152
View File
@@ -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)
+287
View File
@@ -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.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):
print("del")
rows = self.index_selected_rows()
if len(rows) == 0:
print("len 0")
return
self._table_spec.delete(rows)
+46
View File
@@ -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"),
}
+236
View File
@@ -0,0 +1,236 @@
# 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] is "method":
n = self._lst[row].method
if n is None or n == "":
return self._trad['not_associated']
return n
elif self._headers[column] is "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] is "start_rk":
n = self._lst[row].start_rk
if n is None:
return self._trad['not_associated']
return n
elif self._headers[column] is "end_rk":
n = self._lst[row].end_rk
if n is None:
return self._trad['not_associated']
return n
elif self._headers[column] is "dif":
n = self._lst[row].dif
if n is None:
return self._trad['not_associated']
return n
elif self._headers[column] is "b":
n = self._lst[row].b
if n is None:
return self._trad['not_associated']
return n
elif self._headers[column] is "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":
print(self._river.edge(value).id)
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()
+108
View File
@@ -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] is "method":
n = self._data[row].method
if n is None:
return self._trad['not_associated']
return n
elif self._headers[column] is "dif":
n = self._data[row].dif
if n is None:
return self._trad['not_associated']
return n
elif self._headers[column] is "b":
n = self._data[row].b
if n is None:
return self._trad['not_associated']
return n
elif self._headers[column] is "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()
+176
View File
@@ -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)
+295
View File
@@ -0,0 +1,295 @@
# 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):
print("del")
rows = self.index_selected_rows()
if len(rows) == 0:
print("len 0")
return
self._table_spec.delete(rows)
+50
View File
@@ -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"),
}
+241
View File
@@ -0,0 +1,241 @@
# 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()))
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] is "name":
n = self._lst[row].name
if n is None or n == "":
return self._trad['not_associated']
return n
elif self._headers[column] is "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] is "start_rk":
n = self._lst[row].start_rk
if n is None:
return self._trad['not_associated']
return n
elif self._headers[column] is "end_rk":
n = self._lst[row].end_rk
if n is None:
return self._trad['not_associated']
return n
elif self._headers[column] is "concentration":
n = self._lst[row].concentration
if n is None:
return self._trad['not_associated']
return n
elif self._headers[column] is "eg":
n = self._lst[row].eg
if n is None:
return self._trad['not_associated']
return n
elif self._headers[column] is "em":
n = self._lst[row].em
if n is None:
return self._trad['not_associated']
return n
elif self._headers[column] is "ed":
n = self._lst[row].ed
if n is None:
return self._trad['not_associated']
return n
elif self._headers[column] is "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":
print(self._river.edge(value).id)
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] is "name":
return self._data[row].name
elif self._headers[column] is "concentration":
n = self._data[row].concentration
if n is None:
return self._trad['not_associated']
return n
elif self._headers[column] is "eg":
n = self._data[row].eg
if n is None:
return self._trad['not_associated']
return n
elif self._headers[column] is "em":
n = self._data[row].em
if n is None:
return self._trad['not_associated']
return n
elif self._headers[column] is "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)
+289
View File
@@ -0,0 +1,289 @@
# 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):
print("del")
rows = self.index_selected_rows()
if len(rows) == 0:
print("len 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"),
}
@@ -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()
@@ -0,0 +1,121 @@
# 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
from Model.LateralContributionsAdisTS.LateralContributionsAdisTSList \
import LateralContributionsAdisTSList
class SetBeginCommand(QUndoCommand):
def __init__(self, lcs, lcs_lst, index, new_value):
QUndoCommand.__init__(self)
self._lcs = lcs
self._lcs_lst = lcs_lst
self._index = index
self._old = self._lcs_lst[self._index].begin_rk
self._new = float(new_value)
def undo(self):
self._lcs_lst[self._index].begin_rk = float(self._old)
def redo(self):
self._lcs_lst[self._index].begin_rk = float(self._new)
class SetEndCommand(QUndoCommand):
def __init__(self, lcs, lcs_lst, index, new_value):
QUndoCommand.__init__(self)
self._lcs = lcs
self._lcs_lst = lcs_lst
self._index = index
self._old = self._lcs_lst[self._index].end_rk
self._new = float(new_value)
def undo(self):
self._lcs_lst[self._index].end_rk = float(self._old)
def redo(self):
self._lcs_lst[self._index].end_rk = float(self._new)
class SetEdgeCommand(QUndoCommand):
def __init__(self, lcs, lcs_lst, index, edge):
QUndoCommand.__init__(self)
self._lcs = lcs
self._lcs_lst = lcs_lst
self._index = index
self._old = self._lcs_lst[self._index].edge
self._new = edge
def undo(self):
self._lcs_lst[self._index].edge = self._old
def redo(self):
self._lcs_lst[self._index].edge = self._new
class AddCommand(QUndoCommand):
def __init__(self, pollutant, lcs, lcs_lst, index):
QUndoCommand.__init__(self)
self._pollutant = pollutant
self._lcs = lcs
self._lcs_lst = lcs_lst
self._index = index
self._new = None
def undo(self):
del self._lcs[self._index]
def redo(self):
if self._new is None:
self._new = self._lcs.new(self._index, self._pollutant)
else:
self._lcs_lst.insert(self._index, self._new)
class DelCommand(QUndoCommand):
def __init__(self, lcs, rows):
QUndoCommand.__init__(self)
self._lcs = lcs
self._rows = rows
self._bc = []
for row in rows:
self._bc.append((row, self._lcs[row]))
self._bc.sort()
def undo(self):
for row, el in self._bc:
self._lcs.insert(row, el)
def redo(self):
for row in self._rows:
del self._lcs[row]
@@ -0,0 +1,250 @@
# 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
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,
)
from View.LateralContributionsAdisTS.Table import (
TableModel, ComboBoxDelegate
)
from View.Tools.Plot.PamhyrCanvas import MplCanvas
from View.Geometry.PlotXY import PlotXY
from View.LateralContributionsAdisTS.translate \
import LCTranslate
from View.LateralContributionsAdisTS.Edit.Window \
import EditLateralContributionAdisTSWindow
logger = logging.getLogger()
class LateralContributionAdisTSWindow(PamhyrWindow):
_pamhyr_ui = "LateralContributionsAdisTS"
_pamhyr_name = "Lateral contribution AdisTS"
def __init__(self, study=None, pollutant=None, config=None, parent=None):
trad = LCTranslate()
name = trad[self._pamhyr_name] + " - " + study.name
self._pollutant = pollutant
super(LateralContributionAdisTSWindow, self).__init__(
title=name,
study=study,
trad=trad,
config=config,
parent=parent
)
self._lcs = self._study.river.lateral_contributions_adists
self.setup_table()
self.setup_graph()
self.setup_connections()
def setup_table(self):
self._table = {}
self._delegate_rk = []
delegate_rk = ComboBoxDelegate(
data=None,
mode="rk",
trad=self._trad,
parent=self
)
self._delegate_rk.append(delegate_rk)
self._delegate_edge = ComboBoxDelegate(
data=self._study.river,
mode="edge",
trad=self._trad,
parent=self
)
table = self.find(QTableView, f"tableView")
self._table = TableModel(
table_view=table,
table_headers=self._trad.get_dict("table_headers"),
editable_headers=self._trad.get_dict("table_headers"),
delegates={
"edge": self._delegate_edge,
"begin_rk": delegate_rk,
"end_rk": delegate_rk,
},
data=self._study.river,
undo=self._undo_stack,
trad=self._trad,
opt_data="liquid",
pollutant=self._pollutant,
lcs_list=self._lcs,
)
table.setModel(self._table)
table.setSelectionBehavior(QAbstractItemView.SelectRows)
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
table.setAlternatingRowColors(True)
def setup_graph(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas")
self.plot_layout = self.find(QVBoxLayout, "verticalLayout_2")
self.plot_layout.addWidget(self.canvas)
self.plot = PlotXY(
canvas=self.canvas,
data=None,
trad=self._trad,
toolbar=None,
)
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)
table = self.find(QTableView, f"tableView")
table.selectionModel().selectionChanged.connect(
self._set_current_reach
)
self._table.dataChanged.connect(self._set_current_reach)
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 _set_current_reach(self):
rows = self.index_selected_rows()
data = None
highlight = None
tab = "liquid"
if len(rows) > 0:
edge_id = self._study.river\
.lateral_contributions_adists.lst[rows[0]]\
.edge
if edge_id:
edge = next(filter(lambda e: e.id == edge_id,
self._study.river.edges()))
data = edge.reach
lc = self._lcs.lst[rows[0]]
highlight = (lc.begin_rk, lc.end_rk)
for delegate in self._delegate_rk:
delegate.data = edge
self.plot = PlotXY(
canvas=self.canvas,
data=data,
trad=self._trad,
toolbar=None,
)
self.plot.highlight = highlight
self.plot.update()
def add(self):
rows = self.index_selected_rows()
if len(self._lcs) == 0 or len(rows) == 0:
self._table.add(0)
else:
self._table.add(rows[0])
self._set_current_reach()
def delete(self):
rows = self.index_selected_rows()
if len(rows) == 0:
return
self._table.delete(rows)
self._set_current_reach()
def sort(self):
self._table.sort(False)
self._set_current_reach()
def _copy(self):
logger.info("TODO: copy")
self._set_current_reach()
def _paste(self):
logger.info("TODO: paste")
self._set_current_reach()
def _undo(self):
self._table.undo()
self._set_current_reach()
def _redo(self):
self._table.redo()
self._set_current_reach()
def edit(self):
rows = self.index_selected_rows()
for row in rows:
data = self._lcs.lst[row]
if self.sub_window_exists(
EditLateralContributionAdisTSWindow,
data=[self._study, None, data]
):
continue
win = EditLateralContributionAdisTSWindow(
data=data,
study=self._study,
parent=self
)
win.show()
@@ -0,0 +1,49 @@
# 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 LCTranslate(MainTranslate):
def __init__(self):
super(LCTranslate, self).__init__()
self._dict["Lateral contribution AdisTS"] = _translate(
"LateralContribution1adisTS", "Lateral contribution AdisTS"
)
self._sub_dict["long_types"] = {
"ND": self._dict["not_defined"],
"LC": _translate("LateralContribution", "Lateral contribution"),
"RA": _translate("LateralContribution", "Rain"),
"EV": _translate("LateralContribution", "Evaporation"),
}
self._dict["x"] = _translate("Geometry", "X (m)")
self._dict["y"] = _translate("Geometry", "Y (m)")
self._dict["z"] = _translate("Geometry", "Z (m)")
self._sub_dict["table_headers"] = {
"edge": self._dict["reach"],
"begin_rk": _translate("LateralContribution", "Begin rk (m)"),
"end_rk": _translate("LateralContribution", "End rk (m)")
}
+392 -13
View File
@@ -23,7 +23,13 @@ import logging
import subprocess import subprocess
from queue import Queue from queue import Queue
from functools import reduce from functools import reduce
from time import process_time_ns
from numpy.core.multiarray import where
from platformdirs import user_cache_dir from platformdirs import user_cache_dir
from Solver.AdisTS import AdisTS
from Solver.Mage import Mage8
from tools import logger_exception from tools import logger_exception
from PyQt5 import QtGui from PyQt5 import QtGui
@@ -39,6 +45,7 @@ from PyQt5.QtWidgets import (
QMainWindow, QApplication, QAction, QMainWindow, QApplication, QAction,
QFileDialog, QShortcut, QMenu, QToolBar, QFileDialog, QShortcut, QMenu, QToolBar,
QMessageBox, QProgressDialog, QTabWidget, QMessageBox, QProgressDialog, QTabWidget,
QDialog, QVBoxLayout, QLabel,
) )
from PyQt5.uic import loadUi from PyQt5.uic import loadUi
@@ -70,11 +77,29 @@ from View.SedimentLayers.Reach.Window import ReachSedimentLayersWindow
from View.AdditionalFiles.Window import AddFileListWindow from View.AdditionalFiles.Window import AddFileListWindow
from View.REPLines.Window import REPLineListWindow from View.REPLines.Window import REPLineListWindow
from View.SolverParameters.Window import SolverParametersWindow from View.SolverParameters.Window import SolverParametersWindow
from View.RunSolver.Window import SelectSolverWindow, SolverLogWindow from View.RunSolver.Window import (
SelectSolverWindow,
SolverLogWindow,
CompareSolversWindow
)
from View.Results.Window import ResultsWindow # , CompareResultsWindow
from View.RunSolver.WindowAdisTS import (
SelectSolverWindowAdisTS,
SolverLogWindowAdisTS
)
from View.CheckList.Window import CheckListWindow from View.CheckList.Window import CheckListWindow
from View.Results.Window import ResultsWindow from View.CheckList.WindowAdisTS import CheckListWindowAdisTS
from View.Results.WindowAdisTS import ResultsWindowAdisTS
from View.Results.ReadingResultsDialog import ReadingResultsDialog from View.Results.ReadingResultsDialog import ReadingResultsDialog
from View.Debug.Window import ReplWindow from View.Debug.Window import ReplWindow
from View.OutputRKAdisTS.Window import OutputRKAdisTSWindow
from View.Pollutants.Window import PollutantsWindow
from View.D90AdisTS.Window import D90AdisTSWindow
from View.DIFAdisTS.Window import DIFAdisTSWindow
from Solver.Solvers import GenericSolver
from Model.Results.Results import Results
# Optional internal display of documentation for make the application # Optional internal display of documentation for make the application
# package lighter... # package lighter...
@@ -97,6 +122,7 @@ model_action = [
"action_menu_close", "action_menu_edit", "action_menu_save", "action_menu_close", "action_menu_edit", "action_menu_save",
"action_menu_save_as", "action_toolBar_close", "action_toolBar_save", "action_menu_save_as", "action_toolBar_close", "action_toolBar_save",
"action_menu_numerical_parameter", "action_open_results_from_file", "action_menu_numerical_parameter", "action_open_results_from_file",
"action_open_results_adists",
] ]
other_model_action = [ other_model_action = [
@@ -117,8 +143,10 @@ define_model_action = [
"action_menu_edit_reach_sediment_layers", "action_menu_edit_reservoirs", "action_menu_edit_reach_sediment_layers", "action_menu_edit_reservoirs",
"action_menu_edit_hydraulic_structures", "action_menu_additional_file", "action_menu_edit_hydraulic_structures", "action_menu_additional_file",
"action_menu_results_last", "action_open_results_from_file", "action_menu_results_last", "action_open_results_from_file",
"action_menu_boundary_conditions_sediment", "action_compare_results", "action_menu_boundary_conditions_sediment",
"action_menu_rep_additional_lines", "action_menu_rep_additional_lines", "action_menu_output_rk",
"action_menu_run_adists", "action_menu_pollutants",
"action_menu_d90", "action_menu_dif",
] ]
action = ( action = (
@@ -228,6 +256,11 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
""" """
actions = { actions = {
# Menu action # Menu action
"action_menu_dif": self.open_dif,
"action_menu_d90": self.open_d90,
"action_menu_pollutants": self.open_pollutants,
"action_menu_run_adists": self.select_run_solver_adists,
"action_menu_output_rk": self.open_output_rk_adists,
"action_menu_config": self.open_configure, "action_menu_config": self.open_configure,
"action_menu_new": self.open_new_study, "action_menu_new": self.open_new_study,
"action_menu_edit": self.open_edit_study, "action_menu_edit": self.open_edit_study,
@@ -255,6 +288,8 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
"action_menu_close": self.close_model, "action_menu_close": self.close_model,
"action_menu_results_last": self.open_last_results, "action_menu_results_last": self.open_last_results,
"action_open_results_from_file": self.open_results_from_file, "action_open_results_from_file": self.open_results_from_file,
"action_compare_results": self.compare_results,
"action_open_results_adists": self.open_results_adists,
# Help # Help
"action_menu_pamhyr_users_wiki": self.open_doc_user, "action_menu_pamhyr_users_wiki": self.open_doc_user,
"action_menu_pamhyr_developers_pdf": "action_menu_pamhyr_developers_pdf":
@@ -760,14 +795,16 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
dlg.setWindowTitle(self._trad["mb_last_open_title"]) dlg.setWindowTitle(self._trad["mb_last_open_title"])
dlg.setText(self._trad["mb_last_open_msg"]) dlg.setText(self._trad["mb_last_open_msg"])
opt = QMessageBox.Cancel | QMessageBox.Ok # | QMessageBox.Open opt = QMessageBox.No | QMessageBox.Yes # | QMessageBox.Open
dlg.setStandardButtons(opt) dlg.setStandardButtons(opt)
dlg.setIcon(QMessageBox.Question) dlg.setIcon(QMessageBox.Question)
dlg.button(QMessageBox.Yes).setText(self._trad["Yes"])
dlg.button(QMessageBox.No).setText(self._trad["No"])
res = dlg.exec() res = dlg.exec()
if res == QMessageBox.Ok: if res == QMessageBox.Yes:
old = self.conf.last_study old = self.conf.last_study
file = self.dialog_reopen_study_select_backup( file = self.dialog_reopen_study_select_backup(
self.conf.last_study self.conf.last_study
@@ -780,7 +817,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
elif res == QMessageBox.Open: elif res == QMessageBox.Open:
self.open_model() self.open_model()
return True return True
elif res == QMessageBox.Cancel: elif res == QMessageBox.No:
return False return False
def dialog_reopen_study_select_backup(self, filename): def dialog_reopen_study_select_backup(self, filename):
@@ -807,6 +844,10 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
dlg.setStandardButtons(opt) dlg.setStandardButtons(opt)
dlg.setIcon(QMessageBox.Warning) dlg.setIcon(QMessageBox.Warning)
dlg.button(QMessageBox.Save).setText(self._trad["Save"])
dlg.button(QMessageBox.Close).setText(self._trad["Close"])
if cancel:
dlg.button(QMessageBox.Cancel).setText(self._trad["Cancel"])
res = dlg.exec() res = dlg.exec()
@@ -881,6 +922,70 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
# SUB WINDOWS # # SUB WINDOWS #
############### ###############
def open_d90(self):
if len(self._study.river.d90_adists.lst) != 0:
d90_default = self._study.river.d90_adists.lst[0]
else:
d90_default = self._study.river.d90_adists.new(0)
if self.sub_window_exists(
D90AdisTSWindow,
data=[self._study, None, d90_default]
):
return
D90AdisTS = D90AdisTSWindow(
study=self._study,
parent=self,
data=d90_default
)
D90AdisTS.show()
def open_dif(self):
if len(self._study.river.dif_adists.lst) != 0:
dif_default = self._study.river.dif_adists.lst[0]
else:
dif_default = self._study.river.dif_adists.new(0)
if self.sub_window_exists(
DIFAdisTSWindow,
data=[self._study, None, dif_default]
):
return
DIFAdisTS = DIFAdisTSWindow(
study=self._study,
parent=self,
data=dif_default
)
DIFAdisTS.show()
def open_pollutants(self):
if self.sub_window_exists(
PollutantsWindow,
data=[self._study, None]
):
return
Pollutants = PollutantsWindow(
study=self._study,
parent=self
)
Pollutants.show()
def open_output_rk_adists(self):
if self.sub_window_exists(
OutputRKAdisTSWindow,
data=[self._study, None]
):
return
Output_RK_AdisTS = OutputRKAdisTSWindow(
study=self._study,
parent=self
)
Output_RK_AdisTS.show()
def open_configure(self): def open_configure(self):
"""Open configure window """Open configure window
@@ -1206,6 +1311,49 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
if run.exec(): if run.exec():
self.run_solver(run.solver) self.run_solver(run.solver)
def select_run_solver_adists(self):
if self._study is None:
return
# solver = next(filter(lambda x: x._type == "adistswc",
# self.conf.solvers))
# solver = next(filter(lambda x: x.name == "AdisTS-WC",
# self.conf.solvers))
# print(solver._type)
# self.run_solver(solver)
run = SelectSolverWindowAdisTS(
study=self._study,
config=self.conf,
parent=self
)
if run.exec():
self.run_solver_adists(run.solver, run.mage_rep)
def run_solver_adists(self, solver, mage_rep):
if self._study is None:
return
if self.sub_window_exists(
CheckListWindowAdisTS,
data=[
self._study,
self.conf,
solver,
mage_rep
]
):
return
check = CheckListWindowAdisTS(
study=self._study,
config=self.conf,
solver=solver,
parent=self,
mage_rep=mage_rep,
)
check.show()
def run_solver(self, solver): def run_solver(self, solver):
if self._study is None: if self._study is None:
return return
@@ -1228,6 +1376,16 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
) )
check.show() check.show()
def solver_log_adists(self, solver, mage_rep):
sol = SolverLogWindowAdisTS(
study=self._study,
config=self.conf,
solver=solver,
parent=self,
mage_rep=mage_rep,
)
sol.show()
def solver_log(self, solver): def solver_log(self, solver):
sol = SolverLogWindow( sol = SolverLogWindow(
study=self._study, study=self._study,
@@ -1279,14 +1437,69 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
ResultsWindow, ResultsWindow,
data=[ data=[
self._study, self._study,
None, # No config None, # No config
solver, results]
results
]
): ):
return return
res = ResultsWindow( res = ResultsWindow(
study=self._study,
results=[results],
parent=self
)
res.show()
def open_solver_results_adists(self, solver, results=None):
def reading_fn():
self._tmp_results = results
# If no specific results, get last results
if results is None:
def reading_fn():
self._tmp_results = self._last_results
if self._last_results is None:
def reading_fn():
self._tmp_results = solver.results(
self._study,
self._solver_workdir(solver),
)
# Open from file
if type(results) is str:
logger.info(f"Open results from {os.path.dirname(results)}")
name = os.path.basename(results).replace(".BIN", "")
def reading_fn():
self._tmp_results = solver.results(
self._study,
os.path.dirname(results),
name=name
)
dlg = ReadingResultsDialog(reading_fn=reading_fn, parent=self)
dlg.exec_()
results = self._tmp_results
# No results available
if results is None:
return
# Windows already opened
if self.sub_window_exists(
ResultsWindowAdisTS,
data=[
self._study,
None, # No config
solver,
results,
self
]
):
return
res = ResultsWindowAdisTS(
study=self._study, study=self._study,
solver=solver, solver=solver,
results=results, results=results,
@@ -1308,7 +1521,12 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
if self._last_solver is None: if self._last_solver is None:
return return
self.open_solver_results(self._last_solver, self._last_results) if self._last_solver._type == "mage8":
self.open_solver_results(self._last_solver,
self._last_results)
elif self._last_solver._type == "adistswc":
self.open_solver_results_adists(self._last_solver,
self._last_results)
def open_results_from_file(self): def open_results_from_file(self):
if self._study is None: if self._study is None:
@@ -1340,10 +1558,171 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
file_name = dialog.selectedFiles() file_name = dialog.selectedFiles()
logger.info(f"Select results: {file_name}") logger.info(f"Select results: {file_name}")
self.open_solver_results( self.open_solver_results(
self._last_solver, Mage8("Mage"),
results=file_name[0] results=file_name[0]
) )
def compare_results(self):
if self._study is None:
return
run = CompareSolversWindow(
study=self._study,
config=self.conf,
parent=self
)
if run.exec():
results = self.diff_results(run.solver1,
run.solver2)
# At least one result not available
if results is None:
return
# Windows already opened
if self.sub_window_exists(
ResultsWindow,
data=[
self._study,
None] + # No config
results
):
#w = self.get_sub_window(
#ResultsWindow,
#data=[
#self._study,
#None, # No config
#results,
#parent=self
#])
#w.add_result(result[1], run.solver2)
return
res = ResultsWindow(
study=self._study,
results=results,
parent=self
)
res.show()
#res = CompareResultsWindow(
#study=self._study,
#solver=results._solver,
#results=results,
#parent=self
#)
#res.show()
def diff_results(self, solver1, solver2):
if solver1 is None:
# TODO message
return None
if solver2 is None:
# TODO message
return None
solver3 = GenericSolver(solver1.name+" - "+solver2.name)
result1 = solver1.results(
self._study,
self._solver_workdir(solver1),
)
if result1 is None:
# TODO message
return None
result2 = solver2.results(
self._study,
self._solver_workdir(solver2),
)
if result2 is None:
# TODO message
return None
if result2.get("nb_reach") != result1.get("nb_reach"):
# TODO message
return None
if result2.get("nb_profile") != result1.get("nb_profile"):
# TODO message
return None
return [result1, result2]
result3 = Results(self._study, solver3)
result3.set("nb_reach", result1.get("nb_reach"))
result3.set("nb_profile", result1.get("nb_profile"))
ts = list(result1.get("timestamps").intersection(result2.get("timestamps")))
result3.set("timestamps", ts)
for i in range(int(result1.get("nb_reach"))):
# Add reach to results reach list
r = result3.river.add(i)
for timestamp in result3.get("timestamps"):
for r in range(int(result1.get("nb_reach"))):
reach1 = result1.river.reach(r)
reach2 = result2.river.reach(r)
reach3 = result3.river.reach(r)
for p, (profile1, profile2) in enumerate(zip(reach1.profiles, reach2.profiles)):
for key in ["Z", "Q"]:
d1 = profile1.get_ts_key(timestamp, key)
d2 = profile2.get_ts_key(timestamp, key)
reach3.set(p, timestamp, key, d1-d2)
return result3
def open_results_adists(self):
if self._study is None:
return
if self._study.filename == "":
return
options = QFileDialog.Options()
options |= QFileDialog.DontUseNativeDialog
dialog = QFileDialog(self, options=options)
dialog.setFileMode(QFileDialog.DirectoryOnly)
# QFileDialog.FileMode.ExistingFile)
if self._last_solver is None:
dialog.setDirectory(
os.path.dirname(self._study.filename)
)
else:
dialog.setDirectory(
self._solver_workdir(self._last_solver)
)
if dialog.exec_():
dir_path = dialog.selectedFiles()[0]
dir_list = os.listdir(dir_path)
bin_list = list(filter(lambda s: "bin" in s, dir_list))
solver_name = dir_path.split("/")[-2]
solver_results = next(filter(lambda x: x.name == solver_name,
self.conf.solvers))
solver_results_adists = solver_results.results(
self._study,
repertory=dir_path[:-10], qlog=None) # self._output)
logger.info(f"Select results: {dir_path}")
if len(bin_list) >= 2 and ("total_sediment.bin" in bin_list):
self.open_solver_results_adists(
solver_results,
results=solver_results_adists,
)
else:
dlg = QDialog(self)
dlg.setWindowTitle("AdisTS Results")
layout = QVBoxLayout()
message = QLabel("AdisTS Results not found")
layout.addWidget(message)
dlg.setLayout(layout)
dlg.exec()
################# #################
# DOCUMENTATION # # DOCUMENTATION #
################# #################
+224
View File
@@ -0,0 +1,224 @@
# 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.OutputRKAdisTS.UndoCommand import (
SetTitleCommand, SetReachCommand, SetRKCommand,
SetEnabledCommand, AddCommand, DelCommand,
)
from functools import reduce
logger = logging.getLogger()
_translate = QCoreApplication.translate
class ComboBoxDelegate(QItemDelegate):
def __init__(self, data=None, trad=None, parent=None, mode="reaches"):
super(ComboBoxDelegate, self).__init__(parent)
self._data = data
self._trad = trad
self._mode = mode
def createEditor(self, parent, option, index):
self.editor = QComboBox(parent)
val = []
if self._mode == "rk":
reach_id = self._data.Output_rk_adists \
.get(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 TableModel(PamhyrTableModel):
def _setup_lst(self):
self._lst = self._data._Output_rk_adists
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] == "title":
return self._lst.get(row).title
elif self._headers[column] == "reach":
n = self._lst.get(row).reach
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] == "rk":
n = self._lst.get(row).rk
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()
na = self._trad['not_associated']
try:
if self._headers[column] == "title":
self._undo.push(
SetTitleCommand(
self._lst, row, value
)
)
elif self._headers[column] == "reach":
if value == na:
value = None
self._undo.push(
SetReachCommand(
self._lst, row, self._data.edge(value)
)
)
elif self._headers[column] == "rk":
if value == na:
value = None
self._undo.push(
SetRKCommand(
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._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()
def enabled(self, row, enabled, parent=QModelIndex()):
self._undo.push(
SetEnabledCommand(
self._lst, row, enabled
)
)
self.layoutChanged.emit()
def undo(self):
self._undo.undo()
self.layoutChanged.emit()
def redo(self):
self._undo.redo()
self.layoutChanged.emit()
+40
View File
@@ -0,0 +1,40 @@
# 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 OutputRKAdisTSTranslate(MainTranslate):
def __init__(self):
super(OutputRKAdisTSTranslate, self).__init__()
self._dict["Output RK"] = _translate(
"OutputRKAdisTS", "Output RK"
)
self._dict["x"] = _translate("OutputRKAdisTS", "X (m)")
self._sub_dict["table_headers"] = {
"title": self._dict["title"],
"reach": self._dict["reach"],
"rk": self._dict["unit_rk"],
}
+155
View File
@@ -0,0 +1,155 @@
# 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,
)
logger = logging.getLogger()
class SetTitleCommand(QUndoCommand):
def __init__(self, outputrk_lst, index, new_value):
QUndoCommand.__init__(self)
self._outputrk_lst = outputrk_lst
self._index = index
self._old = self._outputrk_lst.get(self._index).title
self._new = str(new_value)
def undo(self):
self._outputrk_lst.get(self._index).title = self._old
def redo(self):
self._outputrk_lst.get(self._index).title = self._new
class SetReachCommand(QUndoCommand):
def __init__(self, outputrk_lst, index, reach):
QUndoCommand.__init__(self)
self._outputrk_lst = outputrk_lst
self._index = index
self._old = self._outputrk_lst.get(self._index).reach
self._new = reach.id
def undo(self):
i = self._outputrk_lst.get(self._index)
i.reach = self._old
def redo(self):
i = self._outputrk_lst.get(self._index)
i.reach = self._new
class SetRKCommand(QUndoCommand):
def __init__(self, outputrk_lst, index, rk):
QUndoCommand.__init__(self)
self._outputrk_lst = outputrk_lst
self._index = index
self._old = self._outputrk_lst.get(self._index).rk
self._new = rk
def undo(self):
self._outputrk_lst.get(self._index).rk = self._old
def redo(self):
self._outputrk_lst.get(self._index).rk = self._new
class SetEnabledCommand(QUndoCommand):
def __init__(self, outputrk_lst, index, enabled):
QUndoCommand.__init__(self)
self._outputrk_lst = outputrk_lst
self._index = index
self._old = not enabled
self._new = enabled
def undo(self):
self._outputrk_lst.get(self._index).enabled = self._old
def redo(self):
self._outputrk_lst.get(self._index).enabled = self._new
class AddCommand(QUndoCommand):
def __init__(self, outputrk_lst, index):
QUndoCommand.__init__(self)
self._outputrk_lst = outputrk_lst
self._index = index
self._new = None
def undo(self):
self._outputrk_lst.delete_i([self._index])
def redo(self):
if self._new is None:
self._new = self._outputrk_lst.new(self._outputrk_lst, self._index)
else:
self._outputrk_lst.insert(self._index, self._new)
class DelCommand(QUndoCommand):
def __init__(self, outputrk_lst, rows):
QUndoCommand.__init__(self)
self._outputrk_lst = outputrk_lst
self._rows = rows
self._outputrk = []
for row in rows:
self._outputrk.append((row, self._outputrk_lst.get(row)))
self._outputrk.sort()
def undo(self):
for row, el in self._outputrk:
self._outputrk_lst.insert(row, el)
def redo(self):
self._outputrk_lst.delete_i(self._rows)
class PasteCommand(QUndoCommand):
def __init__(self, outputrk_lst, row, outputrk):
QUndoCommand.__init__(self)
self._outputrk_lst = outputrk_lst
self._row = row
self._outputrk = deepcopy(outputrk)
self._outputrk.reverse()
def undo(self):
self._outputrk_lst.delete_i(
self._tab,
range(self._row, self._row + len(self._outputrk))
)
def redo(self):
for r in self._outputrk:
self._outputrk_lst.insert(self._row, r)
+209
View File
@@ -0,0 +1,209 @@
# 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 PyQt5 import QtCore
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel, QCoreApplication,
pyqtSlot, pyqtSignal, QItemSelectionModel,
)
from PyQt5.QtWidgets import (
QDialogButtonBox, QPushButton, QLineEdit,
QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate,
QHeaderView, QDoubleSpinBox, QVBoxLayout, QCheckBox
)
from View.Tools.Plot.PamhyrCanvas import MplCanvas
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
from View.OutputRKAdisTS.Table import (
TableModel, ComboBoxDelegate,
)
from View.Network.GraphWidget import GraphWidget
from View.OutputRKAdisTS.Translate import OutputRKAdisTSTranslate
logger = logging.getLogger()
class OutputRKAdisTSWindow(PamhyrWindow):
_pamhyr_ui = "OutputRKAdisTS"
_pamhyr_name = "Output RK"
def __init__(self, study=None, config=None, parent=None):
trad = OutputRKAdisTSTranslate()
name = trad[self._pamhyr_name] + " - " + study.name
super(OutputRKAdisTSWindow, self).__init__(
title=name,
study=study,
config=config,
trad=trad,
parent=parent
)
self._outputrk_lst = self._study.river._Output_rk_adists
self.setup_table()
self.setup_checkbox()
self.setup_connections()
self.update()
def setup_table(self):
self._table = None
self._delegate_reach = ComboBoxDelegate(
trad=self._trad,
data=self._study.river,
parent=self,
mode="reaches"
)
self._delegate_rk = ComboBoxDelegate(
trad=self._trad,
data=self._study.river,
parent=self,
mode="rk"
)
table = self.find(QTableView, f"tableView")
self._table = TableModel(
table_view=table,
table_headers=self._trad.get_dict("table_headers"),
editable_headers=["title", "reach", "rk"],
delegates={
"reach": self._delegate_reach,
"rk": self._delegate_rk,
},
trad=self._trad,
data=self._study.river,
undo=self._undo_stack,
)
selectionModel = table.selectionModel()
index = table.model().index(0, 0)
selectionModel.select(
index,
QItemSelectionModel.Rows |
QItemSelectionModel.ClearAndSelect |
QItemSelectionModel.Select
)
table.scrollTo(index)
def setup_checkbox(self):
self._checkbox = self.find(QCheckBox, f"checkBox")
self._set_checkbox_state()
def setup_connections(self):
self.find(QAction, "action_add").triggered.connect(self.add)
self.find(QAction, "action_delete").triggered.connect(self.delete)
self._checkbox.clicked.connect(self._set_outputrk_state)
table = self.find(QTableView, "tableView")
table.selectionModel()\
.selectionChanged\
.connect(self.update)
self._table.dataChanged.connect(self.update)
self._table.layoutChanged.connect(self.update)
def index_selected(self):
table = self.find(QTableView, "tableView")
r = table.selectionModel().selectedRows()
if len(r) > 0:
return r[0]
else:
return None
def index_selected_row(self):
table = self.find(QTableView, "tableView")
r = table.selectionModel().selectedRows()
if len(r) > 0:
return r[0].row()
else:
return None
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._outputrk_lst) == 0 or len(rows) == 0:
self._table.add(0)
else:
self._table.add(rows[0])
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 _set_checkbox_state(self):
row = self.index_selected_row()
if row is None:
self._checkbox.setEnabled(False)
self._checkbox.setChecked(True)
else:
self._checkbox.setEnabled(True)
self._checkbox.setChecked(self._outputrk_lst.get(row).enabled)
def _set_outputrk_state(self):
rows = self.index_selected_rows()
if len(rows) != 0:
for row in rows:
if row is not None:
self._table.enabled(
row,
self._checkbox.isChecked()
)
def update(self):
self._set_checkbox_state()
+106
View File
@@ -0,0 +1,106 @@
# 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
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, QItemDelegate,
)
from View.Pollutants.Edit.UndoCommand import (
SetDataCommand, PasteCommand,
)
_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()
return self._data.data[row][column]
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(
SetDataCommand(
self._data, row, column, int(value)
)
)
else:
self._undo.push(
SetDataCommand(
self._data, row, column, float(value)
)
)
except Exception as e:
logger.info(e)
logger.debug(traceback.format_exc())
self.dataChanged.emit(index, index)
return True
def paste(self, row, 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(d),
data
)
)
)
)
self.layoutAboutToBeChanged.emit()
self.layoutChanged.emit()
+46
View File
@@ -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
from View.Pollutants.Translate import PollutantsTranslate
_translate = QCoreApplication.translate
class EditPollutantTranslate(PollutantsTranslate):
def __init__(self):
super(EditPollutantTranslate, self).__init__()
self._dict["Edit Pollutant"] = _translate(
"Pollutants", "Edit Pollutant"
)
self._sub_dict["table_headers"] = {
"type": self._dict["type"],
"diametre": self._dict["unit_area"],
"rho": self._dict["unit_rho"],
"porosity": self._dict["unit_porosity"],
"cdc_riv": self._dict["unit_cdc_riv"],
"cdc_cas": self._dict["unit_cdc_cas"],
"apd": self._dict["unit_apd"],
"ac": self._dict["unit_ac"],
"bc": self._dict["unit_bc"],
}
+66
View File
@@ -0,0 +1,66 @@
# 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.Pollutants.Pollutants import Pollutants
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.data[self._index][self._column]
self._new = new_value
def undo(self):
self._data.data[self._index][self._column] = self._old
def redo(self):
self._data.data[self._index][self._column] = self._new
class PasteCommand(QUndoCommand):
def __init__(self, data, row, hs):
QUndoCommand.__init__(self)
self._data = data
self._row = row
self._h = hs
self._h.reverse()
def undo(self):
self._data.delete_i(
range(self._row, self._row + len(self._h))
)
def redo(self):
for h in self._h:
self._data.insert(self._row, h)
+148
View File
@@ -0,0 +1,148 @@
# 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 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.Pollutants.Edit.Translate import EditPollutantTranslate
from View.Pollutants.Edit.Table import TableModel
_translate = QCoreApplication.translate
logger = logging.getLogger()
class EditPolluantWindow(PamhyrWindow):
_pamhyr_ui = "Pollutant"
_pamhyr_name = "Edit Pollutant"
def __init__(self, data=None, study=None, config=None, parent=None):
self._data = data
trad = EditPollutantTranslate()
name = trad[self._pamhyr_name]
if self._data is not None:
name += (
f" - {study.name} " +
f" - {self._data.name}"
)
super(EditPolluantWindow, self).__init__(
title=name,
study=study,
config=config,
trad=trad,
parent=parent
)
self._hash_data.append(data)
self.setup_table()
def setup_table(self):
headers = {}
table_headers = self._trad.get_dict("table_headers")
table = self.find(QTableView, "tableView")
self._table = TableModel(
table_view=table,
table_headers=table_headers,
editable_headers=table_headers,
delegates={},
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 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 _copy(self):
rows = self.index_selected_rows()
table = []
# table.append(self._data.header)
table.append(self._trad.get_dict("table_headers"))
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, data)
def _undo(self):
self._table.undo()
def _redo(self):
self._table.redo()
+127
View File
@@ -0,0 +1,127 @@
# 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.Pollutants.UndoCommand import (
SetNameCommand,
SetEnabledCommand, AddCommand, DelCommand,
)
logger = logging.getLogger()
_translate = QCoreApplication.translate
class TableModel(PamhyrTableModel):
def _setup_lst(self):
self._lst = self._data._Pollutants
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":
return self._lst.get(row).name
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] == "name":
self._undo.push(
SetNameCommand(
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._lst, row, self._data.ic_adists
)
)
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._data.initial_conditions_adists
)
)
self.endRemoveRows()
self.layoutChanged.emit()
def enabled(self, row, enabled, parent=QModelIndex()):
self._undo.push(
SetEnabledCommand(
self._lst, row, enabled
)
)
self.layoutChanged.emit()
def undo(self):
self._undo.undo()
self.layoutChanged.emit()
def redo(self):
self._undo.redo()
self.layoutChanged.emit()
+38
View File
@@ -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 PollutantsTranslate(MainTranslate):
def __init__(self):
super(PollutantsTranslate, self).__init__()
self._dict["Pollutants"] = _translate(
"Pollutants", "Pollutants"
)
self._dict["x"] = _translate("Pollutants", "X (m)")
self._sub_dict["table_headers"] = {
"name": self._dict["name"],
}
+138
View File
@@ -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,
)
logger = logging.getLogger()
class SetNameCommand(QUndoCommand):
def __init__(self, pollutants_lst, index, new_value):
QUndoCommand.__init__(self)
self._pollutants_lst = pollutants_lst
self._index = index
self._old = self._pollutants_lst.get(self._index).name
self._new = str(new_value)
def undo(self):
self._pollutants_lst.get(self._index).name = self._old
def redo(self):
self._pollutants_lst.get(self._index).name = self._new
class SetEnabledCommand(QUndoCommand):
def __init__(self, pollutants_lst, index, enabled):
QUndoCommand.__init__(self)
self._pollutants_lst = pollutants_lst
self._index = index
self._old = not enabled
self._new = enabled
def undo(self):
self._pollutants_lst.get(self._index).enabled = self._old
def redo(self):
self._pollutants_lst.get(self._index).enabled = self._new
class AddCommand(QUndoCommand):
def __init__(self, pollutants_lst, index, data):
QUndoCommand.__init__(self)
self._pollutants_lst = pollutants_lst
self._index = index
self._new = None
self._new_ic_adists = None
self._data = data
def undo(self):
self._pollutants_lst.delete_i([self._index])
self._data.delete_i([self._index])
def redo(self):
if self._new is None:
self._new = self._pollutants_lst.new(
self._pollutants_lst, self._index
)
self._new._data = [[0, 0., 0., 0., 0., 0., 0., 0., 0.]]
self._new_ic_adists = self._data.new(self._index, self._new.id)
else:
self._pollutants_lst.insert(self._index, self._new)
self._data.insert(self._index, self._new_ic_adists)
class DelCommand(QUndoCommand):
def __init__(self, pollutants_lst, rows, data):
QUndoCommand.__init__(self)
self._pollutants_lst = pollutants_lst
self._inc_pollutants_lst = data
self._rows = rows
self._pollutants = []
self._inc_pollutants = []
for row in rows:
self._pollutants.append((row, self._pollutants_lst.get(row)))
self._inc_pollutants.append((row, self._inc_pollutants_lst))
self._pollutants.sort()
self._inc_pollutants.sort()
def undo(self):
for row, el in self._pollutants:
self._pollutants_lst.insert(row, el)
for row, el in self._inc_pollutants:
self._inc_pollutants_lst.insert(row, el)
def redo(self):
self._pollutants_lst.delete_i(self._rows)
self._inc_pollutants_lst.delete_i(self._rows)
class PasteCommand(QUndoCommand):
def __init__(self, pollutants_lst, row, pollutant):
QUndoCommand.__init__(self)
self._pollutants_lst = pollutants_lst
self._row = row
self._pollutant = deepcopy(pollutant)
self._pollutant.reverse()
def undo(self):
self._pollutants_lst.delete_i(
self._tab,
range(self._row, self._row + len(self._pollutant))
)
def redo(self):
for r in self._pollutant:
self._pollutants_lst.insert(self._row, r)
+282
View File
@@ -0,0 +1,282 @@
# 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 PyQt5 import QtCore
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel, QCoreApplication,
pyqtSlot, pyqtSignal, QItemSelectionModel,
)
from PyQt5.QtWidgets import (
QDialogButtonBox, QPushButton, QLineEdit,
QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate,
QHeaderView, QDoubleSpinBox, QVBoxLayout, QCheckBox
)
from View.Pollutants.Table import (
TableModel
)
from View.Pollutants.Translate import PollutantsTranslate
from View.Pollutants.Edit.Window import EditPolluantWindow
from View.InitialConditionsAdisTS.Window import InitialConditionsAdisTSWindow
from View.BoundaryConditionsAdisTS.Window import BoundaryConditionAdisTSWindow
from View.LateralContributionsAdisTS.Window \
import LateralContributionAdisTSWindow
logger = logging.getLogger()
class PollutantsWindow(PamhyrWindow):
_pamhyr_ui = "Pollutants"
_pamhyr_name = "Pollutants"
def __init__(self, study=None, config=None, parent=None):
trad = PollutantsTranslate()
name = trad[self._pamhyr_name] + " - " + study.name
super(PollutantsWindow, self).__init__(
title=name,
study=study,
config=config,
trad=trad,
parent=parent
)
self._pollutants_lst = self._study._river._Pollutants
self.setup_table()
self.setup_checkbox()
self.setup_connections()
self.update()
def setup_table(self):
self._table = None
table = self.find(QTableView, f"tableView")
self._table = TableModel(
table_view=table,
table_headers=self._trad.get_dict("table_headers"),
editable_headers=["name"],
trad=self._trad,
data=self._study.river,
undo=self._undo_stack,
)
selectionModel = table.selectionModel()
index = table.model().index(0, 0)
selectionModel.select(
index,
QItemSelectionModel.Rows |
QItemSelectionModel.ClearAndSelect |
QItemSelectionModel.Select
)
table.scrollTo(index)
def setup_checkbox(self):
self._checkbox = self.find(QCheckBox, f"checkBox")
self._set_checkbox_state()
def setup_connections(self):
self.find(QAction, "action_add").triggered.connect(self.add)
self.find(QAction, "action_delete").triggered.connect(self.delete)
self.find(QAction, "action_edit").triggered.connect(self.edit)
self.find(QAction, "action_initial_conditions"
).triggered.connect(self.initial_conditions)
self.find(QAction, "action_boundary_conditions"
).triggered.connect(self.boundary_conditions)
self.find(QAction, "action_lateral_contributions"
).triggered.connect(self.lateral_contrib)
self._checkbox.clicked.connect(self._set_structure_state)
table = self.find(QTableView, "tableView")
table.selectionModel()\
.selectionChanged\
.connect(self.update)
self._table.dataChanged.connect(self.update)
self._table.layoutChanged.connect(self.update)
def index_selected(self):
table = self.find(QTableView, "tableView")
r = table.selectionModel().selectedRows()
if len(r) > 0:
return r[0]
else:
return None
def index_selected_row(self):
table = self.find(QTableView, "tableView")
r = table.selectionModel().selectedRows()
if len(r) > 0:
return r[0].row()
else:
return None
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._pollutants_lst) == 0 or len(rows) == 0:
self._table.add(0)
else:
self._table.add(rows[-1])
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._pollutants_lst.get(row)
if self.sub_window_exists(
EditPolluantWindow,
data=[self._study, None, data]
):
continue
win = EditPolluantWindow(
data=data,
study=self._study,
parent=self
)
win.show()
def initial_conditions(self):
rows = self.index_selected_rows()
for row in rows:
pollutant_id = self._pollutants_lst.get(row).id
ics_adists = next(filter(lambda x: x.pollutant == pollutant_id,
self._study.river.ic_adists.lst))
if self.sub_window_exists(
InitialConditionsAdisTSWindow,
data=[self._study, None, ics_adists]
):
return
initial = InitialConditionsAdisTSWindow(
study=self._study,
parent=self,
data=ics_adists
)
initial.show()
def boundary_conditions(self, tab=0):
rows = self.index_selected_rows()
for row in rows:
pollutant_id = self._pollutants_lst.get(row).id
if self.sub_window_exists(
BoundaryConditionAdisTSWindow,
data=[self._study, None]
):
bound = self.get_sub_window(
BoundaryConditionAdisTSWindow,
data=[self._study, None]
)
return
bound = BoundaryConditionAdisTSWindow(
study=self._study, parent=self
)
bound.show()
def lateral_contrib(self):
rows = self.index_selected_rows()
for row in rows:
pollutant_id = self._pollutants_lst.get(row).id
if self.sub_window_exists(
LateralContributionAdisTSWindow,
data=[self._study, pollutant_id, None]
):
return
lateral = LateralContributionAdisTSWindow(
study=self._study,
pollutant=pollutant_id,
parent=self
)
lateral.show()
def _set_checkbox_state(self):
row = self.index_selected_row()
if row is None:
self._checkbox.setEnabled(False)
self._checkbox.setChecked(True)
else:
self._checkbox.setEnabled(True)
self._checkbox.setChecked(self._pollutants_lst.get(row).enabled)
def _set_structure_state(self):
rows = self.index_selected_rows()
if len(rows) != 0:
for row in rows:
if row is not None:
self._table.enabled(
row,
self._checkbox.isChecked()
)
def update(self):
self._set_checkbox_state()
+129
View File
@@ -0,0 +1,129 @@
# CustomPlotValuesSelectionDialog.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 View.Tools.PamhyrWindow import PamhyrDialog
from PyQt5.QtWidgets import (
QRadioButton, QCheckBox, QVBoxLayout,
)
from View.Results.translate import ResultsTranslate
class CustomExportAdisDialog(PamhyrDialog):
_pamhyr_ui = "CustomExportAdisDialog"
_pamhyr_name = "Custom Plot Selection"
def __init__(self, pollutants, parent=None):
trad = ResultsTranslate()
super(CustomExportAdisDialog, self).__init__(
title=trad[self._pamhyr_name],
options=[],
trad=trad,
parent=parent
)
if pollutants is not None:
self.pollutants = pollutants
if "total_sediment" in self.pollutants:
self.pollutants.remove("total_sediment")
else:
self.pollutants = pollutants
self._available_values_x = self._trad.get_dict("values_x")
self._available_values_y = self._trad.get_dict("values_y_pol")
self.setup_radio_buttons_x()
self.setup_radio_buttons_pol()
self.setup_check_boxes()
self.value = None
def setup_radio_buttons_x(self):
self._radio = []
layout = self.find(QVBoxLayout, "verticalLayout_x")
for value in self._available_values_x:
btn = QRadioButton(
self._available_values_x[value],
parent=self
)
self._radio.append((value, btn))
layout.addWidget(btn)
self._radio[0][1].setChecked(True)
layout.addStretch()
def setup_radio_buttons_pol(self):
self._radio2 = []
layout = self.find(QVBoxLayout, "verticalLayout_pol")
for value in self.pollutants:
btn = QRadioButton(
value,
parent=self
)
self._radio2.append((value, btn))
layout.addWidget(btn)
self._radio2[0][1].setChecked(True)
layout.addStretch()
def setup_check_boxes(self):
self._check = []
layout = self.find(QVBoxLayout, "verticalLayout_y")
for value in self._available_values_y:
btn = QCheckBox(
self._available_values_y[value],
parent=self
)
self._check.append((value, btn))
layout.addWidget(btn)
self._check[0][1].setChecked(True)
layout.addStretch()
def accept(self):
x = next(
filter(
lambda r: r[1].isChecked(),
self._radio
)
)[0]
y = list(
map(
lambda b: b[0],
filter(
lambda b: b[1].isChecked(),
self._check
)
)
)
pol = next(
filter(
lambda r: r[1].isChecked(),
self._radio2
)
)[0]
self.value = x, y, pol
super().accept()
+15 -11
View File
@@ -49,7 +49,7 @@ unit = {
class CustomPlot(PamhyrPlot): class CustomPlot(PamhyrPlot):
def __init__(self, x, y, envelop, reach, profile, timestamp, def __init__(self, x, y, envelop, reach, profile, timestamp,
data=None, canvas=None, trad=None, data=None, canvas=None, trad=None, res_id=0,
toolbar=None, parent=None): toolbar=None, parent=None):
super(CustomPlot, self).__init__( super(CustomPlot, self).__init__(
canvas=canvas, canvas=canvas,
@@ -65,6 +65,7 @@ class CustomPlot(PamhyrPlot):
self._reach = reach self._reach = reach
self._profile = profile self._profile = profile
self._timestamp = timestamp self._timestamp = timestamp
self._current_res_id = res_id
logger.debug( logger.debug(
"Create custom plot for: " + "Create custom plot for: " +
@@ -130,7 +131,7 @@ class CustomPlot(PamhyrPlot):
""" """
Get SL list for profile p at initial time (initial data) Get SL list for profile p at initial time (initial data)
""" """
t0 = min(list(self.data.get("timestamps"))) t0 = min(list(self.data[self._current_res_id].get("timestamps")))
return map( return map(
lambda p: p.get_ts_key(t0, "sl")[0], lambda p: p.get_ts_key(t0, "sl")[0],
reach.profiles reach.profiles
@@ -146,7 +147,7 @@ class CustomPlot(PamhyrPlot):
) )
def get_ts_zmin(self, profile): def get_ts_zmin(self, profile):
results = self.data results = self.data[self._current_res_id]
nt = len(list(results.get("timestamps"))) nt = len(list(results.get("timestamps")))
reach = results.river.reach(self._reach) reach = results.river.reach(self._reach)
berdrock = self.sl_compute_bedrock(reach) berdrock = self.sl_compute_bedrock(reach)
@@ -161,13 +162,13 @@ class CustomPlot(PamhyrPlot):
sl, z sl, z
), ),
ts_z_bedrock, # Bedrock elevations ts_z_bedrock, # Bedrock elevations
asarray(sl)[:,0,:,0] # Sediment layers asarray(sl)[:, 0, :, 0] # Sediment layers
) )
) )
return ts_z_min return ts_z_min
def _draw_rk(self): def _draw_rk(self):
results = self.data results = self.data[self._current_res_id]
reach = results.river.reach(self._reach) reach = results.river.reach(self._reach)
rk = reach.geometry.get_rk() rk = reach.geometry.get_rk()
if reach.has_sediment(): if reach.has_sediment():
@@ -485,7 +486,7 @@ class CustomPlot(PamhyrPlot):
self.canvas.axes.legend(lns, labs, loc="best") self.canvas.axes.legend(lns, labs, loc="best")
def _redraw_rk(self): def _redraw_rk(self):
results = self.data results = self.data[self._current_res_id]
reach = results.river.reach(self._reach) reach = results.river.reach(self._reach)
rk = reach.geometry.get_rk() rk = reach.geometry.get_rk()
z_min = reach.geometry.get_z_min() z_min = reach.geometry.get_z_min()
@@ -623,7 +624,7 @@ class CustomPlot(PamhyrPlot):
self.canvas.axes.set_xticks(ticks=fx, labels=xt, rotation=45) self.canvas.axes.set_xticks(ticks=fx, labels=xt, rotation=45)
def _draw_time(self): def _draw_time(self):
results = self.data results = self.data[self._current_res_id]
reach = results.river.reach(self._reach) reach = results.river.reach(self._reach)
profile = reach.profile(self._profile) profile = reach.profile(self._profile)
@@ -774,7 +775,7 @@ class CustomPlot(PamhyrPlot):
def _redraw_time(self): def _redraw_time(self):
results = self.data results = self.data[self._current_res_id]
reach = results.river.reach(self._reach) reach = results.river.reach(self._reach)
profile = reach.profile(self._profile) profile = reach.profile(self._profile)
ts = list(results.get("timestamps")) ts = list(results.get("timestamps"))
@@ -895,7 +896,7 @@ class CustomPlot(PamhyrPlot):
elif self._x == "time": elif self._x == "time":
self._draw_time() self._draw_time()
if self._x == "rk": if self._x == "rk":
reach = self.data.river.reach(self._reach) reach = self.data[self._current_res_id].river.reach(self._reach)
profile = reach.profile(self._profile) profile = reach.profile(self._profile)
x = profile.rk x = profile.rk
elif self._x == "time": elif self._x == "time":
@@ -935,6 +936,10 @@ class CustomPlot(PamhyrPlot):
else: else:
self.draw_current() self.draw_current()
def set_result(self, res_id):
self._current_res_id = res_id
self.draw()
def set_timestamp(self, timestamp): def set_timestamp(self, timestamp):
self._timestamp = timestamp self._timestamp = timestamp
@@ -945,11 +950,10 @@ class CustomPlot(PamhyrPlot):
def draw_current(self): def draw_current(self):
if self._x == "rk": if self._x == "rk":
reach = self.data.river.reach(self._reach) reach = self.data[self._current_res_id].river.reach(self._reach)
profile = reach.profile(self._profile) profile = reach.profile(self._profile)
x = profile.rk x = profile.rk
elif self._x == "time": elif self._x == "time":
x = self._timestamp x = self._timestamp
self._current.set_data([x, x], self.canvas.axes.get_ylim()) self._current.set_data([x, x], self.canvas.axes.get_ylim())
self.canvas.draw_idle() self.canvas.draw_idle()
+15 -6
View File
@@ -29,7 +29,7 @@ _translate = QCoreApplication.translate
class PlotAC(PamhyrPlot): class PlotAC(PamhyrPlot):
def __init__(self, canvas=None, trad=None, toolbar=None, def __init__(self, canvas=None, trad=None, toolbar=None,
results=None, reach_id=0, profile_id=0, results=None, reach_id=0, profile_id=0, res_id=0,
parent=None): parent=None):
super(PlotAC, self).__init__( super(PlotAC, self).__init__(
canvas=canvas, canvas=canvas,
@@ -39,9 +39,10 @@ class PlotAC(PamhyrPlot):
parent=parent parent=parent
) )
self._current_timestamp = max(results.get("timestamps")) self._current_timestamp = max(results[res_id].get("timestamps"))
self._current_reach_id = reach_id self._current_reach_id = reach_id
self._current_profile_id = profile_id self._current_profile_id = profile_id
self._current_res_id = res_id
self.label_x = self._trad['x'] self.label_x = self._trad['x']
self.label_y = self._trad["unit_elevation"] self.label_y = self._trad["unit_elevation"]
@@ -62,7 +63,7 @@ class PlotAC(PamhyrPlot):
@results.setter @results.setter
def results(self, results): def results(self, results):
self.data = results self.data = results
self._current_timestamp = max(results.get("timestamps")) self._current_timestamp = max(results[self._current_res_id].get("timestamps"))
@timer @timer
def draw(self, highlight=None): def draw(self, highlight=None):
@@ -71,7 +72,11 @@ class PlotAC(PamhyrPlot):
if self.results is None: if self.results is None:
return return
reach = self.results.river.reach(self._current_reach_id) if self.results[self._current_res_id] is None:
return
results = self.results[self._current_res_id]
reach = results.river.reach(self._current_reach_id)
profile = reach.profile(self._current_profile_id) profile = reach.profile(self._current_profile_id)
self.draw_profile(reach, profile) self.draw_profile(reach, profile)
@@ -137,10 +142,14 @@ class PlotAC(PamhyrPlot):
self._current_profile_id = profile_id self._current_profile_id = profile_id
self.update() self.update()
def set_result(self, res_id):
self._current_result_id = res_id
self.update()
def set_timestamp(self, timestamp): def set_timestamp(self, timestamp):
self._current_timestamp = timestamp self._current_timestamp = timestamp
reach = self.results.river.reach(self._current_reach_id) reach = self.results[self._current_res_id].river.reach(self._current_reach_id)
profile = reach.profile(self._current_profile_id) profile = reach.profile(self._current_profile_id)
x = profile.geometry.get_station() x = profile.geometry.get_station()
z = profile.geometry.z() z = profile.geometry.z()
@@ -152,7 +161,7 @@ class PlotAC(PamhyrPlot):
if not self._init: if not self._init:
self.draw() self.draw()
reach = self.results.river.reach(self._current_reach_id) reach = self.results[self._current_res_id].river.reach(self._current_reach_id)
profile = reach.profile(self._current_profile_id) profile = reach.profile(self._current_profile_id)
x = profile.geometry.get_station() x = profile.geometry.get_station()
z = profile.geometry.z() z = profile.geometry.z()
+17 -7
View File
@@ -35,7 +35,7 @@ logger = logging.getLogger()
class PlotH(PamhyrPlot): class PlotH(PamhyrPlot):
def __init__(self, canvas=None, trad=None, toolbar=None, def __init__(self, canvas=None, trad=None, toolbar=None,
results=None, reach_id=0, profile_id=0, results=None, reach_id=0, profile_id=0, res_id=0,
parent=None): parent=None):
super(PlotH, self).__init__( super(PlotH, self).__init__(
canvas=canvas, canvas=canvas,
@@ -47,9 +47,10 @@ class PlotH(PamhyrPlot):
self._mode = "time" self._mode = "time"
self._current_timestamp = max(results.get("timestamps")) self._current_timestamp = max(results[res_id].get("timestamps"))
self._current_reach_id = reach_id self._current_reach_id = reach_id
self._current_profile_id = profile_id self._current_profile_id = profile_id
self._current_res_id = res_id
self.label_x = self._trad["unit_time_s"] self.label_x = self._trad["unit_time_s"]
self.label_y = self._trad["unit_discharge"] self.label_y = self._trad["unit_discharge"]
@@ -70,7 +71,7 @@ class PlotH(PamhyrPlot):
@results.setter @results.setter
def results(self, results): def results(self, results):
self.data = results self.data = results
self._current_timestamp = max(results.get("timestamps")) self._current_timestamp = max(results[self._current_res_id].get("timestamps"))
@timer @timer
def draw(self, highlight=None): def draw(self, highlight=None):
@@ -79,7 +80,11 @@ class PlotH(PamhyrPlot):
if self.results is None: if self.results is None:
return return
reach = self.results.river.reach(self._current_reach_id) if self.results[self._current_res_id] is None:
return
results = self.results[self._current_res_id]
reach = results.river.reach(self._current_reach_id)
profile = reach.profile(self._current_profile_id) profile = reach.profile(self._current_profile_id)
if reach.geometry.number_profiles == 0: if reach.geometry.number_profiles == 0:
@@ -99,7 +104,7 @@ class PlotH(PamhyrPlot):
self._init = True self._init = True
def draw_data(self, reach, profile): def draw_data(self, reach, profile):
self.ts = list(self.results.get("timestamps")) self.ts = list(self.results[self._current_res_id].get("timestamps"))
self.ts.sort() self.ts.sort()
x = self.ts x = self.ts
@@ -123,7 +128,7 @@ class PlotH(PamhyrPlot):
) )
def draw_max(self, reach): def draw_max(self, reach):
self.ts = list(self.results.get("timestamps")) self.ts = list(self.results[self._current_res_id].get("timestamps"))
self.ts.sort() self.ts.sort()
x = self.ts x = self.ts
@@ -152,6 +157,10 @@ class PlotH(PamhyrPlot):
self._current_profile_id = profile_id self._current_profile_id = profile_id
self.update() self.update()
def set_result(self, res_id):
self._current_result_id = res_id
self.update()
def set_timestamp(self, timestamp): def set_timestamp(self, timestamp):
self._current_timestamp = timestamp self._current_timestamp = timestamp
self.update_current() self.update_current()
@@ -165,7 +174,8 @@ class PlotH(PamhyrPlot):
self.update_idle() self.update_idle()
def update_data(self): def update_data(self):
reach = self.results.river.reach(self._current_reach_id) results = self.results[self._current_res_id]
reach = results.river.reach(self._current_reach_id)
profile = reach.profile(self._current_profile_id) profile = reach.profile(self._current_profile_id)
x = self.ts x = self.ts
+23 -9
View File
@@ -32,7 +32,7 @@ logger = logging.getLogger()
class PlotRKC(PamhyrPlot): class PlotRKC(PamhyrPlot):
def __init__(self, canvas=None, trad=None, toolbar=None, def __init__(self, canvas=None, trad=None, toolbar=None,
results=None, reach_id=0, profile_id=0, results=None, reach_id=0, profile_id=0, res_id=0,
parent=None): parent=None):
super(PlotRKC, self).__init__( super(PlotRKC, self).__init__(
canvas=canvas, canvas=canvas,
@@ -42,10 +42,12 @@ class PlotRKC(PamhyrPlot):
parent=parent parent=parent
) )
self._timestamps = results.get("timestamps") self._timestamps = results[res_id].get("timestamps")
self._current_timestamp = max(results[res_id].get("timestamps"))
self._current_timestamp = max(self._timestamps) self._current_timestamp = max(self._timestamps)
self._current_reach_id = reach_id self._current_reach_id = reach_id
self._current_profile_id = profile_id self._current_profile_id = profile_id
self._current_res_id = res_id
self.label_x = self._trad["unit_rk"] self.label_x = self._trad["unit_rk"]
self.label_y = self._trad["unit_elevation"] self.label_y = self._trad["unit_elevation"]
@@ -63,7 +65,7 @@ class PlotRKC(PamhyrPlot):
@results.setter @results.setter
def results(self, results): def results(self, results):
self.data = results self.data = results
self._current_timestamp = max(results.get("timestamps")) self._current_timestamp = max(results[self._current_res_id].get("timestamps"))
@timer @timer
def draw(self, highlight=None): def draw(self, highlight=None):
@@ -72,7 +74,11 @@ class PlotRKC(PamhyrPlot):
if self.results is None: if self.results is None:
return return
reach = self.results.river.reach(self._current_reach_id) if self.results[self._current_res_id] is None:
return
results = self.results[self._current_res_id]
reach = results.river.reach(self._current_reach_id)
self.draw_bottom(reach) self.draw_bottom(reach)
self.draw_water_elevation(reach) self.draw_water_elevation(reach)
@@ -111,7 +117,7 @@ class PlotRKC(PamhyrPlot):
lambda hs: hs._input_reach.reach is reach.geometry, lambda hs: hs._input_reach.reach is reach.geometry,
filter( filter(
lambda hs: hs._input_reach is not None, lambda hs: hs._input_reach is not None,
self.results.study.river.hydraulic_structures.lst self.results[self._current_res_id].study.river.hydraulic_structures.lst
) )
) )
@@ -312,6 +318,10 @@ class PlotRKC(PamhyrPlot):
self._current_profile_id = profile_id self._current_profile_id = profile_id
self.update_current() self.update_current()
def set_result(self, res_id):
self._current_result_id = res_id
self.update()
def set_timestamp(self, timestamp): def set_timestamp(self, timestamp):
self._current_timestamp = timestamp self._current_timestamp = timestamp
self.update() self.update()
@@ -320,7 +330,8 @@ class PlotRKC(PamhyrPlot):
if not self._init: if not self._init:
self.draw() self.draw()
reach = self.results.river.reach(self._current_reach_id) results = self.results[self._current_res_id]
reach = results.river.reach(self._current_reach_id)
if reach.has_sediment(): if reach.has_sediment():
self.update_bottom_with_bedload() self.update_bottom_with_bedload()
@@ -329,7 +340,8 @@ class PlotRKC(PamhyrPlot):
self.update_idle() self.update_idle()
def update_water_elevation(self): def update_water_elevation(self):
reach = self.results.river.reach(self._current_reach_id) results = self.results[self._current_res_id]
reach = results.river.reach(self._current_reach_id)
rk = reach.geometry.get_rk() rk = reach.geometry.get_rk()
z_min = reach.geometry.get_z_min() z_min = reach.geometry.get_z_min()
@@ -354,7 +366,8 @@ class PlotRKC(PamhyrPlot):
) )
def update_current(self): def update_current(self):
reach = self.results.river.reach(self._current_reach_id) results = self.results[self._current_res_id]
reach = results.river.reach(self._current_reach_id)
rk = reach.geometry.get_rk() rk = reach.geometry.get_rk()
z_min = reach.geometry.get_z_min() z_min = reach.geometry.get_z_min()
z_max = reach.geometry.get_z_max() z_max = reach.geometry.get_z_max()
@@ -367,7 +380,8 @@ class PlotRKC(PamhyrPlot):
self.canvas.figure.canvas.draw_idle() self.canvas.figure.canvas.draw_idle()
def update_bottom_with_bedload(self): def update_bottom_with_bedload(self):
reach = self.results.river.reach(self._current_reach_id) results = self.results[self._current_res_id]
reach = results.river.reach(self._current_reach_id)
rk = reach.geometry.get_rk() rk = reach.geometry.get_rk()
z = self.sl_compute_current_z(reach) z = self.sl_compute_current_z(reach)
+422
View File
@@ -0,0 +1,422 @@
# PlotSedAdisDx.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 numpy as np
from functools import reduce
from datetime import datetime
from math import inf
from tools import timer, trace
from View.Tools.PamhyrPlot import PamhyrPlot
from PyQt5.QtCore import (
QCoreApplication
)
_translate = QCoreApplication.translate
logger = logging.getLogger()
class PlotAdis(PamhyrPlot):
def __init__(self, canvas=None, trad=None, toolbar=None,
results=None, reach_id=0, profile_id=0,
pol_id=[0], key="C", type_pol=[7], parent=None):
super(PlotAdis, self).__init__(
canvas=canvas,
trad=trad,
data=results,
toolbar=toolbar,
parent=parent
)
self._current_timestamp = max(results.get("timestamps"))
self._current_reach_id = reach_id
self._current_profile_id = profile_id
self._current_pol_id = pol_id
self._key = key
self._type_pol = type_pol
self.label_x = self._trad["unit_rk"]
self._available_values_y = self._trad.get_dict("values_y_pol")
self.label = {}
self.label_max = {}
self.label_min = {}
self.label["C"] = _translate("Results", "Concentration")
self.label_max["C"] = _translate("Results", "Max Concentration")
self.label_min["C"] = _translate("Results", "Min Concentration")
if self._type_pol[pol_id[0]] == -1: # Total
self.label["M"] = _translate("Results", "Thickness")
self.label_max["M"] = _translate("Results", "Max Thickness")
self.label_min["M"] = _translate("Results", "Min Thickness")
self.label_y = self._available_values_y["unit_thickness"]
else:
self.label["M"] = _translate("Results", "Mass")
self.label_max["M"] = _translate("Results", "Max Mass")
self.label_min["M"] = _translate("Results", "Min Mass")
self.label_y = self._available_values_y["unit_"+self._key]
self.val_id = {}
self.val_id["C"] = 0
self.val_id["G"] = 1
self.val_id["M"] = 2
self.val_id["D"] = 3
self._lines = []
self._isometric_axis = False
@property
def results(self):
return self.data
@results.setter
def results(self, results):
self.data = results
self._current_timestamp = max(results.get("timestamps"))
@timer
def draw(self, highlight=None):
self.init_axes()
if self.results is None:
return
reach = self.results.river.reach(self._current_reach_id)
profile = reach.profile(self._current_profile_id)
if reach.geometry.number_profiles == 0:
self._init = False
return
self.draw_min_and_max()
self.draw_data()
self.draw_current()
self.idle()
self._init = True
def draw_min_and_max(self):
x, y, z = self.get_min_and_max()
self._line_max, = self.canvas.axes.plot(
x, y,
label=self.label_max[self._key],
color=self.color_plot_highlight,
linestyle='dotted',
lw=1.,
markersize=0
)
self._line_min, = self.canvas.axes.plot(
x, z,
label=self.label_min[self._key],
color='g',
linestyle='dotted',
lw=1.,
markersize=0
)
def update_min_and_max(self):
x, y, z = self.get_min_and_max()
self._line_max.set_data(x, y)
self._line_min.set_data(x, z)
def set_reach(self, reach_id):
self._current_reach_id = reach_id
self._current_profile_id = 0
self.draw()
self.update_current()
def update(self):
if not self._init:
self.draw()
self.update_min_and_max()
self.update_data()
self.update_idle()
def hide_current(self):
self._current.set_visible(False)
def show_current(self):
self._current.set_visible(True)
def set_pollutant(self, pol_id):
for j in self._lines:
j.remove()
self._lines = []
self._current_pol_id = pol_id
self.update_min_and_max()
self.draw_data()
self.update_current()
self.update_idle()
class PlotAdis_dx(PlotAdis):
def __init__(self, canvas=None, trad=None, toolbar=None,
results=None, reach_id=0, profile_id=0,
pol_id=[0], key="C", type_pol=[7], parent=None):
super(PlotAdis_dx, self).__init__(
canvas=canvas,
trad=trad,
toolbar=toolbar,
results=results,
reach_id=reach_id,
profile_id=profile_id,
pol_id=pol_id,
key=key,
type_pol=type_pol,
parent=parent
)
def draw_data(self):
reach = self.results.river.reach(self._current_reach_id)
profile = reach.profile(self._current_profile_id)
x = reach.geometry.get_rk()
self._lines = []
for i in range(len(self._current_pol_id)):
pol_id = self._current_pol_id[i]
name = self.data.pollutants_list[pol_id]
if self.val_id[self._key] > 0 and self._type_pol[pol_id] == 1:
y = [0.0]*len(x)
else:
y = list(map(lambda p:
p.get_ts_key(
self._current_timestamp, "pols"
)[pol_id][self.val_id[self._key]],
reach.profiles))
self._lines.append(self.canvas.axes.plot(
x, y,
label=self.label[self._key]+" "+name,
color=self.colors[pol_id % len(self.colors)],
**self.plot_default_kargs
)[0])
self.enable_legend()
def update_data(self):
reach = self.results.river.reach(self._current_reach_id)
profile = reach.profile(self._current_profile_id)
x = reach.geometry.get_rk()
for i in range(len(self._current_pol_id)):
pol_id = self._current_pol_id[i]
if self.val_id[self._key] > 0 and self._type_pol[pol_id] == 1:
continue # no mass
else:
y = list(map(
lambda p: p.get_ts_key(
self._current_timestamp, "pols"
)[pol_id][self.val_id[self._key]],
reach.profiles))
self._lines[i].set_data(x, y)
self.update_current()
def draw_current(self):
reach = self.results.river.reach(self._current_reach_id)
rk = reach.geometry.get_rk()
self._current, = self.canvas.axes.plot(
[rk[self._current_profile_id], rk[self._current_profile_id]],
self.canvas.axes.get_ylim(),
color="grey",
linestyle="dashed",
lw=1.,
)
def update_current(self):
self.hide_current()
self.canvas.axes.relim(visible_only=True)
self.canvas.axes.autoscale_view()
self.show_current()
reach = self.results.river.reach(self._current_reach_id)
rk = reach.geometry.get_rk()
self._current.set_data(
[rk[self._current_profile_id], rk[self._current_profile_id]],
self.canvas.axes.get_ylim()
)
def get_min_and_max(self):
reach = self.results.river.reach(self._current_reach_id)
profile = reach.profile(self._current_profile_id)
x = reach.geometry.get_rk()
y = [-inf]*reach.geometry.number_profiles
z = [inf]*reach.geometry.number_profiles
for pol_id in self._current_pol_id:
if self.val_id[self._key] > 0 and self._type_pol[pol_id] == 1:
for i in range(reach.geometry.number_profiles):
y[i] = max(0.0, y[i])
z[i] = min(0.0, z[i])
else:
val_id = self.val_id[self._key]
for i, p in enumerate(reach.profiles):
rk_y = list(map(lambda data_el:
data_el[pol_id][val_id],
p.get_key("pols")
))
y[i] = max(np.max(rk_y), y[i])
z[i] = min(np.min(rk_y), z[i])
return x, y, z
def set_profile(self, profile_id):
self._current_profile_id = profile_id
self.update_current()
self.update_idle()
def set_timestamp(self, timestamp):
self._current_timestamp = timestamp
self.update()
class PlotAdis_dt(PlotAdis):
def __init__(self, canvas=None, trad=None, toolbar=None,
results=None, reach_id=0, profile_id=0,
pol_id=[0], key="C", type_pol=[7], parent=None):
super(PlotAdis_dt, self).__init__(
canvas=canvas,
trad=trad,
toolbar=toolbar,
results=results,
reach_id=reach_id,
profile_id=profile_id,
pol_id=pol_id,
key=key,
type_pol=type_pol,
parent=parent
)
self.label_x = self._trad["unit_time_s"]
def draw_data(self):
reach = self.results.river.reach(self._current_reach_id)
profile = reach.profile(self._current_profile_id)
self.ts = list(self.results.get("timestamps"))
self.ts.sort()
self._lines = []
x = self.ts
for i in range(len(self._current_pol_id)):
pol_id = self._current_pol_id[i]
name = self.data.pollutants_list[pol_id]
if self.val_id[self._key] > 0 and self._type_pol[pol_id] == 1:
y = [0.0]*len(x)
else:
val_id = self.val_id[self._key]
y = list(map(lambda data_el:
data_el[pol_id][val_id],
profile.get_key("pols")
))
self._lines.append(self.canvas.axes.plot(
x, y,
label=self.label[self._key]+" "+name,
color=self.colors[pol_id % len(self.colors)],
**self.plot_default_kargs
)[0])
self.set_ticks_time_formater()
self.enable_legend()
def update_data(self):
reach = self.results.river.reach(self._current_reach_id)
profile = reach.profile(self._current_profile_id)
x = self.ts
for i in range(len(self._current_pol_id)):
pol_id = self._current_pol_id[i]
if self.val_id[self._key] > 0 and self._type_pol[pol_id] == 1:
continue # no mass
else:
y = list(map(lambda data_el:
data_el[pol_id][self.val_id[self._key]],
profile.get_key("pols")
))
self._lines[i].set_data(x, y)
self._current.set_data(
[self._current_timestamp, self._current_timestamp],
self.canvas.axes.get_ylim()
)
self.update_current()
def draw_current(self):
self._current, = self.canvas.axes.plot(
[self._current_timestamp, self._current_timestamp],
self.canvas.axes.get_ylim(),
color="grey",
linestyle="dashed",
lw=1.,
)
def update_current(self):
self.hide_current()
self.canvas.axes.relim(visible_only=True)
self.canvas.axes.autoscale_view()
self.show_current()
self._current.set_data(
[self._current_timestamp, self._current_timestamp],
self.canvas.axes.get_ylim()
)
def get_min_and_max(self):
reach = self.results.river.reach(self._current_reach_id)
self.ts = list(self.results.get("timestamps"))
self.ts.sort()
x = self.ts
y = [-inf]*len(x)
z = [inf]*len(x)
for pol_id in self._current_pol_id:
if self.val_id[self._key] > 0 and self._type_pol[pol_id] == 1:
for i in range(len(x)):
y[i] = max(0.0, y[i])
z[i] = min(0.0, z[i])
else:
val_id = self.val_id[self._key]
for i, ts in enumerate(x):
ts_y = list(map(lambda p:
p.get_ts_key(
ts, "pols"
)[pol_id][val_id],
reach.profiles
))
y[i] = max(np.max(ts_y), y[i])
z[i] = min(np.min(ts_y), z[i])
return x, y, z
def set_profile(self, profile_id):
self._current_profile_id = profile_id
self.update()
def set_timestamp(self, timestamp):
self._current_timestamp = timestamp
self.update_current()
self.update_idle()
+23 -9
View File
@@ -38,7 +38,7 @@ logger = logging.getLogger()
class PlotXY(PamhyrPlot): class PlotXY(PamhyrPlot):
def __init__(self, canvas=None, trad=None, toolbar=None, def __init__(self, canvas=None, trad=None, toolbar=None,
results=None, reach_id=0, profile_id=0, results=None, reach_id=0, profile_id=0, res_id=0,
display_current=True, parent=None): display_current=True, parent=None):
super(PlotXY, self).__init__( super(PlotXY, self).__init__(
canvas=canvas, canvas=canvas,
@@ -52,10 +52,11 @@ class PlotXY(PamhyrPlot):
self.line_gl = [] self.line_gl = []
self.overflow = [] self.overflow = []
self._timestamps = results.get("timestamps") self._timestamps = results[res_id].get("timestamps")
self._current_timestamp = max(self._timestamps) self._current_timestamp = max(self._timestamps)
self._current_reach_id = reach_id self._current_reach_id = reach_id
self._current_profile_id = profile_id self._current_profile_id = profile_id
self._current_res_id = res_id
self.label_x = _translate("Results", "X (m)") self.label_x = _translate("Results", "X (m)")
self.label_y = _translate("Results", "Y (m)") self.label_y = _translate("Results", "Y (m)")
@@ -136,7 +137,7 @@ class PlotXY(PamhyrPlot):
@results.setter @results.setter
def results(self, results): def results(self, results):
self.data = results self.data = results
self._current_timestamp = max(results.get("timestamps")) self._current_timestamp = max(results[self._current_res_id].get("timestamps"))
@timer @timer
def draw(self, highlight=None): def draw(self, highlight=None):
@@ -145,8 +146,12 @@ class PlotXY(PamhyrPlot):
if self.results is None: if self.results is None:
return return
reach = self.results.river.reach(self._current_reach_id) if self.results[self._current_res_id] is None:
reaches = self.results.river.reachs return
results = self.results[self._current_res_id]
reach = results.river.reach(self._current_reach_id)
reaches = results.river.reachs
self.draw_profiles(reach, reaches) self.draw_profiles(reach, reaches)
self.draw_water_elevation(reach) self.draw_water_elevation(reach)
@@ -272,7 +277,8 @@ class PlotXY(PamhyrPlot):
) )
def draw_water_elevation(self, reach): def draw_water_elevation(self, reach):
reach = self.results.river.reach(self._current_reach_id) results = self.results[self._current_res_id]
reach = results.river.reach(self._current_reach_id)
poly_l_x, poly_l_y, poly_r_x, poly_r_y = [], [], [], [] poly_l_x, poly_l_y, poly_r_x, poly_r_y = [], [], [], []
for profile in reach.profiles: for profile in reach.profiles:
@@ -308,6 +314,11 @@ class PlotXY(PamhyrPlot):
self.update_profile() self.update_profile()
self.update_idle() self.update_idle()
def set_result(self, res_id):
self._current_result_id = res_id
self.update_profile()
self.update_idle()
def set_timestamp(self, timestamp): def set_timestamp(self, timestamp):
self._current_timestamp = timestamp self._current_timestamp = timestamp
self.update() self.update()
@@ -322,7 +333,8 @@ class PlotXY(PamhyrPlot):
self.update_idle() self.update_idle()
def update_profile(self): def update_profile(self):
reach = self.results.river.reach(self._current_reach_id) results = self.results[self._current_res_id]
reach = results.river.reach(self._current_reach_id)
profile = reach.profile(self._current_profile_id) profile = reach.profile(self._current_profile_id)
self.plot_selected.set_data( self.plot_selected.set_data(
@@ -331,7 +343,8 @@ class PlotXY(PamhyrPlot):
) )
def update_water_elevation(self): def update_water_elevation(self):
reach = self.results.river.reach(self._current_reach_id) results = self.results[self._current_res_id]
reach = results.river.reach(self._current_reach_id)
poly_l_x, poly_l_y, poly_r_x, poly_r_y = [], [], [], [] poly_l_x, poly_l_y, poly_r_x, poly_r_y = [], [], [], []
for profile in reach.profiles: for profile in reach.profiles:
@@ -358,7 +371,8 @@ class PlotXY(PamhyrPlot):
self.water_fill[0].set_xy(poly) self.water_fill[0].set_xy(poly)
def update_water_elevation_overflow(self): def update_water_elevation_overflow(self):
reach = self.results.river.reach(self._current_reach_id) results = self.results[self._current_res_id]
reach = results.river.reach(self._current_reach_id)
profile = reach.profile(self._current_profile_id) profile = reach.profile(self._current_profile_id)
overflow = [] overflow = []
+14 -3
View File
@@ -46,13 +46,15 @@ _translate = QCoreApplication.translate
class TableModel(PamhyrTableModel): class TableModel(PamhyrTableModel):
def _setup_lst(self): def _setup_lst(self):
_river = self._data.river _river = self._data[0].river
if self._opt_data == "reach": if self._opt_data == "reach":
self._lst = _river.reachs self._lst = _river.reachs
elif self._opt_data == "profile": elif self._opt_data == "profile":
self._lst = _river.reach(0).profiles self._lst = _river.reach(0).profiles
elif self._opt_data == "raw_data": elif self._opt_data == "raw_data":
self._lst = _river.reach(0).profiles self._lst = _river.reach(0).profiles
elif self._opt_data == "solver":
self._lst = self._parent._solvers
def __init__(self, **kwargs): def __init__(self, **kwargs):
self._timestamp = 0.0 self._timestamp = 0.0
@@ -76,6 +78,10 @@ class TableModel(PamhyrTableModel):
elif self._headers[column] == "rk": elif self._headers[column] == "rk":
v = self._lst[row].rk v = self._lst[row].rk
return f"{v:.4f}" return f"{v:.4f}"
elif self._opt_data == "solver":
if self._headers[column] == "solver":
v = self._lst[row].name
return str(v)
elif self._opt_data == "raw_data": elif self._opt_data == "raw_data":
p = self._lst[row] p = self._lst[row]
if self._headers[column] == "name": if self._headers[column] == "name":
@@ -123,7 +129,10 @@ class TableModel(PamhyrTableModel):
v = self._lst[row].geometry.speed(q, z) v = self._lst[row].geometry.speed(q, z)
a = self._lst[row].geometry.wet_area(z) a = self._lst[row].geometry.wet_area(z)
b = self._lst[row].geometry.wet_width(z) b = self._lst[row].geometry.wet_width(z)
froude = v / sqrt(9.81 * (a / b)) if b == 0.0 or a == 0.0:
froude = 0.0
else:
froude = v / sqrt(9.81 * (a / b))
return f"{froude:.4f}" return f"{froude:.4f}"
else: else:
v = 0.0 v = 0.0
@@ -132,12 +141,14 @@ class TableModel(PamhyrTableModel):
return QVariant() return QVariant()
def update(self, reach): def update(self, reach):
_river = self._data.river _river = self._data[0].river
if self._opt_data == "reach": if self._opt_data == "reach":
self._lst = _river.reachs self._lst = _river.reachs
elif self._opt_data == "profile" or self._opt_data == "raw_data": elif self._opt_data == "profile" or self._opt_data == "raw_data":
self._lst = _river.reach(reach).profiles self._lst = _river.reach(reach).profiles
elif self._opt_data == "solver":
self._lst = self._parent._solvers
self.layoutChanged.emit() self.layoutChanged.emit()
+131
View File
@@ -0,0 +1,131 @@
# TableAdisTS.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 timer, trace
from PyQt5.QtGui import (
QKeySequence, QColor
)
from PyQt5.QtCore import (
Qt, QAbstractTableModel, QModelIndex,
QVariant, pyqtSlot, QCoreApplication,
)
from PyQt5.QtWidgets import (
QMessageBox, QUndoCommand, QUndoStack,
QStyledItemDelegate, QLineEdit, QAbstractItemView,
QComboBox,
)
from View.Tools.PamhyrTable import PamhyrTableModel
from View.Results.translate import *
logger = logging.getLogger()
_translate = QCoreApplication.translate
class TableModel(PamhyrTableModel):
def _setup_lst(self):
_river = self._data.river
self._timestamp = min(list(self._data.get("timestamps")))
if self._opt_data == "reach":
self._lst = _river.reachs
elif self._opt_data == "profile":
self._lst = _river.reach(0).profiles
elif self._opt_data == "raw_data":
self._lst = _river.reach(0).profiles
elif self._opt_data == "pollutants":
tmp_list = self._data.pollutants_list.copy()
tmp_list.remove("total_sediment")
self._lst = tmp_list
def __init__(self, type_pol, **kwargs):
self._type_pol = type_pol
super(TableModel, self).__init__(**kwargs)
def data(self, index, role=Qt.DisplayRole):
if role != Qt.ItemDataRole.DisplayRole:
return QVariant()
row = index.row()
column = index.column()
if self._opt_data == "reach":
if self._headers[column] == "name":
v = self._lst[row].name
return str(v)
elif self._opt_data == "pollutants":
if self._headers[column] == "name":
v = self._lst[row]
return str(v)
elif self._opt_data == "profile":
if self._headers[column] == "name":
v = self._lst[row].name
return str(v)
elif self._headers[column] == "rk":
v = self._lst[row].rk
return f"{v:.4f}"
elif self._opt_data == "raw_data":
p = self._lst[row]
if self._headers[column] == "name":
if p.name == "":
return f"{p.rk:.4f}"
return f"{p.name}"
tmp_list = self._data.pollutants_list.copy()
tmp_list.remove("total_sediment")
tmp_list2 = self._data.pollutants_list.copy()
for pol in tmp_list:
pol_index = tmp_list2.index(pol)
header_name = pol + " Concentration"
if self._headers[column] == header_name:
v = self._lst[row].get_ts_key(
self._timestamp, "pols")[pol_index][0]
return f"{v:.4f}"
if (self._headers[column] == pol + " Mass"
and self._type_pol[pol_index] == 7):
m1 = self._lst[row].get_ts_key(
self._timestamp, "pols")[pol_index][1]
m2 = self._lst[row].get_ts_key(
self._timestamp, "pols")[pol_index][2]
m3 = self._lst[row].get_ts_key(
self._timestamp, "pols")[pol_index][3]
v = m1 + m2 + m3
return f"{v:.4f}"
return QVariant()
def update(self, reach):
_river = self._data.river
if self._opt_data == "reach":
self._lst = _river.reachs
elif self._opt_data == "profile" or self._opt_data == "raw_data":
self._lst = _river.reach(reach).profiles
self.layoutChanged.emit()
@property
def timestamp(self):
return self._timestamp
def set_timestamp(self, timestamp):
self._timestamp = timestamp
self.layoutChanged.emit()
+105 -19
View File
@@ -61,7 +61,10 @@ from View.Results.CustomPlot.CustomPlotValuesSelectionDialog import (
) )
from View.Results.Table import TableModel from View.Results.Table import TableModel
from View.Results.translate import ResultsTranslate from View.Results.translate import (
ResultsTranslate,
CompareResultsTranslate
)
from View.Stricklers.Window import StricklersWindow from View.Stricklers.Window import StricklersWindow
_translate = QCoreApplication.translate _translate = QCoreApplication.translate
@@ -82,16 +85,17 @@ class ResultsWindow(PamhyrWindow):
) )
def __init__(self, study=None, config=None, def __init__(self, study=None, config=None,
solver=None, results=None, results=None, parent=None, trad=None):
parent=None): self._solvers = [r._solver for r in results]
self._solver = solver
self._results = results self._results = results
self._current_results = 0
trad = ResultsTranslate() if trad is None:
trad = ResultsTranslate()
name = ( name = (
trad[self._pamhyr_name] + " - " trad[self._pamhyr_name] + " - "
+ study.name + " - " + study.name + " - "
+ self._solver.name + " - ".join([s.name for s in self._solvers])
) )
super(ResultsWindow, self).__init__( super(ResultsWindow, self).__init__(
@@ -102,13 +106,13 @@ class ResultsWindow(PamhyrWindow):
parent=parent parent=parent
) )
self._hash_data.append(self._solver) for r in results:
self._hash_data.append(self._results) self._hash_data.append(r)
self._additional_plot = {} self._additional_plot = {}
try: try:
self._timestamps = sorted(list(self._results.get("timestamps"))) self._timestamps = sorted(list(self._results[self._current_results].get("timestamps")))
self.setup_slider() self.setup_slider()
self.setup_table() self.setup_table()
@@ -121,20 +125,24 @@ class ResultsWindow(PamhyrWindow):
def setup_table(self): def setup_table(self):
self._table = {} self._table = {}
for t in ["reach", "profile", "raw_data"]: for t in ["reach", "profile", "raw_data", "solver"]:
table = self.find(QTableView, f"tableView_{t}") table = self.find(QTableView, f"tableView_{t}")
self._table[t] = TableModel( self._table[t] = TableModel(
table_view=table, table_view=table,
table_headers=self._trad.get_dict(f"table_headers_{t}"), table_headers=self._trad.get_dict(f"table_headers_{t}"),
data=self._results, data=self._results,
undo=self._undo_stack, undo=self._undo_stack,
opt_data=t opt_data=t,
parent=self
) )
self._table[t]._timestamp = self._timestamps[ self._table[t]._timestamp = self._timestamps[
self._slider_time.value()] self._slider_time.value()]
if len(self._results) <= 1:
table = self.find(QTableView, f"tableView_solver")
table.hide()
def setup_slider(self): def setup_slider(self):
default_reach = self._results.river.reach(0) default_reach = self._results[self._current_results].river.reach(0)
self._slider_time = self.find(QSlider, f"horizontalSlider_time") self._slider_time = self.find(QSlider, f"horizontalSlider_time")
self._slider_time.setMaximum(len(self._timestamps) - 1) self._slider_time.setMaximum(len(self._timestamps) - 1)
@@ -181,6 +189,7 @@ class ResultsWindow(PamhyrWindow):
results=self._results, results=self._results,
reach_id=0, reach_id=0,
profile_id=0, profile_id=0,
res_id=self._current_results,
trad=self._trad, trad=self._trad,
toolbar=self.toolbar, toolbar=self.toolbar,
display_current=True, display_current=True,
@@ -205,6 +214,7 @@ class ResultsWindow(PamhyrWindow):
results=self._results, results=self._results,
reach_id=0, reach_id=0,
profile_id=0, profile_id=0,
res_id=self._current_results,
trad=self._trad, trad=self._trad,
toolbar=self.toolbar_2 toolbar=self.toolbar_2
) )
@@ -227,6 +237,7 @@ class ResultsWindow(PamhyrWindow):
results=self._results, results=self._results,
reach_id=0, reach_id=0,
profile_id=0, profile_id=0,
res_id=self._current_results,
trad=self._trad, trad=self._trad,
toolbar=self.toolbar_3 toolbar=self.toolbar_3
) )
@@ -250,6 +261,7 @@ class ResultsWindow(PamhyrWindow):
results=self._results, results=self._results,
reach_id=0, reach_id=0,
profile_id=0, profile_id=0,
res_id=self._current_results,
trad=self._trad, trad=self._trad,
toolbar=self.toolbar_4 toolbar=self.toolbar_4
) )
@@ -324,9 +336,10 @@ class ResultsWindow(PamhyrWindow):
"reach": self._set_current_reach, "reach": self._set_current_reach,
"profile": self._set_current_profile, "profile": self._set_current_profile,
"raw_data": self._set_current_profile_raw_data, "raw_data": self._set_current_profile_raw_data,
"solver": self._set_current_results,
} }
for t in ["reach", "profile", "raw_data"]: for t in ["reach", "profile", "raw_data", "solver"]:
table = self.find(QTableView, f"tableView_{t}") table = self.find(QTableView, f"tableView_{t}")
table.selectionModel()\ table.selectionModel()\
@@ -374,7 +387,28 @@ class ResultsWindow(PamhyrWindow):
) )
table.scrollTo(index) table.scrollTo(index)
def update(self, reach_id=None, profile_id=None, timestamp=None): def update_table_selection_solver(self, ind):
table = self.find(QTableView, f"tableView_solver")
selectionModel = table.selectionModel()
index = table.model().index(ind, 0)
selectionModel.select(
index,
QItemSelectionModel.Rows |
QItemSelectionModel.ClearAndSelect |
QItemSelectionModel.Select
)
table.scrollTo(index)
self._table["profile"].update(ind)
self._table["raw_data"].update(ind)
def update(self,
reach_id=None,
profile_id=None,
solver_id=None,
timestamp=None):
if reach_id is not None: if reach_id is not None:
self.plot_xy.set_reach(reach_id) self.plot_xy.set_reach(reach_id)
self.plot_ac.set_reach(reach_id) self.plot_ac.set_reach(reach_id)
@@ -398,6 +432,15 @@ class ResultsWindow(PamhyrWindow):
self.update_table_selection_profile(profile_id) self.update_table_selection_profile(profile_id)
if solver_id is not None:
self.plot_xy.set_result(solver_id)
self.plot_ac.set_result(solver_id)
self.plot_rkc.set_result(solver_id)
self.plot_h.set_result(solver_id)
for plot in self._additional_plot:
self._additional_plot[plot].set_result(solver_id)
if timestamp is not None: if timestamp is not None:
self.plot_xy.set_timestamp(timestamp) self.plot_xy.set_timestamp(timestamp)
self.plot_ac.set_timestamp(timestamp) self.plot_ac.set_timestamp(timestamp)
@@ -458,6 +501,14 @@ class ResultsWindow(PamhyrWindow):
ind = indexes[0].row() ind = indexes[0].row()
self.update(profile_id=ind) self.update(profile_id=ind)
def _set_current_results(self):
table = self.find(QTableView, f"tableView_solver")
indexes = table.selectedIndexes()
if len(indexes) == 0:
return
self.update(solver_id=indexes[0].row())
def _set_current_timestamp(self): def _set_current_timestamp(self):
timestamp = self._timestamps[self._slider_time.value()] timestamp = self._timestamps[self._slider_time.value()]
self.update(timestamp=timestamp) self.update(timestamp=timestamp)
@@ -614,13 +665,13 @@ class ResultsWindow(PamhyrWindow):
if x == "rk": if x == "rk":
timestamp = self._get_current_timestamp() timestamp = self._get_current_timestamp()
first_line.append(f"Time: {timestamp}s") first_line.append(f"Time: {timestamp}s")
val_dict = self._export_rk(timestamp, y, envelop, filename) val_dict = self._export_rk(timestamp, y, envelop)
elif x == "time": elif x == "time":
profile_id = self._get_current_profile() profile_id = self._get_current_profile()
profile = reach.profile(profile_id) profile = reach.profile(profile_id)
pname = profile.name if profile.name != "" else profile.rk pname = profile.name if profile.name != "" else profile.rk
first_line.append(f"Profile: {pname}") first_line.append(f"Profile: {pname}")
val_dict = self._export_time(profile_id, y, filename) val_dict = self._export_time(profile_id, y)
with open(filename, 'w', newline='') as csvfile: with open(filename, 'w', newline='') as csvfile:
writer = csv.writer(csvfile, delimiter=',', writer = csv.writer(csvfile, delimiter=',',
@@ -675,7 +726,7 @@ class ResultsWindow(PamhyrWindow):
self._additional_plot.pop(tab_widget.tabText(index)) self._additional_plot.pop(tab_widget.tabText(index))
tab_widget.removeTab(index) tab_widget.removeTab(index)
def _export_rk(self, timestamp, y, envelop, filename): def _export_rk(self, timestamp, y, envelop):
reach = self._results.river.reachs[self._get_current_reach()] reach = self._results.river.reachs[self._get_current_reach()]
dict_x = self._trad.get_dict("values_x") dict_x = self._trad.get_dict("values_x")
dict_y = self._trad.get_dict("values_y") dict_y = self._trad.get_dict("values_y")
@@ -685,7 +736,7 @@ class ResultsWindow(PamhyrWindow):
my_dict[dict_x["rk"]] = reach.geometry.get_rk() my_dict[dict_x["rk"]] = reach.geometry.get_rk()
if "bed_elevation" in y: if "bed_elevation" in y:
my_dict[dict_y["bed_elevation"]] = reach.geometry.get_z_min() my_dict[dict_y["bed_elevation"]] = reach.geometry.get_z_min()
#if envelop and reach.has_sediment(): # if envelop and reach.has_sediment():
if "discharge" in y: if "discharge" in y:
my_dict[dict_y["discharge"]] = list( my_dict[dict_y["discharge"]] = list(
map( map(
@@ -807,7 +858,7 @@ class ResultsWindow(PamhyrWindow):
return my_dict return my_dict
def _export_time(self, profile, y, filename): def _export_time(self, profile, y):
reach = self._results.river.reachs[self._get_current_reach()] reach = self._results.river.reachs[self._get_current_reach()]
profile = reach.profile(profile) profile = reach.profile(profile)
ts = list(self._results.get("timestamps")) ts = list(self._results.get("timestamps"))
@@ -855,3 +906,38 @@ class ResultsWindow(PamhyrWindow):
) )
return my_dict return my_dict
#class CompareResultsWindow(ResultsWindow):
#_pamhyr_ui = "CompareResults"
#_pamhyr_name = "CompareResults"
#def _path_file(self, filename):
#return os.path.abspath(
#os.path.join(
#os.path.dirname(__file__),
#"..", "ui", "ressources", filename
#)
#)
#def __init__(self, study=None, config=None,
#solver=None, results=None,
#parent=None):
#self._solvers[0] = solver
#self._results = results
#trad = CompareResultsTranslate()
#name = (
#trad[self._pamhyr_name] + " - "
#+ study.name + " - "
#+ self._solvers[0].name
#)
#super(CompareResultsWindow, self).__init__(
#study=study,
#config=config,
#solver=solver,
#results=results,
#parent=parent,
#trad=trad
#)
+744
View File
@@ -0,0 +1,744 @@
# 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 -*-
import os
import csv
import logging
from datetime import datetime
from tools import trace, timer, logger_exception
from View.Tools.PamhyrWindow import PamhyrWindow
from PyQt5.QtGui import (
QKeySequence, QIcon, QPixmap,
)
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel,
QCoreApplication, QModelIndex, pyqtSlot,
QItemSelectionModel, QTimer,
)
from PyQt5.QtWidgets import (
QDialogButtonBox, QPushButton, QLineEdit,
QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate,
QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
QSlider, QLabel, QWidget, QGridLayout,
)
from View.Tools.Plot.PamhyrCanvas import MplCanvas
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
from View.Results.PlotSedAdis import PlotAdis_dx, PlotAdis_dt
from View.Results.CustomPlot.Plot import CustomPlot
from View.Results.CustomExportAdis import (
CustomExportAdisDialog,
)
from View.Results.TableAdisTS import TableModel
from View.Results.translate import ResultsTranslate
_translate = QCoreApplication.translate
logger = logging.getLogger()
class ResultsWindowAdisTS(PamhyrWindow):
_pamhyr_ui = "ResultsAdisTS"
_pamhyr_name = "Results"
def _path_file(self, filename):
return os.path.abspath(
os.path.join(
os.path.dirname(__file__),
"..", "ui", "ressources", filename
)
)
def __init__(self, study=None, config=None,
solver=None, results=None,
parent=None):
self._solver = solver
self._results = results
pollutants_headers = self._results.pollutants_list.copy()
trad = ResultsTranslate(pollutants_headers)
name = (
trad[self._pamhyr_name] + " - "
+ study.name + " - "
+ self._solver.name
)
super(ResultsWindowAdisTS, self).__init__(
title=name,
study=study,
config=config,
trad=trad,
parent=parent
)
self._hash_data.append(self._solver)
self._hash_data.append(self._results)
self._additional_plot = {}
self._current_pol_id = [1]
self._reach_id = 0
self._profile_id = 0
try:
self._timestamps = sorted(list(self._results.get("timestamps")))
self.set_type_pol()
self.setup_slider()
self.setup_table()
self.setup_plots()
self.setup_statusbar()
self.setup_connections()
self.update_table_selection_reach(self._reach_id)
self.update_table_selection_profile(self._profile_id)
self.update_table_selection_pol(self._current_pol_id)
except Exception as e:
logger_exception(e)
return
def set_type_pol(self):
self._type_pol = []
self._pol_id_dict = {}
tmp_list = self._results.river.reach(0).profiles
for pol_index in range(self._results.nb_pollutants):
pol_name = self._results.pollutants_list[pol_index]
if pol_name == "total_sediment":
self._type_pol.append(-1)
else:
self._type_pol.append(len(
tmp_list[0].get_ts_key(
self._timestamps[0], "pols")[pol_index])
)
self._pol_id_dict[pol_name] = pol_index
def setup_table(self):
self._table = {}
for t in ["reach", "profile", "pollutants", "raw_data"]:
table = self.find(QTableView, f"tableView_{t}")
self._table[t] = TableModel(
self._type_pol,
table_view=table,
table_headers=self._trad.get_dict(f"table_headers_{t}"),
data=self._results,
undo=self._undo_stack,
opt_data=t,
)
self._table[t]._timestamp = self._timestamps[
self._slider_time.value()]
def setup_slider(self):
self._slider_time = self.find(QSlider, f"horizontalSlider_time")
self._slider_time.setMaximum(len(self._timestamps) - 1)
self._slider_time.setValue(len(self._timestamps) - 1)
self._icon_start = QIcon()
self._icon_start.addPixmap(
QPixmap(self._path_file("media-playback-start.png"))
)
self._icon_pause = QIcon()
self._icon_pause.addPixmap(
QPixmap(self._path_file("media-playback-pause.png"))
)
self._button_play = self.find(QPushButton, f"playButton")
self._button_play.setIcon(self._icon_start)
self._button_back = self.find(QPushButton, f"backButton")
self._button_next = self.find(QPushButton, f"nextButton")
self._button_first = self.find(QPushButton, f"firstButton")
self._button_last = self.find(QPushButton, f"lastButton")
self._timer = QTimer(self)
def setup_plots(self):
self.canvas_cdt = MplCanvas(width=5, height=4, dpi=100)
self.canvas_cdt.setObjectName("canvas_cdt")
self.toolbar_cdt = PamhyrPlotToolbar(
self.canvas_cdt, self, items=[
"home", "move", "zoom", "save",
"iso", "back/forward"
]
)
self.plot_layout_cdt = self.find(
QVBoxLayout, "verticalLayout_concentration_dt")
self.plot_layout_cdt.addWidget(self.toolbar_cdt)
self.plot_layout_cdt.addWidget(self.canvas_cdt)
self.plot_cdt = PlotAdis_dt(
canvas=self.canvas_cdt,
results=self._results,
reach_id=self._reach_id,
profile_id=self._profile_id,
pol_id=self._current_pol_id,
key="C",
type_pol=self._type_pol,
trad=self._trad,
toolbar=self.toolbar_cdt
)
self.plot_cdt.draw()
self.canvas_cdx = MplCanvas(width=5, height=4, dpi=100)
self.canvas_cdx.setObjectName("canvas_cdx")
self.toolbar_cdx = PamhyrPlotToolbar(
self.canvas_cdx, self, items=[
"home", "move", "zoom", "save",
"iso", "back/forward"
]
)
self.plot_layout_cdx = self.find(
QVBoxLayout, "verticalLayout_concentration_dx")
self.plot_layout_cdx.addWidget(self.toolbar_cdx)
self.plot_layout_cdx.addWidget(self.canvas_cdx)
self.plot_cdx = PlotAdis_dx(
canvas=self.canvas_cdx,
results=self._results,
reach_id=self._reach_id,
profile_id=self._profile_id,
pol_id=self._current_pol_id,
key="C",
type_pol=self._type_pol,
trad=self._trad,
toolbar=self.toolbar_cdx
)
self.plot_cdx.draw()
self.canvas_mdx = MplCanvas(width=5, height=4, dpi=100)
self.canvas_mdx.setObjectName("canvas_mdx")
self.toolbar_mdx = PamhyrPlotToolbar(
self.canvas_mdx, self, items=[
"home", "move", "zoom", "save",
"iso", "back/forward"
]
)
self.plot_layout_mdx = self.find(
QVBoxLayout, "verticalLayout_mass_dx2")
self.plot_layout_mdx.addWidget(self.toolbar_mdx)
self.plot_layout_mdx.addWidget(self.canvas_mdx)
self.plot_mdx = PlotAdis_dx(
canvas=self.canvas_mdx,
results=self._results,
reach_id=self._reach_id,
profile_id=self._profile_id,
pol_id=self._current_pol_id,
key="M",
type_pol=self._type_pol,
trad=self._trad,
toolbar=self.toolbar_mdx
)
self.plot_mdx.draw()
self.canvas_mdt = MplCanvas(width=5, height=4, dpi=100)
self.canvas_mdt.setObjectName("canvas_mdt")
self.toolbar_mdt = PamhyrPlotToolbar(
self.canvas_mdt, self, items=[
"home", "move", "zoom", "save",
"iso", "back/forward"
]
)
self.plot_layout_mdt = self.find(
QVBoxLayout, "verticalLayout_mass_dt2")
self.plot_layout_mdt.addWidget(self.toolbar_mdt)
self.plot_layout_mdt.addWidget(self.canvas_mdt)
self.plot_mdt = PlotAdis_dt(
canvas=self.canvas_mdt,
results=self._results,
reach_id=self._reach_id,
profile_id=self._profile_id,
pol_id=self._current_pol_id,
key="M",
type_pol=self._type_pol,
trad=self._trad,
toolbar=self.toolbar_mdt
)
self.plot_mdt.draw()
# Thickness
self.canvas_tdx = MplCanvas(width=5, height=4, dpi=100)
self.canvas_tdx.setObjectName("canvas_tdx")
self.toolbar_tdx = PamhyrPlotToolbar(
self.canvas_tdx, self, items=[
"home", "move", "zoom", "save",
"iso", "back/forward"
]
)
self.plot_layout_tdx = self.find(
QVBoxLayout, "verticalLayout_tdx")
self.plot_layout_tdx.addWidget(self.toolbar_tdx)
self.plot_layout_tdx.addWidget(self.canvas_tdx)
self.plot_tdx = PlotAdis_dx(
canvas=self.canvas_tdx,
results=self._results,
reach_id=self._reach_id,
profile_id=self._profile_id,
pol_id=[0],
key="M",
type_pol=self._type_pol,
trad=self._trad,
toolbar=self.toolbar_tdx
)
self.plot_tdx.draw()
self.canvas_tdt = MplCanvas(width=5, height=4, dpi=100)
self.canvas_tdt.setObjectName("canvas_tdt")
self.toolbar_tdt = PamhyrPlotToolbar(
self.canvas_tdt, self, items=[
"home", "move", "zoom", "save",
"iso", "back/forward"
]
)
self.plot_layout_tdt = self.find(
QVBoxLayout, "verticalLayout_tdt")
self.plot_layout_tdt.addWidget(self.toolbar_tdt)
self.plot_layout_tdt.addWidget(self.canvas_tdt)
self.plot_tdt = PlotAdis_dt(
canvas=self.canvas_tdt,
results=self._results,
reach_id=self._reach_id,
profile_id=self._profile_id,
pol_id=[0],
key="M",
type_pol=self._type_pol,
trad=self._trad,
toolbar=self.toolbar_tdt
)
self.plot_tdt.draw()
def closeEvent(self, event):
try:
self._timer.stop()
except Exception as e:
logger_exception(e)
super(ResultsWindowAdisTS, self).closeEvent(event)
def _compute_status_label(self):
# Timestamp
ts = self._timestamps[self._slider_time.value()]
t0 = datetime.fromtimestamp(0)
fts = str(
datetime.fromtimestamp(ts) - t0
)
fts.replace("days", _translate("Results", "days"))\
.replace("day", _translate("Results", "day"))
# Reach
table = self.find(QTableView, f"tableView_reach")
indexes = table.selectedIndexes()
if len(indexes) == 0:
reach = self._study.river.edges()[0]
else:
reach = self._study.river.edges()[indexes[0].row()]
# Profile
table = self.find(QTableView, f"tableView_profile")
indexes = table.selectedIndexes()
if len(indexes) == 0:
profile = reach.reach.profile(0)
else:
profile = reach.reach.profile(indexes[0].row())
pname = profile.name if profile.name != "" else profile.rk
# Pollutant
table = self.find(QTableView, f"tableView_pollutants")
indexes = table.selectedIndexes()
if len(indexes) == 0:
pollutant = self._results.pollutants_list[0]
else:
pollutant = self._results.pollutants_list[indexes[0].row()]
#return (f"Reach: {reach.name} | " +
#f"Profile: {pname} | " +
#f"Pollutant: {pollutant}")
return (f"{self._trad['reach']}: {reach.name} | " +
f"{self._trad['cross_section']}: {pname} | " +
f"Pollutant: {pollutant} | " +
f"{self._trad['unit_time_s']} : {fts} ({ts} sec)")
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 setup_connections(self):
# Action
actions = {
"action_reload": self._reload,
# TODO "action_add": self._add_custom_plot,
"action_export": self.export,
}
for action in actions:
self.find(QAction, action).triggered.connect(
actions[action]
)
# Table and Plot
fun = {
"reach": self._set_current_reach,
"profile": self._set_current_profile,
"pollutants": self._set_current_pol,
"raw_data": self._set_current_profile_raw_data,
}
for t in ["reach", "profile", "pollutants"]:
table = self.find(QTableView, f"tableView_{t}")
table.selectionModel()\
.selectionChanged\
.connect(fun[t])
self._table[t].dataChanged.connect(fun[t])
self._slider_time.valueChanged.connect(self._set_current_timestamp)
self._button_play.setChecked(False)
self._button_play.clicked.connect(self._pause)
self._button_back.clicked.connect(self._back)
self._button_next.clicked.connect(self._next)
self._button_first.clicked.connect(self._first)
self._button_last.clicked.connect(self._last)
self._timer.timeout.connect(self._update_slider)
def update_table_selection_reach(self, ind):
table = self.find(QTableView, f"tableView_reach")
selectionModel = table.selectionModel()
index = table.model().index(ind, 0)
selectionModel.select(
index,
QItemSelectionModel.Rows |
QItemSelectionModel.ClearAndSelect |
QItemSelectionModel.Select
)
table.scrollTo(index)
self._table["profile"].update(ind)
self._table["raw_data"].update(ind)
def update_table_selection_profile(self, ind):
for t in ["profile"]:
table = self.find(QTableView, f"tableView_{t}")
selectionModel = table.selectionModel()
index = table.model().index(ind, 0)
selectionModel.select(
index,
QItemSelectionModel.Rows |
QItemSelectionModel.ClearAndSelect |
QItemSelectionModel.Select
)
table.scrollTo(index)
def update_table_selection_pol(self, ind):
for t in ["pollutants"]:
table = self.find(QTableView, f"tableView_{t}")
selectionModel = table.selectionModel()
index = table.model().index(ind[0] - 1, 0)
selectionModel.select(
index,
QItemSelectionModel.Rows |
QItemSelectionModel.ClearAndSelect |
QItemSelectionModel.Select
)
table.scrollTo(index)
def update(self, reach_id=None, profile_id=None,
pol_id=None, timestamp=None):
if reach_id is not None:
self._reach_id = reach_id
self.plot_cdt.set_reach(reach_id)
self.plot_cdx.set_reach(reach_id)
self.plot_mdx.set_reach(reach_id)
self.plot_mdt.set_reach(reach_id)
self.plot_tdt.set_reach(reach_id)
self.plot_tdx.set_reach(reach_id)
self.update_table_selection_reach(reach_id)
self.update_table_selection_profile(0)
if profile_id is not None:
self._profile_id = profile_id
self.plot_cdt.set_profile(profile_id)
self.plot_cdx.set_profile(profile_id)
self.plot_mdx.set_profile(profile_id)
self.plot_mdt.set_profile(profile_id)
self.plot_tdt.set_profile(profile_id)
self.plot_tdx.set_profile(profile_id)
self.update_table_selection_profile(profile_id)
if pol_id is not None:
self._current_pol_id = [p+1 for p in pol_id] # rm total_sediment
self.plot_cdt.set_pollutant(self._current_pol_id)
self.plot_cdx.set_pollutant(self._current_pol_id)
self.plot_mdx.set_pollutant(self._current_pol_id)
self.plot_mdt.set_pollutant(self._current_pol_id)
if timestamp is not None:
self.plot_cdt.set_timestamp(timestamp)
self.plot_cdx.set_timestamp(timestamp)
self.plot_mdx.set_timestamp(timestamp)
self.plot_mdt.set_timestamp(timestamp)
self.plot_tdt.set_timestamp(timestamp)
self.plot_tdx.set_timestamp(timestamp)
self._table["raw_data"].set_timestamp(timestamp)
self.update_statusbar()
def _get_current_timestamp(self):
return self._timestamps[
self._slider_time.value()
]
def _set_current_reach(self):
table = self.find(QTableView, f"tableView_reach")
indexes = table.selectedIndexes()
if len(indexes) == 0:
return
self.update(reach_id=indexes[0].row())
def _set_current_profile(self):
table = self.find(QTableView, f"tableView_profile")
indexes = table.selectedIndexes()
if len(indexes) == 0:
return
self.update(profile_id=indexes[0].row())
def _set_current_pol(self):
table = self.find(QTableView, f"tableView_pollutants")
indexes = table.selectedIndexes()
rows = [i.row() for i in indexes]
if len(indexes) == 0:
return
self.update(pol_id=rows)
def _set_current_profile_raw_data(self):
table = self.find(QTableView, f"tableView_raw_data")
indexes = table.selectedIndexes()
if len(indexes) == 0:
return
ind = indexes[0].row()
self.update(profile_id=ind)
self._slider_profile.setValue(ind)
def _set_current_timestamp(self):
timestamp = self._timestamps[self._slider_time.value()]
self.update(timestamp=timestamp)
def _reload_plots(self):
self.plot_cdt.results = self._results
self.plot_cdx.results = self._results
self.plot_mdx.results = self._results
self.plot_mdt.results = self._results
self.plot_tdt.results = self._results
self.plot_tdx.results = self._results
self.plot_cdt.draw()
self.plot_cdx.draw()
self.plot_mdx.draw()
self.plot_mdt.draw()
self.plot_tdt.draw()
self.plot_tdx.draw()
def _reload_slider(self):
self._slider_time = self.find(QSlider, f"horizontalSlider_time")
self._slider_time.setMaximum(len(self._timestamps) - 1)
self._slider_time.setValue(len(self._timestamps) - 1)
def _reload(self):
logger.debug("Reload results...")
self._results = self._results.reload()
self._timestamps = sorted(list(self._results.get("timestamps")))
self._reload_plots()
self._reload_slider()
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()
# play / pause buttons
def _update_slider(self):
if self._slider_time.value() == self._slider_time.maximum():
self._slider_time.setValue(self._slider_time.minimum())
else:
self._slider_time.setValue(self._slider_time.value()+1)
def _next(self):
self._slider_time.setValue(self._slider_time.value()+1)
def _back(self):
self._slider_time.setValue(self._slider_time.value()-1)
def _first(self):
self._slider_time.setValue(self._slider_time.minimum())
def _last(self):
self._slider_time.setValue(self._slider_time.maximum())
def _pause(self):
if self._button_play.isChecked():
self._button_next.setEnabled(False)
self._button_back.setEnabled(False)
self._button_first.setEnabled(False)
self._button_last.setEnabled(False)
self._timer.start(100)
self._button_play.setIcon(self._icon_pause)
else:
self._timer.stop()
self._button_next.setEnabled(True)
self._button_back.setEnabled(True)
self._button_first.setEnabled(True)
self._button_last.setEnabled(True)
self._button_play.setIcon(self._icon_start)
def export(self):
pols = self._results.pollutants_list.copy()
dlg = CustomExportAdisDialog(pollutants=pols,
parent=self)
if dlg.exec():
x, y, pol = dlg.value
else:
return
self.file_dialog(
select_file="AnyFile",
callback=lambda f: self.export_to(f[0], x, y, pol),
default_suffix=".csv",
file_filter=["CSV (*.csv)"],
)
def export_to(self, filename, x, y, pol):
timestamps = sorted(self._results.get("timestamps"))
reach = self._results.river.reachs[self._reach_id]
first_line = [f"Study: {self._results.study.name}",
f"Reach: {reach.name}"]
if x == "rk":
timestamp = self._get_current_timestamp()
first_line.append(f"Time: {timestamp}s")
val_dict = self._export_rk(timestamp, y, pol)
elif x == "time":
profile = reach.profile(self._profile_id)
pname = profile.name if profile.name != "" else profile.rk
first_line.append(f"Profile: {pname}")
val_dict = self._export_time(self._profile_id, y, pol)
with open(filename, 'w', newline='') as csvfile:
writer = csv.writer(csvfile, delimiter=',',
quotechar='|', quoting=csv.QUOTE_MINIMAL)
dict_x = self._trad.get_dict("values_x")
header = []
writer.writerow(first_line)
for text in val_dict.keys():
header.append(text)
writer.writerow(header)
for row in range(len(val_dict[dict_x[x]])):
line = []
for var in val_dict.keys():
line.append(val_dict[var][row])
writer.writerow(line)
def _export_rk(self, timestamp, y, pol):
reach = self._results.river.reachs[self._reach_id]
dict_x = self._trad.get_dict("values_x")
dict_y = self._trad.get_dict("values_y_pol")
my_dict = {}
my_dict[dict_x["rk"]] = reach.geometry.get_rk()
val_id = {}
val_id["unit_C"] = 0
val_id["unit_M"] = 2
val_id["unit_thickness"] = 2
for unit in y:
if unit == "unit_thickness":
pol_id = 0
else:
pol_id = self._pol_id_dict[pol]
if unit == "unit_M" and self._type_pol[pol_id] == 1:
my_dict[dict_y[unit]] = [0.0]*len(reach.profiles)
else:
my_dict[dict_y[unit]] = list(map(lambda p:
p.get_ts_key(
timestamp, "pols"
)[pol_id][val_id[unit]],
reach.profiles))
return my_dict
def _export_time(self, profile, y, pol):
reach = self._results.river.reachs[self._reach_id]
profile = reach.profile(profile)
ts = list(self._results.get("timestamps"))
ts.sort()
dict_x = self._trad.get_dict("values_x")
dict_y = self._trad.get_dict("values_y_pol")
my_dict = {}
my_dict[dict_x["time"]] = ts
val_id = {}
val_id["unit_C"] = 0
val_id["unit_M"] = 2
val_id["unit_thickness"] = 2
for unit in y:
if unit == "unit_thickness":
pol_id = 0
else:
pol_id = self._pol_id_dict[pol]
if unit == "unit_M" and self._type_pol[pol_id] == 1:
my_dict[dict_y[unit]] = [0.0]*len(ts)
else:
my_dict[dict_y[unit]] = list(map(lambda data_el:
data_el[pol_id][val_id[unit]],
profile.get_key("pols")
))
return my_dict
+75 -2
View File
@@ -24,7 +24,14 @@ _translate = QCoreApplication.translate
class ResultsTranslate(MainTranslate): class ResultsTranslate(MainTranslate):
def __init__(self): def __init__(self, pollutants=None):
if pollutants is not None:
self.pollutants = pollutants
if "total_sediment" in self.pollutants:
self.pollutants.remove("total_sediment")
else:
self.pollutants = pollutants
super(ResultsTranslate, self).__init__() super(ResultsTranslate, self).__init__()
self._dict["Results"] = _translate("Results", "Results") self._dict["Results"] = _translate("Results", "Results")
@@ -48,11 +55,23 @@ class ResultsTranslate(MainTranslate):
"name": _translate("Results", "Reach name"), "name": _translate("Results", "Reach name"),
} }
self._sub_dict["table_headers_phys_var"] = {
"name": _translate("Results", "Variables names"),
}
self._sub_dict["table_headers_profile"] = { self._sub_dict["table_headers_profile"] = {
"name": self._dict["name"], "name": _translate("Results", "Profile name"),
"rk": self._dict["unit_rk"], "rk": self._dict["unit_rk"],
} }
self._sub_dict["table_headers_solver"] = {
"solver": _translate("Results", "Solver"),
}
self._sub_dict["table_headers_pollutants"] = {
"name": _translate("Results", "Pollutant name"),
}
self._sub_dict["table_headers_raw_data"] = { self._sub_dict["table_headers_raw_data"] = {
"name": _translate("Results", "Profile"), "name": _translate("Results", "Profile"),
"water_elevation": self._dict["unit_water_elevation"], "water_elevation": self._dict["unit_water_elevation"],
@@ -83,6 +102,22 @@ class ResultsTranslate(MainTranslate):
"wet_area": self._dict["unit_wet_area"], "wet_area": self._dict["unit_wet_area"],
} }
if self.pollutants is not None:
self._sub_dict["table_headers_raw_data"] = {
"name": _translate("Results", "Profile"),
}
tmp = self._sub_dict["table_headers_raw_data"]
for pol in self.pollutants:
tmp[pol + " Concentration"] = pol + "\n Concentration"
tmp[pol + " Mass"] = pol + "\n Mass"
else:
self._sub_dict["table_headers_raw_data"] = {
"name": _translate("Results", "Profile"),
"water_elevation": self._dict["unit_water_elevation"],
"discharge": self._dict["unit_discharge"],
"discharge": self._dict["unit_discharge"],
}
self._sub_dict["values_y_envelop"] = { self._sub_dict["values_y_envelop"] = {
"min_bed_elevation": self._dict["unit_min_bed_elevation"], "min_bed_elevation": self._dict["unit_min_bed_elevation"],
"max_bed_elevation": self._dict["unit_max_bed_elevation"], "max_bed_elevation": self._dict["unit_max_bed_elevation"],
@@ -95,3 +130,41 @@ class ResultsTranslate(MainTranslate):
"min_depth": self._dict["unit_min_depth"], "min_depth": self._dict["unit_min_depth"],
"max_depth": self._dict["unit_max_depth"], "max_depth": self._dict["unit_max_depth"],
} }
self._sub_dict["values_y_pol"] = {
"unit_C": self._dict["unit_concentration"],
"unit_M": self._dict["unit_mass"],
"unit_thickness": self._dict["unit_thickness"],
}
class CompareResultsTranslate(ResultsTranslate):
def __init__(self):
super(CompareResultsTranslate, self).__init__()
self._dict['label_water'] = u"Δ "+_translate("Results", "Water elevation")
self._dict['unit_elevation'] = u"Δ "+self._dict["unit_elevation"]
self._sub_dict["table_headers_raw_data"] = {
"name": _translate("Results", "Profile"),
"water_elevation": u"Δ "+self._dict["unit_water_elevation"],
"discharge": u"Δ "+self._dict["unit_discharge"],
"velocity": u"Δ "+self._dict["unit_velocity"],
"width": u"Δ "+self._dict["unit_width"],
"depth": u"Δ "+self._dict["unit_depth"],
"mean_depth": u"Δ "+self._dict["unit_mean_depth"],
"wet_area": u"Δ "+self._dict["unit_wet_area"],
"wet_perimeter": u"Δ "+self._dict["unit_wet_perimeter"],
"hydraulic_radius": u"Δ "+self._dict["unit_hydraulic_radius"],
"froude": u"Δ "+self._dict["unit_froude"],
}
self._sub_dict["values_y"] = {
"bed_elevation": u"Δ "+self._dict["unit_bed_elevation"],
"water_elevation": u"Δ "+self._dict["unit_water_elevation"],
"discharge": u"Δ "+self._dict["unit_discharge"],
"velocity": u"Δ "+self._dict["unit_velocity"],
"depth": u"Δ "+self._dict["unit_depth"],
"mean_depth": u"Δ "+self._dict["unit_mean_depth"],
"froude": u"Δ "+self._dict["unit_froude"],
"wet_area": u"Δ "+self._dict["unit_wet_area"],
}
Loaded 100 of 129 files, more files were not shown because too many files have changed in this diff. Show more