Skip to content

Commit 7eff470

Browse files
authored
70 kaavamääräyslaji konfiguraatiotiedoston suunnittelu, muokkauksia kaavamamääräysryhmän lomakkeeseen (#71)
* add configuration file for plan regulations * add plan regulation configuration class and parser * updates to plan regulation config and plan regulation group creation form * use plan regulation config in new plan regulation group form * modified additional info in new plan regulation group form * use names from QGIS layer to display plan regulations
1 parent 9e708d6 commit 7eff470

7 files changed

+461
-124
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
from __future__ import annotations
2+
3+
import logging
4+
import os
5+
from dataclasses import dataclass
6+
from enum import Enum
7+
from pathlib import Path
8+
from typing import TYPE_CHECKING, cast
9+
10+
import yaml
11+
from qgis.core import QgsProject
12+
from qgis.utils import iface
13+
14+
from arho_feature_template.qgis_plugin_tools.tools.resources import resources_path
15+
16+
if TYPE_CHECKING:
17+
from typing import Literal
18+
19+
from qgis.core import QgsMapLayer
20+
from qgis.gui import QgisInterface
21+
22+
iface: QgisInterface = cast("QgisInterface", iface) # type: ignore[no-redef]
23+
24+
25+
logger = logging.getLogger(__name__)
26+
27+
DEFAULT_PLAN_REGULATIONS_CONFIG_PATH = Path(os.path.join(resources_path(), "kaavamaaraykset.yaml"))
28+
29+
30+
class ConfigSyntaxError(Exception):
31+
def __init__(self, message: str):
32+
super().__init__(f"Invalid config syntax: {message}")
33+
34+
35+
class UninitializedError(Exception):
36+
def __init__(self):
37+
super().__init__("PlanRegulationsSet is not initialized. Call 'load_config' first")
38+
39+
40+
class ValueType(Enum):
41+
POSITIVE_DECIMAL = "positiivinen desimaali"
42+
POSITIVE_INTEGER = "positiivinen kokonaisluku"
43+
POSITIVE_INTEGER_RANGE = "positiivinen kokonaisluku arvoväli"
44+
VERSIONED_TEXT = "kieliversioitu teksti"
45+
46+
47+
class Unit(Enum):
48+
SQUARE_METERS = "k-m2"
49+
CUBIC_METERS = "m3"
50+
EFFICIENCY_RATIO = "k-m2/m2"
51+
PERCENTAGE = "prosentti"
52+
AREA_RATIO = "m2/k-m2"
53+
54+
55+
# TODO: Same as in PlanManager, should refactor
56+
def get_layer_by_name(layer_name: str) -> QgsMapLayer | None:
57+
"""Retrieve a layer by name from the project."""
58+
layers = QgsProject.instance().mapLayersByName(layer_name)
59+
if layers:
60+
return layers[0]
61+
iface.messageBar().pushMessage("Error", f"Layer '{layer_name}' not found", level=3)
62+
return None
63+
64+
65+
def get_name_mapping_for_plan_regulations(layer_name: str) -> dict[str, dict[str, str]] | None:
66+
layer = get_layer_by_name(layer_name)
67+
if not layer:
68+
return None
69+
return {feature["value"]: feature["name"] for feature in layer.getFeatures()}
70+
71+
72+
@dataclass
73+
class PlanRegulationsSet:
74+
"""Describes the set of plan regulations."""
75+
76+
version: str
77+
regulations: list[PlanRegulationConfig]
78+
79+
_instance: PlanRegulationsSet | None = None
80+
81+
@classmethod
82+
def get_instance(cls) -> PlanRegulationsSet:
83+
"""Get the singleton instance, if initialized."""
84+
if cls._instance is None:
85+
raise UninitializedError
86+
return cls._instance
87+
88+
@classmethod
89+
def get_regulations(cls) -> list[PlanRegulationConfig]:
90+
"""Get the list of regulation configs, if instance is initialized."""
91+
instance = cls.get_instance()
92+
return instance.regulations
93+
94+
@classmethod
95+
def initialize(
96+
cls,
97+
config_path: Path = DEFAULT_PLAN_REGULATIONS_CONFIG_PATH,
98+
type_of_plan_regulations_layer_name="Kaavamääräyslaji",
99+
language: Literal["fin", "eng", "swe"] = "fin",
100+
) -> PlanRegulationsSet:
101+
# Initialize PlanRegulationsSet and PlanRegulationConfigs from config file
102+
with config_path.open(encoding="utf-8") as f:
103+
data = yaml.safe_load(f)
104+
cls._instance = cls.from_dict(data)
105+
106+
# Add names from plan regulation layer
107+
mapping = get_name_mapping_for_plan_regulations(type_of_plan_regulations_layer_name)
108+
if mapping:
109+
for regulation in cls.get_regulations():
110+
regulation.add_name(mapping, language)
111+
112+
logger.info("PlanRegulationsSet initialized successfully.")
113+
return cls._instance
114+
115+
@classmethod
116+
def from_dict(cls, data: dict) -> PlanRegulationsSet:
117+
file_version = data["version"]
118+
try:
119+
return cls(
120+
version=file_version,
121+
regulations=[PlanRegulationConfig.from_dict(config) for config in data["plan_regulations"]],
122+
)
123+
except KeyError as e:
124+
raise ConfigSyntaxError(str(e)) from e
125+
126+
127+
@dataclass
128+
class PlanRegulationConfig:
129+
"""Describes the configuration of a plan regulation."""
130+
131+
regulation_code: str
132+
name: str
133+
category_only: bool
134+
value_type: ValueType | None
135+
unit: Unit | None
136+
child_regulations: list[PlanRegulationConfig]
137+
138+
@classmethod
139+
def from_dict(cls, data: dict) -> PlanRegulationConfig:
140+
"""
141+
Initialize PlanRegulationConfig from dict.
142+
143+
Intializes child regulations recursively.
144+
"""
145+
return cls(
146+
regulation_code=data["regulation_code"],
147+
name=data["regulation_code"],
148+
category_only=data.get("category_only", False),
149+
value_type=ValueType(data["value_type"]) if "value_type" in data else None,
150+
unit=Unit(data["unit"]) if "unit" in data else None,
151+
child_regulations=[PlanRegulationConfig.from_dict(config) for config in data.get("child_regulations", [])],
152+
)
153+
154+
def add_name(self, code_to_name_mapping: dict[str, dict[str, str]], language: Literal["fin", "eng", "swe"]):
155+
language_to_name_dict = code_to_name_mapping.get(self.regulation_code)
156+
self.name = language_to_name_dict[language] if language_to_name_dict else self.regulation_code
157+
for regulation in self.child_regulations:
158+
regulation.add_name(code_to_name_mapping, language)

arho_feature_template/gui/new_plan_regulation_group_form.py

+24-27
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
from typing import TYPE_CHECKING
55

66
from qgis.PyQt import uic
7+
from qgis.PyQt.QtCore import Qt
78
from qgis.PyQt.QtWidgets import QDialog, QTreeWidget, QTreeWidgetItem
89

10+
from arho_feature_template.core.plan_regulation_config import PlanRegulationConfig, PlanRegulationsSet
911
from arho_feature_template.gui.new_plan_regulation_widget import NewPlanRegulationWidget
1012

1113
if TYPE_CHECKING:
@@ -31,39 +33,34 @@ def __init__(self):
3133
self.initialize_plan_regulations()
3234
self.plan_regulations_view.itemDoubleClicked.connect(self.add_selected_plan_regulation)
3335

36+
def _initalize_regulation_from_config(self, config: PlanRegulationConfig, parent: QTreeWidgetItem | None = None):
37+
tree_item = QTreeWidgetItem(parent)
38+
tree_item.setText(0, config.name)
39+
tree_item.setData(0, Qt.UserRole, config)
40+
41+
if parent is None:
42+
self.plan_regulations_view.addTopLevelItem(tree_item)
43+
44+
# Initialize plan regulations recursively
45+
if config.child_regulations:
46+
for child_config in config.child_regulations:
47+
self._initalize_regulation_from_config(child_config, tree_item)
48+
3449
def initialize_plan_regulations(self):
35-
# NOTE: Replace the dummy plan regulation codes below with codes from DB
36-
plan_regulation_codes = {
37-
"category 1": ["regulation 1", "regulation 2"],
38-
"category 2": ["regulation 3"],
39-
"category 3": ["regulation 4", "regulation 5", "regulation 6"],
40-
}
41-
42-
# Initialize categories
43-
for category_name, plan_regulations in plan_regulation_codes.items():
44-
category_item = QTreeWidgetItem()
45-
category_item.setText(0, category_name)
46-
self.plan_regulations_view.addTopLevelItem(category_item)
47-
48-
# Initialize plan regulations under the categories
49-
for plan_regulation_type in plan_regulations:
50-
plan_regulation_item = QTreeWidgetItem(category_item)
51-
plan_regulation_item.setText(0, plan_regulation_type)
52-
self.plan_regulations_view.addTopLevelItem(plan_regulation_item)
50+
for config in PlanRegulationsSet.get_regulations():
51+
self._initalize_regulation_from_config(config)
5352

5453
def add_selected_plan_regulation(self, item: QTreeWidgetItem, column: int):
55-
# If user double clicked category, don't add plan regulation
56-
if not item.parent():
54+
config: PlanRegulationConfig = item.data(column, Qt.UserRole) # Retrieve the associated config
55+
if config.category_only:
5756
return
58-
self.add_plan_regulation(item.text(column))
57+
self.add_plan_regulation(config)
5958

60-
def add_plan_regulation(self, regulation_type: str):
61-
new_plan_regulation_widget = NewPlanRegulationWidget(
62-
regulation_type=regulation_type, parent=self.plan_regulations_scroll_area_contents
63-
)
64-
new_plan_regulation_widget.delete_signal.connect(self.delete_plan_regulation)
59+
def add_plan_regulation(self, config: PlanRegulationConfig):
60+
widget = NewPlanRegulationWidget(config=config, parent=self.plan_regulations_scroll_area_contents)
61+
widget.delete_signal.connect(self.delete_plan_regulation)
6562
index = self.plan_regulations_layout.count() - 1
66-
self.plan_regulations_layout.insertWidget(index, new_plan_regulation_widget)
63+
self.plan_regulations_layout.insertWidget(index, widget)
6764

6865
def delete_plan_regulation(self, plan_regulation_widget: NewPlanRegulationWidget):
6966
self.plan_regulations_layout.removeWidget(plan_regulation_widget)

arho_feature_template/gui/new_plan_regulation_group_form.ui

+5-5
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
<item row="0" column="0">
4343
<widget class="QLabel" name="label_2">
4444
<property name="text">
45-
<string>Kaavamääräyksen otsikko</string>
45+
<string>Otsikko</string>
4646
</property>
4747
</widget>
4848
</item>
@@ -118,8 +118,8 @@
118118
<rect>
119119
<x>0</x>
120120
<y>0</y>
121-
<width>98</width>
122-
<height>28</height>
121+
<width>465</width>
122+
<height>435</height>
123123
</rect>
124124
</property>
125125
<layout class="QVBoxLayout" name="plan_regulations_layout">
@@ -157,8 +157,8 @@
157157
<rect>
158158
<x>0</x>
159159
<y>0</y>
160-
<width>98</width>
161-
<height>28</height>
160+
<width>727</width>
161+
<height>435</height>
162162
</rect>
163163
</property>
164164
</widget>

0 commit comments

Comments
 (0)