Compare commits

...

3 Commits

11 changed files with 492 additions and 39 deletions

View File

@ -35,6 +35,8 @@ try:
import rasterio.sample import rasterio.sample
import rasterio.vrt import rasterio.vrt
import rasterio._features import rasterio._features
from rasterio.io import MemoryFile
_rasterio_loaded = True _rasterio_loaded = True
except Exception as e: except Exception as e:
print(f"Module 'rasterio' is not available: {e}") print(f"Module 'rasterio' is not available: {e}")
@ -153,7 +155,7 @@ class GeoTIFF(SQLSubModel):
if self._file_bytes == b'': if self._file_bytes == b'':
return None return None
if self._memfile == None: if self._memfile is None:
self._memfile = MemoryFile() self._memfile = MemoryFile()
self._memfile.write(self._file_bytes) self._memfile.write(self._file_bytes)
@ -202,9 +204,12 @@ class GeoTIFF(SQLSubModel):
def _db_update(cls, execute, version, data=None): def _db_update(cls, execute, version, data=None):
major, minor, release = version.strip().split(".") major, minor, release = version.strip().split(".")
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: if int(release) < 3:
cls._create_submodel(execute) cls._db_create(execute)
return True return True
@ -228,6 +233,7 @@ class GeoTIFF(SQLSubModel):
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})" f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
) )
if table is not None:
for row in table: for row in table:
it = iter(row) it = iter(row)
@ -287,7 +293,7 @@ class GeoTIFF(SQLSubModel):
self.name, self.name,
self.description, self.description,
self.file_name, self.file_name,
self.file_bytes self.file_bytes,
self.coordinates['bottom'], self.coordinates['bottom'],
self.coordinates['top'], self.coordinates['top'],
self.coordinates['left'], self.coordinates['left'],

View File

@ -20,7 +20,7 @@ from tools import trace, timer
from Model.Except import NotImplementedMethodeError from Model.Except import NotImplementedMethodeError
from Model.Tools.PamhyrListExt import PamhyrModelList from Model.Tools.PamhyrListExt import PamhyrModelList
from Model.AdditionalFile.GeoTIFF import GeoTIFF from Model.GeoTIFF.GeoTIFF import GeoTIFF
class GeoTIFFList(PamhyrModelList): class GeoTIFFList(PamhyrModelList):

View File

@ -59,6 +59,7 @@ from Model.LateralContributionsAdisTS.LateralContributionsAdisTSList \
import LateralContributionsAdisTSList import LateralContributionsAdisTSList
from Model.D90AdisTS.D90AdisTSList import D90AdisTSList from Model.D90AdisTS.D90AdisTSList import D90AdisTSList
from Model.DIFAdisTS.DIFAdisTSList import DIFAdisTSList from Model.DIFAdisTS.DIFAdisTSList import DIFAdisTSList
from Model.GeoTIFF.GeoTIFFList import GeoTIFFList
from Model.Results.Results import Results from Model.Results.Results import Results
logger = logging.getLogger() logger = logging.getLogger()
@ -468,6 +469,7 @@ class River(Graph):
LateralContributionsAdisTSList, LateralContributionsAdisTSList,
D90AdisTSList, D90AdisTSList,
DIFAdisTSList, DIFAdisTSList,
GeoTIFFList,
Results Results
] ]
@ -505,6 +507,8 @@ class River(Graph):
self._D90AdisTS = D90AdisTSList(status=self._status) self._D90AdisTS = D90AdisTSList(status=self._status)
self._DIFAdisTS = DIFAdisTSList(status=self._status) self._DIFAdisTS = DIFAdisTSList(status=self._status)
self._geo_tiff = GeoTIFFList(status=self._status)
self._results = {} self._results = {}
@classmethod @classmethod
@ -617,6 +621,8 @@ class River(Graph):
new._DIFAdisTS = DIFAdisTSList._db_load(execute, data) new._DIFAdisTS = DIFAdisTSList._db_load(execute, data)
new._geo_tiff = GeoTIFFList._db_load(execute, data)
return new return new
def _db_load_results(self, execute, data=None): def _db_load_results(self, execute, data=None):
@ -726,6 +732,7 @@ class River(Graph):
self._BoundaryConditionsAdisTS, self._BoundaryConditionsAdisTS,
self._LateralContributionsAdisTS, self._LateralContributionsAdisTS,
self._D90AdisTS, self._DIFAdisTS, self._D90AdisTS, self._DIFAdisTS,
self._geo_tiff,
] ]
for solver in self._parameters: for solver in self._parameters:

View File

@ -37,7 +37,7 @@ logger = logging.getLogger()
class Study(SQLModel): class Study(SQLModel):
_version = "0.2.2" _version = "0.2.3"
_sub_classes = [ _sub_classes = [
Scenario, Scenario,

View File

@ -56,6 +56,7 @@ class Modules(IterableFlag):
SEDIMENT_LAYER = auto() SEDIMENT_LAYER = auto()
ADDITIONAL_FILES = auto() ADDITIONAL_FILES = auto()
OUTPUT_RK = auto() OUTPUT_RK = auto()
GEOTIFF = auto()
# Results # Results
RESULTS = auto() RESULTS = auto()
@ -81,6 +82,7 @@ class Modules(IterableFlag):
cls.RESULTS, cls.RESULTS,
cls.WINDOW_LIST, cls.WINDOW_LIST,
cls.OUTPUT_RK, cls.OUTPUT_RK,
cls.GEOTIFF
] ]
@classmethod @classmethod
@ -99,6 +101,7 @@ class Modules(IterableFlag):
| cls.HYDRAULIC_STRUCTURES | cls.HYDRAULIC_STRUCTURES
| cls.RESERVOIR | cls.RESERVOIR
| cls.SEDIMENT_LAYER | cls.SEDIMENT_LAYER
| cls.GEOTIFF
) )
@classmethod @classmethod
@ -114,6 +117,7 @@ class Modules(IterableFlag):
cls.HYDRAULIC_STRUCTURES, cls.HYDRAULIC_STRUCTURES,
cls.RESERVOIR, cls.RESERVOIR,
cls.SEDIMENT_LAYER, cls.SEDIMENT_LAYER,
cls.GEOTIFF,
] ]
@classmethod @classmethod
@ -129,6 +133,7 @@ class Modules(IterableFlag):
cls.HYDRAULIC_STRUCTURES: "Hydraulic structures", cls.HYDRAULIC_STRUCTURES: "Hydraulic structures",
cls.RESERVOIR: "Reservoir", cls.RESERVOIR: "Reservoir",
cls.SEDIMENT_LAYER: "Sediment layer", cls.SEDIMENT_LAYER: "Sediment layer",
cls.GEOTIFF: "GeoTIFF",
} }
def impact(self): def impact(self):
@ -168,4 +173,5 @@ _impact = {
Modules.HYDRAULIC_STRUCTURES: [], Modules.HYDRAULIC_STRUCTURES: [],
Modules.RESERVOIR: [], Modules.RESERVOIR: [],
Modules.SEDIMENT_LAYER: [], Modules.SEDIMENT_LAYER: [],
Modules.GEOTIFF: [],
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import logging
from Modules import Modules
from View.Tools.PamhyrWindow import PamhyrWindow
from PyQt5.QtWidgets import (
QLabel, QPlainTextEdit, QPushButton,
QCheckBox,
)
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()

96
src/View/GeoTIFF/List.py Normal file
View File

@ -0,0 +1,96 @@
# List.py -- Pamhyr
# Copyright (C) 2024-2025 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import logging
from functools import reduce
from tools import trace, timer
from PyQt5.QtCore import (
Qt, QVariant,
)
from PyQt5.QtGui import (
QColor, QBrush,
)
from View.Tools.PamhyrList import PamhyrListModel
from View.GeoTIFF.UndoCommand import (
AddCommand, DelCommand
)
logger = logging.getLogger()
class ListModel(PamhyrListModel):
def get_true_data_row(self, row):
el = self._data.get(row)
return next(
map(
lambda e: e[0],
filter(
lambda e: e[1] == el,
enumerate(self._data._lst)
)
), 0
)
def data(self, index, role):
row = index.row()
column = index.column()
file = self._data.files[row]
if role == Qt.ForegroundRole:
color = Qt.gray
if file.is_enabled():
color = QColor("black")
else:
color = QColor("grey")
return QBrush(color)
if role == Qt.ItemDataRole.DisplayRole:
text = f"{file.name}: '{file.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()

View File

@ -0,0 +1,35 @@
# Translate.py -- Pamhyr
# Copyright (C) 2024-2025 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
from PyQt5.QtCore import QCoreApplication
from View.Translate import MainTranslate
_translate = QCoreApplication.translate
class GeoTIFFTranslate(MainTranslate):
def __init__(self):
super(AddFileTranslate, self).__init__()
self._dict["GeoTIFF files"] = _translate(
"GeoTIFF", "GeoTIFF files"
)
self._dict["Edit additional file"] = _translate(
"GeoTIFF", "Edit GeoTIFF file"
)

View File

@ -0,0 +1,81 @@
# UndoCommand.py -- Pamhyr
# Copyright (C) 2024-2025 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
from tools import trace, timer
from PyQt5.QtWidgets import (
QMessageBox, QUndoCommand, QUndoStack,
)
class SetCommand(QUndoCommand):
def __init__(self, geotiff, **kwargs):
QUndoCommand.__init__(self)
self._geotiff = geotiff
self._new = kwargs
self._old = None
def undo(self):
f = self._geotiff
for key in self._old:
f[key] = self._old[key]
def redo(self):
f = self._geotiff
if self._old is None:
self._old = {}
for key in self._new:
self._old[key] = f[key]
for key in self._new:
f[key] = self._new[key]
class AddCommand(QUndoCommand):
def __init__(self, files, row):
QUndoCommand.__init__(self)
self._files = files
self._row = row
self._new = None
def undo(self):
self._new.set_as_deleted()
def redo(self):
if self._new is None:
self._new = self._files.new(self._row)
else:
self._new.set_as_not_deleted()
class DelCommand(QUndoCommand):
def __init__(self, files, line):
QUndoCommand.__init__(self)
self._files = files
self._line = line
def undo(self):
self._line.set_as_not_deleted()
def redo(self):
self._line.set_as_deleted()

120
src/View/GeoTIFF/Window.py Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
# -*- 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()