From bcbb440ad4a2867473a968a0dbd3e8dfcf954ec9 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Tue, 21 Oct 2025 15:11:28 +0200 Subject: [PATCH 01/48] debug import friction --- src/View/Frictions/Table.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/View/Frictions/Table.py b/src/View/Frictions/Table.py index 64a3e18f..7b5d278c 100644 --- a/src/View/Frictions/Table.py +++ b/src/View/Frictions/Table.py @@ -274,15 +274,14 @@ class FrictionTableModel(PamhyrTableModel): with open(file_name, encoding="utf-8") as rug_file: for line in rug_file: if line.upper().startswith("K"): - line = line.split() - if int(line[1]) == reach_id: - data.append(line[1:]) + if int(line[1:4]) == reach_id: + data.append(line[4:].split()) new_data = [] for d in data: new = None - minor = float(d[3]) - medium = float(d[4]) + minor = float(d[2]) + medium = float(d[3]) for s in self._study.river.stricklers.stricklers: if s.minor == minor and s.medium == medium: new = s @@ -292,5 +291,5 @@ class FrictionTableModel(PamhyrTableModel): self._study.river.stricklers)) new.minor = minor new.medium = medium - new_data.append([self._data, float(d[1]), float(d[2]), new, new]) + new_data.append([self._data, float(d[0]), float(d[1]), new, new]) self.replace_data(new_data) From 0112ad63d9d903361e8ee7fbad60fc624507cdff Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Tue, 21 Oct 2025 15:38:12 +0200 Subject: [PATCH 02/48] Network: Resize network zone. --- src/View/Network/GraphWidget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/View/Network/GraphWidget.py b/src/View/Network/GraphWidget.py index e3ac1aed..3fa0cfb0 100644 --- a/src/View/Network/GraphWidget.py +++ b/src/View/Network/GraphWidget.py @@ -475,7 +475,7 @@ class GraphWidget(QGraphicsView): def setup_scene(self, min_size, max_size, size): scene = QGraphicsScene(self) scene.setItemIndexMethod(QGraphicsScene.NoIndex) - scene.setSceneRect(0, 0, 2000, 2000) + scene.setSceneRect(0, 0, 6000, 6000) self.setScene(scene) self.setCacheMode(QGraphicsView.CacheBackground) From a29df8f904e326dff5852fc465993fd6363f9652 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Tue, 21 Oct 2025 17:28:16 +0200 Subject: [PATCH 03/48] Network: Center on previous size. --- src/View/Network/GraphWidget.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/View/Network/GraphWidget.py b/src/View/Network/GraphWidget.py index 3fa0cfb0..08c08011 100644 --- a/src/View/Network/GraphWidget.py +++ b/src/View/Network/GraphWidget.py @@ -486,6 +486,7 @@ class GraphWidget(QGraphicsView): self.scale(1, 1) self.previousScale = 1 + self.centerOn(1000.0, 1000.0) if min_size: self.setMinimumSize(*min_size) From a31e2127e79f2a19e028038f01f7929ab5c3ebd5 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Thu, 23 Oct 2025 16:32:04 +0200 Subject: [PATCH 04/48] debug old .INI file read --- src/View/InitialConditions/Table.py | 24 +++++++++++++++++++++--- src/View/InitialConditions/Window.py | 4 ++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/View/InitialConditions/Table.py b/src/View/InitialConditions/Table.py index bf1d0f28..47425fa0 100644 --- a/src/View/InitialConditions/Table.py +++ b/src/View/InitialConditions/Table.py @@ -292,9 +292,27 @@ class InitialConditionTableModel(PamhyrTableModel): if not (line.startswith("#") or line.startswith("*") or line.startswith("$")): - line = line.split() - if int(line[0]) == reach_id: - data.append([line[4], line[2], line[3]]) + line_split = line.split() + formated = False + if len(line_split[0]) > 3: + formated = True + elif len(line_split[2]) > 10: + formated = True + elif len(line_split[3]) > 11: + formated = True + elif len(line_split[4]) > 9: + formated = True + + if formated: # old PamHyr format + if int(line[1:4]) == reach_id: + data.append([line[31:40], + line[10:20], + line[20:31]]) + else: + if int(line_split[0]) == reach_id: + data.append([line_split[4], + line_split[2], + line_split[3]]) self._undo.push( ReplaceDataCommand( diff --git a/src/View/InitialConditions/Window.py b/src/View/InitialConditions/Window.py index 90ced991..5ac10c49 100644 --- a/src/View/InitialConditions/Window.py +++ b/src/View/InitialConditions/Window.py @@ -220,6 +220,8 @@ class InitialConditionsWindow(PamhyrWindow): def _update_plot(self): self.plot_1.draw() self.plot_2.draw() + self.plot_1.idle() + self.plot_2.idle() def _propagated_update(self, key=Modules(0)): if Modules.GEOMETRY not in key: @@ -315,10 +317,12 @@ class InitialConditionsWindow(PamhyrWindow): def _import_from_results(self, results): logger.debug(f"import from results: {results}") self._table.import_from_results(results) + self._update() def _import_from_ini_file(self, file_name): logger.debug(f"import from INI file: {file_name}") self._table.read_from_ini(file_name) + self._update() def move_up(self): row = self.index_selected_row() From 7ca6e695262724a028ae768a8d4ef8fdb3a3847e Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Fri, 31 Oct 2025 14:43:01 +0100 Subject: [PATCH 05/48] Pamhyr2: Add save action (ctrl+s) in each pamhyr window. --- src/View/MainWindow.py | 11 +++++++++-- src/View/Tools/PamhyrWindow.py | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index e63954ec..cf2c4add 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -637,7 +637,10 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): logger.info(f"Open Study - {self._study.name}") self.set_title() - def save_study(self): + def _save(self, source): + self.save_study(progress_parent=source) + + def save_study(self, progress_parent=None): """Save current study Save current study, if study as no associate file, open a @@ -667,11 +670,15 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): self._backup_timer.blockSignals(True) self._save_mutex.lock() + parent = self + if progress_parent is not None: + parent = progress_parent + sql_request_count = self._study.sql_save_request_count() progress = QProgressDialog( "Saving...", None, 0, sql_request_count, - parent=self + parent=parent ) progress.setWindowModality(Qt.WindowModal) progress.setValue(0) diff --git a/src/View/Tools/PamhyrWindow.py b/src/View/Tools/PamhyrWindow.py index 321c2864..ffa0e90f 100644 --- a/src/View/Tools/PamhyrWindow.py +++ b/src/View/Tools/PamhyrWindow.py @@ -182,6 +182,7 @@ class PamhyrWindow(ASubMainWindow, ListedSubWindow, PamhyrWindowTools): self._set_title() self._set_icon() + self._setup_save_sc() def _set_title(self): title = self._title @@ -196,12 +197,25 @@ class PamhyrWindow(ASubMainWindow, ListedSubWindow, PamhyrWindowTools): self.ui.setWindowTitle(title) + def _setup_save_sc(self): + if self._parent is None: + return + + self._save_sc = QShortcut(QKeySequence("Ctrl+S"), self) + self._save_sc.activated.connect(lambda: self._save(self)) + def closeEvent(self, event): self._close_sub_window() self._propagate_update(Modules.WINDOW_LIST) super(PamhyrWindow, self).closeEvent(event) + def _save(self, source): + if self._parent is None: + return + + return self._parent._save(source) + class PamhyrDialog(ASubWindow, ListedSubWindow, PamhyrWindowTools): _pamhyr_ui = "dummy" From 16ee5a90e4725d6fef39f83f71b0fc3de56f45e4 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Fri, 31 Oct 2025 15:05:23 +0100 Subject: [PATCH 06/48] Results: Fix update table creation. --- src/Model/Results/River/River.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Model/Results/River/River.py b/src/Model/Results/River/River.py index d111bf9f..bed5fd45 100644 --- a/src/Model/Results/River/River.py +++ b/src/Model/Results/River/River.py @@ -105,16 +105,6 @@ class Profile(SQLSubModel): @classmethod def _db_update(cls, execute, version, data=None): major, minor, release = version.strip().split(".") - create = False - - if major == "0" and int(minor) < 2: - cls._db_create(execute) - create = True - - if major == "0" and int(minor) == 2: - if int(release) < 1 and not create: - cls._db_create(execute) - create = True return cls._update_submodel(execute, version, data) From a5a64f2080ff5d7b270cdf1a1c6d3f048441c30c Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Fri, 31 Oct 2025 15:41:25 +0100 Subject: [PATCH 07/48] MainWindow: Minor change and fix. --- src/View/MainWindow.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index cf2c4add..c7a303fe 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -276,7 +276,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): "action_menu_new": self.open_new_study, "action_menu_edit": self.open_edit_study, "action_menu_open": self.open_model, - "action_menu_save": self.save_study, + "action_menu_save": lambda: self.save_study(), "action_menu_save_as": self.save_as_study, "action_menu_numerical_parameter": self.open_solver_parameters, "action_menu_edit_scenarios": self.open_scenarios, @@ -691,6 +691,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): progress=lambda: progress.setValue(progress.value() + 1) ) + progress.close() status += " Done" logger.info(status) self.statusbar.showMessage(status, 3000) @@ -765,6 +766,8 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): progress.setValue(progress.value() + 1) + progress.close() + def save_as_study_single_scenario(self, sid=-1): sql_request_count = self._study.sql_save_request_count() @@ -789,6 +792,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): ) status += " Done" + progress.close() logger.info(status) self.statusbar.showMessage(status, 3000) From 44a8be6be382efc6b8d28d91d932a9a3e050d966 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Fri, 31 Oct 2025 17:27:47 +0100 Subject: [PATCH 08/48] WaitingDialog: Minor change. --- src/View/WaitingDialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/View/WaitingDialog.py b/src/View/WaitingDialog.py index 283fdc30..b61228e6 100644 --- a/src/View/WaitingDialog.py +++ b/src/View/WaitingDialog.py @@ -63,7 +63,7 @@ class WaitingDialog(PamhyrDialog): ". ", ". ", ".. ", ".. ", "...", "..."], - ["o ", " o ", " o ", " o", " o ", " o "], + ["o ", " o ", " o ", " o", " o ", " o "], ["█▓▒░", "▓█▓▒", "▒▓█▓", "░▒▓█", "▒▓█▓", "▓█▓▒"], "▖▘▝▗", "αβγδεζηθικλμνξοπρστυφχψω", From c63c77698913765642d8269938f100d3932983d5 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 3 Nov 2025 09:46:40 +0100 Subject: [PATCH 09/48] MainWindow: Add message box at results opening error. --- src/View/MainWindow.py | 16 ++++++++++++++++ src/View/Translate.py | 14 ++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index c7a303fe..7ce68594 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -1569,10 +1569,12 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): # No results available if results is None: + self.msg_open_results_no_results() return # results does not have values, for example if geometry missmatch if not results.is_valid: + self.msg_open_results_invalid_results() return if results.get('study_revision') != self._study.status.version: @@ -1598,6 +1600,20 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): ) res.show() + def msg_open_results_no_results(self): + self.message_box( + window_title=self._trad["Warning"], + text=self._trad["mb_open_results_title"], + informative_text=self._trad["mb_open_results_no_results_msg"] + ) + + def msg_open_results_invalid_results(self): + self.message_box( + window_title=self._trad["Error"], + text=self._trad["mb_open_results_title"], + informative_text=self._trad["mb_open_results_invalid_results_msg"] + ) + def open_solver_results_adists(self, solver, results=None): def reading_fn(): self._tmp_results = results diff --git a/src/View/Translate.py b/src/View/Translate.py index 30434d4f..6bd9018b 100644 --- a/src/View/Translate.py +++ b/src/View/Translate.py @@ -180,6 +180,9 @@ class MainTranslate(UnitTranslate): ) # Message box + self._dict["Error"] = _translate( + "MainWindow", "Error" + ) self._dict["Warning"] = _translate( "MainWindow", "Warning" ) @@ -234,6 +237,17 @@ class MainTranslate(UnitTranslate): self._dict["mb_diff_results_param_msg"] = _translate( "MainWindow", "Results comparison parameters is invalid" ) + + self._dict["mb_open_results_title"] = _translate( + "MainWindow", "Open results" + ) + self._dict["mb_open_results_no_results_msg"] = _translate( + "MainWindow", "No results found" + ) + self._dict["mb_open_results_invalid_results_msg"] = _translate( + "MainWindow", "Failed to read results" + ) + self._dict["mb_diff_results_compatibility_msg"] = _translate( "MainWindow", "Results comparison with two " From 4e1acfecdcce7fd732bb65be2b34df3a9f299e76 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 3 Nov 2025 14:49:12 +0100 Subject: [PATCH 10/48] Results: Refacto read csv. --- src/View/Results/Window.py | 74 ++++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index 0004152c..a5b2d89d 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -1231,7 +1231,6 @@ class ResultsWindow(PamhyrWindow): return def import_data(self): - file_types = [ self._trad["file_csv"], self._trad["file_all"], @@ -1248,29 +1247,38 @@ class ResultsWindow(PamhyrWindow): if filename == "": return - sep = " " + x, y = self.read_csv_file_data(filename) + data = self.read_csv_file_format(x, y) + self.read_csv_file_update_plot(data) - def is_float(string): - if string.replace(".", "").isnumeric(): - return True - else: - return False + def read_csv_file_data(self, filename): + sep = "," + x = [] + y = [] with open(filename, 'r', newline='') as f: lines = f.readlines() - x = [] - y = [] for line in lines: if line[0] != "*" and line[0] != "#" and line[0] != "$": row = line.split(sep) - if len(row) >= 2: - if is_float(row[0]) and is_float(row[1]): - x.append(float(row[0])) - y.append(float(row[1])) + if len(row) >= 2: + try: + fx, fy = float(row[0]), float(row[1]) + x.append(fx) + y.append(fy) + except: + continue + + return x, y + + def read_csv_file_format(self, x, y): data_type_lst = ['Q(t)', 'Z(t)', 'Z(x)'] data_type, ok = QInputDialog.getItem( - self, 'Data type', 'Chose the type of data:', data_type_lst) + self, 'Data type', + 'Chose the type of data:', + data_type_lst + ) if not ok: return @@ -1291,25 +1299,37 @@ class ResultsWindow(PamhyrWindow): tmp_unit = {'Z': ' (m)', 'Q': ' (m³/s)'} - data = {'type_x': tmp_dict[data_type[2]], - 'type_y': tmp_dict[data_type[0]], - 'legend': legend, - 'unit': tmp_unit[data_type[0]], - 'x': x, - 'y': y} + data = { + 'type_x': tmp_dict[data_type[2]], + 'type_y': tmp_dict[data_type[0]], + 'legend': legend, + 'unit': tmp_unit[data_type[0]], + 'x': x, 'y': y + } - if data_type == 'Z(x)': - line = self.canvas_2.axes.plot(x, y, marker="+", - label=legend + ' (m)') + return data + + def read_csv_file_update_plot(self, 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 == 'Q(t)': - line = self.canvas_4.axes.plot(x, y, marker="+", - label=legend + ' (m³/s)') + if data['type_x'] == 'discharge' and data['type_y'] == 'time': + 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 + self.plot_h.update_idle() for p in self._additional_plot: self._additional_plot[p].add_imported_plot(data) From 2487bec6d653a6ed931df8877e841febbc9fdd88 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 3 Nov 2025 15:04:05 +0100 Subject: [PATCH 11/48] Results: Minor change. --- src/View/Results/Window.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index a5b2d89d..3040fc99 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -1321,6 +1321,7 @@ class ResultsWindow(PamhyrWindow): ) self.plot_rkc.canvas.draw_idle() self.plot_rkc.update_idle() + if data['type_x'] == 'discharge' and data['type_y'] == 'time': line = self.canvas_4.axes.plot( x, y, marker="+", From 14549330b9b661eb0689651b2fd8d87c2f0cc4b8 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 3 Nov 2025 16:49:59 +0100 Subject: [PATCH 12/48] Results: Add additional data to SQL study save. --- src/Model/Results/Results.py | 136 ++++++++++++++++++++++++++++++++++- src/Model/Study.py | 2 +- src/View/Results/Window.py | 81 +++++++++++++-------- 3 files changed, 187 insertions(+), 32 deletions(-) diff --git a/src/Model/Results/Results.py b/src/Model/Results/Results.py index 3b352bec..c6e51e2e 100644 --- a/src/Model/Results/Results.py +++ b/src/Model/Results/Results.py @@ -29,8 +29,133 @@ from Model.Results.River.River import River logger = logging.getLogger() +class AdditionalData(SQLSubModel): + _sub_classes = [] + + def __init__(self, id=-1, study=None, data=None): + super(AdditionalData, self).__init__( + id=id, status=study.status, + owner_scenario=study.status.scenario.id + ) + + self._study = study + self._data = data + + @property + def data(self): + return self._data + + @classmethod + def _db_create(cls, execute, ext=""): + execute(f""" + CREATE TABLE results_add_data{ext} ( + {cls.create_db_add_pamhyr_id()}, + result INTEGER NOT NULL, + type_x TEXT NOT NULL, + type_y TEXT NOT NULL, + legend TEXT NOT NULL, + unit TEXT NOT NULL, + data_len INTEGER NOT NULL, + x BLOB NOT NULL, + y BLOB NOT NULL, + {Scenario.create_db_add_scenario()}, + {Scenario.create_db_add_scenario_fk()}, + FOREIGN KEY(result) REFERENCES results(pamhyr_id), + PRIMARY KEY(pamhyr_id, result, scenario) + ) + """) + + if ext != "": + return True + + return cls._create_submodel(execute) + + @classmethod + def _db_update(cls, execute, version, data=None): + major, minor, release = version.strip().split(".") + + if major == "0" and int(minor) == 2 and int(release) <= 1: + cls._db_create(execute) + + return cls._update_submodel(execute, version, data) + + @classmethod + def _db_load(cls, execute, data=None): + new = [] + + study = data['study'] + status = data['status'] + scenario = data["scenario"] + + table = execute( + "SELECT pamhyr_id, type_x, type_y, " + + "legend, unit, data_len, x, y, " + + "scenario " + + "FROM results_add_data " + + f"WHERE scenario = {scenario.id}" + ) + + if table is None: + return new + + for v in table: + it = iter(v) + + pid = next(it) + type_x = next(it) + type_y = next(it) + legend = next(it) + unit = next(it) + data_len = next(it) + bx = next(it) + by = next(it) + owner_scenario = next(it) + + data_format = ">" + ''.join(itertools.repeat("d", data_len)) + x = struct.unpack(data_format, bx) + y = struct.unpack(data_format, by) + + data = { + 'type_x': tmp_dict[data_type[2]], + 'type_y': tmp_dict[data_type[0]], + 'legend': legend, + 'unit': tmp_unit[data_type[0]], + 'x': x, 'y': y + } + + new_results = cls(study=study) + new.append(new_results) + + return new + + def _db_save(self, execute, data=None): + if self._status.scenario.id != self._owner_scenario: + return + + pid = self._pamhyr_id + data_len = len(self._data["x"]) + + data_format = ">" + ''.join(itertools.repeat("d", data_len)) + bx = struct.pack(data_format, *self._data["x"]) + by = struct.pack(data_format, *self._data["y"]) + + execute( + "INSERT INTO " + + "results_add_data (pamhyr_id, result, " + + "type_x, type_y, " + + "legend, unit, data_len, x, y, " + + "scenario) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + self._pamhyr_id, data["result"], + self._data["type_x"], self._data["type_y"], + self._data["legend"], self._data["unit"], + data_len, bx, by, self._owner_scenario + ) + + return True + + class Results(SQLSubModel): - _sub_classes = [River] + _sub_classes = [River, AdditionalData] def __init__(self, id=-1, study=None, solver=None, repertory="", name="0"): @@ -50,6 +175,7 @@ class Results(SQLSubModel): # Keep results creation date "creation_date": datetime.now(), "study_revision": study.status.version, + "additional_data": [], } if solver is not None: @@ -206,6 +332,11 @@ class Results(SQLSubModel): f"WHERE scenario = {self._owner_scenario} " + f"AND result = {pid}" ) + execute( + "DELETE FROM results_add_data " + + f"WHERE scenario = {self._owner_scenario} " + + f"AND result = {pid}" + ) def _db_save(self, execute, data=None): if self._status.scenario.id != self._owner_scenario: @@ -238,4 +369,7 @@ class Results(SQLSubModel): data["result"] = self._pamhyr_id self._river._db_save(execute, data) + for add_data in self.get("additional_data"): + add_data._db_save(execute, data) + return True diff --git a/src/Model/Study.py b/src/Model/Study.py index 2d7292b7..bdb3d2db 100644 --- a/src/Model/Study.py +++ b/src/Model/Study.py @@ -37,7 +37,7 @@ logger = logging.getLogger() class Study(SQLModel): - _version = "0.2.1" + _version = "0.2.2" _sub_classes = [ Scenario, diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index 3040fc99..d88f82ea 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -59,6 +59,8 @@ from PyQt5.QtWidgets import ( QSlider, QLabel, QWidget, QGridLayout, QTabBar, QInputDialog ) +from Model.Results.Results import AdditionalData + from View.Tools.Plot.PamhyrCanvas import MplCanvas from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar @@ -1249,7 +1251,17 @@ class ResultsWindow(PamhyrWindow): x, y = self.read_csv_file_data(filename) data = self.read_csv_file_format(x, y) - self.read_csv_file_update_plot(data) + + results = self._results[self._current_results[0]] + data_lst = results.get("additional_data") + data_lst.append( + AdditionalData( + study=self._study, + data=data + ) + ) + + self.update_plot_additional_data() def read_csv_file_data(self, filename): sep = "," @@ -1262,13 +1274,15 @@ class ResultsWindow(PamhyrWindow): if line[0] != "*" and line[0] != "#" and line[0] != "$": row = line.split(sep) - if len(row) >= 2: - try: - fx, fy = float(row[0]), float(row[1]) - x.append(fx) - y.append(fy) - except: - continue + 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 @@ -1309,28 +1323,35 @@ class ResultsWindow(PamhyrWindow): return data - def read_csv_file_update_plot(self, data): - x, y = data['x'], data['y'] - legend = data['legend'] - unit = data['unit'] + def update_plot_additional_data(self): + results = self._results[self._current_results[0]] - 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() + 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'] == 'discharge' and data['type_y'] == 'time': - 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() + 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() - for p in self._additional_plot: - self._additional_plot[p].add_imported_plot(data) + if data['type_x'] == 'discharge' and data['type_y'] == 'time': + 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) From 9b0bdd1e6319fea8369d43cd24e9f58160b64fed Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Tue, 4 Nov 2025 10:15:37 +0100 Subject: [PATCH 13/48] Results: Fix plot add data 'Q(t)'. --- src/View/Results/Window.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index d88f82ea..44fe6861 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -146,11 +146,14 @@ class ResultsWindow(PamhyrWindow): profile_id=[0]) self.update_table_selection_solver(0) + self.update_plot_additional_data() def setup_table(self): self._table = {} + for t in ["reach", "profile", "raw_data", "solver"]: table = self.find(QTableView, f"tableView_{t}") + self._table[t] = TableModel( table_view=table, table_headers=self._trad.get_dict(f"table_headers_{t}"), @@ -159,8 +162,11 @@ class ResultsWindow(PamhyrWindow): opt_data=t, parent=self ) + self._table[t]._timestamp = self._timestamps[ - self._slider_time.value()] + self._slider_time.value() + ] + if len(self._results) <= 1: table = self.find(QTableView, f"tableView_solver") table.hide() @@ -191,12 +197,15 @@ class ResultsWindow(PamhyrWindow): def setup_plots(self): self.canvas = MplCanvas(width=5, height=4, dpi=100) + tab_widget = self.find(QTabWidget, f"tabWidget") + tab_widget.setTabsClosable(True) tab_widget.tabCloseRequested.connect(self.delete_tab) tab_widget.tabBar().setTabButton(0, QTabBar.RightSide, None) tab_widget.tabBar().setTabButton(1, QTabBar.RightSide, None) tab_widget.tabBar().setTabButton(2, QTabBar.RightSide, None) + self.canvas.setObjectName("canvas") self.toolbar = PamhyrPlotToolbar( self.canvas, self, items=[ @@ -204,6 +213,7 @@ class ResultsWindow(PamhyrWindow): "iso", "back/forward" ] ) + self.plot_layout = self.find(QVBoxLayout, "verticalLayout") self.plot_layout.addWidget(self.toolbar) self.plot_layout.addWidget(self.canvas) @@ -1343,7 +1353,7 @@ class ResultsWindow(PamhyrWindow): self.plot_rkc.canvas.draw_idle() self.plot_rkc.update_idle() - if data['type_x'] == 'discharge' and data['type_y'] == 'time': + if data['type_x'] == 'time' and data['type_y'] == 'discharge': line = self.canvas_4.axes.plot( x, y, marker="+", label=legend + ' ' + unit From 874f592cf4e6e36842ad04e15b82f81bf625dd05 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Tue, 4 Nov 2025 14:18:00 +0100 Subject: [PATCH 14/48] Results: Fix add data settings. --- src/Model/Results/Results.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Model/Results/Results.py b/src/Model/Results/Results.py index c6e51e2e..aad79c04 100644 --- a/src/Model/Results/Results.py +++ b/src/Model/Results/Results.py @@ -116,15 +116,16 @@ class AdditionalData(SQLSubModel): y = struct.unpack(data_format, by) data = { - 'type_x': tmp_dict[data_type[2]], - 'type_y': tmp_dict[data_type[0]], + 'type_x': type_x, + 'type_y': type_y, 'legend': legend, - 'unit': tmp_unit[data_type[0]], + 'unit': unit, 'x': x, 'y': y } - new_results = cls(study=study) - new.append(new_results) + new_data = cls(study=study) + new_data._data = data + new.append(new_data) return new @@ -310,6 +311,11 @@ class Results(SQLSubModel): data["timestamps"] = sorted(ts) new_results._river = River._db_load(execute, data) + new_results.set( + "additional_data", + AdditionalData._db_load(execute, data) + ) + yield (solver_type, new_results) def _db_save_clear(self, execute, solver_type, data=None): From 81d58122d6c6e24ec6e2c3b6df7c469fb86ce98a Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Tue, 4 Nov 2025 14:34:32 +0100 Subject: [PATCH 15/48] MainWindow: Fix save callback. --- src/View/MainWindow.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index 7ce68594..a22b0d16 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -277,7 +277,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): "action_menu_edit": self.open_edit_study, "action_menu_open": self.open_model, "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_edit_scenarios": self.open_scenarios, "action_menu_edit_network": self.open_network, @@ -313,7 +313,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): "action_menu_about": self.open_about, # ToolBar action "action_toolBar_open": self.open_model, - "action_toolBar_save": self.save_study, + "action_toolBar_save": lambda: self.save_study(), "action_toolBar_close": self.close_model, "action_toolBar_run_solver": self.run_lasest_solver, # Current actions @@ -1566,6 +1566,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): ) dlg.exec_() results = self._tmp_results + self.last_results = results # No results available if results is None: From b04e367e722b7def1e39026b4480abe3795feacb Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Tue, 4 Nov 2025 16:22:11 +0100 Subject: [PATCH 16/48] Results: Add InputDialog to select solver. --- src/View/MainWindow.py | 57 ++++++++++++++++++++++++++++++-------- src/View/Results/Window.py | 8 ++---- src/View/Translate.py | 1 + 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index a22b0d16..4a0b4476 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -47,7 +47,7 @@ from PyQt5.QtWidgets import ( QMainWindow, QApplication, QAction, QFileDialog, QShortcut, QMenu, QToolBar, QMessageBox, QProgressDialog, QTabWidget, - QDialog, QVBoxLayout, QLabel, + QDialog, QVBoxLayout, QLabel, QInputDialog, ) from PyQt5.uic import loadUi @@ -606,6 +606,16 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): return None + def get_last_results(self, solver): + if self._study is None: + return None + + results = self._study.results + if solver in results: + return self._study.results[solver] + + return None + @last_results.setter def last_results(self, results): if self._study is None: @@ -1534,14 +1544,14 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): # If no specific results, get last results if results is None: def reading_fn(): - self._tmp_results = self.last_results + self._tmp_results = solver.results( + self._study, + self._solver_workdir(solver), + ) - if self.last_results is None: + if solver == self._last_solver: def reading_fn(): - self._tmp_results = solver.results( - self._study, - self._solver_workdir(solver), - ) + self._tmp_results = self.last_results # Open from file if type(results) is str: @@ -1566,7 +1576,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): ) dlg.exec_() results = self._tmp_results - self.last_results = results + # self.last_results = results # No results available if results is None: @@ -1691,12 +1701,35 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): if self._last_solver is None: return + solver_type = self._study.results + + solver_name, ok = QInputDialog.getItem( + self, self._trad['Solver'], + self._trad['Solver'] + ":", + list( + map(lambda s: s.name, + filter(lambda s: s._type in solver_type, + self.conf.solvers)) + ) + ) + if not ok: + return + + solver = next( + filter( + lambda s: s.name == solver_name, + self.conf.solvers + ) + ) + if self._last_solver._type == "mage8": - self.open_solver_results(self._last_solver, - self.last_results) + self.open_solver_results( + solver, # self.last_results + ) elif self._last_solver._type == "adistswc": - self.open_solver_results_adists(self._last_solver, - self.last_results) + self.open_solver_results_adists( + solver, # self.last_results + ) def open_results_from_file(self): if self._study is None: diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index 44fe6861..45953b2d 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -56,7 +56,8 @@ from PyQt5.QtWidgets import ( QFileDialog, QTableView, QAbstractItemView, QUndoStack, QShortcut, QAction, QItemDelegate, QComboBox, QVBoxLayout, QHeaderView, QTabWidget, - QSlider, QLabel, QWidget, QGridLayout, QTabBar, QInputDialog + QSlider, QLabel, QWidget, QGridLayout, QTabBar, + QInputDialog, ) from Model.Results.Results import AdditionalData @@ -1229,8 +1230,7 @@ class ResultsWindow(PamhyrWindow): extent=[b[0], b[2], b[1], b[3]]) else: dlg = CoordinatesDialog( - xlim, - ylim, + xlim, ylim, trad=self._trad, parent=self ) @@ -1303,12 +1303,10 @@ class ResultsWindow(PamhyrWindow): 'Chose the type of data:', data_type_lst ) - if not ok: return legend, ok = QInputDialog.getText(self, 'Legend', 'Legend:') - if not ok: return diff --git a/src/View/Translate.py b/src/View/Translate.py index 6bd9018b..463b1057 100644 --- a/src/View/Translate.py +++ b/src/View/Translate.py @@ -261,3 +261,4 @@ class MainTranslate(UnitTranslate): self._dict["Cancel"] = _translate("MainWindow", "Cancel") self._dict["Save"] = _translate("MainWindow", "Save") self._dict["Close"] = _translate("MainWindow", "Close") + self._dict["Solver"] = _translate("MainWindow", "Solver") From 291b97ac9b0452fb800018be9b28a4de9fd1138a Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Tue, 4 Nov 2025 16:35:37 +0100 Subject: [PATCH 17/48] Results: Select solver for last results also if the results path exists. --- src/View/MainWindow.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index 4a0b4476..00db0ce4 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -1697,6 +1697,11 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): return workdir + def is_solver_workdir_exists(self, solver, scenario=None): + return os.path.exists( + self._solver_workdir(solver, scenario) + ) + def open_last_results(self): if self._last_solver is None: return @@ -1707,9 +1712,14 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): self, self._trad['Solver'], self._trad['Solver'] + ":", list( - map(lambda s: s.name, - filter(lambda s: s._type in solver_type, - self.conf.solvers)) + 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: From 4cf40155798e7524e4c3016bc36edea422e06d49 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Thu, 6 Nov 2025 09:30:29 +0100 Subject: [PATCH 18/48] Results: Set import data methode default dir as results dir. --- src/View/Results/Window.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index 45953b2d..6d0c85b6 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -1253,6 +1253,7 @@ class ResultsWindow(PamhyrWindow): callback=lambda f: self.read_csv_file(f[0]), default_suffix=".csv", file_filter=file_types, + directory=self._results[self._current_results[0]]._repertory, ) def read_csv_file(self, filename): From a308af41e00fa872315b3613b844b63158b8c8b0 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Fri, 7 Nov 2025 17:12:27 +0100 Subject: [PATCH 19/48] GeoTIFF: Prepare add geotiff into pamhyr2 db. --- src/Model/GeoTIFF/GeoTIFF.py | 298 +++++++++++++++++++++++++++++++ src/Model/GeoTIFF/GeoTIFFList.py | 59 ++++++ 2 files changed, 357 insertions(+) create mode 100644 src/Model/GeoTIFF/GeoTIFF.py create mode 100644 src/Model/GeoTIFF/GeoTIFFList.py diff --git a/src/Model/GeoTIFF/GeoTIFF.py b/src/Model/GeoTIFF/GeoTIFF.py new file mode 100644 index 00000000..4bd17632 --- /dev/null +++ b/src/Model/GeoTIFF/GeoTIFF.py @@ -0,0 +1,298 @@ +# 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 . + +# -*- 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 + _rasterio_loaded = True +except Exception as e: + print(f"Module 'rasterio' is not available: {e}") + _rasterio_loaded = False + + +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 = text + + self._file_bytes = b'' + self._coordinates = coordinates + + 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 == "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": + self.file_name = value + elif key == "coordinates": + self.coordinates = 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 memfile(self): + if not _rasterio_loaded: + return None + + if self._file_bytes == b'': + return None + + if self._memfile == None: + self._memfile = MemoryFile() + self._memfile.write(self._file_bytes) + + return self._memfile + + def read_file(self, path): + self._file_name = path + self._file_bytes = b'' + self._memfile = None + + with open(path, "rb") as f: + while True: + data = f.read(4096) + if not data: + break + self._file_bytes += data + + 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: + if int(release) < 3: + cls._create_submodel(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))})" + ) + + 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 diff --git a/src/Model/GeoTIFF/GeoTIFFList.py b/src/Model/GeoTIFF/GeoTIFFList.py new file mode 100644 index 00000000..9e0fafb6 --- /dev/null +++ b/src/Model/GeoTIFF/GeoTIFFList.py @@ -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 . + +# -*- coding: utf-8 -*- + +from tools import trace, timer + +from Model.Except import NotImplementedMethodeError +from Model.Tools.PamhyrListExt import PamhyrModelList +from Model.AdditionalFile.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 af in self._lst: + ok &= af._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 From 4c0a12dcf9632f77c4de34a7f3d2a5e02f693fe6 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 10 Nov 2025 10:21:40 +0100 Subject: [PATCH 20/48] GeoTIFF: Integrate to River submodel. --- src/Model/GeoTIFF/GeoTIFF.py | 76 +++++++++++++++++--------------- src/Model/GeoTIFF/GeoTIFFList.py | 2 +- src/Model/River.py | 7 +++ src/Model/Study.py | 2 +- src/View/MainWindow.py | 4 +- 5 files changed, 52 insertions(+), 39 deletions(-) diff --git a/src/Model/GeoTIFF/GeoTIFF.py b/src/Model/GeoTIFF/GeoTIFF.py index 4bd17632..315d58cf 100644 --- a/src/Model/GeoTIFF/GeoTIFF.py +++ b/src/Model/GeoTIFF/GeoTIFF.py @@ -35,6 +35,8 @@ try: 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}") @@ -153,7 +155,7 @@ class GeoTIFF(SQLSubModel): if self._file_bytes == b'': return None - if self._memfile == None: + if self._memfile is None: self._memfile = MemoryFile() self._memfile.write(self._file_bytes) @@ -202,9 +204,12 @@ class GeoTIFF(SQLSubModel): def _db_update(cls, execute, version, data=None): major, minor, release = version.strip().split(".") - if major == "0" and int(minor) <= 2: + if major == "0" and int(minor) < 2: + cls._db_create(execute) + + if major == "0" and int(minor) == 2: if int(release) < 3: - cls._create_submodel(execute) + cls._db_create(execute) return True @@ -225,43 +230,44 @@ class GeoTIFF(SQLSubModel): "scenario " + "FROM geotiff " + f"WHERE scenario = {scenario.id} " + - f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})" + f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})" ) - for row in table: - it = iter(row) + 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) + 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 = 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 + f._file_bytes = file_bytes - loaded.add(id) - new.append(f) + loaded.add(id) + new.append(f) data["scenario"] = scenario.parent new += cls._db_load(execute, data) @@ -287,7 +293,7 @@ class GeoTIFF(SQLSubModel): self.name, self.description, self.file_name, - self.file_bytes + self.file_bytes, self.coordinates['bottom'], self.coordinates['top'], self.coordinates['left'], diff --git a/src/Model/GeoTIFF/GeoTIFFList.py b/src/Model/GeoTIFF/GeoTIFFList.py index 9e0fafb6..b80a7694 100644 --- a/src/Model/GeoTIFF/GeoTIFFList.py +++ b/src/Model/GeoTIFF/GeoTIFFList.py @@ -20,7 +20,7 @@ from tools import trace, timer from Model.Except import NotImplementedMethodeError from Model.Tools.PamhyrListExt import PamhyrModelList -from Model.AdditionalFile.GeoTIFF import GeoTIFF +from Model.GeoTIFF.GeoTIFF import GeoTIFF class GeoTIFFList(PamhyrModelList): diff --git a/src/Model/River.py b/src/Model/River.py index e0c4602b..b9418026 100644 --- a/src/Model/River.py +++ b/src/Model/River.py @@ -59,6 +59,7 @@ from Model.LateralContributionsAdisTS.LateralContributionsAdisTSList \ import LateralContributionsAdisTSList from Model.D90AdisTS.D90AdisTSList import D90AdisTSList from Model.DIFAdisTS.DIFAdisTSList import DIFAdisTSList +from Model.GeoTIFF.GeoTIFFList import GeoTIFFList from Model.Results.Results import Results logger = logging.getLogger() @@ -468,6 +469,7 @@ class River(Graph): LateralContributionsAdisTSList, D90AdisTSList, DIFAdisTSList, + GeoTIFFList, Results ] @@ -505,6 +507,8 @@ class River(Graph): self._D90AdisTS = D90AdisTSList(status=self._status) self._DIFAdisTS = DIFAdisTSList(status=self._status) + self._geo_tiff = GeoTIFFList(status=self._status) + self._results = {} @classmethod @@ -617,6 +621,8 @@ class River(Graph): new._DIFAdisTS = DIFAdisTSList._db_load(execute, data) + new._geo_tiff = GeoTIFFList._db_load(execute, data) + return new def _db_load_results(self, execute, data=None): @@ -726,6 +732,7 @@ class River(Graph): self._BoundaryConditionsAdisTS, self._LateralContributionsAdisTS, self._D90AdisTS, self._DIFAdisTS, + self._geo_tiff, ] for solver in self._parameters: diff --git a/src/Model/Study.py b/src/Model/Study.py index bdb3d2db..f32e90bf 100644 --- a/src/Model/Study.py +++ b/src/Model/Study.py @@ -37,7 +37,7 @@ logger = logging.getLogger() class Study(SQLModel): - _version = "0.2.2" + _version = "0.2.3" _sub_classes = [ Scenario, diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index 00db0ce4..8f0f54ac 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -1734,11 +1734,11 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): if self._last_solver._type == "mage8": self.open_solver_results( - solver, # self.last_results + solver, # self.last_results ) elif self._last_solver._type == "adistswc": self.open_solver_results_adists( - solver, # self.last_results + solver, # self.last_results ) def open_results_from_file(self): From 5bb6cc40fea1bfedddcb375aa7fa10261977832c Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 10 Nov 2025 16:04:18 +0100 Subject: [PATCH 21/48] GeoTIFF: Add to modules list. --- src/Modules.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Modules.py b/src/Modules.py index fecb6322..860bc0cf 100644 --- a/src/Modules.py +++ b/src/Modules.py @@ -56,6 +56,7 @@ class Modules(IterableFlag): SEDIMENT_LAYER = auto() ADDITIONAL_FILES = auto() OUTPUT_RK = auto() + GEOTIFF = auto() # Results RESULTS = auto() @@ -81,6 +82,7 @@ class Modules(IterableFlag): cls.RESULTS, cls.WINDOW_LIST, cls.OUTPUT_RK, + cls.GEOTIFF ] @classmethod @@ -99,6 +101,7 @@ class Modules(IterableFlag): | cls.HYDRAULIC_STRUCTURES | cls.RESERVOIR | cls.SEDIMENT_LAYER + | cls.GEOTIFF ) @classmethod @@ -114,6 +117,7 @@ class Modules(IterableFlag): cls.HYDRAULIC_STRUCTURES, cls.RESERVOIR, cls.SEDIMENT_LAYER, + cls.GEOTIFF, ] @classmethod @@ -129,6 +133,7 @@ class Modules(IterableFlag): cls.HYDRAULIC_STRUCTURES: "Hydraulic structures", cls.RESERVOIR: "Reservoir", cls.SEDIMENT_LAYER: "Sediment layer", + cls.GEOTIFF: "GeoTIFF", } def impact(self): @@ -168,4 +173,5 @@ _impact = { Modules.HYDRAULIC_STRUCTURES: [], Modules.RESERVOIR: [], Modules.SEDIMENT_LAYER: [], + Modules.GEOTIFF: [], } From 1ed5d69bf4f9336faf52ee70823d4d4f6002032a Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 10 Nov 2025 16:15:40 +0100 Subject: [PATCH 22/48] GeoTIFF: Prepare view integration. --- src/View/GeoTIFF/Edit/Window.py | 102 +++++++++++++++++++++++++++ src/View/GeoTIFF/List.py | 96 +++++++++++++++++++++++++ src/View/GeoTIFF/Translate.py | 35 ++++++++++ src/View/GeoTIFF/UndoCommand.py | 81 +++++++++++++++++++++ src/View/GeoTIFF/Window.py | 120 ++++++++++++++++++++++++++++++++ 5 files changed, 434 insertions(+) create mode 100644 src/View/GeoTIFF/Edit/Window.py create mode 100644 src/View/GeoTIFF/List.py create mode 100644 src/View/GeoTIFF/Translate.py create mode 100644 src/View/GeoTIFF/UndoCommand.py create mode 100644 src/View/GeoTIFF/Window.py diff --git a/src/View/GeoTIFF/Edit/Window.py b/src/View/GeoTIFF/Edit/Window.py new file mode 100644 index 00000000..f3ede26a --- /dev/null +++ b/src/View/GeoTIFF/Edit/Window.py @@ -0,0 +1,102 @@ +# 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 . + +# -*- coding: utf-8 -*- + +import logging + +from Modules import Modules +from View.Tools.PamhyrWindow import PamhyrWindow + +from PyQt5.QtWidgets import ( + QLabel, QPlainTextEdit, QPushButton, + QCheckBox, +) + +from View.GeoTIFF.Translate import GeoTIFFTranslate +from View.GeoTIFF.UndoCommand import ( + SetCommand +) + +logger = logging.getLogger() + + +class EditGeoTIFFWindow(PamhyrWindow): + _pamhyr_ui = "EditGeoTIFF" + _pamhyr_name = "Edit GeoTIFF" + + def __init__(self, study=None, config=None, add_file=None, + trad=None, undo=None, parent=None): + + name = trad[self._pamhyr_name] + " - " + study.name + super(EditGeoTIFFWindow, self).__init__( + title=name, + study=study, + config=config, + options=[], + parent=parent + ) + + self._geotiff = geotiff + self._hash_data.append(self._geotiff) + + self._undo = undo + + self.setup_values() + self.setup_connection() + + 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_path", self._geotiff.description) + self.set_plaintext_edit_text("plainTextEdit", self._geotiff.text) + + 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_path", False) + self.set_plaintext_edit_enable("plainTextEdit", False) + + def setup_connection(self): + self.find(QPushButton, "pushButton_cancel")\ + .clicked.connect(self.close) + self.find(QPushButton, "pushButton_ok")\ + .clicked.connect(self.accept) + + def accept(self): + if self._study.is_editable(): + is_enabled = self.get_check_box("checkBox") + name = self.get_line_edit_text("lineEdit_name") + path = self.get_line_edit_text("lineEdit_path") + coord_bottom = self.get_plaintext_edit_text("plainTextEdit") + coord_top = self.get_plaintext_edit_text("plainTextEdit") + coord_left = self.get_plaintext_edit_text("plainTextEdit") + coord_right = self.get_plaintext_edit_text("plainTextEdit") + + 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, + ) + ) + + self._propagate_update(key=Modules.GEOTIFF) + + self.close() diff --git a/src/View/GeoTIFF/List.py b/src/View/GeoTIFF/List.py new file mode 100644 index 00000000..8bb158aa --- /dev/null +++ b/src/View/GeoTIFF/List.py @@ -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 . + +# -*- 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.path}'" + + 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() diff --git a/src/View/GeoTIFF/Translate.py b/src/View/GeoTIFF/Translate.py new file mode 100644 index 00000000..fe39460f --- /dev/null +++ b/src/View/GeoTIFF/Translate.py @@ -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 . + +# -*- coding: utf-8 -*- + +from PyQt5.QtCore import QCoreApplication +from View.Translate import MainTranslate + +_translate = QCoreApplication.translate + + +class GeoTIFFTranslate(MainTranslate): + def __init__(self): + super(AddFileTranslate, self).__init__() + + self._dict["GeoTIFF files"] = _translate( + "GeoTIFF", "GeoTIFF files" + ) + + self._dict["Edit additional file"] = _translate( + "GeoTIFF", "Edit GeoTIFF file" + ) diff --git a/src/View/GeoTIFF/UndoCommand.py b/src/View/GeoTIFF/UndoCommand.py new file mode 100644 index 00000000..9e26c1be --- /dev/null +++ b/src/View/GeoTIFF/UndoCommand.py @@ -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 . + +# -*- 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() diff --git a/src/View/GeoTIFF/Window.py b/src/View/GeoTIFF/Window.py new file mode 100644 index 00000000..98d1d058 --- /dev/null +++ b/src/View/GeoTIFF/Window.py @@ -0,0 +1,120 @@ +# 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 . + +# -*- coding: utf-8 -*- + +from tools import trace, timer + +from PyQt5.QtWidgets import ( + QAction, QListView, +) + +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 + + +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_connections() + + def setup_list(self): + lst = self.find(QListView, f"listView") + self._list = ListModel( + list_view=lst, + data=self._study.river.geotiff, + undo=self._undo_stack, + trad=self._trad, + ) + + 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) + + self.find(QAction, "action_edit").triggered.connect(self.edit) + + def update(self): + self._list.update() + + 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]) + + def edit(self): + rows = self.selected_rows() + + for row in rows: + add_file = self._study.river.geotiff.files[row] + + if self.sub_window_exists( + EditGeoTIFFWindow, + data=[self._study, self._config, add_file] + ): + continue + + win = EditGeoTIFFWindow( + study=self._study, + config=self._config, + add_file=add_file, + 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() From f27b2cc586e232c67ff843fe4433373cfeb15cae Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 10 Nov 2025 17:09:27 +0100 Subject: [PATCH 23/48] GeoTIFF: Prepare model/view integration. --- src/Model/GeoTIFF/GeoTIFF.py | 44 +++++++++++++++++++++++++++++++++ src/View/GeoTIFF/Edit/Window.py | 17 ++++++++----- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/Model/GeoTIFF/GeoTIFF.py b/src/Model/GeoTIFF/GeoTIFF.py index 315d58cf..221b2acc 100644 --- a/src/Model/GeoTIFF/GeoTIFF.py +++ b/src/Model/GeoTIFF/GeoTIFF.py @@ -80,6 +80,14 @@ class GeoTIFF(SQLSubModel): 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 @@ -96,6 +104,14 @@ class GeoTIFF(SQLSubModel): self.file_name = value elif key == "coordinates": self.coordinates = value + elif key == "coordinates_bottom": + self.coordinates["bottom"] = value + elif key == "coordinates_top": + self.coordinates["top"] = value + elif key == "coordinates_left": + self.coordinates["left"] = value + elif key == "coordinates_right": + self.coordinates["right"] = value self.modified() @@ -147,6 +163,34 @@ class GeoTIFF(SQLSubModel): 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: diff --git a/src/View/GeoTIFF/Edit/Window.py b/src/View/GeoTIFF/Edit/Window.py index f3ede26a..08532128 100644 --- a/src/View/GeoTIFF/Edit/Window.py +++ b/src/View/GeoTIFF/Edit/Window.py @@ -61,8 +61,12 @@ class EditGeoTIFFWindow(PamhyrWindow): 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_path", self._geotiff.description) - self.set_plaintext_edit_text("plainTextEdit", self._geotiff.text) + self.set_line_edit_text("lineEdit_description", self._geotiff.description) + + self.set_double_spin_box("doubleSpinBox_bottom", self._geotiff.coord_bottom) + self.set_double_spin_box("doubleSpinBox_top", self._geotiff.coord_top) + self.set_double_spin_box("doubleSpinBox_left", self._geotiff.coord_left) + self.set_double_spin_box("doubleSpinBox_right", self._geotiff.coord_right) if self._study.is_read_only(): self.set_check_box_enable("checkBox", False) @@ -81,10 +85,11 @@ class EditGeoTIFFWindow(PamhyrWindow): is_enabled = self.get_check_box("checkBox") name = self.get_line_edit_text("lineEdit_name") path = self.get_line_edit_text("lineEdit_path") - coord_bottom = self.get_plaintext_edit_text("plainTextEdit") - coord_top = self.get_plaintext_edit_text("plainTextEdit") - coord_left = self.get_plaintext_edit_text("plainTextEdit") - coord_right = self.get_plaintext_edit_text("plainTextEdit") + + 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( From 6e52b1681eaf7650f2342903762914d4a31a9b07 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Tue, 11 Nov 2025 12:26:50 +0100 Subject: [PATCH 24/48] GeoTIFF: Continue view integration. --- src/Model/GeoTIFF/GeoTIFF.py | 15 ++++++- src/Model/River.py | 10 +++-- src/View/GeoTIFF/Edit/Window.py | 75 ++++++++++++++++++++++++++++----- src/View/GeoTIFF/List.py | 2 +- src/View/GeoTIFF/Translate.py | 2 +- src/View/GeoTIFF/Window.py | 6 +-- src/View/MainWindow.py | 14 ++++++ src/View/Results/translate.py | 5 --- src/View/Translate.py | 12 ++++++ 9 files changed, 116 insertions(+), 25 deletions(-) diff --git a/src/Model/GeoTIFF/GeoTIFF.py b/src/Model/GeoTIFF/GeoTIFF.py index 221b2acc..d7b6e1d7 100644 --- a/src/Model/GeoTIFF/GeoTIFF.py +++ b/src/Model/GeoTIFF/GeoTIFF.py @@ -42,6 +42,8 @@ except Exception as e: print(f"Module 'rasterio' is not available: {e}") _rasterio_loaded = False +logger = logging.getLogger() + class GeoTIFF(SQLSubModel): _sub_classes = [] @@ -57,10 +59,11 @@ class GeoTIFF(SQLSubModel): self._enabled = enabled self._name = f"GeoTIFF #{self._pamhyr_id}" if name == "" else name - self._description = text + self._description = description self._file_bytes = b'' self._coordinates = coordinates + self._file_name = "" if path != "": self.read_file(path) @@ -101,7 +104,7 @@ class GeoTIFF(SQLSubModel): elif key == "description": self.description = value elif key == "file_name": - self.file_name = value + self.read_file(value) elif key == "coordinates": self.coordinates = value elif key == "coordinates_bottom": @@ -206,17 +209,25 @@ class GeoTIFF(SQLSubModel): 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) diff --git a/src/Model/River.py b/src/Model/River.py index b9418026..7520f838 100644 --- a/src/Model/River.py +++ b/src/Model/River.py @@ -507,7 +507,7 @@ class River(Graph): self._D90AdisTS = D90AdisTSList(status=self._status) self._DIFAdisTS = DIFAdisTSList(status=self._status) - self._geo_tiff = GeoTIFFList(status=self._status) + self._geotiff = GeoTIFFList(status=self._status) self._results = {} @@ -621,7 +621,7 @@ class River(Graph): new._DIFAdisTS = DIFAdisTSList._db_load(execute, data) - new._geo_tiff = GeoTIFFList._db_load(execute, data) + new._geotiff = GeoTIFFList._db_load(execute, data) return new @@ -732,7 +732,7 @@ class River(Graph): self._BoundaryConditionsAdisTS, self._LateralContributionsAdisTS, self._D90AdisTS, self._DIFAdisTS, - self._geo_tiff, + self._geotiff, ] for solver in self._parameters: @@ -825,6 +825,10 @@ Last export at: @date.""" def additional_files(self): return self._additional_files + @property + def geotiff(self): + return self._geotiff + @property def rep_lines(self): return self._rep_lines diff --git a/src/View/GeoTIFF/Edit/Window.py b/src/View/GeoTIFF/Edit/Window.py index 08532128..c0d08ef7 100644 --- a/src/View/GeoTIFF/Edit/Window.py +++ b/src/View/GeoTIFF/Edit/Window.py @@ -23,7 +23,15 @@ from View.Tools.PamhyrWindow import PamhyrWindow from PyQt5.QtWidgets import ( QLabel, QPlainTextEdit, QPushButton, - QCheckBox, + QCheckBox, QFileDialog, +) + +from PyQt5.QtGui import ( + QPixmap, +) + +from PyQt5.QtCore import ( + QSettings ) from View.GeoTIFF.Translate import GeoTIFFTranslate @@ -35,22 +43,24 @@ logger = logging.getLogger() class EditGeoTIFFWindow(PamhyrWindow): - _pamhyr_ui = "EditGeoTIFF" + _pamhyr_ui = "GeoTIFF" _pamhyr_name = "Edit GeoTIFF" - def __init__(self, study=None, config=None, add_file=None, + def __init__(self, study=None, config=None, geotiff=None, trad=None, undo=None, parent=None): - name = trad[self._pamhyr_name] + " - " + study.name super(EditGeoTIFFWindow, self).__init__( - title=name, + 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 @@ -61,12 +71,17 @@ class EditGeoTIFFWindow(PamhyrWindow): 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) + self.set_line_edit_text("lineEdit_description", + self._geotiff.description) - self.set_double_spin_box("doubleSpinBox_bottom", self._geotiff.coord_bottom) - self.set_double_spin_box("doubleSpinBox_top", self._geotiff.coord_top) - self.set_double_spin_box("doubleSpinBox_left", self._geotiff.coord_left) - self.set_double_spin_box("doubleSpinBox_right", self._geotiff.coord_right) + self._values = { + "bottom": self._geotiff.coord_bottom, + "top": self._geotiff.coord_top, + "left": self._geotiff.coord_left, + "right": self._geotiff.coord_right, + } + + self._reset_values() if self._study.is_read_only(): self.set_check_box_enable("checkBox", False) @@ -74,11 +89,50 @@ class EditGeoTIFFWindow(PamhyrWindow): self.set_line_edit_enable("lineEdit_path", False) self.set_plaintext_edit_enable("plainTextEdit", False) + def _reset_values(self): + for key in self._values: + self._reset_values_key(key) + + def _reset_values_key(self, key): + self.set_double_spin_box(f"doubleSpinBox_{key}", self._values[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) + + for key in self._values: + self.find(QPushButton, f"pushButton_{key}")\ + .clicked.connect(lambda: self._reset_values_key(key)) + + 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 + + pixmap = QPixmap(filename) + self.find(QLabel, "label_geotiff")\ + .setPixmap(pixmap) def accept(self): if self._study.is_editable(): @@ -99,6 +153,7 @@ class EditGeoTIFFWindow(PamhyrWindow): coordinates_top=coord_top, coordinates_left=coord_left, coordinates_right=coord_right, + file_name=self._file_name, ) ) diff --git a/src/View/GeoTIFF/List.py b/src/View/GeoTIFF/List.py index 8bb158aa..d604d72e 100644 --- a/src/View/GeoTIFF/List.py +++ b/src/View/GeoTIFF/List.py @@ -68,7 +68,7 @@ class ListModel(PamhyrListModel): return QBrush(color) if role == Qt.ItemDataRole.DisplayRole: - text = f"{file.name}: '{file.path}'" + text = f"{file.name}: '{file.description}'" if not file.is_enabled(): text += " (disabled)" diff --git a/src/View/GeoTIFF/Translate.py b/src/View/GeoTIFF/Translate.py index fe39460f..3e328230 100644 --- a/src/View/GeoTIFF/Translate.py +++ b/src/View/GeoTIFF/Translate.py @@ -24,7 +24,7 @@ _translate = QCoreApplication.translate class GeoTIFFTranslate(MainTranslate): def __init__(self): - super(AddFileTranslate, self).__init__() + super(GeoTIFFTranslate, self).__init__() self._dict["GeoTIFF files"] = _translate( "GeoTIFF", "GeoTIFF files" diff --git a/src/View/GeoTIFF/Window.py b/src/View/GeoTIFF/Window.py index 98d1d058..3113c8d3 100644 --- a/src/View/GeoTIFF/Window.py +++ b/src/View/GeoTIFF/Window.py @@ -93,18 +93,18 @@ class GeoTIFFListWindow(PamhyrWindow): rows = self.selected_rows() for row in rows: - add_file = self._study.river.geotiff.files[row] + geotiff= self._study.river.geotiff.files[row] if self.sub_window_exists( EditGeoTIFFWindow, - data=[self._study, self._config, add_file] + data=[self._study, self._config, geotiff] ): continue win = EditGeoTIFFWindow( study=self._study, config=self._config, - add_file=add_file, + geotiff=geotiff, trad=self._trad, undo=self._undo_stack, parent=self, diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index 8f0f54ac..96c123ae 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -79,6 +79,7 @@ from View.Frictions.Window import FrictionsWindow from View.SedimentLayers.Window import SedimentLayersWindow from View.SedimentLayers.Reach.Window import ReachSedimentLayersWindow from View.AdditionalFiles.Window import AddFileListWindow +from View.GeoTIFF.Window import GeoTIFFListWindow from View.REPLines.Window import REPLineListWindow from View.SolverParameters.Window import SolverParametersWindow from View.RunSolver.Window import ( @@ -1360,6 +1361,19 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): ) self.additonal_files.show() + def open_geotiff(self): + if self._study is not None: + if self.sub_window_exists( + GeoTIFFListWindow, + data=[self._study, None] + ): + return + + self.geotiff = GeoTIFFListWindow( + study=self._study, parent=self + ) + self.geotiff.show() + def open_rep_lines(self): if self._study is not None: if self.sub_window_exists( diff --git a/src/View/Results/translate.py b/src/View/Results/translate.py index 7a1e7e9b..95ddd9a4 100644 --- a/src/View/Results/translate.py +++ b/src/View/Results/translate.py @@ -53,11 +53,6 @@ class ResultsTranslate(MainTranslate): "Results", "Max water elevation" ) - self._dict["file_all"] = _translate("Results", "All files (*)") - self._dict["file_geotiff"] = _translate( - "Results", "GeoTIFF file (*.tiff *.tif)") - self._dict["file_csv"] = _translate( - "Results", "CSV file (*.csv)") self._dict["ImageCoordinates"] = _translate( "Results", "Image coordinates" ) diff --git a/src/View/Translate.py b/src/View/Translate.py index 463b1057..e288e196 100644 --- a/src/View/Translate.py +++ b/src/View/Translate.py @@ -54,6 +54,18 @@ class CommonWordTranslate(PamhyrTranslate): self._dict["method"] = _translate("CommonWord", "Method") + # Files + self._dict["open_file"] = _translate( + "CommonWord", "Open file" + ) + self._dict["file_all"] = _translate("CommonWord", "All files (*)") + self._dict["file_geotiff"] = _translate( + "CommonWord", "GeoTIFF file (*.tiff *.tif)" + ) + self._dict["file_csv"] = _translate( + "CommonWord", "CSV file (*.csv)" + ) + class UnitTranslate(CommonWordTranslate): def __init__(self): From 5c83d678656da7306e7e439a0ed8a319bfec115d Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Wed, 12 Nov 2025 10:41:45 +0100 Subject: [PATCH 25/48] GeoTIFF: Continue intergation. --- src/Model/GeoTIFF/GeoTIFF.py | 10 +++- src/Model/GeoTIFF/GeoTIFFList.py | 4 +- src/View/GeoTIFF/Edit/Window.py | 89 ++++++++++++++++++++++++++++---- 3 files changed, 90 insertions(+), 13 deletions(-) diff --git a/src/Model/GeoTIFF/GeoTIFF.py b/src/Model/GeoTIFF/GeoTIFF.py index d7b6e1d7..b92d9163 100644 --- a/src/Model/GeoTIFF/GeoTIFF.py +++ b/src/Model/GeoTIFF/GeoTIFF.py @@ -62,7 +62,15 @@ class GeoTIFF(SQLSubModel): self._description = description self._file_bytes = b'' - self._coordinates = coordinates + 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 != "": diff --git a/src/Model/GeoTIFF/GeoTIFFList.py b/src/Model/GeoTIFF/GeoTIFFList.py index b80a7694..84f3c803 100644 --- a/src/Model/GeoTIFF/GeoTIFFList.py +++ b/src/Model/GeoTIFF/GeoTIFFList.py @@ -43,8 +43,8 @@ class GeoTIFFList(PamhyrModelList): f"WHERE scenario = {self._status.scenario_id}" ) - for af in self._lst: - ok &= af._db_save(execute, data) + for gt in self._lst: + ok &= gt._db_save(execute, data) return ok diff --git a/src/View/GeoTIFF/Edit/Window.py b/src/View/GeoTIFF/Edit/Window.py index c0d08ef7..299c3bdb 100644 --- a/src/View/GeoTIFF/Edit/Window.py +++ b/src/View/GeoTIFF/Edit/Window.py @@ -23,11 +23,7 @@ from View.Tools.PamhyrWindow import PamhyrWindow from PyQt5.QtWidgets import ( QLabel, QPlainTextEdit, QPushButton, - QCheckBox, QFileDialog, -) - -from PyQt5.QtGui import ( - QPixmap, + QCheckBox, QFileDialog, QVBoxLayout, ) from PyQt5.QtCore import ( @@ -39,6 +35,23 @@ from View.GeoTIFF.UndoCommand import ( SetCommand ) +from View.Tools.Plot.PamhyrCanvas import MplCanvas +from View.LateralContribution.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() @@ -65,9 +78,30 @@ class EditGeoTIFFWindow(PamhyrWindow): self._undo = undo + self.setup_graph() self.setup_values() 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.plot_layout.addWidget(self.canvas) + + self.plot = PlotXY( + canvas=self.canvas, + data=None, + trad=self._trad, + toolbar=None, + parent=self + ) + + 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) @@ -89,6 +123,16 @@ class EditGeoTIFFWindow(PamhyrWindow): self.set_line_edit_enable("lineEdit_path", False) self.set_plaintext_edit_enable("plainTextEdit", False) + def _set_values_from_bounds(self, bounds): + self._values = { + "bottom": bounds[0], + "top": bounds[2], + "left": bounds[1], + "right": bounds[3], + } + + self._reset_values() + def _reset_values(self): for key in self._values: self._reset_values_key(key) @@ -108,6 +152,34 @@ class EditGeoTIFFWindow(PamhyrWindow): self.find(QPushButton, f"pushButton_{key}")\ .clicked.connect(lambda: self._reset_values_key(key)) + 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[:] + else: + with memfile.open() as gt: + img = gt.read() + b = gt.bounds[:] + + if self._plot_img is not None: + self._plot_img.remove() + + self._set_values_from_bounds(b) + + self._plot_img = self.canvas.axes.imshow( + img.transpose((1, 2, 0)), + extent=[b[0], b[2], b[1], b[3]] + ) + + self.plot.idle() + def _import(self): options = QFileDialog.Options() settings = QSettings(QSettings.IniFormat, @@ -129,16 +201,13 @@ class EditGeoTIFFWindow(PamhyrWindow): if filename != "": self._file_name = filename - - pixmap = QPixmap(filename) - self.find(QLabel, "label_geotiff")\ - .setPixmap(pixmap) + 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") - path = self.get_line_edit_text("lineEdit_path") + 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") From 7bce725c63ec9faec8cfefada49486682199cf8e Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Wed, 12 Nov 2025 10:43:20 +0100 Subject: [PATCH 26/48] GeoTIFF: Fix pep8. --- src/Model/GeoTIFF/GeoTIFF.py | 2 +- src/View/GeoTIFF/Window.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Model/GeoTIFF/GeoTIFF.py b/src/Model/GeoTIFF/GeoTIFF.py index b92d9163..a6f6de59 100644 --- a/src/Model/GeoTIFF/GeoTIFF.py +++ b/src/Model/GeoTIFF/GeoTIFF.py @@ -64,7 +64,7 @@ class GeoTIFF(SQLSubModel): self._file_bytes = b'' if coordinates is None: self._coordinates = { - "bottom" : 0.0, + "bottom": 0.0, "top": 0.0, "left": 0.0, "right": 0.0, diff --git a/src/View/GeoTIFF/Window.py b/src/View/GeoTIFF/Window.py index 3113c8d3..69fa40c7 100644 --- a/src/View/GeoTIFF/Window.py +++ b/src/View/GeoTIFF/Window.py @@ -93,7 +93,7 @@ class GeoTIFFListWindow(PamhyrWindow): rows = self.selected_rows() for row in rows: - geotiff= self._study.river.geotiff.files[row] + geotiff = self._study.river.geotiff.files[row] if self.sub_window_exists( EditGeoTIFFWindow, From 8976f054c7e77aa7ef513432e02f007f40626ed6 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Wed, 12 Nov 2025 11:16:28 +0100 Subject: [PATCH 27/48] GeoTIFF: Save geotiff into study db. --- src/Model/GeoTIFF/GeoTIFF.py | 2 +- src/Model/River.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Model/GeoTIFF/GeoTIFF.py b/src/Model/GeoTIFF/GeoTIFF.py index a6f6de59..e86588be 100644 --- a/src/Model/GeoTIFF/GeoTIFF.py +++ b/src/Model/GeoTIFF/GeoTIFF.py @@ -356,7 +356,7 @@ class GeoTIFF(SQLSubModel): self.name, self.description, self.file_name, - self.file_bytes, + self._file_bytes, self.coordinates['bottom'], self.coordinates['top'], self.coordinates['left'], diff --git a/src/Model/River.py b/src/Model/River.py index 7520f838..ba5f5e8a 100644 --- a/src/Model/River.py +++ b/src/Model/River.py @@ -656,6 +656,8 @@ class River(Graph): objs.append(self._D90AdisTS) objs.append(self._DIFAdisTS) + objs.append(self._geotiff) + for solv_type in self.results: objs.append(self.results[solv_type]) From 2e360943b28fbd16f6c1d1a408ce3907e0c20ae5 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Wed, 12 Nov 2025 11:42:46 +0100 Subject: [PATCH 28/48] GeoTIFF: Switch PlotXY to display all river reaches. --- src/View/GeoTIFF/Edit/Window.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/View/GeoTIFF/Edit/Window.py b/src/View/GeoTIFF/Edit/Window.py index 299c3bdb..8559710f 100644 --- a/src/View/GeoTIFF/Edit/Window.py +++ b/src/View/GeoTIFF/Edit/Window.py @@ -36,7 +36,7 @@ from View.GeoTIFF.UndoCommand import ( ) from View.Tools.Plot.PamhyrCanvas import MplCanvas -from View.LateralContribution.PlotXY import PlotXY +from View.PlotXY import PlotXY try: import rasterio @@ -90,11 +90,12 @@ class EditGeoTIFFWindow(PamhyrWindow): self.plot = PlotXY( canvas=self.canvas, - data=None, + data=self._study.river.enable_edges(), trad=self._trad, toolbar=None, parent=self ) + self.plot.update() self._plot_img = None From a2f3d220013193ef53f99a5cea2ba7fc2c12139c Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Thu, 13 Nov 2025 10:44:23 +0100 Subject: [PATCH 29/48] GeoTIFF: Add geometry and geotiff diplay next to the geotiff list. --- src/View/Debug/Window.py | 4 ++- src/View/GeoTIFF/Window.py | 70 +++++++++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/src/View/Debug/Window.py b/src/View/Debug/Window.py index c3ebae91..c5f4c720 100644 --- a/src/View/Debug/Window.py +++ b/src/View/Debug/Window.py @@ -17,6 +17,7 @@ # -*- coding: utf-8 -*- import logging +import traceback from tools import trace, timer @@ -92,7 +93,8 @@ class ReplWindow(PamhyrWindow): value = exec(rich_code) value = self.__debug_exec_result__ except Exception as e: - value = f"" + str(e) + "" + value = f"" + str(e) + "\n" + value += f"{traceback.format_exc()}" # Display code msg = f" # " + code + " #" diff --git a/src/View/GeoTIFF/Window.py b/src/View/GeoTIFF/Window.py index 69fa40c7..12ab2047 100644 --- a/src/View/GeoTIFF/Window.py +++ b/src/View/GeoTIFF/Window.py @@ -19,7 +19,7 @@ from tools import trace, timer from PyQt5.QtWidgets import ( - QAction, QListView, + QAction, QListView, QVBoxLayout, ) from View.Tools.PamhyrWindow import PamhyrWindow @@ -28,6 +28,23 @@ from View.GeoTIFF.List import ListModel from View.GeoTIFF.Translate import GeoTIFFTranslate from View.GeoTIFF.Edit.Window import EditGeoTIFFWindow +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" @@ -48,6 +65,7 @@ class GeoTIFFListWindow(PamhyrWindow): ) self.setup_list() + self.setup_graph() self.setup_connections() def setup_list(self): @@ -59,6 +77,26 @@ class GeoTIFFListWindow(PamhyrWindow): 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.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 = {} + + for geotiff in self._study.river.geotiff.lst: + self.draw_geotiff(geotiff) + def setup_connections(self): if self._study.is_editable(): self.find(QAction, "action_add").triggered.connect(self.add) @@ -66,9 +104,39 @@ class GeoTIFFListWindow(PamhyrWindow): self.find(QAction, "action_edit").triggered.connect(self.edit) + def draw_geotiff(self, geotiff): + if not _rasterio_loaded: + return + + memfile = geotiff.memfile + if memfile is None: + return + + with memfile.open() as gt: + img = gt.read() + coords = geotiff.coordinates + + if geotiff in self._plot_img: + self._plot_img[geotiff].remove() + + self._plot_img[geotiff] = self.canvas.axes.imshow( + img.transpose((1, 2, 0)), + extent=list(coords.values()) + ) + + self.plot.idle() + def update(self): self._list.update() + for geotiff in self._plot_img: + self._plot_img[geotiff].remove() + + self._plot_img = {} + + for geotiff in self._study.river.geotiff.lst: + self.draw_geotiff(geotiff) + def selected_rows(self): lst = self.find(QListView, f"listView") return list(map(lambda i: i.row(), lst.selectedIndexes())) From 869e116ad0e9c651a48c8b5f335773a3465e3098 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Thu, 13 Nov 2025 10:49:22 +0100 Subject: [PATCH 30/48] GeoTIFF: Minor change. --- src/View/GeoTIFF/Window.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/View/GeoTIFF/Window.py b/src/View/GeoTIFF/Window.py index 12ab2047..e87db148 100644 --- a/src/View/GeoTIFF/Window.py +++ b/src/View/GeoTIFF/Window.py @@ -156,6 +156,7 @@ class GeoTIFFListWindow(PamhyrWindow): return self._list.delete(rows[0]) + self.update() def edit(self): rows = self.selected_rows() From 9308a73e8ee5b030a35dfed2ca99c8f3858078b3 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Thu, 13 Nov 2025 11:47:51 +0100 Subject: [PATCH 31/48] GeoTIFF: Minor change. --- src/Model/GeoTIFF/GeoTIFF.py | 3 ++- src/View/GeoTIFF/Edit/Window.py | 36 +++++++++++++++++++++++++-------- src/View/GeoTIFF/Window.py | 2 +- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/Model/GeoTIFF/GeoTIFF.py b/src/Model/GeoTIFF/GeoTIFF.py index e86588be..c35bc875 100644 --- a/src/Model/GeoTIFF/GeoTIFF.py +++ b/src/Model/GeoTIFF/GeoTIFF.py @@ -112,7 +112,8 @@ class GeoTIFF(SQLSubModel): elif key == "description": self.description = value elif key == "file_name": - self.read_file(value) + if self._file_name != value: + self.read_file(value) elif key == "coordinates": self.coordinates = value elif key == "coordinates_bottom": diff --git a/src/View/GeoTIFF/Edit/Window.py b/src/View/GeoTIFF/Edit/Window.py index 8559710f..91c083da 100644 --- a/src/View/GeoTIFF/Edit/Window.py +++ b/src/View/GeoTIFF/Edit/Window.py @@ -22,8 +22,8 @@ from Modules import Modules from View.Tools.PamhyrWindow import PamhyrWindow from PyQt5.QtWidgets import ( - QLabel, QPlainTextEdit, QPushButton, - QCheckBox, QFileDialog, QVBoxLayout, + QLabel, QPlainTextEdit, QPushButton, QCheckBox, + QFileDialog, QVBoxLayout, QDoubleSpinBox, ) from PyQt5.QtCore import ( @@ -78,8 +78,8 @@ class EditGeoTIFFWindow(PamhyrWindow): self._undo = undo - self.setup_graph() self.setup_values() + self.setup_graph() self.setup_connection() def setup_graph(self): @@ -115,6 +115,12 @@ class EditGeoTIFFWindow(PamhyrWindow): "left": self._geotiff.coord_left, "right": self._geotiff.coord_right, } + self._values_default = { + "bottom": self._geotiff.coord_bottom, + "top": self._geotiff.coord_top, + "left": self._geotiff.coord_left, + "right": self._geotiff.coord_right, + } self._reset_values() @@ -131,6 +137,12 @@ class EditGeoTIFFWindow(PamhyrWindow): "left": bounds[1], "right": bounds[3], } + self._values_default = { + "bottom": bounds[0], + "top": bounds[2], + "left": bounds[1], + "right": bounds[3], + } self._reset_values() @@ -139,7 +151,7 @@ class EditGeoTIFFWindow(PamhyrWindow): self._reset_values_key(key) def _reset_values_key(self, key): - self.set_double_spin_box(f"doubleSpinBox_{key}", self._values[key]) + self.set_double_spin_box(f"doubleSpinBox_{key}", self._values_default[key]) def setup_connection(self): self.find(QPushButton, "pushButton_cancel")\ @@ -153,6 +165,14 @@ class EditGeoTIFFWindow(PamhyrWindow): self.find(QPushButton, f"pushButton_{key}")\ .clicked.connect(lambda: self._reset_values_key(key)) + self.find(QDoubleSpinBox, f"doubleSpinBox_{key}")\ + .valueChanged.connect(lambda: self.update_spinbox_value(key)) + + def update_spinbox_value(self, key): + self._values[key] = self.get_double_spin_box(f"doubleSpinBox_{key}") + + self._plot_img.set_extent(list(self._values.values())) + def draw_geotiff(self, memfile=None): if not _rasterio_loaded: return @@ -169,14 +189,14 @@ class EditGeoTIFFWindow(PamhyrWindow): img = gt.read() b = gt.bounds[:] + self._set_values_from_bounds(b) + if self._plot_img is not None: self._plot_img.remove() - self._set_values_from_bounds(b) - self._plot_img = self.canvas.axes.imshow( img.transpose((1, 2, 0)), - extent=[b[0], b[2], b[1], b[3]] + extent=list(self._values.values()) ) self.plot.idle() @@ -202,7 +222,7 @@ class EditGeoTIFFWindow(PamhyrWindow): if filename != "": self._file_name = filename - self. draw_geotiff() + self.draw_geotiff() def accept(self): if self._study.is_editable(): diff --git a/src/View/GeoTIFF/Window.py b/src/View/GeoTIFF/Window.py index e87db148..64bd2d3c 100644 --- a/src/View/GeoTIFF/Window.py +++ b/src/View/GeoTIFF/Window.py @@ -134,7 +134,7 @@ class GeoTIFFListWindow(PamhyrWindow): self._plot_img = {} - for geotiff in self._study.river.geotiff.lst: + for geotiff in self._study.river.geotiff.files: self.draw_geotiff(geotiff) def selected_rows(self): From deb9b2069f81db7085179f057b8ddf356e2db0d6 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Thu, 13 Nov 2025 14:56:03 +0100 Subject: [PATCH 32/48] GeoTIFF: Integrate geotiff menu to mainwindow and fix bounds update. --- src/View/GeoTIFF/Edit/Window.py | 49 ++++++++++++++++----------------- src/View/MainWindow.py | 3 +- src/View/ui/MainWindow.ui | 6 ++++ 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/View/GeoTIFF/Edit/Window.py b/src/View/GeoTIFF/Edit/Window.py index 91c083da..37ab2e58 100644 --- a/src/View/GeoTIFF/Edit/Window.py +++ b/src/View/GeoTIFF/Edit/Window.py @@ -85,7 +85,8 @@ class EditGeoTIFFWindow(PamhyrWindow): 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.plot_layout = self.find(QVBoxLayout, + "verticalLayout_geotiff") self.plot_layout.addWidget(self.canvas) self.plot = PlotXY( @@ -109,20 +110,10 @@ class EditGeoTIFFWindow(PamhyrWindow): self.set_line_edit_text("lineEdit_description", self._geotiff.description) - self._values = { - "bottom": self._geotiff.coord_bottom, - "top": self._geotiff.coord_top, - "left": self._geotiff.coord_left, - "right": self._geotiff.coord_right, - } - self._values_default = { - "bottom": self._geotiff.coord_bottom, - "top": self._geotiff.coord_top, - "left": self._geotiff.coord_left, - "right": self._geotiff.coord_right, - } - - self._reset_values() + 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) @@ -137,6 +128,8 @@ class EditGeoTIFFWindow(PamhyrWindow): "left": bounds[1], "right": bounds[3], } + + def _set_default_values_from_bounds(self, bounds): self._values_default = { "bottom": bounds[0], "top": bounds[2], @@ -144,14 +137,15 @@ class EditGeoTIFFWindow(PamhyrWindow): "right": bounds[3], } - self._reset_values() - - def _reset_values(self): + def _reset_spinboxes(self): for key in self._values: - self._reset_values_key(key) + self._reset_spinbox(key) - def _reset_values_key(self, key): - self.set_double_spin_box(f"doubleSpinBox_{key}", self._values_default[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")\ @@ -163,15 +157,18 @@ class EditGeoTIFFWindow(PamhyrWindow): for key in self._values: self.find(QPushButton, f"pushButton_{key}")\ - .clicked.connect(lambda: self._reset_values_key(key)) + .clicked.connect(lambda: self._reset_spinbox(key)) self.find(QDoubleSpinBox, f"doubleSpinBox_{key}")\ - .valueChanged.connect(lambda: self.update_spinbox_value(key)) + .valueChanged.connect( + lambda: self.update_values_from_spinbox(key) + ) - def update_spinbox_value(self, key): + def update_values_from_spinbox(self, key): self._values[key] = self.get_double_spin_box(f"doubleSpinBox_{key}") self._plot_img.set_extent(list(self._values.values())) + self.plot.idle() def draw_geotiff(self, memfile=None): if not _rasterio_loaded: @@ -184,13 +181,13 @@ class EditGeoTIFFWindow(PamhyrWindow): with rasterio.open(self._file_name) as data: img = data.read() b = data.bounds[:] + + self._set_values_from_bounds(b) else: with memfile.open() as gt: img = gt.read() b = gt.bounds[:] - self._set_values_from_bounds(b) - if self._plot_img is not None: self._plot_img.remove() diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index 96c123ae..da2b7bbf 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -158,7 +158,7 @@ define_model_action = [ "action_menu_boundary_conditions_sediment", "action_menu_rep_additional_lines", "action_menu_output_rk", "action_menu_run_adists", "action_menu_pollutants", - "action_menu_d90", "action_menu_dif", + "action_menu_d90", "action_menu_dif", "action_menu_edit_geotiff" ] action = ( @@ -298,6 +298,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): self.open_reach_sediment_layers, "action_menu_additional_file": self.open_additional_files, "action_menu_rep_additional_lines": self.open_rep_lines, + "action_menu_edit_geotiff": self.open_geotiff, "action_menu_close": self.close_model, "action_menu_results_last": self.open_last_results, "action_menu_open_results_from_file": self.open_results_from_file, diff --git a/src/View/ui/MainWindow.ui b/src/View/ui/MainWindow.ui index 96661c4f..b5c6f93f 100644 --- a/src/View/ui/MainWindow.ui +++ b/src/View/ui/MainWindow.ui @@ -129,6 +129,7 @@ &Geometry + @@ -811,6 +812,11 @@ Compare results + + + GeoTIFF + + From 786923bdbfc81a8d84d23b8de91e953998700cf2 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Thu, 13 Nov 2025 15:08:50 +0100 Subject: [PATCH 33/48] GeoTIFF: Add missing ui files. --- src/View/ui/GeoTIFF.ui | 264 +++++++++++++++++++++++++++++++++++++ src/View/ui/GeoTIFFList.ui | 84 ++++++++++++ 2 files changed, 348 insertions(+) create mode 100644 src/View/ui/GeoTIFF.ui create mode 100644 src/View/ui/GeoTIFFList.ui diff --git a/src/View/ui/GeoTIFF.ui b/src/View/ui/GeoTIFF.ui new file mode 100644 index 00000000..48d953da --- /dev/null +++ b/src/View/ui/GeoTIFF.ui @@ -0,0 +1,264 @@ + + + MainWindow + + + + 0 + 0 + 896 + 504 + + + + MainWindow + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Cancel + + + + + + + Ok + + + + + + + + + Informations + + + + + + Name + + + + + + + Enabled + + + true + + + + + + + Description + + + + + + + The relative file path on executable directory + + + + + + + + + + + + + GeoTIFF file + + + + + + Import GeoTIFF file + + + + + + + Qt::Horizontal + + + + + + + + + + + + Reset + + + + + + + true + + + Right coordinate + + + + + + + Reset + + + + + + + 4 + + + -99999999.000000000000000 + + + 99999999.000000000000000 + + + + + + + Reset + + + + + + + Reset + + + + + + + 4 + + + -99999999.000000000000000 + + + 99999999.000000000000000 + + + 1.000000000000000 + + + + + + + 4 + + + -99999999.000000000000000 + + + 99999999.000000000000000 + + + 1.000000000000000 + + + + + + + Top coordinate + + + + + + + 4 + + + -99999999.000000000000000 + + + 99999999.000000000000000 + + + + + + + Left coordinate + + + + + + + Bottom coordinate + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + diff --git a/src/View/ui/GeoTIFFList.ui b/src/View/ui/GeoTIFFList.ui new file mode 100644 index 00000000..b05ec968 --- /dev/null +++ b/src/View/ui/GeoTIFFList.ui @@ -0,0 +1,84 @@ + + + MainWindow + + + + 0 + 0 + 896 + 504 + + + + MainWindow + + + + + + + Qt::Horizontal + + + + + + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + + + + + ressources/add.pngressources/add.png + + + Add + + + Add a new file + + + + + + ressources/del.pngressources/del.png + + + Delete + + + Delete selected file(s) + + + + + + ressources/edit.pngressources/edit.png + + + Edit + + + Edit file + + + + + + From 7f0102a88122f9cb64eaa919ff85f32a805055c3 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Fri, 14 Nov 2025 09:56:46 +0100 Subject: [PATCH 34/48] GeoTIFF: Add geotiff to mainwindow tab info and fix bounds at file import. --- src/View/GeoTIFF/Edit/Window.py | 28 ++++++++++++++++------ src/View/GeoTIFF/Window.py | 2 -- src/View/MainWindowTabInfo.py | 41 +++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/View/GeoTIFF/Edit/Window.py b/src/View/GeoTIFF/Edit/Window.py index 37ab2e58..5cbba817 100644 --- a/src/View/GeoTIFF/Edit/Window.py +++ b/src/View/GeoTIFF/Edit/Window.py @@ -35,6 +35,7 @@ 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 @@ -87,6 +88,11 @@ class EditGeoTIFFWindow(PamhyrWindow): 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( @@ -124,16 +130,16 @@ class EditGeoTIFFWindow(PamhyrWindow): def _set_values_from_bounds(self, bounds): self._values = { "bottom": bounds[0], - "top": bounds[2], - "left": bounds[1], + "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[2], - "left": bounds[1], + "top": bounds[1], + "left": bounds[2], "right": bounds[3], } @@ -180,13 +186,20 @@ class EditGeoTIFFWindow(PamhyrWindow): with rasterio.open(self._file_name) as data: img = data.read() - b = data.bounds[:] + b = data.bounds[:] # left, bottom, right, top - self._set_values_from_bounds(b) + if b[2] > b[0] and b[1] < b[3]: + coord = [b[0], b[2], b[1], b[3]] + else: + xlim = self.canvas.axes.get_xlim() + ylim = self.canvas.axes.get_ylim() + coord = xlim + ylim + + self._set_values_from_bounds(coord) + self._set_default_values_from_bounds(coord) else: with memfile.open() as gt: img = gt.read() - b = gt.bounds[:] if self._plot_img is not None: self._plot_img.remove() @@ -197,6 +210,7 @@ class EditGeoTIFFWindow(PamhyrWindow): ) self.plot.idle() + self._reset_spinboxes() def _import(self): options = QFileDialog.Options() diff --git a/src/View/GeoTIFF/Window.py b/src/View/GeoTIFF/Window.py index 64bd2d3c..724a76c3 100644 --- a/src/View/GeoTIFF/Window.py +++ b/src/View/GeoTIFF/Window.py @@ -124,8 +124,6 @@ class GeoTIFFListWindow(PamhyrWindow): extent=list(coords.values()) ) - self.plot.idle() - def update(self): self._list.update() diff --git a/src/View/MainWindowTabInfo.py b/src/View/MainWindowTabInfo.py index 1dc959fb..447e3e40 100644 --- a/src/View/MainWindowTabInfo.py +++ b/src/View/MainWindowTabInfo.py @@ -27,6 +27,20 @@ from View.Tools.PamhyrWidget import PamhyrWidget from PyQt5.QtWidgets import QVBoxLayout +try: + import rasterio + import rasterio.control + import rasterio.crs + import rasterio.sample + import rasterio.vrt + import rasterio._features + + from rasterio.io import MemoryFile + _rasterio_loaded = True +except Exception as e: + print(f"Module 'rasterio' is not available: {e}") + _rasterio_loaded = False + logger = logging.getLogger() @@ -90,6 +104,8 @@ class WidgetInfo(PamhyrWidget): parent=self ) + self._plot_img = {} + def update(self): if self._study is None: self.set_initial_values() @@ -108,8 +124,33 @@ class WidgetInfo(PamhyrWidget): toolbar=self._toolbar_xy, parent=self ) + + self.draw_all_geotiff() self.plot.update() + def draw_all_geotiff(self): + if not _rasterio_loaded: + return + + for img in self._plot_img: + self._plot_img[img].remove() + + self._plot_img = {} + + for geotiff in self._study.river.geotiff.lst: + memfile = geotiff.memfile + if memfile is None: + return + + with memfile.open() as gt: + img = gt.read() + coords = geotiff.coordinates + + self._plot_img[geotiff] = self.canvas.axes.imshow( + img.transpose((1, 2, 0)), + extent=list(coords.values()) + ) + def set_network_values(self): river = self._study.river From 97ece018aa29b66e6aaf02d1f1c4b55064210dc4 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Fri, 14 Nov 2025 10:01:56 +0100 Subject: [PATCH 35/48] Reservoirs: Fix loading without nodes. --- src/Model/Reservoir/Reservoir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Model/Reservoir/Reservoir.py b/src/Model/Reservoir/Reservoir.py index 1d1998b6..c093691c 100644 --- a/src/Model/Reservoir/Reservoir.py +++ b/src/Model/Reservoir/Reservoir.py @@ -310,7 +310,7 @@ class Reservoir(SQLSubModel): new_reservoir.set_as_deleted() new_reservoir._node = None - if node_id != -1: + if node_id != -1 and node_id is not None: new_reservoir._node = next( filter( lambda n: n.id == node_id, data["nodes"] From ae086421165a51577fad5fd50981fe33dc3912a1 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Fri, 14 Nov 2025 15:04:31 +0100 Subject: [PATCH 36/48] GeoTIFF: Factorise draw geotiff function. --- src/View/GeoTIFF/Window.py | 35 ++----------------- src/View/MainWindowTabInfo.py | 27 +-------------- src/View/PlotXY.py | 63 +++++++++++++++++++++++++++++++---- 3 files changed, 60 insertions(+), 65 deletions(-) diff --git a/src/View/GeoTIFF/Window.py b/src/View/GeoTIFF/Window.py index 724a76c3..1a924ecf 100644 --- a/src/View/GeoTIFF/Window.py +++ b/src/View/GeoTIFF/Window.py @@ -86,17 +86,14 @@ class GeoTIFFListWindow(PamhyrWindow): 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.update() - self._plot_img = {} - - for geotiff in self._study.river.geotiff.lst: - self.draw_geotiff(geotiff) - def setup_connections(self): if self._study.is_editable(): self.find(QAction, "action_add").triggered.connect(self.add) @@ -104,37 +101,9 @@ class GeoTIFFListWindow(PamhyrWindow): self.find(QAction, "action_edit").triggered.connect(self.edit) - def draw_geotiff(self, geotiff): - if not _rasterio_loaded: - return - - memfile = geotiff.memfile - if memfile is None: - return - - with memfile.open() as gt: - img = gt.read() - coords = geotiff.coordinates - - if geotiff in self._plot_img: - self._plot_img[geotiff].remove() - - self._plot_img[geotiff] = self.canvas.axes.imshow( - img.transpose((1, 2, 0)), - extent=list(coords.values()) - ) - def update(self): self._list.update() - for geotiff in self._plot_img: - self._plot_img[geotiff].remove() - - self._plot_img = {} - - for geotiff in self._study.river.geotiff.files: - self.draw_geotiff(geotiff) - def selected_rows(self): lst = self.find(QListView, f"listView") return list(map(lambda i: i.row(), lst.selectedIndexes())) diff --git a/src/View/MainWindowTabInfo.py b/src/View/MainWindowTabInfo.py index 447e3e40..a895f723 100644 --- a/src/View/MainWindowTabInfo.py +++ b/src/View/MainWindowTabInfo.py @@ -104,8 +104,6 @@ class WidgetInfo(PamhyrWidget): parent=self ) - self._plot_img = {} - def update(self): if self._study is None: self.set_initial_values() @@ -120,37 +118,14 @@ class WidgetInfo(PamhyrWidget): self.plot = PlotXY( canvas=self.canvas, data=self._study.river.enable_edges(), + geotiff=self._study.river.geotiff, trad=self.parent._trad, toolbar=self._toolbar_xy, parent=self ) - self.draw_all_geotiff() self.plot.update() - def draw_all_geotiff(self): - if not _rasterio_loaded: - return - - for img in self._plot_img: - self._plot_img[img].remove() - - self._plot_img = {} - - for geotiff in self._study.river.geotiff.lst: - memfile = geotiff.memfile - if memfile is None: - return - - with memfile.open() as gt: - img = gt.read() - coords = geotiff.coordinates - - self._plot_img[geotiff] = self.canvas.axes.imshow( - img.transpose((1, 2, 0)), - extent=list(coords.values()) - ) - def set_network_values(self): river = self._study.river diff --git a/src/View/PlotXY.py b/src/View/PlotXY.py index c8ed228f..32f9d523 100644 --- a/src/View/PlotXY.py +++ b/src/View/PlotXY.py @@ -27,12 +27,26 @@ from PyQt5.QtCore import ( ) from PyQt5.QtWidgets import QApplication, QTableView +try: + import rasterio + import rasterio.control + import rasterio.crs + import rasterio.sample + import rasterio.vrt + import rasterio._features + + from rasterio.io import MemoryFile + _rasterio_loaded = True +except Exception as e: + print(f"Module 'rasterio' is not available: {e}") + _rasterio_loaded = False + _translate = QCoreApplication.translate class PlotXY(PamhyrPlot): - def __init__(self, canvas=None, trad=None, data=None, toolbar=None, - table=None, parent=None): + def __init__(self, canvas=None, trad=None, data=None, geotiff=None, + toolbar=None, table=None, parent=None): super(PlotXY, self).__init__( canvas=canvas, trad=trad, @@ -43,10 +57,14 @@ class PlotXY(PamhyrPlot): ) self._data = data + self._geotiff = geotiff + self.label_x = self._trad["x"] self.label_y = self._trad["y"] self.parent = parent + self._plot_img = {} + self._isometric_axis = True self._auto_relim_update = True @@ -69,7 +87,12 @@ class PlotXY(PamhyrPlot): if data.reach.number_profiles != 0: self.draw_xy(data.reach) self.draw_lr(data.reach) - self.idle() + + if self._geotiff is not None: + self.draw_geotiff(self._geotiff.files) + + self.idle() + return def draw_xy(self, reach): @@ -78,9 +101,9 @@ class PlotXY(PamhyrPlot): line_xy.append(np.column_stack(xy)) line_xy_collection = collections.LineCollection( - line_xy, - colors=self.color_plot_river_bottom - ) + line_xy, + colors=self.color_plot_river_bottom + ) self.canvas.axes.add_collection(line_xy_collection) def draw_lr(self, reach): @@ -113,6 +136,34 @@ class PlotXY(PamhyrPlot): ) self.line_lr.append(line) + def draw_geotiff(self, lst): + if not _rasterio_loaded: + return + + for img in self._plot_img: + self._plot_img[img].remove() + + self._plot_img = {} + + for geotiff in lst: + memfile = geotiff.memfile + if memfile is None: + return + + with memfile.open() as gt: + img = gt.read() + coords = geotiff.coordinates + + self._plot_img[geotiff] = self.canvas.axes.imshow( + img.transpose((1, 2, 0)), + extent=list(coords.values()) + ) + + if not geotiff.is_enabled(): + self._plot_img[geotiff].set(alpha=0.5) + + self.idle() + @timer def update(self): self.draw() From 7b833390f1d9429708fbed0216fd427699af0e93 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Fri, 14 Nov 2025 15:05:00 +0100 Subject: [PATCH 37/48] Results: Add study geotiff in plot xy. --- src/View/Results/PlotXY.py | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/View/Results/PlotXY.py b/src/View/Results/PlotXY.py index 88f5ebdf..e52fa33d 100644 --- a/src/View/Results/PlotXY.py +++ b/src/View/Results/PlotXY.py @@ -31,6 +31,20 @@ from PyQt5.QtCore import ( ) from PyQt5.QtWidgets import QApplication, QTableView +try: + import rasterio + import rasterio.control + import rasterio.crs + import rasterio.sample + import rasterio.vrt + import rasterio._features + + from rasterio.io import MemoryFile + _rasterio_loaded = True +except Exception as e: + print(f"Module 'rasterio' is not available: {e}") + _rasterio_loaded = False + _translate = QCoreApplication.translate logger = logging.getLogger() @@ -52,6 +66,8 @@ class PlotXY(PamhyrPlot): self.line_gl = [] self.overflow = [] + self._plot_img = {} + self._timestamps = parent._timestamps self._current_timestamp = max(self._timestamps) self._current_reach_id = reach_id @@ -153,6 +169,7 @@ class PlotXY(PamhyrPlot): reach = results.river.reach(self._current_reach_id) reaches = results.river.reachs + self.draw_geotiff() self.draw_profiles(reach, reaches) self.draw_water_elevation(reach) self.draw_water_elevation_max(reach) @@ -166,6 +183,7 @@ class PlotXY(PamhyrPlot): if reach.geometry.number_profiles == 0: self._init = False return + self.line_xy = [] # TODO uncomment to draw all the reaches # self.draw_other_profiles(reaches) @@ -306,6 +324,36 @@ class PlotXY(PamhyrPlot): alpha=0.7 ) + def draw_geotiff(self): + if not _rasterio_loaded: + return + + lst = self._data[0]._study.river._geotiff.lst + + for img in self._plot_img: + self._plot_img[img].remove() + + self._plot_img = {} + + for geotiff in lst: + memfile = geotiff.memfile + if memfile is None: + return + + with memfile.open() as gt: + img = gt.read() + coords = geotiff.coordinates + + self._plot_img[geotiff] = self.canvas.axes.imshow( + img.transpose((1, 2, 0)), + extent=list(coords.values()) + ) + + if not geotiff.is_enabled(): + self._plot_img[geotiff].set(alpha=0.5) + + self.idle() + def set_reach(self, reach_id): self._current_reach_id = reach_id self._current_profile_id = 0 From b1c7a77f37d7a8a4f72a63b50da63fd518a5f915 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Fri, 14 Nov 2025 15:18:39 +0100 Subject: [PATCH 38/48] GeoTIFF: Fix pep8. --- src/View/GeoTIFF/Edit/Window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/View/GeoTIFF/Edit/Window.py b/src/View/GeoTIFF/Edit/Window.py index 5cbba817..4e32cb27 100644 --- a/src/View/GeoTIFF/Edit/Window.py +++ b/src/View/GeoTIFF/Edit/Window.py @@ -186,7 +186,7 @@ class EditGeoTIFFWindow(PamhyrWindow): with rasterio.open(self._file_name) as data: img = data.read() - b = data.bounds[:] # left, bottom, right, top + b = data.bounds[:] # left, bottom, right, top if b[2] > b[0] and b[1] < b[3]: coord = [b[0], b[2], b[1], b[3]] From 117e5222e490b05ca43fa0c856650b74299951e3 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Fri, 14 Nov 2025 15:50:24 +0100 Subject: [PATCH 39/48] GeoTIFF: Edit: Fix event connection. --- src/View/GeoTIFF/Edit/Window.py | 34 ++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/View/GeoTIFF/Edit/Window.py b/src/View/GeoTIFF/Edit/Window.py index 4e32cb27..5cb26d60 100644 --- a/src/View/GeoTIFF/Edit/Window.py +++ b/src/View/GeoTIFF/Edit/Window.py @@ -148,6 +148,8 @@ class EditGeoTIFFWindow(PamhyrWindow): self._reset_spinbox(key) def _reset_spinbox(self, key): + print(f"_reset_spinbox {key}") + self.set_double_spin_box( f"doubleSpinBox_{key}", self._values_default[key] @@ -161,16 +163,34 @@ class EditGeoTIFFWindow(PamhyrWindow): self.find(QPushButton, "pushButton_import")\ .clicked.connect(self._import) - for key in self._values: - self.find(QPushButton, f"pushButton_{key}")\ - .clicked.connect(lambda: self._reset_spinbox(key)) + 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_{key}")\ - .valueChanged.connect( - lambda: self.update_values_from_spinbox(key) - ) + 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): + print(f"update_values_from_spinbox {key}") self._values[key] = self.get_double_spin_box(f"doubleSpinBox_{key}") self._plot_img.set_extent(list(self._values.values())) From d47dc0687eec4ae6345c2e02b0b2f9c494d1c45e Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Fri, 14 Nov 2025 15:56:24 +0100 Subject: [PATCH 40/48] GeoTIFF: Minor change. --- src/View/GeoTIFF/Edit/Window.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/View/GeoTIFF/Edit/Window.py b/src/View/GeoTIFF/Edit/Window.py index 5cb26d60..47ad9e78 100644 --- a/src/View/GeoTIFF/Edit/Window.py +++ b/src/View/GeoTIFF/Edit/Window.py @@ -148,11 +148,8 @@ class EditGeoTIFFWindow(PamhyrWindow): self._reset_spinbox(key) def _reset_spinbox(self, key): - print(f"_reset_spinbox {key}") - self.set_double_spin_box( - f"doubleSpinBox_{key}", - self._values_default[key] + f"doubleSpinBox_{key}", self._values_default[key] ) def setup_connection(self): @@ -190,7 +187,6 @@ class EditGeoTIFFWindow(PamhyrWindow): ) def update_values_from_spinbox(self, key): - print(f"update_values_from_spinbox {key}") self._values[key] = self.get_double_spin_box(f"doubleSpinBox_{key}") self._plot_img.set_extent(list(self._values.values())) From 624ae826ebcd34401673a2287943512bfddb669a Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 17 Nov 2025 10:08:06 +0100 Subject: [PATCH 41/48] GeoTIFF: Fix plot display where geotiff is deleted and fix update propagation. --- src/View/GeoTIFF/Window.py | 17 +++++++++++++---- src/View/PlotXY.py | 5 ++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/View/GeoTIFF/Window.py b/src/View/GeoTIFF/Window.py index 1a924ecf..630b3981 100644 --- a/src/View/GeoTIFF/Window.py +++ b/src/View/GeoTIFF/Window.py @@ -22,6 +22,8 @@ from PyQt5.QtWidgets import ( QAction, QListView, QVBoxLayout, ) +from Modules import Modules + from View.Tools.PamhyrWindow import PamhyrWindow from View.GeoTIFF.List import ListModel @@ -80,10 +82,10 @@ class GeoTIFFListWindow(PamhyrWindow): 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.plot_layout.addWidget(self.canvas) + self._plot_layout = self.find(QVBoxLayout, "verticalLayout") + self._plot_layout.addWidget(self.canvas) - self.plot = PlotXY( + self._plot = PlotXY( canvas=self.canvas, data=self._study.river.enable_edges(), geotiff=self._study.river.geotiff, @@ -92,7 +94,7 @@ class GeoTIFFListWindow(PamhyrWindow): parent=self ) - self.plot.update() + self._plot.update() def setup_connections(self): if self._study.is_editable(): @@ -101,8 +103,15 @@ class GeoTIFFListWindow(PamhyrWindow): self.find(QAction, "action_edit").triggered.connect(self.edit) + 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() def selected_rows(self): lst = self.find(QListView, f"listView") diff --git a/src/View/PlotXY.py b/src/View/PlotXY.py index 32f9d523..a62ecb4d 100644 --- a/src/View/PlotXY.py +++ b/src/View/PlotXY.py @@ -146,9 +146,12 @@ class PlotXY(PamhyrPlot): self._plot_img = {} for geotiff in lst: + if geotiff.is_deleted(): + continue + memfile = geotiff.memfile if memfile is None: - return + continue with memfile.open() as gt: img = gt.read() From f8a41fce088b72bffebd14b93fdbbabf3a65def9 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 17 Nov 2025 15:01:00 +0100 Subject: [PATCH 42/48] GeoTIFF: Add red rectangle at GeoTIFF selected. --- src/Model/GeoTIFF/GeoTIFF.py | 8 +++--- src/View/GeoTIFF/Window.py | 50 +++++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/Model/GeoTIFF/GeoTIFF.py b/src/Model/GeoTIFF/GeoTIFF.py index c35bc875..1a9281cd 100644 --- a/src/Model/GeoTIFF/GeoTIFF.py +++ b/src/Model/GeoTIFF/GeoTIFF.py @@ -116,13 +116,13 @@ class GeoTIFF(SQLSubModel): self.read_file(value) elif key == "coordinates": self.coordinates = value - elif key == "coordinates_bottom": + elif key == "coordinates_bottom" or key == "bottom": self.coordinates["bottom"] = value - elif key == "coordinates_top": + elif key == "coordinates_top" or key == "top": self.coordinates["top"] = value - elif key == "coordinates_left": + elif key == "coordinates_left" or key == "left": self.coordinates["left"] = value - elif key == "coordinates_right": + elif key == "coordinates_right" or key == "right": self.coordinates["right"] = value self.modified() diff --git a/src/View/GeoTIFF/Window.py b/src/View/GeoTIFF/Window.py index 630b3981..2ac03485 100644 --- a/src/View/GeoTIFF/Window.py +++ b/src/View/GeoTIFF/Window.py @@ -22,6 +22,8 @@ from PyQt5.QtWidgets import ( QAction, QListView, QVBoxLayout, ) +from matplotlib.patches import Rectangle + from Modules import Modules from View.Tools.PamhyrWindow import PamhyrWindow @@ -30,6 +32,7 @@ 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 @@ -71,7 +74,7 @@ class GeoTIFFListWindow(PamhyrWindow): self.setup_connections() def setup_list(self): - lst = self.find(QListView, f"listView") + lst = self.find(QListView, "listView") self._list = ListModel( list_view=lst, data=self._study.river.geotiff, @@ -83,6 +86,11 @@ class GeoTIFFListWindow(PamhyrWindow): 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( @@ -93,6 +101,7 @@ class GeoTIFFListWindow(PamhyrWindow): toolbar=None, parent=self ) + self._plot_rect = [] self._plot.update() @@ -103,6 +112,11 @@ class GeoTIFFListWindow(PamhyrWindow): 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 @@ -113,6 +127,40 @@ class GeoTIFFListWindow(PamhyrWindow): 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: + geotiff = self._study.river.geotiff.files[row] + coord = geotiff.coordinates + + xy = (coord["bottom"], coord["left"]) + width = abs(coord["top"] - coord["bottom"]) + height = abs(coord["right"] - coord["left"]) + + 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())) From 2c49e991b4ec5d747753b6ef54b40f4a7ad5a0f7 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Wed, 19 Nov 2025 11:46:22 +0100 Subject: [PATCH 43/48] GeoTIFF: Fix coordinates. --- src/View/GeoTIFF/Edit/Window.py | 18 ++++++++++++++---- src/View/GeoTIFF/Window.py | 12 ++++++++---- src/View/PlotXY.py | 7 ++++++- src/View/Results/PlotXY.py | 7 ++++++- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/View/GeoTIFF/Edit/Window.py b/src/View/GeoTIFF/Edit/Window.py index 47ad9e78..fa14292d 100644 --- a/src/View/GeoTIFF/Edit/Window.py +++ b/src/View/GeoTIFF/Edit/Window.py @@ -189,7 +189,12 @@ class EditGeoTIFFWindow(PamhyrWindow): def update_values_from_spinbox(self, key): self._values[key] = self.get_double_spin_box(f"doubleSpinBox_{key}") - self._plot_img.set_extent(list(self._values.values())) + 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): @@ -205,11 +210,11 @@ class EditGeoTIFFWindow(PamhyrWindow): b = data.bounds[:] # left, bottom, right, top if b[2] > b[0] and b[1] < b[3]: - coord = [b[0], b[2], 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 = xlim + ylim + coord = ylim + xlim self._set_values_from_bounds(coord) self._set_default_values_from_bounds(coord) @@ -220,9 +225,14 @@ class EditGeoTIFFWindow(PamhyrWindow): 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=list(self._values.values()) + extent=(left, right, bottom, top) ) self.plot.idle() diff --git a/src/View/GeoTIFF/Window.py b/src/View/GeoTIFF/Window.py index 2ac03485..1fcf6650 100644 --- a/src/View/GeoTIFF/Window.py +++ b/src/View/GeoTIFF/Window.py @@ -140,12 +140,16 @@ class GeoTIFFListWindow(PamhyrWindow): return for row in rows: - geotiff = self._study.river.geotiff.files[row] + files = self._study.river.geotiff.files + if len(files) <= row: + continue + + geotiff = files[row] coord = geotiff.coordinates - xy = (coord["bottom"], coord["left"]) - width = abs(coord["top"] - coord["bottom"]) - height = abs(coord["right"] - coord["left"]) + xy = (coord["left"], coord["bottom"]) + width = abs(coord["right"] - coord["left"]) + height = abs(coord["top"] - coord["bottom"]) rect = Rectangle( xy, width, height, diff --git a/src/View/PlotXY.py b/src/View/PlotXY.py index a62ecb4d..e51933ef 100644 --- a/src/View/PlotXY.py +++ b/src/View/PlotXY.py @@ -157,9 +157,14 @@ class PlotXY(PamhyrPlot): 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=list(coords.values()) + extent=(left, right, bottom, top) ) if not geotiff.is_enabled(): diff --git a/src/View/Results/PlotXY.py b/src/View/Results/PlotXY.py index e52fa33d..a880c009 100644 --- a/src/View/Results/PlotXY.py +++ b/src/View/Results/PlotXY.py @@ -344,9 +344,14 @@ class PlotXY(PamhyrPlot): 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=list(coords.values()) + extent=(left, right, bottom, top) ) if not geotiff.is_enabled(): From d93917b0811255e7b6a5666bcc95c3fa514c17fe Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Wed, 19 Nov 2025 15:27:33 +0100 Subject: [PATCH 44/48] debug internal mesher --- src/Meshing/Internal.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Meshing/Internal.py b/src/Meshing/Internal.py index ed9a5c48..ba4a6d8f 100644 --- a/src/Meshing/Internal.py +++ b/src/Meshing/Internal.py @@ -205,8 +205,10 @@ class InternalMeshing(AMeshingTool): sect2.point(start2).name = '' if tag2 == '': # left end point sect2.point(start2+1).name = '' + len2 += 1 elif ltot1 < 0.0001: sect2.add_npoints(len1-len2) + len2 = len1 else: # regular case # loop on the points to insert for k in range(len1-len2): From d705accaed46272effede65e59a3b9a3cd7cfc89 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Thu, 20 Nov 2025 09:58:30 +0100 Subject: [PATCH 45/48] Wiki: Add scenarios screenshot. --- doc/images/wiki/fr_scenarios.png | Bin 0 -> 42681 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/images/wiki/fr_scenarios.png diff --git a/doc/images/wiki/fr_scenarios.png b/doc/images/wiki/fr_scenarios.png new file mode 100644 index 0000000000000000000000000000000000000000..96bdd3414f457c9f4ddc40dec80374406f383204 GIT binary patch literal 42681 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 literal 0 HcmV?d00001 From d4a259423966a8d8a0ee210938e19c25786a320a Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Fri, 21 Nov 2025 10:20:44 +0100 Subject: [PATCH 46/48] Wiki: Add geotiff screenshot. --- doc/images/wiki/fr_geotiff.png | Bin 0 -> 71625 bytes doc/images/wiki/fr_geotiff_edit.png | Bin 0 -> 73865 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/images/wiki/fr_geotiff.png create mode 100644 doc/images/wiki/fr_geotiff_edit.png diff --git a/doc/images/wiki/fr_geotiff.png b/doc/images/wiki/fr_geotiff.png new file mode 100644 index 0000000000000000000000000000000000000000..24075b40de024eeac9d0c8d8e8381a7c43299188 GIT binary patch literal 71625 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

-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 literal 0 HcmV?d00001 diff --git a/doc/images/wiki/fr_geotiff_edit.png b/doc/images/wiki/fr_geotiff_edit.png new file mode 100644 index 0000000000000000000000000000000000000000..cbf4c74389021ae270f8461d271606ecf0b9859b GIT binary patch literal 73865 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 literal 0 HcmV?d00001 From 4e4ceb5074bffb9dd4d1e98298f7fe8381567435 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Fri, 21 Nov 2025 11:14:29 +0100 Subject: [PATCH 47/48] Geotiff: Edit: Fix readonly mode. --- src/View/GeoTIFF/Edit/Window.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/View/GeoTIFF/Edit/Window.py b/src/View/GeoTIFF/Edit/Window.py index fa14292d..b36980c1 100644 --- a/src/View/GeoTIFF/Edit/Window.py +++ b/src/View/GeoTIFF/Edit/Window.py @@ -124,8 +124,15 @@ class EditGeoTIFFWindow(PamhyrWindow): 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_path", False) - self.set_plaintext_edit_enable("plainTextEdit", 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 = { From 9116009b0b46eb95631c096a7ff5a7493cd480de Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Fri, 21 Nov 2025 11:19:43 +0100 Subject: [PATCH 48/48] Geotiff: Disable add and delete for readonly mode. --- src/View/GeoTIFF/Window.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/View/GeoTIFF/Window.py b/src/View/GeoTIFF/Window.py index 1fcf6650..d428ea30 100644 --- a/src/View/GeoTIFF/Window.py +++ b/src/View/GeoTIFF/Window.py @@ -109,6 +109,9 @@ class GeoTIFFListWindow(PamhyrWindow): 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)