From d7b79f6fae2d9d4afbbec51d5cda0fc87b7aca21 Mon Sep 17 00:00:00 2001 From: Niko Aarnio Date: Thu, 23 Jan 2025 12:08:21 +0200 Subject: [PATCH] add regulation groups and underground type to feature template models Initialization logic of libraries from open project was improved also. --- arho_feature_template/core/models.py | 28 +++++++++- arho_feature_template/core/plan_manager.py | 56 +++++++++++++------ .../gui/docks/new_feature_dock.py | 28 ++++++---- arho_feature_template/plugin.py | 22 +++++--- .../project/layers/__init__.py | 16 +++++- .../feature_templates/katja_asemakaava.yaml | 2 + 6 files changed, 109 insertions(+), 43 deletions(-) diff --git a/arho_feature_template/core/models.py b/arho_feature_template/core/models.py index 6511c95..6fff605 100644 --- a/arho_feature_template/core/models.py +++ b/arho_feature_template/core/models.py @@ -10,6 +10,7 @@ import yaml from arho_feature_template.exceptions import ConfigSyntaxError +from arho_feature_template.project.layers.code_layers import UndergroundTypeLayer from arho_feature_template.qgis_plugin_tools.tools.resources import resources_path from arho_feature_template.utils.misc_utils import LANGUAGE, get_layer_by_name, iface @@ -45,7 +46,20 @@ class FeatureTemplateLibrary: feature_templates: list[PlanFeature] @classmethod - def from_config_file(cls, config_fp: Path) -> FeatureTemplateLibrary: + def find_matching_group_config(cls, group_name: str, regulation_group_libraries: list[RegulationGroupLibrary]): + for library in regulation_group_libraries: + for category in library.regulation_group_categories: + for group in category.regulation_groups: + if group.name == group_name: + return group + return None + + @classmethod + def from_config_file( + cls, config_fp: Path, regulation_group_libraries: list[RegulationGroupLibrary] + ) -> FeatureTemplateLibrary: + get_underground_id = UndergroundTypeLayer.get_attribute_value_by_another_attribute_value + with config_fp.open(encoding="utf-8") as f: data = yaml.safe_load(f) try: @@ -56,11 +70,19 @@ def from_config_file(cls, config_fp: Path) -> FeatureTemplateLibrary: feature_templates=[ PlanFeature( geom=None, - type_of_underground_id=feature_data.get("type_of_underground"), # Need ID conversion? + type_of_underground_id=( + get_underground_id("id", "value", feature_data.get("type_of_underground")) + if feature_data.get("type_of_underground") + else None + ), layer_name=feature_data.get("layer_name"), name=feature_data.get("name"), description=feature_data.get("description"), - regulation_groups=[], # feature_data.get("regulation_groups"), # Handle regulation group creaton + regulation_groups=[ + group + for group_name in feature_data.get("regulation_groups", []) + if (group := cls.find_matching_group_config(group_name, regulation_group_libraries)) + ], plan_id=None, id_=None, ) diff --git a/arho_feature_template/core/plan_manager.py b/arho_feature_template/core/plan_manager.py index 3947e19..dc1cf68 100644 --- a/arho_feature_template/core/plan_manager.py +++ b/arho_feature_template/core/plan_manager.py @@ -24,7 +24,7 @@ from arho_feature_template.gui.dialogs.serialize_plan import SerializePlan from arho_feature_template.gui.docks.new_feature_dock import NewFeatureDock from arho_feature_template.gui.tools.inspect_plan_features_tool import InspectPlanFeatures -from arho_feature_template.project.layers.code_layers import PlanRegulationGroupTypeLayer +from arho_feature_template.project.layers.code_layers import PlanRegulationGroupTypeLayer, code_layers from arho_feature_template.project.layers.plan_layers import ( LandUseAreaLayer, LandUsePointLayer, @@ -79,14 +79,11 @@ def __init__(self): self.json_plan_path = None self.json_plan_outline_path = None - # Initialize libraries - self.feature_template_libraries = [ - FeatureTemplateLibrary.from_config_file(file) for file in feature_template_library_config_files() - ] - self.regulation_group_libraries = None + self.feature_template_libraries = [] + self.regulation_group_libraries = [] # Initialize new feature dock - self.new_feature_dock = NewFeatureDock(self.feature_template_libraries) + self.new_feature_dock = NewFeatureDock() self.new_feature_dock.tool_activated.connect(self.add_new_plan_feature) self.new_feature_dock.hide() @@ -107,13 +104,36 @@ def __init__(self): self.lambda_service = LambdaService() self.lambda_service.jsons_received.connect(self.save_plan_jsons) - def get_regulation_group_libraries(self): - # Lazy initialization of regulation_group_libraries to avoid reading regulation code layer too early - if self.regulation_group_libraries is None: - self.regulation_group_libraries = [ - RegulationGroupLibrary.from_config_file(file) for file in regulation_group_library_config_files() - ] - return self.regulation_group_libraries + def initialize_from_project(self): + # # If project is not open, don't try to initialize + # # NOTE: QgsProject.instance(), QgsProject().fileInfo() and QgsProject().fileIName() + # # don't seem to work as indicators whether a project is open? + # if not QgsProject.instance() or not QgsProject().fileInfo(): + # return + self.initialize_libraries() + + def check_required_layers(self): + missing_layers = [] + for layer in code_layers + plan_layers: + if not layer.exists(): + missing_layers.append(layer.name) # noqa: PERF401 + if len(missing_layers) > 0: # noqa: SIM103 + # iface.messageBar().pushWarning("", f"Project is missing required layers: {', '.join(missing_layers)}") + return False + return True + + def initialize_libraries(self): + if not self.check_required_layers(): + return + + self.regulation_group_libraries = [ + RegulationGroupLibrary.from_config_file(file) for file in regulation_group_library_config_files() + ] + self.feature_template_libraries = [ + FeatureTemplateLibrary.from_config_file(file, self.regulation_group_libraries) + for file in feature_template_library_config_files() + ] + self.new_feature_dock.initialize_feature_template_libraries(self.feature_template_libraries) def toggle_identify_plan_features(self, activate: bool): # noqa: FBT001 if activate: @@ -179,7 +199,7 @@ def edit_plan(self): return plan_model = PlanLayer.model_from_feature(feature) - attribute_form = PlanAttributeForm(plan_model, self.get_regulation_group_libraries()) + attribute_form = PlanAttributeForm(plan_model, self.regulation_group_libraries) if attribute_form.exec_(): feature = save_plan(attribute_form.model) @@ -210,7 +230,7 @@ def _plan_geom_digitized(self, feature: QgsFeature): return plan_model = Plan(geom=feature.geometry()) - attribute_form = PlanAttributeForm(plan_model, self.get_regulation_group_libraries()) + attribute_form = PlanAttributeForm(plan_model, self.regulation_group_libraries) if attribute_form.exec_(): feature = save_plan(attribute_form.model) plan_to_be_activated = feature["id"] @@ -237,7 +257,7 @@ def _plan_feature_geom_digitized(self, feature: QgsFeature): plan_feature.geom = feature.geometry() attribute_form = PlanFeatureForm( - plan_feature, title, [*self.get_regulation_group_libraries(), regulation_group_library_from_active_plan()] + plan_feature, title, [*self.regulation_group_libraries, regulation_group_library_from_active_plan()] ) if attribute_form.exec_(): save_plan_feature(attribute_form.model) @@ -249,7 +269,7 @@ def edit_plan_feature(self, feature: QgsFeature, layer_name: str): # Geom editing handled with basic QGIS vertex editing? title = plan_feature.name if plan_feature.name else layer_name attribute_form = PlanFeatureForm( - plan_feature, title, [*self.get_regulation_group_libraries(), regulation_group_library_from_active_plan()] + plan_feature, title, [*self.regulation_group_libraries, regulation_group_library_from_active_plan()] ) if attribute_form.exec_(): save_plan_feature(attribute_form.model) diff --git a/arho_feature_template/gui/docks/new_feature_dock.py b/arho_feature_template/gui/docks/new_feature_dock.py index 9d8a9a2..933cfd7 100644 --- a/arho_feature_template/gui/docks/new_feature_dock.py +++ b/arho_feature_template/gui/docks/new_feature_dock.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from importlib import resources from typing import TYPE_CHECKING @@ -21,7 +23,7 @@ class NewFeatureDock(QgsDockWidget, DockClass): # type: ignore tool_activated = pyqtSignal() - def __init__(self, feature_template_libraries: list): + def __init__(self): super().__init__() self.setupUi(self) @@ -41,11 +43,8 @@ def __init__(self, feature_template_libraries: list): self.active_feature_type: str | None = None self.active_feature_layer: str | None = None self.active_template: PlanFeature | None = None - self.feature_template_libraries: list[FeatureTemplateLibrary] = feature_template_libraries - self.library_selection.addItems([library.name for library in self.feature_template_libraries]) + self.feature_template_libraries: list[FeatureTemplateLibrary] | None = None self.library_selection.currentIndexChanged.connect(self.set_active_feature_template_library) - if len(self.feature_template_libraries) > 0: - self.set_active_feature_template_library(0) self.template_list.setSelectionMode(self.template_list.SingleSelection) self.search_box.valueChanged.connect(self.filter_plan_feature_templates) @@ -54,6 +53,12 @@ def __init__(self, feature_template_libraries: list): self.template_list.clicked.connect(self.on_template_item_clicked) self.new_feature_grid.active_feature_type_changed.connect(self.on_active_feature_type_changed) + def initialize_feature_template_libraries(self, feature_template_libraries: list[FeatureTemplateLibrary]): + self.feature_template_libraries = feature_template_libraries + self.library_selection.clear() + self.library_selection.addItems([library.name for library in self.feature_template_libraries]) + self.set_active_feature_template_library(0) + def on_active_feature_type_changed(self, feature_name: str, layer_name: str): self.active_feature_type = feature_name if feature_name else None self.active_feature_layer = layer_name if layer_name else None @@ -100,9 +105,10 @@ def deactivate_and_clear_selections(self): self.template_list.clearSelection() def set_active_feature_template_library(self, index: int) -> None: - self.template_list.clear() - library = self.feature_template_libraries[index] - for feature_template in library.feature_templates: - item = QListWidgetItem(feature_template.name) - item.setData(Qt.UserRole, feature_template) - self.template_list.addItem(item) + if self.feature_template_libraries and len(self.feature_template_libraries) > 0: + self.template_list.clear() + library = self.feature_template_libraries[index] + for feature_template in library.feature_templates: + item = QListWidgetItem(feature_template.name) + item.setData(Qt.UserRole, feature_template) + self.template_list.addItem(item) diff --git a/arho_feature_template/plugin.py b/arho_feature_template/plugin.py index be5790a..ad5e98e 100644 --- a/arho_feature_template/plugin.py +++ b/arho_feature_template/plugin.py @@ -127,27 +127,30 @@ def add_action( def initGui(self) -> None: # noqa N802 # plan_icon_path = os.path.join(PLUGIN_PATH, "resources/icons/city.png") # A placeholder icon # load_icon_path = os.path.join(PLUGIN_PATH, "resources/icons/folder.png") # A placeholder icon + # icons to consider: + # icon=QgsApplication.getThemeIcon("mActionStreamingDigitize.svg"), + # icon=QgsApplication.getThemeIcon("mIconGeometryCollectionLayer.svg"), + # icon=QgsApplication.getThemeIcon("mActionSharingExport.svg"), + self.plan_manager = PlanManager() iface.addDockWidget(Qt.RightDockWidgetArea, self.plan_manager.new_feature_dock) self.plan_manager.new_feature_dock.setUserVisible(False) - self.plan_manager.new_feature_dock.visibilityChanged.connect(self.dock_visibility_changed) - iface.mapCanvas().mapToolSet.connect(self.plan_manager.plan_digitize_map_tool.deactivate) - self.validation_dock = ValidationDock() iface.addDockWidget(Qt.RightDockWidgetArea, self.validation_dock) self.validation_dock.setUserVisible(False) - self.validation_dock.visibilityChanged.connect(self.validation_dock_visibility_changed) - # icons to consider: - # icon=QgsApplication.getThemeIcon("mActionStreamingDigitize.svg"), - # icon=QgsApplication.getThemeIcon("mIconGeometryCollectionLayer.svg"), - # icon=QgsApplication.getThemeIcon("mActionSharingExport.svg"), + iface.mapCanvas().mapToolSet.connect(self.plan_manager.plan_digitize_map_tool.deactivate) + + # Try initializing the plugin immediately in case the project is already open + self.plan_manager.initialize_from_project() + + # (Re)initialize whenever a project is opened + iface.projectRead.connect(self.plan_manager.initialize_from_project) - # Add main plugin action to the toolbar self.new_land_use_plan_action = self.add_action( text="Luo kaava", icon=QgsApplication.getThemeIcon("mActionNewMap.svg"), @@ -251,6 +254,7 @@ def unload(self) -> None: # Handle signals self.plan_manager.new_feature_dock.visibilityChanged.disconnect() iface.mapCanvas().mapToolSet.disconnect() + iface.projectRead.disconnect() # Handle actions for action in self.actions: diff --git a/arho_feature_template/project/layers/__init__.py b/arho_feature_template/project/layers/__init__.py index 7c9ba79..0f75997 100644 --- a/arho_feature_template/project/layers/__init__.py +++ b/arho_feature_template/project/layers/__init__.py @@ -3,17 +3,29 @@ from abc import ABC from typing import TYPE_CHECKING, Any, ClassVar, Generator -from qgis.core import QgsFeatureRequest +from qgis.core import QgsFeatureRequest, QgsProject, QgsVectorLayer from arho_feature_template.utils.project_utils import get_vector_layer_from_project if TYPE_CHECKING: - from qgis.core import QgsFeature, QgsFeatureIterator, QgsVectorLayer + from qgis.core import QgsFeature, QgsFeatureIterator class AbstractLayer(ABC): name: ClassVar[str] + @classmethod + def exists(cls) -> bool: + project = QgsProject.instance() + if not project: + return False + + layers = project.mapLayersByName(cls.name) + if not layers: + return False + + return bool([layer for layer in layers if isinstance(layer, QgsVectorLayer)]) + @classmethod def get_from_project(cls) -> QgsVectorLayer: return get_vector_layer_from_project(cls.name) diff --git a/arho_feature_template/resources/libraries/feature_templates/katja_asemakaava.yaml b/arho_feature_template/resources/libraries/feature_templates/katja_asemakaava.yaml index bc4c5f7..f2d8449 100644 --- a/arho_feature_template/resources/libraries/feature_templates/katja_asemakaava.yaml +++ b/arho_feature_template/resources/libraries/feature_templates/katja_asemakaava.yaml @@ -16,6 +16,8 @@ feature_templates: - name: Asuin-, liike- ja toimistorakennusten alue layer_name: Aluevaraus + regulation_groups: + - Asuin-, liike- ja toimistorakennusten alue - name: Sitovan tonttijaon mukainen tontti layer_name: Osa-alue