diff --git a/README.md b/README.md index 6224972..75c0077 100644 --- a/README.md +++ b/README.md @@ -61,3 +61,7 @@ the one with the path `.venv\Scripts\python.exe`. This plugin is distributed under the terms of the [GNU General Public License, version 2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) license. See [LICENSE](LICENSE) for more information. + +### Attributations +<a href="https://www.flaticon.com/free-icons/open" title="open icons">Open icons created by Smashicons - Flaticon</a> +<a href="https://www.flaticon.com/free-icons/land-use" title="land use icons">Land use icons created by Fusion5085 - Flaticon</a> diff --git a/arho_feature_template/core/new_plan.py b/arho_feature_template/core/new_plan.py new file mode 100644 index 0000000..97dd5ee --- /dev/null +++ b/arho_feature_template/core/new_plan.py @@ -0,0 +1,63 @@ +from qgis.core import QgsProject, QgsVectorLayer +from qgis.utils import iface + +from arho_feature_template.core.update_plan import LandUsePlan, update_selected_plan + + +class NewPlan: + def add_new_plan(self): + # Filtered layers are not editable, so clear filters first. + self.clear_all_filters() + + layers = QgsProject.instance().mapLayersByName("Kaava") + if not layers: + iface.messageBar().pushMessage("Error", "Layer 'Kaava' not found", level=3) + return + + kaava_layer = layers[0] + + if not kaava_layer.isEditable(): + kaava_layer.startEditing() + + iface.setActiveLayer(kaava_layer) + + iface.actionAddFeature().trigger() + + # Connect the featureAdded signal + kaava_layer.featureAdded.connect(self.feature_added) + + def feature_added(self): + kaava_layer = iface.activeLayer() + kaava_layer.featureAdded.disconnect() + feature_ids_before_commit = kaava_layer.allFeatureIds() + if kaava_layer.isEditable(): + if not kaava_layer.commitChanges(): + iface.messageBar().pushMessage("Error", "Failed to commit changes to the layer.", level=3) + return + else: + iface.messageBar().pushMessage("Error", "Layer is not editable.", level=3) + return + + feature_ids_after_commit = kaava_layer.allFeatureIds() + + # Find the new plan.id by comparing fids before and after commit. + new_feature_id = next((fid for fid in feature_ids_after_commit if fid not in feature_ids_before_commit), None) + + if new_feature_id is not None: + new_feature = kaava_layer.getFeature(new_feature_id) + + if new_feature.isValid(): + feature_id_value = new_feature["id"] + update_selected_plan(LandUsePlan(feature_id_value)) + else: + iface.messageBar().pushMessage("Error", "Invalid feature retrieved.", level=3) + else: + iface.messageBar().pushMessage("Error", "No new feature was added.", level=3) + + def clear_all_filters(self): + """Clear filters for all vector layers in the project.""" + layers = QgsProject.instance().mapLayers().values() + + for layer in layers: + if isinstance(layer, QgsVectorLayer): + layer.setSubsetString("") diff --git a/arho_feature_template/core/update_plan.py b/arho_feature_template/core/update_plan.py new file mode 100644 index 0000000..d083090 --- /dev/null +++ b/arho_feature_template/core/update_plan.py @@ -0,0 +1,62 @@ +from dataclasses import dataclass + +from qgis.core import QgsMapLayer, QgsProject, QgsVectorLayer +from qgis.utils import iface + + +# To be extended and moved +@dataclass +class LandUsePlan: + id: str + + +# To be replaced later +LAYER_PLAN_ID_MAP = { + "Kaava": "id", + "Maankäytön kohteet": "plan_id", + "Muut pisteet": "plan_id", + "Viivat": "plan_id", + "Aluevaraus": "plan_id", + "Osa-alue": "plan_id", +} + + +def update_selected_plan(new_plan: LandUsePlan): + """Update the project layers based on the selected land use plan.""" + plan_id = new_plan.id + + for layer_name, field_name in LAYER_PLAN_ID_MAP.items(): + # Set the filter on each layer using the plan_id + set_filter_for_vector_layer(layer_name, field_name, plan_id) + + +def set_filter_for_vector_layer(layer_name: str, field_name: str, field_value: str): + """Set a filter for the given vector layer.""" + layers = QgsProject.instance().mapLayersByName(layer_name) + + if not _check_layer_count(layers): + return + + layer = layers[0] + + expression = f"\"{field_name}\" = '{field_value}'" + + # Apply the filter to the layer + if not layer.setSubsetString(expression): + iface.messageBar().pushMessage("Error", f"Failed to filter layer {layer_name} with query {expression}", level=3) + + +def _check_layer_count(layers: list) -> bool: + """Check if any layers are returned.""" + if not layers: + iface.messageBar().pushMessage("Error", "ERROR: No layers found with the specified name.", level=3) + return False + return True + + +def _check_vector_layer(layer: QgsMapLayer) -> bool: + """Check if the given layer is a vector layer.""" + if not isinstance(layer, QgsVectorLayer): + iface.messageBar().pushMessage("Error", f"Layer {layer.name()} is not a vector layer: {type(layer)}", level=3) + return False + return True diff --git a/arho_feature_template/plugin.py b/arho_feature_template/plugin.py index f06d83c..e60d754 100644 --- a/arho_feature_template/plugin.py +++ b/arho_feature_template/plugin.py @@ -1,5 +1,6 @@ from __future__ import annotations +import os from typing import TYPE_CHECKING, Callable, cast from qgis.PyQt.QtCore import QCoreApplication, Qt, QTranslator @@ -8,9 +9,11 @@ from qgis.utils import iface from arho_feature_template.core.feature_template_library import FeatureTemplater, TemplateGeometryDigitizeMapTool +from arho_feature_template.core.new_plan import NewPlan from arho_feature_template.qgis_plugin_tools.tools.custom_logging import setup_logger, teardown_logger from arho_feature_template.qgis_plugin_tools.tools.i18n import setup_translation from arho_feature_template.qgis_plugin_tools.tools.resources import plugin_name +from arho_feature_template.utils.misc_utils import PLUGIN_PATH if TYPE_CHECKING: from qgis.gui import QgisInterface, QgsMapTool @@ -25,6 +28,7 @@ class Plugin: def __init__(self) -> None: setup_logger(Plugin.name) + self.digitizing_tool = None # initialize locale locale, file_path = setup_translation() @@ -120,6 +124,10 @@ def add_action( def initGui(self) -> None: # noqa N802 self.templater = FeatureTemplater() + self.new_plan = NewPlan() + + 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 iface.addDockWidget(Qt.RightDockWidgetArea, self.templater.template_dock) self.templater.template_dock.visibilityChanged.connect(self.dock_visibility_changed) @@ -136,10 +144,33 @@ def initGui(self) -> None: # noqa N802 add_to_toolbar=True, ) + self.new_land_use_plan_action = self.add_action( + plan_icon_path, + "Create New Plan", + self.digitize_new_plan, + add_to_menu=True, + add_to_toolbar=True, + status_tip="Create a new plan", + ) + + self.load_land_use_plan_action = self.add_action( + load_icon_path, + text="Load existing land use plan", + triggered_callback=self.load_existing_land_use_plan, + parent=iface.mainWindow(), + add_to_toolbar=True, + ) + def on_map_tool_changed(self, new_tool: QgsMapTool, old_tool: QgsMapTool) -> None: # noqa: ARG002 if not isinstance(new_tool, TemplateGeometryDigitizeMapTool): self.template_dock_action.setChecked(False) + def digitize_new_plan(self): + self.new_plan.add_new_plan() + + def load_existing_land_use_plan(self) -> None: + """Open existing land use plan.""" + def unload(self) -> None: """Removes the plugin menu item and icon from QGIS GUI.""" for action in self.actions: diff --git a/arho_feature_template/resources/icons/city.png b/arho_feature_template/resources/icons/city.png new file mode 100644 index 0000000..ed82aaf Binary files /dev/null and b/arho_feature_template/resources/icons/city.png differ diff --git a/arho_feature_template/resources/icons/folder.png b/arho_feature_template/resources/icons/folder.png new file mode 100644 index 0000000..9eb2915 Binary files /dev/null and b/arho_feature_template/resources/icons/folder.png differ diff --git a/arho_feature_template/resources/template_libraries/asemakaava-sample.yaml b/arho_feature_template/resources/template_libraries/asemakaava-sample.yaml index 00cdae8..b7901f8 100644 --- a/arho_feature_template/resources/template_libraries/asemakaava-sample.yaml +++ b/arho_feature_template/resources/template_libraries/asemakaava-sample.yaml @@ -7,6 +7,7 @@ templates: description: Kaavakohde ilman kikkareita feature: layer: land_use_area + # layer: Aluevaraus attributes: - attribute: name - attribute: type_of_underground_id @@ -15,6 +16,7 @@ templates: description: Aluella kuvataan ... feature: layer: land_use_area + # layer: Aluevaraus attributes: - attribute: name - attribute: type_of_underground_id diff --git a/arho_feature_template/utils/__init__.py b/arho_feature_template/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/arho_feature_template/utils/misc_utils.py b/arho_feature_template/utils/misc_utils.py new file mode 100644 index 0000000..42f38d6 --- /dev/null +++ b/arho_feature_template/utils/misc_utils.py @@ -0,0 +1,3 @@ +import os + +PLUGIN_PATH = os.path.dirname(os.path.dirname(__file__))