Scenarios: Merge remote-tracking branch 'origin/master'.

test_sound
Pierre-Antoine Rouby 2024-09-06 10:58:05 +02:00
commit e43c20c1e6
38 changed files with 1205 additions and 189 deletions

View File

@ -1334,7 +1334,7 @@ https://gitlab.irstea.fr/theophile.terraz/pamhyr
You can improve or add translation for the project. To contribute to You can improve or add translation for the project. To contribute to
Pamhyr2 translate, you need to use Qt Linguist[fn:qt-linguist]. Open Pamhyr2 translate, you need to use Qt Linguist[fn:qt-linguist]. Open
Qt-linguist and edite the translation ({{{file(.ts)}}}) file, finally, Qt-linguist and edit the translation ({{{file(.ts)}}}) file, finally,
commit the new version of file and make a merge request. commit the new version of file and make a merge request.
If you want add a new language, edit the script If you want add a new language, edit the script

Binary file not shown.

View File

@ -411,7 +411,7 @@ class MeshingWithMageMailleurTT(AMeshingTool):
str, str,
[ [
st_file, m_file, st_file, m_file,
"update_rk", step, "update_kp", step,
limites[0], limites[1], limites[0], limites[1],
directrices[0], directrices[1], directrices[0], directrices[1],
orientation, lm, linear, origin, origin_value orientation, lm, linear, origin, origin_value

View File

@ -509,3 +509,15 @@ class BoundaryCondition(SQLSubModel):
d = self._data d = self._data
d[index], d[prev] = d[prev], d[index] d[index], d[prev] = d[prev], d[index]
self._status.modified() self._status.modified()
def reach(self, river):
r = []
if self._node is not None:
if river is not None:
for edge in river.edges():
if edge.node1.name == self._node.name:
r.append(edge.reach)
if edge.node2.name == self._node.name:
r.append(edge.reach)
return r

View File

@ -23,6 +23,8 @@ from tools import trace, timer
from Model.Tools.PamhyrDB import SQLSubModel from Model.Tools.PamhyrDB import SQLSubModel
from Model.Scenario import Scenario from Model.Scenario import Scenario
from numpy import interp
logger = logging.getLogger() logger = logging.getLogger()
@ -290,3 +292,17 @@ class Friction(SQLSubModel):
def end_strickler(self, strickler): def end_strickler(self, strickler):
self._end_strickler = strickler self._end_strickler = strickler
self._status.modified() self._status.modified()
def get_friction(self, rk):
if not self.contains_rk(rk):
return None
minor = interp(rk,
[self.begin_rk, self.end_rk],
[self.begin_strickler.minor,
self.end_strickler.minor])
medium = interp(rk,
[self.begin_rk, self.end_rk],
[self.begin_strickler.medium,
self.end_strickler.medium])
return minor, medium

View File

@ -332,8 +332,17 @@ class Profile(object):
def wet_points(self, z): def wet_points(self, z):
raise NotImplementedMethodeError(self, self.wet_point) raise NotImplementedMethodeError(self, self.wet_point)
def wet_width(self, z):
raise NotImplementedMethodeError(self, self.wet_width)
def wet_perimeter(self, z): def wet_perimeter(self, z):
raise NotImplementedMethodeError(self, self.wet_perimeter) raise NotImplementedMethodeError(self, self.wet_perimeter)
def wet_area(self, z): def wet_area(self, z):
raise NotImplementedMethodeError(self, self.wet_area) raise NotImplementedMethodeError(self, self.wet_area)
def wet_radius(self, z):
raise NotImplementedMethodeError(self, self.wet_radius)
def get_nb_wet_areas(self, z):
raise NotImplementedMethodeError(self, self.get_nb_wet_areas)

View File

@ -486,31 +486,125 @@ class ProfileXYZ(Profile, SQLSubModel):
return abs(rg.dist(rd)) return abs(rg.dist(rd))
def wet_perimeter(self, z): def wet_width(self, z):
poly = self.wet_polygon(z) start, end = self.get_all_water_limits_ac(z)
if poly is None: if len(start) == 0:
return 0 return 0
return poly.length length = 0.0
for s, e in zip(start, end):
length += abs(s - e)
return length
def wet_perimeter(self, z):
lines = self.wet_lines(z)
if lines is None:
return 0
length = 0.0
for line in lines:
length += line.length
return length
def wet_area(self, z): def wet_area(self, z):
poly = self.wet_polygon(z) lines = self.wet_lines(z)
if poly is None: if lines is None:
return 0 return 0
return poly.area area = 0.0
for line in lines:
if len(line.coords) > 2:
poly = geometry.Polygon(line)
area += poly.area
return area
def wet_radius(self, z):
p = self.wet_perimeter(z)
a = self.wet_area(z)
if p == 0:
return 0
return a/p
def wet_line(self, z):
points = self.wet_points(z)
if len(points) < 3:
return None
zz = map(lambda p: p.z, points)
station = self._get_station(points)
line = geometry.LineString(list(zip(station, zz)))
return line
def wet_lines(self, z):
points = self._points
if len(points) < 3:
return None
lines = []
zz = list(map(lambda p: p.z, points))
station = self._get_station(points)
line = []
for i in range(self.number_points-1):
if zz[i] >= z and zz[i+1] < z:
y = np.interp(
z,
[zz[i], zz[i+1]],
[station[i], station[i+1]]
)
line.append([y, z])
if zz[i] < z:
line.append([station[i], zz[i]])
if zz[i] <= z and zz[i+1] >= z:
y = np.interp(
z,
[zz[i], zz[i+1]],
[station[i], station[i+1]]
)
line.append([y, z])
if len(line) > 2:
lines.append(geometry.LineString(line))
line = []
if zz[self.number_points-1] < z:
line.append([station[self.number_points-1], z])
if len(line) > 2:
lines.append(geometry.LineString(line))
line = []
return lines
def max_water_depth(self, z):
return z - self.z_min()
def mean_water_depth(self, z):
a = self.wet_area(z)
w = self.wet_width(z)
if w == 0:
return 0
return a/w
def wet_polygon(self, z): def wet_polygon(self, z):
points = self.wet_points(z) points = self.wet_points(z)
if len(points) < 3: if len(points) < 3:
return None return None
z = map(lambda p: p.z, points) zz = map(lambda p: p.z, points)
station = self._get_station(points) station = self._get_station(points)
poly = geometry.Polygon(list(zip(station, z))) poly = geometry.Polygon(list(zip(station, zz)))
return poly return poly
def wet_points(self, z): def wet_points(self, z):
@ -522,6 +616,63 @@ class ProfileXYZ(Profile, SQLSubModel):
return points return points
def get_nb_wet_areas(self, z):
n_zones = 0
points = self._points
if points[0].z <= z:
n_zones += 1
for i in range(self.number_points-1):
if points[i].z > z and points[i+1].z <= z:
n_zones += 1
return n_zones
def get_all_water_limits_ac(self, z):
"""
Determine all water limits for z elevation.
"""
points = self._points
if len(points) < 3:
return None
zz = list(map(lambda p: p.z, points))
station = self._get_station(points)
start = []
if points[0].z <= z:
start.append(station[0])
for i in range(self.number_points-1):
if zz[i] > z and zz[i+1] <= z:
y = np.interp(
z,
[zz[i], zz[i+1]],
[station[i], station[i+1]]
)
start.append(y)
end = []
if points[-1].z <= z:
end.append(station[-1])
for i in reversed(range(self.number_points-1)):
if zz[i] <= z and zz[i+1] > z:
y = np.interp(
z,
[zz[i], zz[i+1]],
[station[i], station[i+1]]
)
end.append(y)
if len(start) != len(end):
logger.error(f"ERROR in get_all_water_limits_ac")
return [], []
return start, list(reversed(end))
def get_water_limits(self, z): def get_water_limits(self, z):
""" """
Determine left and right limits of water elevation. Determine left and right limits of water elevation.
@ -585,8 +736,8 @@ class ProfileXYZ(Profile, SQLSubModel):
Returns: Returns:
Projection of the points of the profile on a plane. Projection of the points of the profile on a plane.
""" """
if self.nb_points < 3: if self.nb_points < 2:
return None return [0.0]
else: else:
return self._get_station(self.points) return self._get_station(self.points)
@ -718,3 +869,9 @@ class ProfileXYZ(Profile, SQLSubModel):
self.point(i), self.point(i),
self.point(i+1) self.point(i+1)
) )
def shift(self, x, y, z):
for p in self.points:
p.x = p.x + x
p.y = p.y + y
p.z = p.z + z

View File

@ -479,8 +479,14 @@ class InitialConditions(SQLSubModel):
logger.debug(f"incline = {incline}") logger.debug(f"incline = {incline}")
self._data = [] self._data = []
for profile in profiles: for profile in profiles:
width = profile.width_approximation() width = profile.wet_width(profile.z_min() + height)
strickler = 25 frictions = self._reach.frictions.frictions
strickler = None
for f in frictions:
if f.contains_rk(profile.rk):
strickler = f.get_friction(profile.rk)[0]
if strickler is None:
strickler = 25.0
if not compute_discharge: if not compute_discharge:
discharge = data_discharge[profile.rk] discharge = data_discharge[profile.rk]
@ -533,7 +539,13 @@ class InitialConditions(SQLSubModel):
self._data = [] self._data = []
for profile in profiles: for profile in profiles:
width = profile.width_approximation() width = profile.width_approximation()
strickler = 25 frictions = self._reach.frictions.frictions
strickler = None
for f in frictions:
if f.contains_rk(profile.rk):
strickler = f.get_friction(profile.rk)[0]
if strickler is None:
strickler = 25.0
if not compute_height: if not compute_height:
height = data_height[profile.rk] height = data_height[profile.rk]

View File

@ -324,7 +324,7 @@ class Mage(CommandLineSolver):
if t in ["HYD", "QSO", "LIM"]: if t in ["HYD", "QSO", "LIM"]:
v0 /= 60 # Convert first column to minute v0 /= 60 # Convert first column to minute
f.write(f"{v0:10}{v1:10}\n") f.write(f"{v0:9} {v1:9} \n")
return files return files

View File

@ -0,0 +1,68 @@
# GenerateDialog.py -- Pamhyr
# Copyright (C) 2023-2024 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
from View.Tools.PamhyrWindow import PamhyrDialog
from PyQt5.QtGui import (
QKeySequence,
)
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel,
)
from PyQt5.QtWidgets import (
QDialogButtonBox, QComboBox, QUndoStack, QShortcut,
QDoubleSpinBox, QCheckBox, QPushButton
)
class GenerateDialog(PamhyrDialog):
_pamhyr_ui = "BoundaryConditionsDialogGenerator"
_pamhyr_name = "Boundary Condition Options"
def __init__(self,
value,
reach,
title="Boundary Condition Options",
trad=None,
parent=None):
super(GenerateDialog, self).__init__(
title=trad[self._pamhyr_name],
options=[],
trad=trad,
parent=parent
)
self.value = value
self.find(QDoubleSpinBox, "doubleSpinBox").setValue(self.value)
self.reach = reach
self.find(QPushButton, "EstimateButton").clicked.connect(
self.estimate
)
def accept(self):
self.value = self.find(QDoubleSpinBox, "doubleSpinBox").value()
super().accept()
def reject(self):
self.close()
def estimate(self):
self.value = abs(self.reach.get_incline_median_mean())
self.find(QDoubleSpinBox, "doubleSpinBox").setValue(self.value)

View File

@ -48,6 +48,7 @@ from Model.BoundaryCondition.BoundaryConditionTypes import (
from View.BoundaryCondition.Edit.UndoCommand import ( from View.BoundaryCondition.Edit.UndoCommand import (
SetDataCommand, AddCommand, DelCommand, SetDataCommand, AddCommand, DelCommand,
SortCommand, MoveCommand, PasteCommand, SortCommand, MoveCommand, PasteCommand,
ReplaceDataCommand,
) )
_translate = QCoreApplication.translate _translate = QCoreApplication.translate
@ -200,3 +201,13 @@ class TableModel(PamhyrTableModel):
def update(self): def update(self):
# self.auto_sort() # self.auto_sort()
self.layoutChanged.emit() self.layoutChanged.emit()
def replace_data(self, data1, data2):
self.layoutAboutToBeChanged.emit()
self._undo.push(
ReplaceDataCommand(
self._data, data1, data2
)
)
self.layoutAboutToBeChanged.emit()
self.update()

View File

@ -181,3 +181,30 @@ class PasteCommand(QUndoCommand):
def redo(self): def redo(self):
for bc in self._bcs: for bc in self._bcs:
self._data.insert(self._row, bc) self._data.insert(self._row, bc)
class ReplaceDataCommand(QUndoCommand):
def __init__(self, data, data1, data2):
QUndoCommand.__init__(self)
self._data = data
self._old_rows = len(data)
self._data1 = data1
self._data2 = data2
self._rows = len(data1)
self._old_bc = []
for row in range(self._old_rows):
self._old_bc.append((row, self._data.get_i(row)))
self._old_bc.sort()
def undo(self):
self._data.delete_i(list(range(self._rows)))
for row, el in self._old_bc:
self._data.insert(row, el)
def redo(self):
self._data.delete_i(list(range(self._old_rows)))
for row in range(self._rows):
self._data.add(row)
self._data._set_i_c_v(row, 0, self._data1[row])
self._data._set_i_c_v(row, 1, self._data2[row])

View File

@ -20,6 +20,8 @@ import logging
from tools import timer, trace from tools import timer, trace
from numpy import sqrt
from View.Tools.PamhyrWindow import PamhyrWindow from View.Tools.PamhyrWindow import PamhyrWindow
from View.Tools.PamhyrWidget import PamhyrWidget from View.Tools.PamhyrWidget import PamhyrWidget
from View.Tools.PamhyrDelegate import PamhyrExTimeDelegate from View.Tools.PamhyrDelegate import PamhyrExTimeDelegate
@ -48,6 +50,7 @@ from View.BoundaryCondition.Edit.translate import BCETranslate
from View.BoundaryCondition.Edit.UndoCommand import SetMetaDataCommand from View.BoundaryCondition.Edit.UndoCommand import SetMetaDataCommand
from View.BoundaryCondition.Edit.Table import TableModel from View.BoundaryCondition.Edit.Table import TableModel
from View.BoundaryCondition.Edit.Plot import Plot from View.BoundaryCondition.Edit.Plot import Plot
from View.BoundaryCondition.Edit.GenerateDialog import GenerateDialog
_translate = QCoreApplication.translate _translate = QCoreApplication.translate
@ -104,6 +107,7 @@ class EditBoundaryConditionWindow(PamhyrWindow):
self._data = data self._data = data
trad = BCETranslate() trad = BCETranslate()
self._long_types = trad.get_dict("long_types") self._long_types = trad.get_dict("long_types")
self._study = study
name = trad[self._pamhyr_name] name = trad[self._pamhyr_name]
if self._data is not None: if self._data is not None:
@ -200,6 +204,18 @@ class EditBoundaryConditionWindow(PamhyrWindow):
self.find(QAction, "action_del").triggered.connect(self.delete) self.find(QAction, "action_del").triggered.connect(self.delete)
self.find(QAction, "action_sort").triggered.connect(self.sort) self.find(QAction, "action_sort").triggered.connect(self.sort)
self.find(QAction, "action_generate_uniform")\
.triggered.connect(self.generate_uniform)
self.find(QAction, "action_generate_critical")\
.triggered.connect(self.generate_critical)
if self._data.bctype != "ZD" or not self._data.has_node:
self.find(QAction, "action_generate_uniform").setVisible(False)
self.find(QAction, "action_generate_critical").setVisible(False)
else:
self.find(QAction, "action_generate_uniform").setVisible(True)
self.find(QAction, "action_generate_critical").setVisible(True)
self._table.dataChanged.connect(self.update) self._table.dataChanged.connect(self.update)
self._table.layoutChanged.connect(self.update) self._table.layoutChanged.connect(self.update)
@ -320,3 +336,46 @@ class EditBoundaryConditionWindow(PamhyrWindow):
self._table.redo() self._table.redo()
self.plot.update() self.plot.update()
self.widget_update() self.widget_update()
def generate_uniform(self):
if self._data.has_node:
node = self._data.node
reach = self._data.reach(self._study.river)[0]
profile = reach.profiles[-1]
incline = abs(reach.get_incline_median_mean())
dlg = GenerateDialog(incline,
reach,
trad=self._trad,
parent=self)
if dlg.exec():
incline = dlg.value
frictions = reach._parent.frictions.frictions
z_min = profile.z_min()
z_max = profile.z_max()
strickler = None
for f in frictions:
if f.contains_rk(profile.rk):
strickler = f.get_friction(profile.rk)[0]
if strickler is None:
strickler = 25.0
height = [(i)*(z_max-z_min)/50 for i in range(51)]
q = [((profile.wet_width(z_min + h) * 0.8) * strickler
* (h ** (5/3)) * (abs(incline) ** (0.5)))
for h in height]
self._table.replace_data(height, q)
return
def generate_critical(self):
if self._data.has_node:
node = self._data.node
reach = self._data.reach(self._study.river)[0]
profile = reach.profiles[-1]
z_min = profile.z_min()
z_max = profile.z_max()
height = [(i)*(z_max-z_min)/50 for i in range(51)]
q = [sqrt(9.81 * (profile.wet_area(z_min + h) ** 3)
/ profile.wet_width(z_min + h))
for h in height]
self._table.replace_data(height, q)
return

View File

@ -32,6 +32,8 @@ class BCETranslate(BCTranslate):
self._dict["Edit Boundary Conditions"] = _translate( self._dict["Edit Boundary Conditions"] = _translate(
"BoundaryCondition", "Edit boundary conditions" "BoundaryCondition", "Edit boundary conditions"
) )
self._dict["Boundary Condition Options"] = _translate(
"BoundaryCondition", "Boundary Condition Options")
self._sub_dict["table_headers"] = { self._sub_dict["table_headers"] = {
"x": _translate("BoundaryCondition", "X"), "x": _translate("BoundaryCondition", "X"),

View File

@ -313,10 +313,6 @@ class Plot(PamhyrPlot):
x_carto = self.data.x() x_carto = self.data.x()
y_carto = self.data.y() y_carto = self.data.y()
if (len(x_carto) < 3 or len(y_carto) < 3 or len(x) < 3):
# Noting to do in this case
return
self.profile_line2D, = self.canvas.axes.plot( self.profile_line2D, = self.canvas.axes.plot(
x, y, color=self.color_plot, x, y, color=self.color_plot,
lw=1.5, markersize=7, marker='+', lw=1.5, markersize=7, marker='+',

View File

@ -0,0 +1,60 @@
# ShiftDialog.py -- Pamhyr
# Copyright (C) 2023-2024 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
from View.Tools.PamhyrWindow import PamhyrDialog
class ShiftDialog(PamhyrDialog):
_pamhyr_ui = "GeometryReachShift"
_pamhyr_name = "Shift"
def __init__(self, trad=None, parent=None):
super(ShiftDialog, self).__init__(
title=trad[self._pamhyr_name],
trad=trad,
options=[],
parent=parent
)
self._init_default_values()
def _init_default_values(self):
self._dx = 0.0
self._dy = 0.0
self._dz = 0.0
@property
def dx(self):
return self._dx
@property
def dy(self):
return self._dy
@property
def dz(self):
return self._dz
def accept(self):
self._dx = self.get_double_spin_box("doubleSpinBox_X")
self._dy = self.get_double_spin_box("doubleSpinBox_Y")
self._dz = self.get_double_spin_box("doubleSpinBox_Z")
super().accept()
def reject(self):
self.close()

View File

@ -267,3 +267,12 @@ class GeometryReachTableModel(PamhyrTableModel):
) )
) )
self.layoutChanged.emit() self.layoutChanged.emit()
def shift(self, rows, dx, dy, dz):
self._undo.push(
ShiftCommand(
self._data, rows, dx, dy, dz
)
)
self.layoutChanged.emit()

View File

@ -82,3 +82,12 @@ class GeometryTranslate(MainTranslate):
self._dict["Meshing"] = _translate( self._dict["Meshing"] = _translate(
"Geometry", "Meshing" "Geometry", "Meshing"
) )
self._dict["UpdateRK"] = _translate(
"Geometry", "UpdateRK"
)
self._dict["Purge"] = _translate(
"Geometry", "Purge"
)
self._dict["Shift"] = _translate(
"Geometry", "Shift"
)

View File

@ -281,3 +281,32 @@ class PurgeCommand(QUndoCommand):
def redo(self): def redo(self):
for profile in self._reach._profiles: for profile in self._reach._profiles:
profile.purge(self._np_purge) profile.purge(self._np_purge)
class ShiftCommand(QUndoCommand):
def __init__(self, reach, rows, dx, dy, dz):
QUndoCommand.__init__(self)
self._reach = reach
self._rows = rows
self._dx = dx
self._dy = dy
self._dz = dz
self._old = []
for profile in self._reach.profiles:
self._old.append(profile.points.copy())
def undo(self):
for i in self._rows:
profile = self._reach.profiles[i]
self._reach.profiles[i].shift(-self._dx,
-self._dy,
-self._dz)
def redo(self):
for i in self._rows:
profile = self._reach.profiles[i]
self._reach.profiles[i].shift(self._dx,
self._dy,
self._dz)

View File

@ -58,6 +58,7 @@ from View.Geometry.PlotRKZ import PlotRKZ
from View.Geometry.MeshingDialog import MeshingDialog from View.Geometry.MeshingDialog import MeshingDialog
from View.Geometry.UpdateRKDialog import UpdateRKDialog from View.Geometry.UpdateRKDialog import UpdateRKDialog
from View.Geometry.PurgeDialog import PurgeDialog from View.Geometry.PurgeDialog import PurgeDialog
from View.Geometry.ShiftDialog import ShiftDialog
from View.Geometry.Translate import GeometryTranslate from View.Geometry.Translate import GeometryTranslate
from View.Geometry.Profile.Window import ProfileWindow from View.Geometry.Profile.Window import ProfileWindow
@ -205,6 +206,7 @@ class GeometryWindow(PamhyrWindow):
"action_meshing": self.edit_meshing, "action_meshing": self.edit_meshing,
"action_update_rk": self.update_rk, "action_update_rk": self.update_rk,
"action_purge": self.purge, "action_purge": self.purge,
"action_shift": self.shift,
} }
for action in actions: for action in actions:
@ -511,10 +513,6 @@ class GeometryWindow(PamhyrWindow):
self._table.move_down(row) self._table.move_down(row)
self.select_current_profile() self.select_current_profile()
def purge(self):
self._table.purge()
self.update_redraw()
def purge(self): def purge(self):
try: try:
dlg = PurgeDialog( dlg = PurgeDialog(
@ -527,6 +525,28 @@ class GeometryWindow(PamhyrWindow):
logger_exception(e) logger_exception(e)
return return
def shift(self):
rows = sorted(
list(
set(
[index.row() for index in self.tableView.selectedIndexes()]
)
)
)
try:
dlg = ShiftDialog(
trad=self._trad,
parent=self
)
if dlg.exec():
self._table.shift(rows,
dlg.dx,
dlg.dy,
dlg.dz)
except Exception as e:
logger_exception(e)
return
def duplicate(self): def duplicate(self):
rows = [ rows = [
row.row() for row in row.row() for row in
@ -615,7 +635,7 @@ class GeometryWindow(PamhyrWindow):
QSettings.UserScope, 'MyOrg' QSettings.UserScope, 'MyOrg'
) )
if self._study.filename != "" or self._study.filename is not None: if self._study.filename != "" and self._study.filename is not None:
default_directory = os.path.basename(self._study.filename) default_directory = os.path.basename(self._study.filename)
current_dir = settings.value( current_dir = settings.value(
'current_directory', 'current_directory',

View File

@ -200,7 +200,7 @@ class InitialConditionsWindow(PamhyrWindow):
return rows[0].row() return rows[0].row()
def update(self): def update(self):
self.update(propagate=False) self._update(propagate=False)
def _update(self, propagate=True): def _update(self, propagate=True):
self._update_plot() self._update_plot()

View File

@ -20,6 +20,7 @@ import logging
from functools import reduce from functools import reduce
from datetime import datetime from datetime import datetime
from numpy import sqrt
from tools import timer from tools import timer
from View.Tools.PamhyrPlot import PamhyrPlot from View.Tools.PamhyrPlot import PamhyrPlot
@ -32,6 +33,11 @@ unit = {
"elevation": "0-meter", "elevation": "0-meter",
"water_elevation": "0-meter", "water_elevation": "0-meter",
"discharge": "1-m3s", "discharge": "1-m3s",
"velocity": "2-ms",
"depth": "3-meter",
"mean_depth": "3-meter",
"froude": "4-dimensionless",
"wet_area": "5-m2",
} }
@ -76,74 +82,155 @@ class CustomPlot(PamhyrPlot):
reach = results.river.reach(self._reach) reach = results.river.reach(self._reach)
rk = reach.geometry.get_rk() rk = reach.geometry.get_rk()
z_min = reach.geometry.get_z_min() z_min = reach.geometry.get_z_min()
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
)
)
# self.canvas.axes.set_xlim( shift = 0
# left=min(rk), right=max(rk) compt = 0
# ) for ax in sorted(self._axes):
if compt == 0:
meter_axes = self.canvas.axes self._axes[ax].spines['left'].set_position(('outward', shift))
m3S_axes = self.canvas.axes compt += 1
if "0-meter" in self._y_axes and "1-m3s" in self._y_axes: else:
m3s_axes = self._axes["1-m3s"] self._axes[ax].spines['right'].set_position(('outward', shift))
shift += 60
lines = {} lines = {}
if "elevation" in self._y: if "elevation" in self._y:
# meter_axes.set_ylim(
# bottom=min(0, min(z_min)),
# top=max(z_min) + 1
# )
line = meter_axes.plot( ax = self._axes[unit["elevation"]]
line = ax.plot(
rk, z_min, rk, z_min,
color='grey', lw=1., color='grey', lw=1.,
) )
lines["elevation"] = line lines["elevation"] = line
if "water_elevation" in self._y: if "water_elevation" in self._y:
# Water elevation
water_z = list(
map(
lambda p: p.get_ts_key(self._timestamp, "Z"),
reach.profiles
)
)
# meter_axes.set_ylim( ax = self._axes[unit["water_elevation"]]
# bottom=min(0, min(z_min)), line = ax.plot(
# top=max(water_z) + 1 rk, z, lw=1.,
# )
line = meter_axes.plot(
rk, water_z, lw=1.,
color='blue', color='blue',
) )
lines["water_elevation"] = line lines["water_elevation"] = line
if "elevation" in self._y: if "elevation" in self._y:
meter_axes.fill_between( ax.fill_between(
rk, z_min, water_z, rk, z_min, z,
color='blue', alpha=0.5, interpolate=True color='blue', alpha=0.5, interpolate=True
) )
if "discharge" in self._y: if "discharge" in self._y:
q = list(
map(
lambda p: p.get_ts_key(self._timestamp, "Q"),
reach.profiles
)
)
# m3s_axes.set_ylim( ax = self._axes[unit["discharge"]]
# bottom=min(0, min(q)), line = ax.plot(
# top=max(q) + 1
# )
line = m3s_axes.plot(
rk, q, lw=1., rk, q, lw=1.,
color='r', color='r',
) )
lines["discharge"] = line lines["discharge"] = line
if "velocity" in self._y:
ax = self._axes[unit["velocity"]]
v = list(
map(
lambda p: p.geometry.speed(
p.get_ts_key(self._timestamp, "Q"),
p.get_ts_key(self._timestamp, "Z")),
reach.profiles
)
)
line = ax.plot(
rk, v, lw=1.,
color='g',
)
lines["velocity"] = line
if "depth" in self._y:
ax = self._axes[unit["depth"]]
d = list(
map(
lambda p: p.geometry.max_water_depth(
p.get_ts_key(self._timestamp, "Z")),
reach.profiles
)
)
line = ax.plot(
rk, d,
color='brown', lw=1.,
)
lines["depth"] = line
if "mean_depth" in self._y:
ax = self._axes[unit["mean_depth"]]
d = list(
map(
lambda p: p.geometry.mean_water_depth(
p.get_ts_key(self._timestamp, "Z")),
reach.profiles
)
)
line = ax.plot(
rk, d,
color='orange', lw=1.,
)
lines["mean_depth"] = line
if "froude" in self._y:
ax = self._axes[unit["froude"]]
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
)
)
line = ax.plot(
rk, fr, color='black', linestyle='--', lw=1.,
)
lines["froude"] = line
if "wet_area" in self._y:
ax = self._axes[unit["wet_area"]]
d = list(
map(
lambda p: p.geometry.wet_area(
p.get_ts_key(self._timestamp, "Z")),
reach.profiles
)
)
line = ax.plot(
rk, d,
color='blue', linestyle='--', lw=1.,
)
lines["wet_area"] = line
# Legend # Legend
lns = reduce( lns = reduce(
lambda acc, line: acc + line, lambda acc, line: acc + line,
@ -151,7 +238,7 @@ class CustomPlot(PamhyrPlot):
[] []
) )
labs = list(map(lambda line: self._trad[line], lines)) labs = list(map(lambda line: self._trad[line], lines))
self.canvas.axes.legend(lns, labs, loc="lower left") self.canvas.axes.legend(lns, labs, loc="best")
def _customize_x_axes_time(self, ts, mode="time"): def _customize_x_axes_time(self, ts, mode="time"):
# Custom time display # Custom time display
@ -198,79 +285,137 @@ class CustomPlot(PamhyrPlot):
reach = results.river.reach(self._reach) reach = results.river.reach(self._reach)
profile = reach.profile(self._profile) profile = reach.profile(self._profile)
meter_axes = self.canvas.axes shift = 0
m3S_axes = self.canvas.axes compt = 0
if "0-meter" in self._y_axes and "1-m3s" in self._y_axes: for ax in sorted(self._axes):
m3s_axes = self._axes["1-m3s"] if compt == 0:
self._axes[ax].spines['left'].set_position(('outward', shift))
compt += 1
else:
self._axes[ax].spines['right'].set_position(('outward', shift))
shift += 60
ts = list(results.get("timestamps")) ts = list(results.get("timestamps"))
ts.sort() ts.sort()
# self.canvas.axes.set_xlim( q = profile.get_key("Q")
# left=min(ts), right=max(ts) z = profile.get_key("Z")
# ) z_min = profile.geometry.z_min()
ts_z_min = list(
map(
lambda ts: z_min,
ts
)
)
x = ts
lines = {} lines = {}
if "elevation" in self._y: if "elevation" in self._y:
# Z min is constant in time # Z min is constant in time
z_min = profile.geometry.z_min()
ts_z_min = list(
map(
lambda ts: z_min,
ts
)
)
line = meter_axes.plot( ax = self._axes[unit["elevation"]]
line = ax.plot(
ts, ts_z_min, ts, ts_z_min,
color='grey', lw=1. color='grey', lw=1.
) )
lines["elevation"] = line lines["elevation"] = line
if "water_elevation" in self._y: if "water_elevation" in self._y:
# Water elevation
z = profile.get_key("Z")
# meter_axes.set_ylim( ax = self._axes[unit["water_elevation"]]
# bottom=min(0, min(z)), line = ax.plot(
# top=max(z) + 1
# )
line = meter_axes.plot(
ts, z, lw=1., ts, z, lw=1.,
color='b', color='b',
) )
lines["water_elevation"] = line lines["water_elevation"] = line
if "elevation" in self._y: if "elevation" in self._y:
z_min = profile.geometry.z_min()
ts_z_min = list(
map(
lambda ts: z_min,
ts
)
)
meter_axes.fill_between( ax.fill_between(
ts, ts_z_min, z, ts, ts_z_min, z,
color='blue', alpha=0.5, interpolate=True color='blue', alpha=0.5, interpolate=True
) )
if "discharge" in self._y: if "discharge" in self._y:
q = profile.get_key("Q")
# m3s_axes.set_ylim( ax = self._axes[unit["discharge"]]
# bottom=min(0, min(q)), line = ax.plot(
# top=max(q) + 1
# )
line = m3s_axes.plot(
ts, q, lw=1., ts, q, lw=1.,
color='r', color='r',
) )
lines["discharge"] = line lines["discharge"] = line
if "velocity" in self._y:
ax = self._axes[unit["velocity"]]
v = list(
map(
lambda q, z: profile.geometry.speed(q, z),
q, z
)
)
line = ax.plot(
ts, v, lw=1.,
color='g',
)
lines["velocity"] = line
if "depth" in self._y:
ax = self._axes[unit["depth"]]
d = list(
map(lambda z: profile.geometry.max_water_depth(z), z)
)
line = ax.plot(
ts, d,
color='brown', lw=1.,
)
lines["depth"] = line
if "mean_depth" in self._y:
ax = self._axes[unit["mean_depth"]]
d = list(
map(lambda z: profile.geometry.mean_water_depth(z), z)
)
line = ax.plot(
ts, d,
color='orange', lw=1.,
)
lines["depth"] = line
if "froude" in self._y:
ax = self._axes[unit["froude"]]
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)
)
line = ax.plot(
ts, d, color='black', linestyle='--', lw=1.,
)
lines["froude"] = line
if "wet_area" in self._y:
ax = self._axes[unit["wet_area"]]
d = list(
map(lambda z: profile.geometry.wet_area(z), z)
)
line = ax.plot(
ts, d, color='blue', linestyle='--', lw=1.,
)
lines["wet_area"] = line
self._customize_x_axes_time(ts) self._customize_x_axes_time(ts)
# Legend # Legend
@ -280,7 +425,7 @@ class CustomPlot(PamhyrPlot):
[] []
) )
labs = list(map(lambda line: self._trad[line], lines)) labs = list(map(lambda line: self._trad[line], lines))
self.canvas.axes.legend(lns, labs, loc="lower left") self.canvas.axes.legend(lns, labs, loc="best")
@timer @timer
def draw(self): def draw(self):
@ -300,6 +445,7 @@ class CustomPlot(PamhyrPlot):
color='black', fontsize=10 color='black', fontsize=10
) )
self._axes[self._y_axes[0]] = self.canvas.axes
for axes in self._y_axes[1:]: for axes in self._y_axes[1:]:
if axes in self._axes: if axes in self._axes:
self._axes[axes].clear() self._axes[axes].clear()
@ -316,8 +462,22 @@ class CustomPlot(PamhyrPlot):
self._draw_rk() self._draw_rk()
elif self._x == "time": elif self._x == "time":
self._draw_time() self._draw_time()
if self._x == "rk":
reach = self.data.river.reach(self._reach)
profile = reach.profile(self._profile)
x = profile.rk
elif self._x == "time":
x = self._timestamp
self._current, = self.canvas.axes.plot(
[x, x],
self.canvas.axes.get_ylim(),
# label=self.label_timestamp,
color='grey',
linestyle="dashed",
lw=1.,
)
self.canvas.figure.tight_layout()
self.canvas.figure.canvas.draw_idle() self.canvas.figure.canvas.draw_idle()
if self.toolbar is not None: if self.toolbar is not None:
self.toolbar.update() self.toolbar.update()
@ -326,6 +486,7 @@ class CustomPlot(PamhyrPlot):
def update(self): def update(self):
if not self._init: if not self._init:
self.draw() self.draw()
self.draw_current()
return return
def set_reach(self, reach_id): def set_reach(self, reach_id):
@ -339,9 +500,23 @@ class CustomPlot(PamhyrPlot):
if self._x != "rk": if self._x != "rk":
self.update() self.update()
else:
self.draw_current()
def set_timestamp(self, timestamp): def set_timestamp(self, timestamp):
self._timestamp = timestamp self._timestamp = timestamp
if self._x != "time": if self._x != "time":
self.update() self.update()
else:
self.draw_current()
def draw_current(self):
if self._x == "rk":
reach = self.data.river.reach(self._reach)
profile = reach.profile(self._profile)
x = profile.rk
elif self._x == "time":
x = self._timestamp
self._current.set_data([x, x], self.canvas.axes.get_ylim())
self.canvas.figure.canvas.draw_idle()

View File

@ -40,6 +40,14 @@ class CustomPlotTranslate(ResultsTranslate):
self._dict['elevation'] = _translate( self._dict['elevation'] = _translate(
"CustomPlot", "Bed elevation (m)" "CustomPlot", "Bed elevation (m)"
) )
self._dict['velocity'] = self._dict["unit_speed"]
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['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"]
self._dict['froude'] = self._dict["unit_froude"]
# Unit corresponding long name (plot axes display) # Unit corresponding long name (plot axes display)
@ -47,6 +55,10 @@ class CustomPlotTranslate(ResultsTranslate):
"CustomPlot", "Bed elevation (m)" "CustomPlot", "Bed elevation (m)"
) )
self._dict['1-m3s'] = self._dict["unit_discharge"] 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['4-dimensionless'] = self._dict["unit_froude"]
self._dict['5-m2'] = self._dict["wet_area"]
# SubDict # SubDict
@ -58,4 +70,9 @@ class CustomPlotTranslate(ResultsTranslate):
"elevation": self._dict["elevation"], "elevation": self._dict["elevation"],
"water_elevation": self._dict["water_elevation"], "water_elevation": self._dict["water_elevation"],
"discharge": self._dict["discharge"], "discharge": self._dict["discharge"],
"velocity": self._dict["velocity"],
"depth": self._dict["max_depth"],
"mean_depth": self._dict["mean_depth"],
"froude": self._dict["froude"],
"wet_area": self._dict["wet_area"],
} }

View File

@ -88,13 +88,14 @@ class PlotH(PamhyrPlot):
self.draw_max(reach) self.draw_max(reach)
self.draw_data(reach, profile) self.draw_data(reach, profile)
self.draw_current(reach, profile) self.draw_current()
self.set_ticks_time_formater() self.set_ticks_time_formater()
self.enable_legend() self.enable_legend()
self.idle() self.idle()
self.update_current()
self._init = True self._init = True
def draw_data(self, reach, profile): def draw_data(self, reach, profile):
@ -111,21 +112,12 @@ class PlotH(PamhyrPlot):
**self.plot_default_kargs **self.plot_default_kargs
) )
def draw_current(self, reach, profile): def draw_current(self):
min_y, max_y = reduce(
lambda acc, p: (
acc[0] + [min(p.get_key("Q"))],
acc[1] + [max(p.get_key("Q"))]
),
reach.profiles,
([], [])
)
self._current, = self.canvas.axes.plot( self._current, = self.canvas.axes.plot(
[self._current_timestamp, self._current_timestamp], [self._current_timestamp, self._current_timestamp],
[min(min_y), max(max_y)], self.canvas.axes.get_ylim(),
# label=self.label_timestamp, # label=self.label_timestamp,
color=self.color_plot_river_bottom, color="grey",
linestyle="dashed", linestyle="dashed",
lw=1., lw=1.,
) )
@ -162,14 +154,14 @@ class PlotH(PamhyrPlot):
def set_timestamp(self, timestamp): def set_timestamp(self, timestamp):
self._current_timestamp = timestamp self._current_timestamp = timestamp
self.update() self.update_current()
self.update_idle()
def update(self): def update(self):
if not self._init: if not self._init:
self.draw() self.draw()
self.update_data() self.update_data()
self.update_idle() self.update_idle()
def update_data(self): def update_data(self):
@ -181,8 +173,13 @@ class PlotH(PamhyrPlot):
self._line.set_data(x, y) self._line.set_data(x, y)
_, min_max = self._current.get_data()
self._current.set_data( self._current.set_data(
self._current_timestamp, self._current_timestamp,
min_max self.canvas.axes.get_ylim()
)
def update_current(self):
self._current.set_data(
self._current_timestamp,
self.canvas.axes.get_ylim()
) )

View File

@ -19,6 +19,8 @@
import logging import logging
import traceback import traceback
from numpy import sqrt
from tools import timer, trace from tools import timer, trace
from PyQt5.QtGui import ( from PyQt5.QtGui import (
@ -86,12 +88,46 @@ class TableModel(PamhyrTableModel):
elif self._headers[column] == "discharge": elif self._headers[column] == "discharge":
v = self._lst[row].get_ts_key(self._timestamp, "Q") v = self._lst[row].get_ts_key(self._timestamp, "Q")
return f"{v:.4f}" return f"{v:.4f}"
elif self._headers[column] == "speed": elif self._headers[column] == "velocity":
q = self._lst[row].get_ts_key(self._timestamp, "Q") q = self._lst[row].get_ts_key(self._timestamp, "Q")
z = self._lst[row].get_ts_key(self._timestamp, "Z") z = self._lst[row].get_ts_key(self._timestamp, "Z")
v = self._lst[row].geometry.speed(q, z) v = self._lst[row].geometry.speed(q, z)
return f"{v:.4f}" return f"{v:.4f}"
elif self._headers[column] == "width":
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":
z = self._lst[row].get_ts_key(self._timestamp, "Z")
v = self._lst[row].geometry.max_water_depth(z)
return f"{v:.4f}"
elif self._headers[column] == "mean_depth":
z = self._lst[row].get_ts_key(self._timestamp, "Z")
v = self._lst[row].geometry.mean_water_depth(z)
return f"{v:.4f}"
elif self._headers[column] == "wet_area":
z = self._lst[row].get_ts_key(self._timestamp, "Z")
v = self._lst[row].geometry.wet_area(z)
return f"{v:.4f}"
elif self._headers[column] == "wet_perimeter":
z = self._lst[row].get_ts_key(self._timestamp, "Z")
v = self._lst[row].geometry.wet_perimeter(z)
return f"{v:.4f}"
elif self._headers[column] == "hydraulic_radius":
z = self._lst[row].get_ts_key(self._timestamp, "Z")
v = self._lst[row].geometry.wet_radius(z)
return f"{v:.4f}"
elif self._headers[column] == "froude":
q = self._lst[row].get_ts_key(self._timestamp, "Q")
z = self._lst[row].get_ts_key(self._timestamp, "Z")
v = self._lst[row].geometry.speed(q, z)
a = self._lst[row].geometry.wet_area(z)
b = self._lst[row].geometry.wet_width(z)
froude = v / sqrt(9.81 * (a / b))
return f"{froude:.4f}"
else:
v = 0.0
return f"{v:.4f}"
return QVariant() return QVariant()

View File

@ -40,7 +40,7 @@ from PyQt5.QtWidgets import (
QFileDialog, QTableView, QAbstractItemView, QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate, QUndoStack, QShortcut, QAction, QItemDelegate,
QComboBox, QVBoxLayout, QHeaderView, QTabWidget, QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
QSlider, QLabel, QWidget, QGridLayout, QSlider, QLabel, QWidget, QGridLayout, QTabBar
) )
from View.Tools.Plot.PamhyrCanvas import MplCanvas from View.Tools.Plot.PamhyrCanvas import MplCanvas
@ -108,9 +108,9 @@ class ResultsWindow(PamhyrWindow):
try: try:
self._timestamps = sorted(list(self._results.get("timestamps"))) self._timestamps = sorted(list(self._results.get("timestamps")))
self.setup_slider()
self.setup_table() self.setup_table()
self.setup_plots() self.setup_plots()
self.setup_slider()
self.setup_statusbar() self.setup_statusbar()
self.setup_connections() self.setup_connections()
except Exception as e: except Exception as e:
@ -128,12 +128,11 @@ class ResultsWindow(PamhyrWindow):
undo=self._undo_stack, undo=self._undo_stack,
opt_data=t opt_data=t
) )
self._table[t]._timestamp = self._timestamps[
self._slider_time.value()]
def setup_slider(self): def setup_slider(self):
self._slider_profile = self.find(QSlider, f"verticalSlider_profile")
default_reach = self._results.river.reach(0) default_reach = self._results.river.reach(0)
self._slider_profile.setMaximum(len(default_reach.profiles) - 1)
self._slider_profile.setValue(0)
self._slider_time = self.find(QSlider, f"horizontalSlider_time") self._slider_time = self.find(QSlider, f"horizontalSlider_time")
self._slider_time.setMaximum(len(self._timestamps) - 1) self._slider_time.setMaximum(len(self._timestamps) - 1)
@ -158,6 +157,12 @@ class ResultsWindow(PamhyrWindow):
def setup_plots(self): def setup_plots(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100) self.canvas = MplCanvas(width=5, height=4, dpi=100)
tab_widget = self.find(QTabWidget, f"tabWidget")
tab_widget.setTabsClosable(True)
tab_widget.tabCloseRequested.connect(self.delete_tab)
tab_widget.tabBar().setTabButton(0, QTabBar.RightSide, None)
tab_widget.tabBar().setTabButton(1, QTabBar.RightSide, None)
tab_widget.tabBar().setTabButton(2, QTabBar.RightSide, None)
self.canvas.setObjectName("canvas") self.canvas.setObjectName("canvas")
self.toolbar = PamhyrPlotToolbar( self.toolbar = PamhyrPlotToolbar(
self.canvas, self, items=[ self.canvas, self, items=[
@ -303,7 +308,8 @@ class ResultsWindow(PamhyrWindow):
actions = { actions = {
"action_reload": self._reload, "action_reload": self._reload,
"action_add": self._add_custom_plot, "action_add": self._add_custom_plot,
"action_export": self.export, # "action_export": self.export,
"action_export": self.export_current,
} }
for action in actions: for action in actions:
@ -327,8 +333,6 @@ class ResultsWindow(PamhyrWindow):
self._table[t].dataChanged.connect(fun[t]) self._table[t].dataChanged.connect(fun[t])
self._slider_profile.valueChanged.connect(
self._set_current_profile_slider)
self._slider_time.valueChanged.connect(self._set_current_timestamp) self._slider_time.valueChanged.connect(self._set_current_timestamp)
self._button_play.setChecked(False) self._button_play.setChecked(False)
self._button_play.clicked.connect(self._pause) self._button_play.clicked.connect(self._pause)
@ -442,7 +446,6 @@ class ResultsWindow(PamhyrWindow):
ind = indexes[0].row() ind = indexes[0].row()
self.update(profile_id=ind) self.update(profile_id=ind)
self._slider_profile.setValue(ind)
def _set_current_profile_raw_data(self): def _set_current_profile_raw_data(self):
table = self.find(QTableView, f"tableView_raw_data") table = self.find(QTableView, f"tableView_raw_data")
@ -452,11 +455,6 @@ class ResultsWindow(PamhyrWindow):
ind = indexes[0].row() ind = indexes[0].row()
self.update(profile_id=ind) self.update(profile_id=ind)
self._slider_profile.setValue(ind)
def _set_current_profile_slider(self):
pid = self._slider_profile.value()
self.update(profile_id=pid)
def _set_current_timestamp(self): def _set_current_timestamp(self):
timestamp = self._timestamps[self._slider_time.value()] timestamp = self._timestamps[self._slider_time.value()]
@ -500,7 +498,7 @@ class ResultsWindow(PamhyrWindow):
tab_widget = self.find(QTabWidget, f"tabWidget") tab_widget = self.find(QTabWidget, f"tabWidget")
# This plot already exists # This plot already exists
if name in self._additional_plot: if name in [tab_widget.tabText(i) for i in range(tab_widget.count())]:
tab_widget.setCurrentWidget( tab_widget.setCurrentWidget(
tab_widget.findChild(QWidget, wname) tab_widget.findChild(QWidget, wname)
) )
@ -591,12 +589,15 @@ class ResultsWindow(PamhyrWindow):
) )
def export_to(self, directory): def export_to(self, directory):
timestamps = sorted(self._results.get("timestamps"))
for reach in self._results.river.reachs: for reach in self._results.river.reachs:
self.export_reach(reach, directory) self.export_reach(reach, directory, timestamps)
def export_reach(self, reach, directory): def export_reach(self, reach, directory, timestamps):
name = reach.name name = reach.name
name = name.replace(" ", "-") name = name.replace(" ", "-")
if len(timestamps) == 1:
name = f"{name}_t{timestamps[0]}"
file_name = os.path.join( file_name = os.path.join(
directory, directory,
@ -606,28 +607,40 @@ class ResultsWindow(PamhyrWindow):
with open(file_name, 'w', newline='') as csvfile: with open(file_name, 'w', newline='') as csvfile:
writer = csv.writer(csvfile, delimiter=',', writer = csv.writer(csvfile, delimiter=',',
quotechar='|', quoting=csv.QUOTE_MINIMAL) quotechar='|', quoting=csv.QUOTE_MINIMAL)
writer.writerow(["name", "rk", "data-file"]) if len(timestamps) > 1:
for profile in reach.profiles: writer.writerow(["name", "rk", "data-file"])
p_file_name = os.path.join( for profile in reach.profiles:
directory, p_file_name = os.path.join(
f"cs_{profile.geometry.id}.csv" directory,
) f"cs_{profile.geometry.id}.csv"
)
writer.writerow([ writer.writerow([
profile.name, profile.name,
profile.rk, profile.rk,
p_file_name p_file_name
]) ])
self.export_profile(reach, profile, p_file_name) self.export_profile(reach,
profile,
p_file_name,
timestamps)
else:
ts = timestamps[0]
writer.writerow(self._table["raw_data"]._headers)
for row in range(self._table["raw_data"].rowCount()):
line = []
for column in range(self._table["raw_data"].columnCount()):
index = self._table["raw_data"].index(row, column)
line.append(self._table["raw_data"].data(index))
writer.writerow(line)
def export_profile(self, reach, profile, file_name): def export_profile(self, reach, profile, file_name, timestamps):
with open(file_name, 'w', newline='') as csvfile: with open(file_name, 'w', newline='') as csvfile:
writer = csv.writer(csvfile, delimiter=',', writer = csv.writer(csvfile, delimiter=',',
quotechar='|', quoting=csv.QUOTE_MINIMAL) quotechar='|', quoting=csv.QUOTE_MINIMAL)
writer.writerow(["timestamp", "z", "q"]) writer.writerow(["timestamp", "z", "q"])
timestamps = sorted(self._results.get("timestamps"))
for ts in timestamps: for ts in timestamps:
writer.writerow([ writer.writerow([
@ -635,3 +648,18 @@ class ResultsWindow(PamhyrWindow):
profile.get_ts_key(ts, "Z"), profile.get_ts_key(ts, "Z"),
profile.get_ts_key(ts, "Q"), profile.get_ts_key(ts, "Q"),
]) ])
def export_current(self):
self.file_dialog(
select_file=False,
callback=lambda d: self.export_current_to(d[0])
)
def export_current_to(self, directory):
reach = self._results.river.reachs[self._get_current_reach()]
self.export_reach(reach, directory, [self._get_current_timestamp()])
def delete_tab(self, index):
tab_widget = self.find(QTabWidget, f"tabWidget")
self._additional_plot.pop(tab_widget.tabText(index))
tab_widget.removeTab(index)

View File

@ -57,5 +57,12 @@ class ResultsTranslate(MainTranslate):
"name": _translate("Results", "Profile"), "name": _translate("Results", "Profile"),
"water_elevation": self._dict["unit_water_elevation"], "water_elevation": self._dict["unit_water_elevation"],
"discharge": self._dict["unit_discharge"], "discharge": self._dict["unit_discharge"],
"speed": self._dict["unit_speed"], "velocity": self._dict["unit_speed"],
"width": self._dict["unit_width"],
"max_depth": self._dict["unit_max_height"],
"mean_depth": self._dict["unit_mean_height"],
"wet_area": self._dict["unit_wet_area"],
"wet_perimeter": self._dict["unit_wet_perimeter"],
"hydraulic_radius": self._dict["unit_hydraulic_radius"],
"froude": self._dict["unit_froude"],
} }

View File

@ -192,7 +192,6 @@ class PamhyrPlot(APlot):
self.canvas.axes.autoscale_view(True, True, True) self.canvas.axes.autoscale_view(True, True, True)
self.canvas.axes.autoscale() self.canvas.axes.autoscale()
self.canvas.figure.tight_layout()
self.canvas.figure.canvas.draw_idle() self.canvas.figure.canvas.draw_idle()
self.toolbar_update() self.toolbar_update()
@ -205,7 +204,6 @@ class PamhyrPlot(APlot):
self.canvas.axes.autoscale_view(True, True, True) self.canvas.axes.autoscale_view(True, True, True)
self.canvas.axes.autoscale() self.canvas.axes.autoscale()
self.canvas.figure.tight_layout()
self.canvas.figure.canvas.draw_idle() self.canvas.figure.canvas.draw_idle()
self.toolbar_update() self.toolbar_update()

View File

@ -23,7 +23,7 @@ class MplCanvas(FigureCanvasQTAgg):
fig = Figure( fig = Figure(
figsize=(width, height), figsize=(width, height),
dpi=dpi, dpi=dpi,
layout='tight', layout='constrained',
) )
super(MplCanvas, self).__init__(fig) super(MplCanvas, self).__init__(fig)
@ -36,7 +36,6 @@ class MplCanvas(FigureCanvasQTAgg):
self.axes.yaxis.tick_left() self.axes.yaxis.tick_left()
self.axes.xaxis.tick_bottom() self.axes.xaxis.tick_bottom()
self.axes.spines[['top', 'right']].set_color('none') self.axes.spines[['top', 'right']].set_color('none')
self.figure.tight_layout()
self.add_arrows() self.add_arrows()
def add_arrows(self): def add_arrows(self):

View File

@ -55,16 +55,18 @@ class UnitTranslate(CommonWordTranslate):
def __init__(self): def __init__(self):
super(UnitTranslate, self).__init__() super(UnitTranslate, self).__init__()
self._dict["unit_rk"] = _translate("Unit", "River Kilometric (m)") self._dict["unit_rk"] = _translate("Unit", "River Kilometer (m)")
self._dict["unit_width"] = _translate("Unit", "Width (m)") self._dict["unit_width"] = _translate("Unit", "Width (m)")
self._dict["unit_height"] = _translate("Unit", "Height (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_diameter"] = _translate("Unit", "Diameter (m)") self._dict["unit_diameter"] = _translate("Unit", "Diameter (m)")
self._dict["unit_thickness"] = _translate("Unit", "Thickness (m)") self._dict["unit_thickness"] = _translate("Unit", "Thickness (m)")
self._dict["unit_elevation"] = _translate("Unit", "Elevation (m)") self._dict["unit_elevation"] = _translate("Unit", "Elevation (m)")
self._dict["unit_water_elevation"] = _translate( self._dict["unit_water_elevation"] = _translate(
"Unit", "Water elevation (m)" "Unit", "Water elevation (m)"
) )
self._dict["unit_speed"] = _translate("Unit", "Speed (m/s)") self._dict["unit_speed"] = _translate("Unit", "Velocity (m/s)")
self._dict["unit_discharge"] = _translate("Unit", "Discharge (m³/s)") self._dict["unit_discharge"] = _translate("Unit", "Discharge (m³/s)")
self._dict["unit_area"] = _translate("Unit", "Area (hectare)") self._dict["unit_area"] = _translate("Unit", "Area (hectare)")
@ -74,6 +76,15 @@ class UnitTranslate(CommonWordTranslate):
self._dict["unit_date_s"] = _translate("Unit", "Date (sec)") self._dict["unit_date_s"] = _translate("Unit", "Date (sec)")
self._dict["unit_date_iso"] = _translate("Unit", "Date (ISO format)") self._dict["unit_date_iso"] = _translate("Unit", "Date (ISO format)")
self._dict["unit_wet_area"] = _translate("Unit", "Wet Area (m²)")
self._dict["unit_wet_perimeter"] = _translate(
"Unit", "Wet Perimeter (m)"
)
self._dict["unit_hydraulic_radius"] = _translate(
"Unit", "Hydraulic Radius (m)"
)
self._dict["unit_froude"] = _translate("Unit", "Froude number")
class MainTranslate(UnitTranslate): class MainTranslate(UnitTranslate):
def __init__(self): def __init__(self):

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>356</width>
<height>107</height>
</rect>
</property>
<property name="windowTitle">
<string>Options</string>
</property>
<property name="accessibleName">
<string/>
</property>
<property name="locale">
<locale language="English" country="Europe"/>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Slope</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="doubleSpinBox">
<property name="decimals">
<number>6</number>
</property>
<property name="maximum">
<double>999999.998999999952503</double>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="EstimateButton">
<property name="text">
<string>Estimate</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -73,6 +73,8 @@
<addaction name="action_add"/> <addaction name="action_add"/>
<addaction name="action_del"/> <addaction name="action_del"/>
<addaction name="action_sort"/> <addaction name="action_sort"/>
<addaction name="action_generate_uniform"/>
<addaction name="action_generate_critical"/>
</widget> </widget>
<action name="action_add"> <action name="action_add">
<property name="checkable"> <property name="checkable">
@ -119,6 +121,25 @@
<string>Sort points</string> <string>Sort points</string>
</property> </property>
</action> </action>
<action name="action_generate_uniform">
<property name="text">
<string>Generate uniform</string>
</property>
<property name="iconText">
<string>Generate uniform</string>
</property>
<property name="toolTip">
<string>Generate rating curve from Manning law</string>
</property>
</action>
<action name="action_generate_critical">
<property name="text">
<string>Generate critical</string>
</property>
<property name="toolTip">
<string>Generate rating curve as Q(z) = Sqrt(g*S(z)^3/L(z))</string>
</property>
</action>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>

View File

@ -92,6 +92,7 @@
<addaction name="action_meshing"/> <addaction name="action_meshing"/>
<addaction name="action_update_rk"/> <addaction name="action_update_rk"/>
<addaction name="action_purge"/> <addaction name="action_purge"/>
<addaction name="action_shift"/>
</widget> </widget>
<action name="action_import"> <action name="action_import">
<property name="icon"> <property name="icon">
@ -226,6 +227,14 @@
<string>Purge cross-sections to keep a given number of points</string> <string>Purge cross-sections to keep a given number of points</string>
</property> </property>
</action> </action>
<action name="action_shift">
<property name="text">
<string>Shift</string>
</property>
<property name="toolTip">
<string>Shift selected sections coordinates</string>
</property>
</action>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>

View File

@ -0,0 +1,134 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>381</width>
<height>144</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="3" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="1" column="0">
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Y coordinate</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_1">
<property name="text">
<string>X coordinate</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="doubleSpinBox_Y">
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>-99999999.000000000000000</double>
</property>
<property name="maximum">
<double>99999999.000000000000000</double>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="doubleSpinBox_X">
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>-99999999.000000000000000</double>
</property>
<property name="maximum">
<double>99999999.000000000000000</double>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Z coordinate</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="doubleSpinBox_Z">
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>-99999999.000000000000000</double>
</property>
<property name="maximum">
<double>99999999.000000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -27,14 +27,14 @@
<item> <item>
<widget class="QPushButton" name="pushButton_generate_1"> <widget class="QPushButton" name="pushButton_generate_1">
<property name="text"> <property name="text">
<string>Generate from height</string> <string>Generate height</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="pushButton_generate_2"> <widget class="QPushButton" name="pushButton_generate_2">
<property name="text"> <property name="text">
<string>Generate from discharge</string> <string>Generate discharge</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -39,19 +39,6 @@
<item> <item>
<widget class="QTableView" name="tableView_profile"/> <widget class="QTableView" name="tableView_profile"/>
</item> </item>
<item>
<widget class="QSlider" name="verticalSlider_profile">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="invertedAppearance">
<bool>true</bool>
</property>
<property name="invertedControls">
<bool>true</bool>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</widget> </widget>
@ -145,7 +132,10 @@
<item row="0" column="0"> <item row="0" column="0">
<widget class="QTabWidget" name="tabWidget"> <widget class="QTabWidget" name="tabWidget">
<property name="currentIndex"> <property name="currentIndex">
<number>1</number> <number>0</number>
</property>
<property name="tabsClosable">
<bool>true</bool>
</property> </property>
<widget class="QWidget" name="tab_4"> <widget class="QWidget" name="tab_4">
<attribute name="title"> <attribute name="title">

View File

@ -2168,13 +2168,13 @@
</message> </message>
<message> <message>
<location filename="../View/ui/InitialConditions.ui" line="30"/> <location filename="../View/ui/InitialConditions.ui" line="30"/>
<source>Generate from height</source> <source>Generate height</source>
<translation>Générer pour une hauteur donnée</translation> <translation>Générer une hauteur</translation>
</message> </message>
<message> <message>
<location filename="../View/ui/InitialConditions.ui" line="37"/> <location filename="../View/ui/InitialConditions.ui" line="37"/>
<source>Generate from discharge</source> <source>Generate discharge</source>
<translation>Générer pour un débit donné</translation> <translation>Générer un débit</translation>
</message> </message>
<message> <message>
<location filename="../View/ui/InitialConditions.ui" line="98"/> <location filename="../View/ui/InitialConditions.ui" line="98"/>
@ -3057,7 +3057,7 @@
</message> </message>
<message> <message>
<location filename="../View/Translate.py" line="57"/> <location filename="../View/Translate.py" line="57"/>
<source>Height (m)</source> <source>Depth (m)</source>
<translation>Hauteur (m)</translation> <translation>Hauteur (m)</translation>
</message> </message>
<message> <message>