|
2 | 2 |
|
3 | 3 | import json
|
4 | 4 | import logging
|
5 |
| -from string import Template |
6 |
| -from textwrap import dedent |
7 |
| - |
8 |
| -from qgis.core import QgsExpressionContextUtils, QgsProject, QgsVectorLayer |
| 5 | +from typing import TYPE_CHECKING |
| 6 | + |
| 7 | +from qgis.core import ( |
| 8 | + QgsExpressionContextUtils, |
| 9 | + QgsProject, |
| 10 | + QgsVectorLayer, |
| 11 | +) |
| 12 | +from qgis.gui import QgsMapToolDigitizeFeature |
9 | 13 | from qgis.PyQt.QtWidgets import QDialog, QMessageBox
|
10 | 14 | from qgis.utils import iface
|
11 | 15 |
|
12 | 16 | from arho_feature_template.core.lambda_service import LambdaService
|
| 17 | +from arho_feature_template.exceptions import UnsavedChangesError |
13 | 18 | from arho_feature_template.gui.load_plan_dialog import LoadPlanDialog
|
| 19 | +from arho_feature_template.gui.plan_attribure_form import PlanAttributeForm |
14 | 20 | from arho_feature_template.gui.serialize_plan import SerializePlan
|
| 21 | +from arho_feature_template.project.layers.plan_layers import PlanLayer, RegulationGroupLayer, plan_layers |
15 | 22 | from arho_feature_template.utils.db_utils import get_existing_database_connection_names
|
16 |
| -from arho_feature_template.utils.misc_utils import get_active_plan_id, get_layer_by_name, handle_unsaved_changes |
| 23 | +from arho_feature_template.utils.misc_utils import ( |
| 24 | + check_layer_changes, |
| 25 | + get_active_plan_id, |
| 26 | + handle_unsaved_changes, |
| 27 | +) |
| 28 | + |
| 29 | +if TYPE_CHECKING: |
| 30 | + from qgis.core import QgsFeature |
17 | 31 |
|
| 32 | + from arho_feature_template.core.models import Plan, RegulationGroup |
18 | 33 | logger = logging.getLogger(__name__)
|
19 | 34 |
|
20 |
| -LAYER_NAME_PLAN = "Kaava" |
21 |
| -LAYER_NAME_LAND_USE_POINT = "Maankäytön kohteet" |
22 |
| -LAYER_NAME_OTHER_POINT = "Muut pisteet" |
23 |
| -LAYER_NAME_LINE = "Viivat" |
24 |
| -LAYER_NAME_OTHER_AREA = "Osa-alue" |
25 |
| -LAYER_NAME_LAND_USE_AREA = "Aluevaraus" |
26 |
| -LAYER_NAME_PLAN_REGULATION_GROUP = "Kaavamääräysryhmät" |
27 |
| -LAYER_NAME_PLAN_REGULATION = "Kaavamääräys" |
28 |
| -LAYER_NAME_PLAN_PROPOSITION = "Kaavasuositus" |
29 |
| -LAYER_NAME_DOCUMENT = "Asiakirjat" |
30 |
| -LAYER_NAME_SOURCE_DATA = "Lähtötietoaineistot" |
31 |
| -LAYER_NAME_REGULATION_GROUP_ASSOCIATION = "Kaavamääräysryhmien assosiaatiot" |
32 |
| - |
33 |
| -PLAN_FILTER_TEMPLATES = { |
34 |
| - LAYER_NAME_PLAN: Template("id = '$plan_id'"), |
35 |
| - LAYER_NAME_LAND_USE_POINT: Template("plan_id = '$plan_id'"), |
36 |
| - LAYER_NAME_OTHER_POINT: Template("plan_id = '$plan_id'"), |
37 |
| - LAYER_NAME_LINE: Template("plan_id = '$plan_id'"), |
38 |
| - LAYER_NAME_LAND_USE_AREA: Template("plan_id = '$plan_id'"), |
39 |
| - LAYER_NAME_OTHER_AREA: Template("plan_id = '$plan_id'"), |
40 |
| - LAYER_NAME_PLAN_REGULATION_GROUP: Template("plan_id = '$plan_id'"), |
41 |
| - LAYER_NAME_REGULATION_GROUP_ASSOCIATION: Template( |
42 |
| - dedent( |
43 |
| - """\ |
44 |
| - EXISTS ( |
45 |
| - SELECT 1 |
46 |
| - FROM hame.plan_regulation_group prg |
47 |
| - WHERE |
48 |
| - hame.regulation_group_association.plan_regulation_group_id = prg.id |
49 |
| - AND prg.plan_id = '$plan_id' |
50 |
| - )""" |
51 |
| - ) |
52 |
| - ), |
53 |
| - LAYER_NAME_PLAN_REGULATION: Template( |
54 |
| - dedent( |
55 |
| - """\ |
56 |
| - EXISTS ( |
57 |
| - SELECT 1 |
58 |
| - FROM hame.plan_regulation_group prg |
59 |
| - WHERE |
60 |
| - hame.plan_regulation.plan_regulation_group_id = prg.id |
61 |
| - AND prg.plan_id = '$plan_id' |
62 |
| - )""" |
63 |
| - ) |
64 |
| - ), |
65 |
| - LAYER_NAME_PLAN_PROPOSITION: Template( |
66 |
| - dedent( |
67 |
| - """\ |
68 |
| - EXISTS ( |
69 |
| - SELECT 1 |
70 |
| - FROM hame.plan_regulation_group rg |
71 |
| - WHERE |
72 |
| - hame.plan_proposition.plan_regulation_group_id = rg.id |
73 |
| - AND rg.plan_id = '$plan_id' |
74 |
| - )""" |
75 |
| - ) |
76 |
| - ), |
77 |
| - LAYER_NAME_DOCUMENT: Template("plan_id = '$plan_id'"), |
78 |
| - LAYER_NAME_SOURCE_DATA: Template("plan_id = '$plan_id'"), |
79 |
| -} |
| 35 | + |
| 36 | +class PlanDigitizeMapTool(QgsMapToolDigitizeFeature): ... |
80 | 37 |
|
81 | 38 |
|
82 | 39 | class PlanManager:
|
83 | 40 | def __init__(self):
|
84 | 41 | self.json_plan_path = None
|
85 | 42 | self.json_plan_outline_path = None
|
86 | 43 |
|
| 44 | + self.digitize_map_tool = PlanDigitizeMapTool(iface.mapCanvas(), iface.cadDockWidget()) |
| 45 | + self.digitize_map_tool.digitizingCompleted.connect(self._plan_geom_digitized) |
| 46 | + |
87 | 47 | def add_new_plan(self):
|
88 | 48 | """Initiate the process to add a new plan to the Kaava layer."""
|
| 49 | + |
| 50 | + self.previous_map_tool = iface.mapCanvas().mapTool() |
| 51 | + self.previous_active_plan_id = get_active_plan_id() |
| 52 | + |
89 | 53 | if not handle_unsaved_changes():
|
90 | 54 | return
|
91 | 55 |
|
92 |
| - plan_layer = get_layer_by_name(LAYER_NAME_PLAN) |
93 |
| - |
| 56 | + plan_layer = PlanLayer.get_from_project() |
94 | 57 | if not plan_layer:
|
95 | 58 | return
|
96 |
| - self.set_active_plan(None) |
| 59 | + self.previously_editable = plan_layer.isEditable() |
97 | 60 |
|
98 |
| - if not plan_layer.isEditable(): |
99 |
| - plan_layer.startEditing() |
100 |
| - |
101 |
| - iface.setActiveLayer(plan_layer) |
102 |
| - iface.actionAddFeature().trigger() |
| 61 | + self.set_active_plan(None) |
103 | 62 |
|
104 |
| - # Connect the featureAdded signal to a callback method |
105 |
| - plan_layer.featureAdded.connect(self._feature_added) |
| 63 | + plan_layer.startEditing() |
| 64 | + self.digitize_map_tool.setLayer(plan_layer) |
| 65 | + iface.mapCanvas().setMapTool(self.digitize_map_tool) |
106 | 66 |
|
107 |
| - def _feature_added(self): |
| 67 | + def _plan_geom_digitized(self, feature: QgsFeature): |
108 | 68 | """Callback for when a new feature is added to the Kaava layer."""
|
109 |
| - plan_layer = get_layer_by_name(LAYER_NAME_PLAN) |
| 69 | + plan_layer = PlanLayer.get_from_project() |
110 | 70 | if not plan_layer:
|
111 | 71 | return
|
112 | 72 |
|
113 |
| - # Disconnect the signal to avoid repeated triggers |
114 |
| - plan_layer.featureAdded.disconnect(self._feature_added) |
115 |
| - |
116 |
| - feature_ids_before_commit = plan_layer.allFeatureIds() |
117 |
| - |
118 |
| - if plan_layer.isEditable(): |
119 |
| - if not plan_layer.commitChanges(): |
120 |
| - iface.messageBar().pushMessage("Error", "Failed to commit changes to the layer.", level=3) |
121 |
| - return |
| 73 | + attribute_form = PlanAttributeForm() |
| 74 | + if attribute_form.exec_(): |
| 75 | + plan_attributes = attribute_form.get_plan_attributes() |
| 76 | + plan_attributes.geom = feature.geometry() |
| 77 | + feature = save_plan(plan_attributes) |
| 78 | + plan_to_be_activated = feature["id"] |
122 | 79 | else:
|
123 |
| - iface.messageBar().pushMessage("Error", "Layer is not editable.", level=3) |
124 |
| - return |
| 80 | + plan_to_be_activated = self.previous_active_plan_id |
125 | 81 |
|
126 |
| - feature_ids_after_commit = plan_layer.allFeatureIds() |
127 |
| - new_feature_id = next( |
128 |
| - (fid for fid in feature_ids_after_commit if fid not in feature_ids_before_commit), |
129 |
| - None, |
130 |
| - ) |
| 82 | + self.set_active_plan(plan_to_be_activated) |
131 | 83 |
|
132 |
| - if new_feature_id is not None: |
133 |
| - new_feature = plan_layer.getFeature(new_feature_id) |
134 |
| - if new_feature.isValid(): |
135 |
| - feature_id_value = new_feature["id"] |
136 |
| - self.set_active_plan(feature_id_value) |
137 |
| - else: |
138 |
| - iface.messageBar().pushMessage("Error", "Invalid feature retrieved.", level=3) |
139 |
| - else: |
140 |
| - iface.messageBar().pushMessage("Error", "No new feature was added.", level=3) |
| 84 | + if self.previously_editable: |
| 85 | + plan_layer.startEditing() |
| 86 | + |
| 87 | + iface.mapCanvas().setMapTool(self.previous_map_tool) |
141 | 88 |
|
142 | 89 | def set_active_plan(self, plan_id: str | None):
|
143 |
| - """Update the project layers based on the selected land use plan.""" |
| 90 | + """Update the project layers based on the selected land use plan. |
| 91 | +
|
| 92 | + Layers to be filtered cannot be in edit mode. |
| 93 | + This method disables edit mode temporarily if needed. |
| 94 | + Therefore if there are unsaved changes, this method will raise an exception. |
| 95 | + """ |
| 96 | + |
| 97 | + if check_layer_changes(): |
| 98 | + raise UnsavedChangesError |
| 99 | + |
| 100 | + plan_layer = PlanLayer.get_from_project() |
| 101 | + previously_in_edit_mode = plan_layer.isEditable() |
| 102 | + if previously_in_edit_mode: |
| 103 | + plan_layer.rollBack() |
| 104 | + |
144 | 105 | QgsExpressionContextUtils.setProjectVariable(QgsProject.instance(), "active_plan_id", plan_id)
|
| 106 | + for layer in plan_layers: |
| 107 | + layer.apply_filter(plan_id) |
145 | 108 |
|
146 |
| - for layer_name, filter_template in PLAN_FILTER_TEMPLATES.items(): |
147 |
| - """Set a filter for the given vector layer.""" |
148 |
| - filter_expression = filter_template.substitute(plan_id=plan_id) if plan_id else None |
149 |
| - layer = get_layer_by_name(layer_name) |
150 |
| - if not layer: |
151 |
| - logger.warning("Layer %s not found", layer_name) |
152 |
| - continue |
153 |
| - result = layer.setSubsetString(filter_expression) |
154 |
| - if result is False: |
155 |
| - iface.messageBar().pushMessage( |
156 |
| - "Error", |
157 |
| - f"Failed to filter layer {layer_name} with query {filter_expression}", |
158 |
| - level=3, |
159 |
| - ) |
| 109 | + if previously_in_edit_mode: |
| 110 | + plan_layer.startEditing() |
160 | 111 |
|
161 | 112 | def load_land_use_plan(self):
|
162 | 113 | """Load an existing land use plan using a dialog selection."""
|
@@ -221,4 +172,47 @@ def save_plan_jsons(self, plan_json, outline_json):
|
221 | 172 | with open(self.json_plan_outline_path, "w", encoding="utf-8") as outline_file:
|
222 | 173 | json.dump(outline_json, outline_file, ensure_ascii=False, indent=2)
|
223 | 174 |
|
224 |
| - QMessageBox.information(None, "Tallennus onnistui", "Kaava ja sen ulkoraja tallennettu onnistuneesti.") |
| 175 | + QMessageBox.information( |
| 176 | + None, |
| 177 | + "Tallennus onnistui", |
| 178 | + "Kaava ja sen ulkoraja tallennettu onnistuneesti.", |
| 179 | + ) |
| 180 | + |
| 181 | + |
| 182 | +def save_plan(plan_data: Plan) -> QgsFeature: |
| 183 | + plan_layer = PlanLayer.get_from_project() |
| 184 | + in_edit_mode = plan_layer.isEditable() |
| 185 | + if not in_edit_mode: |
| 186 | + plan_layer.startEditing() |
| 187 | + |
| 188 | + edit_message = "Kaavan lisäys" if plan_data.id_ is None else "Kaavan muokkaus" |
| 189 | + plan_layer.beginEditCommand(edit_message) |
| 190 | + |
| 191 | + plan_data.organisation_id = "99e20d66-9730-4110-815f-5947d3f8abd3" |
| 192 | + plan_feature = PlanLayer.feature_from_model(plan_data) |
| 193 | + |
| 194 | + if plan_data.id_ is None: |
| 195 | + plan_layer.addFeature(plan_feature) |
| 196 | + else: |
| 197 | + plan_layer.updateFeature(plan_feature) |
| 198 | + |
| 199 | + plan_layer.endEditCommand() |
| 200 | + plan_layer.commitChanges(stopEditing=False) |
| 201 | + |
| 202 | + if plan_data.general_regulations: |
| 203 | + for regulation_group in plan_data.general_regulations: |
| 204 | + plan_id = plan_feature["id"] |
| 205 | + regulation_group_feature = save_regulation_group(regulation_group, plan_id) |
| 206 | + save_regulation_grop_assosiation(plan_id, regulation_group_feature["id"]) |
| 207 | + |
| 208 | + return plan_feature |
| 209 | + |
| 210 | + |
| 211 | +def save_regulation_group(regulation_group: RegulationGroup, plan_id: str) -> QgsFeature: |
| 212 | + feature = RegulationGroupLayer.feature_from_model(regulation_group) |
| 213 | + feature["plan_id"] = plan_id |
| 214 | + return feature |
| 215 | + |
| 216 | + |
| 217 | +def save_regulation_grop_assosiation(plan_id: str, regulation_group_id: str): |
| 218 | + pass |
0 commit comments