Compare commits

...
Author SHA1 Message Date
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
46 changed files with 2321 additions and 172 deletions

No files matched your search

+38
View File
@@ -38,6 +38,7 @@ variables:
variables: variables:
MAGE_8_VERSION: "permalink/latest" MAGE_8_VERSION: "permalink/latest"
ADISTS_VERSION: "permalink/latest" ADISTS_VERSION: "permalink/latest"
RUBAR_VERSION: "permalink/latest"
dl-mage8-doc: dl-mage8-doc:
stage: downloads stage: downloads
@@ -111,6 +112,36 @@ dl-adists-windows:
paths: paths:
- adists-windows/adists.exe - 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 # # CONFIGURE #
############# #############
@@ -273,6 +304,8 @@ build-linux:
needs: needs:
- job: dl-adists-linux - job: dl-adists-linux
artifacts: true artifacts: true
- job: dl-rubar-linux
artifacts: true
- job: dl-mage8-linux - job: dl-mage8-linux
artifacts: true artifacts: true
- job: dl-mage8-doc - job: dl-mage8-doc
@@ -314,6 +347,9 @@ build-linux:
# Copy adists # Copy adists
- mkdir -p pamhyr/adists - mkdir -p pamhyr/adists
- cp -v ../adists-linux/* pamhyr/adists/ - cp -v ../adists-linux/* pamhyr/adists/
# Copy rubar
- mkdir -p pamhyr/rubar
- cp -v ../rubar-linux/* pamhyr/rubar/
# Copy Pamhyr # Copy Pamhyr
- cp -r dist/pamhyr/* pamhyr/ - cp -r dist/pamhyr/* pamhyr/
# Pamhyr script to force x11 # Pamhyr script to force x11
@@ -379,6 +415,8 @@ build-windows:
artifacts: true artifacts: true
- job: dl-adists-windows - job: dl-adists-windows
artifacts: true artifacts: true
- job: dl-rubar-windows
artifacts: true
- job: dl-mage8-doc - job: dl-mage8-doc
artifacts: true artifacts: true
- job: set-version - 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\mailleurTT.exe pamhyr\mage8\
copy /y ..\mage8-windows\libbief.dll 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 rem adists
mkdir pamhyr\adists mkdir pamhyr\adists
copy /y ..\adists-windows\adists.exe pamhyr\adists\ copy /y ..\adists-windows\adists.exe pamhyr\adists\
+76 -50
View File
@@ -40,20 +40,24 @@ class InternalMeshing(AMeshingTool):
step: float = 50, step: float = 50,
limites=[-1, -1], limites=[-1, -1],
linear: bool = True): linear: bool = True):
if reach is None or len(reach.profiles) == 0:
return reach
if limites[0] > limites[1]: # pre process
lim = limites[1]
limites[1] = limites[0]
limites[0] = lim
elif limites[0] == limites[1]:
return reach
# we never modify original profiles, we work on copies profiles = self.get_profiles(reach, limites)
m_profiles = self.st_to_m(reach, limites) if profiles is None:
new_profiles = self.interpolate_transversal_step(m_profiles, return []
step)
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 new_profiles2 in new_profiles:
for p in new_profiles2: for p in new_profiles2:
@@ -61,52 +65,31 @@ class InternalMeshing(AMeshingTool):
return new_profiles return new_profiles
def st_to_m(self, reach, limites): def st_to_m(self, profiles, guide_list):
gl = reach.compute_guidelines() guide_list = ["un"] + guide_list + ["np"]
profiles = [reach.profile(i).copy() max_values = [0] * (len(guide_list) - 1)
for i in range(limites[0], limites[1]+1) max_values_index = [0] * (len(guide_list) - 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)
# we work between each couple of guidelines # we work between each couple of guidelines
for j, p in enumerate(profiles): 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): for i in range(len(guide_list) - 1):
index1 = p.named_point_index(guide_list[i]) index1 = p.named_point_index(guide_list[i])
index2 = p.named_point_index(guide_list[i+1]) index2 = p.named_point_index(guide_list[i+1])
if index2 - index1 > max_values[i+1]: if index2 - index1 > max_values[i]:
max_values[i+1] = index2 - index1 max_values[i] = index2 - index1
max_values_index[i+1] = j max_values_index[i] = 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
for i in range(len(max_values)): for i in range(len(max_values)):
for isect in range(max_values_index[i], len(profiles)-1): for isect in range(max_values_index[i], len(profiles)-1):
self.compl_sect(profiles[isect], self.compl_sect(profiles[isect],
profiles[isect+1], profiles[isect+1],
gl1[i], gl2[i]) guide_list[i],
guide_list[i+1])
for isect in reversed(range(0, max_values_index[i])): for isect in reversed(range(0, max_values_index[i])):
self.compl_sect(profiles[isect+1], self.compl_sect(profiles[isect+1],
profiles[isect], profiles[isect],
gl1[i], gl2[i]) guide_list[i],
guide_list[i+1])
return profiles return profiles
def interpolate_transversal_step(self, def interpolate_transversal_step(self,
@@ -174,8 +157,7 @@ class InternalMeshing(AMeshingTool):
len2 = end2 - start2 len2 = end2 - start2
if len1 < len2: if len1 < len2:
self.compl_sect(sect2, sect1, start2, start1, self.compl_sect(sect2, sect1, tag1, tag2)
len2, len1, tag1, tag2)
return return
elif len1 == len2: elif len1 == len2:
return return
@@ -194,23 +176,26 @@ class InternalMeshing(AMeshingTool):
for i in range(len2): for i in range(len2):
ltot2 += sect2.point(start2+i).dist(sect2.point(start2+i+1)) ltot2 += sect2.point(start2+i).dist(sect2.point(start2+i+1))
beta.append(ltot2) 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: if len2 < 1 or ltot2 < 0.0001:
# unique point (copy len1 times) # unique point (copy len1 times)
# we are at an edge of the profile # we are at an edge of the profile
for i in range(len1-len2): for i in range(len1-len2):
p = sect2.point(start2).copy() p = sect2.point(start2).copy()
sect2.insert_point(start2, p) sect2.insert_point(start2, p)
len2 += 1
if tag1 == '': # left end point if tag1 == '': # left end point
sect2.point(start2).name = '' sect2.point(start2).name = ''
if tag2 == '': # left end point if tag2 == '': # left end point
sect2.point(start2+1).name = '' sect2.point(start2+1).name = ''
len2 += 1
elif ltot1 < 0.0001: elif ltot1 < 0.0001:
sect2.add_npoints(len1-len2) sect2.add_npoints(len1-len2)
len2 = len1
else: # regular case 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 # loop on the points to insert
for k in range(len1-len2): for k in range(len1-len2):
trouve = False trouve = False
@@ -304,6 +289,8 @@ class InternalMeshing(AMeshingTool):
sgn = -1.0 sgn = -1.0
else: else:
sgn = old_orientation sgn = old_orientation
if abs(sgn) < 0.5:
sgn = 1.0
new_rk = [0.0]*nprof new_rk = [0.0]*nprof
new_rk[origin] = origin_value new_rk[origin] = origin_value
@@ -344,3 +331,42 @@ class InternalMeshing(AMeshingTool):
return new_rk, new_end_rk, new_begin_rk return new_rk, new_end_rk, new_begin_rk
else: else:
return new_rk, new_begin_rk, new_end_rk 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] return self.points[index]
def named_point(self, name): 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): def named_point_index(self, name):
return next( if name == "un":
(p for p in enumerate(self.points) if p[1].name == name), None return 0
)[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 @property
def reach(self): 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 self.point(i+1).z = 0.5 * self.point(i).z + 0.5 * self.point(i+2).z
def copy(self): def copy(self):
p = ProfileXYZ(id=self.id, p = ProfileXYZ(name=self.name,
name=self.name,
rk=self.rk, rk=self.rk,
reach=self.reach, reach=self.reach,
num=self.num,
status=self._status) status=self._status)
for i, k in enumerate(self.points): for i, k in enumerate(self.points):
p.insert_point(i, k.copy()) p.insert_point(i, k.copy())
+2
View File
@@ -195,7 +195,9 @@ class Reach(SQLSubModel):
""" """
if profile in self._profiles: if profile in self._profiles:
self.undelete([profile]) self.undelete([profile])
profile.num = index
else: else:
profile.num = index
self._profiles.insert(index, profile) self._profiles.insert(index, profile)
self.modified() self.modified()
+1 -1
View File
@@ -310,7 +310,7 @@ class Reservoir(SQLSubModel):
new_reservoir.set_as_deleted() new_reservoir.set_as_deleted()
new_reservoir._node = None new_reservoir._node = None
if node_id != -1: if node_id != -1 and node_id is not None:
new_reservoir._node = next( new_reservoir._node = next(
filter( filter(
lambda n: n.id == node_id, data["nodes"] 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() 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): class Results(SQLSubModel):
_sub_classes = [River] _sub_classes = [River, AdditionalData]
def __init__(self, id=-1, study=None, solver=None, def __init__(self, id=-1, study=None, solver=None,
repertory="", name="0"): repertory="", name="0"):
@@ -50,6 +176,7 @@ class Results(SQLSubModel):
# Keep results creation date # Keep results creation date
"creation_date": datetime.now(), "creation_date": datetime.now(),
"study_revision": study.status.version, "study_revision": study.status.version,
"additional_data": [],
} }
if solver is not None: if solver is not None:
@@ -184,6 +311,11 @@ class Results(SQLSubModel):
data["timestamps"] = sorted(ts) data["timestamps"] = sorted(ts)
new_results._river = River._db_load(execute, data) new_results._river = River._db_load(execute, data)
new_results.set(
"additional_data",
AdditionalData._db_load(execute, data)
)
yield (solver_type, new_results) yield (solver_type, new_results)
def _db_save_clear(self, execute, solver_type, data=None): def _db_save_clear(self, execute, solver_type, data=None):
@@ -206,6 +338,11 @@ class Results(SQLSubModel):
f"WHERE scenario = {self._owner_scenario} " + f"WHERE scenario = {self._owner_scenario} " +
f"AND result = {pid}" 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): def _db_save(self, execute, data=None):
if self._status.scenario.id != self._owner_scenario: if self._status.scenario.id != self._owner_scenario:
@@ -238,4 +375,7 @@ class Results(SQLSubModel):
data["result"] = self._pamhyr_id data["result"] = self._pamhyr_id
self._river._db_save(execute, data) self._river._db_save(execute, data)
for add_data in self.get("additional_data"):
add_data._db_save(execute, data)
return True return True
+3
View File
@@ -50,6 +50,9 @@ class Results(object):
self._phys_var_list = ["C", "G", "M", "D", "L", "N", "R"] self._phys_var_list = ["C", "G", "M", "D", "L", "N", "R"]
if solver is not None:
self.set("solver_type", solver._type)
@property @property
def date(self): def date(self):
date = self._meta_data["creation_date"] date = self._meta_data["creation_date"]
-10
View File
@@ -105,16 +105,6 @@ class Profile(SQLSubModel):
@classmethod @classmethod
def _db_update(cls, execute, version, data=None): def _db_update(cls, execute, version, data=None):
major, minor, release = version.strip().split(".") 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) return cls._update_submodel(execute, version, data)
+13
View File
@@ -59,6 +59,7 @@ from Model.LateralContributionsAdisTS.LateralContributionsAdisTSList \
import LateralContributionsAdisTSList import LateralContributionsAdisTSList
from Model.D90AdisTS.D90AdisTSList import D90AdisTSList from Model.D90AdisTS.D90AdisTSList import D90AdisTSList
from Model.DIFAdisTS.DIFAdisTSList import DIFAdisTSList from Model.DIFAdisTS.DIFAdisTSList import DIFAdisTSList
from Model.GeoTIFF.GeoTIFFList import GeoTIFFList
from Model.Results.Results import Results from Model.Results.Results import Results
logger = logging.getLogger() logger = logging.getLogger()
@@ -468,6 +469,7 @@ class River(Graph):
LateralContributionsAdisTSList, LateralContributionsAdisTSList,
D90AdisTSList, D90AdisTSList,
DIFAdisTSList, DIFAdisTSList,
GeoTIFFList,
Results Results
] ]
@@ -505,6 +507,8 @@ class River(Graph):
self._D90AdisTS = D90AdisTSList(status=self._status) self._D90AdisTS = D90AdisTSList(status=self._status)
self._DIFAdisTS = DIFAdisTSList(status=self._status) self._DIFAdisTS = DIFAdisTSList(status=self._status)
self._geotiff = GeoTIFFList(status=self._status)
self._results = {} self._results = {}
@classmethod @classmethod
@@ -617,6 +621,8 @@ class River(Graph):
new._DIFAdisTS = DIFAdisTSList._db_load(execute, data) new._DIFAdisTS = DIFAdisTSList._db_load(execute, data)
new._geotiff = GeoTIFFList._db_load(execute, data)
return new return new
def _db_load_results(self, execute, data=None): def _db_load_results(self, execute, data=None):
@@ -650,6 +656,8 @@ class River(Graph):
objs.append(self._D90AdisTS) objs.append(self._D90AdisTS)
objs.append(self._DIFAdisTS) objs.append(self._DIFAdisTS)
objs.append(self._geotiff)
for solv_type in self.results: for solv_type in self.results:
objs.append(self.results[solv_type]) objs.append(self.results[solv_type])
@@ -726,6 +734,7 @@ class River(Graph):
self._BoundaryConditionsAdisTS, self._BoundaryConditionsAdisTS,
self._LateralContributionsAdisTS, self._LateralContributionsAdisTS,
self._D90AdisTS, self._DIFAdisTS, self._D90AdisTS, self._DIFAdisTS,
self._geotiff,
] ]
for solver in self._parameters: for solver in self._parameters:
@@ -818,6 +827,10 @@ Last export at: @date."""
def additional_files(self): def additional_files(self):
return self._additional_files return self._additional_files
@property
def geotiff(self):
return self._geotiff
@property @property
def rep_lines(self): def rep_lines(self):
return self._rep_lines return self._rep_lines
+1 -1
View File
@@ -37,7 +37,7 @@ logger = logging.getLogger()
class Study(SQLModel): class Study(SQLModel):
_version = "0.2.1" _version = "0.2.3"
_sub_classes = [ _sub_classes = [
Scenario, Scenario,
+6
View File
@@ -56,6 +56,7 @@ class Modules(IterableFlag):
SEDIMENT_LAYER = auto() SEDIMENT_LAYER = auto()
ADDITIONAL_FILES = auto() ADDITIONAL_FILES = auto()
OUTPUT_RK = auto() OUTPUT_RK = auto()
GEOTIFF = auto()
# Results # Results
RESULTS = auto() RESULTS = auto()
@@ -81,6 +82,7 @@ class Modules(IterableFlag):
cls.RESULTS, cls.RESULTS,
cls.WINDOW_LIST, cls.WINDOW_LIST,
cls.OUTPUT_RK, cls.OUTPUT_RK,
cls.GEOTIFF
] ]
@classmethod @classmethod
@@ -99,6 +101,7 @@ class Modules(IterableFlag):
| cls.HYDRAULIC_STRUCTURES | cls.HYDRAULIC_STRUCTURES
| cls.RESERVOIR | cls.RESERVOIR
| cls.SEDIMENT_LAYER | cls.SEDIMENT_LAYER
| cls.GEOTIFF
) )
@classmethod @classmethod
@@ -114,6 +117,7 @@ class Modules(IterableFlag):
cls.HYDRAULIC_STRUCTURES, cls.HYDRAULIC_STRUCTURES,
cls.RESERVOIR, cls.RESERVOIR,
cls.SEDIMENT_LAYER, cls.SEDIMENT_LAYER,
cls.GEOTIFF,
] ]
@classmethod @classmethod
@@ -129,6 +133,7 @@ class Modules(IterableFlag):
cls.HYDRAULIC_STRUCTURES: "Hydraulic structures", cls.HYDRAULIC_STRUCTURES: "Hydraulic structures",
cls.RESERVOIR: "Reservoir", cls.RESERVOIR: "Reservoir",
cls.SEDIMENT_LAYER: "Sediment layer", cls.SEDIMENT_LAYER: "Sediment layer",
cls.GEOTIFF: "GeoTIFF",
} }
def impact(self): def impact(self):
@@ -168,4 +173,5 @@ _impact = {
Modules.HYDRAULIC_STRUCTURES: [], Modules.HYDRAULIC_STRUCTURES: [],
Modules.RESERVOIR: [], Modules.RESERVOIR: [],
Modules.SEDIMENT_LAYER: [], Modules.SEDIMENT_LAYER: [],
Modules.GEOTIFF: [],
} }
+64 -2
View File
@@ -96,6 +96,69 @@ class AdisTS(CommandLineSolver):
return lst 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): def cmd_args(self, study):
lst = super(AdisTS, self).cmd_args(study) lst = super(AdisTS, self).cmd_args(study)
return lst return lst
@@ -365,8 +428,7 @@ class AdisTSwc(AdisTS):
node = next(filter( node = next(filter(
lambda x: x.id == BC.node, study.river._nodes lambda x: x.id == BC.node, study.river._nodes
)) ))
node_name = f"{node.id:3}".replace(" ", "x") f.write(f"${self.get_node_name(node)}\n")
f.write(f"${node_name}\n")
if BC.type == "Concentration": if BC.type == "Concentration":
f.write(f"*temps |concentration\n") f.write(f"*temps |concentration\n")
+13 -6
View File
@@ -575,8 +575,12 @@ class Rubar3(CommandLineSolver):
for bc in bcs: for bc in bcs:
f.write(f"{len(bc)}\n") f.write(f"{len(bc)}\n")
for d0, d1 in bc.data: if bc.bctype == "ZD":
f.write(f"{d1} {d0}\n") 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"): def read_profil(self, study, fname, results, qlog=None, name="0"):
logger.info(f"read: profil.{name}") logger.info(f"read: profil.{name}")
@@ -590,7 +594,10 @@ class Rubar3(CommandLineSolver):
def read_data_line(f): def read_data_line(f):
line = f.readline().split() 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): def set_and_compute_limites(reach, ind, z, q, s):
reach.set(ind, timestamp, "Z", z) reach.set(ind, timestamp, "Z", z)
@@ -628,9 +635,9 @@ class Rubar3(CommandLineSolver):
h, s, q, z = read_data_line(f) h, s, q, z = read_data_line(f)
set_and_compute_limites( set_and_compute_limites(
reach, ind, reach, ind,
(ph + pz + ph + h) / 2, (pz + z + ph + h) / 2,
(pz + s) / 2, (pq + q) / 2,
(pq + q) / 2 (ps + s) / 2
) )
ph = h ph = h
+5 -1
View File
@@ -58,7 +58,11 @@ logger = logging.getLogger()
class TableModel(PamhyrTableModel): class TableModel(PamhyrTableModel):
def get_true_data_row(self, row): 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( return next(
map( map(
+3 -1
View File
@@ -17,6 +17,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging import logging
import traceback
from tools import trace, timer from tools import trace, timer
@@ -92,7 +93,8 @@ class ReplWindow(PamhyrWindow):
value = exec(rich_code) value = exec(rich_code)
value = self.__debug_exec_result__ value = self.__debug_exec_result__
except Exception as e: 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 # Display code
msg = f"<font color=\"grey\"> # " + code + " #</font>" 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: with open(file_name, encoding="utf-8") as rug_file:
for line in rug_file: for line in rug_file:
if line.upper().startswith("K"): if line.upper().startswith("K"):
line = line.split() if int(line[1:4]) == reach_id:
if int(line[1]) == reach_id: data.append(line[4:].split())
data.append(line[1:])
new_data = [] new_data = []
for d in data: for d in data:
new = None new = None
minor = float(d[3]) minor = float(d[2])
medium = float(d[4]) medium = float(d[3])
for s in self._study.river.stricklers.stricklers: for s in self._study.river.stricklers.stricklers:
if s.minor == minor and s.medium == medium: if s.minor == minor and s.medium == medium:
new = s new = s
@@ -292,5 +291,5 @@ class FrictionTableModel(PamhyrTableModel):
self._study.river.stricklers)) self._study.river.stricklers))
new.minor = minor new.minor = minor
new.medium = medium 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) 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() self.layoutChanged.emit()
def meshing(self, mesher, data, tableView): 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.layoutAboutToBeChanged.emit()
self._undo.push( self._undo.push(
MeshingCommand( 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): class MeshingCommand(QUndoCommand):
def __init__(self, reach, mesher, data, tableView): def __init__(self, reach, new_profiles, data, tableView):
QUndoCommand.__init__(self) QUndoCommand.__init__(self)
self._reach = reach self._reach = reach
self._data = data self._data = data
self._limites = data["limites"] self._limites = data["limites"]
self._mesher = mesher
self._new_profiles = None self._new_profiles = new_profiles
self._new_profiles_indexes = None self._new_profiles_indexes = None
self._tableView = tableView self._tableView = tableView
self._indexes = tableView.selectionModel().selection() self._indexes = tableView.selectionModel().selection()
@@ -308,12 +307,6 @@ class MeshingCommand(QUndoCommand):
) )
def redo(self): 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: if self._new_profiles_indexes is None:
k = self._limites[0] k = self._limites[0]
self._new_profiles_indexes = [] self._new_profiles_indexes = []
+21 -3
View File
@@ -330,9 +330,27 @@ class InitialConditionTableModel(PamhyrTableModel):
if not (line.startswith("#") or if not (line.startswith("#") or
line.startswith("*") or line.startswith("*") or
line.startswith("$")): line.startswith("$")):
line = line.split() line_split = line.split()
if int(line[0]) == reach_id: formated = False
data.append([line[4], line[2], line[3]]) 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( self._undo.push(
ReplaceDataCommand( ReplaceDataCommand(
+4
View File
@@ -224,6 +224,8 @@ class InitialConditionsWindow(PamhyrWindow):
def _update_plot(self): def _update_plot(self):
self.plot_1.draw() self.plot_1.draw()
self.plot_2.draw() self.plot_2.draw()
self.plot_1.idle()
self.plot_2.idle()
def _propagated_update(self, key=Modules(0)): def _propagated_update(self, key=Modules(0)):
if Modules.GEOMETRY not in key: if Modules.GEOMETRY not in key:
@@ -320,10 +322,12 @@ class InitialConditionsWindow(PamhyrWindow):
def _import_from_results(self, results): def _import_from_results(self, results):
logger.debug(f"import from results: {results}") logger.debug(f"import from results: {results}")
self._table.import_from_results(results) self._table.import_from_results(results)
self._update()
def _import_from_ini_file(self, file_name): def _import_from_ini_file(self, file_name):
logger.debug(f"import from INI file: {file_name}") logger.debug(f"import from INI file: {file_name}")
self._table.read_from_ini(file_name) self._table.read_from_ini(file_name)
self._update()
def move_up(self): def move_up(self):
row = self.index_selected_row() row = self.index_selected_row()
+108 -19
View File
@@ -47,7 +47,7 @@ from PyQt5.QtWidgets import (
QMainWindow, QApplication, QAction, QMainWindow, QApplication, QAction,
QFileDialog, QShortcut, QMenu, QToolBar, QFileDialog, QShortcut, QMenu, QToolBar,
QMessageBox, QProgressDialog, QTabWidget, QMessageBox, QProgressDialog, QTabWidget,
QDialog, QVBoxLayout, QLabel, QDialog, QVBoxLayout, QLabel, QInputDialog,
) )
from PyQt5.uic import loadUi from PyQt5.uic import loadUi
@@ -79,6 +79,7 @@ from View.Frictions.Window import FrictionsWindow
from View.SedimentLayers.Window import SedimentLayersWindow from View.SedimentLayers.Window import SedimentLayersWindow
from View.SedimentLayers.Reach.Window import ReachSedimentLayersWindow from View.SedimentLayers.Reach.Window import ReachSedimentLayersWindow
from View.AdditionalFiles.Window import AddFileListWindow from View.AdditionalFiles.Window import AddFileListWindow
from View.GeoTIFF.Window import GeoTIFFListWindow
from View.REPLines.Window import REPLineListWindow from View.REPLines.Window import REPLineListWindow
from View.SolverParameters.Window import SolverParametersWindow from View.SolverParameters.Window import SolverParametersWindow
from View.RunSolver.Window import ( from View.RunSolver.Window import (
@@ -157,7 +158,7 @@ define_model_action = [
"action_menu_boundary_conditions_sediment", "action_menu_boundary_conditions_sediment",
"action_menu_rep_additional_lines", "action_menu_output_rk", "action_menu_rep_additional_lines", "action_menu_output_rk",
"action_menu_run_adists", "action_menu_pollutants", "action_menu_run_adists", "action_menu_pollutants",
"action_menu_d90", "action_menu_dif", "action_menu_d90", "action_menu_dif", "action_menu_edit_geotiff"
] ]
action = ( action = (
@@ -276,8 +277,8 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
"action_menu_new": self.open_new_study, "action_menu_new": self.open_new_study,
"action_menu_edit": self.open_edit_study, "action_menu_edit": self.open_edit_study,
"action_menu_open": self.open_model, "action_menu_open": self.open_model,
"action_menu_save": self.save_study, "action_menu_save": lambda: self.save_study(),
"action_menu_save_as": self.save_as_study, "action_menu_save_as": lambda: self.save_as_study(),
"action_menu_numerical_parameter": self.open_solver_parameters, "action_menu_numerical_parameter": self.open_solver_parameters,
"action_menu_edit_scenarios": self.open_scenarios, "action_menu_edit_scenarios": self.open_scenarios,
"action_menu_edit_network": self.open_network, "action_menu_edit_network": self.open_network,
@@ -297,6 +298,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
self.open_reach_sediment_layers, self.open_reach_sediment_layers,
"action_menu_additional_file": self.open_additional_files, "action_menu_additional_file": self.open_additional_files,
"action_menu_rep_additional_lines": self.open_rep_lines, "action_menu_rep_additional_lines": self.open_rep_lines,
"action_menu_edit_geotiff": self.open_geotiff,
"action_menu_close": self.close_model, "action_menu_close": self.close_model,
"action_menu_results_last": self.open_last_results, "action_menu_results_last": self.open_last_results,
"action_menu_open_results_from_file": self.open_results_from_file, "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, "action_menu_about": self.open_about,
# ToolBar action # ToolBar action
"action_toolBar_open": self.open_model, "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_close": self.close_model,
"action_toolBar_run_solver": self.run_lasest_solver, "action_toolBar_run_solver": self.run_lasest_solver,
# Current actions # Current actions
@@ -606,6 +608,16 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
return None 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 @last_results.setter
def last_results(self, results): def last_results(self, results):
if self._study is None: if self._study is None:
@@ -637,7 +649,10 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
logger.info(f"Open Study - {self._study.name}") logger.info(f"Open Study - {self._study.name}")
self.set_title() 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
Save current study, if study as no associate file, open a 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._backup_timer.blockSignals(True)
self._save_mutex.lock() self._save_mutex.lock()
parent = self
if progress_parent is not None:
parent = progress_parent
sql_request_count = self._study.sql_save_request_count() sql_request_count = self._study.sql_save_request_count()
progress = QProgressDialog( progress = QProgressDialog(
"Saving...", None, "Saving...", None,
0, sql_request_count, 0, sql_request_count,
parent=self parent=parent
) )
progress.setWindowModality(Qt.WindowModal) progress.setWindowModality(Qt.WindowModal)
progress.setValue(0) progress.setValue(0)
@@ -684,6 +703,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
progress=lambda: progress.setValue(progress.value() + 1) progress=lambda: progress.setValue(progress.value() + 1)
) )
progress.close()
status += " Done" status += " Done"
logger.info(status) logger.info(status)
self.statusbar.showMessage(status, 3000) self.statusbar.showMessage(status, 3000)
@@ -758,6 +778,8 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
progress.setValue(progress.value() + 1) progress.setValue(progress.value() + 1)
progress.close()
def save_as_study_single_scenario(self, sid=-1): def save_as_study_single_scenario(self, sid=-1):
sql_request_count = self._study.sql_save_request_count() sql_request_count = self._study.sql_save_request_count()
@@ -782,6 +804,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
) )
status += " Done" status += " Done"
progress.close()
logger.info(status) logger.info(status)
self.statusbar.showMessage(status, 3000) self.statusbar.showMessage(status, 3000)
@@ -1339,6 +1362,19 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
) )
self.additonal_files.show() 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): def open_rep_lines(self):
if self._study is not None: if self._study is not None:
if self.sub_window_exists( if self.sub_window_exists(
@@ -1523,14 +1559,14 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
# If no specific results, get last results # If no specific results, get last results
if results is None: if results is None:
def reading_fn(): 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(): def reading_fn():
self._tmp_results = solver.results( self._tmp_results = self.last_results
self._study,
self._solver_workdir(solver),
)
# Open from file # Open from file
if type(results) is str: if type(results) is str:
@@ -1555,13 +1591,16 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
) )
dlg.exec_() dlg.exec_()
results = self._tmp_results results = self._tmp_results
# self.last_results = results
# No results available # No results available
if results is None: if results is None:
self.msg_open_results_no_results()
return return
# results does not have values, for example if geometry missmatch # results does not have values, for example if geometry missmatch
if not results.is_valid: if not results.is_valid:
self.msg_open_results_invalid_results()
return return
if results.get('study_revision') != self._study.status.version: if results.get('study_revision') != self._study.status.version:
@@ -1587,6 +1626,20 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
) )
res.show() 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 open_solver_results_adists(self, solver, results=None):
def reading_fn(): def reading_fn():
self._tmp_results = results self._tmp_results = results
@@ -1615,8 +1668,11 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
os.path.dirname(results), os.path.dirname(results),
name=name name=name
) )
dlg = WaitingDialog(
dlg = ReadingResultsDialog(reading_fn=reading_fn, parent=self) payload_fn=reading_fn,
title="waiting_result",
parent=self
)
dlg.exec_() dlg.exec_()
results = self._tmp_results results = self._tmp_results
@@ -1659,16 +1715,49 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
return workdir 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): def open_last_results(self):
if self._last_solver is None: if self._last_solver is None:
return 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": if self._last_solver._type == "mage8":
self.open_solver_results(self._last_solver, self.open_solver_results(
self.last_results) solver, # self.last_results
)
elif self._last_solver._type == "adistswc": elif self._last_solver._type == "adistswc":
self.open_solver_results_adists(self._last_solver, self.open_solver_results_adists(
self.last_results) solver, # self.last_results
)
def open_results_from_file(self): def open_results_from_file(self):
if self._study is None: if self._study is None:
+16
View File
@@ -27,6 +27,20 @@ from View.Tools.PamhyrWidget import PamhyrWidget
from PyQt5.QtWidgets import QVBoxLayout 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() logger = logging.getLogger()
@@ -104,10 +118,12 @@ class WidgetInfo(PamhyrWidget):
self.plot = PlotXY( self.plot = PlotXY(
canvas=self.canvas, canvas=self.canvas,
data=self._study.river.enable_edges(), data=self._study.river.enable_edges(),
geotiff=self._study.river.geotiff,
trad=self.parent._trad, trad=self.parent._trad,
toolbar=self._toolbar_xy, toolbar=self._toolbar_xy,
parent=self parent=self
) )
self.plot.update() self.plot.update()
def set_network_values(self): def set_network_values(self):
+65 -6
View File
@@ -27,12 +27,26 @@ from PyQt5.QtCore import (
) )
from PyQt5.QtWidgets import QApplication, QTableView 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 _translate = QCoreApplication.translate
class PlotXY(PamhyrPlot): class PlotXY(PamhyrPlot):
def __init__(self, canvas=None, trad=None, data=None, toolbar=None, def __init__(self, canvas=None, trad=None, data=None, geotiff=None,
table=None, parent=None): toolbar=None, table=None, parent=None):
super(PlotXY, self).__init__( super(PlotXY, self).__init__(
canvas=canvas, canvas=canvas,
trad=trad, trad=trad,
@@ -43,10 +57,14 @@ class PlotXY(PamhyrPlot):
) )
self._data = data self._data = data
self._geotiff = geotiff
self.label_x = self._trad["x"] self.label_x = self._trad["x"]
self.label_y = self._trad["y"] self.label_y = self._trad["y"]
self.parent = parent self.parent = parent
self._plot_img = {}
self._isometric_axis = True self._isometric_axis = True
self._auto_relim_update = True self._auto_relim_update = True
@@ -69,7 +87,12 @@ class PlotXY(PamhyrPlot):
if data.reach.number_profiles != 0: if data.reach.number_profiles != 0:
self.draw_xy(data.reach) self.draw_xy(data.reach)
self.draw_lr(data.reach) self.draw_lr(data.reach)
self.idle()
if self._geotiff is not None:
self.draw_geotiff(self._geotiff.files)
self.idle()
return return
def draw_xy(self, reach): def draw_xy(self, reach):
@@ -78,9 +101,9 @@ class PlotXY(PamhyrPlot):
line_xy.append(np.column_stack(xy)) line_xy.append(np.column_stack(xy))
line_xy_collection = collections.LineCollection( line_xy_collection = collections.LineCollection(
line_xy, line_xy,
colors=self.color_plot_river_bottom colors=self.color_plot_river_bottom
) )
self.canvas.axes.add_collection(line_xy_collection) self.canvas.axes.add_collection(line_xy_collection)
def draw_lr(self, reach): def draw_lr(self, reach):
@@ -113,6 +136,42 @@ class PlotXY(PamhyrPlot):
) )
self.line_lr.append(line) 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 @timer
def update(self): def update(self):
self.draw() self.draw()
+53
View File
@@ -31,6 +31,20 @@ from PyQt5.QtCore import (
) )
from PyQt5.QtWidgets import QApplication, QTableView 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 _translate = QCoreApplication.translate
logger = logging.getLogger() logger = logging.getLogger()
@@ -52,6 +66,8 @@ class PlotXY(PamhyrPlot):
self.line_gl = [] self.line_gl = []
self.overflow = [] self.overflow = []
self._plot_img = {}
self._timestamps = parent._timestamps self._timestamps = parent._timestamps
self._current_timestamp = max(self._timestamps) self._current_timestamp = max(self._timestamps)
self._current_reach_id = reach_id self._current_reach_id = reach_id
@@ -153,6 +169,7 @@ class PlotXY(PamhyrPlot):
reach = results.river.reach(self._current_reach_id) reach = results.river.reach(self._current_reach_id)
reaches = results.river.reachs reaches = results.river.reachs
self.draw_geotiff()
self.draw_profiles(reach, reaches) self.draw_profiles(reach, reaches)
self.draw_water_elevation(reach) self.draw_water_elevation(reach)
self.draw_water_elevation_max(reach) self.draw_water_elevation_max(reach)
@@ -166,6 +183,7 @@ class PlotXY(PamhyrPlot):
if reach.geometry.number_profiles == 0: if reach.geometry.number_profiles == 0:
self._init = False self._init = False
return return
self.line_xy = [] self.line_xy = []
# TODO uncomment to draw all the reaches # TODO uncomment to draw all the reaches
# self.draw_other_profiles(reaches) # self.draw_other_profiles(reaches)
@@ -306,6 +324,41 @@ class PlotXY(PamhyrPlot):
alpha=0.7 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): def set_reach(self, reach_id):
self._current_reach_id = reach_id self._current_reach_id = reach_id
self._current_profile_id = 0 self._current_profile_id = 0
+91 -40
View File
@@ -56,9 +56,12 @@ from PyQt5.QtWidgets import (
QFileDialog, QTableView, QAbstractItemView, QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate, QUndoStack, QShortcut, QAction, QItemDelegate,
QComboBox, QVBoxLayout, QHeaderView, QTabWidget, QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
QSlider, QLabel, QWidget, QGridLayout, 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.PamhyrCanvas import MplCanvas
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
@@ -144,11 +147,14 @@ class ResultsWindow(PamhyrWindow):
profile_id=[0]) profile_id=[0])
self.update_table_selection_solver(0) self.update_table_selection_solver(0)
self.update_plot_additional_data()
def setup_table(self): def setup_table(self):
self._table = {} self._table = {}
for t in ["reach", "profile", "raw_data", "solver"]: for t in ["reach", "profile", "raw_data", "solver"]:
table = self.find(QTableView, f"tableView_{t}") table = self.find(QTableView, f"tableView_{t}")
self._table[t] = TableModel( self._table[t] = TableModel(
table_view=table, table_view=table,
table_headers=self._trad.get_dict(f"table_headers_{t}"), table_headers=self._trad.get_dict(f"table_headers_{t}"),
@@ -157,8 +163,11 @@ class ResultsWindow(PamhyrWindow):
opt_data=t, opt_data=t,
parent=self parent=self
) )
self._table[t]._timestamp = self._timestamps[ self._table[t]._timestamp = self._timestamps[
self._slider_time.value()] self._slider_time.value()
]
if len(self._results) <= 1: if len(self._results) <= 1:
table = self.find(QTableView, f"tableView_solver") table = self.find(QTableView, f"tableView_solver")
table.hide() table.hide()
@@ -189,12 +198,15 @@ class ResultsWindow(PamhyrWindow):
def setup_plots(self): def setup_plots(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100) self.canvas = MplCanvas(width=5, height=4, dpi=100)
tab_widget = self.find(QTabWidget, f"tabWidget") tab_widget = self.find(QTabWidget, f"tabWidget")
tab_widget.setTabsClosable(True) tab_widget.setTabsClosable(True)
tab_widget.tabCloseRequested.connect(self.delete_tab) tab_widget.tabCloseRequested.connect(self.delete_tab)
tab_widget.tabBar().setTabButton(0, QTabBar.RightSide, None) tab_widget.tabBar().setTabButton(0, QTabBar.RightSide, None)
tab_widget.tabBar().setTabButton(1, QTabBar.RightSide, None) tab_widget.tabBar().setTabButton(1, QTabBar.RightSide, None)
tab_widget.tabBar().setTabButton(2, QTabBar.RightSide, None) tab_widget.tabBar().setTabButton(2, QTabBar.RightSide, None)
self.canvas.setObjectName("canvas") self.canvas.setObjectName("canvas")
self.toolbar = PamhyrPlotToolbar( self.toolbar = PamhyrPlotToolbar(
self.canvas, self, items=[ self.canvas, self, items=[
@@ -202,6 +214,7 @@ class ResultsWindow(PamhyrWindow):
"iso", "back/forward" "iso", "back/forward"
] ]
) )
self.plot_layout = self.find(QVBoxLayout, "verticalLayout") self.plot_layout = self.find(QVBoxLayout, "verticalLayout")
self.plot_layout.addWidget(self.toolbar) self.plot_layout.addWidget(self.toolbar)
self.plot_layout.addWidget(self.canvas) self.plot_layout.addWidget(self.canvas)
@@ -1217,8 +1230,7 @@ class ResultsWindow(PamhyrWindow):
extent=[b[0], b[2], b[1], b[3]]) extent=[b[0], b[2], b[1], b[3]])
else: else:
dlg = CoordinatesDialog( dlg = CoordinatesDialog(
xlim, xlim, ylim,
ylim,
trad=self._trad, trad=self._trad,
parent=self parent=self
) )
@@ -1231,7 +1243,6 @@ class ResultsWindow(PamhyrWindow):
return return
def import_data(self): def import_data(self):
file_types = [ file_types = [
self._trad["file_csv"], self._trad["file_csv"],
self._trad["file_all"], self._trad["file_all"],
@@ -1242,41 +1253,61 @@ class ResultsWindow(PamhyrWindow):
callback=lambda f: self.read_csv_file(f[0]), callback=lambda f: self.read_csv_file(f[0]),
default_suffix=".csv", default_suffix=".csv",
file_filter=file_types, file_filter=file_types,
directory=self._results[self._current_results[0]]._repertory,
) )
def read_csv_file(self, filename): def read_csv_file(self, filename):
if filename == "": if filename == "":
return return
sep = " " x, y = self.read_csv_file_data(filename)
data = self.read_csv_file_format(x, y)
def is_float(string): results = self._results[self._current_results[0]]
if string.replace(".", "").isnumeric(): data_lst = results.get("additional_data")
return True data_lst.append(
else: AdditionalData(
return False 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: with open(filename, 'r', newline='') as f:
lines = f.readlines() lines = f.readlines()
x = []
y = []
for line in lines: for line in lines:
if line[0] != "*" and line[0] != "#" and line[0] != "$": if line[0] != "*" and line[0] != "#" and line[0] != "$":
row = line.split(sep) 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_lst = ['Q(t)', 'Z(t)', 'Z(x)']
data_type, ok = QInputDialog.getItem( 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: if not ok:
return return
legend, ok = QInputDialog.getText(self, 'Legend', 'Legend:') legend, ok = QInputDialog.getText(self, 'Legend', 'Legend:')
if not ok: if not ok:
return return
@@ -1291,25 +1322,45 @@ class ResultsWindow(PamhyrWindow):
tmp_unit = {'Z': ' (m)', tmp_unit = {'Z': ' (m)',
'Q': ' (m³/s)'} 'Q': ' (m³/s)'}
data = {'type_x': tmp_dict[data_type[2]], data = {
'type_y': tmp_dict[data_type[0]], 'type_x': tmp_dict[data_type[2]],
'legend': legend, 'type_y': tmp_dict[data_type[0]],
'unit': tmp_unit[data_type[0]], 'legend': legend,
'x': x, 'unit': tmp_unit[data_type[0]],
'y': y} 'x': x, 'y': y
}
if data_type == 'Z(x)': return data
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
for p in self._additional_plot: def update_plot_additional_data(self):
self._additional_plot[p].add_imported_plot(data) results = self._results[self._current_results[0]]
for data in results.get("additional_data"):
data = data._data
x, y = data['x'], data['y']
legend = data['legend']
unit = data['unit']
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()
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()
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", "Results",
"Max water elevation" "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( self._dict["ImageCoordinates"] = _translate(
"Results", "Image coordinates" "Results", "Image coordinates"
) )
+6 -2
View File
@@ -45,6 +45,7 @@ from PyQt5.QtWidgets import (
from View.RunSolver.Log.Window import SolverLogFileWindow from View.RunSolver.Log.Window import SolverLogFileWindow
from View.Results.ReadingResultsDialog import ReadingResultsDialog from View.Results.ReadingResultsDialog import ReadingResultsDialog
from View.WaitingDialog import WaitingDialog
try: try:
from signal import SIGTERM, SIGSTOP, SIGCONT from signal import SIGTERM, SIGSTOP, SIGCONT
@@ -82,7 +83,8 @@ class SelectSolverWindowAdisTS(PamhyrDialog):
# solvers = self._config.solvers # solvers = self._config.solvers
# solvers mage # solvers mage
solvers = list(filter( 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( solvers_name = list(
map( map(
@@ -425,7 +427,9 @@ class SolverLogWindowAdisTS(PamhyrWindow):
self._study, dir_path, qlog=self._output 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_() dlg.exec_()
self._parent.set_results(self._solver, self._results) 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_title()
self._set_icon() self._set_icon()
self._setup_save_sc()
def _set_title(self): def _set_title(self):
title = self._title title = self._title
@@ -196,12 +197,25 @@ class PamhyrWindow(ASubMainWindow, ListedSubWindow, PamhyrWindowTools):
self.ui.setWindowTitle(title) 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): def closeEvent(self, event):
self._close_sub_window() self._close_sub_window()
self._propagate_update(Modules.WINDOW_LIST) self._propagate_update(Modules.WINDOW_LIST)
super(PamhyrWindow, self).closeEvent(event) 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): class PamhyrDialog(ASubWindow, ListedSubWindow, PamhyrWindowTools):
_pamhyr_ui = "dummy" _pamhyr_ui = "dummy"
+27
View File
@@ -54,6 +54,18 @@ class CommonWordTranslate(PamhyrTranslate):
self._dict["method"] = _translate("CommonWord", "Method") 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): class UnitTranslate(CommonWordTranslate):
def __init__(self): def __init__(self):
@@ -180,6 +192,9 @@ class MainTranslate(UnitTranslate):
) )
# Message box # Message box
self._dict["Error"] = _translate(
"MainWindow", "Error"
)
self._dict["Warning"] = _translate( self._dict["Warning"] = _translate(
"MainWindow", "Warning" "MainWindow", "Warning"
) )
@@ -234,6 +249,17 @@ class MainTranslate(UnitTranslate):
self._dict["mb_diff_results_param_msg"] = _translate( self._dict["mb_diff_results_param_msg"] = _translate(
"MainWindow", "Results comparison parameters is invalid" "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( self._dict["mb_diff_results_compatibility_msg"] = _translate(
"MainWindow", "MainWindow",
"Results comparison with two " "Results comparison with two "
@@ -247,3 +273,4 @@ class MainTranslate(UnitTranslate):
self._dict["Cancel"] = _translate("MainWindow", "Cancel") self._dict["Cancel"] = _translate("MainWindow", "Cancel")
self._dict["Save"] = _translate("MainWindow", "Save") self._dict["Save"] = _translate("MainWindow", "Save")
self._dict["Close"] = _translate("MainWindow", "Close") 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> <string>&amp;Geometry</string>
</property> </property>
<addaction name="action_menu_edit_geometry"/> <addaction name="action_menu_edit_geometry"/>
<addaction name="action_menu_edit_geotiff"/>
</widget> </widget>
<widget class="QMenu" name="menu_run"> <widget class="QMenu" name="menu_run">
<property name="locale"> <property name="locale">
@@ -811,6 +812,11 @@
<string>Compare results</string> <string>Compare results</string>
</property> </property>
</action> </action>
<action name="action_menu_edit_geotiff">
<property name="text">
<string>GeoTIFF</string>
</property>
</action>
</widget> </widget>
<resources/> <resources/>
<connections> <connections>
Binary file not shown.