mirror of https://gitlab.com/pamhyr/pamhyr2
Merge branch 'terraz_dev' into adists_release
commit
1266884f93
|
|
@ -41,6 +41,9 @@ __old__
|
|||
|
||||
*-venv
|
||||
|
||||
mage8
|
||||
adists
|
||||
|
||||
### END CUSTOM ###
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/python
|
||||
|
|
|
|||
|
|
@ -28,20 +28,6 @@
|
|||
#+OPTIONS: toc:t
|
||||
#+LANGUAGE: UKenglish
|
||||
|
||||
#+NAME: attr_wrap
|
||||
#+HEADER: :var width="\\textwidth"
|
||||
#+HEADER: :var caption=""
|
||||
#+HEADER: :var smallcaption=""
|
||||
#+HEADER: :var name=""
|
||||
#+HEADER: :var data=""
|
||||
#+HEADER: :var float="nil"
|
||||
#+BEGIN_SRC sh :results output :exports none
|
||||
echo "#+CAPTION[$smallcaption]: $caption"
|
||||
echo "#+NAME: $name"
|
||||
echo "#+ATTR_LATEX: :width $width :float $float"
|
||||
echo "$data"
|
||||
#+END_SRC
|
||||
|
||||
#+BEGIN_abstract
|
||||
This document is for the use of developers. It describes the project
|
||||
architecture, the tools available to assist development and
|
||||
|
|
@ -54,13 +40,13 @@ documentation, translations or code.
|
|||
|
||||
* Introduction
|
||||
|
||||
Pamhyr2 is free and open source software (FOSS) graphical user
|
||||
{{{pamhyr2}}} is free and open source software (FOSS) graphical user
|
||||
interface (GUI) for 1D hydro-sedimentary modelling of rivers developed
|
||||
in Python (with version 3.8). It use PyQt at version 5 and matplotlib
|
||||
in version 3.4.1 or later for the user insterface (see
|
||||
{{{file(/requirements.txt)}}} for details). The architecture of
|
||||
project code follow the Qt Model/View architecture [fn:qt-arch] (see
|
||||
details in section [[Architecture]]). Pamhyr2 packages can be build
|
||||
details in section [[Architecture]]). {{{pamhyr2}}} packages can be build
|
||||
manually (see section [[Building packages]]), but there are automatically
|
||||
build with the gitlab-ci (see the section [[Setup the CI
|
||||
environment]]). Documentation files are written with org-mode[fn:org],
|
||||
|
|
@ -75,7 +61,7 @@ https://doc.qt.io/qt-5/model-view-programming.html (last access
|
|||
|
||||
* Architecture
|
||||
|
||||
Pamhyr2's architecture is based on Qt Model/View, see Figure
|
||||
{{{pamhyr2}}}'s architecture is based on Qt Model/View, see Figure
|
||||
[[graph-architecture]]. It is made up of several different components: the
|
||||
model (in blue), the graphical components (in red), the
|
||||
actions/delegates (in green), the commands (in purple), the solvers
|
||||
|
|
@ -178,7 +164,7 @@ https://doc.qt.io/qt-5/model-view-programming.html
|
|||
|
||||
** Model
|
||||
|
||||
The model is a set of Python classes. In Pamhyr2, this classes must
|
||||
The model is a set of Python classes. In {{{pamhyr2}}}, this classes must
|
||||
respect some constraint. Each model class must inherits
|
||||
=Model.Tools.SQLSubModel= abstract class, except the =Model.Study=
|
||||
class who inherits =Model.Tools.SQLModel= (see [[SQL]]).
|
||||
|
|
@ -456,12 +442,12 @@ of Bar (Listing [[sql-bar]] and [[sql-foo]]).
|
|||
bar._sql_save(execute, data=data)
|
||||
#+end_src
|
||||
|
||||
Let see the results database scheme for Pamhyr2 at version v0.0.7 in
|
||||
Let see the results database scheme for {{{pamhyr2}}} at version v0.0.7 in
|
||||
Figure [[sql_schema]].
|
||||
|
||||
#+NAME: sql_schema
|
||||
#+ATTR_LATEX: :width 16cm
|
||||
#+CAPTION: SQLite database scheme at Pamhyr2 version v0.0.7 (generate with [[https://gitlab.com/Screwtapello/sqlite-schema-diagram]])
|
||||
#+CAPTION: SQLite database scheme at {{{pamhyr2}}} version v0.0.7 (generate with [[https://gitlab.com/Screwtapello/sqlite-schema-diagram]])
|
||||
[[./images/schema_v0.0.7.png]]
|
||||
|
||||
|
||||
|
|
@ -486,7 +472,7 @@ PamhyrModelList but use a dictionary instead of list.
|
|||
|
||||
** View
|
||||
|
||||
Pamhyr2 use Qt as graphical user interface library with the
|
||||
{{{pamhyr2}}} use Qt as graphical user interface library with the
|
||||
application "Qt designer" for windows or widget creation (see [[UI file]])
|
||||
and "Qt linguist" for interface translate (see [[Translate]]). In
|
||||
addition, we use matplotlib as ploting library (see [[Plot]]).
|
||||
|
|
@ -501,11 +487,11 @@ componant translate, and possible other files or sub-directories.
|
|||
|
||||
*** UI file
|
||||
|
||||
We define as possible all Pamhyr2 windows and custom widgets with "Qt
|
||||
designer". This application generate UI file who describes interface
|
||||
organisation with table, layout, button, etc. This method is faster
|
||||
than hand made windows and widget creation, and saves us some purely
|
||||
descriptive code. The UI files are saved into =src/View/ui= for
|
||||
We define as possible all {{{pamhyr2}}} windows and custom widgets
|
||||
with "Qt designer". This application generate UI file who describes
|
||||
interface organisation with table, layout, button, etc. This method is
|
||||
faster than hand made windows and widget creation, and saves us some
|
||||
purely descriptive code. The UI files are saved into =src/View/ui= for
|
||||
window, and =/src/View/ui/Widgets= for custom widget.
|
||||
|
||||
*** Translate
|
||||
|
|
@ -533,10 +519,11 @@ window, and =/src/View/ui/Widgets= for custom widget.
|
|||
|
||||
*** Window
|
||||
|
||||
The abstract class PamhyrWindow and PamhyrDialog are used for most of
|
||||
Pamhyr2 window. These class allow to create an window for Pamhyr2 GUI
|
||||
and implemente some useful methods. The super class method difine some
|
||||
generic value from optional parameters, for examples:
|
||||
The abstract class =PamhyrWindow= and =PamhyrDialog= are used for most
|
||||
of {{{pamhyr2}}} window. These class allow to create an window for
|
||||
{{{pamhyr2}}} GUI and implemente some useful methods. The super class
|
||||
method difine some generic value from optional parameters, for
|
||||
examples:
|
||||
- =self._study=: The study giving in constructor parameters =study=
|
||||
(typically a =Model.Study= class object)
|
||||
- =self._config=: The configuration giving in constructor parameters
|
||||
|
|
@ -546,7 +533,7 @@ generic value from optional parameters, for examples:
|
|||
object)
|
||||
|
||||
#+NAME: window
|
||||
#+CAPTION: Example of Pamhyr2 window
|
||||
#+CAPTION: Example of {{{pamhyr2}}} window
|
||||
#+begin_src python :python python3 :results output :noweb yes
|
||||
from View.Tools.PamhyrWindow import PamhyrWindow
|
||||
from View.My.Translate import MyTranslate
|
||||
|
|
@ -593,7 +580,7 @@ window componants or connections.
|
|||
|
||||
*** Table
|
||||
|
||||
An abstract class PamhyrTableModel is available to define a simple
|
||||
An abstract class =PamhyrTableModel= is available to define a simple
|
||||
QAbstractTableModel shortly. In simple cases, there are only =data=
|
||||
and =setData= methode to implement, but the constructor needs more
|
||||
information than a classic QAbstractTableModel class.
|
||||
|
|
@ -654,7 +641,7 @@ class AddNodeCommand(QUndoCommand):
|
|||
|
||||
All undo command must be push into a =QUndoStack= (see Listing
|
||||
[[undo-cmd-push]]) to perform the action and allow user undo and redo this
|
||||
action. In PamhyrWindow (and PamhyrDialog) the undo stack is
|
||||
action. In =PamhyrWindow= (and =PamhyrDialog=) the undo stack is
|
||||
automatically create if the option ="undo"= is activate at window
|
||||
creation, this stack is accessible at =self._undo_stack=.
|
||||
|
||||
|
|
@ -672,7 +659,7 @@ creation, this stack is accessible at =self._undo_stack=.
|
|||
*** Plot
|
||||
|
||||
To define a new plot you can create a class who inherit to
|
||||
PamhyrPlot. The creator need at leaste five argument:
|
||||
=PamhyrPlot=. The creator need at leaste five argument:
|
||||
- A =canvas= of type =MplCanvas=
|
||||
- A (optional) =trad= of type =PamhyrTranslate=
|
||||
- A =data= used in =draw= and =update= to create and update the plot
|
||||
|
|
@ -719,8 +706,8 @@ data has changed.
|
|||
|
||||
** Solver
|
||||
|
||||
The Pamhyr2 architecture allow to define multiple solver. A solver is
|
||||
define by a:
|
||||
The {{{pamhyr2}}} architecture allow to define multiple solver. A
|
||||
solver is define by a:
|
||||
- type
|
||||
- name
|
||||
- description,
|
||||
|
|
@ -736,11 +723,11 @@ different solver type, this solver type implement the code for export
|
|||
study to solver input format, and read the solver output to study
|
||||
results. There exists a generic solver with a generic input and output
|
||||
format, the type could be use to use a solver not implemented in
|
||||
Pamhyr2, but this solver must can read/write input and output generic
|
||||
format or use external script. There is possible to define different
|
||||
solver with the same type, for example two differents version of the
|
||||
same solver. Finaly, with input and output formater is possible to
|
||||
execute a code on distant computer, for example, over ssh.
|
||||
{{{pamhyr2}}}, but this solver must can read/write input and output
|
||||
generic format or use external script. There is possible to define
|
||||
different solver with the same type, for example two differents
|
||||
version of the same solver. Finaly, with input and output formater is
|
||||
possible to execute a code on distant computer, for example, over ssh.
|
||||
|
||||
#+name: graph-multi-solver
|
||||
#+header: :results drawer
|
||||
|
|
@ -857,8 +844,8 @@ solver and get results:
|
|||
- (2.1) The solver read the input file(s)
|
||||
- (2.2) The solver compute results and write it to solver output
|
||||
file(s)
|
||||
- (3) Pamhyr2 create a =Results= object
|
||||
- (3.1) The Pamhyr2 solver class read solver output file(s) and
|
||||
- (3) {{{pamhyr2}}} create a =Results= object
|
||||
- (3.1) The {{{pamhyr2}}} solver class read solver output file(s) and
|
||||
complete Results with readed data
|
||||
|
||||
#+name: graph-pipeline
|
||||
|
|
@ -976,7 +963,7 @@ the temporal order of action is prensented in Figure
|
|||
}
|
||||
#+end_src
|
||||
|
||||
To implement a Solver in Pamhyr2, there exists a abstract class
|
||||
To implement a Solver in {{{pamhyr2}}}, there exists a abstract class
|
||||
=Solver.AbstractSolver=. A class who herits this class, must implement
|
||||
different methods:
|
||||
- =export=: Export the study to solver input file(s)
|
||||
|
|
@ -987,7 +974,7 @@ different methods:
|
|||
|
||||
** Unit tests
|
||||
|
||||
A very small part of Pamhyr2 has unit test. This part is limited to the Model.
|
||||
A very small part of {{{pamhyr2}}} has unit test. This part is limited to the Model.
|
||||
|
||||
#+begin_src shell
|
||||
python3 -m venv test
|
||||
|
|
@ -1000,9 +987,9 @@ A very small part of Pamhyr2 has unit test. This part is limited to the Model.
|
|||
|
||||
** The debug mode
|
||||
|
||||
To activate an deactivate the Pamhyr2 debug mode you can open the
|
||||
configuration window and type "Ctrl+G" or run Pamhyr2 with command
|
||||
line:
|
||||
To activate an deactivate the {{{pamhyr2}}} debug mode you can open
|
||||
the configuration window and type "Ctrl+G" or run {{{pamhyr2}}} with
|
||||
command line:
|
||||
#+begin_src shell
|
||||
./Pamhyr2 debug
|
||||
#+end_src
|
||||
|
|
@ -1015,7 +1002,7 @@ data base file.
|
|||
|
||||
#+NAME: debug-repl
|
||||
#+ATTR_LATEX: :width 14cm
|
||||
#+CAPTION: Pamhyr2 debug Python REPL
|
||||
#+CAPTION: {{{pamhyr2}}} debug Python REPL
|
||||
[[./images/python-debug-repl.png]]
|
||||
|
||||
* Build the project
|
||||
|
|
@ -1028,7 +1015,7 @@ to build packages manually.
|
|||
If you need an hand made package, you can script available in
|
||||
{{{file(packages)}}} directory.
|
||||
|
||||
*** GNU/Linux
|
||||
*** GNU/Linux {{{linux}}}
|
||||
|
||||
On GNU/Linux building GNU/Linux packages is easy, you just need python
|
||||
in version 3.8 must be installed with venv and pyinstaller packages
|
||||
|
|
@ -1050,7 +1037,7 @@ cd packages
|
|||
./linux.sh
|
||||
#+end_src
|
||||
|
||||
*** Windows
|
||||
*** Windows {{{windows}}}
|
||||
|
||||
To make the Windows packages you have two choice: If you use Windows
|
||||
you can use the script {{{file(packages/windows.bat)}}}, other else
|
||||
|
|
@ -1065,10 +1052,10 @@ winetricks installed.
|
|||
|
||||
** Setup the CI environment
|
||||
|
||||
Pamhyr2 need a Linux ci-runner and a Windows ci-runner for building
|
||||
{{{pamhyr2}}} need a Linux ci-runner and a Windows ci-runner for building
|
||||
package. The windows ci-runner could run on a Wine environement.
|
||||
|
||||
*** Linux
|
||||
*** Linux {{{linux}}}
|
||||
|
||||
The Linux ci-runner need some software and dependencies in addtion of
|
||||
gitlab-ci.
|
||||
|
|
@ -1078,10 +1065,11 @@ gitlab-ci.
|
|||
emacs emacs-goodies-el \
|
||||
texlive-full \
|
||||
python3.8 python3.8-venv
|
||||
|
||||
sudo python3 -m pip install pyinstaller
|
||||
#+end_src
|
||||
|
||||
*** Windows (Wine)
|
||||
*** Windows (Wine) {{{windows}}}
|
||||
|
||||
The ci-runner environment for Wine need at least wine version 8, let
|
||||
[[https://www.numetopia.fr/comment-installer-wine-sur-ubuntu-ou-linux-mint/][see who to add wine official depot to your linux distribution]].
|
||||
|
|
@ -1090,18 +1078,77 @@ The ci-runner environment for Wine need at least wine version 8, let
|
|||
sudo apt install wine-stable winetricks
|
||||
#+end_src
|
||||
|
||||
In addition, the environment need windows version of:
|
||||
- [[https://www.python.org/ftp/python/3.8.10/python-3.8.10-amd64.exe][Python 3.8.10]]
|
||||
- Git
|
||||
- PowerShell
|
||||
- Gitlab-ci
|
||||
**** Setup environment
|
||||
|
||||
Export Wine environment variable to set wine as 64 bits architecture
|
||||
and set the correct path for wine environment.
|
||||
|
||||
#+begin_src shell
|
||||
export WINARCH=win64
|
||||
export WINEPREFIX=$PWD/my-wine-runner-prefix
|
||||
#+end_src
|
||||
|
||||
Setup Wine environment to Windows 10 and install the minimal fonts
|
||||
with =winetricks=.
|
||||
|
||||
#+begin_src shell
|
||||
winetricks corefonts win10
|
||||
#+end_src
|
||||
|
||||
**** Install dependencies
|
||||
|
||||
First install 7zip with help of =winetricks=.
|
||||
|
||||
#+begin_src shell
|
||||
winetricks 7zip
|
||||
#+end_src
|
||||
|
||||
In addition, install in the environment the Windows version of:
|
||||
- [[https://www.python.org/ftp/python/3.8.10/python-3.8.10-amd64.exe][Python 3.8.10]] (ensure the python path is set and Pip is enable)
|
||||
- [[https://git-scm.com/downloads][Git]]
|
||||
- [[https://github.com/PowerShell/PowerShell/releases/download/v7.0.1/PowerShell-7.0.1-win-x64.msi][PowerShell]]
|
||||
- [[https://freefr.dl.sourceforge.net/project/nsis/NSIS%203/3.08/nsis-3.08-setup.exe][Nsis]]
|
||||
|
||||
To run a Windows executable into wine environement, use =wine64= command:
|
||||
|
||||
#+begin_src shell
|
||||
wine64 <the-exe-file>
|
||||
#+end_src
|
||||
|
||||
Now, we can install =pyinstaller= on this windows environment:
|
||||
#+begin_src shell
|
||||
wine python -m pip install pyinstaller
|
||||
#+end_src
|
||||
|
||||
Now, we can download [[https://docs.gitlab.com/runner/install/windows.html][Gitlab-ci]] runner for Windows an put it in the
|
||||
current path.
|
||||
|
||||
**** Setup runner
|
||||
|
||||
You can configure the runner with command:
|
||||
|
||||
#+begin_src shell
|
||||
wine64 gitlab-runner-windows-amd64.exe register
|
||||
#+end_src
|
||||
|
||||
**** Run the runner
|
||||
|
||||
Create a new executable shell script =runner.sh= with following lines:
|
||||
|
||||
#+begin_src shell
|
||||
#! /bin/sh
|
||||
|
||||
export WINARCH=win64
|
||||
export WINEPREFIX=$PWD/my-wine-runner-prefix
|
||||
|
||||
wine64 gitlab-runner-windows-amd64.exe run
|
||||
#+end_src
|
||||
|
||||
Now you can run the runner with command =./runner.sh=.
|
||||
|
||||
{{{bulb}}} You can run this command into a =screen= terminal, detach
|
||||
the terminal and disconnect from runner machine to keep runner alive.
|
||||
|
||||
* Documentation files
|
||||
|
||||
This document and the user documentation are org files. This text file
|
||||
|
|
@ -1284,23 +1331,23 @@ Some org-mode configuration used in documentations files are define in
|
|||
|
||||
* How to contribute?
|
||||
|
||||
Pamhyr2 is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License[fn:license], either
|
||||
version 3 of the License, or any later version.
|
||||
{{{pamhyr2}}} is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License[fn:license],
|
||||
either version 3 of the License, or any later version.
|
||||
|
||||
[fn:license] The GPLv3 web page:
|
||||
https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
||||
** Guidelines
|
||||
|
||||
To contribute to Pamhyr2, we expect a minimum of respect between
|
||||
To contribute to {{{pamhyr2}}}, we expect a minimum of respect between
|
||||
contributors. We therefore ask you to respect the following rules
|
||||
regarding communication and contribution content:
|
||||
+ No gender, racial, religious or social discrimination
|
||||
+ No insults, personal attacks or potentially offensive remarks
|
||||
+ Pamhyr2 is free software, and intended to remain so, so take care
|
||||
with the licensing of libraries and external content you want to add
|
||||
to the project
|
||||
+ {{{pamhyr2}}} is free software, and intended to remain so, so take
|
||||
care with the licensing of libraries and external content you want
|
||||
to add to the project
|
||||
+ Humour or hidden easter eggs are welcome if they respect the
|
||||
previous rules
|
||||
|
||||
|
|
@ -1311,11 +1358,12 @@ an issue on the project's gitlab page[fn:p2-gitlab], or you can create
|
|||
a merge request on the same page with the changes you have made to the
|
||||
code, translation or documentation.
|
||||
|
||||
The Pamhyr2 copyright is owned by INRAE[fn:inrae], but we keep a
|
||||
record of each contributors. If you made a modification to pamhyr2
|
||||
software, please add your name at the end of {{{file(AUTHORS)}}} file
|
||||
and respect the Listing [[auth-format]] format. You can update this file
|
||||
information for following contribution.
|
||||
The {{{pamhyr2}}} copyright is owned by INRAE[fn:inrae], but we keep a
|
||||
record of each contributors. If you made a modification to
|
||||
{{{pamhyr2}}} software, please add your name at the end of
|
||||
{{{file(AUTHORS)}}} file and respect the Listing [[auth-format]]
|
||||
format. You can update this file information for following
|
||||
contribution.
|
||||
|
||||
#+NAME: auth-format
|
||||
#+CAPTION: =AUTHORS= file format
|
||||
|
|
@ -1326,16 +1374,17 @@ information for following contribution.
|
|||
#+CAPTION: Current =AUTHORS= file
|
||||
#+INCLUDE: "../../AUTHORS" src text
|
||||
|
||||
[fn:p2-gitlab] The Pamhyr2 Gitlab project page:
|
||||
[fn:p2-gitlab] The {{{pamhyr2}}} Gitlab project page:
|
||||
https://gitlab.irstea.fr/theophile.terraz/pamhyr
|
||||
[fn:inrae] The INRAE web site: https://www.inrae.fr/
|
||||
|
||||
** Translate
|
||||
|
||||
You can improve or add translation for the project. To contribute to
|
||||
Pamhyr2 translate, you need to use Qt Linguist[fn:qt-linguist]. Open
|
||||
Qt-linguist and edit the translation ({{{file(.ts)}}}) file, finally,
|
||||
commit the new version of file and make a merge request.
|
||||
{{{pamhyr2}}} translate, you need to use Qt
|
||||
Linguist[fn:qt-linguist]. Open Qt-linguist and edit the translation
|
||||
({{{file(.ts)}}}) file, finally, commit the new version of file and
|
||||
make a merge request.
|
||||
|
||||
If you want add a new language, edit the script
|
||||
{{{file(src/lang/create_ts.sh)}}} like Listing [[ts-it]]. Run the script
|
||||
|
|
@ -1344,7 +1393,7 @@ and open the new file with Qt-linguist, setup target language (Figure
|
|||
file and make a merge request.
|
||||
|
||||
#+NAME: ts-it
|
||||
#+CAPTION: Example of modified {{{file(src/lang/create_ts.sh)}}} to add italian (it) translate for Pamhyr2
|
||||
#+CAPTION: Example of modified {{{file(src/lang/create_ts.sh)}}} to add italian (it) translate for {{{pamhyr2}}}
|
||||
#+begin_src shell
|
||||
...
|
||||
LANG="fr it"
|
||||
|
|
@ -1362,7 +1411,7 @@ https://doc.qt.io/qt-5/qtlinguist-index.html (last access 2023-09-18)
|
|||
** Code contribution
|
||||
|
||||
If you are developper you can improve and/or add features to
|
||||
Pamhyr2. Please, follow the architecture described in section
|
||||
{{{pamhyr2}}}. Please, follow the architecture described in section
|
||||
[[Architecture]] as closely as possible. Keep the code simple, clear and
|
||||
efficient as possible. The master branch is reserved for the project
|
||||
maintainer; you can create a new branch or fork the project before the
|
||||
|
|
|
|||
|
|
@ -163,3 +163,7 @@
|
|||
\SetWatermarkScale{3}
|
||||
\SetWatermarkFontSize{1cm}
|
||||
\SetWatermarkText{Work in progress}
|
||||
|
||||
%% Icons
|
||||
|
||||
\usepackage{fontawesome5}
|
||||
|
|
|
|||
|
|
@ -17,11 +17,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Constant name
|
||||
#+MACRO: oldPamhyr PAMHyR
|
||||
#+MACRO: Pamhyr Pamhyr2
|
||||
#+MACRO: Mage Mage
|
||||
#+MACRO: Rubarbe RubarBE
|
||||
#+MACRO: Inrae INRAE
|
||||
#+MACRO: pamhyr \textsc{PAMHyR}
|
||||
#+MACRO: pamhyr2 \textsc{Pamhyr2}
|
||||
#+MACRO: mage \textsc{Mage}
|
||||
#+MACRO: mage7 \textsc{Mage7}
|
||||
#+MACRO: mage8 \textsc{Mage8}
|
||||
#+MACRO: rubarbe \textsc{RubarBE}
|
||||
#+MACRO: adists \textsc{AdisTS}
|
||||
#+MACRO: mascaret \textsc{Mascaret}
|
||||
#+MACRO: inrae INRAE
|
||||
#+MACRO: latex \LaTeX
|
||||
|
||||
# Information
|
||||
|
|
@ -35,6 +39,16 @@
|
|||
#+MACRO: cite [cite:$1]
|
||||
#+MACRO: biblio \bibliography{documentation}
|
||||
|
||||
# Icons
|
||||
#+MACRO: bulb \faIcon{lightbulb}
|
||||
#+MACRO: OK \faIcon{check}
|
||||
|
||||
#+MACRO: linux \faIcon{linux}
|
||||
#+MACRO: windows \faIcon{windows}
|
||||
|
||||
#+MACRO: python \faIcon{python}
|
||||
#+MACRO: java \faIcon{java}
|
||||
|
||||
# Wrapper
|
||||
#+NAME: attr_wrap
|
||||
#+HEADER: :var width="\\textwidth"
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
$Clapet_001 clapet
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 53 KiB |
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
%% LyX 2.0.2 created this file. For more info, see http://www.lyx.org/.
|
||||
%% Do not edit unless you really know what you are doing.
|
||||
\documentclass[12pt,french]{article}
|
||||
|
|
@ -8,6 +9,7 @@
|
|||
\usepackage{textcomp}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{hyperref}
|
||||
\usepackage[frenchb]{babel}
|
||||
|
||||
\makeatletter
|
||||
|
||||
|
|
@ -132,17 +134,17 @@ Pendant que vous travaillez sur votre
|
|||
|
||||
\section{Créer la structure de la rivière}
|
||||
|
||||
Cliquez sur \texttt{[Réseau] => [Éditer le réseau]} ou sur l'icône \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/network.png} pour créer la structure de votre rivière.
|
||||
Cliquez sur \texttt{[Réseau] => [Modifier le réseau]} ou sur l'icône \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/network.png} pour créer la structure de votre rivière.
|
||||
Nous voici dans la fenêtre \textit{Réseau}.
|
||||
Dans cette fenêtre, nous allons définir un graphe orienté qui représente les biefs de notre réseau fluvial : les arêtes sont les biefs, les n½uds sont soit des conditions limites amont, soit des conditions limites aval, soit des jonctions entre biefs.
|
||||
Dans cette fenêtre, nous allons définir un graphe orienté qui représente les biefs de notre réseau fluvial : les arêtes sont les biefs, les n\oe{}uds sont soit des conditions limites amont, soit des conditions limites aval, soit des jonctions entre biefs.
|
||||
Un bief par défaut existe dans la nouvelle étude.
|
||||
Pour les besoins de ce tutoriel, nous allons le supprimer :
|
||||
cliquez sur le bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/del.png} pour entrer dans le mode \textit{Suppression} puis cliquez sur les n½uds.
|
||||
cliquez sur le bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/del.png} pour entrer dans le mode \textit{Suppression} puis cliquez sur les n\oe{}uds.
|
||||
Nous voilà repartis sur une fenêtre vierge.
|
||||
Appuyez sur le bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/add.png} pour entrer dans le mode \textit{Ajout}. Créez deux n½uds en cliquant dans la zone grise de la fenêtre, et créez un lien en cliquant à nouveau sur chaque n½ud.
|
||||
Appuyez sur le bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/add.png} pour entrer dans le mode \textit{Ajout}. Créez deux n\oe{}uds en cliquant dans la zone grise de la fenêtre, et créez un lien en cliquant à nouveau sur chaque n\oe{}ud.
|
||||
Appuyez à nouveau sur \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/add.png} pour quitter le mode \textit{Ajout}.
|
||||
Vous avez créé votre premier bief, avec un n½ud amont et un n½ud aval.
|
||||
Dans la partie inférieure de la fenêtre \textit{Réseau}, vous pouvez renommer les n½uds et les biefs.
|
||||
Vous avez créé votre premier bief, avec un n\oe{}ud amont et un n\oe{}ud aval.
|
||||
Dans la partie inférieure de la fenêtre \textit{Réseau}, vous pouvez renommer les n\oe{}uds et les biefs.
|
||||
Comme le bief que nous avons créé est automatiquement sélectionné, toutes les étapes suivantes s'appliqueront à ce bief.
|
||||
La fenêtre doit se présenter comme suit :
|
||||
|
||||
|
|
@ -154,7 +156,7 @@ Fermez la fen
|
|||
|
||||
\section{Éditer la géométrie de la rivière}
|
||||
|
||||
Cliquez sur \texttt{[Géométrie] => [Éditer la géométrie]} ou sur l'icône \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/geometry.png} pour définir la géométrie du bief sélectionné.
|
||||
Cliquez sur \texttt{[Géométrie] => [Modifier la géométrie]} ou sur l'icône \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/geometry.png} pour définir la géométrie du bief sélectionné.
|
||||
Pour Importer une géométrie depuis un fichier, cliquez sur le bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/import.png}.
|
||||
Sélectionnez le fichier \texttt{Data/Bief\_1.st}.
|
||||
Vous devriez voir :
|
||||
|
|
@ -237,8 +239,8 @@ Nous pouvons cliquer sur la nouvelle ligne pour s
|
|||
Ici, nous définirons le débit mesuré lors de la crue de février 2002.
|
||||
Sélectionnez la cellule \textit{Nom} pour donner un nom à la condition limite par exemple "crue2002".
|
||||
Sélectionner la cellule \textit{Type} et utiliser la combo box pour mettre une loi \textit{Q(t)} : débit en fonction du temps (hydrogramme).
|
||||
Sélectionnez la cellule \textit{N½ud} et attribuez cette condition au n½ud amont.
|
||||
Les noms des n½uds sont rappelés dans le panneau de droite, avec une vue du réseau.
|
||||
Sélectionnez la cellule \textit{n\oe{}ud} et attribuez cette condition au n\oe{}ud amont.
|
||||
Les noms des n\oe{}uds sont rappelés dans le panneau de droite, avec une vue du réseau.
|
||||
Sélectionnez maintenant la ligne entière et cliquez sur le bouton d'édition \includegraphics[width=0.5cm]{"../../../src/View/ui/ressources/edit.png"}.
|
||||
Vous avez ouvert la fenêtre \textit{Éditer les conditions aux limites}.
|
||||
Dans un éditeur de texte, ouvrez le fichier \texttt{data/Fevrier\_2002.txt}.
|
||||
|
|
@ -250,7 +252,7 @@ Vous pouvez maintenant voir la courbe de d
|
|||
\par\end{center}
|
||||
|
||||
Fermez cette fenêtre. Revenez sur la fenêtre \textit{Conditions aux limites}.
|
||||
Ajoutez une nouvelle ligne, donnez lui un nom, donnez lui le type \textit{Z(T)} (limnigramme) et associez là au n½ud aval du réseau.
|
||||
Ajoutez une nouvelle ligne, donnez lui un nom, donnez lui le type \textit{Z(T)} (limnigramme) et associez là au n\oe{}ud aval du réseau.
|
||||
Ouvrez la fenêtre d'édition des conditions aux limites (\includegraphics[width=0.5cm]{"../../../src/View/ui/ressources/edit.png"}).
|
||||
Ajoutez deux lignes et rentrez les valeurs suivantes :
|
||||
\begin{center}
|
||||
|
|
@ -271,7 +273,7 @@ Vous pouvez fermer les fen
|
|||
% Cette condition limite se trouve au niveau d'un seuil.
|
||||
% A cet endroit, l'écoulement passe d'un régime fluvial à un régime torentiel.
|
||||
% Nous allons donc calculer une courbe de tarage qui correspond au régime critique de l'écoulement au niveau du seuil.
|
||||
% Sélectionnez la condition limite et ouvrez la fenêtre \textit{Éditer les conditions aux limites} : (\includegraphics[width=0.5cm]{"../../../src/View/ui/ressources/edit.png"}).
|
||||
% Sélectionnez la condition limite et ouvrez la fenêtre \textit{Éditer les conditions aux limites} : (\includegraphics[width=0.5cm]{"../../../src/View/ui/ressources/edit.png"}).
|
||||
% Dans la fenêtre \textit{Éditer les conditions aux limites} cliquez sur \texttt{[Générer régime critique]} pour générer cette courbe.
|
||||
% Cliquez ensuite sur \texttt{[Rendre croissant]} pour suprimer les points de la courbe qui ne sont pas strictement croissants.
|
||||
% Vous pouvez fermer les fenêtres \textit{Éditer les conditions aux limites} et \textit{Conditions aux limites}.
|
||||
|
|
@ -400,19 +402,19 @@ Si vous r
|
|||
|
||||
\section{Paramètres du solveur}
|
||||
|
||||
Dans la fenêtre principale, cliquez sur \texttt{[Exécuter] => [Parameters numériques des solveurs]}.
|
||||
Dans la fenêtre principale, cliquez sur \texttt{[Exécuter] => [Paramètres numériques des solveurs]}.
|
||||
Dans la fenêtre \textit{Paramètres du solveur}, sélectionnez l'onglet \texttt{[Mage v8]}.
|
||||
Ces paramètres pilotent le comportement du solveur numérique.
|
||||
la valeur 999:99:00:00 du temps final indique au solveur de s'arrêter lorsqu'il a atteint un régime permanent.
|
||||
la valeur {\NoAutoSpacing 999:99:00:00} du temps final indique au solveur de s'arrêter lorsqu'il a atteint un régime permanent.
|
||||
Vous pouvez changer la fréquence d'écriture des résultats dans la ligne \textit{Pas de temps d'écriture dans le fichier .BIN}.
|
||||
Une valeur inférieure à 1 seconde indique que la valeur de la ligne \textit{Pas de temps d'écriture dans le fichier .TRA} s sera prise à la place.
|
||||
Une valeur inférieure à 1 seconde indique que la valeur de la ligne \textit{Pas de temps d'écriture dans le fichier .TRA} s sera prise à la place.
|
||||
% En effet, durant la montée du pic de crue, le solveur a besoin de réduire le pas de temps suffisament pour permettre la convergence des itérations.
|
||||
Pour accélérer les calculs et pour aider le solveur à démarer, nous allons l'autoriser à dégrader la précision, à l'aide des facteurs de réduction de la précision.
|
||||
Pour accélérer les calculs et pour aider le solveur à démarrer, nous allons l'autoriser à dégrader la précision, à l'aide des facteurs de réduction de la précision.
|
||||
Les précisions internes du solveur sont de 10$^{-9}$.
|
||||
Cette précision est multipliée par le facteur de réduction de la précision : un facteur de 1000 ramènera donc la précision à 10$^{-5}$.
|
||||
Pour utiliser ce facteur de réduction de la précision, il faut donner un \textit{nombre d'itérations à précision maximum} inférieur au \textit{nombre maximum d'itérations} : le solveur va d'abord tenter de converger avec un certain nombre d'itératons à la précision maximum avant de basculer sur une précision dégradée pour le reste des itérations.
|
||||
Dans notre cas, c'est nécessaire pour lancer le solveur à partir de la condition initiale calculée par Pamhyr2.
|
||||
rentrez 1000 dans les trois lignes \textit{facteurs de réduction de la précision}, rentrez 99 pour le \textit{Nombre d'itérations} et 5 pour le \textit{nombre d'itérations à la précision maximum}.
|
||||
Rentrez 1000 dans les trois lignes \textit{facteurs de réduction de la précision}, rentrez 99 pour le \textit{Nombre maximum d'itérations} et 5 pour le \textit{nombre d'itérations à la précision maximum}.
|
||||
Gardez les autres paramètres du solveur par défaut.
|
||||
Fermer la fenêtre \textit{Paramètres du solveur}.
|
||||
|
||||
|
|
@ -426,12 +428,13 @@ Cette fen
|
|||
|
||||
\section{Visualiser les résultats}
|
||||
|
||||
il est aussi possible d'ouvrir la fenêtre \textit{Résultats} si vous avez fermé la fenêtre \textit{Log du solveur}, en cliquant sur \texttt{[Résultats] => [Visualiser les derniers résultats]} à partir de la fenêtre principale.
|
||||
Il est aussi possible d'ouvrir la fenêtre \textit{Résultats} si vous avez fermé la fenêtre \textit{Log du solveur}, en cliquant sur \texttt{[Résultats] => [Visualiser les derniers résultats]} à partir de la fenêtre principale.
|
||||
Le panneau supérieur vous permet de sélectionner le bief, le panneau inférieur gauche vous permet de sélectionner une section dans ce bief.
|
||||
Les trois diagrammes sur la droite montrent le bief et la section en travers de la même manière que dans la fenêtre \textit{Géométrie}.
|
||||
Vous pouvez utiliser le curseur du bas pour visualiser les résultats à différents pas de temps.
|
||||
Les croix rouges dans les deux vues du haut correspondent aux points ou l'eau déborde de la géométrie au moins une fois dans la simulation.
|
||||
En pratique il n'y a pas de perte de volume par débordement en dehors du modèle, car le solveur ajoute un mur virtuel aux extrémités des sections.
|
||||
|
||||
Pour visualiser le débit, passez à l'onglet \textit{Hydrogramme}.
|
||||
Pour créer des tracés 2D personnalisés, cliquez sur le bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/add.png} en haut à gauche de la fenêtre.
|
||||
Sélectionnez les valeurs que vous voulez sur les axes $X$ et $Y$ et cliquez sur \texttt{[OK]}.
|
||||
|
|
@ -440,4 +443,3 @@ Le bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/export.p
|
|||
|
||||
\pagebreak{}
|
||||
\end{document}
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -360,26 +360,11 @@ class InitialConditions(SQLSubModel):
|
|||
def get_discharge(self):
|
||||
return self._data_get("discharge")
|
||||
|
||||
def _sort_by_z_and_rk(self, profiles):
|
||||
profiles.sort(
|
||||
reverse=False,
|
||||
key=lambda p: p.rk
|
||||
)
|
||||
|
||||
first_z = profiles[0].z()
|
||||
last_z = profiles[-1].z()
|
||||
|
||||
if first_z > last_z:
|
||||
profiles.sort(
|
||||
reverse=True,
|
||||
key=lambda p: p.rk
|
||||
)
|
||||
|
||||
def generate_growing_constant_depth(self, height: float,
|
||||
compute_discharge: bool):
|
||||
|
||||
profiles = self._reach.reach.profiles.copy()
|
||||
self._sort_by_z_and_rk(profiles)
|
||||
profiles.reverse()
|
||||
|
||||
previous_elevation = -99999.99
|
||||
|
||||
|
|
@ -433,12 +418,12 @@ class InitialConditions(SQLSubModel):
|
|||
previous_elevation = elevation
|
||||
self._data.append(new)
|
||||
|
||||
self._generate_resort_data(profiles)
|
||||
self._data.reverse()
|
||||
|
||||
def generate_discharge(self, discharge: float, compute_height: bool):
|
||||
|
||||
profiles = self._reach.reach.profiles.copy()
|
||||
self._sort_by_z_and_rk(profiles)
|
||||
profiles.reverse()
|
||||
|
||||
previous_elevation = -99999.99
|
||||
|
||||
|
|
@ -491,7 +476,7 @@ class InitialConditions(SQLSubModel):
|
|||
previous_elevation = elevation
|
||||
self._data.append(new)
|
||||
|
||||
self._generate_resort_data(profiles)
|
||||
self._data.reverse()
|
||||
|
||||
def generate_height(self,
|
||||
elevation1: float,
|
||||
|
|
@ -525,13 +510,3 @@ class InitialConditions(SQLSubModel):
|
|||
new["discharge"] = d
|
||||
new["elevation"] = elevation
|
||||
self._data.append(new)
|
||||
|
||||
def _generate_resort_data(self, profiles):
|
||||
is_reverse = False
|
||||
if profiles[0].rk > profiles[-1].rk:
|
||||
is_reverse = True
|
||||
|
||||
self._data.sort(
|
||||
reverse=not is_reverse,
|
||||
key=lambda d: d['rk']
|
||||
)
|
||||
|
|
|
|||
|
|
@ -99,7 +99,10 @@ class CommandLineSolver(AbstractSolver):
|
|||
params = study.river.get_params(self.type)
|
||||
args = params.get_by_key("all_command_line_arguments")
|
||||
|
||||
return args.split(" ")
|
||||
if args is None:
|
||||
return []
|
||||
else:
|
||||
return args.split(" ")
|
||||
|
||||
def input_param(self):
|
||||
"""Return input command line parameter(s)
|
||||
|
|
|
|||
|
|
@ -599,29 +599,50 @@ class Mage(CommandLineSolver):
|
|||
if qlog is not None:
|
||||
qlog.put("Export VAR file")
|
||||
|
||||
with mage_file_open(os.path.join(repertory, f"{name}.VAR"), "w+") as f:
|
||||
files.append(f"{name}.VAR")
|
||||
nb_cv = 0
|
||||
for hs in hydraulic_structures:
|
||||
if hs.input_reach is None:
|
||||
continue
|
||||
|
||||
for hs in hydraulic_structures:
|
||||
if hs.input_reach is None:
|
||||
continue
|
||||
if not hs.input_reach.is_enable():
|
||||
continue
|
||||
|
||||
if not hs.input_reach.is_enable():
|
||||
continue
|
||||
if not hs.enabled:
|
||||
continue
|
||||
|
||||
for bhs in hs.basic_structures:
|
||||
for bhs in hs.basic_structures:
|
||||
if bhs.enabled:
|
||||
logger.info(bhs._type)
|
||||
if bhs._type != "CV":
|
||||
if bhs._type == "CV":
|
||||
nb_cv += 1
|
||||
|
||||
if nb_cv != 0:
|
||||
with mage_file_open(os.path.join(
|
||||
repertory, f"{name}.VAR"), "w+") as f:
|
||||
files.append(f"{name}.VAR")
|
||||
|
||||
for hs in hydraulic_structures:
|
||||
if hs.input_reach is None:
|
||||
continue
|
||||
|
||||
name = bhs.name
|
||||
if name == "":
|
||||
name = f"HS_{bhs.id:>3}".replace(" ", "0")
|
||||
if not hs.input_reach.is_enable():
|
||||
continue
|
||||
|
||||
f.write(
|
||||
f"${name} clapet"
|
||||
)
|
||||
if not hs.enabled:
|
||||
continue
|
||||
|
||||
for bhs in hs.basic_structures:
|
||||
logger.info(bhs._type)
|
||||
if bhs._type != "CV":
|
||||
continue
|
||||
|
||||
name = bhs.name
|
||||
if name == "":
|
||||
name = f"HS_{bhs.id:>3}".replace(" ", "0")
|
||||
|
||||
f.write(
|
||||
f"${name} clapet"
|
||||
)
|
||||
return files
|
||||
|
||||
def _export_DEV(self, study, repertory, qlog, name="0"):
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class PlotRKZ(PamhyrPlot):
|
|||
self.line_rk_zmin_zmax_highlight = None
|
||||
|
||||
self.label_x = self._trad["unit_rk"]
|
||||
self.label_y = self._trad["unit_height"]
|
||||
self.label_y = self._trad["unit_depth"]
|
||||
|
||||
self.before_plot_selected = None
|
||||
self.plot_selected = None
|
||||
|
|
|
|||
|
|
@ -77,29 +77,29 @@ class PlotDRK(PamhyrPlot):
|
|||
rk = self.data.get_rk()
|
||||
elevation = self.data.get_elevation()
|
||||
|
||||
sorted_rk, sorted_elevation = zip(
|
||||
*sorted(zip(rk, elevation))
|
||||
)
|
||||
|
||||
self.line_rk_elevation = self.canvas.axes.plot(
|
||||
rk, elevation,
|
||||
sorted_rk, sorted_elevation,
|
||||
color=self.color_plot_river_water,
|
||||
**self.plot_default_kargs
|
||||
)
|
||||
|
||||
z_min = self.data.reach.reach.get_z_min()
|
||||
geometry_rk = self.data.reach.reach.get_rk()
|
||||
|
||||
filtred_elevation = list(
|
||||
map(
|
||||
lambda x: elevation[x[0]],
|
||||
filter(
|
||||
lambda x: x[1] in geometry_rk,
|
||||
enumerate(rk)
|
||||
)
|
||||
)
|
||||
sorted_geometry_rk, sorted_z_min = zip(
|
||||
*sorted(zip(geometry_rk, z_min), reverse=True)
|
||||
)
|
||||
|
||||
self.collection = self.canvas.axes.fill_between(
|
||||
geometry_rk, z_min, filtred_elevation,
|
||||
poly_x = sorted_rk + sorted_geometry_rk
|
||||
poly_y = sorted_elevation + sorted_z_min
|
||||
|
||||
self.collection = self.canvas.axes.fill(
|
||||
poly_x, poly_y,
|
||||
color=self.color_plot_river_water_zone,
|
||||
alpha=0.7, interpolate=True
|
||||
alpha=0.7,
|
||||
)
|
||||
|
||||
@timer
|
||||
|
|
|
|||
|
|
@ -104,15 +104,15 @@ class InitialConditionTableModel(PamhyrTableModel):
|
|||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
if self._headers[column] is "speed":
|
||||
if self._headers[column] == "velocity":
|
||||
z = self._lst.get(row)["elevation"]
|
||||
q = self._lst.get(row)["discharge"]
|
||||
profile = self._reach.reach.get_profiles_from_rk(
|
||||
self._lst.get(row)["rk"]
|
||||
)
|
||||
if len(profile) >= 1:
|
||||
speed = profile[0].speed(q, z)
|
||||
return f"{speed:.4f}"
|
||||
velocity = profile[0].speed(q, z)
|
||||
return f"{velocity:.4f}"
|
||||
|
||||
return ""
|
||||
elif self._headers[column] not in ["name", "comment"]:
|
||||
|
|
|
|||
|
|
@ -365,40 +365,43 @@ class InitialConditionsWindow(PamhyrWindow):
|
|||
self._update()
|
||||
|
||||
def generate_growing_constant_depth(self):
|
||||
dlg = DepthDialog(self.depth_value,
|
||||
self.depth_option,
|
||||
trad=self._trad,
|
||||
parent=self)
|
||||
if dlg.exec():
|
||||
self.depth_value = dlg.value
|
||||
self.depth_option = dlg.option
|
||||
self._table.generate("growing",
|
||||
self.depth_value,
|
||||
self.depth_option)
|
||||
self._update()
|
||||
|
||||
def generate_discharge(self):
|
||||
dlg = DischargeDialog(self.discharge_value,
|
||||
self.discharge_option,
|
||||
if self._reach.reach.number_profiles > 0:
|
||||
dlg = DepthDialog(self.depth_value,
|
||||
self.depth_option,
|
||||
trad=self._trad,
|
||||
parent=self)
|
||||
if dlg.exec():
|
||||
self.discharge_value = dlg.value
|
||||
self.discharge_option = dlg.option
|
||||
self._table.generate("discharge",
|
||||
self.discharge_value,
|
||||
self.discharge_option)
|
||||
self._update()
|
||||
if dlg.exec():
|
||||
self.depth_value = dlg.value
|
||||
self.depth_option = dlg.option
|
||||
self._table.generate("growing",
|
||||
self.depth_value,
|
||||
self.depth_option)
|
||||
self._update()
|
||||
|
||||
def generate_discharge(self):
|
||||
if self._reach.reach.number_profiles > 1:
|
||||
dlg = DischargeDialog(self.discharge_value,
|
||||
self.discharge_option,
|
||||
trad=self._trad,
|
||||
parent=self)
|
||||
if dlg.exec():
|
||||
self.discharge_value = dlg.value
|
||||
self.discharge_option = dlg.option
|
||||
self._table.generate("discharge",
|
||||
self.discharge_value,
|
||||
self.discharge_option)
|
||||
self._update()
|
||||
|
||||
def generate_height(self):
|
||||
dlg = HeightDialog(self.height_values,
|
||||
self.height_option,
|
||||
trad=self._trad,
|
||||
parent=self)
|
||||
if dlg.exec():
|
||||
self.height_values = dlg.values
|
||||
self.height_option = dlg.option
|
||||
self._table.generate("height",
|
||||
self.height_values,
|
||||
self.height_option)
|
||||
self._update()
|
||||
if self._reach.reach.number_profiles > 0:
|
||||
dlg = HeightDialog(self.height_values,
|
||||
self.height_option,
|
||||
trad=self._trad,
|
||||
parent=self)
|
||||
if dlg.exec():
|
||||
self.height_values = dlg.values
|
||||
self.height_option = dlg.option
|
||||
self._table.generate("height",
|
||||
self.height_values,
|
||||
self.height_option)
|
||||
self._update()
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class ICTranslate(MainTranslate):
|
|||
"rk": self._dict["unit_rk"],
|
||||
"discharge": self._dict["unit_discharge"],
|
||||
"elevation": self._dict["unit_elevation"],
|
||||
"height": self._dict["unit_height"],
|
||||
"speed": self._dict["unit_speed"],
|
||||
"height": self._dict["unit_depth"],
|
||||
"velocity": self._dict["unit_velocity"],
|
||||
# "comment": _translate("InitialCondition", "Comment"),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,13 @@ import logging
|
|||
|
||||
from tools import timer, trace
|
||||
|
||||
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
|
||||
from View.Tools.Plot.PamhyrCanvas import MplCanvas
|
||||
from View.PlotXY import PlotXY
|
||||
from View.Tools.PamhyrWidget import PamhyrWidget
|
||||
|
||||
from PyQt5.QtWidgets import QVBoxLayout
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
|
|
@ -38,7 +43,9 @@ class WidgetInfo(PamhyrWidget):
|
|||
parent=parent
|
||||
)
|
||||
|
||||
self.parent = parent
|
||||
self.set_initial_values()
|
||||
self.setup_graph()
|
||||
|
||||
@property
|
||||
def study(self):
|
||||
|
|
@ -64,6 +71,25 @@ class WidgetInfo(PamhyrWidget):
|
|||
self.set_label_text("label_lc", "-")
|
||||
self.set_label_text("label_hs", "-")
|
||||
|
||||
def setup_graph(self):
|
||||
self.canvas = MplCanvas(width=5, height=4, dpi=100)
|
||||
self.canvas.setObjectName("canvas")
|
||||
self.plot_layout_xy = self.find(QVBoxLayout, "verticalLayout")
|
||||
self._toolbar_xy = PamhyrPlotToolbar(
|
||||
self.canvas, self,
|
||||
items=["home", "zoom", "save", "iso", "back/forward", "move"]
|
||||
)
|
||||
self.plot_layout_xy.addWidget(self._toolbar_xy)
|
||||
self.plot_layout_xy.addWidget(self.canvas)
|
||||
|
||||
self.plot = PlotXY(
|
||||
canvas=self.canvas,
|
||||
data=None,
|
||||
trad=self.parent._trad,
|
||||
toolbar=self._toolbar_xy,
|
||||
parent=self
|
||||
)
|
||||
|
||||
def update(self):
|
||||
if self._study is None:
|
||||
self.set_initial_values()
|
||||
|
|
@ -75,6 +101,15 @@ class WidgetInfo(PamhyrWidget):
|
|||
self.set_network_values()
|
||||
self.set_geometry_values()
|
||||
|
||||
self.plot = PlotXY(
|
||||
canvas=self.canvas,
|
||||
data=self._study.river.enable_edges(),
|
||||
trad=self.parent._trad,
|
||||
toolbar=self._toolbar_xy,
|
||||
parent=self
|
||||
)
|
||||
self.plot.update()
|
||||
|
||||
def set_network_values(self):
|
||||
river = self._study.river
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
# PlotXY.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.PamhyrPlot import PamhyrPlot
|
||||
from matplotlib import collections
|
||||
import numpy as np
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
QCoreApplication, Qt, QItemSelectionModel,
|
||||
QItemSelection, QItemSelectionRange,
|
||||
)
|
||||
from PyQt5.QtWidgets import QApplication, QTableView
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
|
||||
class PlotXY(PamhyrPlot):
|
||||
def __init__(self, canvas=None, trad=None, data=None, toolbar=None,
|
||||
table=None, parent=None):
|
||||
super(PlotXY, self).__init__(
|
||||
canvas=canvas,
|
||||
trad=trad,
|
||||
data=data,
|
||||
toolbar=toolbar,
|
||||
table=table,
|
||||
parent=parent
|
||||
)
|
||||
|
||||
self._data = data
|
||||
self.label_x = self._trad["x"]
|
||||
self.label_y = self._trad["y"]
|
||||
self.parent = parent
|
||||
|
||||
self._isometric_axis = True
|
||||
|
||||
self._auto_relim_update = True
|
||||
self._autoscale_update = True
|
||||
|
||||
@timer
|
||||
def draw(self):
|
||||
self.init_axes()
|
||||
|
||||
if self._data is None:
|
||||
self.idle()
|
||||
return
|
||||
|
||||
if len(self._data) < 1:
|
||||
self.idle()
|
||||
return
|
||||
|
||||
self.line_lr = []
|
||||
for data in self._data:
|
||||
if data.reach.number_profiles != 0:
|
||||
self.draw_xy(data.reach)
|
||||
self.draw_lr(data.reach)
|
||||
self.idle()
|
||||
return
|
||||
|
||||
def draw_xy(self, reach):
|
||||
line_xy = []
|
||||
for xy in zip(reach.get_x(), reach.get_y()):
|
||||
line_xy.append(np.column_stack(xy))
|
||||
|
||||
line_xy_collection = collections.LineCollection(
|
||||
line_xy,
|
||||
colors=self.color_plot_river_bottom
|
||||
)
|
||||
self.canvas.axes.add_collection(line_xy_collection)
|
||||
|
||||
def draw_lr(self, reach):
|
||||
lx = []
|
||||
ly = []
|
||||
rx = []
|
||||
ry = []
|
||||
|
||||
for x, y in zip(reach.get_x(),
|
||||
reach.get_y()):
|
||||
lx.append(x[0])
|
||||
ly.append(y[0])
|
||||
|
||||
rx.append(x[-1])
|
||||
ry.append(y[-1])
|
||||
|
||||
line = self.canvas.axes.plot(
|
||||
lx, ly,
|
||||
color=self.color_plot_river_bottom,
|
||||
linestyle="dotted",
|
||||
lw=1.,
|
||||
)
|
||||
self.line_lr.append(line)
|
||||
|
||||
line = self.canvas.axes.plot(
|
||||
rx, ry,
|
||||
color=self.color_plot_river_bottom,
|
||||
linestyle="dotted",
|
||||
lw=1.,
|
||||
)
|
||||
self.line_lr.append(line)
|
||||
|
||||
@timer
|
||||
def update(self):
|
||||
self.draw()
|
||||
self.update_idle()
|
||||
|
|
@ -49,8 +49,8 @@ class Plot(PamhyrPlot):
|
|||
self._mode = mode
|
||||
self._table_headers = self._trad.get_dict("table_headers")
|
||||
|
||||
self.label_x = self._table_headers["z"]
|
||||
self.label_y = self._table_headers["area"]
|
||||
self.label_x = self._table_headers["area"]
|
||||
self.label_y = self._table_headers["z"]
|
||||
|
||||
self._isometric_axis = False
|
||||
|
||||
|
|
@ -71,8 +71,8 @@ class Plot(PamhyrPlot):
|
|||
self._init = True
|
||||
|
||||
def draw_data(self):
|
||||
x = list(map(lambda v: v[0], self.data.data))
|
||||
y = list(map(lambda v: v[1], self.data.data))
|
||||
x = list(map(lambda v: v[1], self.data.data))
|
||||
y = list(map(lambda v: v[0], self.data.data))
|
||||
self._line, = self.canvas.axes.plot(
|
||||
x, y,
|
||||
color=self.color_plot,
|
||||
|
|
@ -89,7 +89,7 @@ class Plot(PamhyrPlot):
|
|||
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))
|
||||
x = list(map(lambda v: v[1], self.data.data))
|
||||
y = list(map(lambda v: v[0], self.data.data))
|
||||
|
||||
self._line.set_data(x, y)
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ class CustomPlotValuesSelectionDialog(PamhyrDialog):
|
|||
self._available_values_y = self._trad.get_dict("values_y")
|
||||
|
||||
self.setup_radio_buttons()
|
||||
self.setup_envelop_box()
|
||||
self.setup_check_boxs()
|
||||
|
||||
self.value = None
|
||||
|
|
@ -61,6 +62,24 @@ class CustomPlotValuesSelectionDialog(PamhyrDialog):
|
|||
self._radio[0][1].setChecked(True)
|
||||
layout.addStretch()
|
||||
|
||||
def setup_envelop_box(self):
|
||||
self._envelop = []
|
||||
layout = self.find(QVBoxLayout, "verticalLayout_x")
|
||||
self._envelop = QCheckBox(
|
||||
"envelop",
|
||||
parent=self
|
||||
)
|
||||
layout.addWidget(self._envelop)
|
||||
self._envelop.setChecked(True)
|
||||
for r in self._radio:
|
||||
r[1].clicked.connect(self.envelop_box_status)
|
||||
|
||||
def envelop_box_status(self):
|
||||
if self._radio[0][1].isChecked():
|
||||
self._envelop.setEnabled(True)
|
||||
else:
|
||||
self._envelop.setEnabled(False)
|
||||
|
||||
def setup_check_boxs(self):
|
||||
self._check = []
|
||||
layout = self.find(QVBoxLayout, "verticalLayout_y")
|
||||
|
|
@ -94,6 +113,6 @@ class CustomPlotValuesSelectionDialog(PamhyrDialog):
|
|||
)
|
||||
)
|
||||
|
||||
self.value = x, y
|
||||
self.value = x, y, self._envelop.isChecked()
|
||||
|
||||
super().accept()
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import logging
|
|||
from functools import reduce
|
||||
from datetime import datetime
|
||||
from numpy import sqrt
|
||||
from numpy import asarray
|
||||
|
||||
from tools import timer
|
||||
from View.Tools.PamhyrPlot import PamhyrPlot
|
||||
|
|
@ -30,11 +31,16 @@ from View.Results.CustomPlot.Translate import CustomPlotTranslate
|
|||
logger = logging.getLogger()
|
||||
|
||||
unit = {
|
||||
"elevation": "0-meter",
|
||||
"bed_elevation": "0-meter",
|
||||
"bed_elevation_envelop": "0-meter",
|
||||
"water_elevation": "0-meter",
|
||||
"water_elevation_envelop": "0-meter",
|
||||
"discharge": "1-m3s",
|
||||
"discharge_envelop": "1-m3s",
|
||||
"velocity": "2-ms",
|
||||
"max_depth": "3-meter",
|
||||
"velocity_envelop": "2-ms",
|
||||
"depth": "3-meter",
|
||||
"depth_envelop": "3-meter",
|
||||
"mean_depth": "3-meter",
|
||||
"froude": "4-dimensionless",
|
||||
"wet_area": "5-m2",
|
||||
|
|
@ -42,7 +48,7 @@ unit = {
|
|||
|
||||
|
||||
class CustomPlot(PamhyrPlot):
|
||||
def __init__(self, x, y, reach, profile, timestamp,
|
||||
def __init__(self, x, y, envelop, reach, profile, timestamp,
|
||||
data=None, canvas=None, trad=None,
|
||||
toolbar=None, parent=None):
|
||||
super(CustomPlot, self).__init__(
|
||||
|
|
@ -55,6 +61,7 @@ class CustomPlot(PamhyrPlot):
|
|||
|
||||
self._x = x
|
||||
self._y = y
|
||||
self._envelop = envelop
|
||||
self._reach = reach
|
||||
self._profile = profile
|
||||
self._timestamp = timestamp
|
||||
|
|
@ -77,11 +84,97 @@ class CustomPlot(PamhyrPlot):
|
|||
|
||||
self._axes = {}
|
||||
|
||||
def draw_bottom_with_bedload(self, reach):
|
||||
self._bedrock = self.sl_compute_bedrock(reach)
|
||||
|
||||
rk = reach.geometry.get_rk()
|
||||
z = self.sl_compute_current_z(reach)
|
||||
|
||||
return z
|
||||
|
||||
def sl_compute_current_z(self, reach):
|
||||
z_br = self._bedrock
|
||||
sl = self.sl_compute_current_rk(reach)
|
||||
|
||||
z = list(
|
||||
map(
|
||||
lambda z, sl: reduce(
|
||||
lambda z, h: z + h[0],
|
||||
sl, z
|
||||
),
|
||||
z_br, # Bedrock elevation
|
||||
sl # Current sediment layers
|
||||
)
|
||||
)
|
||||
|
||||
return z
|
||||
|
||||
def sl_compute_bedrock(self, reach):
|
||||
z_min = reach.geometry.get_z_min()
|
||||
sl = self.sl_compute_initial(reach)
|
||||
|
||||
z = list(
|
||||
map(
|
||||
lambda z, sl: reduce(
|
||||
lambda z, h: z - h[0],
|
||||
sl, z
|
||||
),
|
||||
z_min, # Original geometry
|
||||
sl # Original sediment layers
|
||||
)
|
||||
)
|
||||
|
||||
return z
|
||||
|
||||
def sl_compute_initial(self, reach):
|
||||
"""
|
||||
Get SL list for profile p at initial time (initial data)
|
||||
"""
|
||||
t0 = min(list(self.data.get("timestamps")))
|
||||
return map(
|
||||
lambda p: p.get_ts_key(t0, "sl")[0],
|
||||
reach.profiles
|
||||
)
|
||||
|
||||
def sl_compute_current_rk(self, reach):
|
||||
"""
|
||||
Get SL list for profile p at current time
|
||||
"""
|
||||
return map(
|
||||
lambda p: p.get_ts_key(self._timestamp, "sl")[0],
|
||||
reach.profiles
|
||||
)
|
||||
|
||||
def get_ts_zmin(self, profile):
|
||||
results = self.data
|
||||
nt = len(list(results.get("timestamps")))
|
||||
reach = results.river.reach(self._reach)
|
||||
berdrock = self.sl_compute_bedrock(reach)
|
||||
sl = reach.profile(profile).get_key("sl")
|
||||
|
||||
ts_z_bedrock = [berdrock[profile]]*nt
|
||||
|
||||
ts_z_min = list(
|
||||
map(
|
||||
lambda z, sl: reduce(
|
||||
lambda z, h: z + h,
|
||||
sl, z
|
||||
),
|
||||
ts_z_bedrock, # Bedrock elevations
|
||||
asarray(sl)[:,0,:,0] # Sediment layers
|
||||
)
|
||||
)
|
||||
return ts_z_min
|
||||
|
||||
def _draw_rk(self):
|
||||
results = self.data
|
||||
reach = results.river.reach(self._reach)
|
||||
rk = reach.geometry.get_rk()
|
||||
z_min = reach.geometry.get_z_min()
|
||||
if reach.has_sediment():
|
||||
z_min = self.draw_bottom_with_bedload(reach)
|
||||
else:
|
||||
z_min = reach.geometry.get_z_min()
|
||||
|
||||
q = list(
|
||||
map(
|
||||
lambda p: p.get_ts_key(self._timestamp, "Q"),
|
||||
|
|
@ -105,15 +198,45 @@ class CustomPlot(PamhyrPlot):
|
|||
self._axes[ax].spines['right'].set_position(('outward', shift))
|
||||
shift += 60
|
||||
|
||||
lines = {}
|
||||
if "elevation" in self._y:
|
||||
self.lines = {}
|
||||
if "bed_elevation" in self._y:
|
||||
|
||||
ax = self._axes[unit["elevation"]]
|
||||
ax = self._axes[unit["bed_elevation"]]
|
||||
line = ax.plot(
|
||||
rk, z_min,
|
||||
color='grey', lw=1.,
|
||||
)
|
||||
lines["elevation"] = line
|
||||
self.lines["bed_elevation"] = line
|
||||
|
||||
if self._envelop and reach.has_sediment():
|
||||
|
||||
ax = self._axes[unit["bed_elevation_envelop"]]
|
||||
|
||||
e = list(
|
||||
map(
|
||||
lambda p: max(self.get_ts_zmin(p)),
|
||||
range(len(reach))
|
||||
)
|
||||
)
|
||||
line1 = ax.plot(
|
||||
rk, e,
|
||||
color='grey', lw=1.,
|
||||
linestyle='dotted',
|
||||
)
|
||||
self.lines["bed_elevation_envelop"] = line1
|
||||
|
||||
e = list(
|
||||
map(
|
||||
lambda p: min(self.get_ts_zmin(p)),
|
||||
range(len(reach))
|
||||
)
|
||||
)
|
||||
line2 = ax.plot(
|
||||
rk, e,
|
||||
color='grey', lw=1.,
|
||||
linestyle='dotted',
|
||||
)
|
||||
# self.lines["bed_elevation_envelop"] = line2
|
||||
|
||||
if "water_elevation" in self._y:
|
||||
|
||||
|
|
@ -122,14 +245,46 @@ class CustomPlot(PamhyrPlot):
|
|||
rk, z, lw=1.,
|
||||
color='blue',
|
||||
)
|
||||
lines["water_elevation"] = line
|
||||
self.lines["water_elevation"] = line
|
||||
|
||||
if "elevation" in self._y:
|
||||
ax.fill_between(
|
||||
if "bed_elevation" in self._y:
|
||||
self.fill = ax.fill_between(
|
||||
rk, z_min, z,
|
||||
color='blue', alpha=0.5, interpolate=True
|
||||
)
|
||||
|
||||
if self._envelop:
|
||||
|
||||
ax = self._axes[unit["water_elevation_envelop"]]
|
||||
|
||||
d = list(
|
||||
map(
|
||||
lambda p: max(p.get_key("Z")),
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
|
||||
line1 = ax.plot(
|
||||
rk, d, lw=1.,
|
||||
color='blue',
|
||||
linestyle='dotted',
|
||||
)
|
||||
self.lines["water_elevation_envelop"] = line1
|
||||
|
||||
d = list(
|
||||
map(
|
||||
lambda p: min(p.get_key("Z")),
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
|
||||
line2 = ax.plot(
|
||||
rk, d, lw=1.,
|
||||
color='blue',
|
||||
linestyle='dotted',
|
||||
)
|
||||
# self.lines["water_elevation_envelop2"] = line2
|
||||
|
||||
if "discharge" in self._y:
|
||||
|
||||
ax = self._axes[unit["discharge"]]
|
||||
|
|
@ -137,7 +292,37 @@ class CustomPlot(PamhyrPlot):
|
|||
rk, q, lw=1.,
|
||||
color='r',
|
||||
)
|
||||
lines["discharge"] = line
|
||||
self.lines["discharge"] = line
|
||||
|
||||
if self._envelop:
|
||||
|
||||
ax = self._axes[unit["discharge_envelop"]]
|
||||
|
||||
q1 = list(
|
||||
map(
|
||||
lambda p: max(p.get_key("Q")),
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
line1 = ax.plot(
|
||||
rk, q1, lw=1.,
|
||||
color='r',
|
||||
linestyle='dotted',
|
||||
)
|
||||
self.lines["discharge_envelop"] = line1
|
||||
|
||||
q2 = list(
|
||||
map(
|
||||
lambda p: min(p.get_key("Q")),
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
line2 = ax.plot(
|
||||
rk, q2, lw=1.,
|
||||
color='r',
|
||||
linestyle='dotted',
|
||||
)
|
||||
# self.lines["discharge_envelop2"] = line2
|
||||
|
||||
if "velocity" in self._y:
|
||||
|
||||
|
|
@ -155,11 +340,40 @@ class CustomPlot(PamhyrPlot):
|
|||
rk, v, lw=1.,
|
||||
color='g',
|
||||
)
|
||||
lines["velocity"] = line
|
||||
self.lines["velocity"] = line
|
||||
|
||||
if "max_depth" in self._y:
|
||||
if self._envelop:
|
||||
|
||||
ax = self._axes[unit["max_depth"]]
|
||||
velocities = list(map(
|
||||
lambda p: list(map(
|
||||
lambda q, z:
|
||||
p.geometry.speed(q, z),
|
||||
p.get_key("Q"), p.get_key("Z")
|
||||
)), reach.profiles
|
||||
)
|
||||
)
|
||||
|
||||
ax = self._axes[unit["velocity_envelop"]]
|
||||
vmax = [max(v) for v in velocities]
|
||||
|
||||
line1 = ax.plot(
|
||||
rk, vmax, lw=1.,
|
||||
color='g',
|
||||
linestyle='dotted',
|
||||
)
|
||||
self.lines["velocity_envelop"] = line1
|
||||
vmin = [min(v) for v in velocities]
|
||||
|
||||
line2 = ax.plot(
|
||||
rk, vmin, lw=1.,
|
||||
color='g',
|
||||
linestyle='dotted',
|
||||
)
|
||||
# self.lines["velocity_envelop2"] = line2
|
||||
|
||||
if "depth" in self._y:
|
||||
|
||||
ax = self._axes[unit["depth"]]
|
||||
d = list(
|
||||
map(
|
||||
lambda p: p.geometry.max_water_depth(
|
||||
|
|
@ -171,7 +385,37 @@ class CustomPlot(PamhyrPlot):
|
|||
rk, d,
|
||||
color='brown', lw=1.,
|
||||
)
|
||||
lines["max_depth"] = line
|
||||
self.lines["depth"] = line
|
||||
|
||||
if self._envelop:
|
||||
|
||||
ax = self._axes[unit["depth_envelop"]]
|
||||
|
||||
d = list(map(
|
||||
lambda p1, p2: p1 - p2, map(
|
||||
lambda p: max(p.get_key("Z")),
|
||||
reach.profiles
|
||||
), z_min)
|
||||
)
|
||||
line1 = ax.plot(
|
||||
rk, d,
|
||||
color='brown', lw=1.,
|
||||
linestyle='dotted',
|
||||
)
|
||||
self.lines["depth_envelop"] = line1
|
||||
|
||||
d = list(map(
|
||||
lambda p1, p2: p1 - p2, map(
|
||||
lambda p: min(p.get_key("Z")),
|
||||
reach.profiles
|
||||
), z_min)
|
||||
)
|
||||
line2 = ax.plot(
|
||||
rk, d,
|
||||
color='brown', lw=1.,
|
||||
linestyle='dotted',
|
||||
)
|
||||
# self.lines["depth_envelop2"] = line2
|
||||
|
||||
if "mean_depth" in self._y:
|
||||
|
||||
|
|
@ -188,7 +432,7 @@ class CustomPlot(PamhyrPlot):
|
|||
rk, d,
|
||||
color='orange', lw=1.,
|
||||
)
|
||||
lines["mean_depth"] = line
|
||||
self.lines["mean_depth"] = line
|
||||
|
||||
if "froude" in self._y:
|
||||
|
||||
|
|
@ -212,7 +456,7 @@ class CustomPlot(PamhyrPlot):
|
|||
line = ax.plot(
|
||||
rk, fr, color='black', linestyle='--', lw=1.,
|
||||
)
|
||||
lines["froude"] = line
|
||||
self.lines["froude"] = line
|
||||
|
||||
if "wet_area" in self._y:
|
||||
|
||||
|
|
@ -229,17 +473,115 @@ class CustomPlot(PamhyrPlot):
|
|||
rk, d,
|
||||
color='blue', linestyle='--', lw=1.,
|
||||
)
|
||||
lines["wet_area"] = line
|
||||
self.lines["wet_area"] = line
|
||||
|
||||
# Legend
|
||||
lns = reduce(
|
||||
lambda acc, line: acc + line,
|
||||
map(lambda line: lines[line], lines),
|
||||
map(lambda line: self.lines[line], self.lines),
|
||||
[]
|
||||
)
|
||||
labs = list(map(lambda line: self._trad[line], lines))
|
||||
labs = list(map(lambda line: self._trad[line], self.lines))
|
||||
self.canvas.axes.legend(lns, labs, loc="best")
|
||||
|
||||
def _redraw_rk(self):
|
||||
results = self.data
|
||||
reach = results.river.reach(self._reach)
|
||||
rk = reach.geometry.get_rk()
|
||||
z_min = reach.geometry.get_z_min()
|
||||
if reach.has_sediment():
|
||||
z_min = self.draw_bottom_with_bedload(reach)
|
||||
else:
|
||||
z_min = reach.geometry.get_z_min()
|
||||
|
||||
q = list(
|
||||
map(
|
||||
lambda p: p.get_ts_key(self._timestamp, "Q"),
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
z = list(
|
||||
map(
|
||||
lambda p: p.get_ts_key(self._timestamp, "Z"),
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
if "bed_elevation" in self._y:
|
||||
self.lines["bed_elevation"][0].set_ydata(z_min)
|
||||
|
||||
if "water_elevation" in self._y:
|
||||
self.lines["water_elevation"][0].set_ydata(z)
|
||||
|
||||
if "bed_elevation" in self._y:
|
||||
ax = self._axes[unit["water_elevation"]]
|
||||
self.fill.remove()
|
||||
self.fill = ax.fill_between(
|
||||
rk, z_min, z,
|
||||
color='blue', alpha=0.5, interpolate=True
|
||||
)
|
||||
|
||||
if "discharge" in self._y:
|
||||
self.lines["discharge"][0].set_ydata(q)
|
||||
|
||||
if "velocity" in self._y:
|
||||
v = list(
|
||||
map(
|
||||
lambda p: p.geometry.speed(
|
||||
p.get_ts_key(self._timestamp, "Q"),
|
||||
p.get_ts_key(self._timestamp, "Z")),
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
self.lines["discharge"][0].set_ydata(v)
|
||||
|
||||
if "depth" in self._y:
|
||||
d = list(
|
||||
map(
|
||||
lambda p: p.geometry.max_water_depth(
|
||||
p.get_ts_key(self._timestamp, "Z")),
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
self.lines["depth"][0].set_ydata(d)
|
||||
|
||||
if "mean_depth" in self._y:
|
||||
d = list(
|
||||
map(
|
||||
lambda p: p.geometry.mean_water_depth(
|
||||
p.get_ts_key(self._timestamp, "Z")),
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
self.lines["mean_depth"][0].set_ydata(d)
|
||||
|
||||
if "froude" in self._y:
|
||||
fr = list(
|
||||
map(
|
||||
lambda p:
|
||||
p.geometry.speed(
|
||||
p.get_ts_key(self._timestamp, "Q"),
|
||||
p.get_ts_key(self._timestamp, "Z")) /
|
||||
sqrt(9.81 * (
|
||||
p.geometry.wet_area(
|
||||
p.get_ts_key(self._timestamp, "Z")) /
|
||||
p.geometry.wet_width(
|
||||
p.get_ts_key(self._timestamp, "Z"))
|
||||
)),
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
self.lines["froude"][0].set_ydata(fr)
|
||||
|
||||
if "wet_area" in self._y:
|
||||
d = list(
|
||||
map(
|
||||
lambda p: p.geometry.wet_area(
|
||||
p.get_ts_key(self._timestamp, "Z")),
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
self.lines["wet_area"][0].set_ydata(d)
|
||||
|
||||
def _customize_x_axes_time(self, ts, mode="time"):
|
||||
# Custom time display
|
||||
nb = len(ts)
|
||||
|
|
@ -301,24 +643,27 @@ class CustomPlot(PamhyrPlot):
|
|||
q = profile.get_key("Q")
|
||||
z = profile.get_key("Z")
|
||||
z_min = profile.geometry.z_min()
|
||||
ts_z_min = list(
|
||||
map(
|
||||
lambda ts: z_min,
|
||||
ts
|
||||
if reach.has_sediment():
|
||||
ts_z_min = self.get_ts_zmin(self._profile)
|
||||
else:
|
||||
ts_z_min = list(
|
||||
map(
|
||||
lambda ts: z_min,
|
||||
ts
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
lines = {}
|
||||
if "elevation" in self._y:
|
||||
self.lines = {}
|
||||
if "bed_elevation" in self._y:
|
||||
# Z min is constant in time
|
||||
|
||||
ax = self._axes[unit["elevation"]]
|
||||
ax = self._axes[unit["bed_elevation"]]
|
||||
|
||||
line = ax.plot(
|
||||
ts, ts_z_min,
|
||||
color='grey', lw=1.
|
||||
)
|
||||
lines["elevation"] = line
|
||||
self.lines["bed_elevation"] = line
|
||||
|
||||
if "water_elevation" in self._y:
|
||||
|
||||
|
|
@ -327,11 +672,11 @@ class CustomPlot(PamhyrPlot):
|
|||
ts, z, lw=1.,
|
||||
color='b',
|
||||
)
|
||||
lines["water_elevation"] = line
|
||||
self.lines["water_elevation"] = line
|
||||
|
||||
if "elevation" in self._y:
|
||||
if "bed_elevation" in self._y:
|
||||
|
||||
ax.fill_between(
|
||||
self.fill = ax.fill_between(
|
||||
ts, ts_z_min, z,
|
||||
color='blue', alpha=0.5, interpolate=True
|
||||
)
|
||||
|
|
@ -343,7 +688,7 @@ class CustomPlot(PamhyrPlot):
|
|||
ts, q, lw=1.,
|
||||
color='r',
|
||||
)
|
||||
lines["discharge"] = line
|
||||
self.lines["discharge"] = line
|
||||
|
||||
if "velocity" in self._y:
|
||||
|
||||
|
|
@ -359,11 +704,11 @@ class CustomPlot(PamhyrPlot):
|
|||
ts, v, lw=1.,
|
||||
color='g',
|
||||
)
|
||||
lines["velocity"] = line
|
||||
self.lines["velocity"] = line
|
||||
|
||||
if "max_depth" in self._y:
|
||||
if "depth" in self._y:
|
||||
|
||||
ax = self._axes[unit["max_depth"]]
|
||||
ax = self._axes[unit["depth"]]
|
||||
d = list(
|
||||
map(lambda z: profile.geometry.max_water_depth(z), z)
|
||||
)
|
||||
|
|
@ -372,7 +717,7 @@ class CustomPlot(PamhyrPlot):
|
|||
ts, d,
|
||||
color='brown', lw=1.,
|
||||
)
|
||||
lines["max_depth"] = line
|
||||
self.lines["depth"] = line
|
||||
|
||||
if "mean_depth" in self._y:
|
||||
|
||||
|
|
@ -385,7 +730,7 @@ class CustomPlot(PamhyrPlot):
|
|||
ts, d,
|
||||
color='orange', lw=1.,
|
||||
)
|
||||
lines["mean_depth"] = line
|
||||
self.lines["mean_depth"] = line
|
||||
|
||||
if "froude" in self._y:
|
||||
|
||||
|
|
@ -402,7 +747,7 @@ class CustomPlot(PamhyrPlot):
|
|||
line = ax.plot(
|
||||
ts, d, color='black', linestyle='--', lw=1.,
|
||||
)
|
||||
lines["froude"] = line
|
||||
self.lines["froude"] = line
|
||||
|
||||
if "wet_area" in self._y:
|
||||
|
||||
|
|
@ -414,21 +759,108 @@ class CustomPlot(PamhyrPlot):
|
|||
line = ax.plot(
|
||||
ts, d, color='blue', linestyle='--', lw=1.,
|
||||
)
|
||||
lines["wet_area"] = line
|
||||
self.lines["wet_area"] = line
|
||||
|
||||
self._customize_x_axes_time(ts)
|
||||
|
||||
# Legend
|
||||
lns = reduce(
|
||||
lambda acc, line: acc + line,
|
||||
map(lambda line: lines[line], lines),
|
||||
map(lambda line: self.lines[line], self.lines),
|
||||
[]
|
||||
)
|
||||
labs = list(map(lambda line: self._trad[line], lines))
|
||||
labs = list(map(lambda line: self._trad[line], self.lines))
|
||||
self.canvas.axes.legend(lns, labs, loc="best")
|
||||
|
||||
@timer
|
||||
def _redraw_time(self):
|
||||
|
||||
results = self.data
|
||||
reach = results.river.reach(self._reach)
|
||||
profile = reach.profile(self._profile)
|
||||
ts = list(results.get("timestamps"))
|
||||
ts.sort()
|
||||
|
||||
q = profile.get_key("Q")
|
||||
z = profile.get_key("Z")
|
||||
if reach.has_sediment():
|
||||
ts_z_min = self.get_ts_zmin(self._profile)
|
||||
else:
|
||||
z_min = profile.geometry.z_min()
|
||||
ts_z_min = list(
|
||||
map(
|
||||
lambda ts: z_min,
|
||||
ts
|
||||
)
|
||||
)
|
||||
if "bed_elevation" in self._y:
|
||||
self.lines["bed_elevation"][0].set_ydata(ts_z_min)
|
||||
|
||||
if "water_elevation" in self._y:
|
||||
self.lines["water_elevation"][0].set_ydata(z)
|
||||
|
||||
if "bed_elevation" in self._y:
|
||||
ax = self._axes[unit["bed_elevation"]]
|
||||
self.fill.remove()
|
||||
self.fill = ax.fill_between(
|
||||
ts, ts_z_min, z,
|
||||
color='blue', alpha=0.5, interpolate=True
|
||||
)
|
||||
|
||||
if "discharge" in self._y:
|
||||
self.lines["discharge"][0].set_ydata(q)
|
||||
|
||||
if "velocity" in self._y:
|
||||
v = list(
|
||||
map(
|
||||
lambda q, z: profile.geometry.speed(q, z),
|
||||
q, z
|
||||
)
|
||||
)
|
||||
self.lines["velocity"][0].set_ydata(v)
|
||||
|
||||
if "depth" in self._y:
|
||||
d = list(
|
||||
map(lambda z: profile.geometry.max_water_depth(z), z)
|
||||
)
|
||||
self.lines["depth"][0].set_ydata(d)
|
||||
|
||||
if "mean_depth" in self._y:
|
||||
d = list(
|
||||
map(lambda z: profile.geometry.mean_water_depth(z), z)
|
||||
)
|
||||
self.lines["mean_depth"][0].set_ydata(d)
|
||||
|
||||
if "froude" in self._y:
|
||||
d = list(
|
||||
map(lambda z, q:
|
||||
profile.geometry.speed(q, z) /
|
||||
sqrt(9.81 * (
|
||||
profile.geometry.wet_area(z) /
|
||||
profile.geometry.wet_width(z))
|
||||
), z, q)
|
||||
)
|
||||
self.lines["froude"][0].set_ydata(d)
|
||||
|
||||
if "wet_area" in self._y:
|
||||
d = list(
|
||||
map(lambda z: profile.geometry.wet_area(z), z)
|
||||
)
|
||||
self.lines["wet_area"][0].set_ydata(d)
|
||||
|
||||
self.canvas.axes.relim(visible_only=True)
|
||||
self.canvas.axes.autoscale_view()
|
||||
|
||||
def draw(self):
|
||||
self.draw_static()
|
||||
|
||||
def draw_update(self):
|
||||
if self._x == "rk":
|
||||
self._redraw_rk()
|
||||
elif self._x == "time":
|
||||
self._redraw_time()
|
||||
|
||||
@timer
|
||||
def draw_static(self):
|
||||
self.canvas.axes.cla()
|
||||
self.canvas.axes.grid(color='grey', linestyle='--', linewidth=0.5)
|
||||
|
||||
|
|
@ -478,16 +910,16 @@ class CustomPlot(PamhyrPlot):
|
|||
lw=1.,
|
||||
)
|
||||
|
||||
self.canvas.figure.canvas.draw_idle()
|
||||
if self.toolbar is not None:
|
||||
self.toolbar.update()
|
||||
self.canvas.draw_idle()
|
||||
|
||||
@timer
|
||||
def update(self):
|
||||
if not self._init:
|
||||
self.draw()
|
||||
self.draw_current()
|
||||
return
|
||||
self.draw_update()
|
||||
self.draw_current()
|
||||
# self.draw_static()
|
||||
return
|
||||
|
||||
def set_reach(self, reach_id):
|
||||
self._reach = reach_id
|
||||
|
|
@ -519,4 +951,5 @@ class CustomPlot(PamhyrPlot):
|
|||
elif self._x == "time":
|
||||
x = self._timestamp
|
||||
self._current.set_data([x, x], self.canvas.axes.get_ylim())
|
||||
self.canvas.figure.canvas.draw_idle()
|
||||
self.canvas.draw_idle()
|
||||
|
||||
|
|
|
|||
|
|
@ -36,14 +36,21 @@ class CustomPlotTranslate(ResultsTranslate):
|
|||
self._dict['time'] = self._dict["unit_time_s"]
|
||||
self._dict['rk'] = self._dict["unit_rk"]
|
||||
self._dict['water_elevation'] = self._dict["unit_water_elevation"]
|
||||
self._dict['water_elevation_envelop'] = self._dict[
|
||||
"unit_water_elevation_envelop"
|
||||
]
|
||||
self._dict['discharge'] = self._dict["unit_discharge"]
|
||||
self._dict['elevation'] = _translate(
|
||||
"CustomPlot", "Bed elevation (m)"
|
||||
)
|
||||
self._dict['velocity'] = self._dict["unit_speed"]
|
||||
self._dict['discharge_envelop'] = self._dict["unit_discharge_envelop"]
|
||||
self._dict['bed_elevation'] = self._dict["unit_bed_elevation"]
|
||||
self._dict['bed_elevation_envelop'] = self._dict[
|
||||
"unit_bed_elevation_envelop"
|
||||
]
|
||||
self._dict['velocity'] = self._dict["unit_velocity"]
|
||||
self._dict['width'] = self._dict["unit_width"]
|
||||
self._dict['max_depth'] = self._dict["unit_max_height"]
|
||||
self._dict['mean_depth'] = self._dict["unit_mean_height"]
|
||||
self._dict['velocity_envelop'] = self._dict["unit_velocity_envelop"]
|
||||
self._dict['depth'] = self._dict["unit_depth"]
|
||||
self._dict['depth_envelop'] = self._dict["unit_depth_envelop"]
|
||||
self._dict['mean_depth'] = self._dict["unit_mean_depth"]
|
||||
self._dict['wet_area'] = self._dict["unit_wet_area"]
|
||||
self._dict['wet_perimeter'] = self._dict["unit_wet_perimeter"]
|
||||
self._dict['hydraulic_radius'] = self._dict["unit_hydraulic_radius"]
|
||||
|
|
@ -55,7 +62,7 @@ class CustomPlotTranslate(ResultsTranslate):
|
|||
"CustomPlot", "Elevation (m)"
|
||||
)
|
||||
self._dict['1-m3s'] = self._dict["unit_discharge"]
|
||||
self._dict['2-ms'] = self._dict["unit_speed"]
|
||||
self._dict['3-meter'] = self._dict["unit_height"]
|
||||
self._dict['2-ms'] = self._dict["unit_velocity"]
|
||||
self._dict['3-meter'] = self._dict["unit_depth"]
|
||||
self._dict['4-dimensionless'] = self._dict["unit_froude"]
|
||||
self._dict['5-m2'] = self._dict["wet_area"]
|
||||
|
|
|
|||
|
|
@ -146,8 +146,9 @@ class PlotXY(PamhyrPlot):
|
|||
return
|
||||
|
||||
reach = self.results.river.reach(self._current_reach_id)
|
||||
reaches = self.results.river.reachs
|
||||
|
||||
self.draw_profiles(reach)
|
||||
self.draw_profiles(reach, reaches)
|
||||
self.draw_water_elevation(reach)
|
||||
self.draw_water_elevation_max(reach)
|
||||
self.draw_guide_lines(reach)
|
||||
|
|
@ -156,12 +157,12 @@ class PlotXY(PamhyrPlot):
|
|||
self.idle()
|
||||
self._init = True
|
||||
|
||||
def draw_profiles(self, reach):
|
||||
def draw_profiles(self, reach, reaches):
|
||||
if reach.geometry.number_profiles == 0:
|
||||
self._init = False
|
||||
return
|
||||
|
||||
self.line_xy = []
|
||||
# TODO uncomment to draw all the reaches
|
||||
# self.draw_other_profiles(reaches)
|
||||
for xy in zip(reach.geometry.get_x(),
|
||||
reach.geometry.get_y()):
|
||||
self.line_xy.append(np.column_stack(xy))
|
||||
|
|
@ -176,6 +177,19 @@ class PlotXY(PamhyrPlot):
|
|||
)
|
||||
self.canvas.axes.add_collection(self.line_xy_collection)
|
||||
|
||||
def draw_other_profiles(self, reaches):
|
||||
|
||||
for reach in reaches:
|
||||
for xy in zip(reach.geometry.get_x(),
|
||||
reach.geometry.get_y()):
|
||||
self.line_xy.append(np.column_stack(xy))
|
||||
|
||||
self.line_xy_collection = collections.LineCollection(
|
||||
self.line_xy,
|
||||
colors=self.color_plot_river_bottom,
|
||||
)
|
||||
self.canvas.axes.add_collection(self.line_xy_collection)
|
||||
|
||||
def draw_guide_lines(self, reach):
|
||||
x_complete = reach.geometry.get_guidelines_x()
|
||||
y_complete = reach.geometry.get_guidelines_y()
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ class TableModel(PamhyrTableModel):
|
|||
z = self._lst[row].get_ts_key(self._timestamp, "Z")
|
||||
v = self._lst[row].geometry.wet_width(z)
|
||||
return f"{v:.4f}"
|
||||
elif self._headers[column] == "max_depth":
|
||||
elif self._headers[column] == "depth":
|
||||
z = self._lst[row].get_ts_key(self._timestamp, "Z")
|
||||
v = self._lst[row].geometry.max_water_depth(z)
|
||||
return f"{v:.4f}"
|
||||
|
|
|
|||
|
|
@ -490,11 +490,13 @@ class ResultsWindow(PamhyrWindow):
|
|||
def _add_custom_plot(self):
|
||||
dlg = CustomPlotValuesSelectionDialog(parent=self)
|
||||
if dlg.exec():
|
||||
x, y = dlg.value
|
||||
self.create_new_tab_custom_plot(x, y)
|
||||
x, y, envelop = dlg.value
|
||||
self.create_new_tab_custom_plot(x, y, envelop)
|
||||
|
||||
def create_new_tab_custom_plot(self, x: str, y: list):
|
||||
def create_new_tab_custom_plot(self, x: str, y: list, envelop: bool):
|
||||
name = f"{x}: {','.join(y)}"
|
||||
if envelop and x == "rk":
|
||||
name += "_envelop"
|
||||
wname = f"tab_custom_{x}_{y}"
|
||||
|
||||
tab_widget = self.find(QTabWidget, f"tabWidget")
|
||||
|
|
@ -518,7 +520,7 @@ class ResultsWindow(PamhyrWindow):
|
|||
)
|
||||
|
||||
plot = CustomPlot(
|
||||
x, y,
|
||||
x, y, envelop,
|
||||
self._get_current_reach(),
|
||||
self._get_current_profile(),
|
||||
self._get_current_timestamp(),
|
||||
|
|
@ -589,7 +591,7 @@ class ResultsWindow(PamhyrWindow):
|
|||
|
||||
dlg = CustomPlotValuesSelectionDialog(parent=self)
|
||||
if dlg.exec():
|
||||
x, y = dlg.value
|
||||
x, y, envelop = dlg.value
|
||||
else:
|
||||
return
|
||||
|
||||
|
|
@ -599,32 +601,39 @@ class ResultsWindow(PamhyrWindow):
|
|||
)
|
||||
self.file_dialog(
|
||||
select_file="AnyFile",
|
||||
callback=lambda f: self.export_to(f[0], x, y),
|
||||
callback=lambda f: self.export_to(f[0], x, y, envelop),
|
||||
default_suffix=".csv",
|
||||
file_filter=["CSV (*.csv)"],
|
||||
)
|
||||
|
||||
def export_to(self, filename, x, y):
|
||||
def export_to(self, filename, x, y, envelop):
|
||||
timestamps = sorted(self._results.get("timestamps"))
|
||||
reach = self._results.river.reachs[self._get_current_reach()]
|
||||
first_line = [f"Study: {self._results.study.name}",
|
||||
f"Reach: {reach.name}"]
|
||||
if x == "rk":
|
||||
timestamp = self._get_current_timestamp()
|
||||
val_dict = self._export_rk(timestamp, y, filename)
|
||||
first_line.append(f"Time: {timestamp}s")
|
||||
val_dict = self._export_rk(timestamp, y, envelop, filename)
|
||||
elif x == "time":
|
||||
profile = self._get_current_profile()
|
||||
val_dict = self._export_time(profile, y, filename)
|
||||
profile_id = self._get_current_profile()
|
||||
profile = reach.profile(profile_id)
|
||||
pname = profile.name if profile.name != "" else profile.rk
|
||||
first_line.append(f"Profile: {pname}")
|
||||
val_dict = self._export_time(profile_id, y, filename)
|
||||
|
||||
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")
|
||||
dict_y = self._trad.get_dict("values_y")
|
||||
header = [dict_x[x]]
|
||||
for text in y:
|
||||
header.append(dict_y[text])
|
||||
header = []
|
||||
writer.writerow(first_line)
|
||||
for text in val_dict.keys():
|
||||
header.append(text)
|
||||
writer.writerow(header)
|
||||
for row in range(len(val_dict[x])):
|
||||
line = [val_dict[x][row]]
|
||||
for var in y:
|
||||
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)
|
||||
|
||||
|
|
@ -666,28 +675,60 @@ class ResultsWindow(PamhyrWindow):
|
|||
self._additional_plot.pop(tab_widget.tabText(index))
|
||||
tab_widget.removeTab(index)
|
||||
|
||||
def _export_rk(self, timestamp, y, filename):
|
||||
def _export_rk(self, timestamp, y, envelop, filename):
|
||||
reach = self._results.river.reachs[self._get_current_reach()]
|
||||
dict_x = self._trad.get_dict("values_x")
|
||||
dict_y = self._trad.get_dict("values_y")
|
||||
if envelop:
|
||||
dict_y.update(self._trad.get_dict("values_y_envelop"))
|
||||
my_dict = {}
|
||||
my_dict["rk"] = reach.geometry.get_rk()
|
||||
if "elevation" in y:
|
||||
my_dict["elevation"] = reach.geometry.get_z_min()
|
||||
my_dict[dict_x["rk"]] = reach.geometry.get_rk()
|
||||
if "bed_elevation" in y:
|
||||
my_dict[dict_y["bed_elevation"]] = reach.geometry.get_z_min()
|
||||
#if envelop and reach.has_sediment():
|
||||
if "discharge" in y:
|
||||
my_dict["discharge"] = list(
|
||||
my_dict[dict_y["discharge"]] = list(
|
||||
map(
|
||||
lambda p: p.get_ts_key(timestamp, "Q"),
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
if envelop:
|
||||
my_dict[dict_y["min_discharge"]] = list(
|
||||
map(
|
||||
lambda p: min(p.get_key("Q")),
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
my_dict[dict_y["max_discharge"]] = list(
|
||||
map(
|
||||
lambda p: max(p.get_key("Q")),
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
if "water_elevation" in y:
|
||||
my_dict["water_elevation"] = list(
|
||||
my_dict[dict_y["water_elevation"]] = list(
|
||||
map(
|
||||
lambda p: p.get_ts_key(timestamp, "Z"),
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
if envelop:
|
||||
my_dict[dict_y["min_water_elevation"]] = list(
|
||||
map(
|
||||
lambda p: min(p.get_key("Z")),
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
my_dict[dict_y["max_water_elevation"]] = list(
|
||||
map(
|
||||
lambda p: max(p.get_key("Z")),
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
|
||||
if "velocity" in y:
|
||||
my_dict["velocity"] = list(
|
||||
my_dict[dict_y["velocity"]] = list(
|
||||
map(
|
||||
lambda p: p.geometry.speed(
|
||||
p.get_ts_key(timestamp, "Q"),
|
||||
|
|
@ -695,16 +736,44 @@ class ResultsWindow(PamhyrWindow):
|
|||
reach.profiles
|
||||
)
|
||||
)
|
||||
if "max_depth" in y:
|
||||
my_dict["max_depth"] = list(
|
||||
if envelop:
|
||||
velocities = list(map(
|
||||
lambda p: list(map(
|
||||
lambda q, z:
|
||||
p.geometry.speed(q, z),
|
||||
p.get_key("Q"), p.get_key("Z")
|
||||
)), reach.profiles
|
||||
)
|
||||
)
|
||||
my_dict[dict_y["min_velocity"]] = [min(v) for v in velocities]
|
||||
my_dict[dict_y["max_velocity"]] = [max(v) for v in velocities]
|
||||
|
||||
if "depth" in y:
|
||||
my_dict[dict_y["depth"]] = list(
|
||||
map(
|
||||
lambda p: p.geometry.max_water_depth(
|
||||
p.get_ts_key(timestamp, "Z")),
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
if envelop:
|
||||
my_dict[dict_y["min_depth"]] = list(map(
|
||||
lambda p1, p2: p1 - p2, map(
|
||||
lambda p: min(p.get_key("Z")),
|
||||
reach.profiles
|
||||
), reach.geometry.get_z_min()
|
||||
)
|
||||
)
|
||||
my_dict[dict_y["max_depth"]] = list(map(
|
||||
lambda p1, p2: p1 - p2, map(
|
||||
lambda p: max(p.get_key("Z")),
|
||||
reach.profiles
|
||||
), reach.geometry.get_z_min()
|
||||
)
|
||||
)
|
||||
|
||||
if "mean_depth" in y:
|
||||
my_dict["mean_depth"] = list(
|
||||
my_dict[dict_y["mean_depth"]] = list(
|
||||
map(
|
||||
lambda p: p.geometry.mean_water_depth(
|
||||
p.get_ts_key(timestamp, "Z")),
|
||||
|
|
@ -712,7 +781,7 @@ class ResultsWindow(PamhyrWindow):
|
|||
)
|
||||
)
|
||||
if "froude" in y:
|
||||
my_dict["froude"] = list(
|
||||
my_dict[dict_y["froude"]] = list(
|
||||
map(
|
||||
lambda p:
|
||||
p.geometry.speed(
|
||||
|
|
@ -728,7 +797,7 @@ class ResultsWindow(PamhyrWindow):
|
|||
)
|
||||
)
|
||||
if "wet_area" in y:
|
||||
my_dict["wet_area"] = list(
|
||||
my_dict[dict_y["wet_area"]] = list(
|
||||
map(
|
||||
lambda p: p.geometry.wet_area(
|
||||
p.get_ts_key(timestamp, "Z")),
|
||||
|
|
@ -743,33 +812,36 @@ class ResultsWindow(PamhyrWindow):
|
|||
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")
|
||||
my_dict = {}
|
||||
my_dict["time"] = ts
|
||||
my_dict[dict_x["time"]] = ts
|
||||
z = profile.get_key("Z")
|
||||
q = profile.get_key("Q")
|
||||
if "elevation" in y:
|
||||
my_dict["elevation"] = [profile.geometry.z_min()] * len(ts)
|
||||
if "bed_elevation" in y:
|
||||
my_dict[dict_y["bed_elevation"]] = [
|
||||
profile.geometry.z_min()] * len(ts)
|
||||
if "discharge" in y:
|
||||
my_dict["discharge"] = q
|
||||
my_dict[dict_y["discharge"]] = q
|
||||
if "water_elevation" in y:
|
||||
my_dict["water_elevation"] = z
|
||||
my_dict[dict_y["water_elevation"]] = z
|
||||
if "velocity" in y:
|
||||
my_dict["velocity"] = list(
|
||||
my_dict[dict_y["velocity"]] = list(
|
||||
map(
|
||||
lambda q, z: profile.geometry.speed(q, z),
|
||||
q, z
|
||||
)
|
||||
)
|
||||
if "max_depth" in y:
|
||||
my_dict["max_depth"] = list(
|
||||
if "depth" in y:
|
||||
my_dict[dict_y["depth"]] = list(
|
||||
map(lambda z: profile.geometry.max_water_depth(z), z)
|
||||
)
|
||||
if "mean_depth" in y:
|
||||
my_dict["mean_depth"] = list(
|
||||
my_dict[dict_y["mean_depth"]] = list(
|
||||
map(lambda z: profile.geometry.mean_water_depth(z), z)
|
||||
)
|
||||
if "froude" in y:
|
||||
my_dict["froude"] = list(
|
||||
my_dict[dict_y["froude"]] = list(
|
||||
map(lambda z, q:
|
||||
profile.geometry.speed(q, z) /
|
||||
sqrt(9.81 * (
|
||||
|
|
@ -778,7 +850,7 @@ class ResultsWindow(PamhyrWindow):
|
|||
), z, q)
|
||||
)
|
||||
if "wet_area" in y:
|
||||
my_dict["wet_area"] = list(
|
||||
my_dict[dict_y["wet_area"]] = list(
|
||||
map(lambda z: profile.geometry.wet_area(z), z)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -72,10 +72,10 @@ class ResultsTranslate(MainTranslate):
|
|||
"name": _translate("Results", "Profile"),
|
||||
"water_elevation": self._dict["unit_water_elevation"],
|
||||
"discharge": self._dict["unit_discharge"],
|
||||
"velocity": self._dict["unit_speed"],
|
||||
"velocity": self._dict["unit_velocity"],
|
||||
"width": self._dict["unit_width"],
|
||||
"max_depth": self._dict["unit_max_height"],
|
||||
"mean_depth": self._dict["unit_mean_height"],
|
||||
"depth": self._dict["unit_depth"],
|
||||
"mean_depth": self._dict["unit_mean_depth"],
|
||||
"wet_area": self._dict["unit_wet_area"],
|
||||
"wet_perimeter": self._dict["unit_wet_perimeter"],
|
||||
"hydraulic_radius": self._dict["unit_hydraulic_radius"],
|
||||
|
|
@ -88,12 +88,12 @@ class ResultsTranslate(MainTranslate):
|
|||
}
|
||||
|
||||
self._sub_dict["values_y"] = {
|
||||
"elevation": self._dict["unit_elevation"],
|
||||
"bed_elevation": self._dict["unit_bed_elevation"],
|
||||
"water_elevation": self._dict["unit_water_elevation"],
|
||||
"discharge": self._dict["unit_discharge"],
|
||||
"velocity": self._dict["unit_speed"],
|
||||
"max_depth": self._dict["unit_max_height"],
|
||||
"mean_depth": self._dict["unit_mean_height"],
|
||||
"velocity": self._dict["unit_velocity"],
|
||||
"depth": self._dict["unit_depth"],
|
||||
"mean_depth": self._dict["unit_mean_depth"],
|
||||
"froude": self._dict["unit_froude"],
|
||||
"wet_area": self._dict["unit_wet_area"],
|
||||
}
|
||||
|
|
@ -110,6 +110,18 @@ class ResultsTranslate(MainTranslate):
|
|||
"name": _translate("Results", "Profile"),
|
||||
"water_elevation": self._dict["unit_water_elevation"],
|
||||
"discharge": self._dict["unit_discharge"],
|
||||
"speed": self._dict["unit_speed"],
|
||||
"discharge": self._dict["unit_discharge"],
|
||||
}
|
||||
|
||||
self._sub_dict["values_y_envelop"] = {
|
||||
"min_bed_elevation": self._dict["unit_min_bed_elevation"],
|
||||
"max_bed_elevation": self._dict["unit_max_bed_elevation"],
|
||||
"min_water_elevation": self._dict["unit_min_water_elevation"],
|
||||
"max_water_elevation": self._dict["unit_max_water_elevation"],
|
||||
"min_discharge": self._dict["unit_min_discharge"],
|
||||
"max_discharge": self._dict["unit_max_discharge"],
|
||||
"min_velocity": self._dict["unit_min_velocity"],
|
||||
"max_velocity": self._dict["unit_max_velocity"],
|
||||
"min_depth": self._dict["unit_min_depth"],
|
||||
"max_depth": self._dict["unit_max_depth"],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class Plot(PamhyrPlot):
|
|||
)
|
||||
|
||||
self.label_x = self._trad["rk"]
|
||||
self.label_y = self._trad["height"]
|
||||
self.label_y = self._trad["elevation"]
|
||||
|
||||
self.line_rk_zmin = None
|
||||
self.line_rk_sl = []
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class Plot(PamhyrPlot):
|
|||
)
|
||||
|
||||
self.label_x = self._trad["x"]
|
||||
self.label_y = self._trad["height"]
|
||||
self.label_y = self._trad["elevation"]
|
||||
|
||||
self.line_rk_zmin = None
|
||||
self.line_rk_sl = []
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class SedimentProfileTranslate(SedimentReachTranslate):
|
|||
self._dict["x"] = _translate(
|
||||
"SedimentLayers", "X (m)"
|
||||
)
|
||||
self._dict["height"] = self._dict["unit_height"]
|
||||
self._dict["elevation"] = self._dict["unit_elevation"]
|
||||
|
||||
self._dict["Profile sediment layers"] = _translate(
|
||||
"SedimentLayers", "Profile sediment layers"
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class SedimentReachTranslate(SedimentTranslate):
|
|||
)
|
||||
|
||||
self._dict["rk"] = self._dict["unit_rk"]
|
||||
self._dict["height"] = self._dict["unit_height"]
|
||||
self._dict["elevation"] = self._dict["unit_elevation"]
|
||||
|
||||
self._sub_dict["table_headers"] = {
|
||||
"name": self._dict["name"],
|
||||
|
|
|
|||
|
|
@ -57,18 +57,65 @@ class UnitTranslate(CommonWordTranslate):
|
|||
|
||||
self._dict["unit_rk"] = _translate("Unit", "River Kilometer (m)")
|
||||
self._dict["unit_width"] = _translate("Unit", "Width (m)")
|
||||
self._dict["unit_height"] = _translate("Unit", "Depth (m)")
|
||||
self._dict["unit_max_height"] = _translate("Unit", "Max Depth (m)")
|
||||
self._dict["unit_mean_height"] = _translate("Unit", "Mean Depth (m)")
|
||||
self._dict["unit_width_envelop"] = _translate(
|
||||
"Unit", "Width Envelop (m)"
|
||||
)
|
||||
self._dict["unit_max_width"] = _translate("Unit", "Max Width (m)")
|
||||
self._dict["unit_min_width"] = _translate("Unit", "Min Width (m)")
|
||||
self._dict["unit_depth"] = _translate("Unit", "Depth (m)")
|
||||
self._dict["unit_max_depth"] = _translate("Unit", "Max Depth (m)")
|
||||
self._dict["unit_min_depth"] = _translate("Unit", "Min Depth (m)")
|
||||
self._dict["unit_depth_envelop"] = _translate(
|
||||
"Unit", "Depth Envelop (m)"
|
||||
)
|
||||
self._dict["unit_mean_depth"] = _translate("Unit", "Mean Depth (m)")
|
||||
self._dict["unit_diameter"] = _translate("Unit", "Diameter (m)")
|
||||
self._dict["unit_thickness"] = _translate("Unit", "Thickness (m)")
|
||||
self._dict["unit_elevation"] = _translate("Unit", "Elevation (m)")
|
||||
self._dict["unit_water_elevation"] = _translate(
|
||||
"Unit", "Water elevation (m)"
|
||||
self._dict["unit_bed_elevation"] = _translate(
|
||||
"Unit", "Bed Elevation (m)"
|
||||
)
|
||||
self._dict["unit_bed_elevation_envelop"] = _translate(
|
||||
"Unit", "Bed Elevation Envelop (m)"
|
||||
)
|
||||
self._dict["unit_max_bed_elevation"] = _translate(
|
||||
"Unit", "Max Bed Elevation (m)"
|
||||
)
|
||||
self._dict["unit_min_bed_elevation"] = _translate(
|
||||
"Unit", "Min Bed Elevation (m)"
|
||||
)
|
||||
self._dict["unit_water_elevation"] = _translate(
|
||||
"Unit", "Water Elevation (m)"
|
||||
)
|
||||
self._dict["unit_water_elevation_envelop"] = _translate(
|
||||
"Unit", "Water Elevation Envelop (m)"
|
||||
)
|
||||
self._dict["unit_max_water_elevation"] = _translate(
|
||||
"Unit", "Max Water Elevation (m)"
|
||||
)
|
||||
self._dict["unit_min_water_elevation"] = _translate(
|
||||
"Unit", "Min Water Elevation (m)"
|
||||
)
|
||||
self._dict["unit_velocity"] = _translate("Unit", "Velocity (m/s)")
|
||||
self._dict["unit_velocity_envelop"] = _translate(
|
||||
"Unit", "Velocity Envelop (m/s)"
|
||||
)
|
||||
self._dict["unit_max_velocity"] = _translate(
|
||||
"Unit", "Max Velocity (m/s)"
|
||||
)
|
||||
self._dict["unit_min_velocity"] = _translate(
|
||||
"Unit", "Min Velocity (m/s)"
|
||||
)
|
||||
|
||||
self._dict["unit_speed"] = _translate("Unit", "Velocity (m/s)")
|
||||
self._dict["unit_discharge"] = _translate("Unit", "Discharge (m^3/s)")
|
||||
self._dict["unit_discharge_envelop"] = _translate(
|
||||
"Unit", "Discharge Envelop (m^3/s)"
|
||||
)
|
||||
self._dict["unit_max_discharge"] = _translate(
|
||||
"Unit", "Max Discharge (m^3/s)"
|
||||
)
|
||||
self._dict["unit_min_discharge"] = _translate(
|
||||
"Unit", "Min Discharge (m^3/s)"
|
||||
)
|
||||
self._dict["unit_area"] = _translate("Unit", "Area (hectare)")
|
||||
|
||||
self._dict["unit_time_s"] = _translate("Unit", "Time (sec)")
|
||||
|
|
@ -146,3 +193,5 @@ class MainTranslate(UnitTranslate):
|
|||
"MainWindow",
|
||||
"Do you want to save current study before closing it?"
|
||||
)
|
||||
self._dict["x"] = _translate("MainWindow", "X (m)")
|
||||
self._dict["y"] = _translate("MainWindow", "Y (m)")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS><TS version="2.0" language="fr_FR" sourcelanguage="en_150">
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="fr_FR" sourcelanguage="en_150">
|
||||
<context>
|
||||
<name>About</name>
|
||||
<message>
|
||||
|
|
@ -1167,7 +1168,7 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie.</translati
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../View/ui/about.ui" line="94"/>
|
||||
<source>Copyright © 2022-2024 INRAE</source>
|
||||
<source>Copyright © 2022-2024 INRAE</source>
|
||||
<translation type="obsolete">Copyright © 2022-2024 INRAE</translation>
|
||||
</message>
|
||||
<message>
|
||||
|
|
@ -1175,11 +1176,6 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie.</translati
|
|||
<source>Version: @version @codename</source>
|
||||
<translation>Version : @version @codename</translation>
|
||||
</message>
|
||||
<message encoding="UTF-8">
|
||||
<location filename="../View/ui/about.ui" line="94"/>
|
||||
<source>Copyright © 2022-2024 INRAE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Frictions</name>
|
||||
|
|
@ -1456,17 +1452,17 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie.</translati
|
|||
<context>
|
||||
<name>MainWindow</name>
|
||||
<message>
|
||||
<location filename="../View/Translate.py" line="98"/>
|
||||
<location filename="../View/Translate.py" line="97"/>
|
||||
<source>Open debug window</source>
|
||||
<translation>Ouvrir la fenêtre de débogage</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../View/Translate.py" line="101"/>
|
||||
<location filename="../View/Translate.py" line="100"/>
|
||||
<source>Open SQLite debuging tool ('sqlitebrowser')</source>
|
||||
<translation>Ouvrir l'outil de débogage SQLite ('sqlitebrowser')</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../View/Translate.py" line="104"/>
|
||||
<location filename="../View/Translate.py" line="103"/>
|
||||
<source>Enable this window</source>
|
||||
<translation>Activer cette fenêtre</translation>
|
||||
</message>
|
||||
|
|
@ -2311,12 +2307,12 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie.</translati
|
|||
<translation>Maillage</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../View/Translate.py" line="91"/>
|
||||
<location filename="../View/Translate.py" line="90"/>
|
||||
<source>Summary</source>
|
||||
<translation>Résumé</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../View/Translate.py" line="94"/>
|
||||
<location filename="../View/Translate.py" line="93"/>
|
||||
<source>Checks</source>
|
||||
<translation>Vérifications</translation>
|
||||
</message>
|
||||
|
|
@ -2536,7 +2532,7 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie.</translati
|
|||
<translation>Données</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../View/Translate.py" line="112"/>
|
||||
<location filename="../View/Translate.py" line="111"/>
|
||||
<source>Please select a reach</source>
|
||||
<translation>Veuillez sélectionner un bief</translation>
|
||||
</message>
|
||||
|
|
@ -2546,35 +2542,45 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie.</translati
|
|||
<translation type="obsolete">L'édition de la géométrie nécessite un bief sélectionné dans le réseau fluvial pour pouvoir travailler dessus</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../View/Translate.py" line="121"/>
|
||||
<location filename="../View/Translate.py" line="120"/>
|
||||
<source>Last open study</source>
|
||||
<translation>Dernière étude ouverte</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../View/Translate.py" line="124"/>
|
||||
<location filename="../View/Translate.py" line="123"/>
|
||||
<source>Do you want to open again the last open study?</source>
|
||||
<translation>Voulez-vous rouvrir la dernière étude ?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../View/Translate.py" line="115"/>
|
||||
<location filename="../View/Translate.py" line="114"/>
|
||||
<source>This edition window need a reach selected into the river network to work on it</source>
|
||||
<translation>Cette fenêtre d'édition a besoin d'un bief sélectionné dans le réseau pour travailler dessus</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../View/Translate.py" line="129"/>
|
||||
<location filename="../View/Translate.py" line="128"/>
|
||||
<source>Close without saving study</source>
|
||||
<translation>Fermer sans sauvegarder l'étude</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../View/Translate.py" line="132"/>
|
||||
<location filename="../View/Translate.py" line="131"/>
|
||||
<source>Do you want to save current study before closing it?</source>
|
||||
<translation>Souhaitez-vous sauvegarder l'étude en cours avant de la fermer ?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../View/Translate.py" line="109"/>
|
||||
<location filename="../View/Translate.py" line="108"/>
|
||||
<source>Warning</source>
|
||||
<translation>Avertissement</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../View/Translate.py" line="135"/>
|
||||
<source>X (m)</source>
|
||||
<translation>X (m)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../View/Translate.py" line="136"/>
|
||||
<source>Y (m)</source>
|
||||
<translation>Y (m)</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow_reach</name>
|
||||
|
|
@ -3255,17 +3261,17 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie.</translati
|
|||
<translation>Vitesse (m/s)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../View/Translate.py" line="78"/>
|
||||
<location filename="../View/Translate.py" line="77"/>
|
||||
<source>Wet Perimeter (m)</source>
|
||||
<translation>Périmètre mouillé (m)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../View/Translate.py" line="81"/>
|
||||
<location filename="../View/Translate.py" line="80"/>
|
||||
<source>Hydraulic Radius (m)</source>
|
||||
<translation>Rayon hydraulique (m)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../View/Translate.py" line="84"/>
|
||||
<location filename="../View/Translate.py" line="83"/>
|
||||
<source>Froude number</source>
|
||||
<translation>Nombre de Froude</translation>
|
||||
</message>
|
||||
|
|
@ -3277,6 +3283,11 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie.</translati
|
|||
<message>
|
||||
<location filename="../View/Translate.py" line="77"/>
|
||||
<source>Wet Area (m^2)</source>
|
||||
<translation type="obsolete">Aire mouillée (m²)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../View/Translate.py" line="76"/>
|
||||
<source>Wet Area (m²)</source>
|
||||
<translation>Aire mouillée (m²)</translation>
|
||||
</message>
|
||||
</context>
|
||||
|
|
|
|||
Loading…
Reference in New Issue