diff --git a/deploy.py b/deploy.py index 92d933a..3225426 100644 --- a/deploy.py +++ b/deploy.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 by Lutra Consulting +# Copyright (C) 2025 by Lutra Consulting import os import shutil diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index c39d9b3..0000000 --- a/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Copyright (C) 2023 by Lutra Consulting diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index 479b352..0000000 --- a/tests/conftest.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (C) 2023 by Lutra Consulting -import os -import shutil -import sys - -import pytest - -from threedi_schematisation_editor.utils import get_qgis - - -@pytest.fixture -def data_conversion_setup(): - # assume the tests run on linux, provide non-default folders - qgis_folder = "" - proj_folder = "" - if sys.platform == "linux": - qgis_folder = "/QGIS" - proj_folder = "/usr/share/proj" - - qgis_app = get_qgis(qgis_folder, proj_folder) - current_dir = os.path.dirname(__file__) - tmp_dir = os.path.join(current_dir, "tmp") - src_sqlite = os.path.join(current_dir, "test_data", "v2_bergermeer_orifices.sqlite") - reference_sqlite = os.path.join(tmp_dir, "v2_bergermeer_orifices_ref.sqlite") - import_export_sqlite = os.path.join(tmp_dir, "v2_bergermeer_orifices_ie.sqlite") - gpkg = os.path.join(tmp_dir, "v2_bergermeer_orifices_ie.gpkg") - - try: - shutil.rmtree(tmp_dir) - except OSError: - pass # directory not present at all - - os.makedirs(tmp_dir, exist_ok=True) - shutil.copy(src_sqlite, reference_sqlite) - shutil.copy(src_sqlite, import_export_sqlite) - - return qgis_app, src_sqlite, reference_sqlite, import_export_sqlite, gpkg diff --git a/tests/test_data/v2_bergermeer_orifices.sqlite b/tests/test_data/v2_bergermeer_orifices.sqlite deleted file mode 100644 index 6f55775..0000000 Binary files a/tests/test_data/v2_bergermeer_orifices.sqlite and /dev/null differ diff --git a/tests/test_import_export.py b/tests/test_import_export.py deleted file mode 100644 index bc4a624..0000000 --- a/tests/test_import_export.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (C) 2023 by Lutra Consulting -from threedi_schematisation_editor.conversion import ModelDataConverter -from threedi_schematisation_editor.data_models import ALL_MODELS -from threedi_schematisation_editor.utils import sqlite_layer - - -def test_data_import_export_integrity(data_conversion_setup): - qgis_app, src_sqlite, reference_sqlite, import_export_sqlite, gpkg = data_conversion_setup - - importer = ModelDataConverter(import_export_sqlite, gpkg) - importer.create_empty_user_layers() - importer.import_all_model_data() - - exporter = ModelDataConverter(import_export_sqlite, gpkg) - exporter.trim_sqlite_targets() - exporter.export_all_model_data() - - for annotated_model_cls in ALL_MODELS: - for src_table in annotated_model_cls.SQLITE_SOURCES or []: - ie_layer = sqlite_layer(import_export_sqlite, src_table) - ref_layer = sqlite_layer(reference_sqlite, src_table) - if not ie_layer.isValid(): - ie_layer = sqlite_layer(import_export_sqlite, src_table, geom_column=None) - if not ref_layer.isValid(): - ref_layer = sqlite_layer(reference_sqlite, src_table, geom_column=None) - ie_lyr_count, ref_lyr_count = ie_layer.featureCount(), ref_layer.featureCount() - assert ref_lyr_count == ie_lyr_count - id_field = annotated_model_cls.IMPORT_FIELD_MAPPINGS.get("id", "id") - ie_feats = {f[id_field]: f for f in ie_layer.getFeatures()} - ref_feats = {f[id_field]: f for f in ref_layer.getFeatures()} - ie_field_types = {field.name(): field.typeName() for field in ie_layer.fields()} - ref_field_types = {field.name(): field.typeName() for field in ref_layer.fields()} - assert ie_field_types == ref_field_types - field_names = list(ref_field_types.keys()) - for feat_id, ref_feat in ref_feats.items(): - ie_feat = ie_feats[feat_id] - assert ie_feat.geometry().asWkt() == ref_feat.geometry().asWkt() - for field_name in field_names: - ie_value = ie_feat[field_name] - ref_value = ref_feat[field_name] - if field_name == "timeseries": - ie_value = [float(value_str) for row in ie_value.split("\n") for value_str in row.split(",")] - ref_value = [float(value_str) for row in ref_value.split("\n") for value_str in row.split(",")] - assert ie_value == ref_value diff --git a/threedi_schematisation_editor/__init__.py b/threedi_schematisation_editor/__init__.py index 3059477..34effaa 100644 --- a/threedi_schematisation_editor/__init__.py +++ b/threedi_schematisation_editor/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 by Lutra Consulting +# Copyright (C) 2025 by Lutra Consulting import os.path from collections import defaultdict @@ -6,9 +6,11 @@ from qgis.PyQt.QtGui import QCursor, QIcon from qgis.PyQt.QtWidgets import QAction, QComboBox, QDialog, QMenu +from threedi_schematisation_editor.deps.custom_imports import patch_wheel_imports + +patch_wheel_imports() import threedi_schematisation_editor.data_models as dm from threedi_schematisation_editor.communication import UICommunication -from threedi_schematisation_editor.conversion import ModelDataConverter from threedi_schematisation_editor.custom_widgets import ImportStructuresDialog, LoadSchematisationDialog from threedi_schematisation_editor.processing import ThreediSchematisationEditorProcessingProvider from threedi_schematisation_editor.user_layer_manager import LayersManager @@ -18,11 +20,10 @@ add_settings_entry, can_write_in_dir, check_enable_macros_option, - create_empty_model, - ensure_valid_schema, get_filepath, + get_icon_path, is_gpkg_connection_exists, - remove_user_layers, + migrate_schematisation_schema, ) from threedi_schematisation_editor.workspace import WorkspaceContextManager @@ -63,31 +64,25 @@ def initGui(self): self.active_schematisation_combo.currentIndexChanged.connect(self.active_schematisation_changed) self.toolbar.addWidget(self.active_schematisation_combo) self.toolbar.addSeparator() - self.action_open = QAction("Open 3Di Geopackage", self.iface.mainWindow()) - self.action_open.triggered.connect(self.open_model_from_geopackage) - self.action_import = QAction("Load from Spatialite", self.iface.mainWindow()) - self.action_import.triggered.connect(self.load_from_spatialite) - self.action_export = QAction("Save to Spatialite", self.iface.mainWindow()) - self.action_export.triggered.connect(self.save_to_default) - self.action_export_as = QAction("Save As", self.iface.mainWindow()) - self.action_export_as.triggered.connect(self.save_to_selected) - self.action_remove = QAction("Remove 3Di model", self.iface.mainWindow()) + self.action_open = QAction( + QIcon(get_icon_path("icon_load.svg")), "Load 3Di Schematisation", self.iface.mainWindow() + ) + self.action_open.triggered.connect(self.load_schematisation) + self.action_remove = QAction( + QIcon(get_icon_path("icon_unload.svg")), "Remove 3Di Schematisation", self.iface.mainWindow() + ) self.action_remove.triggered.connect(self.remove_model_from_project) - import_culverts_icon_path = os.path.join(os.path.dirname(__file__), "import.png") + import_culverts_icon_path = get_icon_path("icon_import.png") import_actions_spec = [ ("Culverts", self.import_external_culverts, None), ("Orifices", self.import_external_orifices, None), ("Weirs", self.import_external_weirs, None), ("Pipes", self.import_external_pipes, None), - ("Manholes", self.import_external_manholes, None), ] self.action_import_culverts = self.add_multi_action_button( "Import schematisation objects", import_culverts_icon_path, import_actions_spec ) self.toolbar.addAction(self.action_open) - self.toolbar.addAction(self.action_import) - self.toolbar.addAction(self.action_export) - self.toolbar.addAction(self.action_export_as) self.toolbar.addAction(self.action_remove) self.toolbar.addAction(self.action_import_culverts) self.toggle_active_project_actions() @@ -99,9 +94,6 @@ def unload(self): del self.toolbar del self.active_schematisation_combo del self.action_open - del self.action_import - del self.action_export - del self.action_export_as del self.action_remove del self.action_import_culverts @@ -193,13 +185,9 @@ def add_multi_action_button(self, name, icon_path, actions_specification): def toggle_active_project_actions(self): if self.model_gpkg is None: - self.action_export.setDisabled(True) - self.action_export_as.setDisabled(True) self.action_remove.setDisabled(True) self.action_import_culverts.setDisabled(True) else: - self.action_export.setEnabled(True) - self.action_export_as.setEnabled(True) self.action_remove.setEnabled(True) self.action_import_culverts.setEnabled(True) @@ -212,16 +200,6 @@ def check_macros_status(self): ) self.uc.bar_warn(msg, dur=10) - def select_user_layers_geopackage(self): - name_filter = "3Di User Layers (*.gpkg *.GPKG)" - filename = get_filepath(self.iface.mainWindow(), extension_filter=name_filter, save=False) - return filename - - def select_sqlite_database(self, title): - name_filter = "Spatialite Database (*.sqlite)" - filename = get_filepath(self.iface.mainWindow(), extension_filter=name_filter, save=False, dialog_title=title) - return filename - def on_3di_project_read(self): custom_vars = self.project.customVariables() try: @@ -244,153 +222,42 @@ def on_3di_project_save(self): if project_model_gpkgs_str: self.project.setCustomVariables({self.THREEDI_GPKG_VAR_NAMES: project_model_gpkgs_str}) - def open_model_from_geopackage(self, model_gpkg=None): + def load_schematisation(self, model_gpkg=None): if not model_gpkg: - model_gpkg = self.select_user_layers_geopackage() - if not model_gpkg: - return - lm = LayersManager(self.iface, self.uc, model_gpkg) - if lm in self.workspace_context_manager: - warn_msg = "Selected schematisation is already loaded. Loading canceled." - self.uc.show_warn(warn_msg) - return - lm.load_all_layers() - self.workspace_context_manager.register_layer_manager(lm) - self.uc.bar_info("3Di User Layers registered!") - self.check_macros_status() - self.toggle_active_project_actions() - if self.model_gpkg and not is_gpkg_connection_exists(self.model_gpkg): - add_gpkg_connection(self.model_gpkg, self.iface) - - def load_from_spatialite(self, src_sqlite=None): - if not src_sqlite: schematisation_loader = LoadSchematisationDialog(self.uc) result = schematisation_loader.exec_() if result != QDialog.Accepted: return - src_sqlite = schematisation_loader.selected_schematisation_sqlite - if not can_write_in_dir(os.path.dirname(src_sqlite)): - warn_msg = "You don't have required write permissions to load data from the selected spatialite." - self.uc.show_warn(warn_msg) - return - schema_version = ModelDataConverter.spatialite_schema_version(src_sqlite) - if schema_version is None: - warn_msg = ( - "The selected spatialite cannot be used because its schema version information is missing. " - "Please upgrade the 3Di Schematisation Editor and try again." - ) - self.uc.show_warn(warn_msg) - self.uc.bar_warn("Loading from the Spatialite aborted!") - return - if schema_version > ModelDataConverter.SUPPORTED_SCHEMA_VERSION: - warn_msg = ( - "The selected spatialite cannot be used because its database schema version is newer than expected. " - "Please upgrade the 3Di Schematisation Editor and try again." - ) - self.uc.show_warn(warn_msg) - self.uc.bar_warn("Loading from the Spatialite aborted!") - return - else: - schema_is_valid = ensure_valid_schema(src_sqlite, self.uc) - if schema_is_valid is False: - self.uc.bar_warn("Loading from the Spatialite aborted!") + schematisation_filepath = schematisation_loader.selected_schematisation_filepath + if not can_write_in_dir(os.path.dirname(schematisation_filepath)): + warn_msg = "You don't have required write permissions to load data from the selected location." + self.uc.show_warn(warn_msg) return - dst_gpkg = os.path.normpath(src_sqlite.replace(".sqlite", ".gpkg")) - if dst_gpkg in set(self.workspace_context_manager.layer_managers.keys()): + if schematisation_filepath.endswith(".sqlite"): + migration_succeed, migration_feedback_msg = migrate_schematisation_schema(schematisation_filepath) + if not migration_succeed: + self.uc.show_warn(migration_feedback_msg) + return + model_gpkg = schematisation_filepath.rsplit(".", 1)[0] + ".gpkg" + else: + model_gpkg = schematisation_filepath + lm = LayersManager(self.iface, self.uc, model_gpkg) + if lm in self.workspace_context_manager: warn_msg = "Selected schematisation is already loaded. Loading canceled." self.uc.show_warn(warn_msg) return - converter = ModelDataConverter(src_sqlite, dst_gpkg, user_communication=self.uc) - known_epsg = converter.set_epsg_from_sqlite() - if known_epsg is False: - return - try: - converter.create_empty_user_layers() - converter.import_all_model_data() - except ConversionError: - self.uc.bar_warn("Loading from the Spatialite failed!") - return - if converter.missing_source_settings is True: - add_settings_entry(dst_gpkg, id=1, epsg_code=converter.epsg_code) - lm = LayersManager(self.iface, self.uc, dst_gpkg) lm.load_all_layers() self.workspace_context_manager.register_layer_manager(lm) - self.uc.show_info("Loading from the Spatialite finished!") + self.uc.bar_info("3Di User Layers registered!") self.check_macros_status() self.toggle_active_project_actions() if self.model_gpkg and not is_gpkg_connection_exists(self.model_gpkg): add_gpkg_connection(self.model_gpkg, self.iface) - def save_to_selected(self): - self.save_to_spatialite() - - def save_to_default(self): - self.save_to_spatialite(pick_destination=False) - - def save_to_spatialite(self, pick_destination=True): - if not self.model_gpkg: - return - if self.layer_manager is None: - return - fixed_errors_msg, unsolved_errors_msg = self.layer_manager.validate_layers() - if unsolved_errors_msg: - warn_msg = ( - "Saving to Spatialite failed. " - "The following features have cross sections with incorrect table inputs:\n" - ) - warn_msg += unsolved_errors_msg - self.uc.show_warn(warn_msg) - return - self.layer_manager.stop_model_editing() - if pick_destination: - dst_sqlite = self.select_sqlite_database(title="Select database to save features to") - else: - dst_sqlite = self.model_gpkg.replace(".gpkg", ".sqlite") - if not dst_sqlite: - return - if not os.path.isfile(dst_sqlite): - warn_msg = "Target spatialite file doesn't exist. Saving to spatialite canceled." - self.uc.show_warn(warn_msg) - return - if not can_write_in_dir(os.path.dirname(dst_sqlite)): - warn_msg = "You don't have required write permissions to save data into the selected spatialite." - self.uc.show_warn(warn_msg) - return - schema_version = ModelDataConverter.spatialite_schema_version(dst_sqlite) - if schema_version > ModelDataConverter.SUPPORTED_SCHEMA_VERSION: - warn_msg = ( - "The selected spatialite cannot be used because its database schema version is newer than expected. " - "Please upgrade the 3Di Schematisation Editor and try again." - ) - self.uc.show_warn(warn_msg) - return - else: - schema_is_valid = ensure_valid_schema(dst_sqlite, self.uc) - if schema_is_valid is False: - return - converter = ModelDataConverter(dst_sqlite, self.model_gpkg, user_communication=self.uc) - known_epsg = converter.set_epsg_from_gpkg() - if known_epsg is False: - return - converter.trim_sqlite_targets() - converter.report_conversion_errors() - converter.export_all_model_data() - self.uc.show_info("Saving to the Spatialite finished!") - - def save_to_spatialite_on_action(self): - model_modified = self.layer_manager.model_modified() - if model_modified: - title = "Save to Spatialite?" - question = "Would you like to save model to Spatialite before closing project?" - answer = self.uc.ask(None, title, question) - if answer is True: - self.save_to_spatialite() - def remove_model_from_project(self): if not self.model_gpkg: return if self.layer_manager is not None: - self.save_to_spatialite_on_action() self.iface.currentLayerChanged.disconnect(self.switch_workspace_context) self.layer_manager.remove_groups() self.iface.currentLayerChanged.connect(self.switch_workspace_context) @@ -421,14 +288,9 @@ def import_external_pipes(self): import_pipes_dlg = ImportStructuresDialog(dm.Pipe, self.model_gpkg, self.layer_manager, self.uc) import_pipes_dlg.exec_() - def import_external_manholes(self): - import_manholes_dlg = ImportStructuresDialog(dm.Manhole, self.model_gpkg, self.layer_manager, self.uc) - import_manholes_dlg.exec_() - def on_project_close(self): if self.layer_manager is None: return - self.save_to_spatialite_on_action() for lm in self.workspace_context_manager: lm.remove_loaded_layers(dry_remove=True) self.workspace_context_manager.unregister_all() diff --git a/threedi_schematisation_editor/communication.py b/threedi_schematisation_editor/communication.py index d49b116..0b12919 100644 --- a/threedi_schematisation_editor/communication.py +++ b/threedi_schematisation_editor/communication.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 by Lutra Consulting +# Copyright (C) 2025 by Lutra Consulting from qgis.core import Qgis, QgsMessageLog from qgis.PyQt.QtCore import Qt from qgis.PyQt.QtWidgets import QInputDialog, QMessageBox, QProgressBar, QPushButton diff --git a/threedi_schematisation_editor/conversion.py b/threedi_schematisation_editor/conversion.py deleted file mode 100644 index 824b791..0000000 --- a/threedi_schematisation_editor/conversion.py +++ /dev/null @@ -1,830 +0,0 @@ -# Copyright (C) 2023 by Lutra Consulting -from collections import OrderedDict, defaultdict -from operator import itemgetter - -from qgis.core import ( - QgsCoordinateTransform, - QgsExpression, - QgsFeature, - QgsFeatureRequest, - QgsGeometry, - QgsPointXY, - QgsProject, - QgsVectorFileWriter, - QgsWkbTypes, -) -from qgis.PyQt.QtCore import QCoreApplication -from qgis.PyQt.QtWidgets import QDialog - -import threedi_schematisation_editor.data_models as dm -import threedi_schematisation_editor.enumerators as en -from threedi_schematisation_editor.communication import UICommunication -from threedi_schematisation_editor.custom_widgets import ProjectionSelectionDialog -from threedi_schematisation_editor.utils import ( - ConversionError, - cast_if_bool, - gpkg_layer, - layer_to_gpkg, - sqlite_layer, - vector_layer_factory, -) - - -class ModelDataConverter: - """Class with methods Spatialite <==> GeoPackage conversion of the 3Di model layers.""" - - SUPPORTED_SCHEMA_VERSION = 219 - - def __init__(self, src_sqlite, dst_gpkg, epsg_code=4326, user_communication=None): - self.src_sqlite = src_sqlite - self.dst_gpkg = dst_gpkg - self.epsg_code = epsg_code - self.missing_source_settings = False - self.all_models = dm.ALL_MODELS[:] - self.timeseries_rawdata = OrderedDict() - self.uc = ( - user_communication - if user_communication is not None - else UICommunication(context="3Di Schematisation Editor") - ) - self.conversion_errors = defaultdict(list) - - def report_conversion_errors(self, report_limit=25): - """Report all caught conversion errors.""" - error_message = "" - errors_per_data_model = list(self.conversion_errors.items()) - errors_per_data_model.sort(key=itemgetter(0)) - total_errors_number = sum([len(errors) for layer_name, errors in errors_per_data_model]) - for layer_name, errors in errors_per_data_model: - errors_str = "\n".join(errors) - error_message += f"{layer_name} conversion errors:\n{errors_str}\n" - error_message.strip() - if error_message: - self.uc.log_warn(error_message) - if total_errors_number > report_limit: - general_error_message = ( - f"More than {report_limit} conversion errors detected. " - f"Please check the QGIS Log Message panel to get the conversion error details." - ) - self.uc.show_error(general_error_message) - else: - self.uc.show_error(error_message) - - @staticmethod - def spatialite_schema_version(sqlite_path): - """Getting Spatialite 3Di model database schema version.""" - schema_version_table = sqlite_layer(sqlite_path, "schema_version", geom_column=None) - if schema_version_table.isValid(): - try: - schema_row = next(iter(schema_version_table.getFeatures())) - spatialite_schema_id = int(schema_row["version_num"]) - except (StopIteration, TypeError): - spatialite_schema_id = 1 - else: - schema_version_table = sqlite_layer(sqlite_path, "south_migrationhistory", geom_column=None) - if not schema_version_table.isValid() or schema_version_table.featureCount() == 0: - return None - schema_row = list(schema_version_table.getFeatures())[-1] - try: - spatialite_schema_id = int(schema_row["migration"].split("_", 1)[0]) - except (AttributeError, IndexError, TypeError, ValueError): - spatialite_schema_id = 1 - return spatialite_schema_id - - def set_epsg_from_sqlite(self): - """Setting EPSG code from SQLITE 3Di model settings table.""" - settings_table = next(iter(dm.GlobalSettings.SQLITE_SOURCES)) - settings_layer = sqlite_layer(self.src_sqlite, settings_table, geom_column=None) - try: - settings_feat = next(settings_layer.getFeatures()) - fetched_epsg = settings_feat["epsg_code"] - except StopIteration: - self.missing_source_settings = True - msg = f"'{settings_table}' table is empty. Please pick EPSG code that you want to use first." - self.uc.show_warn(msg) - crs_selection_dlg = ProjectionSelectionDialog() - res = crs_selection_dlg.exec_() - if res == QDialog.Accepted: - selected_crs = crs_selection_dlg.projection_selection.crs() - epsg_str = selected_crs.authid().split(":")[-1] - if epsg_str.isnumeric(): - fetched_epsg = int(epsg_str) - else: - fetched_epsg = -1 - else: - return False - if fetched_epsg and fetched_epsg != self.epsg_code: - self.epsg_code = fetched_epsg - return True - - def set_epsg_from_gpkg(self): - """Setting EPSG code from GeoPackage 3Di model settings table.""" - settings_table = dm.GlobalSettings.__tablename__ - settings_layer = gpkg_layer(self.dst_gpkg, settings_table) - try: - settings_feat = next(settings_layer.getFeatures()) - except StopIteration: - msg = f"'{dm.GlobalSettings.__layername__}' layer is empty. Please add record with EPSG code first." - self.uc.show_error(msg) - return False - fetched_epsg = settings_feat["epsg_code"] - if fetched_epsg and fetched_epsg != self.epsg_code: - self.epsg_code = fetched_epsg - return True - - def create_empty_user_layers(self, overwrite=True): - """Creating empty 3Di User Layers structure within GeoPackage.""" - vector_layers = [vector_layer_factory(model_cls, epsg=self.epsg_code) for model_cls in self.all_models] - overwrite = overwrite - for vl in vector_layers: - writer, error_msg = layer_to_gpkg(vl, self.dst_gpkg, overwrite=overwrite) - if writer != QgsVectorFileWriter.NoError: - self.uc.show_error(error_msg) - raise ConversionError((writer, error_msg)) - overwrite = False - - def trim_sqlite_targets(self): - """Removing all features from Spatialite tables.""" - self.conversion_errors.clear() - for model_cls in self.all_models: - for src_table in model_cls.SQLITE_TARGETS or tuple(): - src_layer = sqlite_layer(self.src_sqlite, src_table) - if not src_layer.isValid(): - src_layer = sqlite_layer(self.src_sqlite, src_table, geom_column=None) - fids = [f.id() for f in src_layer.getFeatures()] - src_layer.startEditing() - src_layer.deleteFeatures(fids) - success = src_layer.commitChanges() - if not success: - commit_errors = src_layer.commitErrors() - self.conversion_errors[model_cls.__layername__] += commit_errors - - @staticmethod - def copy_features(src_layer, dst_layer, request=None, **field_mappings): - """Handling copying of features during Spatialite <==> GeoPackage conversion.""" - src_crs = src_layer.sourceCrs() - dest_crs = dst_layer.sourceCrs() - if src_crs != dest_crs: - context = QgsProject.instance().transformContext() - transformation = QgsCoordinateTransform(src_crs, dest_crs, context) - - def geometry_transform(geometry): - geometry.transform(transformation) - - else: - - def geometry_transform(geometry): - pass - - src_fields = src_layer.fields() - dst_fields = dst_layer.fields() - src_field_names = {f.name() for f in src_fields} - dst_field_names = {f.name() for f in dst_fields} - skip_geometry = dst_layer.geometryType() == QgsWkbTypes.GeometryType.NullGeometry - if skip_geometry: - - def transfer_geometry(source_feat, destination_feat): - pass - - else: - - def transfer_geometry(source_feat, destination_feat): - src_geom = source_feat.geometry() - new_geom = QgsGeometry(src_geom) - if new_geom.isMultipart(): - new_geom.convertToSingleType() - geometry_transform(new_geom) - destination_feat.setGeometry(new_geom) - - field_mappings = { - dst_fld: src_fld - for dst_fld, src_fld in field_mappings.items() - if src_fld in src_field_names and dst_fld in dst_field_names - } - new_feats = [] - for src_feat in src_layer.getFeatures() if request is None else src_layer.getFeatures(request): - dst_feat = QgsFeature(dst_fields) - transfer_geometry(src_feat, dst_feat) - for dst_field, src_field in field_mappings.items(): - src_value = src_feat[src_field] - dst_feat[dst_field] = cast_if_bool(src_value) - new_feats.append(dst_feat) - return new_feats - - def extract_timeseries_rawdata(self, src_layer, dst_layer): - """Extracting Timeseries data from layer features.""" - dst_layer_name = dst_layer.name() - for src_feat in src_layer.getFeatures(): - self.timeseries_rawdata[dst_layer_name, src_feat["id"]] = src_feat["timeseries"] - - @staticmethod - def parse_timeseries_row(timeseries_txt, timeseries_id, reference_layer, reference_id, offset=0): - """Parsing text Timeseries data into structured form.""" - series = [] - for row in timeseries_txt.split("\n"): - duration_str, value_str = row.split(",") - duration, value = int(duration_str), float(value_str) - series.append((timeseries_id, reference_layer, reference_id, offset, duration, value)) - return series - - def process_timeseries_rawdata(self): - """Writing structured Timeseries into separate table.""" - ts_layer = gpkg_layer(self.dst_gpkg, dm.Timeseries.__tablename__) - ts_fields = ts_layer.fields() - ts_field_names = [f.name() for f in ts_fields] - ts_field_names.remove("fid") - ts_features = [] - for ts_id, ((reference_layer, reference_id), ts_txt) in enumerate(self.timeseries_rawdata.items(), start=1): - timeseries = self.parse_timeseries_row(ts_txt, ts_id, reference_layer, reference_id) - for ts_row in timeseries: - ts_feat = QgsFeature(ts_fields) - for field_name, field_value in zip(ts_field_names, ts_row): - ts_feat[field_name] = field_value - ts_features.append(ts_feat) - ts_layer.startEditing() - ts_layer.addFeatures(ts_features) - success = ts_layer.commitChanges() - if not success: - commit_errors = ts_layer.commitErrors() - self.conversion_errors[dm.Timeseries.__layername__] += commit_errors - - def recreate_timeseries_rawdata(self): - """Reading Timeseries from User Layer table.""" - self.timeseries_rawdata.clear() - ts_layer = gpkg_layer(self.dst_gpkg, dm.Timeseries.__tablename__) - grouped_records = defaultdict(list) - for feat_ts in ts_layer.getFeatures(): - reference_layer = feat_ts["reference_layer"] - reference_id = feat_ts["reference_id"] - ts_row = [feat_ts["duration"], feat_ts["value"]] - grouped_records[reference_layer, reference_id].append(ts_row) - for key, values in grouped_records.items(): - values.sort(key=itemgetter(0)) - ts_txt = "\n".join(",".join(str(col) for col in row) for row in values) - self.timeseries_rawdata[key] = ts_txt - - def fill_required_attributes(self, src_layer, new_feats): - """Filling required attributes during Spatialite <==> GeoPackage conversion.""" - src_layer_name = src_layer.name() - layers_with_ts = {el.__layername__ for el in dm.ELEMENTS_WITH_TIMESERIES} - if src_layer_name == dm.Pumpstation.__layername__: - map_table = dm.PumpstationMap.__tablename__ - map_layer = gpkg_layer(self.dst_gpkg, map_table) - connections_ids = { - feat["pumpstation_id"]: feat["connection_node_end_id"] for feat in map_layer.getFeatures() - } - for feat in new_feats: - feat_id = feat["id"] - try: - feat["connection_node_end_id"] = connections_ids[feat_id] - except KeyError: - continue - elif src_layer_name in layers_with_ts: - for feat in new_feats: - feat_id = feat["id"] - try: - feat["timeseries"] = self.timeseries_rawdata[src_layer_name, feat_id] - except KeyError: - continue - else: - pass - - def add_surface_map_geometries(self): - """Adding polyline geometries to the surfaces map layers.""" - connection_node_layer = gpkg_layer(self.dst_gpkg, dm.ConnectionNode.__tablename__) - impervious_surface_layer = gpkg_layer(self.dst_gpkg, dm.ImperviousSurface.__tablename__) - surface_layer = gpkg_layer(self.dst_gpkg, dm.Surface.__tablename__) - connection_node_points = {f["id"]: f.geometry() for f in connection_node_layer.getFeatures()} - impervious_surface_points = {f["id"]: f.geometry().centroid() for f in impervious_surface_layer.getFeatures()} - surface_points = {f["id"]: f.geometry().centroid() for f in surface_layer.getFeatures()} - impervious_surface_map_layer = gpkg_layer(self.dst_gpkg, dm.ImperviousSurfaceMap.__tablename__) - surface_map_layer = gpkg_layer(self.dst_gpkg, dm.SurfaceMap.__tablename__) - impervious_surface_map_geoms, surface_map_geoms = {}, {} - for feat in impervious_surface_map_layer.getFeatures(): - fid, node_id, surface_id = feat.id(), feat["connection_node_id"], feat["impervious_surface_id"] - if not surface_id or surface_id not in impervious_surface_points: - missing_surface_error = ( - f"Impervious Surface link ({fid}) with an invalid 'impervious_surface_id'. " - f"Impervious surface ID reference is missing." - ) - self.conversion_errors[dm.ImperviousSurface.__layername__].append(missing_surface_error) - continue - try: - connection_node_geom = connection_node_points[node_id] - except KeyError: - missing_node_error = ( - f"Impervious Surface ({fid}) with an invalid 'connection_node_id' reference. " - f"Node ({node_id}) doesn't exist." - ) - self.conversion_errors[dm.ImperviousSurface.__layername__].append(missing_node_error) - continue - connection_node_point = connection_node_geom.asPoint() - isurface_centroid_geom = impervious_surface_points[surface_id] - try: - isurface_point = isurface_centroid_geom.asPoint() - except ValueError: - # If surface have no geometry let use point laying 10 meters to the north from connection node - isurface_point = QgsPointXY(connection_node_point.x(), connection_node_point.y() + 10.0) - link_geom = QgsGeometry.fromPolylineXY([isurface_point, connection_node_point]) - impervious_surface_map_geoms[fid] = link_geom - for feat in surface_map_layer.getFeatures(): - fid, node_id, surface_id = feat.id(), feat["connection_node_id"], feat["surface_id"] - if not surface_id or surface_id not in surface_points: - missing_surface_error = ( - f"Surface link ({fid}) with an invalid 'surface_id'. Surface ID reference is missing." - ) - self.conversion_errors[dm.Surface.__layername__].append(missing_surface_error) - continue - try: - connection_node_geom = connection_node_points[node_id] - except KeyError: - missing_node_error = ( - f"Surface ({fid}) with an invalid 'connection_node_id' reference. " - f"Node ({node_id}) doesn't exist." - ) - self.conversion_errors[dm.Surface.__layername__].append(missing_node_error) - continue - connection_node_point = connection_node_geom.asPoint() - surface_centroid_geom = surface_points[surface_id] - try: - surface_point = surface_centroid_geom.asPoint() - except ValueError: - # If surface have no geometry let use point laying 10 meters to the north from connection node - surface_point = QgsPointXY(connection_node_point.x(), connection_node_point.y() + 10.0) - link_geom = QgsGeometry.fromPolylineXY([surface_point, connection_node_point]) - surface_map_geoms[fid] = link_geom - impervious_surface_map_layer.startEditing() - for fid, link_geom in impervious_surface_map_geoms.items(): - impervious_surface_map_layer.changeGeometry(fid, link_geom) - success = impervious_surface_map_layer.commitChanges() - if not success: - commit_errors = impervious_surface_map_layer.commitErrors() - self.conversion_errors[dm.ImperviousSurface.__layername__] += commit_errors - surface_map_layer.startEditing() - for fid, link_geom in surface_map_geoms.items(): - surface_map_layer.changeGeometry(fid, link_geom) - success = surface_map_layer.commitChanges() - if not success: - commit_errors = surface_map_layer.commitErrors() - self.conversion_errors[dm.Surface.__layername__] += commit_errors - - def import_model_data(self, annotated_model_cls): - """Converting Spatialite layer into GeoPackage User Layer based on model data class.""" - dst_table = annotated_model_cls.__tablename__ - dst_layer_name = annotated_model_cls.__layername__ - dst_layer = gpkg_layer(self.dst_gpkg, dst_table, dst_layer_name) - field_mappings = {k: k for k in annotated_model_cls.__annotations__.keys()} - if annotated_model_cls.IMPORT_FIELD_MAPPINGS: - field_mappings.update(annotated_model_cls.IMPORT_FIELD_MAPPINGS) - for src_table in annotated_model_cls.SQLITE_SOURCES: - src_layer = sqlite_layer(self.src_sqlite, src_table) - if not src_layer.isValid(): - src_layer = sqlite_layer(self.src_sqlite, src_table, geom_column=None) - src_field_names = {f.name() for f in src_layer.fields()} - layer_with_ts = "timeseries" in src_field_names - new_feats = self.copy_features(src_layer, dst_layer, **field_mappings) - # Extra logic for dealing with Linear obstacles - self.fill_required_attributes(src_layer, new_feats) - # Extra logic for dealing with Timeseries - if layer_with_ts: - self.extract_timeseries_rawdata(src_layer, dst_layer) - dst_layer.startEditing() - dst_layer.addFeatures(new_feats) - success = dst_layer.commitChanges() - if not success: - commit_errors = dst_layer.commitErrors() - self.conversion_errors[dst_layer_name] += commit_errors - - def collect_src_dst_ids(self, annotated_model_cls): - """Getting unique feature IDs of source and destination layers.""" - src_feat_ids, dst_feat_ids = set(), set() - src_expr = None - if annotated_model_cls == dm.Pumpstation: - src_expr = QgsExpression('"connection_node_start_id" IS NOT NULL') - elif annotated_model_cls == dm.PumpstationMap: - src_expr = QgsExpression('"connection_node_start_id" IS NOT NULL AND "connection_node_end_id" IS NOT NULL') - if src_expr is None: - src_request = QgsFeatureRequest() - else: - src_request = QgsFeatureRequest(src_expr) - src_request.setFlags(QgsFeatureRequest.NoGeometry) - dst_request = QgsFeatureRequest() - dst_request.setFlags(QgsFeatureRequest.NoGeometry) - for src_table in annotated_model_cls.SQLITE_TARGETS: - src_target_layer = sqlite_layer(self.src_sqlite, src_table) - if not src_target_layer.isValid(): - src_target_layer = sqlite_layer(self.src_sqlite, src_table, geom_column=None) - # Let's assume that ids are unique across multiple sqlite targets - src_feat_ids |= {src_feat["id"] for src_feat in src_target_layer.getFeatures(src_request)} - dst_table = annotated_model_cls.__tablename__ - dst_layer = gpkg_layer(self.dst_gpkg, dst_table) - dst_id_idx = dst_layer.fields().indexFromName("id") - dst_feat_ids |= set(dst_layer.uniqueValues(dst_id_idx)) - return src_feat_ids, dst_feat_ids - - @staticmethod - def tabulate_data(*table_arguments, values_separator=", "): - table_rows = zip(*[arg.split() for arg in table_arguments]) - table_text = "\n".join(values_separator.join(row) for row in table_rows) - return table_text - - @staticmethod - def text_to_number(text_value): - try: - value = float(text_value) - except (TypeError, ValueError): - value = None - return value - - def import_cross_section_definition_data(self): - """Importing and splitting cross-section definition data.""" - xs_def_table = next(iter(dm.CrossSectionDefinition.SQLITE_SOURCES)) - xs_def_lyr = sqlite_layer(self.src_sqlite, xs_def_table) - if not xs_def_lyr.isValid(): - xs_def_lyr = sqlite_layer(self.src_sqlite, xs_def_table, geom_column=None) - model_with_xs_def = dm.CrossSectionLocation - dst_model_field_names = [ - field_name - for field_name in model_with_xs_def.__annotations__.keys() - if field_name.startswith("cross_section_") - ] - # Get cross-section definition data and reformat it to fit User Layers structure - xs_definitions = {} - for xs_def_feat in xs_def_lyr.getFeatures(): - src_xs_def_id = xs_def_feat["id"] - src_xs_def_shape = xs_def_feat["shape"] - src_xs_def_height = xs_def_feat["height"] - src_xs_def_width = xs_def_feat["width"] - src_xs_def_friction = xs_def_feat["friction_values"] - src_xs_def_vegetation_stem_densities = xs_def_feat["vegetation_stem_densities"] - src_xs_def_vegetation_stem_diameters = xs_def_feat["vegetation_stem_diameters"] - src_xs_def_vegetation_heights = xs_def_feat["vegetation_heights"] - src_xs_def_vegetation_vegetation_drag_coefficients = xs_def_feat["vegetation_drag_coefficients"] - all_vegetation_fields = [ - src_xs_def_vegetation_stem_densities, - src_xs_def_vegetation_stem_diameters, - src_xs_def_vegetation_heights, - src_xs_def_vegetation_vegetation_drag_coefficients, - ] - if src_xs_def_shape in dm.TABLE_SHAPES and src_xs_def_height and src_xs_def_width: - if src_xs_def_shape == en.CrossSectionShape.YZ.value: - xs_def_table = self.tabulate_data(src_xs_def_width, src_xs_def_height) - else: - xs_def_table = self.tabulate_data(src_xs_def_height, src_xs_def_width) - xs_def_height = None - xs_def_width = None - else: - xs_def_height = self.text_to_number(src_xs_def_height) - xs_def_width = self.text_to_number(src_xs_def_width) - xs_def_table = None - if src_xs_def_friction: - xs_def_friction_table = self.tabulate_data(src_xs_def_friction) - else: - xs_def_friction_table = None - if all(all_vegetation_fields): - xs_def_vegetation_table = self.tabulate_data(*all_vegetation_fields) - else: - xs_def_vegetation_table = None - xs_def_data = { - "cross_section_shape": src_xs_def_shape, - "cross_section_height": xs_def_height, - "cross_section_width": xs_def_width, - "cross_section_table": xs_def_table, - "cross_section_friction_table": xs_def_friction_table, - "cross_section_vegetation_table": xs_def_vegetation_table, - } - xs_definitions[src_xs_def_id] = xs_def_data - # Update User Layers cross-section definition data - request = QgsFeatureRequest() - request.setFlags(QgsFeatureRequest.NoGeometry) - for model_cls in dm.ELEMENTS_WITH_XS_DEF: - # Initialize spatialite and GeoPackage layers for the model class with the cross-section definition data - table_name = next(iter(model_cls.SQLITE_SOURCES)) - src_xs_def_lyr = sqlite_layer(self.src_sqlite, table_name) - if not src_xs_def_lyr.isValid(): - src_xs_def_lyr = sqlite_layer(self.src_sqlite, table_name, geom_column=None) - dst_xs_def_lyr = gpkg_layer(self.dst_gpkg, model_cls.__tablename__) - # Map field names to field indexes - dst_layer_fields = dst_xs_def_lyr.fields() - dst_layer_field_names = dst_layer_fields.names() - field_indexes = { - field_name: dst_layer_fields.lookupField(field_name) - for field_name in dst_model_field_names - if field_name in dst_layer_field_names - } - # Establish `cross_section_definition_id` field - src_id_field_name = model_cls.IMPORT_FIELD_MAPPINGS["id"] - xs_def_id_field_name = None - for field in src_xs_def_lyr.fields(): - field_name = field.name() - if "definition_id" in field_name: - xs_def_id_field_name = field_name - break - # Map feature `id`s to cross-section definition `id`s based on source spatialite layers - feat_to_xs_def = {} - for src_feat in src_xs_def_lyr.getFeatures(request): - src_feat_id = src_feat[src_id_field_name] - xs_def_id = src_feat[xs_def_id_field_name] - feat_to_xs_def[src_feat_id] = xs_def_id - # Pair cross-section definition data with User Layer features and prepare update dictionary - xs_def_data_changes = {} - for dst_feat in dst_xs_def_lyr.getFeatures(request): - fid = dst_feat.id() - dst_feat_id = dst_feat["id"] - xs_def_id = feat_to_xs_def[dst_feat_id] - xs_def_data = xs_definitions[xs_def_id] - xs_def_data_changes[fid] = { - field_indexes[fld]: xs_def_data[fld] for fld in xs_def_data.keys() if fld in dst_layer_field_names - } - # Update User Layers with cross-section definition data - dst_xs_def_lyr.startEditing() - for fid, changes in xs_def_data_changes.items(): - dst_xs_def_lyr.changeAttributeValues(fid, changes) - success = dst_xs_def_lyr.commitChanges() - if not success: - commit_errors = dst_xs_def_lyr.commitErrors() - self.conversion_errors[model_cls.__layername__] += commit_errors - - def import_all_model_data(self): - """Converting all Spatialite layers into GeoPackage User Layers.""" - self.conversion_errors.clear() - self.timeseries_rawdata.clear() - models_to_import = list(self.all_models) - models_to_import.remove(dm.Timeseries) - models_to_import.remove(dm.CrossSectionDefinition) - number_of_steps = len(models_to_import) - msg = "Loading data from Spatialite..." - self.uc.progress_bar(msg, 0, number_of_steps, 0, clear_msg_bar=True) - QCoreApplication.processEvents() - incomplete_imports = OrderedDict() - for i, data_model_cls in enumerate(models_to_import): - msg = f'Loading "{data_model_cls.__layername__}" layer data...' - self.uc.progress_bar(msg, 0, number_of_steps, i, clear_msg_bar=True) - QCoreApplication.processEvents() - self.import_model_data(data_model_cls) - if data_model_cls == dm.SchemaVersion: - continue - sqlite_feat_ids, gpkg_feat_ids = self.collect_src_dst_ids(data_model_cls) - sqlite_feat_count = len(sqlite_feat_ids) - gpkg_feat_count = len(gpkg_feat_ids) - if sqlite_feat_count != gpkg_feat_count: - missing_ids = list(sorted(sqlite_feat_ids - gpkg_feat_ids)) - missing = len(missing_ids) - if missing: - incomplete_imports[data_model_cls] = (sqlite_feat_count, gpkg_feat_count, missing, missing_ids) - # Adding geometry between surfaces and connection nodes - msg = f"Adding links between surfaces and connection nodes..." - self.uc.progress_bar(msg, 0, number_of_steps, number_of_steps, clear_msg_bar=True) - QCoreApplication.processEvents() - self.add_surface_map_geometries() - msg = f"Importing and splitting cross-section definition data..." - self.uc.progress_bar(msg, 0, number_of_steps, number_of_steps, clear_msg_bar=True) - QCoreApplication.processEvents() - self.import_cross_section_definition_data() - self.uc.clear_message_bar() - # TODO: Uncomment line below after finishing forms implementation - # self.process_timeseries_rawdata() - if incomplete_imports: - warn = "Incomplete import:\n" - for model_cls, (sqlite_fc, gpkg_fc, miss_no, missing_ids) in incomplete_imports.items(): - layer_name = model_cls.__layername__ - warn += f"\n{layer_name}: {gpkg_fc} out of {sqlite_fc} features imported ({miss_no} missing)\n" - ids_str = ", ".join(str(mid) for mid in missing_ids[:10]) - warn += f"ID's of missing features: {ids_str}" - if miss_no > 10: - warn += " ..." - warn += "\nPlease run the 3Di schematisation checker for more details" - self.uc.show_warn(warn) - self.report_conversion_errors() - - def export_model_data(self, annotated_model_cls): - """Converting GeoPackage User Layer into Spatialite layer based on model data class.""" - src_table = annotated_model_cls.__tablename__ - src_layer_name = annotated_model_cls.__layername__ - src_layer = gpkg_layer(self.dst_gpkg, src_table, src_layer_name) - field_mappings = {k: k for k in annotated_model_cls.__annotations__.keys()} - if annotated_model_cls.EXPORT_FIELD_MAPPINGS: - field_mappings.update(annotated_model_cls.EXPORT_FIELD_MAPPINGS) - switched_map = {v: k for k, v in field_mappings.items()} - dst_table = next(iter(annotated_model_cls.SQLITE_TARGETS)) - dst_layer = sqlite_layer(self.src_sqlite, dst_table) - if not dst_layer.isValid(): - dst_layer = sqlite_layer(self.src_sqlite, dst_table, geom_column=None) - new_feats = self.copy_features(src_layer, dst_layer, **switched_map) - self.fill_required_attributes(src_layer, new_feats) - dst_layer.startEditing() - dst_layer.addFeatures(new_feats) - success = dst_layer.commitChanges() - if not success: - commit_errors = dst_layer.commitErrors() - self.conversion_errors[src_layer_name] += commit_errors - - @staticmethod - def cross_section_definition_code(shape, width, width_values=None, height_values=None): - """Generate cross-section definition code.""" - if shape in {en.CrossSectionShape.OPEN_RECTANGLE.value, en.CrossSectionShape.CLOSED_RECTANGLE.value} and width: - code = f"rect_{width:.3f}" - elif shape == en.CrossSectionShape.CIRCLE.value and width: - code = f"round_{width:.3f}" - elif shape == en.CrossSectionShape.EGG.value and width: - code = f"egg_{width:.3f}_{width * 1.5:.3f}" - elif shape == en.CrossSectionShape.TABULATED_RECTANGLE.value and width_values and height_values: - code = f"tab_rect_{float(max(width_values, key=float)):.3f}_{float(max(height_values, key=float)):.3f}" - elif shape == en.CrossSectionShape.TABULATED_TRAPEZIUM.value and width_values and height_values: - code = f"tab_trap_{float(max(width_values, key=float)):.3f}_{float(max(height_values, key=float)):.3f}" - else: - code = None - return code - - def export_cross_section_definition_data(self): - """Exporting and aggregating cross-section definition data.""" - request = QgsFeatureRequest() - request.setFlags(QgsFeatureRequest.NoGeometry) - xs_def_data_ids = OrderedDict() - lyr_feat_to_xs_def_id = {} - next_xs_def_id = 1 - # Get and aggregate cross-section definition data - for model_cls in dm.ELEMENTS_WITH_XS_DEF: - table_name = next(iter(model_cls.SQLITE_TARGETS)) - src_with_xs_def_lyr = gpkg_layer(self.dst_gpkg, model_cls.__tablename__) - for feat_with_xs_def in src_with_xs_def_lyr.getFeatures(request): - feat_id = feat_with_xs_def["id"] - src_xs_def_shape = feat_with_xs_def["cross_section_shape"] - src_xs_def_height = feat_with_xs_def["cross_section_height"] - src_xs_def_width = feat_with_xs_def["cross_section_width"] - src_xs_def_table = feat_with_xs_def["cross_section_table"] - if model_cls == dm.CrossSectionLocation: - src_xs_def_friction_table = feat_with_xs_def["cross_section_friction_table"] - src_xs_def_vegetation_table = feat_with_xs_def["cross_section_vegetation_table"] - xs_def_friction_values = ( - " ".join(src_xs_def_friction_table.split("\n")) if src_xs_def_friction_table else None - ) - if src_xs_def_vegetation_table: - parsed_vegetation_table = [row.split(",") for row in src_xs_def_vegetation_table.split("\n")] - ( - vegetation_stem_densities, - vegetation_stem_diameters, - vegetation_heights, - vegetation_drag_coefficients, - ) = list(zip(*parsed_vegetation_table)) - xs_def_vegetation_stem_densities = " ".join(sde.strip() for sde in vegetation_stem_densities) - xs_def_vegetation_stem_diameters = " ".join(sdi.strip() for sdi in vegetation_stem_diameters) - xs_def_vegetation_heights = " ".join(h.strip() for h in vegetation_heights) - xs_def_vegetation_drag_coefficients = " ".join( - dc.strip() for dc in vegetation_drag_coefficients - ) - else: - ( - xs_def_vegetation_stem_densities, - xs_def_vegetation_stem_diameters, - xs_def_vegetation_heights, - xs_def_vegetation_drag_coefficients, - ) = (None,) * 4 - else: - ( - xs_def_friction_values, - xs_def_vegetation_stem_densities, - xs_def_vegetation_stem_diameters, - xs_def_vegetation_heights, - xs_def_vegetation_drag_coefficients, - ) = (None,) * 5 - - if src_xs_def_shape in dm.TABLE_SHAPES and src_xs_def_table: - parsed_table = [row.split(",") for row in src_xs_def_table.split("\n")] - height_values, width_values = list(zip(*parsed_table)) - if src_xs_def_shape == en.CrossSectionShape.YZ.value: - height_values, width_values = width_values, height_values - xs_def_height = " ".join(hv.strip() for hv in height_values) - xs_def_width = " ".join(wv.strip() for wv in width_values) - src_xs_def_code = self.cross_section_definition_code( - src_xs_def_shape, src_xs_def_width, width_values, height_values - ) - else: - xs_def_height = str(src_xs_def_height) if src_xs_def_height else None - xs_def_width = str(src_xs_def_width) if src_xs_def_width else None - src_xs_def_code = self.cross_section_definition_code(src_xs_def_shape, src_xs_def_width) - xs_def_data = ( - src_xs_def_code, - src_xs_def_shape, - xs_def_height, - xs_def_width, - xs_def_friction_values, - xs_def_vegetation_stem_densities, - xs_def_vegetation_stem_diameters, - xs_def_vegetation_heights, - xs_def_vegetation_drag_coefficients, - ) - try: - xs_def_id = xs_def_data_ids[xs_def_data] - except KeyError: - xs_def_id = next_xs_def_id - xs_def_data_ids[xs_def_data] = xs_def_id - next_xs_def_id += 1 - lyr_feat_to_xs_def_id[table_name, feat_id] = xs_def_id - # Inserting CrossSectionDefinition data - xs_def_table = next(iter(dm.CrossSectionDefinition.SQLITE_TARGETS)) - xs_def_lyr = sqlite_layer(self.src_sqlite, xs_def_table) - if not xs_def_lyr.isValid(): - xs_def_lyr = sqlite_layer(self.src_sqlite, xs_def_table, geom_column=None) - xs_def_fields = xs_def_lyr.fields() - new_xs_def_feats = [] - for ( - xs_def_code, - xs_def_shape, - xs_def_height, - xs_def_width, - xs_def_friction_values, - xs_def_vegetation_stem_densities, - xs_def_vegetation_stem_diameters, - xs_def_vegetation_heights, - xs_def_vegetation_drag_coefficients, - ), xs_def_id in xs_def_data_ids.items(): - new_feat = QgsFeature(xs_def_fields) - new_feat["id"] = xs_def_id - new_feat["code"] = xs_def_code - new_feat["shape"] = xs_def_shape - new_feat["height"] = xs_def_height - new_feat["width"] = xs_def_width - new_feat["friction_values"] = xs_def_friction_values - new_feat["vegetation_stem_densities"] = xs_def_vegetation_stem_densities - new_feat["vegetation_stem_diameters"] = xs_def_vegetation_stem_diameters - new_feat["vegetation_heights"] = xs_def_vegetation_heights - new_feat["vegetation_drag_coefficients"] = xs_def_vegetation_drag_coefficients - new_xs_def_feats.append(new_feat) - xs_def_lyr.startEditing() - xs_def_lyr.addFeatures(new_xs_def_feats) - success = xs_def_lyr.commitChanges() - if not success: - commit_errors = xs_def_lyr.commitErrors() - self.conversion_errors[dm.CrossSectionDefinition.__layername__] += commit_errors - # Update `cross_section_definition_id` fields in the layers that refers to the cross-section definition data - for model_cls in dm.ELEMENTS_WITH_XS_DEF: - dst_changes = {} - table_name = next(iter(model_cls.SQLITE_TARGETS)) - dst_with_xs_def_lyr = sqlite_layer(self.src_sqlite, table_name) - if not dst_with_xs_def_lyr.isValid(): - dst_with_xs_def_lyr = sqlite_layer(self.src_sqlite, table_name, geom_column=None) - # Establish `cross_section_definition_id` field index - dst_with_xs_def_lyr_fields = dst_with_xs_def_lyr.fields() - xs_def_id_field = "definition_id" if model_cls == dm.CrossSectionLocation else "cross_section_definition_id" - xs_def_id_field_idx = dst_with_xs_def_lyr_fields.lookupField(xs_def_id_field) - # Create dictionary with `cross_section_definition_id` updates - for dst_feat in dst_with_xs_def_lyr.getFeatures(request): - feat_fid = dst_feat.id() - dst_feat_id = dst_feat["id"] - xs_def_id = lyr_feat_to_xs_def_id[table_name, dst_feat_id] - dst_changes[feat_fid] = xs_def_id - # Update `cross_section_definition_id`field - dst_with_xs_def_lyr.startEditing() - for feat_fid, xs_def_id in dst_changes.items(): - dst_with_xs_def_lyr.changeAttributeValue(feat_fid, xs_def_id_field_idx, xs_def_id) - success = dst_with_xs_def_lyr.commitChanges() - if not success: - commit_errors = dst_with_xs_def_lyr.commitErrors() - self.conversion_errors[model_cls.__layername__] += commit_errors - - def export_all_model_data(self): - """Converting all GeoPackage User Layers into Spatialite layers.""" - # TODO: Uncomment line below after finishing forms implementation - # self.recreate_timeseries_rawdata() - self.conversion_errors.clear() - models_to_export = list(self.all_models) - models_to_export.remove(dm.Timeseries) - models_to_export.remove(dm.PumpstationMap) - models_to_export.remove(dm.CrossSectionDefinition) - number_of_steps = len(models_to_export) - msg = "Saving data into Spatialite..." - self.uc.progress_bar(msg, 0, number_of_steps, 0, clear_msg_bar=True) - QCoreApplication.processEvents() - incomplete_exports = OrderedDict() - for i, data_model_cls in enumerate(models_to_export): - msg = f'Saving "{data_model_cls.__layername__}" layer data...' - self.uc.progress_bar(msg, 0, number_of_steps, i, clear_msg_bar=True) - QCoreApplication.processEvents() - self.export_model_data(data_model_cls) - if data_model_cls == dm.SchemaVersion: - continue - sqlite_feat_ids, gpkg_feat_ids = self.collect_src_dst_ids(data_model_cls) - sqlite_feat_count = len(sqlite_feat_ids) - gpkg_feat_count = len(gpkg_feat_ids) - if gpkg_feat_count != sqlite_feat_count: - missing_ids = list(sorted(gpkg_feat_ids - sqlite_feat_ids)) - missing = len(missing_ids) - if missing: - incomplete_exports[data_model_cls] = (sqlite_feat_count, gpkg_feat_count, missing, missing_ids) - msg = f"Exporting and aggregating cross-section definition data..." - self.uc.progress_bar(msg, 0, number_of_steps, number_of_steps, clear_msg_bar=True) - QCoreApplication.processEvents() - self.export_cross_section_definition_data() - self.uc.clear_message_bar() - if incomplete_exports: - warn = "Incomplete export:\n" - for model_cls, (sqlite_fc, gpkg_fc, miss_no, miss_ids) in incomplete_exports.items(): - layer_name = model_cls.__layername__ - warn += f"\n{layer_name}: {sqlite_fc} out of {gpkg_fc} features exported ({miss_no} missing)\n" - ids_str = ", ".join(str(mid) for mid in miss_ids[:10]) - warn += f"ID's of missing features: {ids_str}" - if miss_no > 10: - warn += " ..." - self.uc.show_warn(warn) - self.report_conversion_errors() diff --git a/threedi_schematisation_editor/custom_tools/__init__.py b/threedi_schematisation_editor/custom_tools/__init__.py index 0a24aa7..964cbfb 100644 --- a/threedi_schematisation_editor/custom_tools/__init__.py +++ b/threedi_schematisation_editor/custom_tools/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 by Lutra Consulting +# Copyright (C) 2025 by Lutra Consulting from collections import defaultdict, namedtuple from enum import Enum from functools import cached_property @@ -86,7 +86,7 @@ def models_fields_iterator(self): def field_methods_mapping(self): methods_mapping = defaultdict(dict) auto_fields = {"id"} - auto_attribute_fields = {"connection_node_id", "connection_node_start_id", "connection_node_end_id"} + auto_attribute_fields = {"connection_node_id", "connection_node_id_start", "connection_node_id_end"} for field_name, model_cls in self.models_fields_iterator: if field_name in auto_fields: methods_mapping[model_cls][field_name] = [ColumnImportMethod.AUTO] @@ -174,7 +174,6 @@ def __init__(self, external_source, target_gpkg, import_settings): "use_snapping", "snapping_distance", "create_connection_nodes", - "create_manholes", "length_source_field", "length_fallback_value", "azimuth_source_field", @@ -192,7 +191,6 @@ def conversion_settings(self): else: snapping_distance = self.DEFAULT_INTERSECTION_BUFFER create_connection_nodes = conversion_config.get("create_connection_nodes", False) - create_manholes = conversion_config.get("create_manholes", False) length_source_field = conversion_config.get("length_source_field", None) length_fallback_value = conversion_config.get("length_fallback_value", 10.0) azimuth_source_field = conversion_config.get("azimuth_source_field", None) @@ -202,7 +200,6 @@ def conversion_settings(self): use_snapping, snapping_distance, create_connection_nodes, - create_manholes, length_source_field, length_fallback_value, azimuth_source_field, @@ -298,58 +295,6 @@ def import_structures(self, context=None, selected_ids=None): raise NotImplementedError("Function called from the abstract class.") -class AbstractStructuresImporterWithManholes(AbstractStructuresImporter): - def __init__(self, *args): - super().__init__(*args) - self.manhole_layer = None - - @property - def modifiable_layers(self): - """Return a list of the layers that can be modified.""" - return super().modifiable_layers + [self.manhole_layer] - - def setup_target_layers(self, structure_model_cls, structure_layer=None, node_layer=None, manhole_layer=None): - """Setup target layers with fields configuration.""" - super().setup_target_layers(structure_model_cls, structure_layer, node_layer) - self.manhole_layer = ( - gpkg_layer(self.target_gpkg, dm.Manhole.__tablename__) if manhole_layer is None else manhole_layer - ) - self.fields_configurations[dm.Manhole] = self.import_settings.get("manhole_fields", {}) - - def manholes_for_structures(self, external_source_features, new_structure_features): - """Create manholes for the structures.""" - manhole_fields = self.manhole_layer.fields() - next_manhole_id = get_next_feature_id(self.manhole_layer) - manholes_at_nodes = {manhole_feat["connection_node_id"] for manhole_feat in self.manhole_layer.getFeatures()} - new_manholes = [] - for external_src_feat, structure_feat in zip(external_source_features, new_structure_features): - start_node_id = structure_feat["connection_node_start_id"] - end_node_id = structure_feat["connection_node_end_id"] - if start_node_id not in manholes_at_nodes: - start_manhole_feat = QgsFeature(manhole_fields) - start_node = next(get_features_by_expression(self.node_layer, f'"id" = {start_node_id}', True)) - start_node_point = start_node.geometry().asPoint() - start_manhole_feat.setGeometry(QgsGeometry.fromPointXY(start_node_point)) - start_manhole_feat["id"] = next_manhole_id - start_manhole_feat["connection_node_id"] = start_node_id - self.update_attributes(dm.Manhole, external_src_feat, start_manhole_feat) - new_manholes.append(start_manhole_feat) - manholes_at_nodes.add(start_node_id) - next_manhole_id += 1 - if end_node_id not in manholes_at_nodes: - end_manhole_feat = QgsFeature(manhole_fields) - end_node = next(get_features_by_expression(self.node_layer, f'"id" = {end_node_id}', True)) - end_node_point = end_node.geometry().asPoint() - end_manhole_feat.setGeometry(QgsGeometry.fromPointXY(end_node_point)) - end_manhole_feat["id"] = next_manhole_id - end_manhole_feat["connection_node_id"] = end_node_id - self.update_attributes(dm.Manhole, external_src_feat, end_manhole_feat) - new_manholes.append(end_manhole_feat) - manholes_at_nodes.add(end_node_id) - next_manhole_id += 1 - return new_manholes - - class PointStructuresImporter(AbstractStructuresImporter): """Point features importer class.""" @@ -451,7 +396,7 @@ def import_structures(self, context=None, selected_ids=None): self.structure_layer.addFeatures(new_structures) -class LinearStructuresImporter(AbstractStructuresImporterWithManholes): +class LinearStructuresImporter(AbstractStructuresImporter): """Linear features importer class.""" def new_structure_geometry(self, src_structure_feat): @@ -512,7 +457,7 @@ def process_structure_feature( polyline[0] = node_start_point new_geom = QgsGeometry.fromPolylineXY(polyline) node_start_id = node_start_feat["id"] - new_structure_feat["connection_node_start_id"] = node_start_id + new_structure_feat["connection_node_id_start"] = node_start_id else: if self.conversion_settings.create_connection_nodes: new_start_node_feat = QgsFeature(node_fields) @@ -520,7 +465,7 @@ def process_structure_feature( new_start_node_feat.setGeometry(QgsGeometry.fromPointXY(node_start_point)) new_node_start_id = next_connection_node_id new_start_node_feat["id"] = new_node_start_id - new_structure_feat["connection_node_start_id"] = new_node_start_id + new_structure_feat["connection_node_id_start"] = new_node_start_id next_connection_node_id += 1 new_nodes.append(new_start_node_feat) if node_end_feat: @@ -528,7 +473,7 @@ def process_structure_feature( polyline[-1] = node_end_point new_geom = QgsGeometry.fromPolylineXY(polyline) node_end_id = node_end_feat["id"] - new_structure_feat["connection_node_end_id"] = node_end_id + new_structure_feat["connection_node_id_end"] = node_end_id else: if self.conversion_settings.create_connection_nodes: new_end_node_feat = QgsFeature(node_fields) @@ -536,7 +481,7 @@ def process_structure_feature( new_end_node_feat.setGeometry(QgsGeometry.fromPointXY(node_end_point)) new_node_end_id = next_connection_node_id new_end_node_feat["id"] = new_node_end_id - new_structure_feat["connection_node_end_id"] = new_node_end_id + new_structure_feat["connection_node_id_end"] = new_node_end_id next_connection_node_id += 1 new_nodes.append(new_end_node_feat) else: @@ -546,14 +491,14 @@ def process_structure_feature( new_start_node_feat.setGeometry(QgsGeometry.fromPointXY(node_start_point)) new_start_node_id = next_connection_node_id new_start_node_feat["id"] = new_start_node_id - new_structure_feat["connection_node_start_id"] = new_start_node_id + new_structure_feat["connection_node_id_start"] = new_start_node_id next_connection_node_id += 1 new_end_node_feat = QgsFeature(node_fields) node_end_point = polyline[-1] new_end_node_feat.setGeometry(QgsGeometry.fromPointXY(node_end_point)) new_end_node_id = next_connection_node_id new_end_node_feat["id"] = new_end_node_id - new_structure_feat["connection_node_end_id"] = new_end_node_id + new_structure_feat["connection_node_id_end"] = new_end_node_id next_connection_node_id += 1 new_nodes += [new_start_node_feat, new_end_node_feat] new_structure_feat.setGeometry(new_geom) @@ -595,10 +540,6 @@ def import_structures(self, context=None, selected_ids=None): next_structure_id += 1 new_structures.append(new_structure_feat) external_source_structures.append(external_src_feat) - if self.conversion_settings.create_manholes: - self.manhole_layer.startEditing() - new_manholes = self.manholes_for_structures(external_source_structures, new_structures) - self.manhole_layer.addFeatures(new_manholes) self.structure_layer.addFeatures(new_structures) @@ -627,12 +568,11 @@ def setup_target_layers( structure_model_cls, structure_layer=None, node_layer=None, - manhole_layer=None, channel_layer=None, cross_section_location_layer=None, ): """Setup target layers with fields configuration.""" - super().setup_target_layers(structure_model_cls, structure_layer, node_layer, manhole_layer) + super().setup_target_layers(structure_model_cls, structure_layer, node_layer) self.channel_layer = ( gpkg_layer(self.target_gpkg, dm.Channel.__tablename__) if channel_layer is None else channel_layer ) @@ -739,7 +679,6 @@ def update_feature_endpoints(self, dst_feature, **template_node_attributes): start_node_feat.setGeometry(start_node) for field_name, field_value in template_node_attributes.items(): start_node_feat[field_name] = field_value - start_node_feat["fid"] = start_node_id start_node_feat["id"] = start_node_id self.next_node_id += 1 self.node_by_location[start_node_point] = start_node_id @@ -754,14 +693,13 @@ def update_feature_endpoints(self, dst_feature, **template_node_attributes): end_node_feat.setGeometry(end_node) for field_name, field_value in template_node_attributes.items(): end_node_feat[field_name] = field_value - end_node_feat["fid"] = end_node_id end_node_feat["id"] = end_node_id self.next_node_id += 1 self.node_by_location[end_node_point] = end_node_id self.features_to_add[node_layer_name].append(end_node_feat) new_nodes.append(end_node_feat) - dst_feature["connection_node_start_id"] = start_node_id - dst_feature["connection_node_end_id"] = end_node_id + dst_feature["connection_node_id_start"] = start_node_id + dst_feature["connection_node_id_end"] = end_node_id return new_nodes def update_channel_cross_section_references(self, new_channels, source_channel_xs_locations): @@ -851,7 +789,6 @@ def integrate_structure_features(self, channel_feat, channel_structures): channel_fields = self.layer_fields_mapping[channel_layer_name] channel_field_names = self.layer_field_names_mapping[channel_layer_name] channel_attributes = {field_name: channel_feat[field_name] for field_name in channel_field_names} - del channel_attributes["fid"] channel_geom = channel_feat.geometry() channel_polyline = channel_geom.asPolyline() first_point = channel_polyline[0] @@ -871,7 +808,6 @@ def integrate_structure_features(self, channel_feat, channel_structures): # Update with values from the widgets. self.update_attributes(self.structure_model_cls, src_structure_feat, structure_feat) structure_attributes = {field_name: structure_feat[field_name] for field_name in structure_field_names} - del structure_attributes["fid"] structure_length = channel_structure.length half_length = structure_length * 0.5 structure_m = channel_structure.m @@ -941,10 +877,8 @@ def import_structures(self, context=None, selected_ids=None): source_channel_feat = next(get_features_by_expression(self.channel_layer, f'"id" = {channel_id}')) self.channel_layer.deleteFeature(source_channel_feat.id()) visited_channel_ids.add(channel_id) - channel_feat["fid"] = source_channel_feat["fid"] channel_feat["id"] = source_channel_feat["id"] else: - channel_feat["fid"] = next_channel_id channel_feat["id"] = next_channel_id next_channel_id += 1 channels_to_add[channel_id].append(channel_feat) @@ -956,7 +890,6 @@ def import_structures(self, context=None, selected_ids=None): # Process structures structures_to_add = [] for structure_id, structure_feat in enumerate(self.features_to_add[self.structure_layer_name], start=1): - structure_feat["fid"] = structure_id structure_feat["id"] = structure_id structures_to_add.append(structure_feat) self.structure_layer.startEditing() @@ -994,18 +927,14 @@ def import_structures(self, context=None, selected_ids=None): disconnected_structures_to_add.append(new_structure_feat) external_source_structures.append(disconnected_structure) self.structure_layer.addFeatures(disconnected_structures_to_add) - if self.conversion_settings.create_manholes: - self.manhole_layer.startEditing() - new_manholes = self.manholes_for_structures(external_source_structures, disconnected_structures_to_add) - self.manhole_layer.addFeatures(new_manholes) class CulvertsImporter(LinearStructuresImporter): """Class with methods responsible for the importing culverts from the external data source.""" - def __init__(self, *args, structure_layer=None, node_layer=None, manhole_layer=None): + def __init__(self, *args, structure_layer=None, node_layer=None): super().__init__(*args) - self.setup_target_layers(dm.Culvert, structure_layer, node_layer, manhole_layer) + self.setup_target_layers(dm.Culvert, structure_layer, node_layer) class CulvertsIntegrator(StructuresIntegrator): @@ -1016,22 +945,19 @@ def __init__( *args, structure_layer=None, node_layer=None, - manhole_layer=None, channel_layer=None, cross_section_location_layer=None, ): super().__init__(*args) - self.setup_target_layers( - dm.Culvert, structure_layer, node_layer, manhole_layer, channel_layer, cross_section_location_layer - ) + self.setup_target_layers(dm.Culvert, structure_layer, node_layer, channel_layer, cross_section_location_layer) class OrificesImporter(LinearStructuresImporter): """Class with methods responsible for the importing orifices from the external data source.""" - def __init__(self, *args, structure_layer=None, node_layer=None, manhole_layer=None): + def __init__(self, *args, structure_layer=None, node_layer=None): super().__init__(*args) - self.setup_target_layers(dm.Orifice, structure_layer, node_layer, manhole_layer) + self.setup_target_layers(dm.Orifice, structure_layer, node_layer) class OrificesIntegrator(StructuresIntegrator): @@ -1042,22 +968,19 @@ def __init__( *args, structure_layer=None, node_layer=None, - manhole_layer=None, channel_layer=None, cross_section_location_layer=None, ): super().__init__(*args) - self.setup_target_layers( - dm.Orifice, structure_layer, node_layer, manhole_layer, channel_layer, cross_section_location_layer - ) + self.setup_target_layers(dm.Orifice, structure_layer, node_layer, channel_layer, cross_section_location_layer) class WeirsImporter(LinearStructuresImporter): """Class with methods responsible for the importing weirs from the external data source.""" - def __init__(self, *args, structure_layer=None, node_layer=None, manhole_layer=None): + def __init__(self, *args, structure_layer=None, node_layer=None): super().__init__(*args) - self.setup_target_layers(dm.Weir, structure_layer, node_layer, manhole_layer) + self.setup_target_layers(dm.Weir, structure_layer, node_layer) class WeirsIntegrator(StructuresIntegrator): @@ -1068,27 +991,16 @@ def __init__( *args, structure_layer=None, node_layer=None, - manhole_layer=None, channel_layer=None, cross_section_location_layer=None, ): super().__init__(*args) - self.setup_target_layers( - dm.Weir, structure_layer, node_layer, manhole_layer, channel_layer, cross_section_location_layer - ) + self.setup_target_layers(dm.Weir, structure_layer, node_layer, channel_layer, cross_section_location_layer) class PipesImporter(LinearStructuresImporter): """Class with methods responsible for the importing pipes from the external data source.""" - def __init__(self, *args, structure_layer=None, node_layer=None, manhole_layer=None): - super().__init__(*args) - self.setup_target_layers(dm.Pipe, structure_layer, node_layer, manhole_layer) - - -class ManholesImporter(PointStructuresImporter): - """Class with methods responsible for the importing manholes from the external data source.""" - def __init__(self, *args, structure_layer=None, node_layer=None): super().__init__(*args) - self.setup_target_layers(dm.Manhole, structure_layer, node_layer) + self.setup_target_layers(dm.Pipe, structure_layer, node_layer) diff --git a/threedi_schematisation_editor/custom_widgets/__init__.py b/threedi_schematisation_editor/custom_widgets/__init__.py index 4ac7a8d..8b7ac01 100644 --- a/threedi_schematisation_editor/custom_widgets/__init__.py +++ b/threedi_schematisation_editor/custom_widgets/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 by Lutra Consulting +# Copyright (C) 2025 by Lutra Consulting import ast import json import os @@ -16,7 +16,6 @@ ColumnImportMethod, CulvertsImporter, CulvertsIntegrator, - ManholesImporter, OrificesImporter, OrificesIntegrator, PipesImporter, @@ -35,20 +34,11 @@ optional_type, ) -ps_basecls, ps_uicls = uic.loadUiType(os.path.join(os.path.dirname(__file__), "ui", "projection_selection.ui")) ic_basecls, ic_uicls = uic.loadUiType(os.path.join(os.path.dirname(__file__), "ui", "import_structures.ui")) vm_basecls, vm_uicls = uic.loadUiType(os.path.join(os.path.dirname(__file__), "ui", "attribute_value_map.ui")) load_basecls, load_uicls = uic.loadUiType(os.path.join(os.path.dirname(__file__), "ui", "load_schematisation.ui")) -class ProjectionSelectionDialog(ps_basecls, ps_uicls): - """Dialog with selection of the desired projection.""" - - def __init__(self, parent=None): - super().__init__(parent) - self.setupUi(self) - - class AttributeValueMapDialog(vm_basecls, vm_uicls): """Dialog for setting attribute value mappings.""" @@ -170,14 +160,12 @@ class ImportStructuresDialog(ic_basecls, ic_uicls): dm.Orifice: OrificesImporter, dm.Weir: WeirsImporter, dm.Pipe: PipesImporter, - dm.Manhole: ManholesImporter, } STRUCTURE_INTEGRATORS = { dm.Culvert: CulvertsIntegrator, dm.Orifice: OrificesIntegrator, dm.Weir: WeirsIntegrator, } - STRUCTURES_WITH_MANHOLES = (dm.Culvert, dm.Orifice, dm.Weir, dm.Pipe) LAST_CONFIG_DIR_ENTRY = "threedi/last_import_config_dir" @@ -189,20 +177,14 @@ def __init__(self, structure_model_cls, model_gpkg, layer_manager, uc, parent=No self.layer_manager = layer_manager self.uc = uc self.import_configuration = StructuresImportConfig(self.structure_model_cls) - if self.include_manholes: - self.import_configuration.add_related_model_class(dm.Manhole) self.structure_model = QStandardItemModel() self.structure_tv.setModel(self.structure_model) self.connection_node_model = QStandardItemModel() self.connection_node_tv.setModel(self.connection_node_model) - self.manhole_model = QStandardItemModel() - self.manhole_tv.setModel(self.manhole_model) self.data_models_tree_views = { self.structure_model_cls: (self.structure_tv, self.structure_model), dm.ConnectionNode: (self.connection_node_tv, self.connection_node_model), } - if self.include_manholes: - self.data_models_tree_views[dm.Manhole] = (self.manhole_tv, self.manhole_model) self.structure_layer_cbo.setFilters( QgsMapLayerProxyModel.PointLayer if self.structure_model_cls.__geometrytype__ == dm.GeometryType.Point @@ -212,16 +194,11 @@ def __init__(self, structure_model_cls, model_gpkg, layer_manager, uc, parent=No self.populate_conversion_settings_widgets() self.structure_layer_cbo.layerChanged.connect(self.on_layer_changed) self.create_nodes_cb.stateChanged.connect(self.on_create_nodes_change) - self.create_manholes_cb.stateChanged.connect(self.on_create_manholes_change) self.save_pb.clicked.connect(self.save_import_settings) self.load_pb.clicked.connect(self.load_import_settings) self.run_pb.clicked.connect(self.run_import_structures) self.close_pb.clicked.connect(self.close) self.setup_labels() - if not self.include_manholes: - self.create_manholes_cb.hide() - self.create_manholes_cb.setChecked(False) - self.tab_widget.setTabVisible(2, False) if not self.enable_structures_integration: for widget in self.structures_integration_widgets: widget.hide() @@ -237,10 +214,6 @@ def setup_labels(self): def is_obsolete_field(model_cls, field_name): return field_name in model_cls.obsolete_fields() - @property - def include_manholes(self): - return self.structure_model_cls in self.STRUCTURES_WITH_MANHOLES - @property def enable_structures_integration(self): return self.structure_model_cls in self.STRUCTURE_INTEGRATORS @@ -274,8 +247,6 @@ def layer_dependent_widgets(self): self.create_nodes_cb, self.snap_gb, ] - if self.include_manholes: - widgets.append(self.create_manholes_cb) if self.enable_structures_integration: widgets += self.structures_integration_widgets return widgets @@ -290,14 +261,6 @@ def deactivate_layer_dependent_widgets(self): def on_create_nodes_change(self, is_checked): self.connection_node_tab.setEnabled(is_checked) - if self.include_manholes: - if not is_checked: - self.create_manholes_cb.setChecked(is_checked) - self.manhole_tab.setEnabled(is_checked) - - def on_create_manholes_change(self, is_checked): - if self.include_manholes: - self.manhole_tab.setEnabled(is_checked) def on_layer_changed(self, layer): layer_field_names = [""] @@ -311,8 +274,6 @@ def on_layer_changed(self, layer): self.length_source_field_cbo.setLayer(None) self.azimuth_source_field_cbo.setLayer(None) data_models = [self.structure_model_cls, dm.ConnectionNode] - if self.include_manholes: - data_models.append(dm.Manhole) source_attribute_widgets = self.get_column_widgets( StructuresImportConfig.SOURCE_ATTRIBUTE_COLUMN_IDX, *data_models ) @@ -390,8 +351,6 @@ def populate_conversion_settings_widgets(self): def connect_configuration_widgets(self): data_models = [self.structure_model_cls, dm.ConnectionNode] - if self.include_manholes: - data_models.append(dm.Manhole) for model_cls in data_models: tree_view, tree_view_model = self.data_models_tree_views[model_cls] row_idx = 0 @@ -431,7 +390,6 @@ def collect_settings(self): "use_snapping": self.snap_gb.isChecked(), "snapping_distance": self.snap_dsb.value(), "create_connection_nodes": self.create_nodes_cb.isChecked(), - "create_manholes": self.create_manholes_cb.isChecked(), "length_source_field": self.length_source_field_cbo.currentField(), "length_fallback_value": self.length_fallback_value_dsb.value(), "azimuth_source_field": self.azimuth_source_field_cbo.currentField(), @@ -441,8 +399,6 @@ def collect_settings(self): "fields": self.collect_fields_settings(self.structure_model_cls), "connection_node_fields": self.collect_fields_settings(dm.ConnectionNode), } - if self.include_manholes: - import_settings["manhole_fields"] = self.collect_fields_settings(dm.Manhole) return import_settings def collect_fields_settings(self, model_cls): @@ -561,12 +517,6 @@ def load_import_settings(self): self.update_fields_settings(dm.ConnectionNode, connection_node_fields) except KeyError: pass - if self.include_manholes: - self.create_manholes_cb.setChecked(conversion_settings.get("create_manholes", False)) - try: - self.update_fields_settings(dm.Manhole, import_settings["manhole_fields"]) - except KeyError: - pass self.uc.show_info(f"Settings loaded from the template.", self) except Exception as e: self.uc.show_error(f"Import failed due to the following error:\n{e}", self) @@ -575,8 +525,6 @@ def missing_source_fields(self): data_models = [self.structure_model_cls] if self.create_nodes_cb.isChecked(): data_models.append(dm.ConnectionNode) - if self.include_manholes and self.create_manholes_cb.isChecked(): - data_models.append(dm.Manhole) field_labels = self.get_column_widgets(StructuresImportConfig.FIELD_NAME_COLUMN_IDX, *data_models) method_widgets = self.get_column_widgets(StructuresImportConfig.METHOD_COLUMN_IDX, *data_models) source_attribute_widgets = self.get_column_widgets( @@ -618,12 +566,10 @@ def run_import_structures(self): source_layer = self.source_layer structures_handler = self.layer_manager.model_handlers[self.structure_model_cls] node_handler = self.layer_manager.model_handlers[dm.ConnectionNode] - manhole_handler = self.layer_manager.model_handlers[dm.Manhole] channel_handler = self.layer_manager.model_handlers[dm.Channel] cross_section_location_handler = self.layer_manager.model_handlers[dm.CrossSectionLocation] structure_layer = structures_handler.layer node_layer = node_handler.layer - manhole_layer = manhole_handler.layer channel_layer = channel_handler.layer cross_section_location_layer = cross_section_location_handler.layer selected_feat_ids = None @@ -638,9 +584,6 @@ def run_import_structures(self): structure_importer_cls = self.STRUCTURE_IMPORTERS[self.structure_model_cls] processed_handlers = [structures_handler, node_handler] processed_layers = {"structure_layer": structure_layer, "node_layer": node_layer} - if self.include_manholes: - processed_handlers.append(manhole_handler) - processed_layers["manhole_layer"] = manhole_layer if edit_channels: processed_handlers += [channel_handler, cross_section_location_handler] processed_layers["channel_layer"] = channel_layer @@ -648,20 +591,12 @@ def run_import_structures(self): try: for handler in processed_handlers: handler.disconnect_handler_signals() - if self.include_manholes: - structures_importer = structure_importer_cls( - source_layer, - self.model_gpkg, - import_settings, - **processed_layers, - ) - else: - structures_importer = structure_importer_cls( - source_layer, - self.model_gpkg, - import_settings, - **processed_layers, - ) + structures_importer = structure_importer_cls( + source_layer, + self.model_gpkg, + import_settings, + **processed_layers, + ) structures_importer.import_structures(selected_ids=selected_feat_ids) success_msg = ( "Features imported successfully.\n\n" @@ -692,9 +627,9 @@ def __init__(self, uc, parent=None): self.working_dir = self.settings.value("threedi/working_dir", "", type=str) if self.working_dir: self.file_browse_widget.setDefaultRoot(self.working_dir) - self.selected_schematisation_sqlite = None - self.schematisation_tv.doubleClicked.connect(self.set_schematisation_sqlite_filepath) - self.load_pb.clicked.connect(self.set_schematisation_sqlite_filepath) + self.selected_schematisation_filepath = None + self.schematisation_tv.doubleClicked.connect(self.set_schematisation_filepath) + self.load_pb.clicked.connect(self.set_schematisation_filepath) self.cancle_pb.clicked.connect(self.reject) self.list_working_dir_schematisations() @@ -717,27 +652,27 @@ def list_working_dir_schematisations(self): local_schematisation_name = local_schematisation.name wip_revision = local_schematisation.wip_revision try: - wip_revision_sqlite = wip_revision.sqlite + wip_revision_gpkg = wip_revision.geopackage_filepath except (AttributeError, FileNotFoundError): - wip_revision_sqlite = None - if wip_revision_sqlite is not None: + wip_revision_gpkg = None + if wip_revision_gpkg is not None: schematisation_name_item = QStandardItem(local_schematisation_name) revision_number_str = f"{wip_revision.number} (work in progress)" revision_number_item = QStandardItem(revision_number_str) - revision_number_item.setData(wip_revision_sqlite, Qt.UserRole) + revision_number_item.setData(wip_revision_gpkg, Qt.UserRole) self.schematisation_model.appendRow([schematisation_name_item, revision_number_item]) if wip_revision.schematisation_dir == last_used_schematisation_dir: last_used_schematisation_row_number = self.schematisation_model.rowCount() - 1 for revision_number, revision in local_schematisation.revisions.items(): try: - revision_sqlite = revision.sqlite - if revision_sqlite is None: + revision_gpkg = revision.geopackage_filepath + if revision_gpkg is None: continue except FileNotFoundError: continue schematisation_name_item = QStandardItem(local_schematisation_name) revision_number_item = QStandardItem(str(revision.number)) - revision_number_item.setData(revision_sqlite, Qt.UserRole) + revision_number_item.setData(revision_gpkg, Qt.UserRole) self.schematisation_model.appendRow([schematisation_name_item, revision_number_item]) if revision.schematisation_dir == last_used_schematisation_dir: last_used_schematisation_row_number = self.schematisation_model.rowCount() - 1 @@ -750,7 +685,7 @@ def list_working_dir_schematisations(self): ) self.schematisation_tv.scrollTo(last_used_schematisation_row_idx) - def set_schematisation_sqlite_filepath(self): + def set_schematisation_filepath(self): """Set selected schematisation filepath.""" if self.load_tab.currentIndex() == 0: index = self.schematisation_tv.currentIndex() @@ -759,12 +694,12 @@ def set_schematisation_sqlite_filepath(self): return current_row = index.row() revision_item = self.schematisation_model.item(current_row, 1) - revision_sqlite = revision_item.data(Qt.UserRole) - self.selected_schematisation_sqlite = revision_sqlite + revision_gpkg = revision_item.data(Qt.UserRole) + self.selected_schematisation_filepath = revision_gpkg else: selected_filepath = self.file_browse_widget.filePath() if not selected_filepath: self.uc.show_warn("No file selected. Please select schematisation file to continue.", parent=self) return - self.selected_schematisation_sqlite = self.file_browse_widget.filePath() + self.selected_schematisation_filepath = self.file_browse_widget.filePath() self.accept() diff --git a/threedi_schematisation_editor/custom_widgets/ui/import_structures.ui b/threedi_schematisation_editor/custom_widgets/ui/import_structures.ui index d9e4ca5..b316bab 100644 --- a/threedi_schematisation_editor/custom_widgets/ui/import_structures.ui +++ b/threedi_schematisation_editor/custom_widgets/ui/import_structures.ui @@ -122,16 +122,6 @@ - - - Manholes - - - - - - - @@ -235,24 +225,6 @@ - - - - - 10 - - - - Qt::LeftToRight - - - Create manholes - - - true - - - @@ -265,39 +237,6 @@ - - - - - 10 - - - - Qt::LeftToRight - - - Selected features only - - - - - - - - 10 - - - - Qt::LeftToRight - - - Create connection nodes - - - true - - - @@ -394,6 +333,39 @@ + + + + + 10 + + + + Qt::LeftToRight + + + Create connection nodes + + + true + + + + + + + + 10 + + + + Qt::LeftToRight + + + Selected features only + + + @@ -413,9 +385,6 @@ structure_layer_cbo edit_channels_cb - create_manholes_cb - create_nodes_cb - selected_only_cb length_source_field_cbo length_fallback_value_dsb azimuth_source_field_cbo @@ -423,7 +392,6 @@ tab_widget structure_tv connection_node_tv - manhole_tv save_pb load_pb snap_gb diff --git a/threedi_schematisation_editor/custom_widgets/ui/load_schematisation.ui b/threedi_schematisation_editor/custom_widgets/ui/load_schematisation.ui index 57cdca8..367c94e 100644 --- a/threedi_schematisation_editor/custom_widgets/ui/load_schematisation.ui +++ b/threedi_schematisation_editor/custom_widgets/ui/load_schematisation.ui @@ -6,8 +6,8 @@ 0 0 - 750 - 750 + 748 + 743 @@ -56,7 +56,7 @@ - *.sqlite + *.gpkg *.sqlite @@ -76,7 +76,7 @@ - Path to the schematisation Spatialite file: + Path to the schematisation file: diff --git a/threedi_schematisation_editor/custom_widgets/ui/projection_selection.ui b/threedi_schematisation_editor/custom_widgets/ui/projection_selection.ui deleted file mode 100644 index a04d37c..0000000 --- a/threedi_schematisation_editor/custom_widgets/ui/projection_selection.ui +++ /dev/null @@ -1,112 +0,0 @@ - - - projection_dialog - - - - 0 - 0 - 382 - 133 - - - - Select projection - - - - - - - - - - 8 - - - - Notice: after the model has been loaded, fill in the numerical and global settings before saving to Spatialite. - - - Qt::PlainText - - - true - - - - - - - Default projection for 3Di model user layers - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - QgsProjectionSelectionWidget - QWidget -
qgsprojectionselectionwidget.h
-
-
- - - - buttonBox - accepted() - projection_dialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - projection_dialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - -
diff --git a/threedi_schematisation_editor/data/empty.sqlite b/threedi_schematisation_editor/data/empty.sqlite deleted file mode 100644 index 9365487..0000000 Binary files a/threedi_schematisation_editor/data/empty.sqlite and /dev/null differ diff --git a/threedi_schematisation_editor/data_models.py b/threedi_schematisation_editor/data_models.py index 5d9c8c8..decc342 100644 --- a/threedi_schematisation_editor/data_models.py +++ b/threedi_schematisation_editor/data_models.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 by Lutra Consulting +# Copyright (C) 2025 by Lutra Consulting from dataclasses import dataclass from types import MappingProxyType, SimpleNamespace from typing import Optional @@ -6,11 +6,13 @@ from threedi_schematisation_editor.enumerators import ( AggregationMethod, BoundaryType, - CalculationType, - CalculationTypeCulvert, - CalculationTypeNode, + ControlType, CrestType, CrossSectionShape, + ExchangeTypeChannel, + ExchangeTypeCulvert, + ExchangeTypeNode, + ExchangeTypePipe, FlowVariable, FrictionType, FrictionTypeExtended, @@ -19,16 +21,13 @@ InitializationType, InterflowType, Later2DType, - ManholeIndicator, - ManholeShape, - Material, - PipeCalculationType, + MeasureVariable, PipeMaterial, PumpType, SewerageType, - SurfaceClass, - SurfaceInclinationType, - ZoomCategories, + TimeUnit, + Unit, + Visualisation, ) @@ -37,10 +36,6 @@ class ModelObject: __layername__ = None __geometrytype__ = GeometryType.NoGeometry - SQLITE_SOURCES = tuple() - SQLITE_TARGETS = tuple() - IMPORT_FIELD_MAPPINGS = MappingProxyType({}) - EXPORT_FIELD_MAPPINGS = MappingProxyType({}) RELATED_RASTERS = tuple() @classmethod @@ -73,150 +68,98 @@ def obsolete_fields() -> set: @dataclass class ConnectionNode(ModelObject): __tablename__ = "connection_node" - __layername__ = "Connection Node" + __layername__ = "Connection node" __geometrytype__ = GeometryType.Point - SQLITE_SOURCES = ("v2_connection_nodes",) - SQLITE_TARGETS = SQLITE_SOURCES - id: int code: str - initial_waterlevel: Optional[float] + display_name: str storage_area: Optional[float] + initial_water_level: Optional[float] + visualisation: Optional[Visualisation] + manhole_surface_level: Optional[float] + bottom_level: float + exchange_level: Optional[float] + exchange_type: Optional[ExchangeTypeNode] + exchange_thickness: Optional[float] + hydraulic_conductivity_in: Optional[float] + hydraulic_conductivity_out: Optional[float] + tags: Optional[str] @staticmethod def display_names() -> list: display_names_list = [ "ID", "Code", - "Initial water level [m]", + "Display name", "Storage area [m²]", + "Initial water level [m]", + "Visualisation", + "Bottom level [m MSL]", + "Manhole surface level [m MSL]", + "Exchange level [m MSL]", + "Exchange type", + "Exchange thickness [m]", + "Hydraulic conductivity in [m/d]", + "Hydraulic conductivity out [m/d]", + "Tag", ] return display_names_list @dataclass -class BoundaryCondition1D(ModelObject): - __tablename__ = "1d_boundary_condition" - __layername__ = "1D Boundary Condition" - __geometrytype__ = GeometryType.Point - - SQLITE_SOURCES = ("v2_1d_boundary_conditions_view",) - SQLITE_TARGETS = ("v2_1d_boundary_conditions",) +class Material(ModelObject): + __tablename__ = "material" + __layername__ = "Material" + __geometrytype__ = GeometryType.NoGeometry id: int - boundary_type: BoundaryType - connection_node_id: int - timeseries: str # TODO: This is just temporary - remove after finishing Timeseries implementation + description: str + friction_value: float + friction_type: FrictionType @dataclass -class Lateral1D(ModelObject): - __tablename__ = "1d_lateral" - __layername__ = "1D Lateral" +class BoundaryCondition1D(ModelObject): + __tablename__ = "boundary_condition_1d" + __layername__ = "1D Boundary condition" __geometrytype__ = GeometryType.Point - SQLITE_SOURCES = ("v2_1d_lateral_view",) - SQLITE_TARGETS = ("v2_1d_lateral",) - id: int + code: str + display_name: str + type: BoundaryType connection_node_id: int - timeseries: str # TODO: This is just temporary - remove after finishing Timeseries implementation + timeseries: str + time_units: TimeUnit + interpolate: bool + tags: Optional[str] @dataclass -class Manhole(ModelObject): - __tablename__ = "manhole" - __layername__ = "Manhole" +class Lateral1D(ModelObject): + __tablename__ = "lateral_1d" + __layername__ = "1D Lateral" __geometrytype__ = GeometryType.Point - SQLITE_SOURCES = ("v2_manhole_view",) - SQLITE_TARGETS = ("v2_manhole",) - - IMPORT_FIELD_MAPPINGS = MappingProxyType( - { - "id": "manh_id", - "code": "manh_code", - "display_name": "manh_display_name", - "calculation_type": "manh_calculation_type", - "shape": "manh_shape", - "width": "manh_width", - "length": "manh_length", - "bottom_level": "manh_bottom_level", - "surface_level": "manh_surface_level", - "drain_level": "manh_drain_level", - "sediment_level": "manh_sediment_level", - "manhole_indicator": "manh_manhole_indicator", - "zoom_category": "manh_zoom_category", - "connection_node_id": "manh_connection_node_id", - "exchange_thickness": "manh_exchange_thickness", - "hydraulic_conductivity_in": "manh_hydraulic_conductivity_in", - "hydraulic_conductivity_out": "manh_hydraulic_conductivity_out", - } - ) - id: int code: str display_name: str - calculation_type: Optional[CalculationTypeNode] - shape: Optional[ManholeShape] - width: Optional[float] - length: Optional[float] - bottom_level: float - surface_level: Optional[float] - drain_level: Optional[float] - sediment_level: Optional[float] - manhole_indicator: Optional[ManholeIndicator] - zoom_category: Optional[ZoomCategories] + time_units: TimeUnit + interpolate: bool + offset: int + units: Unit connection_node_id: int - exchange_thickness: Optional[float] - hydraulic_conductivity_in: Optional[float] - hydraulic_conductivity_out: Optional[float] - - @staticmethod - def display_names() -> list: - display_names_list = [ - "ID", - "Code", - "Display name", - "Calculation type", - "Shape", - "Width [m]", - "Length [m]", - "Bottom level [m MSL]", - "Surface level [m MSL]", - "Drain level [m MSL]", - "Sediment level [m MSL]", - "Manhole indicator", - "Zoom category", - "Connection node ID", - "Exchange thickness [m]", - "Hydraulic conductivity in [m/d]", - "Hydraulic conductivity out [m/d]", - ] - return display_names_list + timeseries: str + tags: Optional[str] @dataclass -class Pumpstation(ModelObject): - __tablename__ = "pumpstation" - __layername__ = "Pumpstation (without end node)" +class Pump(ModelObject): + __tablename__ = "pump" + __layername__ = "Pump" __geometrytype__ = GeometryType.Point - SQLITE_SOURCES = ("v2_pumpstation_point_view",) - SQLITE_TARGETS = ("v2_pumpstation",) - - IMPORT_FIELD_MAPPINGS = MappingProxyType( - { - "id": "pump_id", - } - ) - EXPORT_FIELD_MAPPINGS = MappingProxyType( - { - "connection_node_id": "connection_node_start_id", - } - ) - id: int code: str display_name: str @@ -226,36 +169,22 @@ class Pumpstation(ModelObject): capacity: float type: PumpType sewerage: bool - zoom_category: Optional[ZoomCategories] connection_node_id: int + tags: Optional[str] @dataclass -class PumpstationMap(ModelObject): - __tablename__ = "pumpstation_map" - __layername__ = "Pumpstation (with end node)" +class PumpMap(ModelObject): + __tablename__ = "pump_map" + __layername__ = "Pump map" __geometrytype__ = GeometryType.Linestring - SQLITE_SOURCES = ("v2_pumpstation_view",) - SQLITE_TARGETS = ("v2_pumpstation",) - - IMPORT_FIELD_MAPPINGS = MappingProxyType( - { - "id": "pump_id", - "code": "pump_code", - "display_name": "pump_display_name", - "pumpstation_id": "pump_id", - "connection_node_start_id": "pump_connection_node_start_id", - "connection_node_end_id": "pump_connection_node_end_id", - } - ) - id: int code: str display_name: str - pumpstation_id: int - connection_node_start_id: int - connection_node_end_id: int + pump_id: int + connection_node_id_end: int + tags: Optional[str] @dataclass @@ -264,28 +193,6 @@ class Weir(ModelObject): __layername__ = "Weir" __geometrytype__ = GeometryType.Linestring - SQLITE_SOURCES = ("v2_weir_view",) - SQLITE_TARGETS = ("v2_weir",) - - IMPORT_FIELD_MAPPINGS = MappingProxyType( - { - "id": "weir_id", - "code": "weir_code", - "display_name": "weir_display_name", - "crest_level": "weir_crest_level", - "crest_type": "weir_crest_type", - "discharge_coefficient_positive": "weir_discharge_coefficient_positive", - "discharge_coefficient_negative": "weir_discharge_coefficient_negative", - "friction_value": "weir_friction_value", - "friction_type": "weir_friction_type", - "sewerage": "weir_sewerage", - "external": "weir_external", - "zoom_category": "weir_zoom_category", - "connection_node_start_id": "weir_connection_node_start_id", - "connection_node_end_id": "weir_connection_node_end_id", - } - ) - @staticmethod def display_names() -> list: display_names_list = [ @@ -296,17 +203,18 @@ def display_names() -> list: "Crest type", "Discharge coefficient positive", "Discharge coefficient negative", + "Material ID", "Friction value", "Friction type", "Sewerage", "External", - "Zoom category", "Connection node start ID", "Connection node end ID", "Cross section shape", "Cross section width [m]", "Cross section height [m]", "Cross section table", + "Tag", ] return display_names_list @@ -317,17 +225,18 @@ def display_names() -> list: crest_type: CrestType discharge_coefficient_positive: Optional[float] discharge_coefficient_negative: Optional[float] + material_id: int friction_value: float friction_type: FrictionType sewerage: bool external: Optional[bool] - zoom_category: Optional[ZoomCategories] - connection_node_start_id: int - connection_node_end_id: int + connection_node_id_start: int + connection_node_id_end: int cross_section_shape: CrossSectionShape cross_section_width: Optional[float] cross_section_height: Optional[float] cross_section_table: Optional[str] + tags: Optional[str] @dataclass @@ -336,46 +245,24 @@ class Culvert(ModelObject): __layername__ = "Culvert" __geometrytype__ = GeometryType.Linestring - SQLITE_SOURCES = ("v2_culvert_view",) - SQLITE_TARGETS = ("v2_culvert",) - - IMPORT_FIELD_MAPPINGS = MappingProxyType( - { - "id": "cul_id", - "code": "cul_code", - "display_name": "cul_display_name", - "calculation_type": "cul_calculation_type", - "dist_calc_points": "cul_dist_calc_points", - "invert_level_start_point": "cul_invert_level_start_point", - "invert_level_end_point": "cul_invert_level_end_point", - "discharge_coefficient_positive": "cul_discharge_coefficient_positive", - "discharge_coefficient_negative": "cul_discharge_coefficient_negative", - "friction_value": "cul_friction_value", - "friction_type": "cul_friction_type", - "zoom_category": "cul_zoom_category", - "connection_node_start_id": "cul_connection_node_start_id", - "connection_node_end_id": "cul_connection_node_end_id", - } - ) - id: int code: str display_name: str - calculation_type: Optional[CalculationTypeCulvert] - dist_calc_points: Optional[float] - invert_level_start_point: float - invert_level_end_point: float + exchange_type: Optional[ExchangeTypeCulvert] + calculation_point_distance: Optional[float] + invert_level_start: float + invert_level_end: float discharge_coefficient_positive: float discharge_coefficient_negative: float friction_value: float friction_type: FrictionType - zoom_category: Optional[ZoomCategories] - connection_node_start_id: int - connection_node_end_id: int + connection_node_id_start: int + connection_node_id_end: int cross_section_shape: CrossSectionShape cross_section_width: Optional[float] cross_section_height: Optional[float] cross_section_table: Optional[str] + tags: Optional[str] @staticmethod def display_names() -> list: @@ -383,7 +270,7 @@ def display_names() -> list: "ID", "Code", "Display name", - "Calculation type", + "Exchange type", "Calculation point distance [m]", "Invert level start point", "Invert level end point", @@ -391,13 +278,13 @@ def display_names() -> list: "Discharge coefficient negative", "Friction value", "Friction type", - "Zoom category", "Connection node start ID", "Connection node end ID", "Cross section shape", "Cross section width [m]", "Cross section height [m]", "Cross section table", + "Tag", ] return display_names_list @@ -408,27 +295,6 @@ class Orifice(ModelObject): __layername__ = "Orifice" __geometrytype__ = GeometryType.Linestring - SQLITE_SOURCES = ("v2_orifice_view",) - SQLITE_TARGETS = ("v2_orifice",) - - IMPORT_FIELD_MAPPINGS = MappingProxyType( - { - "id": "orf_id", - "code": "orf_code", - "display_name": "orf_display_name", - "crest_level": "orf_crest_level", - "crest_type": "orf_crest_type", - "discharge_coefficient_positive": "orf_discharge_coefficient_positive", - "discharge_coefficient_negative": "orf_discharge_coefficient_negative", - "friction_value": "orf_friction_value", - "friction_type": "orf_friction_type", - "sewerage": "orf_sewerage", - "zoom_category": "orf_zoom_category", - "connection_node_start_id": "orf_connection_node_start_id", - "connection_node_end_id": "orf_connection_node_end_id", - } - ) - id: int code: str display_name: str @@ -436,16 +302,17 @@ class Orifice(ModelObject): crest_type: CrestType discharge_coefficient_positive: Optional[float] discharge_coefficient_negative: Optional[float] + material_id: int friction_value: float friction_type: FrictionType sewerage: bool - zoom_category: Optional[ZoomCategories] - connection_node_start_id: int - connection_node_end_id: int + connection_node_id_start: int + connection_node_id_end: int cross_section_shape: CrossSectionShape cross_section_width: Optional[float] cross_section_height: Optional[float] cross_section_table: Optional[str] + tags: Optional[str] @staticmethod def display_names() -> list: @@ -457,16 +324,17 @@ def display_names() -> list: "Crest type", "Discharge coefficient positive", "Discharge coefficient negative", + "Material ID", "Friction value", "Friction type", "Sewerage", - "Zoom category", "Connection node start ID", "Connection node end ID", "Cross section shape", "Cross section width [m]", "Cross section height [m]", "Cross section table", + "Tag", ] return display_names_list @@ -477,51 +345,19 @@ class Pipe(ModelObject): __layername__ = "Pipe" __geometrytype__ = GeometryType.Linestring - SQLITE_SOURCES = ("v2_pipe_view",) - SQLITE_TARGETS = ("v2_pipe",) - - IMPORT_FIELD_MAPPINGS = MappingProxyType( - { - "id": "pipe_id", - "code": "pipe_code", - "display_name": "pipe_display_name", - "calculation_type": "pipe_calculation_type", - "dist_calc_points": "pipe_dist_calc_points", - "invert_level_start_point": "pipe_invert_level_start_point", - "invert_level_end_point": "pipe_invert_level_end_point", - "friction_value": "pipe_friction_value", - "friction_type": "pipe_friction_type", - "material": "pipe_material", - "pipe_quality": "pipe_pipe_quality", - "sewerage_type": "pipe_sewerage_type", - "zoom_category": "pipe_zoom_category", - "profile_num": "pipe_profile_num", - "original_length": "pipe_original_length", - "connection_node_start_id": "pipe_connection_node_start_id", - "connection_node_end_id": "pipe_connection_node_end_id", - "exchange_thickness": "pipe_exchange_thickness", - "hydraulic_conductivity_in": "pipe_hydraulic_conductivity_in", - "hydraulic_conductivity_out": "pipe_hydraulic_conductivity_out", - } - ) - id: int code: str display_name: str - calculation_type: PipeCalculationType - dist_calc_points: Optional[float] - invert_level_start_point: float - invert_level_end_point: float + exchange_type: ExchangeTypePipe + calculation_point_distance: Optional[float] + invert_level_start: float + invert_level_end: float + material_id: int friction_value: float friction_type: FrictionType - material: Optional[PipeMaterial] - pipe_quality: float sewerage_type: Optional[SewerageType] - zoom_category: Optional[ZoomCategories] - profile_num: Optional[int] - original_length: Optional[float] - connection_node_start_id: int - connection_node_end_id: int + connection_node_id_start: int + connection_node_id_end: int cross_section_shape: CrossSectionShape cross_section_width: Optional[float] cross_section_height: Optional[float] @@ -529,6 +365,7 @@ class Pipe(ModelObject): exchange_thickness: Optional[float] hydraulic_conductivity_in: Optional[float] hydraulic_conductivity_out: Optional[float] + tags: Optional[str] @staticmethod def display_names() -> list: @@ -536,18 +373,14 @@ def display_names() -> list: "ID", "Code", "Display name", - "Calculation type", + "exchange type", "Calculation point distance [m]", "Invert level start point", "Invert level end point", + "Material ID", "Friction value", "Friction type", - "Material", - "Pipe quality", "Sewerage type", - "Zoom category", - "Profile number", - "Original length", "Connection node start ID", "Connection node end ID", "Cross section shape", @@ -557,14 +390,10 @@ def display_names() -> list: "Exchange thickness [m]", "Hydraulic conductivity in [m/d]", "Hydraulic conductivity out [m/d]", + "Tag", ] return display_names_list - @staticmethod - def obsolete_fields() -> set: - obsolete_fields_set = {"pipe_quality", "profile_num", "original_length"} - return obsolete_fields_set - @dataclass class CrossSectionLocation(ModelObject): @@ -572,25 +401,6 @@ class CrossSectionLocation(ModelObject): __layername__ = "Cross section location" __geometrytype__ = GeometryType.Point - SQLITE_SOURCES = ("v2_cross_section_location_view",) - SQLITE_TARGETS = ("v2_cross_section_location",) - - IMPORT_FIELD_MAPPINGS = MappingProxyType( - { - "id": "loc_id", - "code": "loc_code", - "reference_level": "loc_reference_level", - "friction_value": "loc_friction_value", - "friction_type": "loc_friction_type", - "bank_level": "loc_bank_level", - "channel_id": "loc_channel_id", - "vegetation_stem_density": "loc_vegetation_stem_density", - "vegetation_stem_diameter": "loc_vegetation_stem_diameter", - "vegetation_height": "loc_vegetation_height", - "vegetation_drag_coefficient": "loc_vegetation_drag_coefficient", - } - ) - id: int code: str reference_level: float @@ -598,16 +408,17 @@ class CrossSectionLocation(ModelObject): friction_value: float bank_level: Optional[float] channel_id: int - vegetation_stem_density: Optional[float] - vegetation_stem_diameter: Optional[float] - vegetation_height: Optional[float] - vegetation_drag_coefficient: Optional[float] cross_section_shape: CrossSectionShape cross_section_width: Optional[float] cross_section_height: Optional[float] cross_section_table: Optional[str] - cross_section_friction_table: Optional[str] + cross_section_friction_values: Optional[str] cross_section_vegetation_table: Optional[str] + vegetation_stem_density: Optional[float] + vegetation_stem_diameter: Optional[float] + vegetation_height: Optional[float] + vegetation_drag_coefficient: Optional[float] + tags: Optional[str] @dataclass @@ -616,78 +427,80 @@ class Channel(ModelObject): __layername__ = "Channel" __geometrytype__ = GeometryType.Linestring - SQLITE_SOURCES = ("v2_channel",) - SQLITE_TARGETS = SQLITE_SOURCES - id: int code: str display_name: str - calculation_type: CalculationType - dist_calc_points: Optional[float] - zoom_category: Optional[ZoomCategories] - connection_node_start_id: int - connection_node_end_id: int + exchange_type: ExchangeTypeChannel + calculation_point_distance: Optional[float] + connection_node_id_start: int + connection_node_id_end: int exchange_thickness: Optional[float] hydraulic_conductivity_in: Optional[float] hydraulic_conductivity_out: Optional[float] + tags: Optional[str] @dataclass class BoundaryCondition2D(ModelObject): - __tablename__ = "2d_boundary_condition" + __tablename__ = "boundary_condition_2d" __layername__ = "2D Boundary condition" __geometrytype__ = GeometryType.Linestring - SQLITE_SOURCES = ("v2_2d_boundary_conditions",) - SQLITE_TARGETS = SQLITE_SOURCES - id: int + code: str display_name: str - boundary_type: BoundaryType - timeseries: str # TODO: This is just temporary - remove after finishing Timeseries implementation + type: BoundaryType + timeseries: str + time_units: TimeUnit + interpolate: bool + tags: Optional[str] @dataclass class Lateral2D(ModelObject): - __tablename__ = "2d_lateral" + __tablename__ = "lateral_2d" __layername__ = "2D Lateral" __geometrytype__ = GeometryType.Point - SQLITE_SOURCES = ("v2_2d_lateral",) - SQLITE_TARGETS = SQLITE_SOURCES - id: int + code: str + display_name: str type: Later2DType - timeseries: str # TODO: This is just temporary - remove after finishing Timeseries implementation + time_units: TimeUnit + interpolate: bool + offset: int + units: Unit + timeseries: str + tags: Optional[str] @dataclass -class LinearObstacle(ModelObject): - __tablename__ = "linear_obstacle" - __layername__ = "Linear Obstacle" +class Obstacle(ModelObject): + __tablename__ = "obstacle" + __layername__ = "Obstacle" __geometrytype__ = GeometryType.Linestring - SQLITE_SOURCES = ("v2_obstacle",) - SQLITE_TARGETS = SQLITE_SOURCES - id: int code: str + display_name: str crest_level: float + affects_2d: bool + affects_1d2d_open_water: bool + affects_1d2d_closed: bool + tags: Optional[str] @dataclass -class GridRefinement(ModelObject): - __tablename__ = "grid_refinement" - __layername__ = "Grid refinement" +class GridRefinementLine(ModelObject): + __tablename__ = "grid_refinement_line" + __layername__ = "Grid refinement line" __geometrytype__ = GeometryType.Linestring - SQLITE_SOURCES = ("v2_grid_refinement",) - SQLITE_TARGETS = SQLITE_SOURCES - id: int code: str display_name: str - refinement_level: int + grid_level: int + tags: Optional[str] @dataclass @@ -696,13 +509,11 @@ class GridRefinementArea(ModelObject): __layername__ = "Grid refinement area" __geometrytype__ = GeometryType.Polygon - SQLITE_SOURCES = ("v2_grid_refinement_area",) - SQLITE_TARGETS = SQLITE_SOURCES - id: int code: str display_name: str - refinement_level: int + grid_level: int + tags: Optional[str] @dataclass @@ -711,20 +522,17 @@ class DEMAverageArea(ModelObject): __layername__ = "DEM average area" __geometrytype__ = GeometryType.Polygon - SQLITE_SOURCES = ("v2_dem_average_area",) - SQLITE_TARGETS = SQLITE_SOURCES - id: int + code: str + display_name: str + tags: Optional[str] @dataclass -class Windshielding(ModelObject): - __tablename__ = "windshielding" - __layername__ = "Windshielding" - __geometrytype__ = GeometryType.NoGeometry - - SQLITE_SOURCES = ("v2_windshielding",) - SQLITE_TARGETS = SQLITE_SOURCES +class Windshielding1D(ModelObject): + __tablename__ = "windshielding_1d" + __layername__ = "1D Windshielding" + __geometrytype__ = GeometryType.Point id: int north: Optional[float] @@ -736,6 +544,7 @@ class Windshielding(ModelObject): west: Optional[float] northwest: Optional[float] channel_id: int + tags: Optional[str] @dataclass @@ -744,16 +553,14 @@ class PotentialBreach(ModelObject): __layername__ = "Potential breach" __geometrytype__ = GeometryType.Linestring - SQLITE_SOURCES = ("v2_potential_breach",) - SQLITE_TARGETS = SQLITE_SOURCES - id: int code: Optional[str] display_name: Optional[str] channel_id: int - exchange_level: Optional[float] + initial_exchange_level: Optional[float] + final_exchange_level: Optional[float] levee_material: Optional[Material] - maximum_breach_depth: Optional[float] + tags: Optional[str] @dataclass @@ -762,49 +569,12 @@ class ExchangeLine(ModelObject): __layername__ = "Exchange line" __geometrytype__ = GeometryType.Linestring - SQLITE_SOURCES = ("v2_exchange_line",) - SQLITE_TARGETS = SQLITE_SOURCES - - id: int - channel_id: int - exchange_level: Optional[float] - - -@dataclass -class ImperviousSurface(ModelObject): - __tablename__ = "impervious_surface" - __layername__ = "Impervious Surface" - __geometrytype__ = GeometryType.Polygon - - SQLITE_SOURCES = ("v2_impervious_surface",) - SQLITE_TARGETS = SQLITE_SOURCES - id: int code: str display_name: str - surface_inclination: SurfaceInclinationType - surface_class: SurfaceClass - surface_sub_class: Optional[str] - zoom_category: Optional[ZoomCategories] - nr_of_inhabitants: Optional[float] - area: Optional[float] - dry_weather_flow: Optional[float] - function: Optional[str] - - -@dataclass -class ImperviousSurfaceMap(ModelObject): - __tablename__ = "impervious_surface_map" - __layername__ = "Impervious surface map" - __geometrytype__ = GeometryType.Linestring - - SQLITE_SOURCES = ("v2_impervious_surface_map",) - SQLITE_TARGETS = SQLITE_SOURCES - - id: int - percentage: float - impervious_surface_id: int - connection_node_id: int + channel_id: int + exchange_level: Optional[float] + tags: Optional[str] @dataclass @@ -813,18 +583,12 @@ class Surface(ModelObject): __layername__ = "Surface" __geometrytype__ = GeometryType.Polygon - SQLITE_SOURCES = ("v2_surface",) - SQLITE_TARGETS = SQLITE_SOURCES - id: int code: str display_name: str - zoom_category: Optional[ZoomCategories] - nr_of_inhabitants: Optional[float] area: Optional[float] - dry_weather_flow: Optional[float] - function: Optional[str] surface_parameters_id: int + tags: Optional[str] @dataclass @@ -833,13 +597,13 @@ class SurfaceMap(ModelObject): __layername__ = "Surface map" __geometrytype__ = GeometryType.Linestring - SQLITE_SOURCES = ("v2_surface_map",) - SQLITE_TARGETS = SQLITE_SOURCES - id: int + code: str + display_name: str percentage: Optional[float] surface_id: int connection_node_id: int + tags: Optional[str] @dataclass @@ -848,10 +612,8 @@ class SurfaceParameters(ModelObject): __layername__ = "Surface parameters" __geometrytype__ = GeometryType.NoGeometry - SQLITE_SOURCES = ("v2_surface_parameters",) - SQLITE_TARGETS = SQLITE_SOURCES - id: int + description: str outflow_delay: float surface_layer_thickness: float infiltration: bool @@ -859,74 +621,119 @@ class SurfaceParameters(ModelObject): min_infiltration_capacity: float infiltration_decay_constant: float infiltration_recovery_constant: float + tags: Optional[str] + + +@dataclass +class DryWeatherFlow(ModelObject): + __tablename__ = "dry_weather_flow" + __layername__ = "Dry weather flow" + __geometrytype__ = GeometryType.Polygon + + id: int + code: str + display_name: str + multiplier: Optional[float] + daily_total: Optional[float] + interpolate: bool + dry_weather_flow_distribution_id: int + tags: Optional[str] @dataclass -class GlobalSettings(ModelObject): - __tablename__ = "global_settings" - __layername__ = "Global settings" +class DryWeatherFlowMap(ModelObject): + __tablename__ = "dry_weather_flow_map" + __layername__ = "Dry weather flow map" + __geometrytype__ = GeometryType.Linestring + + id: int + percentage: float + dry_weather_flow_id: int + connection_node_id: int + tags: Optional[str] + + +@dataclass +class DryWeatherFlowDistribution(ModelObject): + __tablename__ = "dry_weather_flow_distribution" + __layername__ = "Dry weather flow distribution" + __geometrytype__ = GeometryType.NoGeometry + + id: int + description: str + distribution: str + tags: Optional[str] + + +@dataclass +class ModelSettings(ModelObject): + __tablename__ = "model_settings" + __layername__ = "Model settings" __geometrytype__ = GeometryType.NoGeometry - SQLITE_SOURCES = ("v2_global_settings",) - SQLITE_TARGETS = SQLITE_SOURCES RELATED_RASTERS = ( ("dem_file", "Digital elevation model [m MSL]"), - ("frict_coef_file", "Friction coefficient [-]"), - ("initial_groundwater_level_file", "Initial groundwater level [m MSL]"), - ("initial_waterlevel_file", "Initial water level [m MSL]"), - ("interception_file", "Interception [m]"), + ("friction_coefficient_file", "Friction coefficient [-]"), ) id: int use_2d_flow: bool use_1d_flow: bool - manhole_storage_area: Optional[float] - name: Optional[str] - sim_time_step: float - output_time_step: Optional[float] - nr_timesteps: int - start_time: Optional[str] - start_date: str - grid_space: float - dist_calc_points: float - kmax: int - guess_dams: Optional[int] - table_step_size: float - flooding_threshold: float - advection_1d: int - advection_2d: int + manhole_aboveground_storage_area: Optional[float] + minimum_cell_size: float + calculation_point_distance_1d: float + nr_grid_levels: int + node_open_water_detection: int + minimum_table_step_size: float dem_file: Optional[str] - frict_type: Optional[int] - frict_coef: float - frict_coef_file: Optional[str] - water_level_ini_type: Optional[InitializationType] - initial_waterlevel: float - initial_waterlevel_file: Optional[str] - interception_global: Optional[float] - interception_file: Optional[str] - dem_obstacle_detection: bool - dem_obstacle_height: Optional[float] + friction_type: Optional[int] + friction_coefficient: float + friction_coefficient_file: Optional[str] embedded_cutoff_threshold: Optional[float] epsg_code: Optional[int] - timestep_plus: bool max_angle_1d_advection: Optional[float] - minimum_sim_time_step: Optional[float] - maximum_sim_time_step: Optional[float] - frict_avg: Optional[int] - wind_shielding_file: Optional[str] - use_0d_inflow: int + friction_averaging: Optional[int] table_step_size_1d: Optional[float] use_2d_rain: int + use_interflow: bool + use_simple_infiltration: bool + use_groundwater_flow: bool + use_groundwater_storage: bool + use_interception: bool + maximum_table_step_size: float + use_vegetation_drag_2d: Optional[bool] + + +@dataclass +class InitialConditionsSettings(ModelObject): + __tablename__ = "initial_conditions" + __layername__ = "Initial conditions" + __geometrytype__ = GeometryType.NoGeometry + + RELATED_RASTERS = ( + ("initial_groundwater_level_file", "Initial groundwater level [m MSL]"), + ("initial_water_level_file", "Initial water level [m MSL]"), + ) + + id: int + initial_water_level: float + initial_water_level_file: Optional[str] initial_groundwater_level: Optional[float] initial_groundwater_level_file: Optional[str] - initial_groundwater_level_type: Optional[InitializationType] - numerical_settings_id: int - interflow_settings_id: int - control_group_id: int - simple_infiltration_settings_id: int - groundwater_settings_id: int - maximum_table_step_size: float - vegetation_drag_settings_id: Optional[int] + initial_groundwater_level_aggregation: Optional[InitializationType] + + +@dataclass +class InterceptionSettings(ModelObject): + __tablename__ = "interception" + __layername__ = "Interception" + __geometrytype__ = GeometryType.NoGeometry + + RELATED_RASTERS = (("interception_file", "Interception [m]"),) + + id: int + interception: Optional[float] + interception_file: Optional[str] @dataclass @@ -935,50 +742,41 @@ class AggregationSettings(ModelObject): __layername__ = "Aggregation settings" __geometrytype__ = GeometryType.NoGeometry - SQLITE_SOURCES = ("v2_aggregation_settings",) - SQLITE_TARGETS = SQLITE_SOURCES - id: int - global_settings_id: int - var_name: str flow_variable: FlowVariable aggregation_method: Optional[AggregationMethod] - timestep: int + interval: int @dataclass class SimpleInfiltrationSettings(ModelObject): - __tablename__ = "simple_infiltration_settings" - __layername__ = "Simple infiltration settings" + __tablename__ = "simple_infiltration" + __layername__ = "Simple infiltration" __geometrytype__ = GeometryType.NoGeometry - SQLITE_SOURCES = ("v2_simple_infiltration",) - SQLITE_TARGETS = SQLITE_SOURCES RELATED_RASTERS = ( ("infiltration_rate_file", "Infiltration rate [mm/d]"), - ("max_infiltration_capacity_file", "Max infiltration capacity [m]"), + ("max_infiltration_volume_file", "Max infiltration capacity [m]"), ) id: int + display_name: str infiltration_rate: float infiltration_rate_file: Optional[str] infiltration_surface_option: Optional[InfiltrationSurfaceOption] - max_infiltration_capacity_file: Optional[str] - display_name: str - max_infiltration_capacity: Optional[float] + max_infiltration_volume_file: Optional[str] + max_infiltration_volume: Optional[float] @dataclass class GroundWaterSettings(ModelObject): - __tablename__ = "groundwater_settings" - __layername__ = "Groundwater settings" + __tablename__ = "groundwater" + __layername__ = "Groundwater" __geometrytype__ = GeometryType.NoGeometry - SQLITE_SOURCES = ("v2_groundwater",) - SQLITE_TARGETS = SQLITE_SOURCES RELATED_RASTERS = ( - ("equilibrium_infiltration_rate_file", "Equilibrium infiltration rate [mm/d]"), - ("groundwater_hydro_connectivity_file", "Hydraulic conductivity [m/day]"), + ("equilibrium_infiltration_rate_aggregation", "Equilibrium infiltration rate [mm/d]"), + ("groundwater_hydraulic_conductivity_file", "Hydraulic conductivity [m/day]"), ("groundwater_impervious_layer_level_file", "Impervious layer level [m MSL]"), ("infiltration_decay_period_file", "Infiltration decay period [d]"), ("initial_infiltration_rate_file", "Initial infiltration rate [mm/d]"), @@ -989,22 +787,22 @@ class GroundWaterSettings(ModelObject): id: int groundwater_impervious_layer_level: Optional[float] groundwater_impervious_layer_level_file: Optional[str] - groundwater_impervious_layer_level_type: Optional[InitializationType] + groundwater_impervious_layer_level_aggregation: Optional[InitializationType] phreatic_storage_capacity: Optional[float] phreatic_storage_capacity_file: Optional[str] - phreatic_storage_capacity_type: Optional[InitializationType] + phreatic_storage_capacity_aggregation: Optional[InitializationType] equilibrium_infiltration_rate: Optional[float] - equilibrium_infiltration_rate_file: Optional[str] - equilibrium_infiltration_rate_type: Optional[InitializationType] + equilibrium_infiltration_rate_aggregation: Optional[str] + equilibrium_infiltration_rate_aggregation: Optional[InitializationType] initial_infiltration_rate: Optional[float] initial_infiltration_rate_file: Optional[str] - initial_infiltration_rate_type: Optional[InitializationType] + initial_infiltration_rate_aggregation: Optional[InitializationType] infiltration_decay_period: Optional[float] infiltration_decay_period_file: Optional[str] - infiltration_decay_period_type: Optional[InitializationType] - groundwater_hydro_connectivity: Optional[float] - groundwater_hydro_connectivity_file: Optional[str] - groundwater_hydro_connectivity_type: Optional[InitializationType] + infiltration_decay_period_aggregation: Optional[InitializationType] + groundwater_hydraulic_conductivity: Optional[float] + groundwater_hydraulic_conductivity_file: Optional[str] + groundwater_hydraulic_conductivity_aggregation: Optional[InitializationType] display_name: str leakage: Optional[float] leakage_file: Optional[str] @@ -1012,12 +810,10 @@ class GroundWaterSettings(ModelObject): @dataclass class InterflowSettings(ModelObject): - __tablename__ = "interflow_settings" - __layername__ = "Interflow settings" + __tablename__ = "interflow" + __layername__ = "Interflow" __geometrytype__ = GeometryType.NoGeometry - SQLITE_SOURCES = ("v2_interflow",) - SQLITE_TARGETS = SQLITE_SOURCES RELATED_RASTERS = ( ("hydraulic_conductivity_file", "Hydraulic conductivity [m/d]"), ("porosity_file", "Porosity [-]"), @@ -1031,7 +827,6 @@ class InterflowSettings(ModelObject): impervious_layer_elevation: Optional[float] hydraulic_conductivity: Optional[float] hydraulic_conductivity_file: Optional[str] - display_name: str @dataclass @@ -1040,247 +835,170 @@ class NumericalSettings(ModelObject): __layername__ = "Numerical settings" __geometrytype__ = GeometryType.NoGeometry - SQLITE_SOURCES = ("v2_numerical_settings",) - SQLITE_TARGETS = SQLITE_SOURCES - id: int cfl_strictness_factor_1d: Optional[float] cfl_strictness_factor_2d: Optional[float] convergence_cg: Optional[float] convergence_eps: Optional[float] flow_direction_threshold: Optional[float] - frict_shallow_water_correction: Optional[int] + friction_shallow_water_depth_correction: Optional[int] general_numerical_threshold: Optional[float] - integration_method: Optional[int] - limiter_grad_1d: Optional[int] - limiter_grad_2d: Optional[int] + time_integration_method: Optional[int] + limiter_waterlevel_gradient_1d: Optional[int] + limiter_waterlevel_gradient_2d: Optional[int] limiter_slope_crossectional_area_2d: Optional[int] limiter_slope_friction_2d: Optional[int] - max_nonlin_iterations: Optional[int] - max_degree: int - minimum_friction_velocity: Optional[float] - minimum_surface_area: Optional[float] - precon_cg: Optional[int] + max_non_linear_newton_iterations: Optional[int] + max_degree_gauss_seidel: int + min_friction_velocity: Optional[float] + min_surface_area: Optional[float] + use_preconditioner_cg: Optional[int] preissmann_slot: Optional[float] pump_implicit_ratio: Optional[float] - thin_water_layer_definition: Optional[float] + limiter_slope_thin_water_layer: Optional[float] use_of_cg: int - use_of_nested_newton: int + use_nested_newton: int @dataclass -class SchemaVersion(ModelObject): - __tablename__ = "schema_version" - __layername__ = "Schema version" +class PhysicalSettings(ModelObject): + __tablename__ = "physical_settings" + __layername__ = "Physical settings" __geometrytype__ = GeometryType.NoGeometry - SQLITE_SOURCES = ("schema_version",) - SQLITE_TARGETS = SQLITE_SOURCES - - version_num: str + use_advection_1d: int + use_advection_2d: int @dataclass -class CrossSectionDefinition(ModelObject): - __tablename__ = "cross_section_definition" - __layername__ = "Cross section definition" +class SimulationTemplateSettings(ModelObject): + __tablename__ = "simulation_template_settings" + __layername__ = "Simulation template settings" __geometrytype__ = GeometryType.NoGeometry - SQLITE_SOURCES = ("v2_cross_section_definition",) - SQLITE_TARGETS = SQLITE_SOURCES - id: int - code: str - width: Optional[str] - height: Optional[str] - shape: Optional[CrossSectionShape] - friction_values: Optional[str] - vegetation_stem_densities: Optional[str] - vegetation_stem_diameters: Optional[str] - vegetation_heights: Optional[str] - vegetation_drag_coefficients: Optional[str] + name: str + use_0d_inflow: int + use_structure_control: bool @dataclass -class Timeseries(ModelObject): - __tablename__ = "timeseries" - __layername__ = "Timeseries" +class TimeStepSettings(ModelObject): + __tablename__ = "time_step_settings" + __layername__ = "Time step settings" __geometrytype__ = GeometryType.NoGeometry id: int - reference_layer: str - reference_id: int - offset: int # seconds - duration: int # seconds - value: float + max_time_step: Optional[float] + min_time_step: Optional[float] + output_time_step: Optional[float] + time_step: Optional[float] + use_time_step_stretch: bool @dataclass -class Control(ModelObject): - __tablename__ = "control" - __layername__ = "Control" +class Tag(ModelObject): + __tablename__ = "tag" + __layername__ = "Tag" __geometrytype__ = GeometryType.NoGeometry - SQLITE_SOURCES = ("v2_control",) - SQLITE_TARGETS = SQLITE_SOURCES - id: int - control_id: Optional[int] - control_group_id: Optional[int] - control_type: Optional[str] - measure_group_id: Optional[int] - measure_frequency: Optional[int] - start: Optional[str] - end: Optional[str] + description: str @dataclass -class ControlDelta(ModelObject): - __tablename__ = "control_delta" - __layername__ = "Control delta" +class CrossSectionDefinition(ModelObject): + __tablename__ = "cross_section_definition" + __layername__ = "Cross section definition" __geometrytype__ = GeometryType.NoGeometry - SQLITE_SOURCES = ("v2_control_delta",) - SQLITE_TARGETS = SQLITE_SOURCES - id: int - measure_variable: Optional[str] - measure_delta: Optional[float] - measure_dt: Optional[float] - action_type: Optional[str] - action_value: Optional[str] - action_time: Optional[float] - target_type: Optional[str] - target_id: Optional[int] - - -@dataclass -class ControlGroup(ModelObject): - __tablename__ = "control_group" - __layername__ = "Control group" - __geometrytype__ = GeometryType.NoGeometry - - SQLITE_SOURCES = ("v2_control_group",) - SQLITE_TARGETS = SQLITE_SOURCES - - id: Optional[int] - name: Optional[str] - description: Optional[str] + code: str + width: Optional[str] + height: Optional[str] + shape: Optional[CrossSectionShape] + friction_values: Optional[str] + vegetation_stem_densities: Optional[str] + vegetation_stem_diameters: Optional[str] + vegetation_heights: Optional[str] + vegetation_drag_coefficients: Optional[str] @dataclass -class ControlMeasureGroup(ModelObject): - __tablename__ = "control_measure_group" - __layername__ = "Control measure group" - __geometrytype__ = GeometryType.NoGeometry - - SQLITE_SOURCES = ("v2_control_measure_group",) - SQLITE_TARGETS = SQLITE_SOURCES +class MeasureLocation(ModelObject): + __tablename__ = "measure_location" + __layername__ = "Measure location" + __geometrytype__ = GeometryType.Point id: int + code: str + display_name: str + measure_variable: MeasureVariable + connection_node_id: int + tags: Optional[str] @dataclass -class ControlMeasureMap(ModelObject): - __tablename__ = "control_measure_map" - __layername__ = "Control measure map" - __geometrytype__ = GeometryType.NoGeometry - - SQLITE_SOURCES = ("v2_control_measure_map",) - SQLITE_TARGETS = SQLITE_SOURCES +class MeasureMap(ModelObject): + __tablename__ = "measure_map" + __layername__ = "Measure map" + __geometrytype__ = GeometryType.Linestring id: int - measure_group_id: Optional[int] - object_id: Optional[int] - object_type: Optional[str] + code: str + display_name: str weight: Optional[float] + control_measure_location_id: Optional[int] + control_id: Optional[int] + control_type: Optional[ControlType] + tags: Optional[str] @dataclass -class ControlMemory(ModelObject): - __tablename__ = "control_memory" - __layername__ = "Control memory" - __geometrytype__ = GeometryType.NoGeometry - - SQLITE_SOURCES = ("v2_control_memory",) - SQLITE_TARGETS = SQLITE_SOURCES +class MemoryControl(ModelObject): + __tablename__ = "memory_control" + __layername__ = "Memory control" + __geometrytype__ = GeometryType.Point id: Optional[int] - action_value: Optional[str] + code: str + display_name: str + action_type: Optional[str] + action_value_1: Optional[float] + action_value_2: Optional[float] is_inverse: bool - upper_threshold: Optional[float] - lower_threshold: Optional[float] - target_type: Optional[str] - measure_variable: Optional[str] is_active: bool - action_type: Optional[str] - target_id: Optional[int] - - -@dataclass -class ControlPID(ModelObject): - __tablename__ = "control_pid" - __layername__ = "Control PID" - __geometrytype__ = GeometryType.NoGeometry - - SQLITE_SOURCES = ("v2_control_pid",) - SQLITE_TARGETS = SQLITE_SOURCES - - id: Optional[int] - target_lower_limit: Optional[str] - setpoint: Optional[float] - kd: Optional[float] - ki: Optional[float] + lower_threshold: Optional[float] + upper_threshold: Optional[float] target_type: Optional[str] - measure_variable: Optional[str] - kp: Optional[float] - action_type: Optional[str] - target_upper_limit: Optional[str] target_id: Optional[int] + tags: Optional[str] @dataclass -class ControlTable(ModelObject): - __tablename__ = "control_table" - __layername__ = "Control table" - __geometrytype__ = GeometryType.NoGeometry - - SQLITE_SOURCES = ("v2_control_table",) - SQLITE_TARGETS = SQLITE_SOURCES +class TableControl(ModelObject): + __tablename__ = "table_control" + __layername__ = "Table control" + __geometrytype__ = GeometryType.Point id: Optional[int] - action_table: Optional[str] - measure_operator: Optional[str] - target_type: Optional[str] - measure_variable: Optional[str] - action_type: Optional[str] - target_id: Optional[int] - - -@dataclass -class ControlTimed(ModelObject): - __tablename__ = "control_timed" - __layername__ = "Control timed" - __geometrytype__ = GeometryType.NoGeometry - - SQLITE_SOURCES = ("v2_control_timed",) - SQLITE_TARGETS = SQLITE_SOURCES - - id: int + code: str + display_name: str action_type: Optional[str] action_table: Optional[str] target_type: Optional[str] target_id: Optional[int] + measure_variable: Optional[MeasureVariable] + measure_operator: Optional[str] + tags: Optional[str] @dataclass -class VegetationDrag(ModelObject): - __tablename__ = "vegetation_drag" - __layername__ = "Vegetation drag settings" +class VegetationDrag2D(ModelObject): + __tablename__ = "vegetation_drag_2d" + __layername__ = "2D Vegetation drag" __geometrytype__ = GeometryType.NoGeometry - SQLITE_SOURCES = ("v2_vegetation_drag",) - SQLITE_TARGETS = SQLITE_SOURCES RELATED_RASTERS = ( ("vegetation_height_file", "Vegetation height [m]"), ("vegetation_stem_count_file", "Vegetation stem count [-]"), @@ -1289,7 +1007,6 @@ class VegetationDrag(ModelObject): ) id: int - display_name: Optional[str] vegetation_height: Optional[float] vegetation_height_file: Optional[str] vegetation_stem_count: Optional[float] @@ -1303,72 +1020,80 @@ class VegetationDrag(ModelObject): MODEL_1D_ELEMENTS = ( ConnectionNode, BoundaryCondition1D, - Lateral1D, - Manhole, - Pumpstation, - PumpstationMap, + Pump, + PumpMap, Weir, - Culvert, Orifice, + Culvert, Pipe, CrossSectionLocation, Channel, + Windshielding1D, + Material, +) + +MODEL_1D2D_ELEMENTS = ( + PotentialBreach, + ExchangeLine, ) MODEL_2D_ELEMENTS = ( BoundaryCondition2D, - Lateral2D, - LinearObstacle, - GridRefinement, + Obstacle, GridRefinementArea, + GridRefinementLine, DEMAverageArea, - Windshielding, -) - -MODEL_1D2D_ELEMENTS = ( - PotentialBreach, - ExchangeLine, ) -INFLOW_ELEMENTS = ( - ImperviousSurfaceMap, - ImperviousSurface, +MODEL_0D_INFLOW_ELEMENTS = ( + Lateral1D, + Lateral2D, + DryWeatherFlowMap, + DryWeatherFlow, + DryWeatherFlowDistribution, SurfaceMap, Surface, SurfaceParameters, ) +STRUCTURE_CONTROL_ELEMENTS = ( + MeasureMap, + MeasureLocation, + MemoryControl, + TableControl, +) +HYDROLOGICAL_PROCESSES = ( + InitialConditionsSettings, + InterceptionSettings, + InterflowSettings, + GroundWaterSettings, + SimpleInfiltrationSettings, + VegetationDrag2D, +) SETTINGS_ELEMENTS = ( - GlobalSettings, + ModelSettings, AggregationSettings, - SimpleInfiltrationSettings, - GroundWaterSettings, - InterflowSettings, NumericalSettings, - SchemaVersion, - VegetationDrag, + PhysicalSettings, + SimulationTemplateSettings, + TimeStepSettings, + Tag, ) -CONTROL_STRUCTURES_ELEMENTS = ( - Control, - ControlDelta, - ControlGroup, - ControlMeasureGroup, - ControlMeasureMap, - ControlMemory, - ControlPID, - ControlTable, - ControlTimed, -) HIDDEN_ELEMENTS = tuple() -ALL_MODELS = MODEL_1D_ELEMENTS + MODEL_2D_ELEMENTS + MODEL_1D2D_ELEMENTS + INFLOW_ELEMENTS + SETTINGS_ELEMENTS -ALL_MODELS = ALL_MODELS + ( - Timeseries, - CrossSectionDefinition, +ALL_MODELS = ( + MODEL_1D_ELEMENTS + + MODEL_1D2D_ELEMENTS + + MODEL_2D_ELEMENTS + + MODEL_0D_INFLOW_ELEMENTS + + STRUCTURE_CONTROL_ELEMENTS + + HYDROLOGICAL_PROCESSES + + SETTINGS_ELEMENTS ) -ALL_MODELS = ALL_MODELS + CONTROL_STRUCTURES_ELEMENTS + HIDDEN_ELEMENTS +ALL_MODELS = ALL_MODELS + (CrossSectionDefinition,) +ALL_MODELS = ALL_MODELS + HIDDEN_ELEMENTS ELEMENTS_WITH_XS_DEF = ( Weir, @@ -1421,51 +1146,73 @@ class VegetationDrag(ModelObject): BoundaryCondition1D: ("connection_node_id",), Lateral1D: ("connection_node_id",), Channel: ( - "connection_node_start_id", - "connection_node_end_id", + "connection_node_id_start", + "connection_node_id_end", ), Culvert: ( - "connection_node_start_id", - "connection_node_end_id", + "connection_node_id_start", + "connection_node_id_end", ), - ImperviousSurfaceMap: ("connection_node_id",), + DryWeatherFlowMap: ("connection_node_id",), SurfaceMap: ("connection_node_id",), - Manhole: ("connection_node_id",), Orifice: ( - "connection_node_start_id", - "connection_node_end_id", + "connection_node_id_start", + "connection_node_id_end", ), Pipe: ( - "connection_node_start_id", - "connection_node_end_id", + "connection_node_id_start", + "connection_node_id_end", ), Weir: ( - "connection_node_start_id", - "connection_node_end_id", + "connection_node_id_start", + "connection_node_id_end", ), - PumpstationMap: ( - "connection_node_start_id", - "connection_node_end_id", + PumpMap: ( + "connection_node_id_start", + "connection_node_id_end", ), - Pumpstation: ("connection_node_id",), + Pump: ("connection_node_id",), + MeasureLocation: ("connection_node_id",), }, Channel: { CrossSectionLocation: ("channel_id",), - Windshielding: ("channel_id",), + Windshielding1D: ("channel_id",), PotentialBreach: ("channel_id",), ExchangeLine: ("channel_id",), }, - ImperviousSurface: { - ImperviousSurfaceMap: ("impervious_surface_id",), + DryWeatherFlow: { + DryWeatherFlowMap: ("dry_weather_flow_id",), }, Surface: { SurfaceMap: ("surface_id",), }, - Pumpstation: { - PumpstationMap: ("pumpstation_id",), + Pump: { + PumpMap: ("pump_id",), + MemoryControl: (("target_id", "target_type"),), + TableControl: (("target_id", "target_type"),), }, SurfaceParameters: { Surface: ("surface_parameters_id",), }, + DryWeatherFlowDistribution: { + DryWeatherFlow: ("dry_weather_flow_distribution_id",), + }, + Orifice: { + MemoryControl: (("target_id", "target_type"),), + TableControl: (("target_id", "target_type"),), + }, + Weir: { + MemoryControl: (("target_id", "target_type"),), + TableControl: (("target_id", "target_type"),), + }, + MemoryControl: { + MeasureMap: (("control_id", "control_type"),), + }, + TableControl: { + MeasureMap: (("control_id", "control_type"),), + }, + MeasureLocation: { + MeasureMap: ("control_measure_location_id",), + }, } ) diff --git a/threedi_schematisation_editor/deps/__init__.py b/threedi_schematisation_editor/deps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/threedi_schematisation_editor/deps/custom_imports.py b/threedi_schematisation_editor/deps/custom_imports.py new file mode 100644 index 0000000..ea28a43 --- /dev/null +++ b/threedi_schematisation_editor/deps/custom_imports.py @@ -0,0 +1,27 @@ +# Copyright (C) 2025 by Lutra Consulting +import os +import sys + +MAIN_DIR = os.path.dirname(os.path.abspath(__file__)) +REQUIRED_3DI_SCHEMA_VERSION = "0.230.0.dev0" +REQUIRED_3DI_MI_UTILS_VERSION = "0.1.5" +THREEDI_SCHEMA_WHEEL = os.path.join(MAIN_DIR, f"threedi_schema-{REQUIRED_3DI_SCHEMA_VERSION}-py3-none-any.whl") +MI_UTILS_WHEEL = os.path.join(MAIN_DIR, f"threedi_mi_utils-{REQUIRED_3DI_MI_UTILS_VERSION}-py3-none-any.whl") + + +def patch_wheel_imports(): + """ + Function that tests if extra modules are installed. + If modules are not available then it will add missing modules wheels to the Python path. + """ + try: + import threedi_schema + except ImportError: + deps_path = THREEDI_SCHEMA_WHEEL + sys.path.append(deps_path) + + try: + import threedi_mi_utils + except ImportError: + deps_path = MI_UTILS_WHEEL + sys.path.append(deps_path) diff --git a/threedi_schematisation_editor/deps/threedi_mi_utils-0.1.5-py3-none-any.whl b/threedi_schematisation_editor/deps/threedi_mi_utils-0.1.5-py3-none-any.whl new file mode 100644 index 0000000..3069dd1 Binary files /dev/null and b/threedi_schematisation_editor/deps/threedi_mi_utils-0.1.5-py3-none-any.whl differ diff --git a/threedi_schematisation_editor/deps/threedi_schema-0.230.0.dev0-py3-none-any.whl b/threedi_schematisation_editor/deps/threedi_schema-0.230.0.dev0-py3-none-any.whl new file mode 100644 index 0000000..100592e Binary files /dev/null and b/threedi_schematisation_editor/deps/threedi_schema-0.230.0.dev0-py3-none-any.whl differ diff --git a/threedi_schematisation_editor/enumerators.py b/threedi_schematisation_editor/enumerators.py index d204aa4..c0000eb 100644 --- a/threedi_schematisation_editor/enumerators.py +++ b/threedi_schematisation_editor/enumerators.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 by Lutra Consulting +# Copyright (C) 2025 by Lutra Consulting from enum import Enum @@ -27,8 +27,8 @@ class FlowVariable(Enum): FLOW_VELOCITY = "flow_velocity" PUMP_DISCHARGE = "pump_discharge" RAIN = "rain" - WATER_LEVEL = "waterlevel" - WET_CROSS_SECTIONAL_AREA = "wet_cross-section" + WATER_LEVEL = "water_level" + WET_CROSS_SECTIONAL_AREA = "wet_cross_section" WET_SURFACE_AREA = "wet_surface" LATERAL_DISCHARGE = "lateral_discharge" VOLUME = "volume" @@ -49,21 +49,27 @@ class AggregationMethod(Enum): CURRENT = "current" -class CalculationType(Enum): +class ExchangeTypeChannel(Enum): EMBEDDED = 100 ISOLATED = 101 CONNECTED = 102 DOUBLE_CONNECTED = 105 -class CalculationTypeCulvert(Enum): +class ExchangeTypeCulvert(Enum): EMBEDDED = 100 ISOLATED = 101 CONNECTED = 102 DOUBLE_CONNECTED = 105 -class CalculationTypeNode(Enum): +class ExchangeTypeNode(Enum): + EMBEDDED = 0 + ISOLATED = 1 + CONNECTED = 2 + + +class ExchangeTypePipe(Enum): EMBEDDED = 0 ISOLATED = 1 CONNECTED = 2 @@ -86,10 +92,11 @@ class ManholeShape(Enum): RECTANGLE = "02" -class ManholeIndicator(Enum): +class Visualisation(Enum): INSPECTION = 0 OUTLET = 1 PUMP = 2 + OTHER = 99 class FrictionType(Enum): @@ -132,7 +139,7 @@ class InterflowType(Enum): GLOBAL_DEEPEST_POINT_CONSTANT_POROSITY = 4 -class Material(Enum): +class LeveeMaterial(Enum): SAND = 1 CLAY = 2 @@ -156,12 +163,6 @@ class CrestType(Enum): SHORT_CRESTED = 4 -class PipeCalculationType(Enum): - EMBEDDED = 0 - ISOLATED = 1 - CONNECTED = 2 - - class SewerageType(Enum): COMBINED_SEWER = 0 STORM_DRAIN = 1 @@ -193,3 +194,23 @@ class ZoomCategories(Enum): MEDIUM_VISIBILITY = 3 HIGH_VISIBILITY = 4 HIGHEST_VISIBILITY = 5 + + +class TimeUnit(Enum): + SECONDS = "seconds" + MINUTES = "minutes" + HOURS = "hours" + + +class Unit(Enum): + M3_SECONDS = "m3/s" + + +class MeasureVariable(Enum): + S1 = "s1" + VOL1 = "vol1" + + +class ControlType(Enum): + TABLE = "table" + MEMORY = "memory" diff --git a/threedi_schematisation_editor/forms/__init__.py b/threedi_schematisation_editor/forms/__init__.py index c39d9b3..7e8abda 100644 --- a/threedi_schematisation_editor/forms/__init__.py +++ b/threedi_schematisation_editor/forms/__init__.py @@ -1 +1 @@ -# Copyright (C) 2023 by Lutra Consulting +# Copyright (C) 2025 by Lutra Consulting diff --git a/threedi_schematisation_editor/forms/custom_forms.py b/threedi_schematisation_editor/forms/custom_forms.py index 574ca4a..df4df8e 100644 --- a/threedi_schematisation_editor/forms/custom_forms.py +++ b/threedi_schematisation_editor/forms/custom_forms.py @@ -1,8 +1,8 @@ -# Copyright (C) 2023 by Lutra Consulting +# Copyright (C) 2025 by Lutra Consulting import sys from collections import defaultdict from enum import Enum -from functools import partial +from functools import cached_property, partial from operator import itemgetter from types import MappingProxyType @@ -51,11 +51,12 @@ ) -class BaseForm(QObject): +class AbstractBaseForm(QObject): """Base edit form for user layers edit form logic.""" MODEL = None MIN_FID = -sys.maxsize - 1 + AUTOGENERATE_ID = "Autogenerate" def __init__(self, layer_manager, dialog, layer, feature): super().__init__(parent=dialog) # We need to set dialog as a parent to keep form alive @@ -89,7 +90,6 @@ def foreign_models_features(self): def setup_form_widgets(self): """Setting up all form widgets.""" - # TODO: Improve handling of newly added related features for field, customisation_fn in self.handler.FORM_CUSTOMIZATIONS.items(): widget = self.dialog.findChild(QObject, field) customisation_fn(widget) @@ -101,11 +101,12 @@ def setup_form_widgets(self): if not geometry: return # form open for an invalid feature else: - if self.feature["id"] is not None: + if self.feature["id"] != self.AUTOGENERATE_ID: # This is the case after accepting new feature return self.creation = True self.handler.set_feature_values(self.feature) + self.activate_field_based_conditions() self.toggle_edit_mode() self.connect_foreign_widgets() @@ -163,8 +164,8 @@ def populate_widgets(self, data_model_cls=None, feature=None, start_end_modifier """ Populate form's widgets - widgets are named after their attributes in the data model. If data_model_cls is given, then populate widgets for this class and feature. - start_end_modifier is used when there are multiple features edited in the form, for example two manholes in - a pipe form. The modifier should be 1 for starting point and 2 for ending. + start_end_modifier is used when there are multiple features edited in the form, for example two connection nodes + in a pipe form. The modifier should be 1 for starting point and 2 for ending. """ if data_model_cls is not None: field_name_prefix = data_model_cls.__tablename__ + "_" @@ -392,7 +393,332 @@ def activate_field_based_conditions(self): pass -class FormWithXSTable(BaseForm): +class AbstractFormWithTag(AbstractBaseForm): + """Base edit form for user layers with tags table reference.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, *kwargs) + self.tags = None + + def setup_form_widgets(self): + """Setting up all form widgets.""" + super().setup_form_widgets() + self.setup_tag_widgets() + + def setup_tag_widgets(self): + """Setup tag widgets.""" + self.tags = self.dialog.findChild(QObject, "tag_descriptions") + self.tags.clear() + try: + tag_ids_str = self.feature["tags"] + except KeyError: + return + if tag_ids_str: + tag_ids = [int(tag_id) for tag_id in tag_ids_str.split(",")] + tags_handler = self.layer_manager.model_handlers[dm.Tag] + tags_layer = tags_handler.layer + tag_descriptions = [tag_feat["description"] for tag_feat in tags_layer.getFeatures(tag_ids)] + self.tags.setText(", ".join(tag_descriptions)) + + +class AbstractFormWithDistribution(AbstractBaseForm): + """Base edit form for user layers with distribution table.""" + + NUMBER_OF_ROWS = 24 + + def __init__(self, *args, **kwargs): + super().__init__(*args, *kwargs) + self.distribution_table_field_name = "distribution" + self.distribution_table = None + self.distribution_table_clear = None + self.setup_distribution_table_widgets() + + @property + def table_header(self): + return ["%"] + + def setup_form_widgets(self): + """Setting up all form widgets.""" + super().setup_form_widgets() + self.update_distribution_table_header() + + def setup_distribution_table_widgets(self): + """Setup distribution widgets.""" + distribution_table_widget_name = "distribution_table" + self.distribution_table = self.dialog.findChild(QTableWidget, distribution_table_widget_name) + self.distribution_table_clear = self.dialog.findChild(QPushButton, f"{distribution_table_widget_name}_clear") + for widget in [self.distribution_table, self.distribution_table_clear]: + self.custom_widgets[widget.objectName()] = self.distribution_table + + def connect_custom_widgets(self): + """Connect other widgets.""" + super().connect_custom_widgets() + connect_signal(self.distribution_table.cellChanged, self.save_distribution_table_edits) + self.dialog.active_form_signals.add((self.distribution_table.cellChanged, self.save_distribution_table_edits)) + connect_signal(self.distribution_table_clear.clicked, self.clear_table_row_values) + self.dialog.active_form_signals.add((self.distribution_table_clear.clicked, self.clear_table_row_values)) + + def update_distribution_table_header(self): + """Update distribution table headers.""" + self.distribution_table.setHorizontalHeaderLabels(self.table_header) + + def get_distribution_table_values(self): + """Get distribution table values.""" + distribution_table_values = [] + for row_num in range(self.NUMBER_OF_ROWS): + item = self.distribution_table.item(row_num, 0) + row_value = item.text().strip() if item is not None else "" + distribution_table_values.append(row_value) + return distribution_table_values + + def get_distribution_table_text(self): + """Get distribution table data as a string representation.""" + distribution_table_values = self.get_distribution_table_values() + distribution_table_str = ",".join(row for row in distribution_table_values) + return distribution_table_str + + def save_distribution_table_edits(self): + """Slot for handling table cells edits.""" + distribution_table_str = self.get_distribution_table_text() + if self.creation is True: + self.feature[self.distribution_table_field_name] = distribution_table_str + else: + distribution_table_idx = self.layer.fields().lookupField(self.distribution_table_field_name) + changes = {distribution_table_idx: distribution_table_str} + self.layer.changeAttributeValues(self.feature.id(), changes) + + def clear_table_row_values(self): + """Slot for clearing table values.""" + disconnect_signal(self.distribution_table.cellChanged, self.save_distribution_table_edits) + for row_num in range(self.NUMBER_OF_ROWS): + self.distribution_table.setItem(row_num, 0, QTableWidgetItem("")) + connect_signal(self.distribution_table.cellChanged, self.save_distribution_table_edits) + self.save_distribution_table_edits() + + def populate_distribution_table_data(self): + """Populate distribution tabular data in the table widget.""" + disconnect_signal(self.distribution_table.cellChanged, self.save_distribution_table_edits) + self.distribution_table.clearContents() + self.distribution_table.setRowCount(self.NUMBER_OF_ROWS) + self.distribution_table.setColumnCount(1) + self.update_distribution_table_header() + self.distribution_table.setItemDelegateForColumn(0, NumericItemDelegate(self.distribution_table)) + if self.feature is not None: + table = self.feature[self.distribution_table_field_name] or "" + else: + table = "" + for row_number, row_value in enumerate(table.split(",")): + self.distribution_table.setItem(row_number, 0, QTableWidgetItem(row_value)) + connect_signal(self.distribution_table.cellChanged, self.save_distribution_table_edits) + + def populate_with_extra_widgets(self): + """Populate widgets for other layers attributes.""" + if self.creation is True: + self.fill_related_attributes() + self.populate_widgets() + self.populate_flow_distribution_table_data() + + +class AbstractFormWithTable(AbstractBaseForm): + """Base edit form for user layers with table.""" + + TABLE_NAME = "" + ROW_SEPARATOR = "\n" + COLUMN_SEPARATOR = "," + + def __init__(self, *args, **kwargs): + super().__init__(*args, *kwargs) + self.table = None + self.table_add = None + self.table_delete = None + self.table_copy = None + self.table_paste = None + self.setup_table_widgets() + + @property + def table_header(self): + """Return table header.""" + raise NotImplementedError("Table header not implemented.") + + def setup_form_widgets(self): + """Setting up all form widgets.""" + super().setup_form_widgets() + self.update_table_header() + + def setup_table_widgets(self): + """Setup timeseries widgets.""" + table_widget_name = f"{self.TABLE_NAME}_table" + self.table = self.dialog.findChild(QTableWidget, table_widget_name) + self.table_add = self.dialog.findChild(QPushButton, f"{table_widget_name}_add") + self.table_delete = self.dialog.findChild(QPushButton, f"{table_widget_name}_delete") + self.table_copy = self.dialog.findChild(QPushButton, f"{table_widget_name}_copy") + self.table_paste = self.dialog.findChild(QPushButton, f"{table_widget_name}_paste") + for widget in [ + self.table, + self.table_add, + self.table_delete, + self.table_copy, + self.table_paste, + ]: + self.custom_widgets[widget.objectName()] = widget + + def connect_custom_widgets(self): + """Connect other widgets.""" + super().connect_custom_widgets() + connect_signal(self.table.cellChanged, self.save_table_edits) + self.dialog.active_form_signals.add((self.table.cellChanged, self.save_table_edits)) + connect_signal(self.table_add.clicked, self.add_table_row) + self.dialog.active_form_signals.add((self.table_add.clicked, self.add_table_row)) + connect_signal(self.table_delete.clicked, self.delete_table_rows) + self.dialog.active_form_signals.add((self.table_delete.clicked, self.delete_table_rows)) + connect_signal(self.table_paste.clicked, self.paste_table_rows) + self.dialog.active_form_signals.add((self.table_paste.clicked, self.paste_table_rows)) + connect_signal(self.table_copy.clicked, self.copy_table_rows) + self.dialog.active_form_signals.add((self.table_copy.clicked, self.copy_table_rows)) + + def update_table_header(self): + """Update table headers.""" + self.table.setHorizontalHeaderLabels(self.table_header) + + def get_table_values(self): + """Get table values.""" + num_of_rows = self.table.rowCount() + num_of_cols = self.table.columnCount() + table_values = [] + for row_num in range(num_of_rows): + row_values = [] + for col_num in range(num_of_cols): + item = self.table.item(row_num, col_num) + if item is not None: + item_text = item.text().strip() + else: + item_text = "" + row_values.append(item_text) + table_values.append(row_values) + return table_values + + def get_table_text(self): + """Get table data as a string representation.""" + table_values = self.get_table_values() + table_str = self.ROW_SEPARATOR.join(self.COLUMN_SEPARATOR.join(row) for row in table_values if all(row)) + return table_str + + def save_table_edits(self): + """Slot for handling table cells edits.""" + table_str = self.get_table_text() + if self.creation is True: + self.feature[self.TABLE_NAME] = table_str + else: + table_idx = self.layer.fields().lookupField(self.TABLE_NAME) + changes = {table_idx: table_str} + self.layer.changeAttributeValues(self.feature.id(), changes) + + def add_table_row(self): + """Slot for handling new row addition.""" + selected_rows = {idx.row() for idx in self.table.selectedIndexes()} + if selected_rows: + last_row_number = max(selected_rows) + 1 + else: + last_row_number = self.table.rowCount() + self.table.insertRow(last_row_number) + + def delete_table_rows(self): + """Slot for handling deletion of the selected rows.""" + selected_rows = {idx.row() for idx in self.table.selectedIndexes()} + for row_number in sorted(selected_rows, reverse=True): + self.table.removeRow(row_number) + self.save_table_edits() + + def paste_table_rows(self): + """Handling pasting new rows from the clipboard.""" + text = QApplication.clipboard().text() + rows = text.split(self.ROW_SEPARATOR) + last_row_num = self.table.rowCount() + disconnect_signal(self.table.cellChanged, self.save_table_edits) + for row in rows: + try: + height_str, width_str = row.replace(" ", "").split(self.COLUMN_SEPARATOR) + except ValueError: + continue + self.table.insertRow(last_row_num) + self.table.setItem(last_row_num, 0, QTableWidgetItem(height_str)) + self.table.setItem(last_row_num, 1, QTableWidgetItem(width_str)) + last_row_num += 1 + connect_signal(self.table.cellChanged, self.save_table_edits) + self.save_table_edits() + + def copy_table_rows(self): + """Slot for copying table values into the clipboard.""" + table_values = self.get_table_values() + clipboard_values = "\n".join([",".join(row_values) for row_values in table_values]) + QApplication.clipboard().setText(clipboard_values) + + def clear_table_row_values(self): + """Slot for clearing table values.""" + num_of_rows = self.table.rowCount() + num_of_cols = self.table.columnCount() + disconnect_signal(self.table.cellChanged, self.save_table_edits) + for row_num in range(num_of_rows): + for col_num in range(num_of_cols): + self.table.setItem(row_num, col_num, QTableWidgetItem("")) + connect_signal(self.table.cellChanged, self.save_table_edits) + self.save_table_edits() + + def populate_table_data(self): + """Populate timeseries tabular data in the table widget.""" + disconnect_signal(self.table.cellChanged, self.save_table_edits) + table = self.feature[self.TABLE_NAME] or "" + number_of_rows_main = len(table.split(self.ROW_SEPARATOR)) + table_columns_count = len(self.table_header) + self.table.clearContents() + self.table.setRowCount(0) + self.table.setColumnCount(table_columns_count) + self.update_table_header() + for column_idx in range(table_columns_count): + self.table.setItemDelegateForColumn(column_idx, NumericItemDelegate(self.table)) + for row_num_main in range(number_of_rows_main): + self.table.insertRow(row_num_main) + if self.feature is not None: + table = self.feature[self.TABLE_NAME] or "" + else: + table = "" + for row_number, row in enumerate(table.split(self.ROW_SEPARATOR)): + row_values = [val for val in row.replace(" ", "").split(self.COLUMN_SEPARATOR) if val] + if len(row_values) != table_columns_count: + continue + for col_idx, row_value in enumerate(row_values): + self.table.setItem(row_number, col_idx, QTableWidgetItem(row_value)) + connect_signal(self.table.cellChanged, self.save_table_edits) + + def populate_with_extra_widgets(self): + """Populate widgets for other layers attributes.""" + if self.creation is True: + self.fill_related_attributes() + self.populate_widgets() + self.populate_table_data() + + +class AbstractFormWithTimeseries(AbstractFormWithTable): + """Base edit form for user layers with timeseries table.""" + + TABLE_NAME = "timeseries" + + @property + def table_header(self): + return ["Time", "Value"] + + +class AbstractFormWithActionTable(AbstractFormWithTable): + """Base edit form for user layers with action table.""" + + TABLE_NAME = "action_table" + + @property + def table_header(self): + return ["Measured value", "Action value 1", "Action value 2"] + + +class AbstractFormWithXSTable(AbstractBaseForm): """Base edit form for user layers with cross-section table reference.""" def __init__(self, *args, **kwargs): @@ -439,7 +765,7 @@ def __init__(self, *args, **kwargs): def cross_section_table_field_widget_map(self): field_map = { "cross_section_table": self.cross_section_table, - "cross_section_friction_table": self.cross_section_friction, + "cross_section_friction_values": self.cross_section_friction, "cross_section_vegetation_table": self.cross_section_vegetation, } return field_map @@ -538,11 +864,11 @@ def connect_custom_widgets(self): cross_section_vegetation_clear_signal = self.cross_section_vegetation_clear.clicked cross_section_friction_copy_signal = self.cross_section_friction_copy.clicked cross_section_vegetation_copy_signal = self.cross_section_vegetation_copy.clicked - cross_section_friction_edit_slot = partial(self.edit_table_row, "cross_section_friction_table") + cross_section_friction_edit_slot = partial(self.edit_table_row, "cross_section_friction_values") cross_section_vegetation_edit_slot = partial(self.edit_table_row, "cross_section_vegetation_table") - cross_section_friction_clear_slot = partial(self.clear_table_row_values, "cross_section_friction_table") + cross_section_friction_clear_slot = partial(self.clear_table_row_values, "cross_section_friction_values") cross_section_vegetation_clear_slot = partial(self.clear_table_row_values, "cross_section_vegetation_table") - cross_section_friction_copy_slot = partial(self.copy_table_rows, "cross_section_friction_table") + cross_section_friction_copy_slot = partial(self.copy_table_rows, "cross_section_friction_values") cross_section_vegetation_copy_slot = partial(self.copy_table_rows, "cross_section_vegetation_table") connect_signal(cross_section_friction_edit_signal, cross_section_friction_edit_slot) connect_signal(cross_section_vegetation_edit_signal, cross_section_vegetation_edit_slot) @@ -586,7 +912,7 @@ def get_cross_section_table_header(self, table_field_name): table_header += ["Y [m]", "Z [m]"] else: table_header += ["Height [m]", "Width [m]"] - elif table_field_name == "cross_section_friction_table": + elif table_field_name == "cross_section_friction_values": table_header += ["Friction coefficient"] elif table_field_name == "cross_section_vegetation_table": table_header += ["Stem density [m-2]", "Stem diameter [m]", "Height [m]", "Drag coefficient [-]"] @@ -662,7 +988,7 @@ def delete_table_rows(self): self.cross_section_vegetation.removeRow(frict_vege_last_row_number) self.save_cross_section_table_edits() if self.MODEL in [dm.CrossSectionLocation, dm.Channel]: - self.save_cross_section_table_edits("cross_section_friction_table") + self.save_cross_section_table_edits("cross_section_friction_values") self.save_cross_section_table_edits("cross_section_vegetation_table") def paste_table_rows(self): @@ -779,7 +1105,7 @@ def activate_field_based_conditions(self): self.dialog.active_form_signals.add((friction_edit_signal, friction_edit_slot)) -class FormWithNode(BaseForm): +class AbstractFormWithNode(AbstractBaseForm): """Base edit form for user layers with a single connection node.""" def __init__(self, *args, **kwargs): @@ -831,7 +1157,7 @@ def populate_with_extra_widgets(self): self.populate_widgets() -class FormWithStartEndNode(BaseForm): +class AbstractFormWithStartEndNode(AbstractBaseForm): """Base edit form for user layers start and end connection nodes.""" def __init__(self, *args, **kwargs): @@ -851,10 +1177,10 @@ def foreign_models_features(self): def setup_connection_nodes_on_edit(self): """Setting up connection nodes during editing feature.""" connection_node_handler = self.layer_manager.model_handlers[dm.ConnectionNode] - connection_node_start_id = self.feature["connection_node_start_id"] - connection_node_end_id = self.feature["connection_node_end_id"] - self.connection_node_start = connection_node_handler.get_feat_by_id(connection_node_start_id) - self.connection_node_end = connection_node_handler.get_feat_by_id(connection_node_end_id) + connection_node_id_start = self.feature["connection_node_id_start"] + connection_node_id_end = self.feature["connection_node_id_end"] + self.connection_node_start = connection_node_handler.get_feat_by_id(connection_node_id_start) + self.connection_node_end = connection_node_handler.get_feat_by_id(connection_node_id_end) def setup_connection_nodes_on_creation(self): """Setting up connection nodes during adding feature.""" @@ -895,8 +1221,8 @@ def setup_connection_nodes_on_creation(self): def fill_related_attributes(self): """Filling feature values based on related features attributes.""" super().fill_related_attributes() - self.feature["connection_node_start_id"] = self.connection_node_start["id"] - self.feature["connection_node_end_id"] = self.connection_node_end["id"] + self.feature["connection_node_id_start"] = self.connection_node_start["id"] + self.feature["connection_node_id_end"] = self.connection_node_end["id"] code_display_name = f"{self.connection_node_start['code']}-{self.connection_node_end['code']}" try: self.feature["code"] = code_display_name @@ -917,14 +1243,14 @@ def populate_with_extra_widgets(self): self.populate_widgets() -class NodeToSurfaceMapForm(BaseForm): +class AbstractNodeToSurfaceMapForm(AbstractFormWithTag): """Basic surface to node map edit form logic.""" def __init__(self, *args, **kwargs): super().__init__(*args, *kwargs) self.surface_model = None self.surface_id_field = None - self.surface = None + self.surface_feature = None def fill_related_attributes(self): """Filling feature values based on related features attributes.""" @@ -939,15 +1265,15 @@ def fill_related_attributes(self): connection_node = connection_node_feat if connection_node is not None: self.feature["connection_node_id"] = connection_node["id"] - if self.surface is None: - self.surface = find_point_polygons(start_point, surface_layer) - if self.surface is not None: - self.feature[self.surface_id_field] = self.surface["id"] + if self.surface_feature is None: + self.surface_feature = find_point_polygons(start_point, surface_layer) + if self.surface_feature is not None: + self.feature[self.surface_id_field] = self.surface_feature["id"] def populate_with_extra_widgets(self): """Populate widgets for other layers attributes.""" if self.creation is True: - self.surface = self.select_start_surface() + self.surface_feature = self.select_start_surface() self.fill_related_attributes() self.populate_widgets() @@ -974,31 +1300,88 @@ def select_start_surface(self): return surface_feat -class ConnectionNodeForm(BaseForm): - """Connection node edit form logic.""" +class AbstractFormWithTargetStructure(AbstractBaseForm): + """Base edit form for user layers with a single structure.""" - MODEL = dm.ConnectionNode + def __init__(self, *args, **kwargs): + super().__init__(*args, *kwargs) + self.structure_feature = None + self.structure_feature_type = None + + def fill_related_attributes(self): + """Filling feature values based on related features attributes.""" + super().fill_related_attributes() + if self.structure_feature is not None: + self.feature["target_type"] = self.structure_feature_type + self.feature["target_id"] = self.structure_feature["id"] + + @cached_property + def target_structure_types(self): + """Return supported target structure type data models.""" + target_structure_data_models = { + model_cls.__tablename__: model_cls for model_cls in [dm.Weir, dm.Orifice, dm.Pump] + } + return target_structure_data_models + + def setup_target_structure_on_edit(self): + """Setting up target structure during editing feature.""" + try: + target_structure_type = self.target_structure_types[self.feature["target_type"]] + model_cls = self.target_structure_types[target_structure_type] + structure_handler = self.layer_manager.model_handlers[model_cls] + structure_feat = structure_handler.get_feat_by_id(self.feature["target_id"]) + except KeyError: + return + self.structure_feature = structure_feat + self.structure_feature_type = target_structure_type + + def setup_target_structure_on_creation(self): + """Setting up target structure during adding feature.""" + feature_point = self.feature.geometry().asPoint() + for structure_type, model_cls in self.target_structure_types.items(): + structure_handler = self.layer_manager.model_handlers[model_cls] + structure_layer = structure_handler.layer + dm_geometry_type = model_cls.__geometrytype__ + if dm_geometry_type == en.GeometryType.Point: + structure_feat = find_point_nodes(feature_point, structure_layer) + elif dm_geometry_type == en.GeometryType.Linestring: + structure_feat = find_point_polyline(feature_point, structure_layer) + else: + continue + if structure_feat is not None: + self.structure_feature, self.structure_feature_type = structure_feat, structure_type + break def populate_with_extra_widgets(self): + """Populate widgets for other layers attributes.""" + if self.creation is True: + self.setup_target_structure_on_creation() + # Set feature specific attributes + self.fill_related_attributes() + else: + self.setup_target_structure_on_edit() # Populate widgets based on features attributes + self.populate_foreign_widgets() self.populate_widgets() -class ManholeForm(FormWithNode): - """Manhole user layer edit form logic.""" +class ConnectionNodeForm(AbstractFormWithTag): + """Connection node edit form logic.""" - MODEL = dm.Manhole + MODEL = dm.ConnectionNode + + def populate_with_extra_widgets(self): + # Populate widgets based on features attributes + self.populate_widgets() -class PipeForm(FormWithStartEndNode, FormWithXSTable): +class PipeForm(AbstractFormWithStartEndNode, AbstractFormWithXSTable, AbstractFormWithTag): """Pipe user layer edit form logic.""" MODEL = dm.Pipe def __init__(self, *args, **kwargs): super().__init__(*args, *kwargs) - self.manhole_start = None - self.manhole_end = None @property def foreign_models_features(self): @@ -1006,102 +1389,32 @@ def foreign_models_features(self): fm_features = { (dm.ConnectionNode, 1): self.connection_node_start, (dm.ConnectionNode, 2): self.connection_node_end, - (dm.Manhole, 1): self.manhole_start, - (dm.Manhole, 2): self.manhole_end, } return fm_features - def setup_manholes_on_edit(self): - """Setting up manholes during editing feature.""" - connection_node_handler = self.layer_manager.model_handlers[dm.ConnectionNode] - connection_node_start_id = self.feature["connection_node_start_id"] - connection_node_end_id = self.feature["connection_node_end_id"] - self.connection_node_start = connection_node_handler.get_feat_by_id(connection_node_start_id) - self.connection_node_end = connection_node_handler.get_feat_by_id(connection_node_end_id) - self.manhole_start = connection_node_handler.get_manhole_feat_for_node_id(connection_node_start_id) - self.manhole_end = connection_node_handler.get_manhole_feat_for_node_id(connection_node_end_id) - - def setup_manholes_on_creation(self): - """Setting up manholes during adding feature.""" - connection_node_handler = self.layer_manager.model_handlers[dm.ConnectionNode] - manhole_handler = self.layer_manager.model_handlers[dm.Manhole] - manhole_layer = manhole_handler.layer - linestring = self.feature.geometry().asPolyline() - start_point, end_point = linestring[0], linestring[-1] - start_manhole_feat, end_manhole_feat = find_linestring_nodes(linestring, manhole_layer) - if start_manhole_feat is not None and end_manhole_feat is None: - start_connection_node_id = start_manhole_feat["connection_node_id"] - start_connection_node_feat = connection_node_handler.get_feat_by_id(start_connection_node_id) - # Create and add ending points - end_geom = QgsGeometry.fromPointXY(end_point) - end_manhole_feat, end_connection_node_feat = manhole_handler.create_manhole_with_connection_node( - end_geom, template_feat=start_manhole_feat - ) - self.extra_features[connection_node_handler].append(end_connection_node_feat) - self.extra_features[manhole_handler].append(end_manhole_feat) - elif start_manhole_feat is None and end_manhole_feat is not None: - end_connection_node_id = end_manhole_feat["connection_node_id"] - end_connection_node_feat = connection_node_handler.get_feat_by_id(end_connection_node_id) - # Create and add starting points - start_geom = QgsGeometry.fromPointXY(start_point) - start_manhole_feat, start_connection_node_feat = manhole_handler.create_manhole_with_connection_node( - start_geom, template_feat=end_manhole_feat - ) - self.extra_features[connection_node_handler].append(start_connection_node_feat) - self.extra_features[manhole_handler].append(start_manhole_feat) - elif start_manhole_feat is None and end_manhole_feat is None: - # Create and add starting points - start_geom = QgsGeometry.fromPointXY(start_point) - start_manhole_feat, start_connection_node_feat = manhole_handler.create_manhole_with_connection_node( - start_geom - ) - self.extra_features[connection_node_handler].append(start_connection_node_feat) - self.extra_features[manhole_handler].append(start_manhole_feat) - # Create and add ending points - end_geom = QgsGeometry.fromPointXY(end_point) - end_manhole_feat, end_connection_node_feat = manhole_handler.create_manhole_with_connection_node(end_geom) - self.extra_features[connection_node_handler].append(end_connection_node_feat) - self.extra_features[manhole_handler].append(end_manhole_feat) - else: - start_connection_node_id = start_manhole_feat["connection_node_id"] - start_connection_node_feat = connection_node_handler.get_feat_by_id(start_connection_node_id) - end_connection_node_id = end_manhole_feat["connection_node_id"] - end_connection_node_feat = connection_node_handler.get_feat_by_id(end_connection_node_id) - - # Sequence related features ids - self.sequence_related_features_ids() - # Reassign manholes connection_node_id after sequencing - start_manhole_feat["connection_node_id"] = start_connection_node_feat["id"] - end_manhole_feat["connection_node_id"] = end_connection_node_feat["id"] - # Assign features as a form instance attributes. - self.connection_node_start = start_connection_node_feat - self.connection_node_end = end_connection_node_feat - self.manhole_start = start_manhole_feat - self.manhole_end = end_manhole_feat - def fill_related_attributes(self): """Filling feature values based on related features attributes.""" super().fill_related_attributes() - code_display_name = f"{self.manhole_start['code']}-{self.manhole_end['code']}" + code_display_name = f"{self.connection_node_start['code']}-{self.connection_node_end['code']}" self.feature["code"] = code_display_name self.feature["display_name"] = code_display_name - self.feature["invert_level_start_point"] = self.manhole_start["bottom_level"] - self.feature["invert_level_end_point"] = self.manhole_end["bottom_level"] + self.feature["invert_level_start"] = self.connection_node_start["bottom_level"] + self.feature["invert_level_end"] = self.connection_node_end["bottom_level"] def populate_with_extra_widgets(self): """Populate widgets for other layers attributes.""" if self.creation is True: - self.setup_manholes_on_creation() + self.setup_connection_nodes_on_creation() self.fill_related_attributes() else: - self.setup_manholes_on_edit() + self.setup_connection_nodes_on_edit() # Populate widgets based on features attributes self.populate_foreign_widgets() self.populate_widgets() self.populate_cross_section_table_data() -class WeirForm(FormWithStartEndNode, FormWithXSTable): +class WeirForm(AbstractFormWithStartEndNode, AbstractFormWithXSTable, AbstractFormWithTag): """Weir user layer edit form logic.""" MODEL = dm.Weir @@ -1135,7 +1448,7 @@ def populate_with_extra_widgets(self): self.populate_cross_section_table_data() -class CulvertForm(FormWithStartEndNode, FormWithXSTable): +class CulvertForm(AbstractFormWithStartEndNode, AbstractFormWithXSTable, AbstractFormWithTag): """Culvert user layer edit form logic.""" MODEL = dm.Culvert @@ -1169,7 +1482,7 @@ def populate_with_extra_widgets(self): self.populate_cross_section_table_data() -class OrificeForm(FormWithStartEndNode, FormWithXSTable): +class OrificeForm(AbstractFormWithStartEndNode, AbstractFormWithXSTable, AbstractFormWithTag): """Orifice user layer edit form logic.""" MODEL = dm.Orifice @@ -1203,63 +1516,59 @@ def populate_with_extra_widgets(self): self.populate_cross_section_table_data() -class PumpstationForm(FormWithNode): - """Pumpstation without end node user layer edit form logic.""" +class PumpForm(AbstractFormWithNode, AbstractFormWithTag): + """Pump without end node user layer edit form logic.""" - MODEL = dm.Pumpstation + MODEL = dm.Pump def __init__(self, *args, **kwargs): super().__init__(*args, *kwargs) -class PumpstationMapForm(FormWithStartEndNode): - """Pumpstation with end node user layer edit form logic.""" +class PumpMapForm(AbstractFormWithTag): + """Pump with end node user layer edit form logic.""" - MODEL = dm.PumpstationMap + MODEL = dm.PumpMap def __init__(self, *args, **kwargs): super().__init__(*args, *kwargs) - self.pumpstation = None + self.connection_node_end = None + self.pump = None @property def foreign_models_features(self): """Property returning dictionary where key = data model class with identifier and value = data model feature.""" fm_features = { - (dm.ConnectionNode, 1): self.connection_node_start, - (dm.ConnectionNode, 2): self.connection_node_end, - (dm.Pumpstation, None): self.pumpstation, + (dm.ConnectionNode, None): self.connection_node_end, + (dm.Pump, None): self.pump, } return fm_features - def setup_pumpstation_on_edit(self): - """Setting up pumpstation during editing feature.""" + def setup_pump_on_edit(self): + """Setting up pump during editing feature.""" connection_node_handler = self.layer_manager.model_handlers[dm.ConnectionNode] - pumpstation_handler = self.layer_manager.model_handlers[dm.Pumpstation] - connection_node_start_id = self.feature["connection_node_start_id"] - connection_node_end_id = self.feature["connection_node_end_id"] - pumpstation_id = self.feature["pumpstation_id"] - self.connection_node_start = connection_node_handler.get_feat_by_id(connection_node_start_id) - self.connection_node_end = connection_node_handler.get_feat_by_id(connection_node_end_id) - self.pumpstation = pumpstation_handler.get_feat_by_id(pumpstation_id) - - def setup_pumpstation_on_creation(self): - """Setting up pumpstation during adding feature.""" + pump_handler = self.layer_manager.model_handlers[dm.Pump] + connection_node_id_end = self.feature["connection_node_id_end"] + pump_id = self.feature["pump_id"] + self.connection_node_end = connection_node_handler.get_feat_by_id(connection_node_id_end) + self.pump = pump_handler.get_feat_by_id(pump_id) + + def setup_pump_on_creation(self): + """Setting up pump during adding feature.""" connection_node_handler = self.layer_manager.model_handlers[dm.ConnectionNode] - pumpstation_handler = self.layer_manager.model_handlers[dm.Pumpstation] + pump_handler = self.layer_manager.model_handlers[dm.Pump] connection_node_layer = connection_node_handler.layer linestring = self.feature.geometry().asPolyline() start_point, end_point = linestring[0], linestring[-1] start_connection_node_feat, end_connection_node_feat = find_linestring_nodes(linestring, connection_node_layer) - start_pump_feat = self.pumpstation + start_pump_feat = self.pump if start_pump_feat is None: start_geom = QgsGeometry.fromPointXY(start_point) if start_connection_node_feat is None: - start_pump_feat, start_connection_node_feat = pumpstation_handler.create_pump_with_connection_node( - start_geom - ) + start_pump_feat, start_connection_node_feat = pump_handler.create_pump_with_connection_node(start_geom) self.extra_features[connection_node_handler].append(start_connection_node_feat) else: - start_pump_feat = pumpstation_handler.create_new_feature(start_geom) + start_pump_feat = pump_handler.create_new_feature(start_geom) start_pump_feat["connection_node_id"] = start_connection_node_feat["id"] if end_connection_node_feat is None: end_geom = QgsGeometry.fromPointXY(end_point) @@ -1267,7 +1576,7 @@ def setup_pumpstation_on_creation(self): start_connection_node_feat, geometry=end_geom ) self.extra_features[connection_node_handler].append(end_connection_node_feat) - self.extra_features[pumpstation_handler].append(start_pump_feat) + self.extra_features[pump_handler].append(start_pump_feat) else: if end_connection_node_feat is None: end_geom = QgsGeometry.fromPointXY(end_point) @@ -1276,63 +1585,61 @@ def setup_pumpstation_on_creation(self): ) self.extra_features[connection_node_handler].append(end_connection_node_feat) # Assign features as a form instance attributes. - self.connection_node_start = start_connection_node_feat self.connection_node_end = end_connection_node_feat - self.pumpstation = start_pump_feat + self.pump = start_pump_feat self.sequence_related_features_ids() def fill_related_attributes(self): """Filling feature values based on related features attributes.""" super().fill_related_attributes() - self.feature["pumpstation_id"] = self.pumpstation["id"] + self.feature["pump_id"] = self.pump["id"] + self.feature["connection_node_id_end"] = self.connection_node_end["id"] def populate_with_extra_widgets(self): """Populate widgets for other layers attributes.""" if self.creation is True: - self.pumpstation = self.select_start_pumpstation() - self.setup_pumpstation_on_creation() + self.pump = self.select_start_pump() + self.setup_pump_on_creation() self.fill_related_attributes() else: - self.setup_pumpstation_on_edit() + self.setup_pump_on_edit() # Populate widgets based on features attributes self.populate_foreign_widgets() self.populate_widgets() - def select_start_pumpstation(self): - """Selecting start pumpstation""" - title = "Select start pumpstation" - message = "Pumpstations at location" + def select_start_pump(self): + """Selecting start pump""" + title = "Select start pump" + message = "pumps at location" linestring = self.feature.geometry().asPolyline() start_point, end_point = linestring[0], linestring[-1] - pumpstation_handler = self.layer_manager.model_handlers[dm.Pumpstation] - pumpstation_layer = pumpstation_handler.layer - start_pump_feats = find_point_nodes(start_point, pumpstation_layer, allow_multiple=True) + pump_handler = self.layer_manager.model_handlers[dm.Pump] + pump_layer = pump_handler.layer + start_pump_feats = find_point_nodes(start_point, pump_layer, allow_multiple=True) pump_no = len(start_pump_feats) if pump_no == 0: - pumpstation_feat = None + pump_feat = None elif pump_no == 1: - pumpstation_feat = next(iter(start_pump_feats)) + pump_feat = next(iter(start_pump_feats)) else: pump_feats_by_id = {feat["id"]: feat for feat in start_pump_feats} pump_entries = [f"{feat_id} ({feat['display_name']})" for feat_id, feat in pump_feats_by_id.items()] - pumpstation_entry = self.uc.pick_item(title, message, None, *pump_entries) - pumpstation_id = int(pumpstation_entry.split()[0]) if pumpstation_entry else None - pumpstation_feat = pump_feats_by_id[pumpstation_id] if pumpstation_id else None - return pumpstation_feat + pump_entry = self.uc.pick_item(title, message, None, *pump_entries) + pump_id = int(pump_entry.split()[0]) if pump_entry else None + pump_feat = pump_feats_by_id[pump_id] if pump_id else None + return pump_feat -class ImperviousSurfaceMapForm(NodeToSurfaceMapForm): - """Impervious Surface Map user layer edit form logic.""" +class SurfaceForm(AbstractFormWithTag): + """Surface user layer edit form logic.""" - MODEL = dm.ImperviousSurfaceMap + MODEL = dm.Surface def __init__(self, *args, **kwargs): super().__init__(*args, *kwargs) - self.surface_model = dm.ImperviousSurface - self.surface_id_field = "impervious_surface_id" -class SurfaceMapForm(NodeToSurfaceMapForm): +class SurfaceMapForm(AbstractNodeToSurfaceMapForm, AbstractFormWithTag): """Surface Map user layer edit form logic.""" MODEL = dm.SurfaceMap @@ -1343,7 +1650,47 @@ def __init__(self, *args, **kwargs): self.surface_id_field = "surface_id" -class ChannelForm(FormWithStartEndNode, FormWithXSTable): +class DryWeatherFlowForm(AbstractFormWithTag): + """Dry weather flow user layer edit form logic.""" + + MODEL = dm.DryWeatherFlow + + def __init__(self, *args, **kwargs): + super().__init__(*args, *kwargs) + + +class DryWeatherFlowMapForm(AbstractNodeToSurfaceMapForm, AbstractFormWithTag): + """Dry weather flow Map user layer edit form logic.""" + + MODEL = dm.DryWeatherFlowMap + + def __init__(self, *args, **kwargs): + super().__init__(*args, *kwargs) + self.surface_model = dm.DryWeatherFlow + self.surface_id_field = "dry_weather_flow_id" + + +class DryWeatherFlowDistributionForm(AbstractFormWithTag, AbstractFormWithDistribution): + """Dry weather flow Distribution user layer edit form logic.""" + + MODEL = dm.DryWeatherFlowDistribution + + def __init__(self, *args, **kwargs): + super().__init__(*args, *kwargs) + + def fill_related_attributes(self): + """Filling feature values based on related features attributes.""" + super().fill_related_attributes() + + def populate_with_extra_widgets(self): + """Populate widgets for other layers attributes.""" + # Populate widgets based on features attributes + self.populate_foreign_widgets() + self.populate_widgets() + self.populate_distribution_table_data() + + +class ChannelForm(AbstractFormWithStartEndNode, AbstractFormWithXSTable, AbstractFormWithTag): """Channel user layer edit form logic.""" MODEL = dm.Channel @@ -1369,11 +1716,11 @@ def setup_cross_section_location_on_edit(self): """Setting up cross-section location during editing feature.""" connection_node_handler = self.layer_manager.model_handlers[dm.ConnectionNode] cross_section_location_handler = self.layer_manager.model_handlers[dm.CrossSectionLocation] - connection_node_start_id = self.feature["connection_node_start_id"] - connection_node_end_id = self.feature["connection_node_end_id"] + connection_node_id_start = self.feature["connection_node_id_start"] + connection_node_id_end = self.feature["connection_node_id_end"] channel_id = self.feature["id"] - self.connection_node_start = connection_node_handler.get_feat_by_id(connection_node_start_id) - self.connection_node_end = connection_node_handler.get_feat_by_id(connection_node_end_id) + self.connection_node_start = connection_node_handler.get_feat_by_id(connection_node_id_start) + self.connection_node_end = connection_node_handler.get_feat_by_id(connection_node_id_end) self.cross_section_locations = cross_section_location_handler.get_multiple_feats_by_id(channel_id, "channel_id") if self.cross_section_locations: channel_cross_section_location_ids = {str(feat["id"]): feat for feat in self.cross_section_locations} @@ -1400,7 +1747,7 @@ def setup_cross_section_location_on_creation(self): def fill_related_attributes(self): """Filling feature values based on related features attributes.""" super().fill_related_attributes() - global_settings_handler = self.layer_manager.model_handlers[dm.GlobalSettings] + global_settings_handler = self.layer_manager.model_handlers[dm.ModelSettings] global_settings_layer = global_settings_handler.layer channel_code = self.feature["code"] cross_section_location_code = f"{channel_code}_cross_section_1" @@ -1411,7 +1758,7 @@ def fill_related_attributes(self): except StopIteration: global_settings_feat = None if global_settings_feat: - self.current_cross_section_location["friction_type"] = global_settings_feat["frict_type"] + self.current_cross_section_location["friction_type"] = global_settings_feat["friction_type"] def set_current_cross_section_location(self, current_text): """Set handling of selected channel cross-section location.""" @@ -1477,7 +1824,7 @@ def populate_with_extra_widgets(self): self.populate_cross_section_table_data() -class CrossSectionLocationForm(FormWithXSTable): +class CrossSectionLocationForm(AbstractFormWithXSTable): """Cross-section location user layer edit form logic.""" MODEL = dm.CrossSectionLocation @@ -1499,7 +1846,7 @@ def fill_related_attributes(self): super().fill_related_attributes() connection_node_handler = self.layer_manager.model_handlers[dm.ConnectionNode] channel_handler = self.layer_manager.model_handlers[dm.Channel] - global_settings_handler = self.layer_manager.model_handlers[dm.GlobalSettings] + global_settings_handler = self.layer_manager.model_handlers[dm.ModelSettings] channel_layer = channel_handler.layer global_settings_layer = global_settings_handler.layer point_geom = self.feature.geometry() @@ -1574,18 +1921,18 @@ def fill_related_attributes(self): "cross_section_width", "cross_section_height", "cross_section_table", - "cross_section_friction_table", + "cross_section_friction_values", "cross_section_vegetation_table", ]: self.feature[xs_field_name] = closest_existing_cross_section[xs_field_name] try: global_settings_feat = next(global_settings_layer.getFeatures()) - self.feature["friction_type"] = global_settings_feat["frict_type"] + self.feature["friction_type"] = global_settings_feat["friction_type"] except StopIteration: pass -class PotentialBreachForm(BaseForm): +class PotentialBreachForm(AbstractFormWithTag): """Potential breach user layer edit form logic.""" MODEL = dm.PotentialBreach @@ -1622,7 +1969,7 @@ def populate_with_extra_widgets(self): self.populate_widgets() -class ExchangeLineForm(BaseForm): +class ExchangeLineForm(AbstractFormWithTag): """Exchange line user layer edit form logic.""" MODEL = dm.ExchangeLine @@ -1637,21 +1984,239 @@ def populate_with_extra_widgets(self): self.populate_widgets() +class BoundaryCondition1D(AbstractFormWithTag, AbstractFormWithNode, AbstractFormWithTimeseries): + """Boundary Condition 1D user layer edit form logic.""" + + MODEL = dm.BoundaryCondition1D + + def __init__(self, *args, **kwargs): + super().__init__(*args, *kwargs) + + def fill_related_attributes(self): + """Filling feature values based on related features attributes.""" + super().fill_related_attributes() + + def populate_with_extra_widgets(self): + """Populate widgets for other layers attributes.""" + if self.creation is True: + self.setup_connection_node_on_creation() + self.fill_related_attributes() + else: + self.setup_connection_node_on_edit() + # Populate widgets based on features attributes + self.populate_foreign_widgets() + self.populate_widgets() + self.populate_table_data() + + +class BoundaryCondition2D(AbstractFormWithTag, AbstractFormWithTimeseries): + """Boundary Condition 2D user layer edit form logic.""" + + MODEL = dm.BoundaryCondition2D + + def __init__(self, *args, **kwargs): + super().__init__(*args, *kwargs) + + def fill_related_attributes(self): + """Filling feature values based on related features attributes.""" + super().fill_related_attributes() + + def populate_with_extra_widgets(self): + """Populate widgets for other layers attributes.""" + # Populate widgets based on features attributes + self.populate_foreign_widgets() + self.populate_widgets() + self.populate_table_data() + + +class Lateral1D(AbstractFormWithTag, AbstractFormWithNode, AbstractFormWithTimeseries): + """Lateral 1D user layer edit form logic.""" + + MODEL = dm.Lateral1D + + def __init__(self, *args, **kwargs): + super().__init__(*args, *kwargs) + + def fill_related_attributes(self): + """Filling feature values based on related features attributes.""" + super().fill_related_attributes() + + def populate_with_extra_widgets(self): + """Populate widgets for other layers attributes.""" + if self.creation is True: + self.setup_connection_node_on_creation() + self.fill_related_attributes() + else: + self.setup_connection_node_on_edit() + # Populate widgets based on features attributes + self.populate_foreign_widgets() + self.populate_widgets() + self.populate_table_data() + + +class Lateral2D(AbstractFormWithTag, AbstractFormWithTimeseries): + """Lateral 2D user layer edit form logic.""" + + MODEL = dm.Lateral2D + + def __init__(self, *args, **kwargs): + super().__init__(*args, *kwargs) + + def fill_related_attributes(self): + """Filling feature values based on related features attributes.""" + super().fill_related_attributes() + + def populate_with_extra_widgets(self): + """Populate widgets for other layers attributes.""" + # Populate widgets based on features attributes + self.populate_foreign_widgets() + self.populate_widgets() + self.populate_table_data() + + +class MeasureLocation(AbstractFormWithNode, AbstractFormWithTag): + """Measure Location user layer edit form logic.""" + + MODEL = dm.MeasureLocation + + def __init__(self, *args, **kwargs): + super().__init__(*args, *kwargs) + + +class MemoryControl(AbstractFormWithTargetStructure, AbstractFormWithTag): + """Memory Control user layer edit form logic.""" + + MODEL = dm.MemoryControl + + def __init__(self, *args, **kwargs): + super().__init__(*args, *kwargs) + + +class TableControl(AbstractFormWithTargetStructure, AbstractFormWithActionTable, AbstractFormWithTag): + """Table Control user layer edit form logic.""" + + MODEL = dm.TableControl + + def __init__(self, *args, **kwargs): + super().__init__(*args, *kwargs) + + def fill_related_attributes(self): + """Filling feature values based on related features attributes.""" + super().fill_related_attributes() + + def populate_with_extra_widgets(self): + """Populate widgets for other layers attributes.""" + # Populate widgets based on features attributes + if self.creation is True: + self.setup_target_structure_on_creation() + self.fill_related_attributes() + else: + self.setup_target_structure_on_edit() + self.populate_widgets() + self.populate_foreign_widgets() + self.populate_table_data() + + +class MeasureMap(AbstractFormWithTag): + """Measure Map user layer edit form logic.""" + + MODEL = dm.MeasureMap + + def __init__(self, *args, **kwargs): + super().__init__(*args, *kwargs) + self.measure_location_feature = None + self.control_feature = None + self.control_feature_type = None + + def fill_related_attributes(self): + """Filling feature values based on related features attributes.""" + super().fill_related_attributes() + if self.measure_location_feature is not None: + self.feature["measure_location_id"] = self.measure_location_feature["id"] + if self.control_feature is not None: + self.feature["control_id"] = self.control_feature["id"] + self.feature["control_type"] = self.control_feature_type + + @cached_property + def control_types(self): + """Return supported structure control data models.""" + control_data_models = {model_cls.__tablename__: model_cls for model_cls in [dm.TableControl, dm.MemoryControl]} + return control_data_models + + def setup_measure_location_on_edit(self): + """Setting up measure map location and structure control during editing feature.""" + try: + control_type = self.control_types[self.feature["control_type"]] + control_model_cls = self.control_types[control_type] + control_handler = self.layer_manager.model_handlers[control_model_cls] + control_feat = control_handler.get_feat_by_id(self.feature["control_id"]) + self.control_feature = control_feat + self.control_feature_type = control_type + except KeyError: + pass + try: + measure_location_handler = self.layer_manager.model_handlers[dm.MeasureLocation] + measure_location_feat = measure_location_handler.get_feat_by_id(self.feature["control_measure_location_id"]) + self.measure_location_feature = measure_location_feat + except KeyError: + pass + + def setup_measure_control_on_creation(self): + """Setting up measure map location and structure control during adding feature.""" + measure_location_handler = self.layer_manager.model_handlers[dm.MeasureLocation] + measure_location_layer = measure_location_handler.layer + feature_linestring = self.feature.geometry().asPolyline() + feature_start_point, feature_end_point = feature_linestring[0], feature_linestring[-1] + measure_location_feat = find_point_nodes(feature_start_point, measure_location_layer) + if measure_location_feat is not None: + self.measure_location_feature = measure_location_feat + for control_type, control_model_cls in self.control_types.items(): + control_handler = self.layer_manager.model_handlers[control_model_cls] + control_layer = control_handler.layer + feature_end_point = self.feature.geometry().asPolyline()[-1] + control_feat = find_point_nodes(feature_end_point, control_layer) + if control_feat is not None: + self.control_feature, self.control_feature_type = control_feat, control_type + break + + def populate_with_extra_widgets(self): + """Populate widgets for other layers attributes.""" + if self.creation is True: + self.setup_measure_control_on_creation() + # Set feature specific attributes + self.fill_related_attributes() + else: + self.setup_measure_location_on_edit() + # Populate widgets based on features attributes + self.populate_foreign_widgets() + self.populate_widgets() + + ALL_FORMS = ( ConnectionNodeForm, - ManholeForm, PipeForm, WeirForm, CulvertForm, OrificeForm, - PumpstationForm, - PumpstationMapForm, - ImperviousSurfaceMapForm, + PumpForm, + PumpMapForm, + DryWeatherFlowForm, + DryWeatherFlowMapForm, + DryWeatherFlowDistributionForm, + SurfaceForm, SurfaceMapForm, ChannelForm, CrossSectionLocationForm, PotentialBreachForm, ExchangeLineForm, + BoundaryCondition1D, + Lateral1D, + BoundaryCondition2D, + Lateral2D, + MeasureLocation, + MemoryControl, + TableControl, + MeasureMap, ) MODEL_FORMS = MappingProxyType({form.MODEL: form for form in ALL_FORMS}) diff --git a/threedi_schematisation_editor/forms/ui/boundary_condition_1d.ui b/threedi_schematisation_editor/forms/ui/boundary_condition_1d.ui new file mode 100644 index 0000000..e84e5c5 --- /dev/null +++ b/threedi_schematisation_editor/forms/ui/boundary_condition_1d.ui @@ -0,0 +1,597 @@ + + + Dialog + + + + 0 + 0 + 552 + 557 + + + + Dialog + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 534 + 510 + + + + + + + + + + Qt::ImhNone + + + 0 + + + + 1D Boundary Condition + + + + + + + + + Qt::ImhNone + + + Timeseries + + + + + + + 0 + 125 + + + + + 8 + + + + Qt::ImhDigitsOnly + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::SelectRows + + + 23 + + + + + + + Table + + + + + + + 0 + + + + + Delete selected rows + + + Delete + + + + + + + Copy + + + + + + + Paste rows from clipboard + + + Paste + + + + + + + Add new row + + + Add + + + + + + + + + + + + Qt::ImhNone + + + Time units + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + 0 + + + + + Interpolate + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + false + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 0 + + + + + Tag + + + + + + + true + + + + + + + + + true + + + + + + Connection node + + + + + + + + + Node storage area [m²] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + + + Node code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Node initial water level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + 999999999.000000000000000 + + + true + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -1000.000000000000000 + + + 1000000.000000000000000 + + + true + + + true + + + true + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + 0 + 0 + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 999999999 + + + true + + + true + + + false + + + + + + + + + + Connection node ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + x + + + + + + + x + + + + + + + + + + true + + + + + + General + + + + + + + + + Display name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 99999999 + + + true + + + true + + + false + + + + + + + + + + Boundary type + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + + + + + + + + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + scrollArea + tabWidget + id + code + display_name + timeseries_table + timeseries_table_delete + timeseries_table_copy + timeseries_table_paste + timeseries_table_add + + + + diff --git a/threedi_schematisation_editor/forms/ui/boundary_condition_2d.ui b/threedi_schematisation_editor/forms/ui/boundary_condition_2d.ui new file mode 100644 index 0000000..f8ec31a --- /dev/null +++ b/threedi_schematisation_editor/forms/ui/boundary_condition_2d.ui @@ -0,0 +1,398 @@ + + + Dialog + + + + 0 + 0 + 552 + 557 + + + + Dialog + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 534 + 510 + + + + + + + + + + Qt::ImhNone + + + 0 + + + + 2D Boundary Condition + + + + + + 0 + + + + + Tag + + + + + + + true + + + + + + + + + true + + + + + + General + + + + + + + + + Display name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 99999999 + + + true + + + true + + + false + + + + + + + + + + Boundary type + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + + + + + + + + + + + + + Qt::ImhNone + + + Timeseries + + + + + + + 0 + 125 + + + + + 8 + + + + Qt::ImhDigitsOnly + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::SelectRows + + + 23 + + + + + + + Table + + + + + + + 0 + + + + + Delete selected rows + + + Delete + + + + + + + Copy + + + + + + + Paste rows from clipboard + + + Paste + + + + + + + Add new row + + + Add + + + + + + + + + + + + Qt::ImhNone + + + Time units + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + 0 + + + + + Interpolate + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + false + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + scrollArea + tabWidget + id + code + display_name + timeseries_table + timeseries_table_delete + timeseries_table_copy + timeseries_table_paste + timeseries_table_add + + + + diff --git a/threedi_schematisation_editor/forms/ui/channel.ui b/threedi_schematisation_editor/forms/ui/channel.ui index acb0cf4..f4b98d2 100644 --- a/threedi_schematisation_editor/forms/ui/channel.ui +++ b/threedi_schematisation_editor/forms/ui/channel.ui @@ -14,6 +14,13 @@ Dialog
+ + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + @@ -26,9 +33,9 @@ 0 - -5 + 0 515 - 929 + 955 @@ -48,21 +55,8 @@ Channel - - - - Qt::Vertical - - - - 20 - 40 - - - - - - + + @@ -70,11 +64,11 @@ Qt::ImhNone - Visualisation + General - - - + + + 0 @@ -87,50 +81,13 @@ Qt::ImhNone - - - - - - - - - Qt::ImhNone - - - Zoom category - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - -1 + + true - - - - - - - - - - Qt::ImhNone - - - Connection nodes - - - - - - - 0 - 0 - - + + @@ -144,55 +101,11 @@ QAbstractSpinBox::NoButtons - 999999999 - - - true - - - false - - - - - - - - - - Qt::ImhNone - - - Start connection node ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - - - Qt::ImhNone + 99999999 - + true - - QAbstractSpinBox::NoButtons - - - 999999999 - true @@ -201,8 +114,8 @@ - - + + @@ -210,31 +123,13 @@ Qt::ImhNone - End connection node ID + ID Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - 10 - - - - - - - - - - - Qt::ImhNone - - - General - - @@ -289,8 +184,8 @@ - - + + @@ -298,15 +193,31 @@ Qt::ImhNone - ID + Exchange type Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + -1 + + + + + + + Distance between calculation points [m] + + + true + + + 10 + - - + + 0 @@ -342,75 +253,15 @@ - - - - - - - Qt::ImhNone - - - true - - - QAbstractSpinBox::NoButtons - - - 99999999 - - - true - - - true - - - false - - - - - - - Distance between calculation points [m] - - - true - - - 10 - - - - - - - - - - Qt::ImhNone - - - Calculation type - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - + + x - - + + 0 @@ -425,29 +276,10 @@ - - - - - 0 - 0 - - - - - - - Qt::ImhNone - - - true - - - - + Groundwater exchange @@ -582,15 +414,8 @@ - - - - - Connection nodes - - - - + + Qt::Vertical @@ -602,35 +427,31 @@ - - - - true + + + + + + + Qt::ImhNone - Starting connection node + Connection nodes - - - + + + 0 0 - - true + + - - - - - - - 0 - 0 - + + Qt::ImhNone true @@ -649,41 +470,63 @@ + + + + + + + Qt::ImhNone + + + Start connection node ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + - + 0 0 - - QAbstractSpinBox::NoButtons + + - - 3 + + Qt::ImhNone - - -1000.000000000000000 + + true - - 1000000.000000000000000 + + QAbstractSpinBox::NoButtons - - true + + 999999999 true - true + false - - + + + + + + + Qt::ImhNone + - Node storage area [m²] + End connection node ID Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -693,24 +536,54 @@ - - - - Connection node ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - + + + + + + + 0 + + + + + Tag + + + + + + + true + + + + + + + + + + Starting connection node + + + + + + Groundwater exchange + + + + 0 0 + + + QAbstractSpinBox::NoButtons @@ -718,70 +591,54 @@ 3 - 999999999.000000000000000 - - - true + 1000000.000000000000000 - + - Node initial water level [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 + x - - + + + + + - Node code + Exchange thickness [m] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - x + + + + - - - - - x + Hydraulic conductivity in [m/d] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - true - - - Ending connection node - - - - + + 0 0 + + + QAbstractSpinBox::NoButtons @@ -789,58 +646,82 @@ 3 - 999999999.000000000000000 + 1000000.000000000000000 - - true + + + + + + x - - + + + + + - Connection node ID + Hydraulic conductivity out [m/d] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - + + 0 0 + + + QAbstractSpinBox::NoButtons 3 - - -1000.000000000000000 - 1000000.000000000000000 - - true - - - true + + + + + + x - - true + + + + + + + + + Dimensions + + + + + + x - - + + + + + - Node initial water level [m] + Storage area [m²] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -850,76 +731,85 @@ - - + + + + + - Node code + Bottom level [m MSL] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + -1 + - + 0 0 - - true + + + + + Qt::ImhNone QAbstractSpinBox::NoButtons - - 999999999 + + 3 - - true + + -9999999999.000000000000000 - - false + + 9999999999.000000000000000 - - + + 0 0 - - true + + - - - - - - Node storage area [m²] + + Qt::ImhNone - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + QAbstractSpinBox::NoButtons - - 10 + + 3 - - - - - - x + + -99999999.000000000000000 + + + 999999999.000000000000000 - - + + + + + 8 + + x @@ -928,28 +818,14 @@ - - - - - Cross section locations - - - - + + - Friction + Administrative properties - - - - - Friction coefficients per segment (overrides single friction value) - - - - - + + + 0 @@ -966,31 +842,57 @@ QAbstractSpinBox::NoButtons - 4 + 3 + + + -9999999999.000000000000000 - 1000000.000000000000000 + 9999999999.000000000000000 - - true + + + + + + - - true + + Manhole surface level [m] - - true + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 - - + + + + + 50 + false + false + + x + + + + + + + Initial water level + + - + 0 @@ -1003,119 +905,48 @@ Qt::ImhNone - - - - - - - - - Qt::ImhNone + + QAbstractSpinBox::NoButtons - - Friction value + + 3 - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + -99999999.000000000000000 - - 10 + + 999999999.000000000000000 - + - - Qt::ImhNone - - Friction type + Initial water level [m MSL] - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - 10 + -1 - - - - - 0 - 125 - - - - - 8 - - - - Qt::ImhDigitsOnly - - - Qt::ScrollBarAsNeeded - - - QAbstractScrollArea::AdjustToContents - - - QAbstractItemView::SelectRows + + + + x - - 21 - - - - - 0 - - - - - Clear - - - - - - - Copy - - - - - - - - - 0 - - - - - - - - Channel cross section locations - - - - - - - + + Qt::Vertical @@ -1127,62 +958,56 @@ - - + + true + + + General - - - + + + - ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + Display name - - - - true - - - QAbstractSpinBox::NoButtons - - - 99999999 - - - true - - - true + + + + + 0 + 0 + - - false + + - - + + + + + - Bank level + Code Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - -1 + 10 - - + + 0 @@ -1192,67 +1017,95 @@ + + true + QAbstractSpinBox::NoButtons - - 3 - - - -99999999.000000000000000 - - 1000000.000000000000000 - - - true + 999999999 - - true + + + + + + + 0 + 0 + - - true + + - - + + + + + + + + - x + Visualisation + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 - - + + + + + - Code + ID Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - 10 - - - + + + + + + + true + + + + + + 2D exchange + + + + - Reference level [m] + Exchange level [m MSL] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - 10 + -1 - - + + 0 @@ -1262,6 +1115,9 @@ + + Qt::ImhNone + QAbstractSpinBox::NoButtons @@ -1269,136 +1125,223 @@ 3 - -99999999.000000000000000 + -9999999999.000000000000000 - 1000000.000000000000000 - - - true - - - true - - - true + 9999999999.000000000000000 - - + + x - - - - - 0 - 0 - - - - true + + + + Exchange type + + + - - + + + + 0 + + + + + Tag + + + + + + + true + + + Open this feature's own attribute form to see its tags + + + + + + + + + + Ending connection node + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + true + - - Qt::ImhNone - - Cross-section + 2D exchange - - - - - Table + + + + + - - - - - x + Exchange level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 - - + + - + 0 0 + + + + + Qt::ImhNone + QAbstractSpinBox::NoButtons 3 + + -9999999999.000000000000000 + - 1000000.000000000000000 + 9999999999.000000000000000 - - - - - - - Qt::ImhNone - + + - Shape + x - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + Exchange type - - + + + + + + + + + + Initial water level + + + + 0 0 + + + + + Qt::ImhNone + QAbstractSpinBox::NoButtons 3 + + -99999999.000000000000000 + - 1000000.000000000000000 + 999999999.000000000000000 - - + + + + + + + Initial water level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + x - - + + + + + + + Administrative properties + + + + - + 0 0 @@ -1409,131 +1352,1001 @@ Qt::ImhNone - - false + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 - - + + + + + + + Manhole surface level [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + 50 + false + false + + + + x + + + + + + + + + + Dimensions + + + + + + x + + + + + + + + + + Storage area [m²] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + + + Bottom level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -99999999.000000000000000 + + + 999999999.000000000000000 + + + + + + + + 8 + + + + x + + + + + + + + + + true + + + + + + General + + + + + + + + + ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 999999999 + + + + + + + + 0 + 0 + + + + + + + + + + + + + + Display name + + + + + + + + + + Code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + + + + + + + + Visualisation + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + + + Groundwater exchange + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + + + + + x + + + + + + + + + + Exchange thickness [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Hydraulic conductivity in [m/d] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + + + + + x + + + + + + + + + + Hydraulic conductivity out [m/d] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + + + + + x + + + + + + + + + + 0 + + + + + Tag + + + + + + + true + + + Open this feature's own attribute form to see its tags + + + + + + + + + + Cross section locations + + + + + + true + + + General + + + + + + ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 99999999 + + + true + + + true + + + false + + + + + + + Bank level + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + -99999999.000000000000000 + + + 1000000.000000000000000 + + + true + + + true + + + true + + + + + + + x + + + + + + + Code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + + + Reference level [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + -99999999.000000000000000 + + + 1000000.000000000000000 + + + true + + + true + + + true + + + + + + + x + + + + + + + + 0 + 0 + + + + true + + + + + + + + + + Vegetation + + + + + + + + + Qt::ImhNone + + + Drag coefficient [-] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 125 + + + + + 8 + + + + Qt::ImhDigitsOnly + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::SelectRows + + + 23 + + + + + + + + + + Qt::ImhNone + + + Height [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 0 + + + 10 + + + + + + + Vegetation parameters per segment (overrides single vegetation values) + + + + + + + + + + Stem diameter [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + x + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 4 + + + 1000000.000000000000000 + + + true + + + true + + + true + + + + + + + x + + + + + + + + + + <html><head/><body><p>Stem density [m<span style=" vertical-align:super;">-2</span>]</p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + x + + + + + + + + 0 + 0 + + Qt::ImhNone - - Height [m] + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + + + true + + + true + + + true + + + + + + + x + + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + true - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + true - - 10 + + true - - + + + + + 0 + 0 + + - - Qt::ImhNone - - - Width [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 125 - + + QAbstractSpinBox::NoButtons - - - 8 - + + 3 - - Qt::ImhDigitsOnly + + 1000000.000000000000000 - - Qt::ScrollBarAsNeeded + + true - - QAbstractScrollArea::AdjustToContents + + true - - QAbstractItemView::SelectRows + + true - - 21 - - - + + 0 - - - Delete selected rows - + - Delete + Clear - + Copy - - - - Paste rows from clipboard - - - Paste - - - - - - - Add new row - - - Add - - - - - + + - Vegetation + Friction - - - + + + + + Friction coefficients per segment (overrides single friction value) + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 4 + + + 1000000.000000000000000 + + + true + + + true + + + true + + + + + + + x + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + + + @@ -1541,7 +2354,7 @@ Qt::ImhNone - Drag coefficient [-] + Friction value Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -1551,8 +2364,27 @@ - - + + + + + + + Qt::ImhNone + + + Friction type + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 10 + + + + + 0 @@ -1577,168 +2409,138 @@ QAbstractItemView::SelectRows - 21 + 23 - - - - - - - Qt::ImhNone - - - Height [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - + + + 0 - - 10 - - - - - - - Vegetation parameters per segment (overrides single vegetation values) - - + + + + Clear + + + + + + + Copy + + + + - - - - - + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + Qt::ImhNone + + + Cross-section + + + + - Stem diameter [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + Table - - + + x - - + + - + 0 0 - - - - - Qt::ImhNone - QAbstractSpinBox::NoButtons - 4 + 3 1000000.000000000000000 - - true - - - true - - - true - - - - - - - x - - - + + + + Qt::ImhNone + - <html><head/><body><p>Stem density [m<span style=" vertical-align:super;">-2</span>]</p></body></html> + Shape Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - x - - - - - + + 0 0 - - - - - Qt::ImhNone - QAbstractSpinBox::NoButtons 3 - - -9999999999.000000000000000 - - 9999999999.000000000000000 - - - true - - - true - - - true + 1000000.000000000000000 - - + + x - - + + - + 0 0 @@ -1746,81 +2548,143 @@ - - QAbstractSpinBox::NoButtons + + Qt::ImhNone - - 3 + + false - - 1000000.000000000000000 + + + + + + - - true + + Qt::ImhNone - - true + + Height [m] - - true + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 - - - - - 0 - 0 - - + + - - QAbstractSpinBox::NoButtons + + Qt::ImhNone - - 3 + + Width [m] - - 1000000.000000000000000 + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - true + + + + + + + 0 + 125 + - - true + + + 8 + - - true + + Qt::ImhDigitsOnly + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::SelectRows + + 23 + - - + + 0 - + + + Delete selected rows + - Clear + Delete - + Copy + + + + Paste rows from clipboard + + + Paste + + + + + + + Add new row + + + Add + + + + + + + 0 + + + + + + + + Channel cross section locations + + + + + @@ -1829,13 +2693,6 @@ - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - @@ -1844,30 +2701,14 @@ id code display_name - calculation_type - dist_calc_points - dist_calc_points_clear - zoom_category exchange_thickness exchange_thickness_clear hydraulic_conductivity_in hydraulic_conductivity_in_clear hydraulic_conductivity_out hydraulic_conductivity_out_clear - connection_node_start_id - connection_node_end_id - connection_node_1_id - connection_node_1_code - connection_node_1_initial_waterlevel - connection_node_1_initial_waterlevel_clear - connection_node_1_storage_area - connection_node_1_storage_area_clear - connection_node_2_id - connection_node_2_code - connection_node_2_initial_waterlevel - connection_node_2_initial_waterlevel_clear - connection_node_2_storage_area - connection_node_2_storage_area_clear + connection_node_id_start + connection_node_id_end cross_section_locations cross_section_location_id cross_section_location_bank_level diff --git a/threedi_schematisation_editor/forms/ui/connection_node.ui b/threedi_schematisation_editor/forms/ui/connection_node.ui index f0785c2..df544b3 100644 --- a/threedi_schematisation_editor/forms/ui/connection_node.ui +++ b/threedi_schematisation_editor/forms/ui/connection_node.ui @@ -17,13 +17,6 @@ - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - @@ -37,25 +30,58 @@ 0 0 - 532 - 503 + 515 + 525 - - - - true - - - - + + - General + Dimensions - - - + + + + + x + + + + + + + + + + Storage area [m²] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + + + Bottom level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + 0 @@ -65,9 +91,24 @@ + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + - + @@ -95,6 +136,33 @@ + + + + + 8 + + + + x + + + + + + + + + + true + + + + + + General + + @@ -108,13 +176,58 @@ - - + + + + + 0 + 0 + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 999999999 + + + + + + + + 0 + 0 + + + + + + + + + - Storage area [m²] + Display name + + + + + + + + + + Code Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -124,37 +237,128 @@ - - + + + + + 0 + 0 + + + + + + + + + - Code + Visualisation Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + -1 + - + + + + + + + Initial water level + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -99999999.000000000000000 + + + 999999999.000000000000000 + + + + - Initial water level [m] + Initial water level [m MSL] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - 10 + -1 + + + + + + + x + + + + + + + + + + true + + + + + + 2D exchange + + + + + + + + + Exchange level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 - - + + 0 @@ -174,15 +378,41 @@ 3 - -99999999.000000000000000 + -9999999999.000000000000000 - 999999999.000000000000000 + 9999999999.000000000000000 - - + + + + x + + + + + + + Exchange type + + + + + + + + + + + + + Administrative properties + + + + 0 @@ -192,26 +422,183 @@ - - true + + Qt::ImhNone QAbstractSpinBox::NoButtons + + 3 + + + -9999999999.000000000000000 + - 999999999 + 9999999999.000000000000000 - - + + + + + + + Manhole surface level [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + 50 + false + false + + x - - + + + + + + + Groundwater exchange + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + + + + + x + + + + + + + + + + Exchange thickness [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Hydraulic conductivity in [m/d] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + + + + + x + + + + + + + + + + Hydraulic conductivity out [m/d] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + + + x @@ -220,7 +607,7 @@ - + Qt::Vertical @@ -233,20 +620,43 @@ + + + + 0 + + + + + Tag + + + + + + + true + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + scrollArea id - code - initial_waterlevel - initial_waterlevel_clear - storage_area - storage_area_clear diff --git a/threedi_schematisation_editor/forms/ui/cross_section_location.ui b/threedi_schematisation_editor/forms/ui/cross_section_location.ui index 55d102c..ab6d847 100644 --- a/threedi_schematisation_editor/forms/ui/cross_section_location.ui +++ b/threedi_schematisation_editor/forms/ui/cross_section_location.ui @@ -28,23 +28,10 @@ 0 0 515 - 915 + 943 - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -402,6 +389,237 @@ + + + + + + + Qt::ImhNone + + + Cross-section + + + + + + + + + Qt::ImhNone + + + Height [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + x + + + + + + + + 0 + 125 + + + + + 8 + + + + Qt::ImhDigitsOnly + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::SelectRows + + + 21 + + + + + + + x + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + false + + + + + + + + 0 + 0 + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + + + + + + + + Qt::ImhNone + + + Width [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + + + + + + + + Qt::ImhNone + + + Shape + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Table + + + + + + + 0 + + + + + Delete selected rows + + + Delete + + + + + + + Copy + + + + + + + Paste rows from clipboard + + + Paste + + + + + + + Add new row + + + Add + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -745,223 +963,26 @@ - - - - - - - Qt::ImhNone - - - Cross-section + + + + 0 - - - - - - - - Qt::ImhNone - - - Height [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - x - - - - - - - - 0 - 125 - - - - - 8 - - - - Qt::ImhDigitsOnly - - - Qt::ScrollBarAsNeeded - - - QAbstractScrollArea::AdjustToContents - - - QAbstractItemView::SelectRows - - - 21 - - - - - - - x - - - - - - - - 0 - 0 - - - - - - - Qt::ImhNone - - - false - - - - - - - - 0 - 0 - - - - QAbstractSpinBox::NoButtons - - - 3 - - - 1000000.000000000000000 - - - - - - - - - - Qt::ImhNone - - - Width [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - QAbstractSpinBox::NoButtons - - - 3 - - - 1000000.000000000000000 - - - - - - - - - - Qt::ImhNone - - - Shape - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - Table - - - - - - - 0 - - - - - Delete selected rows - - - Delete - - - - - - - Copy - - - - - - - Paste rows from clipboard - - - Paste - - - - - - - Add new row - - - Add - - - - - - - + + + + Tag + + + + + + + true + + + + diff --git a/threedi_schematisation_editor/forms/ui/culvert.ui b/threedi_schematisation_editor/forms/ui/culvert.ui index 44bd37b..7bcc3ab 100644 --- a/threedi_schematisation_editor/forms/ui/culvert.ui +++ b/threedi_schematisation_editor/forms/ui/culvert.ui @@ -35,7 +35,7 @@ 0 0 515 - 715 + 684 @@ -55,20 +55,20 @@ Culvert - - - - true - + + + + Qt::ImhNone + - Characteristics + Connection nodes - - - + + + 0 @@ -78,28 +78,44 @@ + + Qt::ImhNone + + + true + QAbstractSpinBox::NoButtons - - 3 - - 1000000.000000000000000 - - - true + 999999999 true - true + false - - + + + + + + + Qt::ImhNone + + + Start connection node ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + 0 @@ -112,128 +128,126 @@ Qt::ImhNone + + true + QAbstractSpinBox::NoButtons - - 4 - - 1000000.000000000000000 - - - true + 999999999 true - true + false - - + + + + Qt::ImhNone + - Start invert level [m] + End connection node ID Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - x + + 10 - - + + + + + + + true + + + + + + General + + + + + + + - Discharge coefficient negative + Display name Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - true - - - 0 - 10 - - + + - - Qt::ImhNone + + true - - Friction type + + QAbstractSpinBox::NoButtons - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + 99999999 - - 10 + + true + + + true + + + false - - + + - End invert level [m] + Code Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - + + - - Qt::ImhNone - - Friction value + ID Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - 0 - - - 10 - - - - - - - x - - + 0 @@ -243,34 +257,13 @@ - - Qt::ImhNone - - - QAbstractSpinBox::NoButtons - - - 3 - - - -99999999.000000000000000 - - - 99999999.000000000000000 - - - true - - - true - - + true - - + + 0 @@ -280,41 +273,26 @@ - - Qt::ImhNone - - - QAbstractSpinBox::NoButtons - - - 3 - - - -99999999.000000000000000 - - - 99999999.000000000000000 - - - true - - - true - - + true - - + + - x + Distance between calculation points [m] + + + true + + + 10 - - + + 0 @@ -339,56 +317,42 @@ 9999999999.000000000000000 - - true - - - true - - - true + + + + + + x - - + + - - Discharge -coefficient positive + + Qt::ImhNone - - Qt::PlainText + + Exchange type Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - true - -1 - - - - x - - - - - - - x + + + + + 0 + 0 + - - - - @@ -400,8 +364,8 @@ coefficient positive - - + + true @@ -409,27 +373,11 @@ coefficient positive - Visualisation + Characteristics - - - - - - - - Zoom category - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - -1 - - - - - + + + 0 @@ -439,41 +387,28 @@ coefficient positive - - - - - - - - - true - - - - - - General - - - - - - + + QAbstractSpinBox::NoButtons - - Display name + + 3 - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + 1000000.000000000000000 - - 10 + + true + + + true + + + true - - + + 0 @@ -490,29 +425,10 @@ coefficient positive QAbstractSpinBox::NoButtons - 3 - - - -9999999999.000000000000000 - - - 9999999999.000000000000000 - - - - - - - - - - true - - - QAbstractSpinBox::NoButtons + 4 - 99999999 + 1000000.000000000000000 true @@ -521,93 +437,112 @@ coefficient positive true - false + true - + - Code + Start invert level [m] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - Qt::ImhNone + + + + x + + + + - Calculation type + Discharge coefficient negative Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + true + + + 0 + 10 - - + + + + + + + Qt::ImhNone + - Distance between calculation points [m] + Friction type - - true + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 10 - - + + - ID + End invert level [m] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - 0 - 0 - - + + - - true + + Qt::ImhNone + + + Friction value + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 0 + + + 10 - - + + x - - + + 0 @@ -617,44 +552,34 @@ coefficient positive - - true + + Qt::ImhNone - - - - - - - 0 - 0 - + + QAbstractSpinBox::NoButtons - - + + 3 - - Qt::ImhNone + + -99999999.000000000000000 + + + 99999999.000000000000000 + + + true + + + true + + + true - - - - - - - - - - Qt::ImhNone - - - Connection nodes - - - - + + 0 @@ -667,41 +592,38 @@ coefficient positive Qt::ImhNone - - true - QAbstractSpinBox::NoButtons + + 3 + + + -99999999.000000000000000 + - 999999999 + 99999999.000000000000000 + + + true true - false + true - - - - - - - Qt::ImhNone - + + - Start connection node ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + x - - + + 0 @@ -714,39 +636,73 @@ coefficient positive Qt::ImhNone - - true - QAbstractSpinBox::NoButtons + + 3 + + + -9999999999.000000000000000 + - 999999999 + 9999999999.000000000000000 + + + true true - false + true - - + + - - Qt::ImhNone - - End connection node ID + Discharge +coefficient positive + + + Qt::PlainText Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + true + - 10 + -1 + + + + + + + x + + + + + + + x + + + + + + + + + + Qt::ImhNone @@ -766,7 +722,7 @@ coefficient positive - + @@ -811,7 +767,7 @@ coefficient positive QAbstractItemView::SelectRows - 21 + 23 @@ -984,39 +940,111 @@ coefficient positive + + + + 0 + + + + + Tag + + + + + + + true + + + + + - + - Connection nodes + Starting connection node - - + + true + + + - Ending connection node + General - + + + + + + + + ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + - + 0 0 - + + + + true + + QAbstractSpinBox::NoButtons + + + 999999999 + + + + + + + + 0 + 0 + + + + + + + + + + + + + + Display name + - + + + + - Node storage area [m²] + Code Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -1024,19 +1052,780 @@ coefficient positive 10 - - - - + + + + + + + 0 + 0 + + + + + + + + + + + + + + Visualisation + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + + + Groundwater exchange + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + + + + + x + + + + + + + + + + Exchange thickness [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Hydraulic conductivity in [m/d] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + + + + + x + + + + + + + + + + Hydraulic conductivity out [m/d] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + + + + + x + + + + + + + + + + Dimensions + + + + + + x + + + + + + + + + + Storage area [m²] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + + + Bottom level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -99999999.000000000000000 + + + 999999999.000000000000000 + + + + + + + + 8 + + + + x + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + true + + + + + + 2D exchange + + + + + + + + + Exchange level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + + + + + + + x + + + + + + + Exchange type + + + + + + + + + + + + + Administrative properties + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + + + + + + + + + + Manhole surface level [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + 50 + false + false + + + + x + + + + + + + + + + Initial water level + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -99999999.000000000000000 + + + 999999999.000000000000000 + + + + + + + + + + Initial water level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + x + + + + + + + + + + 0 + + + + + Tag + + + + + + + true + + + Open this feature's own attribute form to see its tags + + + + + + + + + + Ending connection node + + + + + + Initial water level + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -99999999.000000000000000 + + + 999999999.000000000000000 + + + + + + + + + + Initial water level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + x + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + true + + + + + + 2D exchange + + + + + + + + + Exchange level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + + + + + + + x + + + + + + + Exchange type + + + + + + + + + + + + + Administrative properties + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + + + + + + + + + + Manhole surface level [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + 50 + false + false + + + + x + + + + + + + + + + true + + + + + + General + + + + + + + - Connection node ID + ID Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - + @@ -1044,6 +1833,9 @@ coefficient positive 0 + + + true @@ -1053,37 +1845,38 @@ coefficient positive 999999999 - - true - - - false - - - + + 0 0 - - QAbstractSpinBox::NoButtons - - - 3 + + - - 999999999.000000000000000 + + + + + + + + + Display name - - + + + + + - Node initial water level [m] + Code Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -1093,211 +1886,281 @@ coefficient positive - - + + + + + 0 + 0 + + + + + + + + + + + + + + Visualisation + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + + + Groundwater exchange + + + + 0 0 + + + QAbstractSpinBox::NoButtons 3 - - -1000.000000000000000 - 1000000.000000000000000 - - - - Node code - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - + + x - - + + + + + - x + Exchange thickness [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - true - - - Starting connection node - - - + + + + - Node code + Hydraulic conductivity in [m/d] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - + + 0 0 + + + QAbstractSpinBox::NoButtons 3 - - -1000.000000000000000 - 1000000.000000000000000 - - + + + + x + + + + + + + + - Node initial water level [m] + Hydraulic conductivity out [m/d] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - 10 - - - + + 0 0 - - true + + QAbstractSpinBox::NoButtons + + 3 + - 999999999 + 1000000.000000000000000 - - true + + + + + + x - - false + + + + + + + + + Dimensions + + + + + + x + + + + + + + + + + Storage area [m²] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 - + + + + - Connection node ID + Bottom level [m MSL] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + -1 + - - + + 0 0 - - true + + - - - - - - Node storage area [m²] + + Qt::ImhNone - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + QAbstractSpinBox::NoButtons - - 10 + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 - - + + 0 0 + + + + + Qt::ImhNone + QAbstractSpinBox::NoButtons 3 + + -99999999.000000000000000 + 999999999.000000000000000 - - - - x + + + + + 8 + - - - - x @@ -1306,6 +2169,30 @@ coefficient positive + + + + 0 + + + + + Tag + + + + + + + true + + + Open this feature's own attribute form to see its tags + + + + + @@ -1322,13 +2209,10 @@ coefficient positive id code display_name - calculation_type - dist_calc_points - dist_calc_points_clear - invert_level_start_point - invert_level_start_point_clear - invert_level_end_point - invert_level_end_point_clear + invert_level_start + invert_level_start_clear + invert_level_end + invert_level_end_clear discharge_coefficient_positive discharge_coefficient_positive_clear friction_value @@ -1336,7 +2220,6 @@ coefficient positive friction_type discharge_coefficient_negative discharge_coefficient_negative_clear - zoom_category cross_section_shape cross_section_width cross_section_width_clear @@ -1347,20 +2230,8 @@ coefficient positive cross_section_table_copy cross_section_table_paste cross_section_table_add - connection_node_start_id - connection_node_end_id - connection_node_1_id - connection_node_1_code - connection_node_1_initial_waterlevel - connection_node_1_initial_waterlevel_clear - connection_node_1_storage_area - connection_node_1_storage_area_clear - connection_node_2_id - connection_node_2_code - connection_node_2_initial_waterlevel - connection_node_2_initial_waterlevel_clear - connection_node_2_storage_area - connection_node_2_storage_area_clear + connection_node_id_start + connection_node_id_end
diff --git a/threedi_schematisation_editor/forms/ui/dry_weather_flow.ui b/threedi_schematisation_editor/forms/ui/dry_weather_flow.ui new file mode 100644 index 0000000..961d7a9 --- /dev/null +++ b/threedi_schematisation_editor/forms/ui/dry_weather_flow.ui @@ -0,0 +1,284 @@ + + + Form + + + + 0 + 0 + 550 + 550 + + + + Form + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 532 + 503 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 0 + + + + + Tag + + + + + + + true + + + + + + + + + true + + + + + + General + + + + + + + + + Code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 10000000000000000.000000000000000 + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + Multiplier + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 99999999 + + + true + + + true + + + false + + + + + + + + + + Display name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + Interpolate + + + + + + + Daily total + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 10000000000000000.000000000000000 + + + + + + + + + + 0 + + + + + Dry Weather Flow distribution ID + + + + + + + + 0 + 0 + + + + + + + + + + + + + + scrollArea + + + + diff --git a/threedi_schematisation_editor/forms/ui/dry_weather_flow_distribution.ui b/threedi_schematisation_editor/forms/ui/dry_weather_flow_distribution.ui new file mode 100644 index 0000000..b33ce47 --- /dev/null +++ b/threedi_schematisation_editor/forms/ui/dry_weather_flow_distribution.ui @@ -0,0 +1,267 @@ + + + Dialog + + + + 0 + 0 + 552 + 557 + + + + Dialog + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 534 + 510 + + + + + + + + + + Qt::ImhNone + + + 0 + + + + Dry Weather Flow distribution + + + + + + true + + + + + + General + + + + + + 1 + + + + + + Description + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 99999999 + + + true + + + true + + + false + + + + + + + + + + ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 0 + + + + + Tag + + + + + + + true + + + + + + + + + + + + Qt::ImhNone + + + Distribution + + + + + + + 0 + 125 + + + + + 8 + + + + Qt::ImhDigitsOnly + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::SelectRows + + + 23 + + + + + + + 0 + + + + + Paste rows from clipboard + + + Clear + + + + + + + + + + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + scrollArea + tabWidget + id + distribution_table + distribution_table_clear + + + + diff --git a/threedi_schematisation_editor/forms/ui/impervious_surface_map.ui b/threedi_schematisation_editor/forms/ui/dry_weather_flow_map.ui similarity index 72% rename from threedi_schematisation_editor/forms/ui/impervious_surface_map.ui rename to threedi_schematisation_editor/forms/ui/dry_weather_flow_map.ui index f7e680d..8199825 100644 --- a/threedi_schematisation_editor/forms/ui/impervious_surface_map.ui +++ b/threedi_schematisation_editor/forms/ui/dry_weather_flow_map.ui @@ -17,6 +17,13 @@ + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + @@ -35,6 +42,19 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -47,8 +67,8 @@ General - - + + 0 @@ -58,30 +78,33 @@ - - Qt::ImhNone + + true QAbstractSpinBox::NoButtons - - 2 + + 999999999 - - 0.000000000000000 + + true - - 100.000000000000000 + + true + + + false - + - Impervious surface ID + Dry Weather Flow ID Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -91,13 +114,13 @@ - - + + - Connection node ID + ID Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -126,37 +149,21 @@ - - - - - - - Percentage - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - + + - ID + Connection node ID Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - + + 0 @@ -186,15 +193,24 @@ - - + + + + + - x + Percentage + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 - - + + 0 @@ -204,23 +220,88 @@ - - true + + Qt::ImhNone QAbstractSpinBox::NoButtons + + 2 + + + 0.000000000000000 + - 999999999 + 100.000000000000000 - - true + + + + + + x - + + + + + + + + + Display name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + true - - false + + + + + + + + + Code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + true @@ -228,38 +309,37 @@ - - - Qt::Vertical + + + 0 - - - 20 - 40 - - - + + + + Tag + + + + + + + true + + + + - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - scrollArea id connection_node_id - percentage - percentage_clear - impervious_surface_id + dry_weather_flow_id diff --git a/threedi_schematisation_editor/forms/ui/exchange_line.ui b/threedi_schematisation_editor/forms/ui/exchange_line.ui index 07c4f92..136cc15 100644 --- a/threedi_schematisation_editor/forms/ui/exchange_line.ui +++ b/threedi_schematisation_editor/forms/ui/exchange_line.ui @@ -32,36 +32,69 @@ - - - - + + + + Qt::Vertical - - Qt::ImhNone + + + 20 + 40 + + + + + + + + true - Channel + General - - - - - + + + + + true - - Qt::ImhNone + + QAbstractSpinBox::NoButtons + + 99999999 + + + true + + + true + + + false + + + + + - Channel ID + x + + + + + + + ID Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - + + 0 @@ -71,46 +104,59 @@ - - Qt::ImhNone - - - false - QAbstractSpinBox::NoButtons + + 3 + + + -99999999.000000000000000 + - 999999999 + 1000000.000000000000000 + + + true true - false + true - - - - - - - true - - - General - - - - + + + + + - x + Exchange level [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 - + + + + Code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + @@ -124,9 +170,12 @@ - + + + + - Code + Display name Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -136,43 +185,55 @@ - - - - true - - - QAbstractSpinBox::NoButtons - - - 99999999 + + + + + 0 + 0 + - - true + + - + true - - false - - - + + + + + + + + + + Qt::ImhNone + + + Channel + + + + + + Qt::ImhNone + - Exchange level [m] + Channel ID Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - + + 0 @@ -182,36 +243,23 @@ - - QAbstractSpinBox::NoButtons + + Qt::ImhNone - - 3 + + false - - -99999999.000000000000000 + + QAbstractSpinBox::NoButtons - 1000000.000000000000000 - - - true + 999999999 true - true - - - - - - - ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + false @@ -219,17 +267,25 @@ - - - Qt::Vertical + + + 0 - - - 20 - 40 - - - + + + + Tag + + + + + + + true + + + + @@ -247,9 +303,6 @@ scrollArea id - code - exchange_level - exchange_level_clear channel_id diff --git a/threedi_schematisation_editor/forms/ui/lateral_1d.ui b/threedi_schematisation_editor/forms/ui/lateral_1d.ui new file mode 100644 index 0000000..9ae3582 --- /dev/null +++ b/threedi_schematisation_editor/forms/ui/lateral_1d.ui @@ -0,0 +1,633 @@ + + + Dialog + + + + 0 + 0 + 552 + 557 + + + + Dialog + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 534 + 510 + + + + + + + + + + Qt::ImhNone + + + 0 + + + + 1D Lateral + + + + + + 0 + + + + + Tag + + + + + + + true + + + + + + + + + true + + + + + + General + + + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 99999999 + + + true + + + true + + + false + + + + + + + + + + Code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Display name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + true + + + + + + Connection node + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -1000.000000000000000 + + + 1000000.000000000000000 + + + true + + + true + + + true + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + Node initial water level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + + + Node code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Node storage area [m²] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 999999999 + + + true + + + true + + + false + + + + + + + x + + + + + + + x + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + 999999999.000000000000000 + + + true + + + + + + + + + + Connection node ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + + + + Qt::ImhNone + + + Timeseries + + + + + + 0 + + + + + Delete selected rows + + + Delete + + + + + + + Copy + + + + + + + Paste rows from clipboard + + + Paste + + + + + + + Add new row + + + Add + + + + + + + + + + 0 + 125 + + + + + 8 + + + + Qt::ImhDigitsOnly + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::SelectRows + + + 23 + + + + + + + 0 + + + + + Interpolate + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + false + + + + + + + + + Table + + + + + + + + + + Qt::ImhNone + + + Time units + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Offset + + + + + + + 0 + + + + + + 0 + 0 + + + + QAbstractSpinBox::NoButtons + + + 999999999 + + + + + + + + + + Units + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + scrollArea + tabWidget + id + code + display_name + timeseries_table + timeseries_table_delete + timeseries_table_copy + timeseries_table_paste + timeseries_table_add + + + + diff --git a/threedi_schematisation_editor/forms/ui/lateral_2d.ui b/threedi_schematisation_editor/forms/ui/lateral_2d.ui new file mode 100644 index 0000000..445884f --- /dev/null +++ b/threedi_schematisation_editor/forms/ui/lateral_2d.ui @@ -0,0 +1,457 @@ + + + Dialog + + + + 0 + 0 + 552 + 557 + + + + Dialog + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 534 + 510 + + + + + + + + + + Qt::ImhNone + + + 0 + + + + 2D Lateral + + + + + + 0 + + + + + Tag + + + + + + + true + + + + + + + + + true + + + + + + General + + + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 99999999 + + + true + + + true + + + false + + + + + + + + + + Code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Display name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + Lateral type + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + + + + + + + + + + + + + Qt::ImhNone + + + Timeseries + + + + + + 0 + + + + + Delete selected rows + + + Delete + + + + + + + Copy + + + + + + + Paste rows from clipboard + + + Paste + + + + + + + Add new row + + + Add + + + + + + + + + + 0 + 125 + + + + + 8 + + + + Qt::ImhDigitsOnly + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::SelectRows + + + 23 + + + + + + + 0 + + + + + Interpolate + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + false + + + + + + + + + Table + + + + + + + Offset + + + + + + + + + + Qt::ImhNone + + + Time units + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + 0 + + + + + + 0 + 0 + + + + QAbstractSpinBox::NoButtons + + + 999999999 + + + + + + + + + + Units + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + scrollArea + tabWidget + id + code + display_name + timeseries_table + timeseries_table_delete + timeseries_table_copy + timeseries_table_paste + timeseries_table_add + + + + diff --git a/threedi_schematisation_editor/forms/ui/manhole.ui b/threedi_schematisation_editor/forms/ui/manhole.ui deleted file mode 100644 index 4f72009..0000000 --- a/threedi_schematisation_editor/forms/ui/manhole.ui +++ /dev/null @@ -1,872 +0,0 @@ - - - ManholeEditForm - - - - 0 - 0 - 550 - 550 - - - - Manhole Edit Form - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - QFrame::NoFrame - - - true - - - - - 0 - 0 - 532 - 503 - - - - - - - Groundwater exchange - - - - - - - 0 - 0 - - - - - - - QAbstractSpinBox::NoButtons - - - 3 - - - 1000000.000000000000000 - - - - - - - x - - - - - - - - - - Exchange thickness [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - - - Hydraulic conductivity in [m/d] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - - - QAbstractSpinBox::NoButtons - - - 3 - - - 1000000.000000000000000 - - - - - - - x - - - - - - - - - - Hydraulic conductivity out [m/d] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - - - QAbstractSpinBox::NoButtons - - - 3 - - - 1000000.000000000000000 - - - - - - - x - - - - - - - - - - true - - - - - - Visualisation - - - - - - - - - Zoom category - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - -1 - - - - - - - - - - Manhole indicator - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - - 0 - 0 - - - - - - - - - - - - 0 - 0 - - - - - - - - - - - - - - true - - - - - - Characteristics - - - - - - - - - Bottom level [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - - 0 - 0 - - - - - - - QAbstractSpinBox::NoButtons - - - 3 - - - 1000000.000000000000000 - - - - - - - - - - Drain level [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - - 8 - - - - x - - - - - - - - 0 - 0 - - - - - - - Qt::ImhNone - - - QAbstractSpinBox::NoButtons - - - 3 - - - -9999999999.000000000000000 - - - 9999999999.000000000000000 - - - - - - - - - - Shape - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - - - Qt::ImhNone - - - QAbstractSpinBox::NoButtons - - - 3 - - - -9999999999.000000000000000 - - - 9999999999.000000000000000 - - - - - - - - - - Width [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - - - QAbstractSpinBox::NoButtons - - - 3 - - - 1000000.000000000000000 - - - - - - - - - - Length [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - - - Surface level [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - - 0 - 0 - - - - - - - Qt::ImhNone - - - QAbstractSpinBox::NoButtons - - - 3 - - - -9999999999.000000000000000 - - - 9999999999.000000000000000 - - - - - - - x - - - - - - - x - - - - - - - - 50 - false - false - - - - x - - - - - - - x - - - - - - - - - - - - - - - - - true - - - - - - Connection node - - - - - - - - - Node initial water level [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - - - - Connection node ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - - - true - - - QAbstractSpinBox::NoButtons - - - - - - - QAbstractSpinBox::NoButtons - - - 3 - - - -999999.000000000000000 - - - 999999999.000000000000000 - - - - - - - - - - Node code - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - - - true - - - - - - - - - - Node storage area [m²] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - QAbstractSpinBox::NoButtons - - - 3 - - - 999999999.000000000000000 - - - - - - - x - - - - - - - x - - - - - - - - - - true - - - - - - General - - - - - - - - - Display name - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - - 0 - 0 - - - - - - - true - - - - - - - - - - ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - - - Code - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - - - true - - - - - - - - - - Calculation type - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - - 0 - 0 - - - - - - - - - - - - - - true - - - QAbstractSpinBox::NoButtons - - - 99999999 - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - scrollArea - id - code - display_name - calculation_type - shape - width - width_clear - length - length_clear - bottom_level - bottom_level_clear - surface_level - surface_level_clear - drain_level - drain_level_clear - zoom_category - manhole_indicator - exchange_thickness - exchange_thickness_clear - hydraulic_conductivity_in - hydraulic_conductivity_in_clear - hydraulic_conductivity_out - hydraulic_conductivity_out_clear - connection_node_id - connection_node_code - connection_node_initial_waterlevel - connection_node_initial_waterlevel_clear - connection_node_storage_area - connection_node_storage_area_clear - - - - diff --git a/threedi_schematisation_editor/forms/ui/measure_location.ui b/threedi_schematisation_editor/forms/ui/measure_location.ui new file mode 100644 index 0000000..938e980 --- /dev/null +++ b/threedi_schematisation_editor/forms/ui/measure_location.ui @@ -0,0 +1,437 @@ + + + Dialog + + + + 0 + 0 + 552 + 557 + + + + Dialog + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 534 + 510 + + + + + + + + + + Qt::ImhNone + + + 0 + + + + Measure Location + + + + + + true + + + + + + Connection node + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -1000.000000000000000 + + + 1000000.000000000000000 + + + true + + + true + + + true + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + Node initial water level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + + + Node code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Node storage area [m²] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 999999999 + + + true + + + true + + + false + + + + + + + x + + + + + + + x + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + 999999999.000000000000000 + + + true + + + + + + + + + + Connection node ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + true + + + + + + General + + + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 99999999 + + + true + + + true + + + false + + + + + + + + + + Code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Display name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + Measure variable + + + 10 + + + + + + + + + + + + + 0 + + + + + Tag + + + + + + + true + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + scrollArea + tabWidget + id + code + display_name + + + + diff --git a/threedi_schematisation_editor/forms/ui/measure_map.ui b/threedi_schematisation_editor/forms/ui/measure_map.ui new file mode 100644 index 0000000..1df1422 --- /dev/null +++ b/threedi_schematisation_editor/forms/ui/measure_map.ui @@ -0,0 +1,408 @@ + + + Dialog + + + + 0 + 0 + 552 + 557 + + + + Dialog + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 534 + 510 + + + + + + + + + + Qt::ImhNone + + + 0 + + + + Measure Map + + + + + + 0 + + + + + Tag + + + + + + + true + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + true + + + + + + General + + + + + + + + + Weight + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 99999999 + + + true + + + true + + + false + + + + + + + + + + ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Display name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + 999999999.000000000000000 + + + true + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + x + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + true + + + + + + Measure location and control + + + + + + + 0 + 0 + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 999999999 + + + true + + + true + + + false + + + + + + + + + + Measure location ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + false + + + + + + + + + + Control type + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + + + Control ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 999999999 + + + true + + + true + + + false + + + + + + + true + + + + + + + + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + scrollArea + tabWidget + id + code + display_name + + + + diff --git a/threedi_schematisation_editor/forms/ui/memory_control.ui b/threedi_schematisation_editor/forms/ui/memory_control.ui new file mode 100644 index 0000000..7d6662d --- /dev/null +++ b/threedi_schematisation_editor/forms/ui/memory_control.ui @@ -0,0 +1,475 @@ + + + Dialog + + + + 0 + 0 + 552 + 557 + + + + Dialog + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 534 + 510 + + + + + + + + + + Qt::ImhNone + + + 0 + + + + Memory Control + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + true + + + + + + General + + + + + + + + + Display name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + + + Code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 99999999 + + + true + + + true + + + false + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + + + + Qt::ImhNone + + + Action + + + + + + + + + Qt::ImhNone + + + Type + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + + + + + Value 1 + + + + + + + Value 2 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + + + + + Is active + + + + + + + Is inverse + + + + + + + Upper threshold + + + + + + + x + + + + + + + Lower threshold + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + + + + + x + + + + + + + + 0 + 0 + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + + + + + x + + + + + + + x + + + + + + + + + + + + + true + + + + + + Target relationship + + + + + + + + + Target type + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 999999999 + + + true + + + true + + + false + + + + + + + + + + Target ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + true + + + + + + + + + + 0 + + + + + Tag + + + + + + + true + + + + + + + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + scrollArea + tabWidget + id + code + display_name + + + + diff --git a/threedi_schematisation_editor/forms/ui/orifice.ui b/threedi_schematisation_editor/forms/ui/orifice.ui index 97844a8..6645f61 100644 --- a/threedi_schematisation_editor/forms/ui/orifice.ui +++ b/threedi_schematisation_editor/forms/ui/orifice.ui @@ -14,6 +14,13 @@ Dialog + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + @@ -27,8 +34,8 @@ 0 0 - 515 - 670 + 553 + 660 @@ -48,121 +55,7 @@ Orifice - - - - true - - - - - - General - - - - - - - - - Display name - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - - 0 - 0 - - - - - - - true - - - - - - - - - - ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - - - Code - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - - - true - - - - - - - - - - true - - - QAbstractSpinBox::NoButtons - - - 99999999 - - - true - - - true - - - false - - - - - - - + @@ -175,7 +68,7 @@ - + 0 @@ -222,7 +115,7 @@ - + 0 @@ -274,176 +167,137 @@ - - - - true + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + Qt::ImhNone + - Visualisation + Cross section definition - - - + + + + + Qt::ImhNone + - Zoom category + Height [m] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - -1 + 10 - - + + - Sewerage + x - - - - - 0 - 0 - + + + + + 0 + 125 + - - + + + 8 + + + + Qt::ImhDigitsOnly + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::SelectRows + + 23 + - - - - - - - true - - - - 0 - 0 - - - - - - - Characteristics - - - - + + + + Qt::ImhNone + - Crest level [m] + Width [m] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - x - - - - - + + x - - + + - + 0 0 - - - - - Qt::ImhNone - QAbstractSpinBox::NoButtons - 4 + 3 1000000.000000000000000 - - true - - - true - - - true - - - - - - - - - - Qt::ImhNone - - - Friction type - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - - - - Qt::ImhNone - - - + + 0 0 - - - QAbstractSpinBox::NoButtons @@ -453,33 +307,36 @@ 1000000.000000000000000 - - true + + + + + + + 0 + 0 + - - true + + - - true + + Qt::ImhNone - - - - - - x + + false - - - - + + + + Table - - + + @@ -487,21 +344,90 @@ Qt::ImhNone - Friction value + Shape Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - + + + + + 0 + + + + Delete selected rows + + + Delete + + + + + + + Copy + + + + + + + Paste rows from clipboard + + + Paste + + + + + + + Add new row + + + Add + + + + + + + + + + + + true + + + + + + General + + + + + + + + + Display name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + 10 - - + + 0 @@ -511,54 +437,39 @@ - - QAbstractSpinBox::NoButtons - - - 3 - - - 1000000.000000000000000 - - - true - - - true - - + true - - - - x + + + + - - - - - Discharge coefficient negative + ID Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - true + + + + + + - - 0 + + Code - - 10 + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - + + 0 @@ -568,20 +479,24 @@ - - Qt::ImhNone + + true - - QAbstractSpinBox::NoButtons + + + + + + - - 3 + + true - - -9999999999.000000000000000 + + QAbstractSpinBox::NoButtons - 9999999999.000000000000000 + 99999999 true @@ -590,76 +505,86 @@ true - true + false - - + + + + + + + true + + + + 0 + 0 + + + + + + + Characteristics + + + + + + x + + + + + + + Qt::ImhNone + - Discharge -coefficient positive - - - Qt::PlainText + Material ID Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - true - - -1 + 10 - - - - + + + + true + + QAbstractSpinBox::NoButtons + + + 99999999 + + + + + - Crest type + Sewerage - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - Qt::ImhNone - - - Cross section definition - - - - + + @@ -667,7 +592,7 @@ coefficient positive Qt::ImhNone - Height [m] + Friction type Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -677,93 +602,123 @@ coefficient positive - - + + x - - - - - 0 - 125 - - - - - 8 - - - - Qt::ImhDigitsOnly - - - Qt::ScrollBarAsNeeded - - - QAbstractScrollArea::AdjustToContents - - - QAbstractItemView::SelectRows - - - 21 - - - - - + + Qt::ImhNone + + + + - Width [m] + Discharge coefficient negative Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - x + + true + + + 0 + + + 10 - - + + - + 0 0 + + + + + Qt::ImhNone + QAbstractSpinBox::NoButtons - 3 + 4 1000000.000000000000000 + + true + + + true + + + true + - - + + + + x + + + + + + + x + + + + + + + + + + Qt::ImhNone + + + Friction value + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 0 + + + 10 + + + + + 0 0 + + + QAbstractSpinBox::NoButtons @@ -773,12 +728,21 @@ coefficient positive 1000000.000000000000000 + + true + + + true + + + true + - - + + - + 0 0 @@ -789,89 +753,217 @@ coefficient positive Qt::ImhNone - - false + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + + + true + + + true + + + true - - + + + + + - Table + Crest type + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - + + - - Qt::ImhNone - - Shape + Discharge +coefficient positive + + + Qt::PlainText Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + true + + + -1 + - - - - 0 + + + + - - - - Delete selected rows - - - Delete - - - - - - - Copy - - - - - - - Paste rows from clipboard - - - Paste - - - - - - - Add new row - - - Add - - - - + + Crest level [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + true + + + true + + + true + + + + + + 0 + + + + + Tag + + + + + + + true + + + + + - + - Connection nodes + Starting connection node - + + + + true + + + + + + 2D exchange + + + + + + + + + Exchange level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + + + + + + + x + + + + + + + Exchange type + + + + + + + + + + Qt::Vertical @@ -884,29 +976,818 @@ coefficient positive - - + + true + + + - Ending connection node + General - - - + + + + + + + + ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 999999999 + + + + + + + + 0 + 0 + + + + + + + + + + + + + + Display name + + + + + + + + + + Code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + + + + + + + + Visualisation + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + + + Groundwater exchange + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + + + + + x + + + + + + + + + + Exchange thickness [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Hydraulic conductivity in [m/d] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + + + + + x + + + + + + + + + + Hydraulic conductivity out [m/d] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + + + + + x + + + + + + + + + + Initial water level + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -99999999.000000000000000 + + + 999999999.000000000000000 + + + + + + + + + + Initial water level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + x + + + + + + + + + + Administrative properties + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + + + + + + + + + + Manhole surface level [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + 50 + false + false + + + + x + + + + + + + + + + Dimensions + + + + + + x + + + + + + + + + + Storage area [m²] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + + + Bottom level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -99999999.000000000000000 + + + 999999999.000000000000000 + + + + + + + + 8 + + + + x + + + + + + + + + + 0 + + + + + Tag + + + + + + + true + + + Open this feature's own attribute form to see its tags + + + + + + + + + + Ending connection node + + + + + + Dimensions + + + + + + x + + + + + + + + + + Storage area [m²] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + + + Bottom level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -99999999.000000000000000 + + + 999999999.000000000000000 + + + + + + + + 8 + + + + x + + + + + + + + + + true + + + + + + 2D exchange + + + + + + + + + Exchange level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + + + + + + + x + + + + + + + Exchange type + + + + + + + + + + + + + Administrative properties + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + + + + + + + + + + Manhole surface level [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + 50 + false + false + + + + x + + + + + + + + + + true + + + + + + General + + + + + + + - Node initial water level [m] + ID Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - 10 - - + @@ -914,6 +1795,9 @@ coefficient positive 0 + + + true @@ -923,38 +1807,38 @@ coefficient positive 999999999 - - true - - - false - - - - - Connection node ID + + + + + 0 + 0 + - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + - - + + + + + - Node code - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + Display name - + + + + - Node storage area [m²] + Code Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -964,7 +1848,7 @@ coefficient positive - + @@ -972,98 +1856,115 @@ coefficient positive 0 - - true + + - - - - - 0 - 0 - - - - QAbstractSpinBox::NoButtons + + + + - - 3 + + Visualisation - - 999999999.000000000000000 + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - true + + -1 - - + + + + + + + Qt::Vertical + + + + 505 + 199 + + + + + + + + Groundwater exchange + + + + 0 0 + + + QAbstractSpinBox::NoButtons 3 - - -1000.000000000000000 - 1000000.000000000000000 - - true - - - true - - - true - - - + + x - - + + + + + - x + Exchange thickness [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - true - - - Starting connection node - - - - + + + + + + + Hydraulic conductivity in [m/d] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + 0 0 + + + QAbstractSpinBox::NoButtons @@ -1071,105 +1972,82 @@ coefficient positive 3 - 999999999.000000000000000 - - - true - - - - - - - - 0 - 0 - - - - true + 1000000.000000000000000 - - + + - Node initial water level [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 + x - - + + + + + - Node storage area [m²] + Hydraulic conductivity out [m/d] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - 10 - - - + + 0 0 - - true + + QAbstractSpinBox::NoButtons - - 999999999 - - - true - - - false - - - - - - - Node code + + 3 - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + 1000000.000000000000000 - - + + - Connection node ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + x - - + + + + + + + Initial water level + + + + 0 0 + + + + + Qt::ImhNone + QAbstractSpinBox::NoButtons @@ -1177,31 +2055,31 @@ coefficient positive 3 - -1000.000000000000000 + -99999999.000000000000000 - 1000000.000000000000000 - - - true - - - true - - - true + 999999999.000000000000000 - - + + + + + - x + Initial water level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 - - + + x @@ -1210,6 +2088,30 @@ coefficient positive + + + + 0 + + + + + Tag + + + + + + + true + + + Open this feature's own attribute form to see its tags + + + + + @@ -1218,13 +2120,6 @@ coefficient positive - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - @@ -1243,8 +2138,6 @@ coefficient positive friction_type discharge_coefficient_negative discharge_coefficient_negative_clear - zoom_category - sewerage cross_section_shape cross_section_width cross_section_width_clear @@ -1255,20 +2148,8 @@ coefficient positive cross_section_table_copy cross_section_table_paste cross_section_table_add - connection_node_start_id - connection_node_end_id - connection_node_1_id - connection_node_1_code - connection_node_1_initial_waterlevel - connection_node_1_initial_waterlevel_clear - connection_node_1_storage_area - connection_node_1_storage_area_clear - connection_node_2_id - connection_node_2_code - connection_node_2_initial_waterlevel - connection_node_2_initial_waterlevel_clear - connection_node_2_storage_area - connection_node_2_storage_area_clear + connection_node_id_start + connection_node_id_end diff --git a/threedi_schematisation_editor/forms/ui/pipe.ui b/threedi_schematisation_editor/forms/ui/pipe.ui index 30c32c7..b6ac66c 100644 --- a/threedi_schematisation_editor/forms/ui/pipe.ui +++ b/threedi_schematisation_editor/forms/ui/pipe.ui @@ -28,7 +28,7 @@ 0 0 515 - 820 + 811 @@ -48,132 +48,7 @@ Pipe - - - - - - - Qt::ImhNone - - - Connection nodes - - - - - - - 0 - 0 - - - - - - - Qt::ImhNone - - - true - - - QAbstractSpinBox::NoButtons - - - 999999999 - - - true - - - false - - - - - - - - - - Qt::ImhNone - - - Start connection node ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - - - Qt::ImhNone - - - true - - - QAbstractSpinBox::NoButtons - - - 999999999 - - - true - - - false - - - - - - - - - - Qt::ImhNone - - - End connection node ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - + @@ -340,7 +215,7 @@ QAbstractItemView::SelectRows - 21 + 23 @@ -391,8 +266,8 @@ - - + + @@ -400,55 +275,37 @@ Qt::ImhNone - General + Characteristics - - - - - - - - Qt::ImhNone - + + + - ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + x - - + + Qt::ImhNone - - true - - - QAbstractSpinBox::NoButtons - - - 99999999 - - - true + + Friction type - - true + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - false + + 10 - - + + @@ -456,18 +313,15 @@ Qt::ImhNone - Calculation type + Start invert level Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - 10 - - - + + @@ -475,28 +329,15 @@ Qt::ImhNone - Code + End invert level Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - Distance between calculation points [m] - - - true - - - 10 - - - - - + + 0 @@ -516,10 +357,10 @@ 3 - -9999999999.000000000000000 + -99999999.000000000000000 - 9999999999.000000000000000 + 99999999.000000000000000 true @@ -532,27 +373,15 @@ - - - - - - - Qt::ImhNone - + + - Display name - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 + x - - + + 0 @@ -565,20 +394,31 @@ Qt::ImhNone - + + QAbstractSpinBox::NoButtons + + + 3 + + + -99999999.000000000000000 + + + 99999999.000000000000000 + + true - - - - - - x + + true + + + true - - + + 0 @@ -591,42 +431,62 @@ Qt::ImhNone - - - - - - - 0 - 0 - + + QAbstractSpinBox::NoButtons + + + 4 + + + 1000000.000000000000000 + + + true + + + true + + + true + + + + + + + x + + + + Qt::ImhNone - - true + + Sewerage type + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + + + Qt::ImhNone - - - - - - - - - - Qt::ImhNone - - - Characteristics - - @@ -646,8 +506,27 @@ - - + + + + + + + Qt::ImhNone + + + Material ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + 0 @@ -660,31 +539,45 @@ Qt::ImhNone + + + + + + true + QAbstractSpinBox::NoButtons - - 3 - - - -99999999.000000000000000 - - 99999999.000000000000000 - - - true - - - true - - - true + 99999999 - - + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Groundwater exchange + + + + 0 @@ -694,65 +587,31 @@ - - Qt::ImhNone - QAbstractSpinBox::NoButtons - 4 + 3 1000000.000000000000000 - - true - - - true - - - true - - - + + x - - - - - - - Qt::ImhNone - - - Friction type - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - + - - Qt::ImhNone - - Start invert level + Exchange thickness [m] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -760,23 +619,20 @@ - + - - Qt::ImhNone - - End invert level + Hydraulic conductivity in [m/d] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - + + 0 @@ -786,90 +642,71 @@ - - Qt::ImhNone - QAbstractSpinBox::NoButtons 3 - - -99999999.000000000000000 - - 99999999.000000000000000 - - - true - - - true - - - true + 1000000.000000000000000 - - + + x - - + + - - Qt::ImhNone - - Material + Hydraulic conductivity out [m/d] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - 10 - - - - - x + + + + + 0 + 0 + - - - - - - Qt::ImhNone + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 - - - - - - - Qt::ImhNone + + + + x - - + + @@ -877,11 +714,42 @@ Qt::ImhNone - Visualisation + Connection nodes - - - + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + true + + + QAbstractSpinBox::NoButtons + + + 999999999 + + + true + + + false + + + + + @@ -889,18 +757,15 @@ Qt::ImhNone - Zoom category + Start connection node ID Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - -1 - - + 0 @@ -913,10 +778,25 @@ Qt::ImhNone + + true + + + QAbstractSpinBox::NoButtons + + + 999999999 + + + true + + + false + - + @@ -924,7 +804,7 @@ Qt::ImhNone - Sewerage type + End connection node ID Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -934,88 +814,102 @@ - - - - - 0 - 0 - - + + + + + + + + + + Qt::ImhNone + + + General + + + + Qt::ImhNone + + ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + - - - - - - - Groundwater exchange - - - - - - - 0 - 0 - - + + + + Qt::ImhNone + + + true + QAbstractSpinBox::NoButtons - - 3 - - 1000000.000000000000000 + 99999999 - - - - - - x + + true + + + true + + + false - - + + + + Qt::ImhNone + - Exchange thickness [m] + Code Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - + + + + Qt::ImhNone + - Hydraulic conductivity in [m/d] + Display name Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + 10 + - - + + 0 @@ -1025,39 +919,67 @@ - - QAbstractSpinBox::NoButtons - - - 3 + + Qt::ImhNone - - 1000000.000000000000000 + + true - - - - x + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + true - - + + + + Qt::ImhNone + - Hydraulic conductivity out [m/d] + Exchange type Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + -1 + - - + + + + Distance between calculation points [m] + + + true + + + 10 + + + + + 0 @@ -1067,84 +989,94 @@ + + Qt::ImhNone + QAbstractSpinBox::NoButtons 3 + + -9999999999.000000000000000 + - 1000000.000000000000000 + 9999999999.000000000000000 + + + true + + + true + + + true - - + + x + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + + + + + 0 + + + + + Tag + + + + + + + true + + + + + - Starting manhole + Starting connection node - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - true - + + - Visualisation + Initial water level - - - - - Zoom category - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - -1 - - - - - - - Manhole indicator - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - + + + 0 @@ -1154,198 +1086,82 @@ - - - - - - - 0 - 0 - - - - - - - - - - - true - - - General - - - - - - Display name + + Qt::ImhNone - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + QAbstractSpinBox::NoButtons - - 10 + + 3 - - - - - - - 0 - 0 - + + -99999999.000000000000000 - - true + + 999999999.000000000000000 - - - ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - Code - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - true + + + - - - - - Calculation type + Initial water level [m MSL] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - 10 - - - - - - - - 0 - 0 - + -1 - - - - true - - - QAbstractSpinBox::NoButtons - - - 99999999 - - - true - - - true - - - false + + + + x - - - - true + + + + Qt::Vertical + + + + 20 + 40 + + + + + - Connection node + Dimensions - - - + + + - Node initial water level [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - - 0 - 0 - - - - QAbstractSpinBox::NoButtons - - - 3 - - - 999999999.000000000000000 - - - true - - - - - - - - 0 - 0 - - - - true + x - + + + + - Node storage area [m²] + Storage area [m²] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -1355,24 +1171,36 @@ - - + + + + + - Node code + Bottom level [m MSL] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + -1 + - - + + 0 0 + + + + + Qt::ImhNone + QAbstractSpinBox::NoButtons @@ -1380,66 +1208,48 @@ 3 - -1000.000000000000000 + -9999999999.000000000000000 - 1000000.000000000000000 - - - true - - - true - - - true - - - - - - - Connection node ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + 9999999999.000000000000000 - - + + 0 0 - - true + + + + + Qt::ImhNone QAbstractSpinBox::NoButtons - - 999999999 + + 3 - - true + + -99999999.000000000000000 - - false + + 999999999.000000000000000 - - - - x + + + + + 8 + - - - - x @@ -1448,142 +1258,83 @@ - - + + true + + + - Characteristics + General - - - - - - 0 - 0 - - - - QAbstractSpinBox::NoButtons - - - 3 - - - 1000000.000000000000000 - - - true - - - true - - - true + + + + + - - - - - Bottom level [m] + ID Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - 10 - - - + + 0 0 - - QAbstractSpinBox::NoButtons - - - 3 - - - -99999999.000000000000000 - - - 1000000.000000000000000 + + - + true - - true + + QAbstractSpinBox::NoButtons - - true + + 999999999 - - + + 0 0 - - QAbstractSpinBox::NoButtons - - - 3 - - - -99999999.000000000000000 - - - 1000000.000000000000000 - - - true - - - true - - - true + + - - - - Length [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - + + - - + + - Width [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + Display name - - + + + + + - Drain level [m] + Code Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -1593,79 +1344,80 @@ - - + + 0 0 - - QAbstractSpinBox::NoButtons - - - 3 - - - 1000000.000000000000000 - - - true - - - true - - - true + + - - + + + + + - Surface level [m] + Visualisation Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - 10 - - - - - - - x + -1 - - - - x + + + + + + + true + + + + + + 2D exchange + + + + + + - - - - - Shape + Exchange level [m MSL] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + -1 + - - + + 0 0 + + + + + Qt::ImhNone + QAbstractSpinBox::NoButtons @@ -1673,57 +1425,41 @@ 3 - -99999999.000000000000000 + -9999999999.000000000000000 - 1000000.000000000000000 - - - true - - - true - - - true - - - - - - - x + 9999999999.000000000000000 - - + + x - - + + - x + Exchange type - + - - + + Groundwater exchange - + - + 0 @@ -1745,14 +1481,14 @@ - + x - + @@ -1765,7 +1501,7 @@ - + @@ -1778,7 +1514,7 @@ - + 0 @@ -1800,14 +1536,14 @@ - + x - + @@ -1820,7 +1556,7 @@ - + 0 @@ -1842,7 +1578,7 @@ - + x @@ -1851,248 +1587,176 @@ - - - - - Ending manhole - - - - - - true - + + - General + Administrative properties - - - - - Display name - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - - 0 - 0 - - - - true - - - - - - - ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - Code - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - + + + 0 0 - - true - - - - - - - Calculation type - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - - 0 - 0 - + + - - - - - - true + + Qt::ImhNone QAbstractSpinBox::NoButtons - - 99999999 - - - true + + 3 - - true + + -9999999999.000000000000000 - - false + + 9999999999.000000000000000 - - - - - - - true - - - Visualisation - - - - - Zoom category - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - -1 - - - - - - - - 0 - 0 - - + - - - - - Manhole indicator + Manhole surface level [m] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - 10 + -1 - - - - - 0 - 0 - + + + + + 50 + false + false + + + + x - - - - true + + + + 0 + + + + Tag + + + + + + + true + + + Open this feature's own attribute form to see its tags + + + + + + + + + + Ending connection node + + + + - Characteristics + Groundwater exchange - - - - - Length [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - + + + 0 0 + + + QAbstractSpinBox::NoButtons 3 - - -99999999.000000000000000 - 1000000.000000000000000 - - true + + + + + + x - - true + + + + + + - - true + + Exchange thickness [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - + + + + + + + Hydraulic conductivity in [m/d] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + 0 0 + + + QAbstractSpinBox::NoButtons @@ -2102,58 +1766,39 @@ 1000000.000000000000000 - - true - - - true - - - true - - - + + - Surface level [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 + x - - + + + + + - Drain level [m] + Hydraulic conductivity out [m/d] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - 10 - - - - - - - x - - - + + 0 0 + + + QAbstractSpinBox::NoButtons @@ -2163,31 +1808,38 @@ 1000000.000000000000000 - - true - - - true - - - true - - - + + - Width [m] + x - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + Dimensions + + + + + + x - + + + + - Bottom level [m] + Storage area [m²] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -2198,23 +1850,35 @@ - + + + + - Shape + Bottom level [m MSL] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + -1 + - - + + 0 0 + + + + + Qt::ImhNone + QAbstractSpinBox::NoButtons @@ -2222,30 +1886,27 @@ 3 - -99999999.000000000000000 + -9999999999.000000000000000 - 1000000.000000000000000 - - - true - - - true - - - true + 9999999999.000000000000000 - - + + 0 0 + + + + + Qt::ImhNone + QAbstractSpinBox::NoButtons @@ -2256,147 +1917,118 @@ -99999999.000000000000000 - 1000000.000000000000000 - - - true - - - true - - - true - - - - - - - x - - - - - - - x + 999999999.000000000000000 - - - - x + + + + + 8 + - - - - x - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - true - + + - Connection node + Administrative properties - - - + + + 0 0 - - true + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 - - - - - - Node code + + -9999999999.000000000000000 - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + 9999999999.000000000000000 - - + + + + + - Node storage area [m²] + Manhole surface level [m] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - 10 + -1 - - - Node initial water level [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + 50 + false + false + - - 10 + + x - - - - - 0 - 0 - - - - QAbstractSpinBox::NoButtons - - - 3 + + + + + + + true + + + + + + General + + + + + + - - 999999999.000000000000000 + + ID - - true + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - + @@ -2404,6 +2036,9 @@ 0 + + + true @@ -2413,80 +2048,49 @@ 999999999 - - true - - - false - - - - - Connection node ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - + + 0 0 - - QAbstractSpinBox::NoButtons - - - 3 - - - -1000.000000000000000 - - - 1000000.000000000000000 - - - true - - - true - - - true + + - - + + + + + - x + Display name - - + + + + + - x + Code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 - - - - - - - Groundwater exchange - - - - + + 0 @@ -2496,52 +2100,57 @@ - - QAbstractSpinBox::NoButtons - - - 3 - - - 1000000.000000000000000 - - - - - - - x - - - + + - Exchange thickness [m] + Visualisation Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + -1 + - - + + + + + + + true + + + + + + 2D exchange + + + + - Hydraulic conductivity in [m/d] + Exchange level [m MSL] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + -1 + - - + + 0 @@ -2551,39 +2160,64 @@ + + Qt::ImhNone + QAbstractSpinBox::NoButtons 3 + + -9999999999.000000000000000 + - 1000000.000000000000000 + 9999999999.000000000000000 - - + + x - - - - - + + - Hydraulic conductivity out [m/d] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + Exchange type - - + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Initial water level + + + + 0 @@ -2593,19 +2227,41 @@ + + Qt::ImhNone + QAbstractSpinBox::NoButtons 3 + + -99999999.000000000000000 + - 1000000.000000000000000 + 999999999.000000000000000 - - + + + + + + + Initial water level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + x @@ -2614,6 +2270,30 @@ + + + + 0 + + + + + Tag + + + + + + + true + + + Open this feature's own attribute form to see its tags + + + + + @@ -2637,19 +2317,13 @@ id code display_name - calculation_type - dist_calc_points - dist_calc_points_clear - invert_level_start_point - invert_level_start_point_clear - invert_level_end_point - invert_level_end_point_clear + invert_level_start + invert_level_start_clear + invert_level_end + invert_level_end_clear friction_value friction_value_clear friction_type - material - zoom_category - sewerage_type exchange_thickness exchange_thickness_clear hydraulic_conductivity_in @@ -2666,66 +2340,8 @@ cross_section_table_copy cross_section_table_paste cross_section_table_add - connection_node_start_id - connection_node_end_id - manhole_1_id - manhole_1_code - manhole_1_display_name - manhole_1_calculation_type - manhole_1_shape - manhole_1_width - manhole_1_width_clear - manhole_1_length - manhole_1_length_clear - manhole_1_bottom_level - manhole_1_bottom_level_clear - manhole_1_surface_level - manhole_1_surface_level_clear - manhole_1_drain_level - manhole_1_drain_level_clear - manhole_1_zoom_category - manhole_1_manhole_indicator - manhole_1_exchange_thickness - manhole_1_exchange_thickness_clear - manhole_1_hydraulic_conductivity_in - manhole_1_hydraulic_conductivity_in_clear - manhole_1_hydraulic_conductivity_out - manhole_1_hydraulic_conductivity_out_clear - connection_node_1_id - connection_node_1_code - connection_node_1_initial_waterlevel - connection_node_1_initial_waterlevel_clear - connection_node_1_storage_area - connection_node_1_storage_area_clear - manhole_2_id - manhole_2_code - manhole_2_display_name - manhole_2_calculation_type - manhole_2_shape - manhole_2_width - manhole_2_width_clear - manhole_2_length - manhole_2_length_clear - manhole_2_bottom_level - manhole_2_bottom_level_clear - manhole_2_surface_level - manhole_2_surface_level_clear - manhole_2_drain_level - manhole_2_drain_level_clear - manhole_2_zoom_category - manhole_2_manhole_indicator - manhole_2_exchange_thickness - manhole_2_exchange_thickness_clear - manhole_2_hydraulic_conductivity_in - manhole_2_hydraulic_conductivity_in_clear - manhole_2_hydraulic_conductivity_out - manhole_2_hydraulic_conductivity_out_clear - connection_node_2_id - connection_node_2_code - connection_node_2_initial_waterlevel - connection_node_2_initial_waterlevel_clear - connection_node_2_storage_area - connection_node_2_storage_area_clear + connection_node_id_start + connection_node_id_end diff --git a/threedi_schematisation_editor/forms/ui/potential_breach.ui b/threedi_schematisation_editor/forms/ui/potential_breach.ui index c01a60f..5eed5d2 100644 --- a/threedi_schematisation_editor/forms/ui/potential_breach.ui +++ b/threedi_schematisation_editor/forms/ui/potential_breach.ui @@ -32,6 +32,19 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -116,7 +129,7 @@ - Max breach depth [m] + Fianl breach depth [m] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -204,7 +217,7 @@ - + 0 @@ -238,14 +251,14 @@ - + x - + 0 @@ -279,7 +292,7 @@ - + x @@ -317,7 +330,7 @@ - Exchange level [m MSL] + Initial exchange level [m MSL] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -331,17 +344,25 @@ - - - Qt::Vertical + + + 0 - - - 20 - 40 - - - + + + + Tag + + + + + + + true + + + + @@ -361,10 +382,10 @@ id code display_name - exchange_level - exchange_level_clear - maximum_breach_depth - maximum_breach_depth_clear + initial_exchange_level + initial_exchange_level_clear + final_exchange_level + final_exchange_level_clear levee_material channel_id diff --git a/threedi_schematisation_editor/forms/ui/pumpstation.ui b/threedi_schematisation_editor/forms/ui/pump.ui similarity index 91% rename from threedi_schematisation_editor/forms/ui/pumpstation.ui rename to threedi_schematisation_editor/forms/ui/pump.ui index dda6fa5..75a6a76 100644 --- a/threedi_schematisation_editor/forms/ui/pumpstation.ui +++ b/threedi_schematisation_editor/forms/ui/pump.ui @@ -1,7 +1,7 @@ - PumpstationEditForm - + PumpEditForm + 0 @@ -11,7 +11,7 @@ - Pumpstation without end node + Pump @@ -32,7 +32,134 @@ - + + + + true + + + + + + General + + + + + + + + + Display name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 99999999 + + + true + + + true + + + false + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + true @@ -79,7 +206,7 @@ - Node initial water level [m] + Node initial water level [m MSL] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -118,7 +245,7 @@ - + 0 @@ -215,7 +342,7 @@ - + x @@ -231,6 +358,27 @@ + + + + 0 + + + + + Tag + + + + + + + true + + + + + @@ -243,8 +391,44 @@ Characteristics - - + + + + x + + + + + + + + + + Start level [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Capacity [L/s] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + 0 @@ -254,9 +438,6 @@ - - Qt::ImhNone - QAbstractSpinBox::NoButtons @@ -264,10 +445,10 @@ 3 - -9999999999.000000000000000 + -99999999.000000000000000 - 9999999999.000000000000000 + 1000000.000000000000000 true @@ -280,46 +461,27 @@ - - - - x - - - - - + + - - Start level [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - + + - Lower stop level [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + Sewerage - - + + - Type + Upper stop level [m] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -329,24 +491,29 @@ - - - - - + + - Capacity [L/s] + x - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + x - - 10 + + + + + + x - - + + 0 @@ -382,22 +549,8 @@ - - - - x - - - - - - - x - - - - - + + 0 @@ -407,6 +560,9 @@ + + Qt::ImhNone + QAbstractSpinBox::NoButtons @@ -414,10 +570,10 @@ 3 - -99999999.000000000000000 + -9999999999.000000000000000 - 1000000.000000000000000 + 9999999999.000000000000000 true @@ -430,20 +586,7 @@ - - - - - - - Upper stop level [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - + @@ -477,49 +620,13 @@ - - - - x - - - - - - - - - - - - - - - - - true - - - - - - Visualisation - - - - - - Sewerage - - - - - + + - Zoom category + Type Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -529,149 +636,22 @@ - - - - - 0 - 0 - - - - - - - - - - - - - - true - - - - - - General - - - - - - - - - Display name - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - - 0 - 0 - - - - - - - true - - - - - - - - - - ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - + - Code + Lower stop level [m] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - 0 - 0 - - - - - - - true - - - - - - - - - - true - - - QAbstractSpinBox::NoButtons - - - 99999999 - - - true - - - true - - - false - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -694,17 +674,12 @@ start_level_clear lower_stop_level lower_stop_level_clear - upper_stop_level - upper_stop_level_clear capacity capacity_clear - type - zoom_category - sewerage connection_node_id connection_node_code - connection_node_initial_waterlevel - connection_node_initial_waterlevel_clear + connection_node_initial_water_level + connection_node_initial_water_level_clear connection_node_storage_area connection_node_storage_area_clear diff --git a/threedi_schematisation_editor/forms/ui/pump_map.ui b/threedi_schematisation_editor/forms/ui/pump_map.ui new file mode 100644 index 0000000..6514d01 --- /dev/null +++ b/threedi_schematisation_editor/forms/ui/pump_map.ui @@ -0,0 +1,678 @@ + + + PumpMapEditForm + + + + 0 + 0 + 550 + 550 + + + + Pump map + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 532 + 503 + + + + + + + + + + Qt::ImhNone + + + 0 + + + + Pump map + + + + + + true + + + + + + Pump characteristics + + + + + + + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + Type + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + + + true + + + true + + + true + + + + + + + x + + + + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 99999999 + + + true + + + true + + + false + + + + + + + + + + Start level [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + -99999999.000000000000000 + + + 1000000.000000000000000 + + + true + + + true + + + true + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + + + true + + + true + + + true + + + + + + + x + + + + + + + + + + Display name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + + + Code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + x + + + + + + + Qt::LeftToRight + + + Sewerage + + + + + + + + + + Lower stop level [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 10 + + + + + + + + + + + ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + -99999999.000000000000000 + + + 1000000.000000000000000 + + + true + + + true + + + true + + + + + + + + + + Upper stop level [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + + + Capacity [L/s] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + x + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + + + + Qt::ImhNone + + + End connection node ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + true + + + + + + General + + + + + + + + + ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Display name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 99999999 + + + true + + + true + + + false + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + Code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + true + + + QAbstractSpinBox::NoButtons + + + 999999999 + + + true + + + false + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 10 + + + + + + + + 0 + + + + + Tag + + + + + + + true + + + + + + + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + scrollArea + tabWidget + id + code + display_name + pump_id + pump_code + pump_display_name + pump_start_level + pump_start_level_clear + pump_lower_stop_level + pump_lower_stop_level_clear + pump_capacity + pump_capacity_clear + + + + diff --git a/threedi_schematisation_editor/forms/ui/pumpstation_map.ui b/threedi_schematisation_editor/forms/ui/pumpstation_map.ui deleted file mode 100644 index 2dd63c5..0000000 --- a/threedi_schematisation_editor/forms/ui/pumpstation_map.ui +++ /dev/null @@ -1,1126 +0,0 @@ - - - PumpstationMapEditForm - - - - 0 - 0 - 550 - 550 - - - - Pumpstation with end node - - - - - - QFrame::NoFrame - - - true - - - - - 0 - 0 - 532 - 503 - - - - - - - - - - Qt::ImhNone - - - 0 - - - - Pumpstation map - - - - - - Qt::Vertical - - - QSizePolicy::MinimumExpanding - - - - 20 - 10 - - - - - - - - - - - Qt::ImhNone - - - Connection nodes - - - - - - - 0 - 0 - - - - - - - Qt::ImhNone - - - true - - - QAbstractSpinBox::NoButtons - - - 999999999 - - - true - - - false - - - - - - - - - - Qt::ImhNone - - - Start connection node ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - - - Qt::ImhNone - - - true - - - QAbstractSpinBox::NoButtons - - - 999999999 - - - true - - - false - - - - - - - - - - Qt::ImhNone - - - End connection node ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - - - - true - - - - - - General - - - - - - - - - true - - - QAbstractSpinBox::NoButtons - - - 99999999 - - - true - - - true - - - false - - - - - - - - - - Display name - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - - - - ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - - - true - - - - - - - - - - Code - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - - - true - - - - - - - - - - true - - - - - - Pumpstation characteristics - - - - - - Qt::Vertical - - - QSizePolicy::MinimumExpanding - - - - 20 - 10 - - - - - - - - x - - - - - - - - - - Type - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - - - - Lower stop level [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - Qt::Vertical - - - QSizePolicy::MinimumExpanding - - - - 20 - 10 - - - - - - - - Qt::LeftToRight - - - Sewerage - - - - - - - - - - Zoom category - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - -1 - - - - - - - - 0 - 0 - - - - - - - Qt::ImhNone - - - QAbstractSpinBox::NoButtons - - - 3 - - - -9999999999.000000000000000 - - - 9999999999.000000000000000 - - - true - - - true - - - true - - - - - - - - - - Display name - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - x - - - - - - - - 0 - 0 - - - - - - - QAbstractSpinBox::NoButtons - - - 3 - - - -99999999.000000000000000 - - - 1000000.000000000000000 - - - true - - - true - - - true - - - - - - - - 0 - 0 - - - - - - - QAbstractSpinBox::NoButtons - - - 3 - - - -99999999.000000000000000 - - - 1000000.000000000000000 - - - true - - - true - - - true - - - - - - - - 0 - 0 - - - - - - - Qt::ImhNone - - - QAbstractSpinBox::NoButtons - - - 3 - - - -9999999999.000000000000000 - - - 9999999999.000000000000000 - - - true - - - true - - - true - - - - - - - - - - Start level [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - - - Code - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - - - Upper stop level [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - - - Capacity [L/s] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - - - - ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - x - - - - - - - x - - - - - - - - - - true - - - QAbstractSpinBox::NoButtons - - - 99999999 - - - true - - - true - - - false - - - - - - - - 0 - 0 - - - - - - - true - - - - - - - - 0 - 0 - - - - - - - true - - - - - - - - - - - - - - - 0 - 0 - - - - - - - - - - - - - - - Connection nodes - - - - - - true - - - Ending connection node - - - - - - Node storage area [m²] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - - 0 - 0 - - - - true - - - QAbstractSpinBox::NoButtons - - - 999999999 - - - true - - - false - - - - - - - Connection node ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - QAbstractSpinBox::NoButtons - - - 3 - - - 999999999.000000000000000 - - - true - - - - - - - - 0 - 0 - - - - QAbstractSpinBox::NoButtons - - - 3 - - - -1000.000000000000000 - - - 1000000.000000000000000 - - - true - - - true - - - true - - - - - - - Node initial water level [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - Node code - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - true - - - - - - - x - - - - - - - x - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - true - - - Starting connection node - - - - - - Connection node ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - true - - - - - - - - 0 - 0 - - - - QAbstractSpinBox::NoButtons - - - 3 - - - 999999999.000000000000000 - - - true - - - - - - - Node initial water level [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - Node code - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - QAbstractSpinBox::NoButtons - - - 3 - - - -1000.000000000000000 - - - 1000000.000000000000000 - - - true - - - true - - - true - - - - - - - Node storage area [m²] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - - 0 - 0 - - - - true - - - QAbstractSpinBox::NoButtons - - - 999999999 - - - true - - - false - - - - - - - x - - - - - - - x - - - - - - - - - - - - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - scrollArea - tabWidget - id - code - display_name - pumpstation_id - pumpstation_code - pumpstation_display_name - pumpstation_start_level - pumpstation_start_level_clear - pumpstation_lower_stop_level - pumpstation_lower_stop_level_clear - pumpstation_upper_stop_level - pumpstation_upper_stop_level_clear - pumpstation_capacity - pumpstation_capacity_clear - pumpstation_type - pumpstation_zoom_category - pumpstation_sewerage - connection_node_start_id - connection_node_end_id - connection_node_1_id - connection_node_1_code - connection_node_1_initial_waterlevel - connection_node_1_initial_waterlevel_clear - connection_node_1_storage_area - connection_node_1_storage_area_clear - connection_node_2_id - connection_node_2_code - connection_node_2_initial_waterlevel - connection_node_2_initial_waterlevel_clear - connection_node_2_storage_area - connection_node_2_storage_area_clear - - - - diff --git a/threedi_schematisation_editor/forms/ui/surface.ui b/threedi_schematisation_editor/forms/ui/surface.ui new file mode 100644 index 0000000..abab0f9 --- /dev/null +++ b/threedi_schematisation_editor/forms/ui/surface.ui @@ -0,0 +1,254 @@ + + + Form + + + + 0 + 0 + 548 + 543 + + + + Form + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 530 + 496 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 0 + + + + + Tag + + + + + + + true + + + + + + + + + true + + + + + + General + + + + + + + + + Display name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 99999999 + + + true + + + true + + + false + + + + + + + + + + Area + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + QAbstractSpinBox::NoButtons + + + 10000000000000000.000000000000000 + + + + + + + + + + 0 + + + + + Surface parameter ID + + + + + + + + 0 + 0 + + + + + + + + + + + + + + scrollArea + + + + diff --git a/threedi_schematisation_editor/forms/ui/surface_map.ui b/threedi_schematisation_editor/forms/ui/surface_map.ui index d6f4c45..baa1120 100644 --- a/threedi_schematisation_editor/forms/ui/surface_map.ui +++ b/threedi_schematisation_editor/forms/ui/surface_map.ui @@ -42,6 +42,19 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -54,7 +67,38 @@ General - + + + + + 0 + 0 + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 999999999 + + + true + + + true + + + false + + + + @@ -70,31 +114,16 @@ - - - - - 0 - 0 - - + + - - Qt::ImhNone - - - QAbstractSpinBox::NoButtons - - - 2 - - - 0.000000000000000 + + ID - - 100.000000000000000 + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -120,8 +149,21 @@ - - + + + + + + + Connection node ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + 0 @@ -151,39 +193,64 @@ - - + + - ID + Percentage Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + 10 + - - + + + + + 0 + 0 + + - - Connection node ID + + Qt::ImhNone - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + QAbstractSpinBox::NoButtons + + + 2 + + + 0.000000000000000 + + + 100.000000000000000 + + + + + + + x - + - Percentage + Display name Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -193,15 +260,8 @@ - - - - x - - - - - + + 0 @@ -211,23 +271,37 @@ - + true - - QAbstractSpinBox::NoButtons + + + + + + - - 999999999 + + Code - - true + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - true + + + + + + + 0 + 0 + - - false + + + + + true @@ -235,17 +309,25 @@ - - - Qt::Vertical - - - - 20 - 40 - + + + 0 - + + + + Tag + + + + + + + true + + + + @@ -257,8 +339,6 @@ scrollArea id connection_node_id - percentage - percentage_clear surface_id diff --git a/threedi_schematisation_editor/forms/ui/table_control.ui b/threedi_schematisation_editor/forms/ui/table_control.ui new file mode 100644 index 0000000..ed35786 --- /dev/null +++ b/threedi_schematisation_editor/forms/ui/table_control.ui @@ -0,0 +1,467 @@ + + + Dialog + + + + 0 + 0 + 550 + 550 + + + + Dialog + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 532 + 503 + + + + + + + + + + Qt::ImhNone + + + 0 + + + + Table Control + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + true + + + + + + General + + + + + + + + + Display name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + + + + + + + + ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 99999999 + + + true + + + true + + + false + + + + + + + + + + Code + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + Measure variable + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + Measure operator + + + + + + + + + + + + + + + + Qt::ImhNone + + + Action + + + + + + + 0 + 125 + + + + + 8 + + + + Qt::ImhDigitsOnly + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::SelectRows + + + 23 + + + + + + + Table + + + + + + + 0 + + + + + Delete selected rows + + + Delete + + + + + + + Copy + + + + + + + Paste rows from clipboard + + + Paste + + + + + + + Add new row + + + Add + + + + + + + + + + + + Qt::ImhNone + + + Type + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + 0 + + + + + + + + + + + + + true + + + + + + Target relationship + + + + + + + + + Target type + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 999999999 + + + true + + + true + + + false + + + + + + + + + + Target ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + true + + + + + + + + + + 0 + + + + + Tag + + + + + + + true + + + + + + + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + scrollArea + tabWidget + id + code + display_name + action_table_table + action_table_table_delete + action_table_table_copy + action_table_table_paste + action_table_table_add + + + + diff --git a/threedi_schematisation_editor/forms/ui/weir.ui b/threedi_schematisation_editor/forms/ui/weir.ui index 52f7eac..fb89049 100644 --- a/threedi_schematisation_editor/forms/ui/weir.ui +++ b/threedi_schematisation_editor/forms/ui/weir.ui @@ -27,8 +27,8 @@ 0 0 - 515 - 670 + 553 + 661 @@ -48,20 +48,7 @@ Weir - - - - Qt::Vertical - - - - 20 - 40 - - - - - + @@ -74,7 +61,7 @@ - + 0 @@ -121,7 +108,7 @@ - + 0 @@ -287,29 +274,40 @@ - - - - true - + + + + Qt::ImhNone + - Characteristics + Cross section definition - - - + + + + + x + + + + + + + Table + + + + + 0 0 - - - QAbstractSpinBox::NoButtons @@ -319,26 +317,10 @@ 1000000.000000000000000 - - true - - - true - - - true - - - - - - - x - - - + + @@ -346,89 +328,46 @@ Qt::ImhNone - Friction type - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 10 - - - - - - - Discharge coefficient negative + Height [m] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - true - - - 0 - 10 - - - - - - - Crest type - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - + + - + 0 0 - - - - - Qt::ImhNone - QAbstractSpinBox::NoButtons 3 - - -9999999999.000000000000000 - - 9999999999.000000000000000 - - - true - - - true + 1000000.000000000000000 - - true + + + + + + x - - + + - + 0 0 @@ -436,161 +375,899 @@ - - QAbstractSpinBox::NoButtons - - - 3 - - - 1000000.000000000000000 - - - true - - - true + + Qt::ImhNone - - true + + false - - - - + + + + + 0 + 125 + - - Qt::ImhNone + + + 8 + - - Friction value + + Qt::ImhDigitsOnly - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + Qt::ScrollBarAsNeeded - - 0 + + QAbstractScrollArea::AdjustToContents - - 10 + + QAbstractItemView::SelectRows + + 23 + - + + + Qt::ImhNone + - Crest level [m] + Shape Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - x - - - - - + + - - Discharge -coefficient positive + + Qt::ImhNone - - Qt::PlainText + + Width [m] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - true - - - -1 - - - - - - 0 - 0 - - - - - - - Qt::ImhNone - - - QAbstractSpinBox::NoButtons - - - 4 - - - 1000000.000000000000000 - - - true + + + + 0 - - true + + + + Delete selected rows + + + Delete + + + + + + + Copy + + + + + + + Paste rows from clipboard + + + Paste + + + + + + + Add new row + + + Add + + + + + + + + + + + + true + + + + + + Characteristics + + + + + + x + + + + + + + x + + + + + + + + + + Crest level [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + 0 + + + + + External + + + + + + + Sewerage + + + + + + + + + + + + Qt::ImhNone + + + Friction value + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 0 + + + 10 + + + + + + + + + + Discharge +coefficient positive + + + Qt::PlainText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + true + + + -1 + + + + + + + + + + + + + + Discharge coefficient negative + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + true + + + 0 + + + 10 + + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + true + + + true + + + true + + + + + + + + + + Qt::ImhNone + + + Friction type + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + + + Crest type + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Qt::ImhNone + + + + + + + + + + Qt::ImhNone + + + Material ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + + + true + + + true + + + true + + + + + + + x + + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + true + + + true + + + true + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 4 + + + 1000000.000000000000000 + + + true + + + true + + + true + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 99999999 + + + + + + + x + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 0 + + + + + Tag + + + + + + + true + + + + + + + + + + Starting connection node + + + + + + Administrative properties + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + + + + + + + + + + Manhole surface level [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + 50 + false + false + + + + x + + + + + + + + + + Dimensions + + + + + + x + + + + + + + + + + Storage area [m²] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 10 + + + + + + + + + + Bottom level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -99999999.000000000000000 + + + 999999999.000000000000000 + + + + + + + + 8 + + + + x + + + + + + + + + + Initial water level + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -99999999.000000000000000 + + + 999999999.000000000000000 + + + + + + + + + + Initial water level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + x - - true + + + + + + + + + Groundwater exchange + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 - - + + x - - + + + + + + + Exchange thickness [m] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Hydraulic conductivity in [m/d] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + + + x - - + + - - Qt::ImhNone + + Hydraulic conductivity out [m/d] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - + + + + + 0 + 0 + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1000000.000000000000000 + + + + + + + x + - - + + true @@ -598,23 +1275,74 @@ coefficient positive - Visualisation + General - - - + + + + + + - Sewerage + ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - + + + + + 0 + 0 + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 999999999 + + + + + + + + 0 + 0 + + + + + + + + + + + + + + Display name + + + + + - Zoom category + Code Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -624,8 +1352,8 @@ coefficient positive - - + + 0 @@ -637,50 +1365,162 @@ coefficient positive - - + + + + + - External + Visualisation + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 - - + + + + true + - - Qt::ImhNone - - Cross section definition + 2D exchange - - - + + + + + + + + Exchange level [m MSL] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + 0 + 0 + + + + + + + Qt::ImhNone + + + QAbstractSpinBox::NoButtons + + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + + + + + x - - + + - Table + Exchange type - - + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 0 + + + + + Tag + + + + + + + true + + + Open this feature's own attribute form to see its tags + + + + + + + + + + Ending connection node + + + + + + Groundwater exchange + + + + 0 0 + + + QAbstractSpinBox::NoButtons @@ -692,33 +1532,50 @@ coefficient positive - - + + + + x + + + + + - - Qt::ImhNone - - Height [m] + Exchange thickness [m] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - 10 + + + + + + + + + Hydraulic conductivity in [m/d] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - + + - + 0 0 + + + QAbstractSpinBox::NoButtons @@ -730,17 +1587,30 @@ coefficient positive - - + + x - - + + + + + + + Hydraulic conductivity out [m/d] + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + - + 0 0 @@ -748,221 +1618,229 @@ coefficient positive - - Qt::ImhNone + + QAbstractSpinBox::NoButtons - - false + + 3 + + + 1000000.000000000000000 - - - - - 0 - 125 - + + + + x - - - 8 - + + + + + + + + + Dimensions + + + + + + x - - Qt::ImhDigitsOnly + + + + + + - - Qt::ScrollBarAsNeeded + + Storage area [m²] - - QAbstractScrollArea::AdjustToContents + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - QAbstractItemView::SelectRows + + 10 - - 21 - - - + + - - Qt::ImhNone - - Shape + Bottom level [m MSL] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + -1 + - - + + + + + 0 + 0 + + Qt::ImhNone - - Width [m] + + QAbstractSpinBox::NoButtons - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + 3 + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 - - - - 0 + + + + + 0 + 0 + - - - - Delete selected rows - - - Delete - - - - - - - Copy - - - - - - - Paste rows from clipboard - - - Paste - - - - - - - Add new row - - - Add - - - - - - - - - - - - - Connection nodes - - - - - - true - - - Ending connection node - - - - - - Node code + + - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + Qt::ImhNone - - - - - - Node storage area [m²] + + QAbstractSpinBox::NoButtons - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + 3 - - 10 + + -99999999.000000000000000 + + + 999999999.000000000000000 - - - Node initial water level [m] - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + 8 + - - 10 + + x - - + + + + + + + Initial water level + + + + 0 0 + + + + + Qt::ImhNone + QAbstractSpinBox::NoButtons 3 + + -99999999.000000000000000 + 999999999.000000000000000 - - true - - + + + + - Connection node ID + Initial water level [m MSL] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + -1 + - - - - - 0 - 0 - + + + + x - - true + + + + + + + + + true + + + + + + General + + + + + + + + + ID + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - + @@ -970,6 +1848,9 @@ coefficient positive 0 + + + true @@ -979,230 +1860,227 @@ coefficient positive 999999999 - - true - - - false - - - + + 0 0 - - QAbstractSpinBox::NoButtons - - - 3 + + - - -1000.000000000000000 + + + + + + + + + Display name - - 1000000.000000000000000 + + + + + + - - true + + Code - - true + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - true + + 10 - - - - x + + + + + 0 + 0 + + + + - - + + + + + - x + Visualisation + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 - - + + Qt::Vertical - 20 - 40 + 505 + 258 - - + + true + + + - Starting connection node + 2D exchange - - - - - - 0 - 0 - - - - QAbstractSpinBox::NoButtons - - - 3 - - - -1000.000000000000000 - - - 1000000.000000000000000 - - - true - - - true - - - true - - - - - - - Node code - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + - - - - - Node initial water level [m] + Exchange level [m MSL] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - 10 + -1 - - + + 0 0 - - true + + + + + Qt::ImhNone QAbstractSpinBox::NoButtons - - 999999999 + + 3 - - true + + -9999999999.000000000000000 - - false + + 9999999999.000000000000000 - - + + - Connection node ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + x - - - - - 0 - 0 - - - - true + + + + Exchange type - - + + + + + + + + + + Administrative properties + + + + 0 0 + + + + + Qt::ImhNone + QAbstractSpinBox::NoButtons 3 - - 999999999.000000000000000 + + -9999999999.000000000000000 - - true + + 9999999999.000000000000000 - - + + + + + - Node storage area [m²] + Manhole surface level [m] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - 10 + -1 - - - - x + + + + + 50 + false + false + - - - - x @@ -1211,6 +2089,30 @@ coefficient positive + + + + 0 + + + + + Tag + + + + + + + true + + + Open this feature's own attribute form to see its tags + + + + + @@ -1244,9 +2146,6 @@ coefficient positive friction_type discharge_coefficient_negative discharge_coefficient_negative_clear - zoom_category - sewerage - external cross_section_shape cross_section_width cross_section_width_clear @@ -1257,20 +2156,8 @@ coefficient positive cross_section_table_copy cross_section_table_paste cross_section_table_add - connection_node_start_id - connection_node_end_id - connection_node_1_id - connection_node_1_code - connection_node_1_initial_waterlevel - connection_node_1_initial_waterlevel_clear - connection_node_1_storage_area - connection_node_1_storage_area_clear - connection_node_2_id - connection_node_2_code - connection_node_2_initial_waterlevel - connection_node_2_initial_waterlevel_clear - connection_node_2_storage_area - connection_node_2_storage_area_clear + connection_node_id_start + connection_node_id_end diff --git a/threedi_schematisation_editor/icon.png b/threedi_schematisation_editor/icons/icon.png similarity index 100% rename from threedi_schematisation_editor/icon.png rename to threedi_schematisation_editor/icons/icon.png diff --git a/threedi_schematisation_editor/import.png b/threedi_schematisation_editor/icons/icon_import.png similarity index 100% rename from threedi_schematisation_editor/import.png rename to threedi_schematisation_editor/icons/icon_import.png diff --git a/threedi_schematisation_editor/icons/icon_load.svg b/threedi_schematisation_editor/icons/icon_load.svg new file mode 100644 index 0000000..0f5bec3 --- /dev/null +++ b/threedi_schematisation_editor/icons/icon_load.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/threedi_schematisation_editor/icons/icon_unload.svg b/threedi_schematisation_editor/icons/icon_unload.svg new file mode 100644 index 0000000..9315d60 --- /dev/null +++ b/threedi_schematisation_editor/icons/icon_unload.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/threedi_schematisation_editor/metadata.txt b/threedi_schematisation_editor/metadata.txt index 0b1d9ae..7a62861 100644 --- a/threedi_schematisation_editor/metadata.txt +++ b/threedi_schematisation_editor/metadata.txt @@ -1,9 +1,9 @@ [general] name=3Di Schematisation Editor -qgisMinimumVersion=3.22 +qgisMinimumVersion=3.28 qgisMaximumVersion=3.99 description=3Di Schematisation Editor for QGIS -version=1.13.1 +version=1.99.3 author=Lutra Consulting for 3Di Water Management email=servicedesk@nelen-schuurmans.nl about=View and edit 3Di schematisations in the Modeller Interface. @@ -27,7 +27,7 @@ tags=3di, edit, 1D, hydraulics homepage=https://www.3diwatermanagement.com category=Plugins -icon=icon.png +icon=icon/icon.png experimental=False hasProcessingProvider=no diff --git a/threedi_schematisation_editor/processing/__init__.py b/threedi_schematisation_editor/processing/__init__.py index bc394ee..b5a193a 100644 --- a/threedi_schematisation_editor/processing/__init__.py +++ b/threedi_schematisation_editor/processing/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 by Lutra Consulting +# Copyright (C) 2025 by Lutra Consulting import os from qgis.core import QgsProcessingProvider @@ -9,7 +9,6 @@ from threedi_schematisation_editor.processing.algorithms_1d2d import GenerateExchangeLines from threedi_schematisation_editor.processing.algorithms_conversion import ( ImportCulverts, - ImportManholes, ImportOrifices, ImportPipes, ImportWeirs, @@ -48,7 +47,6 @@ def loadAlgorithms(self): ImportOrifices(), ImportWeirs(), ImportPipes(), - ImportManholes(), BottomLevelCalculator(), ] for alg in self.algorithms_list: diff --git a/threedi_schematisation_editor/processing/alghorithms_inflow.py b/threedi_schematisation_editor/processing/alghorithms_inflow.py index b71c584..032c6f6 100644 --- a/threedi_schematisation_editor/processing/alghorithms_inflow.py +++ b/threedi_schematisation_editor/processing/alghorithms_inflow.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 by Lutra Consulting +# Copyright (C) 2025 by Lutra Consulting from operator import itemgetter from qgis.core import ( @@ -21,7 +21,7 @@ class LinkSurfacesWithNodes(QgsProcessingAlgorithm): - """Link (impervious) surfaces to connection nodes.""" + """Link (DWF) surfaces to connection nodes.""" SURFACE_LAYER = "SURFACE_LAYER" SELECTED_SURFACES = "SELECTED_SURFACES" @@ -44,7 +44,7 @@ def name(self): return "threedi_map_surfaces_to_connection_nodes" def displayName(self): - return self.tr("Map (impervious) surfaces to connection nodes") + return self.tr("Map surfaces to connection nodes") def group(self): return self.tr("Inflow") @@ -55,14 +55,14 @@ def groupId(self): def shortHelpString(self): return self.tr( """ -

Connect (impervious) surfaces to the sewer system by creating (impervious) surface map features. The new features are added to the (impervious) surface layer directly.

-

For each (impervious) surface, the nearest pipe is found; the surface is mapped to the the nearest of this pipe's connection nodes.

+

Connect surfaces to the sewer system by creating surface map features. The new features are added to the surface layer directly.

+

For each surface, the nearest pipe is found; the surface is mapped to the the nearest of this pipe's connection nodes.

In some cases, you may want to prefer e.g. stormwater drains over combined sewers. This can be done by setting the stormwater sewer preference to a value greater than zero.

Parameters

-

(Impervious) surface layer

-

Surface or Impervious surface layer that is added to the project with the 3Di Schematisation Editor.

-

(Impervious) surface map layer

-

Surface map or Impervious surface map layer that is added to the project with the 3Di Schematisation Editor.

+

(DWF) surface layer

+

Surface or DWF surface layer that is added to the project with the 3Di Schematisation Editor.

+

(DWF) surface map layer

+

Surface map or DWF surface map layer that is added to the project with the 3Di Schematisation Editor.

Pipe layer

Pipe layer that is added to the project with the 3Di Schematisation Editor.

Connection node layer

@@ -70,11 +70,11 @@ def shortHelpString(self):

Sewerage types

Only pipes of the selected sewerage types will be used in the algorithm

Stormwater sewer preference

-

This value (in meters) will be subtracted from the distance between the (impervious) surface and the stormwater drain. For example: there is a combined sewer within 10 meters from the (impervious) surface, and a stormwater drain within 11 meters; if the stormwater sewer preference is 2 m, the algorithm will use 11 - 2 = 9 m as distance to the stormwater sewer, so the (impervious) surface will be mapped to one of the stormwater drain's connection nodes, instead of to the combined sewer's connection nodes.

+

This value (in meters) will be subtracted from the distance between the surface and the stormwater drain. For example: there is a combined sewer within 10 meters from the surface, and a stormwater drain within 11 meters; if the stormwater sewer preference is 2 m, the algorithm will use 11 - 2 = 9 m as distance to the stormwater sewer, so the surface will be mapped to one of the stormwater drain's connection nodes, instead of to the combined sewer's connection nodes.

Sanitary sewer preference

-

This value (in meters) will be subtracted from the distance between the (impervious) surface and the sanitary sewer. See 'stormwater sewer preference' for further explanation.

+

This value (in meters) will be subtracted from the distance between the surface and the sanitary sewer. See 'stormwater sewer preference' for further explanation.

Search distance

-

Only pipes within search distance (m) from the (impervious) surface will be used in the algorithm.

+

Only pipes within search distance (m) from the surface will be used in the algorithm.

""" ) @@ -82,23 +82,23 @@ def initAlgorithm(self, config=None): self.addParameter( QgsProcessingParameterVectorLayer( self.SURFACE_LAYER, - self.tr("(Impervious) surface layer"), + self.tr("Surface layer"), [QgsProcessing.TypeVectorPolygon], - defaultValue="Impervious Surface", + defaultValue="Surface", ) ) self.addParameter( QgsProcessingParameterBoolean( self.SELECTED_SURFACES, - self.tr("Selected (impervious) surfaces only"), + self.tr("Selected surfaces only"), ) ) self.addParameter( QgsProcessingParameterVectorLayer( self.SURFACE_MAP_LAYER, - self.tr("(Impervious) surface map layer"), + self.tr("Surface map layer"), [QgsProcessing.TypeVectorLine], - defaultValue="Impervious surface map", + defaultValue="Surface map", ) ) self.addParameter( @@ -120,7 +120,7 @@ def initAlgorithm(self, config=None): self.NODE_LAYER, self.tr("Connection node layer"), [QgsProcessing.TypeVectorPoint], - defaultValue="Connection Node", + defaultValue="Connection node", ) ) self.addParameter( @@ -215,9 +215,8 @@ def processAlgorithm(self, parameters, context, feedback): step += 1 surface_map_feats = [] surface_map_fields = surface_map_lyr.fields() - surface_map_field_names = {fld.name() for fld in surface_map_fields} next_surface_map_id = get_next_feature_id(surface_map_lyr) - surface_id_field = "surface_id" if "surface_id" in surface_map_field_names else "impervious_surface_id" + surface_id_field = "surface_id" for surface_id, surface_pipes in surface_to_pipes_distances.items(): if feedback.isCanceled(): return {} @@ -231,8 +230,8 @@ def processAlgorithm(self, parameters, context, feedback): surface_centroid = surface_geom.centroid() pipe_id, surface_pipe_distance = surface_pipes[0] pipe_feat = pipe_features[pipe_id] - start_node_id = pipe_feat["connection_node_start_id"] - end_node_id = pipe_feat["connection_node_end_id"] + start_node_id = pipe_feat["connection_node_id_start"] + end_node_id = pipe_feat["connection_node_id_end"] start_node = get_feature_by_id(node_lyr, start_node_id) end_node = get_feature_by_id(node_lyr, end_node_id) start_node_geom = start_node.geometry() diff --git a/threedi_schematisation_editor/processing/algorithms_1d.py b/threedi_schematisation_editor/processing/algorithms_1d.py index 14b82e2..1a61c8e 100644 --- a/threedi_schematisation_editor/processing/algorithms_1d.py +++ b/threedi_schematisation_editor/processing/algorithms_1d.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 by Lutra Consulting +# Copyright (C) 2025 by Lutra Consulting from collections import defaultdict from qgis.core import ( @@ -14,10 +14,10 @@ class BottomLevelCalculator(QgsProcessingAlgorithm): - """Calculate manhole bottom level from pipes.""" + """Calculate connection node manhole bottom level from pipes.""" - MANHOLE_LAYER = "MANHOLE_LAYER" - SELECTED_MANHOLES = "SELECTED_MANHOLES" + CONNECTION_NODE_LAYER = "CONNECTION_NODE_LAYER" + SELECTED_CONNECTION_NODES = "SELECTED_CONNECTION_NODES" PIPE_LAYER = "PIPE_LAYER" SELECTED_PIPES = "SELECTED_PIPES" OVERWRITE_LEVELS = "OVERWRITE_LEVELS" @@ -44,12 +44,12 @@ def groupId(self): def shortHelpString(self): return self.tr( """ -

Calculate manhole bottom level from the invert levels of pipes or culverts.

-

For each manhole, the algorithm determines which sides of which pipes (or culverts) are connected to it, and what the invert level is at that side. It than takes the lowest of these invert levels as bottom level for the manhole.

+

Calculate connection node manhole bottom level from the invert levels of pipes or culverts.

+

For each connection node manhole, the algorithm determines which sides of which pipes (or culverts) are connected to it, and what the invert level is at that side. It than takes the lowest of these invert levels as bottom level for the manhole.

Parameters

-

Manhole layer

-

Manhole layer that is added to the project with the 3Di Schematisation Editor.

-

If "Selected manholes only" is checked, only the selected manholes will be used in the algorithm.

+

Connection node layer

+

Connection node layer that is added to the project with the 3Di Schematisation Editor.

+

If "Selected connection nodes only" is checked, only the selected manholes will be used in the algorithm.

Pipe layer

Pipe or Culvert layer that is added to the project with the 3Di Schematisation Editor.

If "Selected pipes only" is checked, only the selected pipes will be used in the algorithm.

@@ -64,16 +64,16 @@ def shortHelpString(self): def initAlgorithm(self, config=None): self.addParameter( QgsProcessingParameterVectorLayer( - self.MANHOLE_LAYER, - self.tr("Manhole layer"), + self.CONNECTION_NODE_LAYER, + self.tr("Connection node layer"), [QgsProcessing.TypeVectorPoint], - defaultValue="Manhole", + defaultValue="Connection node", ) ) self.addParameter( QgsProcessingParameterBoolean( - self.SELECTED_MANHOLES, - self.tr("Selected manholes only"), + self.SELECTED_CONNECTION_NODES, + self.tr("Selected connection node manholes only"), ) ) self.addParameter( @@ -106,10 +106,10 @@ def initAlgorithm(self, config=None): ) def processAlgorithm(self, parameters, context, feedback): - manhole_lyr = self.parameterAsLayer(parameters, self.MANHOLE_LAYER, context) - if manhole_lyr is None: - raise QgsProcessingException(self.invalidSourceError(parameters, self.MANHOLE_LAYER)) - selected_manholes = self.parameterAsBool(parameters, self.SELECTED_MANHOLES, context) + connection_node_lyr = self.parameterAsLayer(parameters, self.CONNECTION_NODE_LAYER, context) + if connection_node_lyr is None: + raise QgsProcessingException(self.invalidSourceError(parameters, self.CONNECTION_NODE_LAYER)) + selected_manhole_nodes = self.parameterAsBool(parameters, self.SELECTED_CONNECTION_NODES, context) pipe_lyr = self.parameterAsLayer(parameters, self.PIPE_LAYER, context) if pipe_lyr is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.PIPE_LAYER)) @@ -121,27 +121,31 @@ def processAlgorithm(self, parameters, context, feedback): num_pipes = pipe_lyr.selectedFeatureCount() if selected_pipes else pipe_lyr.featureCount() processed_pipes = 0 for pipe_feat in pipe_lyr.selectedFeatures() if selected_pipes else pipe_lyr.getFeatures(): - pipe_start_node_id = pipe_feat["connection_node_start_id"] - pipe_end_node_id = pipe_feat["connection_node_end_id"] - invert_level_start_point = pipe_feat["invert_level_start_point"] - invert_level_end_point = pipe_feat["invert_level_end_point"] - if invert_level_start_point != NULL: - node_adjacent_invert_levels[pipe_start_node_id].add(invert_level_start_point) - if invert_level_end_point != NULL: - node_adjacent_invert_levels[pipe_end_node_id].add(invert_level_end_point) + pipe_start_node_id = pipe_feat["connection_node_id_start"] + pipe_end_node_id = pipe_feat["connection_node_id_end"] + invert_level_start = pipe_feat["invert_level_start"] + invert_level_end = pipe_feat["invert_level_end"] + if invert_level_start != NULL: + node_adjacent_invert_levels[pipe_start_node_id].add(invert_level_start) + if invert_level_end != NULL: + node_adjacent_invert_levels[pipe_end_node_id].add(invert_level_end) processed_pipes += 1 feedback.setProgress(100 / 3 * processed_pipes / num_pipes) if feedback.isCanceled(): return {} bottom_level_changes = {} - num_manholes = manhole_lyr.selectedFeatureCount() if selected_manholes else manhole_lyr.featureCount() - processed_manholes = 0 - for manhole_feat in manhole_lyr.selectedFeatures() if selected_manholes else manhole_lyr.getFeatures(): - manhole_fid = manhole_feat.id() - node_id = manhole_feat["connection_node_id"] + num_manhole_nodes = ( + connection_node_lyr.selectedFeatureCount() if selected_manhole_nodes else connection_node_lyr.featureCount() + ) + processed_nodes = 0 + for node_feat in ( + connection_node_lyr.selectedFeatures() if selected_manhole_nodes else connection_node_lyr.getFeatures() + ): + node_fid = node_feat.id() + node_id = node_feat["id"] if not node_id: continue - bottom_level = manhole_feat["bottom_level"] + bottom_level = node_feat["bottom_level"] if bottom_level != NULL and not overwrite_levels: continue invert_levels = node_adjacent_invert_levels[node_id] @@ -151,24 +155,24 @@ def processAlgorithm(self, parameters, context, feedback): if bottom_level != NULL: if min_invert_level > bottom_level and do_not_raise_levels: continue - bottom_level_changes[manhole_fid] = min_invert_level - processed_manholes += 1 - feedback.setProgress(100 / 3 + 100 / 3 * processed_manholes / num_manholes) + bottom_level_changes[node_fid] = min_invert_level + processed_nodes += 1 + feedback.setProgress(100 / 3 + 100 / 3 * processed_nodes / num_manhole_nodes) if feedback.isCanceled(): return {} if bottom_level_changes: - bottom_level_field_idx = manhole_lyr.fields().lookupField("bottom_level") - manhole_lyr.startEditing() - for i, (manhole_fid, bottom_level) in enumerate(bottom_level_changes.items()): + bottom_level_field_idx = connection_node_lyr.fields().lookupField("bottom_level") + connection_node_lyr.startEditing() + for i, (node_fid, bottom_level) in enumerate(bottom_level_changes.items()): if feedback.isCanceled(): return {} - manhole_lyr.changeAttributeValue(manhole_fid, bottom_level_field_idx, bottom_level) + connection_node_lyr.changeAttributeValue(node_fid, bottom_level_field_idx, bottom_level) feedback.setProgress(200 / 3 + 100 / 3 * (i + 1) / len(bottom_level_changes)) if feedback.isCanceled(): return {} - success = manhole_lyr.commitChanges() + success = connection_node_lyr.commitChanges() if not success: - commit_errors = manhole_lyr.commitErrors() + commit_errors = connection_node_lyr.commitErrors() commit_errors_message = "\n".join(commit_errors) feedback.reportError(commit_errors_message) return {} diff --git a/threedi_schematisation_editor/processing/algorithms_1d2d.py b/threedi_schematisation_editor/processing/algorithms_1d2d.py index 5876c7b..6a129eb 100644 --- a/threedi_schematisation_editor/processing/algorithms_1d2d.py +++ b/threedi_schematisation_editor/processing/algorithms_1d2d.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 by Lutra Consulting +# Copyright (C) 2025 by Lutra Consulting from qgis.core import ( Qgis, QgsFeature, @@ -11,7 +11,7 @@ ) from qgis.PyQt.QtCore import QCoreApplication -from threedi_schematisation_editor.enumerators import CalculationType +from threedi_schematisation_editor.enumerators import ExchangeTypeChannel from threedi_schematisation_editor.utils import get_features_by_expression, get_next_feature_id @@ -46,7 +46,7 @@ def shortHelpString(self):

This processing algorithm generates exchange lines for (a selection of) channels. The resulting exchange line's geometry is a copy of the input channel's geometry, at user specified distance from that channel (the GIS term for this is 'offset curve'). The resulting exchange lines is added to the exchange line layer, and the attribute 'channel_id' refers to the channel it was derived from.

Parameters

Input channel layer

-

Usually this is the Channel layer that is added to the project with the 3Di Schematisation Editor. Technically, any layer with a line geometry and the fields 'id' and 'calculation_type' can be used as input.

+

Usually this is the Channel layer that is added to the project with the 3Di Schematisation Editor. Technically, any layer with a line geometry and the fields 'id' and 'exchange_type' can be used as input.

Distance

Offset distance in meters. A positive value will place the output exchange line to the left of the line, negative values will place it to the right.

Exchange lines layer

@@ -81,11 +81,11 @@ def checkParameterValues(self, parameters, context): if success: invalid_parameters_messages = [] channels = self.parameterAsSource(parameters, self.INPUT_CHANNELS, context) - required_channel_fields = {"id", "calculation_type"} + required_channel_fields = {"id", "exchange_type"} channels_field_names = {f.name() for f in channels.fields()} if not required_channel_fields.issubset(channels_field_names): invalid_parameters_messages.append( - "Channel layer is missing required fields ('id' and/or 'calculation_type')" + "Channel layer is missing required fields ('id' and/or 'exchange_type')" ) offset_distance = self.parameterAsDouble(parameters, self.OFFSET_DISTANCE, context) if offset_distance == 0: @@ -112,15 +112,15 @@ def processAlgorithm(self, parameters, context, feedback): exchange_line_feats = [] exchange_lines_fields = exchange_lines_lyr.fields() current_exchange_line_id = get_next_feature_id(exchange_lines_lyr) - calculation_type_max_exchange_lines = { - CalculationType.ISOLATED.value: 0, - CalculationType.EMBEDDED.value: 0, - CalculationType.CONNECTED.value: 1, - CalculationType.DOUBLE_CONNECTED.value: 2, + exchange_type_max_exchange_lines = { + ExchangeTypeChannel.ISOLATED.value: 0, + ExchangeTypeChannel.EMBEDDED.value: 0, + ExchangeTypeChannel.CONNECTED.value: 1, + ExchangeTypeChannel.DOUBLE_CONNECTED.value: 2, } error_template = ( - "Error: channel {} with calculation type {} ({}) already has a maximum of {} exchange lines. " - "Change the calculation type or remove exchange lines for this channel and try again." + "Error: channel {} with exchange type {} ({}) already has a maximum of {} exchange lines. " + "Change the exchange type or remove exchange lines for this channel and try again." ) for channel_feat in channels.getFeatures(): channel_fid = channel_feat.id() @@ -128,18 +128,18 @@ def processAlgorithm(self, parameters, context, feedback): if not channel_id: feedback.reportError(f"Error: invalid channel ID. Processing feature with FID {channel_fid} skipped.") continue - calculation_type = channel_feat["calculation_type"] - if calculation_type not in calculation_type_max_exchange_lines: + exchange_type = channel_feat["exchange_type"] + if exchange_type not in exchange_type_max_exchange_lines: feedback.reportError( - f"Error: invalid channel calculation type. Processing feature with FID {channel_fid} skipped." + f"Error: invalid channel exchange type. Processing feature with FID {channel_fid} skipped." ) continue - calculation_type_name = CalculationType(calculation_type).name + exchange_type_name = ExchangeTypeChannel(exchange_type).name channel_expression_text = f'"channel_id" = {channel_id}' channel_exchange_lines = list(get_features_by_expression(exchange_lines_lyr, channel_expression_text)) - calc_type_limit = calculation_type_max_exchange_lines[calculation_type] + calc_type_limit = exchange_type_max_exchange_lines[exchange_type] if len(channel_exchange_lines) >= calc_type_limit: - error_msg = error_template.format(channel_id, calculation_type, calculation_type_name, calc_type_limit) + error_msg = error_template.format(channel_id, exchange_type, exchange_type_name, calc_type_limit) feedback.reportError(error_msg) continue channel_geom = channel_feat.geometry() diff --git a/threedi_schematisation_editor/processing/algorithms_conversion.py b/threedi_schematisation_editor/processing/algorithms_conversion.py index b4c80d5..d8213ba 100644 --- a/threedi_schematisation_editor/processing/algorithms_conversion.py +++ b/threedi_schematisation_editor/processing/algorithms_conversion.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 by Lutra Consulting +# Copyright (C) 2025 by Lutra Consulting import json from qgis.core import ( @@ -14,7 +14,6 @@ from threedi_schematisation_editor.custom_tools import ( CulvertsImporter, CulvertsIntegrator, - ManholesImporter, OrificesImporter, OrificesIntegrator, PipesImporter, @@ -331,76 +330,3 @@ def postProcessAlgorithm(self, context, feedback): for layer in QgsProject.instance().mapLayers().values(): layer.triggerRepaint() return {} - - -class ImportManholes(QgsProcessingAlgorithm): - """Import manholes.""" - - SOURCE_LAYER = "SOURCE_LAYER" - IMPORT_CONFIG = "IMPORT_CONFIG" - TARGET_GPKG = "TARGET_GPKG" - - def tr(self, string): - return QCoreApplication.translate("Processing", string) - - def createInstance(self): - return ImportManholes() - - def name(self): - return "threedi_import_manholes" - - def displayName(self): - return self.tr("Import manholes") - - def group(self): - return self.tr("Conversion") - - def groupId(self): - return "conversion" - - def shortHelpString(self): - return self.tr("""Import manholes from the external source layer.""") - - def initAlgorithm(self, config=None): - source_layer = QgsProcessingParameterFeatureSource( - self.SOURCE_LAYER, - self.tr("Source manholes layer"), - [QgsProcessing.TypeVectorPoint], - ) - self.addParameter(source_layer) - import_config_file = QgsProcessingParameterFile( - self.IMPORT_CONFIG, - self.tr("Manholes import configuration file"), - extension="json", - behavior=QgsProcessingParameterFile.File, - ) - self.addParameter(import_config_file) - target_gpkg = QgsProcessingParameterFile( - self.TARGET_GPKG, - self.tr("Target Schematisation Editor GeoPackage file"), - extension="gpkg", - behavior=QgsProcessingParameterFile.File, - ) - self.addParameter(target_gpkg) - - def processAlgorithm(self, parameters, context, feedback): - source_layer = self.parameterAsSource(parameters, self.SOURCE_LAYER, context) - if source_layer is None: - raise QgsProcessingException(self.invalidSourceError(parameters, self.SOURCE_LAYER)) - import_config_file = self.parameterAsFile(parameters, self.IMPORT_CONFIG, context) - if import_config_file is None: - raise QgsProcessingException(self.invalidSourceError(parameters, self.IMPORT_CONFIG)) - target_gpkg = self.parameterAsFile(parameters, self.TARGET_GPKG, context) - if target_gpkg is None: - raise QgsProcessingException(self.invalidSourceError(parameters, self.TARGET_GPKG)) - with open(import_config_file) as import_config_json: - import_config = json.loads(import_config_json.read()) - manholes_importer = ManholesImporter(source_layer, target_gpkg, import_config) - manholes_importer.import_structures(context=context) - manholes_importer.commit_pending_changes() - return {} - - def postProcessAlgorithm(self, context, feedback): - for layer in QgsProject.instance().mapLayers().values(): - layer.triggerRepaint() - return {} diff --git a/threedi_schematisation_editor/processing/config/import_culvert.json b/threedi_schematisation_editor/processing/config/import_culvert.json index d525b74..508326b 100644 --- a/threedi_schematisation_editor/processing/config/import_culvert.json +++ b/threedi_schematisation_editor/processing/config/import_culvert.json @@ -17,7 +17,7 @@ "display_name": { "method": "ignore" }, - "calculation_type": { + "exchange_type": { "method": "source_attribute", "source_attribute": "kdu_open", "value_map": { diff --git a/threedi_schematisation_editor/styles/vector/1d_boundary_condition/default.qml b/threedi_schematisation_editor/styles/vector/1d_boundary_condition/default.qml deleted file mode 100644 index 0dc487d..0000000 --- a/threedi_schematisation_editor/styles/vector/1d_boundary_condition/default.qml +++ /dev/null @@ -1,271 +0,0 @@ - - - - 1 - 0 - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - 0 - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - - - 0 - tablayout - - - - - - - - - - - - - - - - - - - - - - - - - - - ROWID - - 0 - diff --git a/threedi_schematisation_editor/styles/vector/1d_lateral/default.qml b/threedi_schematisation_editor/styles/vector/1d_lateral/default.qml deleted file mode 100644 index a33ebdb..0000000 --- a/threedi_schematisation_editor/styles/vector/1d_lateral/default.qml +++ /dev/null @@ -1,241 +0,0 @@ - - - - 1 - 0 - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - 0 - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - . - - 0 - . - - 0 - tablayout - - - - - - - - - - - - - - - - - - - - - - - - ROWID - - 0 - diff --git a/threedi_schematisation_editor/styles/vector/2d_boundary_condition/default.qml b/threedi_schematisation_editor/styles/vector/2d_boundary_condition/default.qml deleted file mode 100644 index 8054950..0000000 --- a/threedi_schematisation_editor/styles/vector/2d_boundary_condition/default.qml +++ /dev/null @@ -1,272 +0,0 @@ - - - - 1 - 0 - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - 0 - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - - - 0 - tablayout - - - - - - - - - - - - - - - - - - - - - - - - - "display_name" - - 1 - diff --git a/threedi_schematisation_editor/styles/vector/2d_lateral/default.qml b/threedi_schematisation_editor/styles/vector/2d_lateral/default.qml deleted file mode 100644 index 7ad09f7..0000000 --- a/threedi_schematisation_editor/styles/vector/2d_lateral/default.qml +++ /dev/null @@ -1,242 +0,0 @@ - - - - 1 - 0 - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - 0 - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - - - 0 - tablayout - - - - - - - - - - - - - - - - - - - - - - "id" - - 0 - diff --git a/threedi_schematisation_editor/styles/vector/aggregation_settings/default.qml b/threedi_schematisation_editor/styles/vector/aggregation_settings/default.qml index 5c07f1f..7ae57ac 100644 --- a/threedi_schematisation_editor/styles/vector/aggregation_settings/default.qml +++ b/threedi_schematisation_editor/styles/vector/aggregation_settings/default.qml @@ -150,7 +150,7 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C:\Users/lukas/AppData/Roaming/QGIS/QGIS3\profiles\default/python/plugins\threedi_schematisation_editor\forms\ui\boundary_condition_1d.ui + open_edit_form + 2 + + + 0 + uifilelayout + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ROWID + + 0 + diff --git a/threedi_schematisation_editor/styles/vector/1d_boundary_condition/timeseries label.qml b/threedi_schematisation_editor/styles/vector/boundary_condition_1d/timeseries label.qml similarity index 86% rename from threedi_schematisation_editor/styles/vector/1d_boundary_condition/timeseries label.qml rename to threedi_schematisation_editor/styles/vector/boundary_condition_1d/timeseries label.qml index acc3edc..4a9a147 100644 --- a/threedi_schematisation_editor/styles/vector/1d_boundary_condition/timeseries label.qml +++ b/threedi_schematisation_editor/styles/vector/boundary_condition_1d/timeseries label.qml @@ -37,7 +37,7 @@ - + @@ -136,7 +136,7 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C:\Users/lukas/AppData/Roaming/QGIS/QGIS3\profiles\default/python/plugins\threedi_schematisation_editor\forms\ui\boundary_condition_2d.ui + open_edit_form + 2 + + + 0 + uifilelayout + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "display_name" + + 1 + diff --git a/threedi_schematisation_editor/styles/vector/2d_boundary_condition/timeseries label.qml b/threedi_schematisation_editor/styles/vector/boundary_condition_2d/timeseries label.qml similarity index 88% rename from threedi_schematisation_editor/styles/vector/2d_boundary_condition/timeseries label.qml rename to threedi_schematisation_editor/styles/vector/boundary_condition_2d/timeseries label.qml index 4254161..5a9f2d1 100644 --- a/threedi_schematisation_editor/styles/vector/2d_boundary_condition/timeseries label.qml +++ b/threedi_schematisation_editor/styles/vector/boundary_condition_2d/timeseries label.qml @@ -40,7 +40,7 @@ - + @@ -176,7 +176,7 @@ - + @@ -288,7 +288,7 @@ - + @@ -541,7 +541,7 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -94,7 +94,7 @@ @@ -162,7 +162,7 @@ @@ -362,7 +362,7 @@ - + @@ -83,7 +83,7 @@ @@ -141,7 +141,7 @@ @@ -156,7 +156,7 @@ - + @@ -287,7 +287,7 @@ - + @@ -236,7 +236,7 @@ - + @@ -46,7 +46,7 @@ - + @@ -144,7 +144,7 @@ @@ -157,7 +157,7 @@ - + - + @@ -207,19 +207,19 @@ - + - + - + - + @@ -230,7 +230,7 @@ - - + - + - + - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - . - - 0 - . - - 0 - tablayout - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "display_name" - - 2 - diff --git a/threedi_schematisation_editor/styles/vector/impervious_surface/default.qml b/threedi_schematisation_editor/styles/vector/impervious_surface/default.qml deleted file mode 100644 index fa4b972..0000000 --- a/threedi_schematisation_editor/styles/vector/impervious_surface/default.qml +++ /dev/null @@ -1,662 +0,0 @@ - - - - 1 - 0 - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - 0 - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - . - - 0 - . - - 0 - tablayout - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "display_name" - - 2 - diff --git a/threedi_schematisation_editor/styles/vector/impervious_surface/surface inclination.qml b/threedi_schematisation_editor/styles/vector/impervious_surface/surface inclination.qml deleted file mode 100644 index a862bda..0000000 --- a/threedi_schematisation_editor/styles/vector/impervious_surface/surface inclination.qml +++ /dev/null @@ -1,483 +0,0 @@ - - - - 1 - 1 - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "display_name" - - - - - - 0 - 0 - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - . - - 0 - . - - 0 - tablayout - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "display_name" - - 2 - diff --git a/threedi_schematisation_editor/styles/vector/impervious_surface_map/default.qml b/threedi_schematisation_editor/styles/vector/impervious_surface_map/default.qml deleted file mode 100644 index 75e66d7..0000000 --- a/threedi_schematisation_editor/styles/vector/impervious_surface_map/default.qml +++ /dev/null @@ -1,146 +0,0 @@ - - - - 1 - 0 - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - 0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C:/Users/lukas/AppData/Roaming/QGIS/QGIS3\profiles\default/python/plugins\threedi_schematisation_editor\forms\ui\impervious_surface_map.ui - open_edit_form - 2 - - - 0 - uifilelayout - - - - - - - - - - - - - - - - - "fid" - - 1 - diff --git a/threedi_schematisation_editor/styles/vector/impervious_surface_map/percentage.qml b/threedi_schematisation_editor/styles/vector/impervious_surface_map/percentage.qml deleted file mode 100644 index 831fded..0000000 --- a/threedi_schematisation_editor/styles/vector/impervious_surface_map/percentage.qml +++ /dev/null @@ -1,238 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - 0 - 1 - diff --git a/threedi_schematisation_editor/styles/vector/interflow_settings/default.qml b/threedi_schematisation_editor/styles/vector/interflow/default.qml similarity index 99% rename from threedi_schematisation_editor/styles/vector/interflow_settings/default.qml rename to threedi_schematisation_editor/styles/vector/interflow/default.qml index 0718413..eb87bf4 100644 --- a/threedi_schematisation_editor/styles/vector/interflow_settings/default.qml +++ b/threedi_schematisation_editor/styles/vector/interflow/default.qml @@ -132,7 +132,7 @@ - + diff --git a/threedi_schematisation_editor/styles/vector/lateral_1d/default.qml b/threedi_schematisation_editor/styles/vector/lateral_1d/default.qml new file mode 100644 index 0000000..50067f1 --- /dev/null +++ b/threedi_schematisation_editor/styles/vector/lateral_1d/default.qml @@ -0,0 +1,576 @@ + + + + 1 + 0 + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C:\Users/lukas/AppData/Roaming/QGIS/QGIS3\profiles\default/python/plugins\threedi_schematisation_editor\forms\ui\lateral_1d.ui + open_edit_form + 2 + . + + 0 + uifilelayout + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ROWID + + 0 + diff --git a/threedi_schematisation_editor/styles/vector/1d_lateral/timeseries label.qml b/threedi_schematisation_editor/styles/vector/lateral_1d/timeseries label.qml similarity index 97% rename from threedi_schematisation_editor/styles/vector/1d_lateral/timeseries label.qml rename to threedi_schematisation_editor/styles/vector/lateral_1d/timeseries label.qml index b44e0b4..2e95e8d 100644 --- a/threedi_schematisation_editor/styles/vector/1d_lateral/timeseries label.qml +++ b/threedi_schematisation_editor/styles/vector/lateral_1d/timeseries label.qml @@ -159,8 +159,8 @@ - - + + . diff --git a/threedi_schematisation_editor/styles/vector/lateral_2d/default.qml b/threedi_schematisation_editor/styles/vector/lateral_2d/default.qml new file mode 100644 index 0000000..78da370 --- /dev/null +++ b/threedi_schematisation_editor/styles/vector/lateral_2d/default.qml @@ -0,0 +1,559 @@ + + + + 1 + 0 + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C:\Users/lukas/AppData/Roaming/QGIS/QGIS3\profiles\default/python/plugins\threedi_schematisation_editor\forms\ui\lateral_2d.ui + open_edit_form + 2 + + + 0 + uifilelayout + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "id" + + 0 + diff --git a/threedi_schematisation_editor/styles/vector/2d_lateral/timeseries label.qml b/threedi_schematisation_editor/styles/vector/lateral_2d/timeseries label.qml similarity index 99% rename from threedi_schematisation_editor/styles/vector/2d_lateral/timeseries label.qml rename to threedi_schematisation_editor/styles/vector/lateral_2d/timeseries label.qml index ee42650..1b5a5c0 100644 --- a/threedi_schematisation_editor/styles/vector/2d_lateral/timeseries label.qml +++ b/threedi_schematisation_editor/styles/vector/lateral_2d/timeseries label.qml @@ -190,7 +190,7 @@ - + diff --git a/threedi_schematisation_editor/styles/vector/manhole/calculation type.qml b/threedi_schematisation_editor/styles/vector/manhole/calculation type.qml index 97ee798..2745e4a 100644 --- a/threedi_schematisation_editor/styles/vector/manhole/calculation type.qml +++ b/threedi_schematisation_editor/styles/vector/manhole/calculation type.qml @@ -144,7 +144,7 @@ - + @@ -472,7 +472,7 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - C:/Users/lukas/AppData/Roaming/QGIS/QGIS3\profiles\default/python/plugins\threedi_schematisation_editor\forms\ui\pumpstation_map.ui + C:/Users/lukas/AppData/Roaming/QGIS/QGIS3\profiles\default/python/plugins\threedi_schematisation_editor\forms\ui\pump_map.ui open_edit_form 2 . @@ -212,7 +212,7 @@ 0 uifilelayout - + @@ -256,7 +256,7 @@ - + @@ -279,7 +279,7 @@ - + diff --git a/threedi_schematisation_editor/styles/vector/pumpstation_map/levels.qml b/threedi_schematisation_editor/styles/vector/pump_map/levels.qml similarity index 99% rename from threedi_schematisation_editor/styles/vector/pumpstation_map/levels.qml rename to threedi_schematisation_editor/styles/vector/pump_map/levels.qml index d2a72ee..37c7cef 100644 --- a/threedi_schematisation_editor/styles/vector/pumpstation_map/levels.qml +++ b/threedi_schematisation_editor/styles/vector/pump_map/levels.qml @@ -442,7 +442,7 @@ - + @@ -538,7 +538,7 @@ def my_form_open(dialog, layer, feature): 0 tablayout - + diff --git a/threedi_schematisation_editor/styles/vector/simple_infiltration_settings/default.qml b/threedi_schematisation_editor/styles/vector/simple_infiltration/default.qml similarity index 88% rename from threedi_schematisation_editor/styles/vector/simple_infiltration_settings/default.qml rename to threedi_schematisation_editor/styles/vector/simple_infiltration/default.qml index 86156d0..48c4831 100644 --- a/threedi_schematisation_editor/styles/vector/simple_infiltration_settings/default.qml +++ b/threedi_schematisation_editor/styles/vector/simple_infiltration/default.qml @@ -92,7 +92,7 @@ - + - + @@ -230,8 +230,8 @@ def my_form_open(dialog, layer, feature): - - + + @@ -240,8 +240,8 @@ def my_form_open(dialog, layer, feature): - - + + @@ -250,8 +250,8 @@ def my_form_open(dialog, layer, feature): - - + + diff --git a/threedi_schematisation_editor/styles/vector/surface/area and DWF production.qml b/threedi_schematisation_editor/styles/vector/surface/area and DWF production.qml deleted file mode 100644 index 5a9470c..0000000 --- a/threedi_schematisation_editor/styles/vector/surface/area and DWF production.qml +++ /dev/null @@ -1,648 +0,0 @@ - - - - 1 - 1 - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "display_name" - - - - - - 0 - 0 - 0.9 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - - - 0 - tablayout - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "display_name" - - 2 - diff --git a/threedi_schematisation_editor/styles/vector/surface/default.qml b/threedi_schematisation_editor/styles/vector/surface/default.qml index c58c1d8..34f8d01 100644 --- a/threedi_schematisation_editor/styles/vector/surface/default.qml +++ b/threedi_schematisation_editor/styles/vector/surface/default.qml @@ -1,31 +1,173 @@ - + 1 0 1 + 0 - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - + + 0 0 1 - - + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - + - + - - - - - - - + - + - - + + - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - + - + - + - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + - - - - - - - - - - + + + + + + - - - - - - - - - - + + + + + + - + - @@ -297,48 +437,54 @@ - - 0 + open_edit_form + 2 - + 0 - tablayout + uifilelayout - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + @@ -349,9 +495,10 @@ def my_form_open(dialog, layer, feature): - + + @@ -364,11 +511,20 @@ def my_form_open(dialog, layer, feature): + + + + + + + + + "display_name" - + 2 diff --git a/threedi_schematisation_editor/styles/vector/surface_map/default.qml b/threedi_schematisation_editor/styles/vector/surface_map/default.qml index 656521c..5a0cbb5 100644 --- a/threedi_schematisation_editor/styles/vector/surface_map/default.qml +++ b/threedi_schematisation_editor/styles/vector/surface_map/default.qml @@ -102,7 +102,7 @@ - + - C:/Users/lukas/AppData/Roaming/QGIS/QGIS3\profiles\default/python/plugins\threedi_schematisation_editor\forms\ui\surface_map.ui + C:/Users/lukas/AppData/Roaming/QGIS/QGIS3\profiles\default/python/plugins\threedi_schematisation_editor\forms\ui\impervious_surface_map.ui open_edit_form 2 @@ -128,15 +128,15 @@ + - + - diff --git a/threedi_schematisation_editor/styles/vector/surface_parameters/default.qml b/threedi_schematisation_editor/styles/vector/surface_parameters/default.qml index b050975..867089b 100644 --- a/threedi_schematisation_editor/styles/vector/surface_parameters/default.qml +++ b/threedi_schematisation_editor/styles/vector/surface_parameters/default.qml @@ -124,7 +124,7 @@ - + diff --git a/threedi_schematisation_editor/styles/vector/table_control/default.qml b/threedi_schematisation_editor/styles/vector/table_control/default.qml new file mode 100644 index 0000000..b62f151 --- /dev/null +++ b/threedi_schematisation_editor/styles/vector/table_control/default.qml @@ -0,0 +1,503 @@ + + + + 1 + 1 + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + open_edit_form + 2 + + + 0 + uifilelayout + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "display_name" + + 0 + diff --git a/threedi_schematisation_editor/styles/vector/vegetation_drag/default.qml b/threedi_schematisation_editor/styles/vector/vegetation_drag_2d/default.qml similarity index 99% rename from threedi_schematisation_editor/styles/vector/vegetation_drag/default.qml rename to threedi_schematisation_editor/styles/vector/vegetation_drag_2d/default.qml index 2f44cd6..3ccf8ac 100644 --- a/threedi_schematisation_editor/styles/vector/vegetation_drag/default.qml +++ b/threedi_schematisation_editor/styles/vector/vegetation_drag_2d/default.qml @@ -254,7 +254,7 @@ - + diff --git a/threedi_schematisation_editor/styles/vector/windshielding/default.qml b/threedi_schematisation_editor/styles/vector/windshielding_1d/default.qml similarity index 99% rename from threedi_schematisation_editor/styles/vector/windshielding/default.qml rename to threedi_schematisation_editor/styles/vector/windshielding_1d/default.qml index 9fb9a5e..d46c9a0 100644 --- a/threedi_schematisation_editor/styles/vector/windshielding/default.qml +++ b/threedi_schematisation_editor/styles/vector/windshielding_1d/default.qml @@ -134,7 +134,7 @@ - + diff --git a/threedi_schematisation_editor/user_layer_forms.py b/threedi_schematisation_editor/user_layer_forms.py index 4570de6..44a6d78 100644 --- a/threedi_schematisation_editor/user_layer_forms.py +++ b/threedi_schematisation_editor/user_layer_forms.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 by Lutra Consulting +# Copyright (C) 2025 by Lutra Consulting from threedi_schematisation_editor.forms.custom_forms import MODEL_FORMS from threedi_schematisation_editor.utils import disconnect_signal diff --git a/threedi_schematisation_editor/user_layer_handlers.py b/threedi_schematisation_editor/user_layer_handlers.py index 2e445e5..102c614 100644 --- a/threedi_schematisation_editor/user_layer_handlers.py +++ b/threedi_schematisation_editor/user_layer_handlers.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 by Lutra Consulting +# Copyright (C) 2025 by Lutra Consulting from collections import defaultdict from functools import cached_property, partial from types import MappingProxyType @@ -8,16 +8,19 @@ import threedi_schematisation_editor.data_models as dm from threedi_schematisation_editor.enumerators import ( - CalculationTypeCulvert, - CalculationTypeNode, + BoundaryType, CrestType, + ExchangeTypeCulvert, + ExchangeTypeNode, FrictionType, GeometryType, - ManholeIndicator, + Later2DType, ManholeShape, PipeMaterial, PumpType, - ZoomCategories, + TimeUnit, + Unit, + Visualisation, ) from threedi_schematisation_editor.utils import ( connect_signal, @@ -213,10 +216,20 @@ def detect_dependent_features(self, fid, model_cls, visited_features): feat_real = next(handler.layer_dt.getFeatures(request)) except StopIteration: # Feature not committed return dependent_features + feat_table_name = model_cls.__tablename__ feat_real_id = feat_real["id"] for dependent_data_model, dependent_fields in dm.MODEL_DEPENDENCIES[model_cls].items(): dependent_layer = self.layer_manager.model_handlers[dependent_data_model].layer - expr_str = " OR ".join(f'"{field_name}" = {feat_real_id}' for field_name in dependent_fields) + expr_parts = [] + for dependent_feat_id_field in dependent_fields: + if isinstance(dependent_feat_id_field, tuple): + # We have a pair of fields: field with dependent feature ID and field with dependent feature type + dependent_feat_id_field, dependent_feat_type_field = dependent_feat_id_field + expr_part = f'("{dependent_feat_id_field}" = {feat_real_id} AND "{dependent_feat_type_field}" = \'{feat_table_name}\')' + else: + expr_part = f'"{dependent_feat_id_field}" = {feat_real_id}' + expr_parts.append(expr_part) + expr_str = " OR ".join(expr_parts) expr = QgsExpression(expr_str) for dependent_feat in dependent_layer.getFeatures(QgsFeatureRequest(expr)): dependent_fid = dependent_feat.id() @@ -323,7 +336,7 @@ def create_new_feature_from_template(self, template_feat, geometry=None, fields_ field_values = dict() for field in template_feat.fields(): field_name = field.name() - if field_name == "fid" or (fields_to_skip is not None and field_name in fields_to_skip): + if fields_to_skip is not None and field_name in fields_to_skip: continue field_values[field_name] = template_feat[field_name] new_feat = self.create_new_feature(geometry=geometry, use_defaults=False) @@ -363,16 +376,16 @@ def update_node_references(self, feat_id, geometry): start_connection_node_feat, end_connection_node_feat = find_linestring_nodes(linestring, node_layer) changes = {} start_connection_node_id = start_connection_node_feat["id"] if start_connection_node_feat else None - start_connection_node_id_idx = layer_fields.lookupField("connection_node_start_id") + start_connection_node_id_idx = layer_fields.lookupField("connection_node_id_start") changes[start_connection_node_id_idx] = start_connection_node_id end_connection_node_id = end_connection_node_feat["id"] if end_connection_node_feat else None - end_connection_node_id_idx = layer_fields.lookupField("connection_node_end_id") + end_connection_node_id_idx = layer_fields.lookupField("connection_node_id_end") changes[end_connection_node_id_idx] = end_connection_node_id - if self.MODEL == dm.PumpstationMap: - pumpstation_layer = self.layer_manager.model_handlers[dm.Pumpstation].layer - start_pump_feat = find_point_nodes(linestring[0], pumpstation_layer) + if self.MODEL == dm.PumpMap: + pump_layer = self.layer_manager.model_handlers[dm.Pump].layer + start_pump_feat = find_point_nodes(linestring[0], pump_layer) start_pump_id = start_pump_feat["id"] if start_pump_feat else None - start_pump_id_idx = layer_fields.lookupField("pumpstation_id") + start_pump_id_idx = layer_fields.lookupField("pump_id") changes[start_pump_id_idx] = start_pump_id self.layer.changeAttributeValues(feat_id, changes) @@ -386,67 +399,54 @@ class ConnectionNodeHandler(UserLayerHandler): MODEL = dm.ConnectionNode DEFAULTS = MappingProxyType( { + "display_name": "new", "code": "new", + "length": 0.8, + "width": 0.8, + "shape": ManholeShape.ROUND.value, + "exchange_type": ExchangeTypeNode.ISOLATED.value, + "bottom_level": -10.0, } ) - def get_manhole_feat_for_node_id(self, node_id): - """Check if there is a manhole feature defined for node of the given node_id and return it.""" - manhole_feat = None - if node_id not in (None, NULL): - manhole_feats = self.layer_manager.get_layer_features(dm.Manhole, f'"connection_node_id" = {node_id}') - try: - manhole_feat = next(manhole_feats) - except StopIteration: - pass - return manhole_feat - class BoundaryCondition1DHandler(UserLayerHandler): MODEL = dm.BoundaryCondition1D - - -class Lateral1DHandler(UserLayerHandler): - MODEL = dm.Lateral1D - - -class ManholeHandler(UserLayerHandler): - MODEL = dm.Manhole + DEFAULTS = MappingProxyType( + { + "display_name": "new", + "code": "new", + "type": BoundaryType.WATER_LEVEL.value, + "time_units": "seconds", + } + ) RELATED_MODELS = MappingProxyType( { dm.ConnectionNode: 1, } ) + + +class Lateral1DHandler(UserLayerHandler): + MODEL = dm.Lateral1D DEFAULTS = MappingProxyType( { "display_name": "new", "code": "new", - "length": 0.8, - "width": 0.8, - "shape": ManholeShape.ROUND.value, - "manhole_indicator": ManholeIndicator.INSPECTION.value, - "calculation_type": CalculationTypeNode.ISOLATED.value, - "bottom_level": -10.0, + "offset": 0, + "units": Unit.M3_SECONDS.value, + "time_units": TimeUnit.SECONDS.value, + } + ) + RELATED_MODELS = MappingProxyType( + { + dm.ConnectionNode: 1, } ) - - def create_manhole_with_connection_node(self, geometry, template_feat=None): - """Creating manhole with connection node at same location.""" - connection_node_handler = self.layer_manager.model_handlers[dm.ConnectionNode] - if template_feat is not None: - template_connection_node_id = template_feat["connection_node_id"] - node_template = connection_node_handler.layer.getFeature(template_connection_node_id) - node_feat = connection_node_handler.create_new_feature_from_template(node_template, geometry=geometry) - manhole_feat = self.create_new_feature_from_template(template_feat, geometry=geometry) - else: - node_feat = connection_node_handler.create_new_feature(geometry=geometry) - manhole_feat = self.create_new_feature(geometry=geometry) - manhole_feat["connection_node_id"] = node_feat["id"] - return manhole_feat, node_feat -class PumpstationHandler(UserLayerHandler): - MODEL = dm.Pumpstation +class PumpHandler(UserLayerHandler): + MODEL = dm.Pump RELATED_MODELS = MappingProxyType( { dm.ConnectionNode: 1, @@ -464,58 +464,59 @@ class PumpstationHandler(UserLayerHandler): def connect_additional_signals(self): """Connecting signals to action specific for the particular layers.""" - self.layer.featureAdded.connect(self.adjust_manhole_indicator) + self.layer.featureAdded.connect(self.adjust_visualisation) self.layer.geometryChanged.connect(self.trigger_update_node_references) def disconnect_additional_signals(self): """Disconnecting signals to action specific for the particular layers.""" - self.layer.featureAdded.disconnect(self.adjust_manhole_indicator) + self.layer.featureAdded.disconnect(self.adjust_visualisation) self.layer.geometryChanged.disconnect(self.trigger_update_node_references) - def adjust_manhole_indicator(self, feat_id): - """Adjusting underlying manhole attributes.""" + def adjust_visualisation(self, feat_id): + """Adjusting underlying connection node type.""" if feat_id < 0: # This logic should be triggered just once after adding feature, but before committing changes. feat = self.layer.getFeature(feat_id) point = feat.geometry().asPoint() - manhole_handler = self.layer_manager.model_handlers[dm.Manhole] - manhole_layer = manhole_handler.layer - manhole_feat = find_point_nodes(point, manhole_layer) - if manhole_feat is not None: - manhole_fid = manhole_feat.id() - if not manhole_layer.isEditable(): - manhole_layer.startEditing() - manhole_indicator_idx = manhole_layer.fields().lookupField("manhole_indicator") - manhole_layer.changeAttributeValue(manhole_fid, manhole_indicator_idx, ManholeIndicator.PUMP.value) - - def get_pumpstation_feats_for_node_id(self, node_id): - """Check if there is a pumpstation features defined for node of the given node_id and return it.""" + connection_node_handler = self.layer_manager.model_handlers[dm.ConnectionNode] + connection_node_layer = connection_node_handler.layer + connection_node_feat = find_point_nodes(point, connection_node_layer) + if connection_node_feat is not None: + connection_node_fid = connection_node_feat.id() + if not connection_node_layer.isEditable(): + connection_node_layer.startEditing() + visualisation_idx = connection_node_layer.fields().lookupField("visualisation") + connection_node_layer.changeAttributeValue( + connection_node_fid, visualisation_idx, Visualisation.PUMP.value + ) + + def get_pump_feats_for_node_id(self, node_id): + """Check if there is a pump features defined for node of the given node_id and return it.""" pump_feats = [] if node_id not in (None, NULL): exp = f'"connection_node_id" = {node_id}' - pump_feats = list(self.layer_manager.get_layer_features(dm.Pumpstation, exp)) + pump_feats = list(self.layer_manager.get_layer_features(dm.Pump, exp)) return pump_feats def create_pump_with_connection_node(self, geometry, template_feat=None): - """Creating pumpstation with connection node at same location.""" + """Creating pump with connection node at same location.""" connection_node_handler = self.layer_manager.model_handlers[dm.ConnectionNode] if template_feat is not None: template_connection_node_id = template_feat["connection_node_id"] node_template = connection_node_handler.layer.getFeature(template_connection_node_id) node_feat = connection_node_handler.create_new_feature_from_template(node_template, geometry=geometry) - pumpstation_feat = self.create_new_feature_from_template(template_feat, geometry=geometry) + pump_feat = self.create_new_feature_from_template(template_feat, geometry=geometry) else: node_feat = connection_node_handler.create_new_feature(geometry=geometry) - pumpstation_feat = self.create_new_feature(geometry=geometry) - pumpstation_feat["connection_node_id"] = node_feat["id"] - return pumpstation_feat, node_feat + pump_feat = self.create_new_feature(geometry=geometry) + pump_feat["connection_node_id"] = node_feat["id"] + return pump_feat, node_feat -class PumpstationMapHandler(UserLayerHandler): - MODEL = dm.PumpstationMap +class PumpMapHandler(UserLayerHandler): + MODEL = dm.PumpMap RELATED_MODELS = MappingProxyType( { - dm.ConnectionNode: 2, - dm.Pumpstation: 1, + dm.Pump: 1, } ) DEFAULTS = MappingProxyType( @@ -527,17 +528,17 @@ class PumpstationMapHandler(UserLayerHandler): def connect_additional_signals(self): """Connecting signals to action specific for the particular layers.""" - self.layer.featureAdded.connect(self.trigger_simplify_pumpstation_map) + self.layer.featureAdded.connect(self.trigger_simplify_pump_map) self.layer.geometryChanged.connect(self.trigger_update_node_references) def disconnect_additional_signals(self): """Disconnecting signals to action specific for the particular layers.""" - self.layer.featureAdded.disconnect(self.trigger_simplify_pumpstation_map) + self.layer.featureAdded.disconnect(self.trigger_simplify_pump_map) self.layer.geometryChanged.disconnect(self.trigger_update_node_references) - def trigger_simplify_pumpstation_map(self, pumpstation_map_id): + def trigger_simplify_pump_map(self, pump_map_id): """Triggering geometry simplification on newly added feature.""" - simplify_method = partial(self.simplify_linear_feature, pumpstation_map_id) + simplify_method = partial(self.simplify_linear_feature, pump_map_id) QTimer.singleShot(0, simplify_method) @@ -593,14 +594,14 @@ class CulvertHandler(UserLayerHandler): { "display_name": "new", "code": "new", - "dist_calc_points": 1000, - "calculation_type": CalculationTypeCulvert.ISOLATED.value, + "calculation_point_distance": 1000, + "exchange_type": ExchangeTypeCulvert.ISOLATED.value, "friction_type": FrictionType.MANNING.value, "friction_value": 0.02, "discharge_coefficient_positive": 0.8, "discharge_coefficient_negative": 0.8, - "invert_level_start_point": -10.0, - "invert_level_end_point": -10.0, + "invert_level_start": -10.0, + "invert_level_end": -10.0, } ) @@ -660,20 +661,18 @@ class PipeHandler(UserLayerHandler): RELATED_MODELS = MappingProxyType( { dm.ConnectionNode: 2, - dm.Manhole: 2, } ) DEFAULTS = MappingProxyType( { "display_name": "new", "code": "new", - "dist_calc_points": 1000, + "calculation_point_distance": 1000, "friction_type": FrictionType.MANNING.value, - "calculation_type": CalculationTypeNode.ISOLATED.value, - "material": PipeMaterial.CONCRETE.value, + "exchange_type": ExchangeTypeNode.ISOLATED.value, "friction_value": dm.TABLE_MANNING[PipeMaterial.CONCRETE], - "invert_level_start_point": -10.0, - "invert_level_end_point": -10.0, + "invert_level_start": -10.0, + "invert_level_end": -10.0, } ) @@ -703,8 +702,6 @@ def segmentize_pipe(self, pipe_feat_id): """Method to split single pipe into 2 vertices segments.""" connection_node_handler = self.layer_manager.model_handlers[dm.ConnectionNode] connection_node_layer = connection_node_handler.layer - manhole_handler = self.layer_manager.model_handlers[dm.Manhole] - manhole_layer = manhole_handler.layer pipe_feat = self.layer.getFeature(pipe_feat_id) pipe_geom = pipe_feat.geometry() vertices_count = count_vertices(pipe_geom) @@ -714,59 +711,51 @@ def segmentize_pipe(self, pipe_feat_id): points_connection_nodes = {} intermediate_bottom_levels = {} pipe_polyline = pipe_geom.asPolyline() - manhole_template = None + connection_node_template = None for idx, point in enumerate(pipe_polyline): if idx == start_vertex_idx: - connection_node_id = pipe_feat["connection_node_start_id"] + connection_node_id = pipe_feat["connection_node_id_start"] points_connection_nodes[point] = connection_node_id - manhole_template = connection_node_handler.get_manhole_feat_for_node_id(connection_node_id) + connection_node_template = connection_node_handler.get_feat_by_id(connection_node_id) elif idx == end_vertex_idx: - connection_node_id = pipe_feat["connection_node_end_id"] + connection_node_id = pipe_feat["connection_node_id_end"] points_connection_nodes[point] = connection_node_id - intermediate_bottom_levels[point] = pipe_feat["invert_level_end_point"] + intermediate_bottom_levels[point] = pipe_feat["invert_level_end"] else: geom = QgsGeometry.fromPointXY(point) existing_node_feat = find_point_nodes(point, connection_node_layer) if existing_node_feat is not None: new_node_feat = existing_node_feat - existing_manhole_feat = find_point_nodes(point, manhole_layer) - if existing_manhole_feat is None: - new_manhole_feat = manhole_handler.create_new_feature(geom) - new_manhole_feat["connection_node_id"] = new_node_feat["id"] - else: - new_manhole_feat = existing_manhole_feat - intermediate_bottom_levels[point] = new_manhole_feat["bottom_level"] points_connection_nodes[point] = new_node_feat["id"] + intermediate_bottom_levels[point] = existing_node_feat["bottom_level"] else: - extra_feats = manhole_handler.create_manhole_with_connection_node( - geom, template_feat=manhole_template + new_node_feat = connection_node_handler.create_new_feature_from_template( + connection_node_template, geom ) - new_manhole_feat, new_node_feat = extra_feats points_connection_nodes[point] = new_node_feat["id"] - intermediate_bottom_levels[point] = new_manhole_feat["bottom_level"] + intermediate_bottom_levels[point] = new_node_feat["bottom_level"] connection_node_handler.layer.addFeature(new_node_feat) - manhole_handler.layer.addFeature(new_manhole_feat) # Split pipe into segments segments = zip(pipe_polyline, pipe_polyline[1:]) # Extract first segment and update source pipe first_seg_start_point, first_seg_end_point = next(segments) new_source_pipe_geom = QgsGeometry.fromPolylineXY([first_seg_start_point, first_seg_end_point]) pipe_feat.setGeometry(new_source_pipe_geom) - pipe_feat["connection_node_end_id"] = points_connection_nodes[first_seg_end_point] + pipe_feat["connection_node_id_end"] = points_connection_nodes[first_seg_end_point] if first_seg_end_point in intermediate_bottom_levels: - pipe_feat["invert_level_end_point"] = intermediate_bottom_levels[first_seg_end_point] + pipe_feat["invert_level_end"] = intermediate_bottom_levels[first_seg_end_point] self.layer.updateFeature(pipe_feat) # Let's add a new pipes - skip_fields = ["connection_node_start_id", "connection_node_end_id"] + skip_fields = ["connection_node_id_start", "connection_node_id_end"] for start_point, end_point in segments: new_geom = QgsGeometry.fromPolylineXY([start_point, end_point]) new_feat = self.create_new_feature_from_template(pipe_feat, geometry=new_geom, fields_to_skip=skip_fields) - new_feat["connection_node_start_id"] = points_connection_nodes[start_point] - new_feat["connection_node_end_id"] = points_connection_nodes[end_point] + new_feat["connection_node_id_start"] = points_connection_nodes[start_point] + new_feat["connection_node_id_end"] = points_connection_nodes[end_point] if start_point in intermediate_bottom_levels: - new_feat["invert_level_start_point"] = intermediate_bottom_levels[start_point] + new_feat["invert_level_start"] = intermediate_bottom_levels[start_point] if end_point in intermediate_bottom_levels: - new_feat["invert_level_end_point"] = intermediate_bottom_levels[end_point] + new_feat["invert_level_end"] = intermediate_bottom_levels[end_point] self.layer.addFeature(new_feat) @@ -784,8 +773,8 @@ class CrossSectionLocationHandler(UserLayerHandler): "length": 0.8, "width": 0.8, "shape": ManholeShape.ROUND.value, - "manhole_indicator": ManholeIndicator.INSPECTION.value, - "calculation_type": CalculationTypeNode.ISOLATED.value, + "visualisation": Visualisation.INSPECTION.value, + "exchange_type": ExchangeTypeNode.ISOLATED.value, "bottom_level": -10.0, } ) @@ -826,11 +815,6 @@ class ChannelHandler(UserLayerHandler): dm.CrossSectionLocation: float("inf"), } ) - DEFAULTS = MappingProxyType( - { - "zoom_category": ZoomCategories.LOWEST_VISIBILITY.value, - } - ) def connect_additional_signals(self): """Connecting signals to action specific for the particular layers.""" @@ -909,20 +893,42 @@ def on_channel_geometry_change(self, channel_fid, new_geometry): potential_breach_layer.changeGeometry(breach_fid, new_breach_geometry) +class MaterialHandler(UserLayerHandler): + MODEL = dm.Material + + class BoundaryCondition2DHandler(UserLayerHandler): MODEL = dm.BoundaryCondition2D + DEFAULTS = MappingProxyType( + { + "display_name": "new", + "code": "new", + "type": BoundaryType.WATER_LEVEL.value, + "time_units": TimeUnit.SECONDS.value, + } + ) class Lateral2DHandler(UserLayerHandler): MODEL = dm.Lateral2D + DEFAULTS = MappingProxyType( + { + "display_name": "new", + "code": "new", + "offset": 0, + "type": Later2DType.SURFACE.value, + "units": Unit.M3_SECONDS.value, + "time_units": TimeUnit.SECONDS.value, + } + ) -class LinearObstacleHandler(UserLayerHandler): - MODEL = dm.LinearObstacle +class ObstacleHandler(UserLayerHandler): + MODEL = dm.Obstacle -class GridRefinementHandler(UserLayerHandler): - MODEL = dm.GridRefinement +class GridRefinementLineHandler(UserLayerHandler): + MODEL = dm.GridRefinementLine class GridRefinementAreaHandler(UserLayerHandler): @@ -933,8 +939,8 @@ class DEMAverageAreaHandler(UserLayerHandler): MODEL = dm.DEMAverageArea -class WindshieldingHandler(UserLayerHandler): - MODEL = dm.Windshielding +class Windshielding1DHandler(UserLayerHandler): + MODEL = dm.Windshielding1D class PotentialBreachHandler(UserLayerHandler): @@ -997,32 +1003,6 @@ class ExchangeLineHandler(UserLayerHandler): ) -class ImperviousSurfaceHandler(UserLayerHandler): - MODEL = dm.ImperviousSurface - - def connect_additional_signals(self): - """Connecting signals to action specific for the particular layers.""" - self.layer.geometryChanged.connect(self.update_surface_link) - - def disconnect_additional_signals(self): - """Disconnecting signals to action specific for the particular layers.""" - self.layer.geometryChanged.disconnect(self.update_surface_link) - - def update_surface_link(self, feat_id, geometry): - """Update geometry of the surface - node link.""" - surface_handler = self.layer_manager.model_handlers[dm.ImperviousSurface] - surface_layer = surface_handler.layer - surface_link_handler = self.layer_manager.model_handlers[dm.ImperviousSurfaceMap] - surface_link_layer = surface_link_handler.layer - surface_feat = surface_layer.getFeature(feat_id) - link_feat = surface_link_handler.get_feat_by_id(surface_feat["id"], "impervious_surface_id") - point = geometry.centroid().asPoint() - link_linestring = link_feat.geometry().asPolyline() - link_linestring[0] = point - link_new_geom = QgsGeometry.fromPolylineXY(link_linestring) - surface_link_layer.changeGeometry(link_feat.id(), link_new_geom) - - class SurfaceHandler(UserLayerHandler): MODEL = dm.Surface @@ -1049,8 +1029,8 @@ def update_surface_link(self, feat_id, geometry): surface_link_layer.changeGeometry(link_feat.id(), link_new_geom) -class ImperviousSurfaceMapHandler(UserLayerHandler): - MODEL = dm.ImperviousSurfaceMap +class SurfaceMapHandler(UserLayerHandler): + MODEL = dm.SurfaceMap DEFAULTS = MappingProxyType( { "percentage": 100.00, @@ -1073,7 +1053,7 @@ def trigger_update_link_references(self, feat_id, geometry): def update_link_references(self, feat_id, geometry): """Update references to the connections nodes and surfaces after geometry change.""" node_handler = self.layer_manager.model_handlers[dm.ConnectionNode] - surface_handler = self.layer_manager.model_handlers[dm.ImperviousSurface] + surface_handler = self.layer_manager.model_handlers[dm.Surface] node_layer = node_handler.layer surface_layer = surface_handler.layer layer_fields = self.layer.fields() @@ -1084,15 +1064,45 @@ def update_link_references(self, feat_id, geometry): changes = {} start_surface_id = start_surface_feat["id"] if start_surface_feat else None end_connection_node_id = end_connection_node_feat["id"] if end_connection_node_feat else None - start_surface_id_idx = layer_fields.lookupField("impervious_surface_id") + start_surface_id_idx = layer_fields.lookupField("surface_id") end_connection_node_id_idx = layer_fields.lookupField("connection_node_id") changes[start_surface_id_idx] = start_surface_id changes[end_connection_node_id_idx] = end_connection_node_id self.layer.changeAttributeValues(feat_id, changes) -class SurfaceMapHandler(UserLayerHandler): - MODEL = dm.SurfaceMap +class SurfaceParameterHandler(UserLayerHandler): + MODEL = dm.SurfaceParameters + + +class DryWeatherFlowHandler(UserLayerHandler): + MODEL = dm.DryWeatherFlow + + def connect_additional_signals(self): + """Connecting signals to action specific for the particular layers.""" + self.layer.geometryChanged.connect(self.update_dwf_link) + + def disconnect_additional_signals(self): + """Disconnecting signals to action specific for the particular layers.""" + self.layer.geometryChanged.disconnect(self.update_dwf_link) + + def update_dwf_link(self, feat_id, geometry): + """Update geometry of the DWF area - node link.""" + dwf_handler = self.layer_manager.model_handlers[dm.DryWeatherFlow] + dwf_layer = dwf_handler.layer + dwf_link_handler = self.layer_manager.model_handlers[dm.DryWeatherFlowMap] + dwf_link_layer = dwf_link_handler.layer + dwf_feat = dwf_layer.getFeature(feat_id) + link_feat = dwf_link_handler.get_feat_by_id(dwf_feat["id"], "dry_weather_flow_id") + point = geometry.centroid().asPoint() + link_linestring = link_feat.geometry().asPolyline() + link_linestring[0] = point + link_new_geom = QgsGeometry.fromPolylineXY(link_linestring) + dwf_link_layer.changeGeometry(link_feat.id(), link_new_geom) + + +class DryWeatherFlowMapHandler(UserLayerHandler): + MODEL = dm.DryWeatherFlowMap DEFAULTS = MappingProxyType( { "percentage": 100.00, @@ -1115,7 +1125,7 @@ def trigger_update_link_references(self, feat_id, geometry): def update_link_references(self, feat_id, geometry): """Update references to the connections nodes and surfaces after geometry change.""" node_handler = self.layer_manager.model_handlers[dm.ConnectionNode] - surface_handler = self.layer_manager.model_handlers[dm.Surface] + surface_handler = self.layer_manager.model_handlers[dm.DryWeatherFlow] node_layer = node_handler.layer surface_layer = surface_handler.layer layer_fields = self.layer.fields() @@ -1126,19 +1136,19 @@ def update_link_references(self, feat_id, geometry): changes = {} start_surface_id = start_surface_feat["id"] if start_surface_feat else None end_connection_node_id = end_connection_node_feat["id"] if end_connection_node_feat else None - start_surface_id_idx = layer_fields.lookupField("surface_id") + start_surface_id_idx = layer_fields.lookupField("dry_weather_flow_id") end_connection_node_id_idx = layer_fields.lookupField("connection_node_id") changes[start_surface_id_idx] = start_surface_id changes[end_connection_node_id_idx] = end_connection_node_id self.layer.changeAttributeValues(feat_id, changes) -class SurfaceParameterHandler(UserLayerHandler): - MODEL = dm.SurfaceParameters +class DryWeatherFlowDistributionHandler(UserLayerHandler): + MODEL = dm.DryWeatherFlowDistribution -class GlobalSettingsHandler(UserLayerHandler): - MODEL = dm.GlobalSettings +class ModelSettingsHandler(UserLayerHandler): + MODEL = dm.ModelSettings class AggregationSettingsHandler(UserLayerHandler): @@ -1157,98 +1167,258 @@ class InterflowSettingsHandler(UserLayerHandler): MODEL = dm.InterflowSettings +class InterceptionSettingsHandler(UserLayerHandler): + MODEL = dm.InterceptionSettings + + +class InitialConditionsSettingsHandler(UserLayerHandler): + MODEL = dm.InitialConditionsSettings + + +class PhysicalSettingsHandler(UserLayerHandler): + MODEL = dm.PhysicalSettings + + class NumericalSettingsHandler(UserLayerHandler): MODEL = dm.NumericalSettings -class SchemaVersionHandler(UserLayerHandler): - MODEL = dm.SchemaVersion +class SimulationTemplateSettingsHandler(UserLayerHandler): + MODEL = dm.SimulationTemplateSettings + + +class TimeStepSettingsHandler(UserLayerHandler): + MODEL = dm.TimeStepSettings + + +class VegetationDrag2DHandler(UserLayerHandler): + MODEL = dm.VegetationDrag2D + + +class TagHandler(UserLayerHandler): + MODEL = dm.Tag + + +class AbstractControlHandler(UserLayerHandler): + + @cached_property + def target_data_models(self): + return {model_cls.__tablename__: model_cls for model_cls in [dm.Pump, dm.Orifice, dm.Weir]} + + def snap_to_target_centroid(self, feat_id): + """Move geometry to target centroid (if target is linear).""" + feat = self.layer.getFeature(feat_id) + target_feat_id = feat["target_id"] + if not target_feat_id: + return + target_type = feat["target_type"] + if not target_type: + return + try: + target_model_cls = self.target_data_models[target_type] + except KeyError: + return + if target_model_cls.__geometrytype__ != GeometryType.Linestring: + return + target_handler = self.layer_manager.model_handlers[target_model_cls] + target_layer = target_handler.layer + target_feat = target_layer.getFeature(target_feat_id) + target_feat_centroid = target_feat.geometry().centroid() + new_source_geom = QgsGeometry(target_feat_centroid) + feat.setGeometry(new_source_geom) + self.layer.updateFeature(feat) + def update_target_references(self, feat_id, geometry): + """Update references to the target after geometry change.""" + feature_point = geometry.asPoint() + target_feature, target_type = None, None + layer_fields = self.layer.fields() + for model_cls in self.target_data_models.values(): + structure_handler = self.layer_manager.model_handlers[model_cls] + structure_layer = structure_handler.layer + dm_geometry_type = model_cls.__geometrytype__ + if dm_geometry_type == GeometryType.Point: + structure_feat = find_point_nodes(feature_point, structure_layer) + elif dm_geometry_type == GeometryType.Linestring: + structure_feat = find_point_polyline(feature_point, structure_layer) + else: + continue + if structure_feat is not None: + target_feature, target_type = structure_feat, model_cls.__tablename__ + break + target_id = target_feature["id"] if target_feature is not None else None + target_id_idx = layer_fields.lookupField("target_id") + target_type_idx = layer_fields.lookupField("target_type") + changes = {target_id_idx: target_id, target_type_idx: target_type} + self.layer.changeAttributeValues(feat_id, changes) -class VegetationDragHandler(UserLayerHandler): - MODEL = dm.VegetationDrag + def trigger_snap_to_target_centroid(self, feat_id): + """Triggering snapping geometry to the target centroid after feature added.""" + snap_to_target_centroid_method = partial(self.snap_to_target_centroid, feat_id) + QTimer.singleShot(0, snap_to_target_centroid_method) + def trigger_update_target_references(self, feat_id, geometry): + """Triggering update of the target references after feature geometry change.""" + update_target_references_method = partial(self.update_target_references, feat_id, geometry) + QTimer.singleShot(0, update_target_references_method) -class ControlHandler(UserLayerHandler): - MODEL = dm.Control +class MemoryControlHandler(AbstractControlHandler): + MODEL = dm.MemoryControl + DEFAULTS = MappingProxyType( + { + "display_name": "new", + "code": "new", + } + ) -class ControlDeltaHandler(UserLayerHandler): - MODEL = dm.ControlDelta + def connect_additional_signals(self): + """Connecting signals to action specific for the particular layers.""" + self.layer.featureAdded.connect(self.trigger_snap_to_target_centroid) + self.layer.geometryChanged.connect(self.trigger_update_target_references) + def disconnect_additional_signals(self): + """Disconnecting signals to action specific for the particular layers.""" + self.layer.featureAdded.disconnect(self.trigger_snap_to_target_centroid) + self.layer.geometryChanged.disconnect(self.trigger_update_target_references) -class ControlGroupHandler(UserLayerHandler): - MODEL = dm.ControlGroup +class TableControlHandler(AbstractControlHandler): + MODEL = dm.TableControl + DEFAULTS = MappingProxyType( + { + "display_name": "new", + "code": "new", + } + ) -class ControlMeasureGroupHandler(UserLayerHandler): - MODEL = dm.ControlMeasureGroup + def connect_additional_signals(self): + """Connecting signals to action specific for the particular layers.""" + self.layer.featureAdded.connect(self.trigger_snap_to_target_centroid) + self.layer.geometryChanged.connect(self.trigger_update_target_references) + def disconnect_additional_signals(self): + """Disconnecting signals to action specific for the particular layers.""" + self.layer.featureAdded.disconnect(self.trigger_snap_to_target_centroid) + self.layer.geometryChanged.disconnect(self.trigger_update_target_references) -class ControlMeasureMapHandler(UserLayerHandler): - MODEL = dm.ControlMeasureMap +class MeasureLocationHandler(UserLayerHandler): + MODEL = dm.MeasureLocation + DEFAULTS = MappingProxyType( + { + "display_name": "new", + "code": "new", + } + ) -class ControlMemoryHandler(UserLayerHandler): - MODEL = dm.ControlMemory +class MeasureMapHandler(UserLayerHandler): + MODEL = dm.MeasureMap + DEFAULTS = MappingProxyType( + { + "display_name": "new", + "code": "new", + "weight": 1.0, + } + ) -class ControlPIDHandler(UserLayerHandler): - MODEL = dm.ControlPID + @cached_property + def control_data_models(self): + return {model_cls.__tablename__: model_cls for model_cls in [dm.MemoryControl, dm.TableControl]} + + def update_control_references(self, feat_id, geometry): + """Update references to the control and measure location feature after geometry change.""" + feature_polyline = geometry.asPolyline() + feature_start_point, feature_end_point = feature_polyline[0], feature_polyline[-1] + control_feature, control_type = None, None + layer_fields = self.layer.fields() + for model_cls in self.control_data_models.values(): + control_handler = self.layer_manager.model_handlers[model_cls] + control_layer = control_handler.layer + control_feat = find_point_nodes(feature_end_point, control_layer) + if control_feat is not None: + control_feature, control_type = control_feat, model_cls.__tablename__ + break + control_id = control_feature["id"] if control_feature is not None else None + control_id_idx = layer_fields.lookupField("control_id") + control_type_idx = layer_fields.lookupField("control_type") + changes = {control_id_idx: control_id, control_type_idx: control_type} + measure_location_handler = self.layer_manager.model_handlers[dm.MeasureLocation] + measure_location_layer = measure_location_handler.layer + measure_location_feat = find_point_nodes(feature_start_point, measure_location_layer) + if measure_location_feat is not None: + measure_location_id = measure_location_feat["id"] if measure_location_feat is not None else None + measure_location_id_idx = layer_fields.lookupField("control_measure_location_id") + changes[measure_location_id_idx] = measure_location_id + self.layer.changeAttributeValues(feat_id, changes) + def trigger_update_control_references(self, feat_id, geometry): + """Triggering update of the control and measure location references after feature geometry change.""" + update_control_references_method = partial(self.update_control_references, feat_id, geometry) + QTimer.singleShot(0, update_control_references_method) -class ControlTableHandler(UserLayerHandler): - MODEL = dm.ControlTable + def trigger_simplify_measure_map(self, measure_map_id): + """Triggering geometry simplification on newly added feature.""" + simplify_method = partial(self.simplify_linear_feature, measure_map_id) + QTimer.singleShot(0, simplify_method) + def connect_additional_signals(self): + """Connecting signals to action specific for the particular layers.""" + self.layer.featureAdded.connect(self.trigger_simplify_measure_map) + self.layer.geometryChanged.connect(self.trigger_update_control_references) -class ControlTimedHandler(UserLayerHandler): - MODEL = dm.ControlTimed + def disconnect_additional_signals(self): + """Disconnecting signals to action specific for the particular layers.""" + self.layer.featureAdded.disconnect(self.trigger_simplify_measure_map) + self.layer.geometryChanged.disconnect(self.trigger_update_control_references) ALL_HANDLERS = ( ConnectionNodeHandler, BoundaryCondition1DHandler, Lateral1DHandler, - ManholeHandler, - PumpstationHandler, - PumpstationMapHandler, + PumpHandler, + PumpMapHandler, WeirHandler, CulvertHandler, OrificeHandler, PipeHandler, CrossSectionLocationHandler, + MaterialHandler, ChannelHandler, BoundaryCondition2DHandler, Lateral2DHandler, - LinearObstacleHandler, - GridRefinementHandler, + ObstacleHandler, + GridRefinementLineHandler, GridRefinementAreaHandler, DEMAverageAreaHandler, - WindshieldingHandler, + Windshielding1DHandler, PotentialBreachHandler, ExchangeLineHandler, - ImperviousSurfaceHandler, SurfaceHandler, - ImperviousSurfaceMapHandler, SurfaceMapHandler, SurfaceParameterHandler, - GlobalSettingsHandler, + DryWeatherFlowHandler, + DryWeatherFlowMapHandler, + DryWeatherFlowDistributionHandler, + ModelSettingsHandler, AggregationSettingsHandler, SimpleInfiltrationSettingsHandler, GroundWaterSettingsHandler, InterflowSettingsHandler, + InitialConditionsSettingsHandler, + InterceptionSettingsHandler, NumericalSettingsHandler, - SchemaVersionHandler, - VegetationDragHandler, - ControlHandler, - ControlDeltaHandler, - ControlGroupHandler, - ControlMeasureGroupHandler, - ControlMeasureMapHandler, - ControlMemoryHandler, - ControlPIDHandler, - ControlTableHandler, - ControlTimedHandler, + PhysicalSettingsHandler, + SimulationTemplateSettingsHandler, + TimeStepSettingsHandler, + TagHandler, + VegetationDrag2DHandler, + MeasureMapHandler, + MeasureLocationHandler, + MemoryControlHandler, + TableControlHandler, ) MODEL_HANDLERS = MappingProxyType({handler.MODEL: handler for handler in ALL_HANDLERS}) diff --git a/threedi_schematisation_editor/user_layer_manager.py b/threedi_schematisation_editor/user_layer_manager.py index c29ea45..93d1eca 100644 --- a/threedi_schematisation_editor/user_layer_manager.py +++ b/threedi_schematisation_editor/user_layer_manager.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 by Lutra Consulting +# Copyright (C) 2025 by Lutra Consulting import os import re from functools import cached_property @@ -7,6 +7,8 @@ from qgis.core import ( Qgis, + QgsEditFormConfig, + QgsEditorWidgetSetup, QgsExpression, QgsFeatureRequest, QgsProject, @@ -50,12 +52,20 @@ class LayersManager: ("1D", dm.MODEL_1D_ELEMENTS), ("1D2D", dm.MODEL_1D2D_ELEMENTS), ("2D", dm.MODEL_2D_ELEMENTS), - ("Inflow", dm.INFLOW_ELEMENTS), - ("Control structures", dm.CONTROL_STRUCTURES_ELEMENTS), + ("Laterals & 0D inflow", dm.MODEL_0D_INFLOW_ELEMENTS), + ("Structure control", dm.STRUCTURE_CONTROL_ELEMENTS), + ("Hydrological processes", dm.HYDROLOGICAL_PROCESSES), ("Settings", dm.SETTINGS_ELEMENTS), ) - RASTER_GROUPS = (("Model rasters", dm.ELEMENTS_WITH_RASTERS),) + RASTER_GROUPS = (("Rasters", dm.ELEMENTS_WITH_RASTERS),) LAYER_JOINS = MappingProxyType({}) + VALUE_RELATIONS = MappingProxyType( + { + # parent model: (child model, parent column, child key column, child value column) + dm.Surface: (dm.SurfaceParameters, "surface_parameters_id", "id", "description"), + dm.DryWeatherFlow: (dm.DryWeatherFlowDistribution, "dry_weather_flow_distribution_id", "id", "description"), + } + ) def __init__(self, iface, user_communication, model_gpkg_path): self.iface = iface @@ -72,22 +82,25 @@ def __init__(self, iface, user_communication, model_gpkg_path): def snapping_groups(self): snap_groups = { dm.ConnectionNode: { - dm.Manhole, dm.Pipe, dm.Weir, dm.Orifice, dm.Culvert, - dm.Pumpstation, - dm.PumpstationMap, + dm.Pump, + dm.PumpMap, dm.Channel, dm.SurfaceMap, - dm.ImperviousSurfaceMap, + dm.DryWeatherFlowMap, dm.Lateral1D, dm.BoundaryCondition1D, + dm.MeasureLocation, }, dm.Channel: {dm.ConnectionNode, dm.CrossSectionLocation, dm.PotentialBreach}, dm.CrossSectionLocation: {dm.Channel}, dm.PotentialBreach: {dm.Channel}, + dm.MemoryControl: {dm.Pump, dm.Orifice, dm.Weir}, + dm.TableControl: {dm.Pump, dm.Orifice, dm.Weir}, + dm.MeasureMap: {dm.MemoryControl, dm.TableControl}, } for model_cls in dm.ALL_MODELS: if model_cls.__geometrytype__ == en.GeometryType.NoGeometry: @@ -102,7 +115,7 @@ def validate_layers(self, return_raw_errors=False): """Validate all layers registered within handlers.""" fixed_errors, unsolved_errors = [], [] handlers_count = len(self.model_handlers) - msg = "Validating data before the export..." + msg = "Validating layers data..." self.uc.progress_bar(msg, 0, handlers_count, 0, clear_msg_bar=True) QCoreApplication.processEvents() for i, handler in enumerate(self.model_handlers.values(), start=1): @@ -141,17 +154,23 @@ def set_layers_snapping(self, layer_handler): snap_config.setMode(QgsSnappingConfig.AdvancedConfiguration) snap_config.setIntersectionSnapping(True) individual_configs = snap_config.individualLayerSettings() + vertex_segment_snapping_models = { + dm.CrossSectionLocation, + dm.PotentialBreach, + dm.TableControl, + dm.MemoryControl, + } try: snap_type = ( Qgis.SnappingTypes(Qgis.SnappingType.Vertex | Qgis.SnappingType.Segment) - if layer_model in {dm.CrossSectionLocation, dm.PotentialBreach} + if layer_model in vertex_segment_snapping_models else Qgis.SnappingType.Vertex ) except AttributeError: # Backward compatibility for QGIS versions before introducing `Qgis.SnappingTypes` snap_type = ( QgsSnappingConfig.VertexFlag | QgsSnappingConfig.SegmentFlag - if layer_model in {dm.CrossSectionLocation, dm.PotentialBreach} + if layer_model in vertex_segment_snapping_models else QgsSnappingConfig.VertexFlag ) for layer in snapped_layers: @@ -183,11 +202,12 @@ def common_editing_group(self): dm.MODEL_1D_ELEMENTS + dm.MODEL_1D2D_ELEMENTS + ( - dm.ImperviousSurface, - dm.ImperviousSurfaceMap, + dm.DryWeatherFlow, + dm.DryWeatherFlowMap, dm.Surface, dm.SurfaceMap, ) + + dm.STRUCTURE_CONTROL_ELEMENTS ) return linked_models @@ -276,6 +296,24 @@ def unregister_custom_functions(): QgsExpression.unregisterFunction("cross_section_max_height") QgsExpression.unregisterFunction("cross_section_max_width") + def setup_value_relation_widgets(self): + """Setup value relation widget.""" + for parent_model_cls, ( + child_model_cls, + parent_column, + key_column, + value_column, + ) in self.VALUE_RELATIONS.items(): + parent_layer = self.model_handlers[parent_model_cls].layer + parent_column_idx = parent_layer.fields().lookupField(parent_column) + child_layer = self.model_handlers[child_model_cls].layer + default_ews = parent_layer.editorWidgetSetup(parent_column_idx) + config = default_ews.config() + config["Layer"] = child_layer.id() + config["LayerSource"] = child_layer.source() + ews = QgsEditorWidgetSetup(default_ews.type(), config) + parent_layer.setEditorWidgetSetup(parent_column_idx, ews) + def create_groups(self): """Creating all User Layers groups.""" self.remove_groups() @@ -339,10 +377,16 @@ def initialize_data_model_layer(self, model_cls): default_edit_form_config = layer.editFormConfig() if form_ui_path: default_edit_form_config.setUiForm(form_ui_path) + try: + default_edit_form_config.setInitCodeSource(Qgis.AttributeFormPythonInitCodeSource.Dialog) + except AttributeError: + default_edit_form_config.setInitCodeSource(QgsEditFormConfig.PythonInitCodeSource.Dialog) + default_edit_form_config.setInitFunction("open_edit_form") + default_edit_form_config.setInitCode("from threedi_schematisation_editor.utils import open_edit_form") + set_field_default_value(layer, "id", "") else: - if model_cls != dm.SchemaVersion: - id_increment_expression = "if (maximum(id) is null, 1, maximum(id) + 1)" - set_field_default_value(layer, "id", id_increment_expression) + id_increment_expression = "if (maximum(id) is null, 1, maximum(id) + 1)" + set_field_default_value(layer, "id", id_increment_expression) for style in all_styles: style_manager.setCurrentStyle(style) layer.setEditFormConfig(default_edit_form_config) @@ -398,7 +442,7 @@ def load_raster_layers(self): relative_path = feat[raster_file_field] if not relative_path: continue - raster_filepath = os.path.normpath(os.path.join(gpkg_dir, relative_path)) + raster_filepath = os.path.normpath(os.path.join(gpkg_dir, "rasters", relative_path)) if not os.path.isfile(raster_filepath): continue rlayer = QgsRasterLayer(raster_filepath, raster_layer_name) @@ -423,6 +467,7 @@ def load_all_layers(self, from_project=False): self.remove_loaded_layers(dry_remove=True) self.register_groups() self.register_vector_layers() + self.setup_value_relation_widgets() self.iface.setActiveLayer(self.model_handlers[dm.ConnectionNode].layer) def remove_loaded_layers(self, dry_remove=False): diff --git a/threedi_schematisation_editor/utils.py b/threedi_schematisation_editor/utils.py index 5b84794..d507827 100644 --- a/threedi_schematisation_editor/utils.py +++ b/threedi_schematisation_editor/utils.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 by Lutra Consulting +# Copyright (C) 2025 by Lutra Consulting import os import shutil import sys @@ -13,7 +13,6 @@ NULL, QgsBilinearRasterResampler, QgsCoordinateTransform, - QgsDataSourceUri, QgsEditorWidgetSetup, QgsExpression, QgsFeature, @@ -54,24 +53,14 @@ } -def backup_sqlite(filename): - """Make a backup of the sqlite database.""" +def backup_schematisation_file(filename): + """Make a backup of the schematisation file.""" backup_folder = os.path.join(os.path.dirname(os.path.dirname(filename)), "_backup") os.makedirs(backup_folder, exist_ok=True) prefix = str(uuid4())[:8] - backup_sqlite_path = os.path.join(backup_folder, f"{prefix}_{os.path.basename(filename)}") - shutil.copyfile(filename, backup_sqlite_path) - return backup_sqlite_path - - -def cast_if_bool(value): - """Function for changing True/False from GeoPackage layers to 0/1 integers used in Spatialite layers.""" - if value is True: - return 1 - elif value is False: - return 0 - else: - return value + backup_file_path = os.path.join(backup_folder, f"{prefix}_{os.path.basename(filename)}") + shutil.copyfile(filename, backup_file_path) + return backup_file_path def vector_layer_factory(annotated_model_cls, epsg=4326): @@ -140,7 +129,7 @@ def layer_to_gpkg(layer, gpkg_filename, overwrite=False, driver_name="GPKG"): QgsVectorFileWriter.CreateOrOverwriteLayer if overwrite is False else QgsVectorFileWriter.CreateOrOverwriteFile ) fields = layer.fields() - valid_indexes = [fields.lookupField(fname) for fname in fields.names() if fname != "fid"] + valid_indexes = [fields.lookupField(fname) for fname in fields.names()] options.attributes = valid_indexes options.driverName = driver_name options.layerName = layer.name() @@ -156,22 +145,6 @@ def gpkg_layer(gpkg_path, table_name, layer_name=None): return vlayer -def sqlite_layer(sqlite_path, table_name, layer_name=None, geom_column="the_geom", schema=""): - """Creating vector layer out of Spatialite source.""" - uri = QgsDataSourceUri() - uri.setDatabase(sqlite_path) - uri.setDataSource(schema, table_name, geom_column) - layer_name = table_name if layer_name is None else layer_name - vlayer = QgsVectorLayer(uri.uri(), layer_name, "spatialite") - return vlayer - - -def create_empty_model(export_sqlite_path): - """Copying Spatialite database template with 3Di model data structure.""" - empty_sqlite = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "empty.sqlite") - shutil.copy(empty_sqlite, export_sqlite_path) - - def get_qml_style_path(style_name, *subfolders): """Getting QML styles path.""" qml_filename = f"{style_name}.qml" @@ -185,7 +158,11 @@ def get_multiple_qml_style_paths(styles_folder_name, *subfolders): """Getting QML styles paths within given styles folder.""" styles_folder_path = os.path.join(os.path.dirname(__file__), "styles", *subfolders, styles_folder_name) if os.path.exists(styles_folder_path): - qml_paths = [os.path.join(styles_folder_path, q) for q in os.listdir(styles_folder_path) if q.endswith(".qml")] + qml_paths = [ + os.path.normpath(os.path.join(styles_folder_path, q)) + for q in os.listdir(styles_folder_path) + if q.endswith(".qml") + ] else: qml_paths = [] return qml_paths @@ -194,12 +171,18 @@ def get_multiple_qml_style_paths(styles_folder_name, *subfolders): def get_form_ui_path(table_name): """Getting UI form path for a given table name.""" ui_filename = f"{table_name}.ui" - filepath = os.path.join(os.path.dirname(__file__), "forms", "ui", ui_filename) + filepath = os.path.normpath(os.path.join(os.path.dirname(__file__), "forms", "ui", ui_filename)) if os.path.isfile(filepath): return filepath return None +def get_icon_path(icon_filename, root_dir=None): + """Getting icon path for a given icon file.""" + icon_filepath = os.path.join(os.path.dirname(__file__) if root_dir is None else root_dir, "icons", icon_filename) + return icon_filepath + + def create_tree_group(name, insert_at_top=False, root=None): """Creating layer tree group with given name.""" root = QgsProject.instance().layerTreeRoot() if root is None else root @@ -295,7 +278,6 @@ def set_initial_layer_configuration(layer, model_cls): fields = layer.dataProvider().fields() columns = attr_table_config.columns() model_hidden_fields = model_cls.hidden_fields() - model_hidden_fields.add("fid") for column in columns: column_name = column.name if column_name in model_hidden_fields: @@ -333,7 +315,7 @@ def load_user_layers(gpkg_path): groups = OrderedDict() groups["1D"] = dm.MODEL_1D_ELEMENTS groups["2D"] = dm.MODEL_2D_ELEMENTS - groups["Inflow"] = dm.INFLOW_ELEMENTS + groups["Inflow"] = dm.MODEL_0D_INFLOW_ELEMENTS groups["Settings"] = dm.SETTINGS_ELEMENTS default_style_name = "default" for group_name, group_models in groups.items(): @@ -376,7 +358,7 @@ def load_user_layers(gpkg_path): def load_model_raster_layers(gpkg_path): """Loading raster layers related with 3Di model.""" gpkg_dir = os.path.dirname(gpkg_path) - group_name = "Model rasters" + group_name = "Rasters" get_tree_group(group_name) for settings_cls in dm.SETTINGS_ELEMENTS: if settings_cls.RELATED_RASTERS is None: @@ -402,7 +384,16 @@ def load_model_raster_layers(gpkg_path): def remove_user_layers(): """Removing all 3Di model User Layers and rasters from the map canvas.""" - groups = ["1D", "2D", "Inflow", "Settings", "Model rasters"] + groups = [ + "1D", + "1D2D", + "2D", + "Laterals & 0D inflow", + "Structure control", + "Hydrological processes", + "Settings", + "Rasters", + ] for group_name in groups: remove_group_with_children(group_name) @@ -612,7 +603,7 @@ def get_feature_by_id(layer, object_id, id_field="id"): def add_settings_entry(gpkg_path, **initial_fields_values): """Adding initial settings entry with defined fields values.""" - settings_layer = gpkg_layer(gpkg_path, dm.GlobalSettings.__tablename__) + settings_layer = gpkg_layer(gpkg_path, dm.ModelSettings.__tablename__) if settings_layer.featureCount() == 0: settings_fields = settings_layer.fields() settings_feat = QgsFeature(settings_fields) @@ -703,24 +694,24 @@ def modify_raster_style(raster_layer, limits=QgsRasterMinMaxOrigin.MinMax, exten raster_layer.setRenderer(renderer) -def migrate_spatialite_schema(sqlite_filepath): +def migrate_schematisation_schema(schematisation_filepath): migration_succeed = False try: from threedi_schema import ThreediDatabase, errors - threedi_db = ThreediDatabase(sqlite_filepath) + threedi_db = ThreediDatabase(schematisation_filepath) schema = threedi_db.schema - backup_filepath = backup_sqlite(sqlite_filepath) - schema.upgrade(backup=False, upgrade_spatialite_version=True) - schema.set_spatial_indexes() + backup_filepath = backup_schematisation_file(schematisation_filepath) + # schema.upgrade(backup=False, convert_to_geopackage=True) + schema.convert_to_geopackage() shutil.rmtree(os.path.dirname(backup_filepath)) migration_succeed = True migration_feedback_msg = "Migration succeed." except ImportError: - migration_feedback_msg = "Missing threedi-schema library. Schema migration failed." + migration_feedback_msg = "Missing threedi-schema library (or its dependencies). Schema migration failed." except errors.UpgradeFailedError: migration_feedback_msg = ( - "The spatialite database schema cannot be migrated to the current version. " + "The schematisation database schema cannot be migrated to the current version. " "Please contact the service desk for assistance." ) except Exception as e: @@ -750,59 +741,6 @@ def bypass_max_path_limit(path, is_file=False): return valid_path -def ensure_valid_schema(schematisation_sqlite, communication): - """Check if schema version is up-to-date and migrate it if needed.""" - try: - from threedi_schema import ThreediDatabase, errors - except ImportError: - return - schematisation_dirname = os.path.dirname(schematisation_sqlite) - schematisation_filename = os.path.basename(schematisation_sqlite) - backup_folder = os.path.join(schematisation_dirname, "_backup") - os.makedirs(bypass_max_path_limit(backup_folder), exist_ok=True) - prefix = str(uuid4())[:8] - backup_sqlite_path = os.path.join(backup_folder, f"{prefix}_{schematisation_filename}") - shutil.copyfile(schematisation_sqlite, bypass_max_path_limit(backup_sqlite_path, is_file=True)) - threedi_db = ThreediDatabase(schematisation_sqlite) - schema = threedi_db.schema - try: - schema.validate_schema() - schema.set_spatial_indexes() - except errors.MigrationMissingError: - warn_and_ask_msg = ( - "The selected spatialite cannot be used because its database schema version is out of date. " - "Would you like to migrate your spatialite to the current schema version?" - ) - do_migration = communication.ask(None, "Missing migration", warn_and_ask_msg) - if not do_migration: - return False - try: - schema.upgrade(backup=False, upgrade_spatialite_version=True) - schema.set_spatial_indexes() - shutil.rmtree(backup_folder) - except errors.MigrationMissingError as e: - if "This tool cannot update versions below 160" in str(e): - error_msg = ( - "This tool cannot update versions below 160. " "Please contact the service desk for assistance." - ) - communication.show_error(error_msg) - return False - else: - raise e - except errors.UpgradeFailedError: - error_msg = ( - "The spatialite database schema cannot be migrated to the current version. " - "Please contact the service desk for assistance." - ) - communication.show_error(error_msg) - return False - except Exception as e: - error_msg = f"{e}" - communication.show_error(error_msg) - return False - return True - - def validation_errors_summary(validation_errors): """Create validation summary message grouped by the data model class.""" summary_per_model = [] @@ -972,7 +910,7 @@ def setup_friction_and_vegetation_widgets(custom_form, cross_section_shape_widge related_widget.setDisabled(True) cross_section_shape = custom_form.get_widget_value(cross_section_shape_widget) friction_value = custom_form.get_widget_value(friction_widget) - custom_form.update_cross_section_table_header("cross_section_friction_table") + custom_form.update_cross_section_table_header("cross_section_friction_values") custom_form.update_cross_section_table_header("cross_section_vegetation_table") if not custom_form.layer.isEditable(): return @@ -1070,3 +1008,54 @@ def extract_substring(linestring_geometry, start_distance, end_distance): before_start_geometry = QgsGeometry(before_start_substring) after_end_geometry = QgsGeometry(after_end_substring) return substring_geometry, before_start_geometry, after_end_geometry + + +# def ensure_valid_schema(schematisation_filepath, communication): +# """Check if schema version is up-to-date and migrate it if needed.""" +# try: +# from threedi_schema import ThreediDatabase, errors +# except ImportError: +# return +# schematisation_dirname = os.path.dirname(schematisation_filepath) +# schematisation_filename = os.path.basename(schematisation_filepath) +# backup_folder = os.path.join(schematisation_dirname, "_backup") +# os.makedirs(bypass_max_path_limit(backup_folder), exist_ok=True) +# prefix = str(uuid4())[:8] +# backup_filepath = os.path.join(backup_folder, f"{prefix}_{schematisation_filename}") +# shutil.copyfile(schematisation_filepath, bypass_max_path_limit(backup_filepath, is_file=True)) +# threedi_db = ThreediDatabase(schematisation_filepath) +# schema = threedi_db.schema +# try: +# schema.validate_schema() +# except errors.MigrationMissingError: +# warn_and_ask_msg = ( +# "The selected geopackage cannot be used because its database schema version is out of date. " +# "Would you like to migrate your geopackage to the current schema version?" +# ) +# do_migration = communication.ask(None, "Missing migration", warn_and_ask_msg) +# if not do_migration: +# return False +# try: +# schema.upgrade(backup=False, upgrade_schema_version=True) +# shutil.rmtree(backup_folder) +# except errors.MigrationMissingError as e: +# if "This tool cannot update versions below 160" in str(e): +# error_msg = ( +# "This tool cannot update versions below 160. " "Please contact the service desk for assistance." +# ) +# communication.show_error(error_msg) +# return False +# else: +# raise e +# except errors.UpgradeFailedError: +# error_msg = ( +# "The geopackage database schema cannot be migrated to the current version. " +# "Please contact the service desk for assistance." +# ) +# communication.show_error(error_msg) +# return False +# except Exception as e: +# error_msg = f"{e}" +# communication.show_error(error_msg) +# return False +# return True diff --git a/threedi_schematisation_editor/validators.py b/threedi_schematisation_editor/validators.py index 02542e9..6f42cf1 100644 --- a/threedi_schematisation_editor/validators.py +++ b/threedi_schematisation_editor/validators.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 by Lutra Consulting +# Copyright (C) 2025 by Lutra Consulting from functools import cached_property from itertools import chain diff --git a/threedi_schematisation_editor/workspace.py b/threedi_schematisation_editor/workspace.py index 509c133..bc0cfcf 100644 --- a/threedi_schematisation_editor/workspace.py +++ b/threedi_schematisation_editor/workspace.py @@ -1,4 +1,4 @@ -# Copyright (C) 2024 by Lutra Consulting +# Copyright (C) 2025 by Lutra Consulting class WorkspaceContextManager: diff --git a/zip_plugin.py b/zip_plugin.py index 6d69604..3208624 100644 --- a/zip_plugin.py +++ b/zip_plugin.py @@ -1,5 +1,5 @@ # 3Di Schematisation Editor for QGIS, licensed under GPLv2 or (at your option) any later version -# Copyright (C) 2023 by Lutra Consulting for 3Di Water Management +# Copyright (C) 2025 by Lutra Consulting for 3Di Water Management import os import re import shutil