Compare commits

..
Author SHA1 Message Date
Theophile Terraz 1db1a543c7 add default rubar3 in config 2026-01-30 12:00:19 +01:00
Theophile Terraz 3e92a0c40d debug IC 2025-12-05 16:58:54 +01:00
Theophile Terraz f3c461cbd9 debug AdisTS 2025-12-02 13:47:56 +01:00
Theophile Terraz 012408d19e translate 2025-11-28 16:25:51 +01:00
Theophile Terraz f7e563e71a prevent scenario window to open if study is not saved 2025-11-28 16:08:54 +01:00
Theophile Terraz 81c6d39c1a debug GL in resluts 2025-11-28 15:32:15 +01:00
Theophile Terraz d682443e1d cleaning 2025-11-28 14:23:32 +01:00
Theophile Terraz c065b19ef8 debug 2025-11-26 11:46:38 +01:00
Theophile Terraz b20a4c3c3d debug meshing again... 2025-11-25 17:41:26 +01:00
Theophile Terraz d66e75c183 debug mesh AGAIN 2025-11-25 16:27:50 +01:00
Theophile Terraz 31b4c47379 debug CI 2025-11-25 13:20:57 +01:00
Theophile Terraz 71bba499e0 Revert "debug CI"
This reverts commit 7a08fa42d9.
2025-11-25 13:19:04 +01:00
Theophile Terraz 7a08fa42d9 debug CI 2025-11-25 12:03:16 +01:00
Theophile Terraz 18a008462a debug CI 2025-11-24 18:00:57 +01:00
Theophile Terraz d81da59398 debug CI 2025-11-24 17:54:33 +01:00
Theophile Terraz 3ab1eabe75 debug CI 2025-11-24 17:38:03 +01:00
Theophile Terraz cf4f860db8 debug CI 2025-11-24 17:25:51 +01:00
Theophile Terraz c68e3337ef debug CI 2025-11-24 17:09:00 +01:00
Theophile Terraz db1ae613d6 test release2 2025-11-21 16:12:53 +01:00
Theophile Terraz a800816cda test release 2025-11-21 16:01:59 +01:00
Theophile Terraz d4ef06c7c1 debug CI 2025-11-21 14:52:46 +01:00
Theophile Terraz 95d1d231c9 pep8 2025-11-21 14:07:26 +01:00
Theophile Terraz 10b278e26f Merge branch 'scenarios' of gitlab.com:pamhyr/pamhyr2 into scenarios 2025-11-21 14:05:25 +01:00
Theophile Terraz 0550eef2eb add rubarBE in release 2025-11-21 14:05:13 +01:00
parouby 73eb9919eb Merge remote-tracking branch 'origin/master' into scenarios 2025-11-21 11:23:46 +01:00
parouby 9116009b0b Geotiff: Disable add and delete for readonly mode. 2025-11-21 11:19:43 +01:00
parouby 4e4ceb5074 Geotiff: Edit: Fix readonly mode. 2025-11-21 11:14:29 +01:00
parouby d79e2ff187 Merge branch 'master' into scenarios 2025-11-21 10:55:51 +01:00
parouby d4a2594239 Wiki: Add geotiff screenshot. 2025-11-21 10:20:44 +01:00
parouby b666162bdf Merge branch 'scenario-dev-pa' into scenarios. 2025-11-20 15:44:52 +01:00
parouby d705accaed Wiki: Add scenarios screenshot. 2025-11-20 09:58:30 +01:00
Theophile Terraz d93917b081 debug internal mesher 2025-11-19 15:27:33 +01:00
Theophile Terraz ceff5f3083 debug internal mesher 2025-11-19 15:26:09 +01:00
Theophile Terraz 97762756f8 debug 2025-11-19 13:40:12 +01:00
parouby 2c49e991b4 GeoTIFF: Fix coordinates. 2025-11-19 11:46:22 +01:00
Theophile Terraz c2a8f38ef5 quick fix for AdisTS 2025-11-18 17:25:48 +01:00
parouby f8a41fce08 GeoTIFF: Add red rectangle at GeoTIFF selected. 2025-11-17 15:01:59 +01:00
parouby 624ae826eb GeoTIFF: Fix plot display where geotiff is deleted and fix update propagation. 2025-11-17 10:08:06 +01:00
parouby d47dc0687e GeoTIFF: Minor change. 2025-11-14 15:56:24 +01:00
parouby 117e5222e4 GeoTIFF: Edit: Fix event connection. 2025-11-14 15:50:24 +01:00
parouby b1c7a77f37 GeoTIFF: Fix pep8. 2025-11-14 15:18:39 +01:00
parouby 7b833390f1 Results: Add study geotiff in plot xy. 2025-11-14 15:05:00 +01:00
parouby ae08642116 GeoTIFF: Factorise draw geotiff function. 2025-11-14 15:04:31 +01:00
Theophile Terraz c1905aa7e1 debug rubarBE format 2025-11-14 11:48:02 +01:00
parouby 97ece018aa Reservoirs: Fix loading without nodes. 2025-11-14 10:01:56 +01:00
parouby 7f0102a881 GeoTIFF: Add geotiff to mainwindow tab info and fix bounds at file import. 2025-11-14 09:56:46 +01:00
parouby 786923bdbf GeoTIFF: Add missing ui files. 2025-11-13 15:08:50 +01:00
parouby deb9b2069f GeoTIFF: Integrate geotiff menu to mainwindow and fix bounds update. 2025-11-13 14:56:38 +01:00
parouby 9308a73e8e GeoTIFF: Minor change. 2025-11-13 11:47:51 +01:00
parouby 869e116ad0 GeoTIFF: Minor change. 2025-11-13 10:49:22 +01:00
parouby a2f3d22001 GeoTIFF: Add geometry and geotiff diplay next to the geotiff list. 2025-11-13 10:44:23 +01:00
parouby 2e360943b2 GeoTIFF: Switch PlotXY to display all river reaches. 2025-11-12 11:42:46 +01:00
parouby 8976f054c7 GeoTIFF: Save geotiff into study db. 2025-11-12 11:16:28 +01:00
parouby 7bce725c63 GeoTIFF: Fix pep8. 2025-11-12 10:43:20 +01:00
parouby 5c83d67865 GeoTIFF: Continue intergation. 2025-11-12 10:41:45 +01:00
parouby 6e52b1681e GeoTIFF: Continue view integration. 2025-11-11 12:26:50 +01:00
parouby f27b2cc586 GeoTIFF: Prepare model/view integration. 2025-11-10 17:10:01 +01:00
parouby 1ed5d69bf4 GeoTIFF: Prepare view integration. 2025-11-10 16:28:21 +01:00
parouby 5bb6cc40fe GeoTIFF: Add to modules list. 2025-11-10 16:04:18 +01:00
parouby 4c0a12dcf9 GeoTIFF: Integrate to River submodel. 2025-11-10 10:21:40 +01:00
parouby a308af41e0 GeoTIFF: Prepare add geotiff into pamhyr2 db. 2025-11-07 17:12:27 +01:00
parouby 4cf4015579 Results: Set import data methode default dir as results dir. 2025-11-06 09:30:29 +01:00
parouby 291b97ac9b Results: Select solver for last results also if the results path exists. 2025-11-04 16:38:35 +01:00
parouby b04e367e72 Results: Add InputDialog to select solver. 2025-11-04 16:22:11 +01:00
parouby 81d58122d6 MainWindow: Fix save callback. 2025-11-04 14:34:32 +01:00
parouby 874f592cf4 Results: Fix add data settings. 2025-11-04 14:18:00 +01:00
parouby 9b0bdd1e63 Results: Fix plot add data 'Q(t)'. 2025-11-04 10:15:37 +01:00
parouby 14549330b9 Results: Add additional data to SQL study save. 2025-11-03 16:55:36 +01:00
parouby 2487bec6d6 Results: Minor change. 2025-11-03 15:04:05 +01:00
parouby 4e1acfecdc Results: Refacto read csv. 2025-11-03 14:55:42 +01:00
parouby c63c776989 MainWindow: Add message box at results opening error. 2025-11-03 10:03:49 +01:00
parouby 44a8be6be3 WaitingDialog: Minor change. 2025-10-31 17:27:47 +01:00
parouby a5a64f2080 MainWindow: Minor change and fix. 2025-10-31 15:41:25 +01:00
parouby 16ee5a90e4 Results: Fix update table creation. 2025-10-31 15:05:38 +01:00
parouby 7ca6e69526 Pamhyr2: Add save action (ctrl+s) in each pamhyr window. 2025-10-31 14:43:01 +01:00
Theophile Terraz a31e2127e7 debug old .INI file read 2025-10-23 16:32:04 +02:00
parouby a29df8f904 Network: Center on previous size. 2025-10-21 17:28:16 +02:00
parouby 97ebf9324a Merge branch 'master' of gitlab.com:pamhyr/pamhyr2 2025-10-21 15:42:01 +02:00
parouby 0112ad63d9 Network: Resize network zone. 2025-10-21 15:38:12 +02:00
Theophile Terraz bcbb440ad4 debug import friction 2025-10-21 15:11:28 +02:00
55 changed files with 2777 additions and 558 deletions

No files matched your search

+38
View File
@@ -38,6 +38,7 @@ variables:
variables:
MAGE_8_VERSION: "permalink/latest"
ADISTS_VERSION: "permalink/latest"
RUBAR_VERSION: "permalink/latest"
dl-mage8-doc:
stage: downloads
@@ -111,6 +112,36 @@ dl-adists-windows:
paths:
- adists-windows/adists.exe
dl-rubar-linux:
stage: downloads
tags:
- linux
rules:
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
script:
- mkdir -p rubar-linux
- cd rubar-linux
- curl -L -o rubar.tgz https://forge.inrae.fr/river-hydraulics/rubarbe/-/releases/$RUBAR_VERSION/downloads/packages/rubar_linux.tgz
- tar xf rubar.tgz
artifacts:
paths:
- rubar-linux/rubarbe
dl-rubar-windows:
stage: downloads
tags:
- linux
rules:
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
script:
- mkdir -p rubar-windows
- cd rubar-windows
- curl -L -o rubar.tgz https://forge.inrae.fr/river-hydraulics/rubarbe/-/releases/$RUBAR_VERSION/downloads/packages/rubar_windows.tgz
- tar xf rubar.tgz
artifacts:
paths:
- rubar-windows/rubarbe.exe
#############
# CONFIGURE #
#############
@@ -273,6 +304,8 @@ build-linux:
needs:
- job: dl-adists-linux
artifacts: true
- job: dl-rubar-linux
artifacts: true
- job: dl-mage8-linux
artifacts: true
- job: dl-mage8-doc
@@ -314,6 +347,9 @@ build-linux:
# Copy adists
- mkdir -p pamhyr/adists
- cp -v ../adists-linux/* pamhyr/adists/
# Copy rubar
- mkdir -p pamhyr/rubar
- cp -v ../rubar-linux/* pamhyr/rubar/
# Copy Pamhyr
- cp -r dist/pamhyr/* pamhyr/
# Pamhyr script to force x11
@@ -379,6 +415,8 @@ build-windows:
artifacts: true
- job: dl-adists-windows
artifacts: true
- job: dl-rubar-windows
artifacts: true
- job: dl-mage8-doc
artifacts: true
- job: set-version
Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

+4
View File
@@ -46,6 +46,10 @@ copy /y ..\mage8-windows\mage_extraire.exe pamhyr\mage8\
copy /y ..\mage8-windows\mailleurTT.exe pamhyr\mage8\
copy /y ..\mage8-windows\libbief.dll pamhyr\mage8\
rem rubar
mkdir pamhyr\rubar
copy /y ..\adists-windows\rubarbe.exe pamhyr\rubar\
rem adists
mkdir pamhyr\adists
copy /y ..\adists-windows\adists.exe pamhyr\adists\
+83 -68
View File
@@ -40,20 +40,24 @@ class InternalMeshing(AMeshingTool):
step: float = 50,
limites=[-1, -1],
linear: bool = True):
if reach is None or len(reach.profiles) == 0:
return reach
if limites[0] > limites[1]:
lim = limites[1]
limites[1] = limites[0]
limites[0] = lim
elif limites[0] == limites[1]:
return reach
# pre process
# we never modify original profiles, we work on copies
m_profiles = self.st_to_m(reach, limites)
new_profiles = self.interpolate_transversal_step(m_profiles,
step)
profiles = self.get_profiles(reach, limites)
if profiles is None:
return []
for p in profiles:
if len(p) < 1:
return []
guide_list = self.get_guide_list(reach, profiles)
if guide_list is None:
return []
# we never modify original profiles, we work on the copies
m_profiles = self.st_to_m(profiles, guide_list)
new_profiles = self.interpolate_transversal_step(m_profiles, step)
for new_profiles2 in new_profiles:
for p in new_profiles2:
@@ -61,52 +65,31 @@ class InternalMeshing(AMeshingTool):
return new_profiles
def st_to_m(self, reach, limites):
def st_to_m(self, profiles, guide_list):
gl = reach.compute_guidelines()
profiles = [reach.profile(i).copy()
for i in range(limites[0], limites[1]+1)
]
# we make sure that the lines are in the left-to-right order
guide_list = [
x.name
for x in profiles[0].named_points()
if x.name in gl[0]
]
gl1 = [""] + guide_list
gl2 = guide_list + [""]
max_values = [0] * (len(guide_list) + 1)
max_values_index = [0] * (len(guide_list) + 1)
guide_list = ["un"] + guide_list + ["np"]
max_values = [0] * (len(guide_list) - 1)
max_values_index = [0] * (len(guide_list) - 1)
# we work between each couple of guidelines
for j, p in enumerate(profiles):
index1 = 0
index2 = p.named_point_index(guide_list[0])
if index2 - index1 > max_values[0]:
max_values[0] = index2 - index1
max_values_index[0] = j
for i in range(len(guide_list) - 1):
index1 = p.named_point_index(guide_list[i])
index2 = p.named_point_index(guide_list[i+1])
if index2 - index1 > max_values[i+1]:
max_values[i+1] = index2 - index1
max_values_index[i+1] = j
index1 = p.named_point_index(guide_list[-1])
index2 = len(p) - 1
if index2 - index1 > max_values[-1]:
max_values[-1] = index2 - index1
max_values_index[-1] = j
if index2 - index1 > max_values[i]:
max_values[i] = index2 - index1
max_values_index[i] = j
for i in range(len(max_values)):
for isect in range(max_values_index[i], len(profiles)-1):
self.compl_sect(profiles[isect],
profiles[isect+1],
gl1[i], gl2[i])
guide_list[i],
guide_list[i+1])
for isect in reversed(range(0, max_values_index[i])):
self.compl_sect(profiles[isect+1],
profiles[isect],
gl1[i], gl2[i])
guide_list[i],
guide_list[i+1])
return profiles
def interpolate_transversal_step(self,
@@ -156,26 +139,17 @@ class InternalMeshing(AMeshingTool):
# len1 and 2: number of intervals
# len1: target len
if tag1 == '': # left end point
start1 = 0
start2 = 0
else:
start1 = sect1.named_point_index(tag1)
start2 = sect2.named_point_index(tag1)
start1 = sect1.named_point_index(tag1)
start2 = sect2.named_point_index(tag1)
if tag2 == '': # right end point
end1 = sect1.nb_points-1
end2 = sect2.nb_points-1
else:
end1 = sect1.named_point_index(tag2)
end2 = sect2.named_point_index(tag2)
end1 = sect1.named_point_index(tag2)
end2 = sect2.named_point_index(tag2)
len1 = end1 - start1
len2 = end2 - start2
if len1 < len2:
self.compl_sect(sect2, sect1, start2, start1,
len2, len1, tag1, tag2)
self.compl_sect(sect2, sect1, tag1, tag2)
return
elif len1 == len2:
return
@@ -194,23 +168,21 @@ class InternalMeshing(AMeshingTool):
for i in range(len2):
ltot2 += sect2.point(start2+i).dist(sect2.point(start2+i+1))
beta.append(ltot2)
beta = list(map(lambda x: x/ltot2, beta)) # current ratios
for i in range(len1 - len2):
beta.append(1.0)
beta.append(0.0) # beta[-1]
if len2 < 1 or ltot2 < 0.0001:
# unique point (copy len1 times)
# we are at an edge of the profile
for i in range(len1-len2):
p = sect2.point(start2).copy()
sect2.insert_point(start2, p)
if tag1 == '': # left end point
sect2.point(start2).name = ''
if tag2 == '': # left end point
sect2.point(start2+1).name = ''
len2 += 1
elif ltot1 < 0.0001:
sect2.add_npoints(len1-len2)
len2 = len1
else: # regular case
beta = list(map(lambda x: x/ltot2, beta)) # current ratios
for i in range(len1 - len2):
beta.append(1.0)
beta.append(0.0) # beta[-1]
# loop on the points to insert
for k in range(len1-len2):
trouve = False
@@ -269,8 +241,10 @@ class InternalMeshing(AMeshingTool):
p = sect2.point(start2+len2).copy()
sect2.insert_point(start2+len2, p)
sect2.point(start2+len2).name = ''
sect2.point(start2).name = tag1
sect2.point(start2+len2).name = tag2
if tag1 != "un":
sect2.point(start2).name = tag1
if tag1 != "np":
sect2.point(start2+len2).name = tag2
sect2.modified()
def update_rk(self, reach, begin_rk, end_rk,
@@ -304,6 +278,8 @@ class InternalMeshing(AMeshingTool):
sgn = -1.0
else:
sgn = old_orientation
if abs(sgn) < 0.5:
sgn = 1.0
new_rk = [0.0]*nprof
new_rk[origin] = origin_value
@@ -344,3 +320,42 @@ class InternalMeshing(AMeshingTool):
return new_rk, new_end_rk, new_begin_rk
else:
return new_rk, new_begin_rk, new_end_rk
def get_guide_list(self, reach, profiles):
gl = reach.compute_guidelines()
# we make sure that the lines are in the left-to-right order
guide_list = [
[
x.name
for x in p.named_points()
if x.name in gl[0]
]
for p in profiles]
# we make sure the lines never cross
for gl in guide_list:
for i in range(len(guide_list[0])):
if gl[i] != guide_list[0][i]:
return None
return guide_list[0]
def get_profiles(self, reach, limites):
if reach is None or len(reach.profiles) == 0:
return None
if limites[0] > limites[1]:
lim = limites[1]
limites[1] = limites[0]
limites[0] = lim
elif limites[0] == limites[1]:
return None
profiles = [reach.profile(i).copy()
for i in range(limites[0], limites[1]+1)
]
return profiles
+368
View File
@@ -0,0 +1,368 @@
# GeoTIFF.py -- Pamhyr
# Copyright (C) 2024-2025 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import os
import struct
import logging
from functools import reduce
from tools import trace, timer
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
from Model.Scenario import Scenario
try:
import rasterio
import rasterio.control
import rasterio.crs
import rasterio.sample
import rasterio.vrt
import rasterio._features
from rasterio.io import MemoryFile
_rasterio_loaded = True
except Exception as e:
print(f"Module 'rasterio' is not available: {e}")
_rasterio_loaded = False
logger = logging.getLogger()
class GeoTIFF(SQLSubModel):
_sub_classes = []
def __init__(self, id: int = -1, enabled=True,
name="", description="",
path="", coordinates=None,
status=None, owner_scenario=-1):
super(GeoTIFF, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
self._enabled = enabled
self._name = f"GeoTIFF #{self._pamhyr_id}" if name == "" else name
self._description = description
self._file_bytes = b''
if coordinates is None:
self._coordinates = {
"bottom": 0.0,
"top": 0.0,
"left": 0.0,
"right": 0.0,
}
else:
self._coordinates = coordinates
self._file_name = ""
if path != "":
self.read_file(path)
self._memfile = None
def __getitem__(self, key):
value = None
if key == "enabled":
value = self._enabled
elif key == "name":
value = self.name
elif key == "description":
value = self.description
elif key == "file_name":
value = self.file_name
elif key == "coordinates":
value = self.coordinates
elif key == "coordinates_bottom":
value = self.coordinates["bottom"]
elif key == "coordinates_top":
value = self.coordinates["top"]
elif key == "coordinates_left":
value = self.coordinates["left"]
elif key == "coordinates_right":
value = self.coordinates["right"]
elif key == "memfile":
value = self.memfile
return value
def __setitem__(self, key, value):
if key == "enabled":
self.enabled = value
elif key == "name":
self.name = value
elif key == "description":
self.description = value
elif key == "file_name":
if self._file_name != value:
self.read_file(value)
elif key == "coordinates":
self.coordinates = value
elif key == "coordinates_bottom" or key == "bottom":
self.coordinates["bottom"] = value
elif key == "coordinates_top" or key == "top":
self.coordinates["top"] = value
elif key == "coordinates_left" or key == "left":
self.coordinates["left"] = value
elif key == "coordinates_right" or key == "right":
self.coordinates["right"] = value
self.modified()
@property
def enabled(self):
return self._enabled
@enabled.setter
def enabled(self, enabled):
self._enabled = enabled
self.modified()
def is_enabled(self):
return self._enabled
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = name
self.modified()
@property
def description(self):
return self._description
@description.setter
def description(self, description):
self._description = description
self.modified()
@property
def file_name(self):
return self._file_name
@file_name.setter
def file_name(self, file_name):
self._file_name = file_name
self.modified()
@property
def coordinates(self):
return self._coordinates
@coordinates.setter
def coordinates(self, coordinates):
self._coordinates = coordinates
self.modified()
@property
def coord_bottom(self):
if self._coordinates is None:
return 0.0
return self._coordinates["bottom"]
@property
def coord_top(self):
if self._coordinates is None:
return 0.0
return self._coordinates["top"]
@property
def coord_left(self):
if self._coordinates is None:
return 0.0
return self._coordinates["left"]
@property
def coord_right(self):
if self._coordinates is None:
return 0.0
return self._coordinates["right"]
@property
def memfile(self):
if not _rasterio_loaded:
return None
if self._file_bytes == b'':
return None
if self._memfile is None:
self._memfile = MemoryFile()
self._memfile.write(self._file_bytes)
return self._memfile
def read_file(self, path):
logger.debug(f"Read GeoTIFF file at : '{path}'")
self._file_name = path
self._file_bytes = b''
self._memfile = None
nbytes = 0
with open(path, "rb") as f:
while True:
data = f.read(4096)
if not data:
break
nbytes += len(data)
self._file_bytes += data
logger.debug(f"Read GeoTIFF: {nbytes} bytes readed")
def write_file(self, path):
with open(path, "w+b") as f:
f.write(self._file_bytes)
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE geotiff{ext} (
{cls.create_db_add_pamhyr_id()},
enabled BOOLEAN NOT NULL,
deleted BOOLEAN NOT NULL DEFAULT FALSE,
name TEXT NOT NULL,
description TEXT NOT NULL,
file_name TEXT NOT NULL,
file_bytes BLOB NOT NULL,
coordinates_bottom REAL NOT NULL,
coordinates_top REAL NOT NULL,
coordinates_left REAL NOT NULL,
coordinates_right REAL NOT NULL,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
PRIMARY KEY(pamhyr_id, scenario)
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
major, minor, release = version.strip().split(".")
if major == "0" and int(minor) < 2:
cls._db_create(execute)
if major == "0" and int(minor) == 2:
if int(release) < 3:
cls._db_create(execute)
return True
@classmethod
def _db_load(cls, execute, data=None):
new = []
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, enabled, deleted, " +
"name, description, file_name, file_bytes, " +
"coordinates_bottom, coordinates_top, " +
"coordinates_left, coordinates_right, " +
"scenario " +
"FROM geotiff " +
f"WHERE scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
)
if table is not None:
for row in table:
it = iter(row)
id = next(it)
enabled = (next(it) == 1)
deleted = (next(it) == 1)
name = next(it)
description = next(it)
file_name = next(it)
file_bytes = next(it)
coordinates_bottom = next(it)
coordinates_top = next(it)
coordinates_left = next(it)
coordinates_right = next(it)
owner_scenario = next(it)
f = cls(
id=id, enabled=enabled, name=name,
description=description, coordinates={
"bottom": coordinates_bottom,
"top": coordinates_top,
"left": coordinates_left,
"right": coordinates_right,
},
status=data['status'],
owner_scenario=owner_scenario
)
if deleted:
f.set_as_deleted()
f._file_bytes = file_bytes
loaded.add(id)
new.append(f)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
execute(
"INSERT INTO geotiff (" +
"pamhyr_id, enabled, deleted, " +
"name, description, file_name, file_bytes, " +
"coordinates_bottom, coordinates_top, " +
"coordinates_left, coordinates_right, " +
"scenario) " +
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
self._pamhyr_id,
self._enabled,
self.is_deleted(),
self.name,
self.description,
self.file_name,
self._file_bytes,
self.coordinates['bottom'],
self.coordinates['top'],
self.coordinates['left'],
self.coordinates['right'],
self._status.scenario_id,
)
return True
+59
View File
@@ -0,0 +1,59 @@
# GeoTIFFList.py -- Pamhyr
# Copyright (C) 2024-2025 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
from tools import trace, timer
from Model.Except import NotImplementedMethodeError
from Model.Tools.PamhyrListExt import PamhyrModelList
from Model.GeoTIFF.GeoTIFF import GeoTIFF
class GeoTIFFList(PamhyrModelList):
_sub_classes = [GeoTIFF]
@classmethod
def _db_load(cls, execute, data=None):
new = cls(status=data["status"])
new._lst = GeoTIFF._db_load(execute, data)
return new
def _db_save(self, execute, data=None):
ok = True
# Delete previous data
execute(
"DELETE FROM geotiff " +
f"WHERE scenario = {self._status.scenario_id}"
)
for gt in self._lst:
ok &= gt._db_save(execute, data)
return ok
@property
def files(self):
return self.lst
def new(self, index):
n = GeoTIFF(status=self._status)
self.insert(index, n)
self._status.modified()
return n
+15 -4
View File
@@ -76,12 +76,23 @@ class Profile(object):
return self.points[index]
def named_point(self, name):
return next((p for p in self.points if p.name == name), None)
if name == "un":
return self.points[0]
elif name == "np":
return self.points[-1]
else:
return next((p for p in self.points if p.name == name), None)
def named_point_index(self, name):
return next(
(p for p in enumerate(self.points) if p[1].name == name), None
)[0]
if name == "un":
return 0
elif name == "np":
return len(self.points) - 1
else:
return next(
(p for p in enumerate(self.points) if p[1].name == name),
None
)[0]
@property
def reach(self):
+1 -3
View File
@@ -1101,11 +1101,9 @@ class ProfileXYZ(Profile, SQLSubModel):
self.point(i+1).z = 0.5 * self.point(i).z + 0.5 * self.point(i+2).z
def copy(self):
p = ProfileXYZ(id=self.id,
name=self.name,
p = ProfileXYZ(name=self.name,
rk=self.rk,
reach=self.reach,
num=self.num,
status=self._status)
for i, k in enumerate(self.points):
p.insert_point(i, k.copy())
+2
View File
@@ -195,7 +195,9 @@ class Reach(SQLSubModel):
"""
if profile in self._profiles:
self.undelete([profile])
profile.num = index
else:
profile.num = index
self._profiles.insert(index, profile)
self.modified()
@@ -537,7 +537,10 @@ class InitialConditions(SQLSubModel):
strickler = 25.0
if not compute_discharge:
discharge = data_discharge[profile.rk]
if profile.rk in data_discharge:
discharge = data_discharge[profile.rk]
else:
discharge = 0.0
else:
discharge = (
# ((width * 0.8)
@@ -605,7 +608,10 @@ class InitialConditions(SQLSubModel):
strickler = 25.0
if not compute_height:
height = data_height[profile.rk]
if profile.rk in data_height:
height = data_height[profile.rk]
else:
height = 0.0
else:
if abs(incline) <= 0:
height = 0.0
@@ -656,7 +662,10 @@ class InitialConditions(SQLSubModel):
for profile in profiles:
if not compute_discharge:
d = data_discharge[profile.rk]
if profile.rk in data_discharge:
d = data_discharge[profile.rk]
else:
d = 0.0
else:
d = discharge
elevation = interp(profile.rk,
+17 -2
View File
@@ -307,6 +307,21 @@ class Pollutants(SQLSubModel):
def data(self):
return self._data
@data.setter
def data(self, data):
self._data = [PollutantCharacteristics(type=int(data[0]),
diametre=float(data[1]),
rho=float(data[2]),
porosity=float(data[3]),
cdc_riv=float(data[4]),
cdc_cas=float(data[5]),
apd=float(data[6]),
ac=float(data[7]),
bc=float(data[8]),
pollutant=self,
status=self._status
)]
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
@@ -397,7 +412,7 @@ class Pollutants(SQLSubModel):
data["pollutant"] = new_pollutant
new_pollutant._data = PollutantCharacteristics._db_load(
execute, data.copy()
execute, data=data
)
loaded.add(pid)
@@ -421,7 +436,7 @@ class Pollutants(SQLSubModel):
f"AND scenario = {self._status.scenario_id}"
)
execute(
f"DELETE FROM pollutants_characteristics " +
"DELETE FROM pollutants_characteristics " +
f"WHERE pollutant = {self.id} " +
f"AND scenario = {self._status.scenario_id}"
)
+1 -1
View File
@@ -310,7 +310,7 @@ class Reservoir(SQLSubModel):
new_reservoir.set_as_deleted()
new_reservoir._node = None
if node_id != -1:
if node_id != -1 and node_id is not None:
new_reservoir._node = next(
filter(
lambda n: n.id == node_id, data["nodes"]
+141 -1
View File
@@ -29,8 +29,134 @@ from Model.Results.River.River import River
logger = logging.getLogger()
class AdditionalData(SQLSubModel):
_sub_classes = []
def __init__(self, id=-1, study=None, data=None):
super(AdditionalData, self).__init__(
id=id, status=study.status,
owner_scenario=study.status.scenario.id
)
self._study = study
self._data = data
@property
def data(self):
return self._data
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE results_add_data{ext} (
{cls.create_db_add_pamhyr_id()},
result INTEGER NOT NULL,
type_x TEXT NOT NULL,
type_y TEXT NOT NULL,
legend TEXT NOT NULL,
unit TEXT NOT NULL,
data_len INTEGER NOT NULL,
x BLOB NOT NULL,
y BLOB NOT NULL,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(result) REFERENCES results(pamhyr_id),
PRIMARY KEY(pamhyr_id, result, scenario)
)
""")
if ext != "":
return True
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
major, minor, release = version.strip().split(".")
if major == "0" and int(minor) == 2 and int(release) <= 1:
cls._db_create(execute)
return cls._update_submodel(execute, version, data)
@classmethod
def _db_load(cls, execute, data=None):
new = []
study = data['study']
status = data['status']
scenario = data["scenario"]
table = execute(
"SELECT pamhyr_id, type_x, type_y, " +
"legend, unit, data_len, x, y, " +
"scenario " +
"FROM results_add_data " +
f"WHERE scenario = {scenario.id}"
)
if table is None:
return new
for v in table:
it = iter(v)
pid = next(it)
type_x = next(it)
type_y = next(it)
legend = next(it)
unit = next(it)
data_len = next(it)
bx = next(it)
by = next(it)
owner_scenario = next(it)
data_format = ">" + ''.join(itertools.repeat("d", data_len))
x = struct.unpack(data_format, bx)
y = struct.unpack(data_format, by)
data = {
'type_x': type_x,
'type_y': type_y,
'legend': legend,
'unit': unit,
'x': x, 'y': y
}
new_data = cls(study=study)
new_data._data = data
new.append(new_data)
return new
def _db_save(self, execute, data=None):
if self._status.scenario.id != self._owner_scenario:
return
pid = self._pamhyr_id
data_len = len(self._data["x"])
data_format = ">" + ''.join(itertools.repeat("d", data_len))
bx = struct.pack(data_format, *self._data["x"])
by = struct.pack(data_format, *self._data["y"])
execute(
"INSERT INTO " +
"results_add_data (pamhyr_id, result, " +
"type_x, type_y, " +
"legend, unit, data_len, x, y, " +
"scenario) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
self._pamhyr_id, data["result"],
self._data["type_x"], self._data["type_y"],
self._data["legend"], self._data["unit"],
data_len, bx, by, self._owner_scenario
)
return True
class Results(SQLSubModel):
_sub_classes = [River]
_sub_classes = [River, AdditionalData]
def __init__(self, id=-1, study=None, solver=None,
repertory="", name="0"):
@@ -50,6 +176,7 @@ class Results(SQLSubModel):
# Keep results creation date
"creation_date": datetime.now(),
"study_revision": study.status.version,
"additional_data": [],
}
if solver is not None:
@@ -184,6 +311,11 @@ class Results(SQLSubModel):
data["timestamps"] = sorted(ts)
new_results._river = River._db_load(execute, data)
new_results.set(
"additional_data",
AdditionalData._db_load(execute, data)
)
yield (solver_type, new_results)
def _db_save_clear(self, execute, solver_type, data=None):
@@ -206,6 +338,11 @@ class Results(SQLSubModel):
f"WHERE scenario = {self._owner_scenario} " +
f"AND result = {pid}"
)
execute(
"DELETE FROM results_add_data " +
f"WHERE scenario = {self._owner_scenario} " +
f"AND result = {pid}"
)
def _db_save(self, execute, data=None):
if self._status.scenario.id != self._owner_scenario:
@@ -238,4 +375,7 @@ class Results(SQLSubModel):
data["result"] = self._pamhyr_id
self._river._db_save(execute, data)
for add_data in self.get("additional_data"):
add_data._db_save(execute, data)
return True
+3
View File
@@ -50,6 +50,9 @@ class Results(object):
self._phys_var_list = ["C", "G", "M", "D", "L", "N", "R"]
if solver is not None:
self.set("solver_type", solver._type)
@property
def date(self):
date = self._meta_data["creation_date"]
-10
View File
@@ -105,16 +105,6 @@ class Profile(SQLSubModel):
@classmethod
def _db_update(cls, execute, version, data=None):
major, minor, release = version.strip().split(".")
create = False
if major == "0" and int(minor) < 2:
cls._db_create(execute)
create = True
if major == "0" and int(minor) == 2:
if int(release) < 1 and not create:
cls._db_create(execute)
create = True
return cls._update_submodel(execute, version, data)
+13
View File
@@ -59,6 +59,7 @@ from Model.LateralContributionsAdisTS.LateralContributionsAdisTSList \
import LateralContributionsAdisTSList
from Model.D90AdisTS.D90AdisTSList import D90AdisTSList
from Model.DIFAdisTS.DIFAdisTSList import DIFAdisTSList
from Model.GeoTIFF.GeoTIFFList import GeoTIFFList
from Model.Results.Results import Results
logger = logging.getLogger()
@@ -468,6 +469,7 @@ class River(Graph):
LateralContributionsAdisTSList,
D90AdisTSList,
DIFAdisTSList,
GeoTIFFList,
Results
]
@@ -505,6 +507,8 @@ class River(Graph):
self._D90AdisTS = D90AdisTSList(status=self._status)
self._DIFAdisTS = DIFAdisTSList(status=self._status)
self._geotiff = GeoTIFFList(status=self._status)
self._results = {}
@classmethod
@@ -617,6 +621,8 @@ class River(Graph):
new._DIFAdisTS = DIFAdisTSList._db_load(execute, data)
new._geotiff = GeoTIFFList._db_load(execute, data)
return new
def _db_load_results(self, execute, data=None):
@@ -650,6 +656,8 @@ class River(Graph):
objs.append(self._D90AdisTS)
objs.append(self._DIFAdisTS)
objs.append(self._geotiff)
for solv_type in self.results:
objs.append(self.results[solv_type])
@@ -726,6 +734,7 @@ class River(Graph):
self._BoundaryConditionsAdisTS,
self._LateralContributionsAdisTS,
self._D90AdisTS, self._DIFAdisTS,
self._geotiff,
]
for solver in self._parameters:
@@ -818,6 +827,10 @@ Last export at: @date."""
def additional_files(self):
return self._additional_files
@property
def geotiff(self):
return self._geotiff
@property
def rep_lines(self):
return self._rep_lines
+1 -1
View File
@@ -31,7 +31,7 @@ class Scenario(SQLSubModel):
id: int = -1,
name: str = "",
description: str = "",
x: int = 0.0, y: int = 0.0,
x: int = 1000.0, y: int = 1000.0,
revision: int = 0,
parent=None):
super(Scenario, self).__init__()
+1 -1
View File
@@ -37,7 +37,7 @@ logger = logging.getLogger()
class Study(SQLModel):
_version = "0.2.1"
_version = "0.2.3"
_sub_classes = [
Scenario,
+6
View File
@@ -56,6 +56,7 @@ class Modules(IterableFlag):
SEDIMENT_LAYER = auto()
ADDITIONAL_FILES = auto()
OUTPUT_RK = auto()
GEOTIFF = auto()
# Results
RESULTS = auto()
@@ -81,6 +82,7 @@ class Modules(IterableFlag):
cls.RESULTS,
cls.WINDOW_LIST,
cls.OUTPUT_RK,
cls.GEOTIFF
]
@classmethod
@@ -99,6 +101,7 @@ class Modules(IterableFlag):
| cls.HYDRAULIC_STRUCTURES
| cls.RESERVOIR
| cls.SEDIMENT_LAYER
| cls.GEOTIFF
)
@classmethod
@@ -114,6 +117,7 @@ class Modules(IterableFlag):
cls.HYDRAULIC_STRUCTURES,
cls.RESERVOIR,
cls.SEDIMENT_LAYER,
cls.GEOTIFF,
]
@classmethod
@@ -129,6 +133,7 @@ class Modules(IterableFlag):
cls.HYDRAULIC_STRUCTURES: "Hydraulic structures",
cls.RESERVOIR: "Reservoir",
cls.SEDIMENT_LAYER: "Sediment layer",
cls.GEOTIFF: "GeoTIFF",
}
def impact(self):
@@ -168,4 +173,5 @@ _impact = {
Modules.HYDRAULIC_STRUCTURES: [],
Modules.RESERVOIR: [],
Modules.SEDIMENT_LAYER: [],
Modules.GEOTIFF: [],
}
+68 -6
View File
@@ -96,6 +96,69 @@ class AdisTS(CommandLineSolver):
return lst
##########
# Export #
##########
_alph = list(
map(
chr,
chain(
range(48, 58), # 0..9
range(65, 91), # A..Z
range(97, 123) # a..z
)
)
)
_l_alph = len(_alph)
_nodes_cnt = 0
_nodes_names = {}
_nodes_views = set()
def get_reach_name(self, reach):
return f"Reach_{reach.pamhyr_id:>3}".replace(" ", "0")
def get_node_name(self, node):
"""Generate a 3 char name for node
Args:
node: The node
Returns:
A 3 char name string
"""
n = node.pamhyr_id
if n in self._nodes_names:
return self._nodes_names[n]
name = ""
checked_new = False
while not checked_new:
self._nodes_cnt += 1
nc = self._nodes_cnt
name = "".join(
map(
lambda i: self._alph[i % self._l_alph],
[
int(nc / (self._l_alph * self._l_alph)),
int(nc / self._l_alph),
nc
]
)
)
checked_new = name not in self._nodes_views
self._nodes_views.add(name)
self._nodes_names[n] = name
return name
def cmd_args(self, study):
lst = super(AdisTS, self).cmd_args(study)
return lst
@@ -197,7 +260,7 @@ class AdisTS(CommandLineSolver):
_nodes_views = set()
def get_reach_name(self, reach):
return f"Reach_{reach.id:>3}".replace(" ", "0")
return f"Reach_{reach.pamhyr_id:>3}".replace(" ", "0")
def get_node_name(self, node):
"""Generate a 3 char name for node
@@ -208,7 +271,7 @@ class AdisTS(CommandLineSolver):
Returns:
A 3 char name string
"""
n = node.id
n = node.pamhyr_id
# print("node name id: ", n)
@@ -338,9 +401,9 @@ class AdisTSwc(AdisTS):
) as f:
for LC in POL_LC:
reach = next(filter(
lambda edge: edge.id == LC.edge, study.river.edges()
lambda edge: edge.id == LC.edge, study.river.enable_edges()
)) # .name
reach_name = self.get_reach_name(self, reach)
reach_name = self.get_reach_name(reach)
f.write(f"${reach_name} {LC.begin_rk} {LC.end_rk}\n")
f.write(f"*temps |débit massique (kg/s)\n")
f.write(f"*---------++++++++++\n")
@@ -365,8 +428,7 @@ class AdisTSwc(AdisTS):
node = next(filter(
lambda x: x.id == BC.node, study.river._nodes
))
node_name = f"{node.id:3}".replace(" ", "x")
f.write(f"${node_name}\n")
f.write(f"${self.get_node_name(node)}\n")
if BC.type == "Concentration":
f.write(f"*temps |concentration\n")
+16 -10
View File
@@ -169,9 +169,8 @@ class Rubar3(CommandLineSolver):
it = iter(params)
line = 0
while line < 27:
param = next(it)
param = next(it, None)
while param != None:
name = param.name
value = param.value
@@ -204,7 +203,7 @@ class Rubar3(CommandLineSolver):
# New line
f.write(f"\n")
line += 1
param = next(it, None)
def _export_ts(self, study, repertory, qlog, name="0"):
if qlog is not None:
@@ -575,8 +574,12 @@ class Rubar3(CommandLineSolver):
for bc in bcs:
f.write(f"{len(bc)}\n")
for d0, d1 in bc.data:
f.write(f"{d1} {d0}\n")
if bc.bctype == "ZD":
for d0, d1 in bc.data:
f.write(f"{d1} {d0}\n")
else:
for d0, d1 in bc.data:
f.write(f"{d0} {d1}\n")
def read_profil(self, study, fname, results, qlog=None, name="0"):
logger.info(f"read: profil.{name}")
@@ -590,7 +593,10 @@ class Rubar3(CommandLineSolver):
def read_data_line(f):
line = f.readline().split()
return tuple(map(float, line))
if len(line) > 3:
return tuple(map(float, line))
else:
return (0.0, 0.0, 0.0, 0.0)
def set_and_compute_limites(reach, ind, z, q, s):
reach.set(ind, timestamp, "Z", z)
@@ -628,9 +634,9 @@ class Rubar3(CommandLineSolver):
h, s, q, z = read_data_line(f)
set_and_compute_limites(
reach, ind,
(ph + pz + ph + h) / 2,
(pz + s) / 2,
(pq + q) / 2
(pz + z + ph + h) / 2,
(pq + q) / 2,
(ps + s) / 2
)
ph = h
+5 -1
View File
@@ -58,7 +58,11 @@ logger = logging.getLogger()
class TableModel(PamhyrTableModel):
def get_true_data_row(self, row):
bc = self._data.data[row]
if len(self._data.data) > 0:
bc = self._data.data[row]
else:
return 0
return next(
map(
+3 -1
View File
@@ -17,6 +17,7 @@
# -*- coding: utf-8 -*-
import logging
import traceback
from tools import trace, timer
@@ -92,7 +93,8 @@ class ReplWindow(PamhyrWindow):
value = exec(rich_code)
value = self.__debug_exec_result__
except Exception as e:
value = f"<font color=\"red\">" + str(e) + "</font>"
value = f"<font color=\"red\">" + str(e) + "</font>\n"
value += f"<font color=\"grey\">{traceback.format_exc()}</font>"
# Display code
msg = f"<font color=\"grey\"> # " + code + " #</font>"
+5 -6
View File
@@ -274,15 +274,14 @@ class FrictionTableModel(PamhyrTableModel):
with open(file_name, encoding="utf-8") as rug_file:
for line in rug_file:
if line.upper().startswith("K"):
line = line.split()
if int(line[1]) == reach_id:
data.append(line[1:])
if int(line[1:4]) == reach_id:
data.append(line[4:].split())
new_data = []
for d in data:
new = None
minor = float(d[3])
medium = float(d[4])
minor = float(d[2])
medium = float(d[3])
for s in self._study.river.stricklers.stricklers:
if s.minor == minor and s.medium == medium:
new = s
@@ -292,5 +291,5 @@ class FrictionTableModel(PamhyrTableModel):
self._study.river.stricklers))
new.minor = minor
new.medium = medium
new_data.append([self._data, float(d[1]), float(d[2]), new, new])
new_data.append([self._data, float(d[0]), float(d[1]), new, new])
self.replace_data(new_data)
+296
View File
@@ -0,0 +1,296 @@
# Window.py -- Pamhyr
# Copyright (C) 2024-2025 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import logging
from Modules import Modules
from View.Tools.PamhyrWindow import PamhyrWindow
from PyQt5.QtWidgets import (
QLabel, QPlainTextEdit, QPushButton, QCheckBox,
QFileDialog, QVBoxLayout, QDoubleSpinBox,
)
from PyQt5.QtCore import (
QSettings
)
from View.GeoTIFF.Translate import GeoTIFFTranslate
from View.GeoTIFF.UndoCommand import (
SetCommand
)
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
from View.Tools.Plot.PamhyrCanvas import MplCanvas
from View.PlotXY import PlotXY
try:
import rasterio
import rasterio.control
import rasterio.crs
import rasterio.sample
import rasterio.vrt
import rasterio._features
from rasterio.io import MemoryFile
_rasterio_loaded = True
except Exception as e:
print(f"Module 'rasterio' is not available: {e}")
_rasterio_loaded = False
logger = logging.getLogger()
class EditGeoTIFFWindow(PamhyrWindow):
_pamhyr_ui = "GeoTIFF"
_pamhyr_name = "Edit GeoTIFF"
def __init__(self, study=None, config=None, geotiff=None,
trad=None, undo=None, parent=None):
super(EditGeoTIFFWindow, self).__init__(
title=self._pamhyr_name,
study=study,
config=config,
trad=trad,
options=[],
parent=parent
)
self._geotiff = geotiff
self._file_name = geotiff.file_name
self._hash_data.append(self._geotiff)
self._undo = undo
self.setup_values()
self.setup_graph()
self.setup_connection()
def setup_graph(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas")
self.plot_layout = self.find(QVBoxLayout,
"verticalLayout_geotiff")
self._toolbar = PamhyrPlotToolbar(
self.canvas, self,
items=["home", "zoom", "save", "iso", "back/forward", "move"]
)
self.plot_layout.addWidget(self._toolbar)
self.plot_layout.addWidget(self.canvas)
self.plot = PlotXY(
canvas=self.canvas,
data=self._study.river.enable_edges(),
trad=self._trad,
toolbar=None,
parent=self
)
self.plot.update()
self._plot_img = None
memfile = self._geotiff.memfile
if memfile is not None:
self.draw_geotiff(memfile=memfile)
def setup_values(self):
self.set_check_box("checkBox", self._geotiff.enabled)
self.set_line_edit_text("lineEdit_name", self._geotiff.name)
self.set_line_edit_text("lineEdit_description",
self._geotiff.description)
bounds = list(self._geotiff.coordinates.values())
self._set_values_from_bounds(bounds)
self._set_default_values_from_bounds(bounds)
self._reset_spinboxes()
if self._study.is_read_only():
self.set_check_box_enable("checkBox", False)
self.set_line_edit_enable("lineEdit_name", False)
self.set_line_edit_enable("lineEdit_description", False)
for button in ["import", "bottom", "top", "left", "right"]:
self.find(QPushButton, f"pushButton_{button}")\
.setEnabled(False)
for spin in ["bottom", "top", "left", "right"]:
self.find(QDoubleSpinBox, f"doubleSpinBox_{spin}")\
.setEnabled(False)
def _set_values_from_bounds(self, bounds):
self._values = {
"bottom": bounds[0],
"top": bounds[1],
"left": bounds[2],
"right": bounds[3],
}
def _set_default_values_from_bounds(self, bounds):
self._values_default = {
"bottom": bounds[0],
"top": bounds[1],
"left": bounds[2],
"right": bounds[3],
}
def _reset_spinboxes(self):
for key in self._values:
self._reset_spinbox(key)
def _reset_spinbox(self, key):
self.set_double_spin_box(
f"doubleSpinBox_{key}", self._values_default[key]
)
def setup_connection(self):
self.find(QPushButton, "pushButton_cancel")\
.clicked.connect(self.close)
self.find(QPushButton, "pushButton_ok")\
.clicked.connect(self.accept)
self.find(QPushButton, "pushButton_import")\
.clicked.connect(self._import)
self.find(QPushButton, "pushButton_bottom")\
.clicked.connect(lambda: self._reset_spinbox("bottom"))
self.find(QPushButton, "pushButton_top")\
.clicked.connect(lambda: self._reset_spinbox("top"))
self.find(QPushButton, f"pushButton_left")\
.clicked.connect(lambda: self._reset_spinbox("left"))
self.find(QPushButton, f"pushButton_right")\
.clicked.connect(lambda: self._reset_spinbox("right"))
self.find(QDoubleSpinBox, f"doubleSpinBox_bottom")\
.valueChanged.connect(
lambda: self.update_values_from_spinbox("bottom")
)
self.find(QDoubleSpinBox, f"doubleSpinBox_top")\
.valueChanged.connect(
lambda: self.update_values_from_spinbox("top")
)
self.find(QDoubleSpinBox, f"doubleSpinBox_left")\
.valueChanged.connect(
lambda: self.update_values_from_spinbox("left")
)
self.find(QDoubleSpinBox, f"doubleSpinBox_right")\
.valueChanged.connect(
lambda: self.update_values_from_spinbox("right")
)
def update_values_from_spinbox(self, key):
self._values[key] = self.get_double_spin_box(f"doubleSpinBox_{key}")
left = self._values["left"]
right = self._values["right"]
bottom = self._values["bottom"]
top = self._values["top"]
self._plot_img.set_extent((left, right, bottom, top))
self.plot.idle()
def draw_geotiff(self, memfile=None):
if not _rasterio_loaded:
return
if memfile is None:
if self._file_name == "":
return
with rasterio.open(self._file_name) as data:
img = data.read()
b = data.bounds[:] # left, bottom, right, top
if b[2] > b[0] and b[1] < b[3]:
coord = [b[1], b[3], b[0], b[2]]
else:
xlim = self.canvas.axes.get_xlim()
ylim = self.canvas.axes.get_ylim()
coord = ylim + xlim
self._set_values_from_bounds(coord)
self._set_default_values_from_bounds(coord)
else:
with memfile.open() as gt:
img = gt.read()
if self._plot_img is not None:
self._plot_img.remove()
left = self._values["left"]
right = self._values["right"]
bottom = self._values["bottom"]
top = self._values["top"]
self._plot_img = self.canvas.axes.imshow(
img.transpose((1, 2, 0)),
extent=(left, right, bottom, top)
)
self.plot.idle()
self._reset_spinboxes()
def _import(self):
options = QFileDialog.Options()
settings = QSettings(QSettings.IniFormat,
QSettings.UserScope, 'MyOrg', )
options |= QFileDialog.DontUseNativeDialog
file_types = [
self._trad["file_geotiff"],
self._trad["file_all"],
]
filename, _ = QFileDialog.getOpenFileName(
self,
self._trad["open_file"],
"",
";; ".join(file_types),
options=options
)
if filename != "":
self._file_name = filename
self.draw_geotiff()
def accept(self):
if self._study.is_editable():
is_enabled = self.get_check_box("checkBox")
name = self.get_line_edit_text("lineEdit_name")
description = self.get_line_edit_text("lineEdit_description")
coord_bottom = self.get_double_spin_box("doubleSpinBox_bottom")
coord_top = self.get_double_spin_box("doubleSpinBox_top")
coord_left = self.get_double_spin_box("doubleSpinBox_left")
coord_right = self.get_double_spin_box("doubleSpinBox_right")
self._undo.push(
SetCommand(
self._geotiff, enabled=is_enabled,
name=name, description=description,
coordinates_bottom=coord_bottom,
coordinates_top=coord_top,
coordinates_left=coord_left,
coordinates_right=coord_right,
file_name=self._file_name,
)
)
self._propagate_update(key=Modules.GEOTIFF)
self.close()
+96
View File
@@ -0,0 +1,96 @@
# List.py -- Pamhyr
# Copyright (C) 2024-2025 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import logging
from functools import reduce
from tools import trace, timer
from PyQt5.QtCore import (
Qt, QVariant,
)
from PyQt5.QtGui import (
QColor, QBrush,
)
from View.Tools.PamhyrList import PamhyrListModel
from View.GeoTIFF.UndoCommand import (
AddCommand, DelCommand
)
logger = logging.getLogger()
class ListModel(PamhyrListModel):
def get_true_data_row(self, row):
el = self._data.get(row)
return next(
map(
lambda e: e[0],
filter(
lambda e: e[1] == el,
enumerate(self._data._lst)
)
), 0
)
def data(self, index, role):
row = index.row()
column = index.column()
file = self._data.files[row]
if role == Qt.ForegroundRole:
color = Qt.gray
if file.is_enabled():
color = QColor("black")
else:
color = QColor("grey")
return QBrush(color)
if role == Qt.ItemDataRole.DisplayRole:
text = f"{file.name}: '{file.description}'"
if not file.is_enabled():
text += " (disabled)"
return text
return QVariant()
def add(self, row):
row = self.get_true_data_row(row)
self._undo.push(
AddCommand(
self._data, row
)
)
self.update()
def delete(self, row):
self._undo.push(
DelCommand(
self._data, self._data.files[row]
)
)
self.update()
+35
View File
@@ -0,0 +1,35 @@
# Translate.py -- Pamhyr
# Copyright (C) 2024-2025 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
from PyQt5.QtCore import QCoreApplication
from View.Translate import MainTranslate
_translate = QCoreApplication.translate
class GeoTIFFTranslate(MainTranslate):
def __init__(self):
super(GeoTIFFTranslate, self).__init__()
self._dict["GeoTIFF files"] = _translate(
"GeoTIFF", "GeoTIFF files"
)
self._dict["Edit additional file"] = _translate(
"GeoTIFF", "Edit GeoTIFF file"
)
+81
View File
@@ -0,0 +1,81 @@
# UndoCommand.py -- Pamhyr
# Copyright (C) 2024-2025 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
from tools import trace, timer
from PyQt5.QtWidgets import (
QMessageBox, QUndoCommand, QUndoStack,
)
class SetCommand(QUndoCommand):
def __init__(self, geotiff, **kwargs):
QUndoCommand.__init__(self)
self._geotiff = geotiff
self._new = kwargs
self._old = None
def undo(self):
f = self._geotiff
for key in self._old:
f[key] = self._old[key]
def redo(self):
f = self._geotiff
if self._old is None:
self._old = {}
for key in self._new:
self._old[key] = f[key]
for key in self._new:
f[key] = self._new[key]
class AddCommand(QUndoCommand):
def __init__(self, files, row):
QUndoCommand.__init__(self)
self._files = files
self._row = row
self._new = None
def undo(self):
self._new.set_as_deleted()
def redo(self):
if self._new is None:
self._new = self._files.new(self._row)
else:
self._new.set_as_not_deleted()
class DelCommand(QUndoCommand):
def __init__(self, files, line):
QUndoCommand.__init__(self)
self._files = files
self._line = line
def undo(self):
self._line.set_as_not_deleted()
def redo(self):
self._line.set_as_deleted()
+220
View File
@@ -0,0 +1,220 @@
# Window.py -- Pamhyr
# Copyright (C) 2024-2025 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
from tools import trace, timer
from PyQt5.QtWidgets import (
QAction, QListView, QVBoxLayout,
)
from matplotlib.patches import Rectangle
from Modules import Modules
from View.Tools.PamhyrWindow import PamhyrWindow
from View.GeoTIFF.List import ListModel
from View.GeoTIFF.Translate import GeoTIFFTranslate
from View.GeoTIFF.Edit.Window import EditGeoTIFFWindow
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
from View.Tools.Plot.PamhyrCanvas import MplCanvas
from View.PlotXY import PlotXY
try:
import rasterio
import rasterio.control
import rasterio.crs
import rasterio.sample
import rasterio.vrt
import rasterio._features
from rasterio.io import MemoryFile
_rasterio_loaded = True
except Exception as e:
print(f"Module 'rasterio' is not available: {e}")
_rasterio_loaded = False
class GeoTIFFListWindow(PamhyrWindow):
_pamhyr_ui = "GeoTIFFList"
_pamhyr_name = "GeoTIFF files"
def __init__(self, study=None, config=None,
parent=None):
trad = GeoTIFFTranslate()
name = trad[self._pamhyr_name] + " - " + study.name
super(GeoTIFFListWindow, self).__init__(
title=name,
study=study,
config=config,
trad=trad,
options=[],
parent=parent
)
self.setup_list()
self.setup_graph()
self.setup_connections()
def setup_list(self):
lst = self.find(QListView, "listView")
self._list = ListModel(
list_view=lst,
data=self._study.river.geotiff,
undo=self._undo_stack,
trad=self._trad,
)
def setup_graph(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas")
self._plot_layout = self.find(QVBoxLayout, "verticalLayout")
self._toolbar = PamhyrPlotToolbar(
self.canvas, self,
items=["home", "zoom", "save", "iso", "back/forward", "move"]
)
self._plot_layout.addWidget(self._toolbar)
self._plot_layout.addWidget(self.canvas)
self._plot = PlotXY(
canvas=self.canvas,
data=self._study.river.enable_edges(),
geotiff=self._study.river.geotiff,
trad=self._trad,
toolbar=None,
parent=self
)
self._plot_rect = []
self._plot.update()
def setup_connections(self):
if self._study.is_editable():
self.find(QAction, "action_add").triggered.connect(self.add)
self.find(QAction, "action_delete").triggered.connect(self.delete)
else:
self.find(QAction, "action_add").setEnabled(False)
self.find(QAction, "action_delete").setEnabled(False)
self.find(QAction, "action_edit").triggered.connect(self.edit)
self.find(QListView, "listView")\
.selectionModel()\
.selectionChanged\
.connect(self._update_rectangle)
def _propagated_update(self, key=Modules(0)):
if Modules.GEOMETRY not in key and Modules.GEOTIFF not in key:
return
self.update()
def update(self):
self._list.update()
self._plot.update()
self._update_rectangle()
def _update_rectangle(self):
for rect in self._plot_rect:
rect.remove()
self._plot_rect = []
rows = self.selected_rows()
if len(rows) <= 0:
return
for row in rows:
files = self._study.river.geotiff.files
if len(files) <= row:
continue
geotiff = files[row]
coord = geotiff.coordinates
xy = (coord["left"], coord["bottom"])
width = abs(coord["right"] - coord["left"])
height = abs(coord["top"] - coord["bottom"])
rect = Rectangle(
xy, width, height,
edgecolor='red', facecolor='none',
lw=2
)
self._plot_rect.append(rect)
self.canvas.axes.add_patch(
rect
)
self._plot.idle()
def selected_rows(self):
lst = self.find(QListView, f"listView")
return list(map(lambda i: i.row(), lst.selectedIndexes()))
def add(self):
rows = self.selected_rows()
if len(rows) > 0:
row = rows[0]
else:
row = 0
self._list.add(row)
def delete(self):
rows = self.selected_rows()
if len(rows) == 0:
return
self._list.delete(rows[0])
self.update()
def edit(self):
rows = self.selected_rows()
for row in rows:
geotiff = self._study.river.geotiff.files[row]
if self.sub_window_exists(
EditGeoTIFFWindow,
data=[self._study, self._config, geotiff]
):
continue
win = EditGeoTIFFWindow(
study=self._study,
config=self._config,
geotiff=geotiff,
trad=self._trad,
undo=self._undo_stack,
parent=self,
)
win.show()
def _undo(self):
self._undo_stack.undo()
self.update()
def _redo(self):
self._undo_stack.redo()
self.update()
+13 -1
View File
@@ -251,11 +251,23 @@ class GeometryReachTableModel(PamhyrTableModel):
self.layoutChanged.emit()
def meshing(self, mesher, data, tableView):
new_profiles = mesher.meshing(
self._data,
**data
)
if new_profiles is None:
return
if len(new_profiles) == 0:
return
self.layoutAboutToBeChanged.emit()
self._undo.push(
MeshingCommand(
self._data, mesher, data, tableView
self._data, new_profiles, data, tableView
)
)
+2 -9
View File
@@ -274,15 +274,14 @@ class UpdateRKCommand(QUndoCommand):
class MeshingCommand(QUndoCommand):
def __init__(self, reach, mesher, data, tableView):
def __init__(self, reach, new_profiles, data, tableView):
QUndoCommand.__init__(self)
self._reach = reach
self._data = data
self._limites = data["limites"]
self._mesher = mesher
self._new_profiles = None
self._new_profiles = new_profiles
self._new_profiles_indexes = None
self._tableView = tableView
self._indexes = tableView.selectionModel().selection()
@@ -308,12 +307,6 @@ class MeshingCommand(QUndoCommand):
)
def redo(self):
if self._new_profiles is None:
self._new_profiles = self._mesher.meshing(
self._reach,
**self._data
)
if self._new_profiles_indexes is None:
k = self._limites[0]
self._new_profiles_indexes = []
+21 -3
View File
@@ -330,9 +330,27 @@ class InitialConditionTableModel(PamhyrTableModel):
if not (line.startswith("#") or
line.startswith("*") or
line.startswith("$")):
line = line.split()
if int(line[0]) == reach_id:
data.append([line[4], line[2], line[3]])
line_split = line.split()
formated = False
if len(line_split[0]) > 3:
formated = True
elif len(line_split[2]) > 10:
formated = True
elif len(line_split[3]) > 11:
formated = True
elif len(line_split[4]) > 9:
formated = True
if formated: # old PamHyr format
if int(line[1:4]) == reach_id:
data.append([line[31:40],
line[10:20],
line[20:31]])
else:
if int(line_split[0]) == reach_id:
data.append([line_split[4],
line_split[2],
line_split[3]])
self._undo.push(
ReplaceDataCommand(
+4
View File
@@ -224,6 +224,8 @@ class InitialConditionsWindow(PamhyrWindow):
def _update_plot(self):
self.plot_1.draw()
self.plot_2.draw()
self.plot_1.idle()
self.plot_2.idle()
def _propagated_update(self, key=Modules(0)):
if Modules.GEOMETRY not in key:
@@ -320,10 +322,12 @@ class InitialConditionsWindow(PamhyrWindow):
def _import_from_results(self, results):
logger.debug(f"import from results: {results}")
self._table.import_from_results(results)
self._update()
def _import_from_ini_file(self, file_name):
logger.debug(f"import from INI file: {file_name}")
self._table.read_from_ini(file_name)
self._update()
def move_up(self):
row = self.index_selected_row()
+133 -19
View File
@@ -47,7 +47,7 @@ from PyQt5.QtWidgets import (
QMainWindow, QApplication, QAction,
QFileDialog, QShortcut, QMenu, QToolBar,
QMessageBox, QProgressDialog, QTabWidget,
QDialog, QVBoxLayout, QLabel,
QDialog, QVBoxLayout, QLabel, QInputDialog,
)
from PyQt5.uic import loadUi
@@ -79,6 +79,7 @@ from View.Frictions.Window import FrictionsWindow
from View.SedimentLayers.Window import SedimentLayersWindow
from View.SedimentLayers.Reach.Window import ReachSedimentLayersWindow
from View.AdditionalFiles.Window import AddFileListWindow
from View.GeoTIFF.Window import GeoTIFFListWindow
from View.REPLines.Window import REPLineListWindow
from View.SolverParameters.Window import SolverParametersWindow
from View.RunSolver.Window import (
@@ -157,7 +158,7 @@ define_model_action = [
"action_menu_boundary_conditions_sediment",
"action_menu_rep_additional_lines", "action_menu_output_rk",
"action_menu_run_adists", "action_menu_pollutants",
"action_menu_d90", "action_menu_dif",
"action_menu_d90", "action_menu_dif", "action_menu_edit_geotiff"
]
action = (
@@ -276,8 +277,8 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
"action_menu_new": self.open_new_study,
"action_menu_edit": self.open_edit_study,
"action_menu_open": self.open_model,
"action_menu_save": self.save_study,
"action_menu_save_as": self.save_as_study,
"action_menu_save": lambda: self.save_study(),
"action_menu_save_as": lambda: self.save_as_study(),
"action_menu_numerical_parameter": self.open_solver_parameters,
"action_menu_edit_scenarios": self.open_scenarios,
"action_menu_edit_network": self.open_network,
@@ -297,6 +298,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
self.open_reach_sediment_layers,
"action_menu_additional_file": self.open_additional_files,
"action_menu_rep_additional_lines": self.open_rep_lines,
"action_menu_edit_geotiff": self.open_geotiff,
"action_menu_close": self.close_model,
"action_menu_results_last": self.open_last_results,
"action_menu_open_results_from_file": self.open_results_from_file,
@@ -313,7 +315,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
"action_menu_about": self.open_about,
# ToolBar action
"action_toolBar_open": self.open_model,
"action_toolBar_save": self.save_study,
"action_toolBar_save": lambda: self.save_study(),
"action_toolBar_close": self.close_model,
"action_toolBar_run_solver": self.run_lasest_solver,
# Current actions
@@ -606,6 +608,16 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
return None
def get_last_results(self, solver):
if self._study is None:
return None
results = self._study.results
if solver in results:
return self._study.results[solver]
return None
@last_results.setter
def last_results(self, results):
if self._study is None:
@@ -637,7 +649,10 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
logger.info(f"Open Study - {self._study.name}")
self.set_title()
def save_study(self):
def _save(self, source):
self.save_study(progress_parent=source)
def save_study(self, progress_parent=None):
"""Save current study
Save current study, if study as no associate file, open a
@@ -667,11 +682,15 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
self._backup_timer.blockSignals(True)
self._save_mutex.lock()
parent = self
if progress_parent is not None:
parent = progress_parent
sql_request_count = self._study.sql_save_request_count()
progress = QProgressDialog(
"Saving...", None,
0, sql_request_count,
parent=self
parent=parent
)
progress.setWindowModality(Qt.WindowModal)
progress.setValue(0)
@@ -684,6 +703,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
progress=lambda: progress.setValue(progress.value() + 1)
)
progress.close()
status += " Done"
logger.info(status)
self.statusbar.showMessage(status, 3000)
@@ -758,6 +778,8 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
progress.setValue(progress.value() + 1)
progress.close()
def save_as_study_single_scenario(self, sid=-1):
sql_request_count = self._study.sql_save_request_count()
@@ -782,6 +804,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
)
status += " Done"
progress.close()
logger.info(status)
self.statusbar.showMessage(status, 3000)
@@ -1183,6 +1206,31 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
Returns:
Nothing
"""
if self._study is None:
return
if self._study.filename is None or self._study.filename == "":
self.message_box(
window_title=self._trad["Warning"],
text=self._trad["mb_unsaved_study_title"],
informative_text=self._trad["mb_unsaved_studyh_msg"]
)
options = QFileDialog.Options()
options |= QFileDialog.DontUseNativeDialog
file_name, _ = QFileDialog.getSaveFileName(
self, "Save File",
"", "Pamhyr(*.pamhyr)",
options=options,
)
if file_name is None or file_name == "":
return
if file_name.rsplit(".", 1)[-1] == "pamhyr":
self._study.filename = file_name
else:
self._study.filename = file_name + ".pamhyr"
if self._study is not None:
if self.sub_window_exists(
ScenariosWindow,
@@ -1339,6 +1387,19 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
)
self.additonal_files.show()
def open_geotiff(self):
if self._study is not None:
if self.sub_window_exists(
GeoTIFFListWindow,
data=[self._study, None]
):
return
self.geotiff = GeoTIFFListWindow(
study=self._study, parent=self
)
self.geotiff.show()
def open_rep_lines(self):
if self._study is not None:
if self.sub_window_exists(
@@ -1523,14 +1584,14 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
# If no specific results, get last results
if results is None:
def reading_fn():
self._tmp_results = self.last_results
self._tmp_results = solver.results(
self._study,
self._solver_workdir(solver),
)
if self.last_results is None:
if solver == self._last_solver:
def reading_fn():
self._tmp_results = solver.results(
self._study,
self._solver_workdir(solver),
)
self._tmp_results = self.last_results
# Open from file
if type(results) is str:
@@ -1555,13 +1616,16 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
)
dlg.exec_()
results = self._tmp_results
# self.last_results = results
# No results available
if results is None:
self.msg_open_results_no_results()
return
# results does not have values, for example if geometry missmatch
if not results.is_valid:
self.msg_open_results_invalid_results()
return
if results.get('study_revision') != self._study.status.version:
@@ -1587,6 +1651,20 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
)
res.show()
def msg_open_results_no_results(self):
self.message_box(
window_title=self._trad["Warning"],
text=self._trad["mb_open_results_title"],
informative_text=self._trad["mb_open_results_no_results_msg"]
)
def msg_open_results_invalid_results(self):
self.message_box(
window_title=self._trad["Error"],
text=self._trad["mb_open_results_title"],
informative_text=self._trad["mb_open_results_invalid_results_msg"]
)
def open_solver_results_adists(self, solver, results=None):
def reading_fn():
self._tmp_results = results
@@ -1615,8 +1693,11 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
os.path.dirname(results),
name=name
)
dlg = ReadingResultsDialog(reading_fn=reading_fn, parent=self)
dlg = WaitingDialog(
payload_fn=reading_fn,
title="waiting_result",
parent=self
)
dlg.exec_()
results = self._tmp_results
@@ -1659,16 +1740,49 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
return workdir
def is_solver_workdir_exists(self, solver, scenario=None):
return os.path.exists(
self._solver_workdir(solver, scenario)
)
def open_last_results(self):
if self._last_solver is None:
return
solver_type = self._study.results
solver_name, ok = QInputDialog.getItem(
self, self._trad['Solver'],
self._trad['Solver'] + ":",
list(
map(
lambda s: s.name,
filter(
lambda s: (self.is_solver_workdir_exists(s)
or s._type in solver_type),
self.conf.solvers
)
)
)
)
if not ok:
return
solver = next(
filter(
lambda s: s.name == solver_name,
self.conf.solvers
)
)
if self._last_solver._type == "mage8":
self.open_solver_results(self._last_solver,
self.last_results)
self.open_solver_results(
solver, # self.last_results
)
elif self._last_solver._type == "adistswc":
self.open_solver_results_adists(self._last_solver,
self.last_results)
self.open_solver_results_adists(
solver, # self.last_results
)
def open_results_from_file(self):
if self._study is None:
+16
View File
@@ -27,6 +27,20 @@ from View.Tools.PamhyrWidget import PamhyrWidget
from PyQt5.QtWidgets import QVBoxLayout
try:
import rasterio
import rasterio.control
import rasterio.crs
import rasterio.sample
import rasterio.vrt
import rasterio._features
from rasterio.io import MemoryFile
_rasterio_loaded = True
except Exception as e:
print(f"Module 'rasterio' is not available: {e}")
_rasterio_loaded = False
logger = logging.getLogger()
@@ -104,10 +118,12 @@ class WidgetInfo(PamhyrWidget):
self.plot = PlotXY(
canvas=self.canvas,
data=self._study.river.enable_edges(),
geotiff=self._study.river.geotiff,
trad=self.parent._trad,
toolbar=self._toolbar_xy,
parent=self
)
self.plot.update()
def set_network_values(self):
+65 -6
View File
@@ -27,12 +27,26 @@ from PyQt5.QtCore import (
)
from PyQt5.QtWidgets import QApplication, QTableView
try:
import rasterio
import rasterio.control
import rasterio.crs
import rasterio.sample
import rasterio.vrt
import rasterio._features
from rasterio.io import MemoryFile
_rasterio_loaded = True
except Exception as e:
print(f"Module 'rasterio' is not available: {e}")
_rasterio_loaded = False
_translate = QCoreApplication.translate
class PlotXY(PamhyrPlot):
def __init__(self, canvas=None, trad=None, data=None, toolbar=None,
table=None, parent=None):
def __init__(self, canvas=None, trad=None, data=None, geotiff=None,
toolbar=None, table=None, parent=None):
super(PlotXY, self).__init__(
canvas=canvas,
trad=trad,
@@ -43,10 +57,14 @@ class PlotXY(PamhyrPlot):
)
self._data = data
self._geotiff = geotiff
self.label_x = self._trad["x"]
self.label_y = self._trad["y"]
self.parent = parent
self._plot_img = {}
self._isometric_axis = True
self._auto_relim_update = True
@@ -69,7 +87,12 @@ class PlotXY(PamhyrPlot):
if data.reach.number_profiles != 0:
self.draw_xy(data.reach)
self.draw_lr(data.reach)
self.idle()
if self._geotiff is not None:
self.draw_geotiff(self._geotiff.files)
self.idle()
return
def draw_xy(self, reach):
@@ -78,9 +101,9 @@ class PlotXY(PamhyrPlot):
line_xy.append(np.column_stack(xy))
line_xy_collection = collections.LineCollection(
line_xy,
colors=self.color_plot_river_bottom
)
line_xy,
colors=self.color_plot_river_bottom
)
self.canvas.axes.add_collection(line_xy_collection)
def draw_lr(self, reach):
@@ -113,6 +136,42 @@ class PlotXY(PamhyrPlot):
)
self.line_lr.append(line)
def draw_geotiff(self, lst):
if not _rasterio_loaded:
return
for img in self._plot_img:
self._plot_img[img].remove()
self._plot_img = {}
for geotiff in lst:
if geotiff.is_deleted():
continue
memfile = geotiff.memfile
if memfile is None:
continue
with memfile.open() as gt:
img = gt.read()
coords = geotiff.coordinates
left = coords["left"]
right = coords["right"]
bottom = coords["bottom"]
top = coords["top"]
self._plot_img[geotiff] = self.canvas.axes.imshow(
img.transpose((1, 2, 0)),
extent=(left, right, bottom, top)
)
if not geotiff.is_enabled():
self._plot_img[geotiff].set(alpha=0.5)
self.idle()
@timer
def update(self):
self.draw()
+1 -1
View File
@@ -80,7 +80,7 @@ class AddCommand(QUndoCommand):
self._new = self._pollutants_lst.new(
self._pollutants_lst, self._index
)
self._new._data = [[0, 0., 0., 0., 0., 0., 0., 0., 0.]]
self._new.data = [0, 0., 0., 0., 0., 0., 0., 0., 0.]
self._new_ic_adists = self._data.new(self._index, self._new.id)
else:
self._pollutants_lst.insert(self._index, self._new)
+12 -3
View File
@@ -149,8 +149,17 @@ class PlotAC(PamhyrPlot):
self.annotation = []
self.cgl, self.igl = reach.geometry.compute_guidelines()
lcomplete = list(self.cgl)
lincomplete = list(self.igl)
# we make sure that the lines are in the left-to-right order
lcomplete = [
x.name
for x in profile.geometry.named_points()
if x.name in self.cgl
]
lincomplete = [
x.name
for x in profile.geometry.named_points()
if x.name in self.igl
]
self.color_complete_gl = self.colors
self.color_incomplete_gl = 2 * ["grey"]
@@ -170,7 +179,7 @@ class PlotAC(PamhyrPlot):
]
else:
color = self.color_incomplete_gl[
lincomplete.index(txt)
lincomplete.index(txt) % len(self.color_incomplete_gl)
]
annotation = self.canvas.axes.annotate(
+53
View File
@@ -31,6 +31,20 @@ from PyQt5.QtCore import (
)
from PyQt5.QtWidgets import QApplication, QTableView
try:
import rasterio
import rasterio.control
import rasterio.crs
import rasterio.sample
import rasterio.vrt
import rasterio._features
from rasterio.io import MemoryFile
_rasterio_loaded = True
except Exception as e:
print(f"Module 'rasterio' is not available: {e}")
_rasterio_loaded = False
_translate = QCoreApplication.translate
logger = logging.getLogger()
@@ -52,6 +66,8 @@ class PlotXY(PamhyrPlot):
self.line_gl = []
self.overflow = []
self._plot_img = {}
self._timestamps = parent._timestamps
self._current_timestamp = max(self._timestamps)
self._current_reach_id = reach_id
@@ -153,6 +169,7 @@ class PlotXY(PamhyrPlot):
reach = results.river.reach(self._current_reach_id)
reaches = results.river.reachs
self.draw_geotiff()
self.draw_profiles(reach, reaches)
self.draw_water_elevation(reach)
self.draw_water_elevation_max(reach)
@@ -166,6 +183,7 @@ class PlotXY(PamhyrPlot):
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)
@@ -306,6 +324,41 @@ class PlotXY(PamhyrPlot):
alpha=0.7
)
def draw_geotiff(self):
if not _rasterio_loaded:
return
lst = self._data[0]._study.river._geotiff.lst
for img in self._plot_img:
self._plot_img[img].remove()
self._plot_img = {}
for geotiff in lst:
memfile = geotiff.memfile
if memfile is None:
return
with memfile.open() as gt:
img = gt.read()
coords = geotiff.coordinates
left = coords["left"]
right = coords["right"]
bottom = coords["bottom"]
top = coords["top"]
self._plot_img[geotiff] = self.canvas.axes.imshow(
img.transpose((1, 2, 0)),
extent=(left, right, bottom, top)
)
if not geotiff.is_enabled():
self._plot_img[geotiff].set(alpha=0.5)
self.idle()
def set_reach(self, reach_id):
self._current_reach_id = reach_id
self._current_profile_id = 0
+94 -138
View File
@@ -31,23 +31,14 @@ except Exception as e:
print(f"Module 'rasterio' is not available: {e}")
_rasterio_loaded = False
try:
import pyaudio
import wave
_audio_loaded = True
except Exception as e:
print(f"Either 'pyaudio' or 'wave' is not available: {e}")
_audio_loaded = False
from functools import reduce
import numpy as np
from numpy import sqrt
from datetime import datetime
from tools import trace, timer, logger_exception
from View.Tools.PamhyrWindow import PamhyrWindow
from View.WaitingDialog import WaitingDialog
from PyQt5.QtGui import (
QKeySequence, QIcon, QPixmap,
@@ -65,9 +56,12 @@ from PyQt5.QtWidgets import (
QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate,
QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
QSlider, QLabel, QWidget, QGridLayout, QTabBar, QInputDialog
QSlider, QLabel, QWidget, QGridLayout, QTabBar,
QInputDialog,
)
from Model.Results.Results import AdditionalData
from View.Tools.Plot.PamhyrCanvas import MplCanvas
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
@@ -153,11 +147,14 @@ class ResultsWindow(PamhyrWindow):
profile_id=[0])
self.update_table_selection_solver(0)
self.update_plot_additional_data()
def setup_table(self):
self._table = {}
for t in ["reach", "profile", "raw_data", "solver"]:
table = self.find(QTableView, f"tableView_{t}")
self._table[t] = TableModel(
table_view=table,
table_headers=self._trad.get_dict(f"table_headers_{t}"),
@@ -166,8 +163,11 @@ class ResultsWindow(PamhyrWindow):
opt_data=t,
parent=self
)
self._table[t]._timestamp = self._timestamps[
self._slider_time.value()]
self._slider_time.value()
]
if len(self._results) <= 1:
table = self.find(QTableView, f"tableView_solver")
table.hide()
@@ -198,12 +198,15 @@ class ResultsWindow(PamhyrWindow):
def setup_plots(self):
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.toolbar = PamhyrPlotToolbar(
self.canvas, self, items=[
@@ -211,6 +214,7 @@ class ResultsWindow(PamhyrWindow):
"iso", "back/forward"
]
)
self.plot_layout = self.find(QVBoxLayout, "verticalLayout")
self.plot_layout.addWidget(self.toolbar)
self.plot_layout.addWidget(self.canvas)
@@ -358,8 +362,7 @@ class ResultsWindow(PamhyrWindow):
"action_add": self._add_custom_plot,
"action_export": self._export,
# "action_export": self.export_current,
"action_import_data": self.import_data,
"action_play_sound": self.play_sound
"action_import_data": self.import_data
}
if _rasterio_loaded:
actions["action_Geo_tiff"] = self.import_geotiff
@@ -367,12 +370,6 @@ class ResultsWindow(PamhyrWindow):
self.find(QAction, "action_Geo_tiff")\
.setEnabled(False)
if _audio_loaded:
actions["action_play_sound"] = self.play_sound
else:
self.find(QAction, "action_play_sound")\
.setVisible(False)
if len(self._results) > 1:
self.find(QAction, "action_reload").setEnabled(False)
@@ -975,7 +972,7 @@ class ResultsWindow(PamhyrWindow):
map(
lambda p:
p.get_ts_key(timestamp, "V") /
np.sqrt(9.81 * (
sqrt(9.81 * (
p.geometry.wet_area(
p.get_ts_key(timestamp, "Z")) /
p.geometry.wet_width(
@@ -989,7 +986,7 @@ class ResultsWindow(PamhyrWindow):
map(
lambda p:
p.get_ts_key(timestamp, "V") /
np.sqrt(9.81 * (
sqrt(9.81 * (
p.geometry.wet_area(
p.get_ts_key(timestamp, "Z")) /
p.geometry.wet_width(
@@ -1002,7 +999,7 @@ class ResultsWindow(PamhyrWindow):
map(
lambda p:
p.get_ts_key(timestamp, "V") /
np.sqrt(9.81 * (
sqrt(9.81 * (
p.geometry.wet_area(
p.get_ts_key(timestamp, "Z")) /
p.geometry.wet_width(
@@ -1138,7 +1135,7 @@ class ResultsWindow(PamhyrWindow):
if self._current_results != 2:
fr = my_dict[dict_y["froude"]] = list(
map(lambda z, v:
v / np.sqrt(9.81 * (
v / sqrt(9.81 * (
profile.geometry.wet_area(z) /
profile.geometry.wet_width(z))
), z, v)
@@ -1147,7 +1144,7 @@ class ResultsWindow(PamhyrWindow):
fr1 = list(
map(lambda z, v:
v /
np.sqrt(9.81 * (
sqrt(9.81 * (
profile1.geometry.wet_area(z) /
profile1.geometry.wet_width(z))
), z1, v1)
@@ -1155,7 +1152,7 @@ class ResultsWindow(PamhyrWindow):
fr2 = list(
map(lambda z, v:
v /
np.sqrt(9.81 * (
sqrt(9.81 * (
profile2.geometry.wet_area(z) /
profile2.geometry.wet_width(z))
), z2, v2)
@@ -1233,8 +1230,7 @@ class ResultsWindow(PamhyrWindow):
extent=[b[0], b[2], b[1], b[3]])
else:
dlg = CoordinatesDialog(
xlim,
ylim,
xlim, ylim,
trad=self._trad,
parent=self
)
@@ -1247,7 +1243,6 @@ class ResultsWindow(PamhyrWindow):
return
def import_data(self):
file_types = [
self._trad["file_csv"],
self._trad["file_all"],
@@ -1258,41 +1253,61 @@ class ResultsWindow(PamhyrWindow):
callback=lambda f: self.read_csv_file(f[0]),
default_suffix=".csv",
file_filter=file_types,
directory=self._results[self._current_results[0]]._repertory,
)
def read_csv_file(self, filename):
if filename == "":
return
sep = " "
x, y = self.read_csv_file_data(filename)
data = self.read_csv_file_format(x, y)
def is_float(string):
if string.replace(".", "").isnumeric():
return True
else:
return False
results = self._results[self._current_results[0]]
data_lst = results.get("additional_data")
data_lst.append(
AdditionalData(
study=self._study,
data=data
)
)
self.update_plot_additional_data()
def read_csv_file_data(self, filename):
sep = ","
x = []
y = []
with open(filename, 'r', newline='') as f:
lines = f.readlines()
x = []
y = []
for line in lines:
if line[0] != "*" and line[0] != "#" and line[0] != "$":
row = line.split(sep)
if len(row) >= 2:
if is_float(row[0]) and is_float(row[1]):
x.append(float(row[0]))
y.append(float(row[1]))
if len(row) < 2:
continue
try:
fx, fy = float(row[0]), float(row[1])
x.append(fx)
y.append(fy)
except Exception as e:
continue
return x, y
def read_csv_file_format(self, x, y):
data_type_lst = ['Q(t)', 'Z(t)', 'Z(x)']
data_type, ok = QInputDialog.getItem(
self, 'Data type', 'Chose the type of data:', data_type_lst)
self, 'Data type',
'Chose the type of data:',
data_type_lst
)
if not ok:
return
legend, ok = QInputDialog.getText(self, 'Legend', 'Legend:')
if not ok:
return
@@ -1307,104 +1322,45 @@ class ResultsWindow(PamhyrWindow):
tmp_unit = {'Z': ' (m)',
'Q': ' (m³/s)'}
data = {'type_x': tmp_dict[data_type[2]],
'type_y': tmp_dict[data_type[0]],
'legend': legend,
'unit': tmp_unit[data_type[0]],
'x': x,
'y': y}
data = {
'type_x': tmp_dict[data_type[2]],
'type_y': tmp_dict[data_type[0]],
'legend': legend,
'unit': tmp_unit[data_type[0]],
'x': x, 'y': y
}
if data_type == 'Z(x)':
line = self.canvas_2.axes.plot(x, y, marker="+",
label=legend + ' (m)')
self.plot_rkc.canvas.draw_idle()
self.plot_rkc.update_idle()
if data_type == 'Q(t)':
line = self.canvas_4.axes.plot(x, y, marker="+",
label=legend + ' (m³/s)')
self.plot_h._line.append(line)
self.plot_h.enable_legend()
self.plot_h.canvas.draw_idle()
self.plot_h.update_idle
return data
for p in self._additional_plot:
self._additional_plot[p].add_imported_plot(data)
def play_sound(self):
def get_sine_wave(frequency, duration,
sample_rate=44100, amplitude=4096):
t = np.linspace(0, duration, int(sample_rate*duration))
return amplitude*np.sin(2*np.pi*frequency*t)
if not _audio_loaded:
return
# results
def update_plot_additional_data(self):
results = self._results[self._current_results[0]]
if results is None:
return
# reach
reach = results.river.reachs[self._get_current_reach()]
profile_id = self._get_current_profile()
profile = reach.profile(profile_id)
for data in results.get("additional_data"):
data = data._data
x, y = data['x'], data['y']
legend = data['legend']
unit = data['unit']
t = self._timestamps
q = profile.get_key("Q")
if (
data['type_x'] == 'water_elevation' and
data['type_y'] == 'time'
):
line = self.canvas_2.axes.plot(
x, y, marker="+",
label=legend + ' ' + unit
)
self.plot_rkc.canvas.draw_idle()
self.plot_rkc.update_idle()
scale = []
low = 30
high = 84
for k in range(low, high):
note = 440*2**((k-49)/12)
scale.append(note) # add musical note
n_notes = len(scale) # number of musical notes
# rescale
# we start at low to high
tmax = max(t)
tmin = min(t)
qmax = max(q)
qmin = min(q)
qfact = (n_notes) / (qmax - qmin + 1)
for i in range(len(q)):
q[i] = int((q[i] - qmin) * qfact)
nq = [q[0]] # (notes)
dq = [0.1] # durée
for i in range(len(t)-1):
if q[i] == q[i+1]:
dq[-1] += 0.1
else:
nq.append(q[i+1])
dq.append(0.1)
wq = []
volume = 2048
for i in range(len(dq)):
new_w = get_sine_wave(frequency=scale[nq[i]],
duration=dq[i],
amplitude=volume)
wq = np.concatenate((wq, new_w))
p = pyaudio.PyAudio()
chunk = 1048
stream = p.open(format=p.get_format_from_width(2),
channels=1,
rate=44100,
output=True)
if data['type_x'] == 'time' and data['type_y'] == 'discharge':
line = self.canvas_4.axes.plot(
x, y, marker="+",
label=legend + ' ' + unit
)
self.plot_h._line.append(line)
self.plot_h.enable_legend()
self.plot_h.canvas.draw_idle()
self.plot_h.update_idle()
def fn():
i = 0
while i < len(wq):
stream.write(
wq[i:min(i+chunk, len(wq))].astype(np.int16).tobytes()
)
i += chunk
dlg = WaitingDialog(
payload_fn=fn,
title="playing_sound",
parent=self
)
dlg.exec_()
stream.stop_stream()
stream.close()
p.terminate()
for p in self._additional_plot:
self._additional_plot[p].add_imported_plot(data)
-5
View File
@@ -53,11 +53,6 @@ class ResultsTranslate(MainTranslate):
"Results",
"Max water elevation"
)
self._dict["file_all"] = _translate("Results", "All files (*)")
self._dict["file_geotiff"] = _translate(
"Results", "GeoTIFF file (*.tiff *.tif)")
self._dict["file_csv"] = _translate(
"Results", "CSV file (*.csv)")
self._dict["ImageCoordinates"] = _translate(
"Results", "Image coordinates"
)
+6 -2
View File
@@ -45,6 +45,7 @@ from PyQt5.QtWidgets import (
from View.RunSolver.Log.Window import SolverLogFileWindow
from View.Results.ReadingResultsDialog import ReadingResultsDialog
from View.WaitingDialog import WaitingDialog
try:
from signal import SIGTERM, SIGSTOP, SIGCONT
@@ -82,7 +83,8 @@ class SelectSolverWindowAdisTS(PamhyrDialog):
# solvers = self._config.solvers
# solvers mage
solvers = list(filter(
lambda x: "mage" not in x._type, self._config.solvers
lambda x: "mage" not in x._type and "rubar" not in x._type,
self._config.solvers
))
solvers_name = list(
map(
@@ -425,7 +427,9 @@ class SolverLogWindowAdisTS(PamhyrWindow):
self._study, dir_path, qlog=self._output
)
dlg = ReadingResultsDialog(reading_fn=reading_fn, parent=self)
dlg = WaitingDialog(payload_fn=reading_fn,
title="waiting_result",
parent=self)
dlg.exec_()
self._parent.set_results(self._solver, self._results)
+14
View File
@@ -182,6 +182,7 @@ class PamhyrWindow(ASubMainWindow, ListedSubWindow, PamhyrWindowTools):
self._set_title()
self._set_icon()
self._setup_save_sc()
def _set_title(self):
title = self._title
@@ -196,12 +197,25 @@ class PamhyrWindow(ASubMainWindow, ListedSubWindow, PamhyrWindowTools):
self.ui.setWindowTitle(title)
def _setup_save_sc(self):
if self._parent is None:
return
self._save_sc = QShortcut(QKeySequence("Ctrl+S"), self)
self._save_sc.activated.connect(lambda: self._save(self))
def closeEvent(self, event):
self._close_sub_window()
self._propagate_update(Modules.WINDOW_LIST)
super(PamhyrWindow, self).closeEvent(event)
def _save(self, source):
if self._parent is None:
return
return self._parent._save(source)
class PamhyrDialog(ASubWindow, ListedSubWindow, PamhyrWindowTools):
_pamhyr_ui = "dummy"
+36 -2
View File
@@ -54,6 +54,18 @@ class CommonWordTranslate(PamhyrTranslate):
self._dict["method"] = _translate("CommonWord", "Method")
# Files
self._dict["open_file"] = _translate(
"CommonWord", "Open file"
)
self._dict["file_all"] = _translate("CommonWord", "All files (*)")
self._dict["file_geotiff"] = _translate(
"CommonWord", "GeoTIFF file (*.tiff *.tif)"
)
self._dict["file_csv"] = _translate(
"CommonWord", "CSV file (*.csv)"
)
class UnitTranslate(CommonWordTranslate):
def __init__(self):
@@ -180,6 +192,9 @@ class MainTranslate(UnitTranslate):
)
# Message box
self._dict["Error"] = _translate(
"MainWindow", "Error"
)
self._dict["Warning"] = _translate(
"MainWindow", "Warning"
)
@@ -234,13 +249,31 @@ class MainTranslate(UnitTranslate):
self._dict["mb_diff_results_param_msg"] = _translate(
"MainWindow", "Results comparison parameters is invalid"
)
self._dict["mb_open_results_title"] = _translate(
"MainWindow", "Open results"
)
self._dict["mb_open_results_no_results_msg"] = _translate(
"MainWindow", "No results found"
)
self._dict["mb_open_results_invalid_results_msg"] = _translate(
"MainWindow", "Failed to read results"
)
self._dict["mb_diff_results_compatibility_msg"] = _translate(
"MainWindow",
"Results comparison with two "
"incompatible study version"
)
self._dict["playing_sound"] = _translate(
"MainWindow", "Playing sound..."
self._dict["mb_unsaved_study_title"] = _translate(
"MainWindow", "Open scenarios window"
)
self._dict["mb_unsaved_studyh_msg"] = _translate(
"MainWindow",
"You must save your study to open "
"the scenario windows"
)
self._dict["x"] = _translate("MainWindow", "X (m)")
@@ -250,3 +283,4 @@ class MainTranslate(UnitTranslate):
self._dict["Cancel"] = _translate("MainWindow", "Cancel")
self._dict["Save"] = _translate("MainWindow", "Save")
self._dict["Close"] = _translate("MainWindow", "Close")
self._dict["Solver"] = _translate("MainWindow", "Solver")
+1 -1
View File
@@ -63,7 +63,7 @@ class WaitingDialog(PamhyrDialog):
". ", ". ",
".. ", ".. ",
"...", "..."],
["o ", " o ", " o ", " o", " o ", " o "],
["o ", " o ", " o ", " o", " o ", " o "],
["█▓▒░", "▓█▓▒", "▒▓█▓", "░▒▓█", "▒▓█▓", "▓█▓▒"],
"▖▘▝▗",
"αβγδεζηθικλμνξοπρστυφχψω",
+264
View File
@@ -0,0 +1,264 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>896</width>
<height>504</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<property name="locale">
<locale language="English" country="Europe"/>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout_3">
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_cancel">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_ok">
<property name="text">
<string>Ok</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Informations</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="checkBox">
<property name="text">
<string>Enabled</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Description</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEdit_description">
<property name="toolTip">
<string>The relative file path on executable directory</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_name"/>
</item>
</layout>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>GeoTIFF file</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPushButton" name="pushButton_import">
<property name="text">
<string>Import GeoTIFF file</string>
</property>
</widget>
</item>
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="">
<layout class="QVBoxLayout" name="verticalLayout_geotiff"/>
</widget>
<widget class="QWidget" name="">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="2" column="2">
<widget class="QPushButton" name="pushButton_left">
<property name="text">
<string>Reset</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_right">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Right coordinate</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QPushButton" name="pushButton_right">
<property name="text">
<string>Reset</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="doubleSpinBox_left">
<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="2">
<widget class="QPushButton" name="pushButton_bottom">
<property name="text">
<string>Reset</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="pushButton_top">
<property name="text">
<string>Reset</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QDoubleSpinBox" name="doubleSpinBox_right">
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>-99999999.000000000000000</double>
</property>
<property name="maximum">
<double>99999999.000000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="doubleSpinBox_top">
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>-99999999.000000000000000</double>
</property>
<property name="maximum">
<double>99999999.000000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_top">
<property name="text">
<string>Top coordinate</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="doubleSpinBox_bottom">
<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_left">
<property name="text">
<string>Left coordinate</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_bottom">
<property name="text">
<string>Bottom coordinate</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>
+84
View File
@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>896</width>
<height>504</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QListView" name="listView"/>
<widget class="QWidget" name="verticalLayoutWidget">
<layout class="QVBoxLayout" name="verticalLayout"/>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QToolBar" name="toolBar">
<property name="windowTitle">
<string>toolBar</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="action_add"/>
<addaction name="action_delete"/>
<addaction name="action_edit"/>
</widget>
<action name="action_add">
<property name="icon">
<iconset>
<normaloff>ressources/add.png</normaloff>ressources/add.png</iconset>
</property>
<property name="text">
<string>Add</string>
</property>
<property name="toolTip">
<string>Add a new file</string>
</property>
</action>
<action name="action_delete">
<property name="icon">
<iconset>
<normaloff>ressources/del.png</normaloff>ressources/del.png</iconset>
</property>
<property name="text">
<string>Delete</string>
</property>
<property name="toolTip">
<string>Delete selected file(s)</string>
</property>
</action>
<action name="action_edit">
<property name="icon">
<iconset>
<normaloff>ressources/edit.png</normaloff>ressources/edit.png</iconset>
</property>
<property name="text">
<string>Edit</string>
</property>
<property name="toolTip">
<string>Edit file</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>
+6
View File
@@ -129,6 +129,7 @@
<string>&amp;Geometry</string>
</property>
<addaction name="action_menu_edit_geometry"/>
<addaction name="action_menu_edit_geotiff"/>
</widget>
<widget class="QMenu" name="menu_run">
<property name="locale">
@@ -811,6 +812,11 @@
<string>Compare results</string>
</property>
</action>
<action name="action_menu_edit_geotiff">
<property name="text">
<string>GeoTIFF</string>
</property>
</action>
</widget>
<resources/>
<connections>
-13
View File
@@ -240,7 +240,6 @@
<addaction name="action_reload"/>
<addaction name="action_Geo_tiff"/>
<addaction name="action_import_data"/>
<addaction name="action_play_sound"/>
</widget>
<action name="action_add">
<property name="icon">
@@ -302,18 +301,6 @@
<string>Import data from SCV file</string>
</property>
</action>
<action name="action_play_sound">
<property name="icon">
<iconset>
<normaloff>ressources/multimedia-volume-control-symbolic.symbolic.png</normaloff>ressources/multimedia-volume-control-symbolic.symbolic.png</iconset>
</property>
<property name="text">
<string>play_sound</string>
</property>
<property name="toolTip">
<string>Play discharge as sound</string>
</property>
</action>
</widget>
<resources/>
<connections/>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 443 B

+31 -1
View File
@@ -37,7 +37,7 @@ logger = logging.getLogger()
class Config(SQL):
def __init__(self):
self._version = '0.0.6'
self._version = '0.0.7'
self.filename = Config.filename()
self.set_default_value()
@@ -165,6 +165,29 @@ class Config(SQL):
"WHERE name='default-AdisTS'"
)
if int(release) < 7:
posix = os.name == 'posix'
ext = "" if posix else ".exe"
self.execute(f"""
INSERT INTO solver VALUES (
'rubar3',
'default-Rubar3',
'Default Pamhyr2 Rubar 3 version',
'', '', '',
'',
'@install_dir/rubar/rubar3{ext} @args @input',
''
)
""")
self.execute(
"UPDATE solver SET cmd_solver=" +
f"'@install_dir/rubar/rubar3{ext} @args @input' "
"WHERE name='default-Rubar3'"
)
self.execute(
f"UPDATE info SET value='{self._version}' " +
"WHERE key='version'"
@@ -337,6 +360,13 @@ class Config(SQL):
new._cmd_solver = f""""@install_dir/adists/adists{ext}" @args @input"""
self._solvers.append(new)
# Rubar3
ctor = solver_type_list["rubar3"]
new = ctor("default-Rubar3")
new._description = "Default Pamhyr2 Rubar 3 version"
new._cmd_solver = f""""@install_dir/rubar/rubar3{ext}" @args @input"""
self._solvers.append(new)
# Mage fake 7
# ctor = solver_type_list["mage_fake7"]
# new = ctor("default-mage-fake-7")
+331 -236
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.