Skip to content

Commit 9375cbc

Browse files
committed
refactoring finished, UIs work again, deleted unneeded config modules, feature saving still TBD
1 parent 4f1427d commit 9375cbc

12 files changed

+329
-450
lines changed

arho_feature_template/core/models.py

+194-9
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
11
from __future__ import annotations
22

3+
import logging
4+
import os
35
from dataclasses import dataclass, field
46
from enum import Enum
7+
from pathlib import Path
58
from typing import TYPE_CHECKING
69

10+
import yaml
11+
12+
from arho_feature_template.exceptions import ConfigSyntaxError
13+
from arho_feature_template.qgis_plugin_tools.tools.resources import resources_path
14+
from arho_feature_template.utils.misc_utils import get_layer_by_name, iface
15+
716
if TYPE_CHECKING:
8-
from numbers import Number
9-
from qgis.core import QgsGeometry, QgsFeature
17+
from typing import Literal
18+
19+
from qgis.core import QgsFeature, QgsGeometry
20+
21+
22+
logger = logging.getLogger(__name__)
23+
24+
DEFAULT_PLAN_REGULATIONS_CONFIG_PATH = Path(os.path.join(resources_path(), "kaavamaaraykset.yaml"))
1025

1126

1227
class ValueType(Enum):
@@ -17,13 +32,145 @@ class ValueType(Enum):
1732
VERSIONED_TEXT = "kieliversioitu teksti"
1833

1934

35+
@dataclass
36+
class RegulationGroupCategory:
37+
category_code: str
38+
name: str | None
39+
regulation_groups: list[RegulationGroup]
40+
41+
@classmethod
42+
def from_config_data(cls, data: dict) -> RegulationGroupCategory:
43+
return cls(
44+
category_code=data["category_code"],
45+
name=data.get("name"),
46+
regulation_groups=[
47+
RegulationGroup.from_config_data(config_data) for config_data in data["plan_regulation_groups"]
48+
],
49+
)
50+
51+
52+
@dataclass
53+
class RegulationGroupLibrary:
54+
"""Describes the configuration of a plan regulation group library"""
55+
56+
name: str
57+
version: int | None
58+
description: str | None
59+
regulation_group_categories: list[RegulationGroupCategory]
60+
# regulation_groups: list[RegulationGroup]
61+
62+
@classmethod
63+
def from_config_file(cls, config_fp: Path) -> RegulationGroupLibrary:
64+
with config_fp.open(encoding="utf-8") as f:
65+
data = yaml.safe_load(f)
66+
return RegulationGroupLibrary(
67+
name=data["name"],
68+
version=data.get("version"),
69+
description=data.get("description"),
70+
regulation_group_categories=[
71+
RegulationGroupCategory.from_config_data(category) for category in data["categories"]
72+
],
73+
)
74+
75+
76+
@dataclass
77+
class RegulationLibrary:
78+
"""Describes the set of plan regulations."""
79+
80+
version: str
81+
regulations: list[RegulationConfig]
82+
regulations_dict: dict[str, RegulationConfig]
83+
84+
_instance: RegulationLibrary | None = None
85+
86+
@classmethod
87+
def get_instance(cls) -> RegulationLibrary:
88+
"""Get the singleton instance, if initialized."""
89+
if cls._instance is None:
90+
return cls.initialize()
91+
return cls._instance
92+
93+
@classmethod
94+
def get_regulations(cls) -> list[RegulationConfig]:
95+
"""Get the list of top-level regulation configs, if instance is initialized."""
96+
return cls.get_instance().regulations
97+
98+
@classmethod
99+
def get_regulations_dict(cls) -> dict[str, RegulationConfig]:
100+
"""Get all regulations in a dictionary where keys are regulations codes and values RegulationConfigs."""
101+
return cls.get_instance().regulations_dict
102+
103+
@classmethod
104+
def get_regulation_by_code(cls, regulation_code: str) -> RegulationConfig | None:
105+
"""Get a regulation by it's regulation code (if exists)."""
106+
return cls.get_instance().regulations_dict.get(regulation_code)
107+
108+
@classmethod
109+
def initialize(
110+
cls,
111+
config_path: Path = DEFAULT_PLAN_REGULATIONS_CONFIG_PATH,
112+
type_of_plan_regulations_layer_name="Kaavamääräyslaji",
113+
language: Literal["fin", "eng", "swe"] = "fin",
114+
) -> RegulationLibrary:
115+
# Initialize RegulationLibrary and RegulationConfigs from QGIS layer and config file
116+
117+
# 1. Read config file into a dict
118+
with config_path.open(encoding="utf-8") as f:
119+
config_data = yaml.safe_load(f)
120+
121+
# 2. Read code layer
122+
layer = get_layer_by_name(type_of_plan_regulations_layer_name)
123+
if layer is None:
124+
msg = f"Could not find layer {type_of_plan_regulations_layer_name}!"
125+
raise KeyError(msg)
126+
127+
# 3. Initialize regulation configs from layer. Storing them by their ID is handy for adding childs later
128+
id_to_regulation_map: dict[str, RegulationConfig] = {
129+
feature["id"]: RegulationConfig.from_feature(feature, language) for feature in layer.getFeatures()
130+
}
131+
132+
# 4. Add information from config file (value, unit, category only) and link child regulations
133+
try:
134+
regulation_data: dict = {data["regulation_code"]: data for data in config_data["plan_regulations"]}
135+
top_level_regulations: list[RegulationConfig] = []
136+
for regulation_config in id_to_regulation_map.values():
137+
# Add possible information from config data file
138+
data = regulation_data.get(regulation_config.regulation_code)
139+
if data:
140+
regulation_config.category_only = data.get("category_only", False)
141+
regulation_config.value_type = ValueType(data["value_type"]) if "value_type" in data else None
142+
regulation_config.unit = data["unit"] if "unit" in data else None
143+
144+
# Top-level, add to list
145+
if not regulation_config.parent_id:
146+
top_level_regulations.append(regulation_config)
147+
else:
148+
# Add as child of another regulation
149+
id_to_regulation_map[regulation_config.parent_id].child_regulations.append(regulation_config)
150+
except KeyError as e:
151+
raise ConfigSyntaxError(str(e)) from e
152+
153+
# 5. Create dictionary, useful when creating PlanRegulationDefinitions at least
154+
regulations_dict: dict[str, RegulationConfig] = {}
155+
for reg in top_level_regulations:
156+
reg.add_to_dictionary(regulations_dict)
157+
158+
# 5. Create instance
159+
cls._instance = cls(
160+
version=config_data["version"], regulations=top_level_regulations, regulations_dict=regulations_dict
161+
)
162+
logger.info("RegulationLibrary initialized successfully.")
163+
return cls._instance
164+
165+
20166
@dataclass
21167
class RegulationConfig:
22168
"""
23169
Describes plan regulation type.
24-
170+
25171
Initialized from DB/QGIS layer and extended with data from a config file.
26172
"""
173+
27174
id: str
28175
regulation_code: str
29176
name: str
@@ -66,10 +213,10 @@ def add_to_dictionary(self, dictionary: dict[str, RegulationConfig]):
66213
@dataclass
67214
class Regulation:
68215
config: RegulationConfig # includes regulation_code and unit among other needed data for saving feature
69-
value: str | Number | tuple[int, int] | None = None
70-
regulation_number: int | None
71-
additional_information: dict[str, str | Number | None]
72-
files: list[str] = []
216+
value: str | float | int | tuple[int, int] | None = None
217+
additional_information: dict[str, str | float | int | None] | None = None
218+
regulation_number: int | None = None
219+
files: list[str] = field(default_factory=list)
73220
theme: str | None = None
74221
topic_tag: str | None = None
75222
id_: int | None = None
@@ -80,14 +227,52 @@ class Regulation:
80227

81228
@dataclass
82229
class RegulationGroup:
83-
type_code: str
230+
type_code: str | None
84231
name: str | None
85232
short_name: str | None
86233
color_code: str | None
87234
letter_code: str | None
88-
regulations: list[Regulation] | list[RegulationConfig]
235+
regulations: list[Regulation]
89236
id_: int | None = None
90237

238+
@classmethod
239+
def from_config_data(cls, data: dict) -> RegulationGroup:
240+
regulations = []
241+
for reg_data in data["plan_regulations"]:
242+
reg_code = reg_data["regulation_code"]
243+
config = RegulationLibrary.get_regulation_by_code(reg_code)
244+
if config:
245+
info_data = reg_data.get("additional_information")
246+
regulations.append(
247+
Regulation(
248+
config=config,
249+
value=reg_data.get("value"),
250+
additional_information={info["type"]: info.get("value") for info in info_data}
251+
if info_data
252+
else None,
253+
regulation_number=reg_data.get("regulation_number"),
254+
files=reg_data.get("files") if reg_data.get("files") else [],
255+
theme=reg_data.get("theme"),
256+
topic_tag=reg_data.get("topic_tag"),
257+
id_=None,
258+
)
259+
)
260+
else:
261+
iface.messageBar().pushWarning("", f"Could not find plan regulation {reg_code}!")
262+
return cls(
263+
type_code=None,
264+
name=data.get("name"),
265+
short_name=data.get("color_code"),
266+
color_code=data.get("color_code"),
267+
letter_code=data.get("letter_code"),
268+
regulations=regulations,
269+
id_=None,
270+
)
271+
272+
# @classmethod
273+
# def from_database(cls) -> RegulationGroup:
274+
# pass
275+
91276

92277
@dataclass
93278
class Plan:

0 commit comments

Comments
 (0)