Skip to content

Commit 31b68d5

Browse files
authored
Merge pull request #90 from GispoCoding/new-plan-form
Kaavan lomekkeen yksinkertaistaminen
2 parents ca1af82 + 1fe2047 commit 31b68d5

15 files changed

+933
-132
lines changed

arho_feature_template/core/feature_template_library.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from arho_feature_template.gui.template_attribute_form import TemplateAttributeForm
2121
from arho_feature_template.gui.template_dock import TemplateLibraryDock
2222
from arho_feature_template.resources.template_libraries import library_config_files
23-
from arho_feature_template.utils.project_utils import get_layer_from_project
23+
from arho_feature_template.utils.project_utils import get_vector_layer_from_project
2424

2525
if TYPE_CHECKING:
2626
from qgis.core import QgsFeature, QgsVectorLayer
@@ -38,7 +38,7 @@ def is_valid(self) -> bool:
3838
3939
Checks if the layer and attributes defined in the template acutally exists"""
4040
try:
41-
get_layer_from_project(self.config.feature.layer) # TODO: check child features recursively
41+
get_vector_layer_from_project(self.config.feature.layer) # TODO: check child features recursively
4242
except (LayerNotFoundError, LayerNotVectorTypeError):
4343
return False
4444
else:
@@ -88,7 +88,7 @@ def on_template_item_clicked(self, index):
8888
return
8989

9090
try:
91-
layer = get_layer_from_project(item.config.feature.layer)
91+
layer = get_vector_layer_from_project(item.config.feature.layer)
9292
except (LayerNotFoundError, LayerNotVectorTypeError):
9393
logger.exception("Failed to activate template")
9494
return
@@ -160,7 +160,7 @@ def ask_for_feature_attributes(self, feature: QgsFeature) -> None:
160160
attribute_form = TemplateAttributeForm(self.active_template.config)
161161

162162
if attribute_form.exec_():
163-
layer = get_layer_from_project(self.active_template.config.feature.layer)
163+
layer = get_vector_layer_from_project(self.active_template.config.feature.layer)
164164
# Save the feature
165165
attribute_form.set_feature_attributes(feature)
166166

arho_feature_template/core/models.py

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass, field
4+
from typing import TYPE_CHECKING
5+
6+
if TYPE_CHECKING:
7+
from qgis.core import QgsGeometry
8+
9+
10+
@dataclass
11+
class Regulation:
12+
type_code: str
13+
value_string: str | None
14+
value_int: int | None
15+
value_float: float | None
16+
unit: str | None
17+
id_: int | None = None
18+
19+
20+
@dataclass
21+
class RegulationGroup:
22+
name: str
23+
short_name: str | None
24+
type_code: str
25+
regulations: list[Regulation]
26+
id_: int | None = None
27+
28+
29+
@dataclass
30+
class Plan:
31+
name: str
32+
description: str | None
33+
plan_type_id: str
34+
lifecycle_status_id: str
35+
record_number: str | None
36+
matter_management_identifier: str | None
37+
permanent_plan_identifier: str | None
38+
producers_plan_identifier: str | None
39+
organisation_id: str | None = None
40+
general_regulations: list[RegulationGroup] = field(default_factory=list)
41+
geom: QgsGeometry | None = None
42+
id_: int | None = None

arho_feature_template/core/plan_manager.py

+112-118
Original file line numberDiff line numberDiff line change
@@ -2,161 +2,112 @@
22

33
import json
44
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
913
from qgis.PyQt.QtWidgets import QDialog, QMessageBox
1014
from qgis.utils import iface
1115

1216
from arho_feature_template.core.lambda_service import LambdaService
17+
from arho_feature_template.exceptions import UnsavedChangesError
1318
from arho_feature_template.gui.load_plan_dialog import LoadPlanDialog
19+
from arho_feature_template.gui.plan_attribure_form import PlanAttributeForm
1420
from arho_feature_template.gui.serialize_plan import SerializePlan
21+
from arho_feature_template.project.layers.plan_layers import PlanLayer, RegulationGroupLayer, plan_layers
1522
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
1731

32+
from arho_feature_template.core.models import Plan, RegulationGroup
1833
logger = logging.getLogger(__name__)
1934

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): ...
8037

8138

8239
class PlanManager:
8340
def __init__(self):
8441
self.json_plan_path = None
8542
self.json_plan_outline_path = None
8643

44+
self.digitize_map_tool = PlanDigitizeMapTool(iface.mapCanvas(), iface.cadDockWidget())
45+
self.digitize_map_tool.digitizingCompleted.connect(self._plan_geom_digitized)
46+
8747
def add_new_plan(self):
8848
"""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+
8953
if not handle_unsaved_changes():
9054
return
9155

92-
plan_layer = get_layer_by_name(LAYER_NAME_PLAN)
93-
56+
plan_layer = PlanLayer.get_from_project()
9457
if not plan_layer:
9558
return
96-
self.set_active_plan(None)
59+
self.previously_editable = plan_layer.isEditable()
9760

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)
10362

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)
10666

107-
def _feature_added(self):
67+
def _plan_geom_digitized(self, feature: QgsFeature):
10868
"""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()
11070
if not plan_layer:
11171
return
11272

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"]
12279
else:
123-
iface.messageBar().pushMessage("Error", "Layer is not editable.", level=3)
124-
return
80+
plan_to_be_activated = self.previous_active_plan_id
12581

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)
13183

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)
14188

14289
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+
144105
QgsExpressionContextUtils.setProjectVariable(QgsProject.instance(), "active_plan_id", plan_id)
106+
for layer in plan_layers:
107+
layer.apply_filter(plan_id)
145108

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()
160111

161112
def load_land_use_plan(self):
162113
"""Load an existing land use plan using a dialog selection."""
@@ -221,4 +172,47 @@ def save_plan_jsons(self, plan_json, outline_json):
221172
with open(self.json_plan_outline_path, "w", encoding="utf-8") as outline_file:
222173
json.dump(outline_json, outline_file, ensure_ascii=False, indent=2)
223174

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

arho_feature_template/exceptions.py

+13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@ def __init__(self, layer_name: str):
33
super().__init__(f"Layer {layer_name} not found")
44

55

6+
class LayerIoError(IOError): ...
7+
8+
9+
class LayerEditableError(LayerIoError):
10+
def __init__(self, layer_name: str):
11+
super().__init__(f"Layer {layer_name} is editable, when it should not be.")
12+
13+
14+
class UnsavedChangesError(LayerIoError):
15+
def __init__(self):
16+
super().__init__("Unsaved changes found")
17+
18+
619
class LayerNotVectorTypeError(Exception):
720
def __init__(self, layer_name: str):
821
super().__init__(f"Layer {layer_name} is not a vector layer")

0 commit comments

Comments
 (0)