Skip to content

Commit d1aecda

Browse files
committed
Save additional information
Refactor also regulation values to use the common logic.
1 parent 95a7520 commit d1aecda

12 files changed

+944
-338
lines changed

arho_feature_template/core/models.py

+192-20
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
from __future__ import annotations
22

3+
import enum
34
import logging
45
import os
56
from dataclasses import dataclass, field
6-
from enum import Enum
77
from pathlib import Path
88
from typing import TYPE_CHECKING
99

1010
import yaml
1111

1212
from arho_feature_template.exceptions import ConfigSyntaxError
13-
from arho_feature_template.project.layers.code_layers import UndergroundTypeLayer
13+
from arho_feature_template.project.layers.code_layers import AdditionalInformationTypeLayer, UndergroundTypeLayer
1414
from arho_feature_template.qgis_plugin_tools.tools.resources import resources_path
1515
from arho_feature_template.utils.misc_utils import LANGUAGE, get_layer_by_name, iface
1616

@@ -23,14 +23,25 @@
2323
logger = logging.getLogger(__name__)
2424

2525
DEFAULT_PLAN_REGULATIONS_CONFIG_PATH = Path(os.path.join(resources_path(), "libraries", "kaavamaaraykset.yaml"))
26-
27-
28-
class ValueType(Enum):
29-
DECIMAL = "desimaali"
30-
POSITIVE_DECIMAL = "positiivinen desimaali"
31-
POSITIVE_INTEGER = "positiivinen kokonaisluku"
32-
POSITIVE_INTEGER_RANGE = "positiivinen kokonaisluku arvoväli"
33-
VERSIONED_TEXT = "kieliversioitu teksti"
26+
ADDITIONAL_INFORMATION_CONFIG_PATH = Path(os.path.join(resources_path(), "libraries", "additional_information.yaml"))
27+
28+
29+
class AttributeValueDataType(enum.StrEnum):
30+
LOCALIZED_TEXT = "LocalizedText"
31+
TEXT = "Text"
32+
NUMERIC = "Numeric"
33+
NUMERIC_RANGE = "NumericRange"
34+
POSITIVE_NUMERIC = "PositiveNumeric"
35+
POSITIVE_NUMERIC_RANGE = "PositiveNumericRange"
36+
DECIMAL = "Decimal"
37+
DECIMAL_RANGE = "DecimalRange"
38+
POSITIVE_DECIMAL = "PositiveDecimal"
39+
POSITIVE_DECIMAL_RANGE = "PositiveDecimalRange"
40+
CODE = "Code"
41+
IDENTIFIER = "Identifier"
42+
SPOT_ELEVATION = "SpotElevation"
43+
TIME_PERIOD = "TimePeriod"
44+
TIME_PERIOD_DATE_ONLY = "TimePeriodDateOnly"
3445

3546

3647
class TemplateSyntaxError(Exception):
@@ -198,8 +209,10 @@ def initialize(
198209
data = regulation_data.get(regulation_config.regulation_code)
199210
if data:
200211
regulation_config.category_only = data.get("category_only", False)
201-
regulation_config.value_type = ValueType(data["value_type"]) if "value_type" in data else None
202-
regulation_config.unit = data["unit"] if "unit" in data else None
212+
regulation_config.default_value = AttributeValue(
213+
value_data_type=(AttributeValueDataType(data["value_type"]) if "value_type" in data else None),
214+
unit=data["unit"] if "unit" in data else None,
215+
)
203216

204217
# Top-level, add to list
205218
if not regulation_config.parent_id:
@@ -242,8 +255,7 @@ class RegulationConfig:
242255

243256
# Data from config file
244257
category_only: bool = False
245-
value_type: ValueType | None = None
246-
unit: str | None = None
258+
default_value: AttributeValue | None = None
247259

248260
# NOTE: Perhaps this ("model_from_feature") should be method of PlanTypeLayer class?
249261
@classmethod
@@ -270,11 +282,145 @@ def add_to_dictionary(self, dictionary: dict[str, RegulationConfig]):
270282
regulation.add_to_dictionary(dictionary)
271283

272284

285+
@dataclass
286+
class AdditionalInformationConfig:
287+
# From layer
288+
id: str
289+
additional_information_type: str
290+
name: str
291+
description: str
292+
status: str
293+
level: int
294+
parent_id: str | None
295+
296+
children: list[str] = field(default_factory=list)
297+
298+
# From config file
299+
default_value: AttributeValue | None = None
300+
301+
302+
@dataclass
303+
class AdditionalInformationConfigLibrary:
304+
version: str
305+
configs: dict[str, AdditionalInformationConfig] = field(default_factory=dict)
306+
top_level_codes: list[str] = field(default_factory=list)
307+
308+
_id_to_configs: dict[str, AdditionalInformationConfig] = field(default_factory=dict)
309+
_instance: AdditionalInformationConfigLibrary | None = None
310+
311+
@classmethod
312+
def get_instance(cls) -> AdditionalInformationConfigLibrary:
313+
"""Get the singleton instance, if initialized."""
314+
if cls._instance is None:
315+
cls._instance = cls.initialize(ADDITIONAL_INFORMATION_CONFIG_PATH)
316+
return cls._instance
317+
318+
@classmethod
319+
def initialize(cls, config_fp: Path = ADDITIONAL_INFORMATION_CONFIG_PATH) -> AdditionalInformationConfigLibrary:
320+
with config_fp.open(encoding="utf-8") as f:
321+
data = yaml.safe_load(f)
322+
if data.get("version") != 1:
323+
msg = "Version must be 1"
324+
raise ConfigSyntaxError(msg)
325+
config_file_configs = {
326+
ai_config_data["code"]: ai_config_data for ai_config_data in data["additional_information"]
327+
}
328+
329+
code_to_configs: dict[str, AdditionalInformationConfig] = {}
330+
id_to_cofigs: dict[str, AdditionalInformationConfig] = {}
331+
for feature in AdditionalInformationTypeLayer.get_features():
332+
ai_code = feature["value"]
333+
congig_file_config = config_file_configs.get(ai_code)
334+
335+
default_value = (
336+
AttributeValue(
337+
value_data_type=AttributeValueDataType(congig_file_config["data_type"]),
338+
unit=congig_file_config.get("unit"),
339+
)
340+
if congig_file_config is not None
341+
else None
342+
)
343+
344+
ai_config = AdditionalInformationConfig(
345+
id=feature["id"],
346+
additional_information_type=ai_code,
347+
name=feature["name"].get(LANGUAGE) if feature["name"] else None,
348+
description=feature["description"].get(LANGUAGE) if feature["description"] else None,
349+
status=feature["status"],
350+
level=feature["level"],
351+
parent_id=feature["parent_id"],
352+
default_value=default_value,
353+
)
354+
code_to_configs[ai_code] = ai_config
355+
id_to_cofigs[feature["id"]] = ai_config
356+
357+
top_level_codes = []
358+
for ai_config in code_to_configs.values():
359+
if ai_config.parent_id:
360+
id_to_cofigs[ai_config.parent_id].children.append(ai_config.additional_information_type)
361+
else:
362+
top_level_codes.append(ai_config.additional_information_type)
363+
364+
return cls(
365+
version=data["version"],
366+
configs=code_to_configs,
367+
top_level_codes=top_level_codes,
368+
_id_to_configs=id_to_cofigs,
369+
)
370+
371+
@classmethod
372+
def get_config_by_code(cls, code: str) -> AdditionalInformationConfig:
373+
"""Get a regulation by it's regulation code.
374+
375+
Raises a KeyError if code not exists.
376+
"""
377+
return cls.get_instance().configs[code]
378+
379+
@classmethod
380+
def get_config_by_id(cls, id_: str) -> AdditionalInformationConfig:
381+
"""Get a regulation by it's regulation code.
382+
383+
Raises a KeyError if code not exists.
384+
"""
385+
386+
return cls.get_instance()._id_to_configs[id_] # noqa: SLF001
387+
388+
389+
@dataclass
390+
class AttributeValue:
391+
value_data_type: AttributeValueDataType | None = None
392+
393+
numeric_value: int | float | None = None
394+
numeric_range_min: int | float | None = None
395+
numeric_range_max: int | float | None = None
396+
397+
unit: str | None = None
398+
399+
text_value: str | None = None
400+
text_syntax: str | None = None
401+
402+
code_list: str | None = None
403+
code_value: str | None = None
404+
code_title: str | None = None
405+
406+
height_reference_point: str | None = None
407+
408+
409+
@dataclass
410+
class AdditionalInformation:
411+
config: AdditionalInformationConfig # includes code and unit among other needed data for saving feature
412+
413+
id_: str | None = None
414+
plan_regulation_id: str | None = None
415+
type_additional_information_id: str | None = None
416+
value: AttributeValue | None = None
417+
418+
273419
@dataclass
274420
class Regulation:
275421
config: RegulationConfig # includes regulation_code and unit among other needed data for saving feature
276-
value: str | float | int | tuple[int, int] | None = None
277-
additional_information: dict[str, str | float | int | None] | None = None
422+
value: AttributeValue | None = None
423+
additional_information: list[AdditionalInformation] = field(default_factory=list)
278424
regulation_number: int | None = None
279425
files: list[str] = field(default_factory=list)
280426
theme: str | None = None
@@ -304,21 +450,47 @@ class RegulationGroup:
304450
propositions: list[Proposition] = field(default_factory=list)
305451
id_: str | None = None
306452

453+
@staticmethod
454+
def _additional_information_model_from_config(info_data: dict) -> AdditionalInformation:
455+
ai_config = AdditionalInformationConfigLibrary.get_config_by_code(info_data["type"])
456+
return AdditionalInformation(
457+
config=ai_config,
458+
value=AttributeValue(
459+
value_data_type=info_data.get(
460+
"value_data_type",
461+
ai_config.default_value.value_data_type if ai_config.default_value is not None else None,
462+
),
463+
numeric_value=info_data.get("numeric_value"),
464+
numeric_range_min=info_data.get("numeric_range_min"),
465+
numeric_range_max=info_data.get("numeric_range_max"),
466+
unit=info_data.get(
467+
"unit",
468+
ai_config.default_value.unit if ai_config.default_value is not None else None,
469+
),
470+
text_value=info_data.get("text_value"),
471+
text_syntax=info_data.get("text_syntax"),
472+
code_list=info_data.get("code_list"),
473+
code_value=info_data.get("code_value"),
474+
code_title=info_data.get("code_title"),
475+
height_reference_point=info_data.get("height_reference_point"),
476+
),
477+
)
478+
307479
@classmethod
308480
def from_config_data(cls, data: dict) -> RegulationGroup:
309481
regulations = []
310482
for reg_data in data["plan_regulations"]:
311483
reg_code = reg_data["regulation_code"]
312484
config = RegulationLibrary.get_regulation_by_code(reg_code)
313485
if config:
314-
info_data = reg_data.get("additional_information")
315486
regulations.append(
316487
Regulation(
317488
config=config,
318489
value=reg_data.get("value"),
319-
additional_information={info["type"]: info.get("value") for info in info_data}
320-
if info_data
321-
else None,
490+
additional_information=[
491+
cls._additional_information_model_from_config(info)
492+
for info in reg_data.get("additional_information", [])
493+
],
322494
regulation_number=reg_data.get("regulation_number"),
323495
files=reg_data.get("files") if reg_data.get("files") else [],
324496
theme=reg_data.get("theme"),

arho_feature_template/core/plan_manager.py

+21-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from arho_feature_template.core.lambda_service import LambdaService
1212
from arho_feature_template.core.models import (
13+
AdditionalInformation,
1314
Document,
1415
FeatureTemplateLibrary,
1516
Plan,
@@ -29,6 +30,7 @@
2930
from arho_feature_template.gui.tools.inspect_plan_features_tool import InspectPlanFeatures
3031
from arho_feature_template.project.layers.code_layers import PlanRegulationGroupTypeLayer, code_layers
3132
from arho_feature_template.project.layers.plan_layers import (
33+
AdditionalInformationLayer,
3234
DocumentLayer,
3335
LandUseAreaLayer,
3436
LandUsePointLayer,
@@ -649,16 +651,33 @@ def save_regulation_group_association(regulation_group_id: str, layer_name: str,
649651

650652

651653
def save_regulation(regulation: Regulation) -> QgsFeature:
652-
feature = PlanRegulationLayer.feature_from_model(regulation)
654+
regulation_feature = PlanRegulationLayer.feature_from_model(regulation)
653655
layer = PlanRegulationLayer.get_from_project()
654656

655657
_save_feature(
656-
feature=feature,
658+
feature=regulation_feature,
657659
layer=layer,
658660
id_=regulation.id_,
659661
edit_text="Kaavamääräyksen lisäys" if regulation.id_ is None else "Kaavamääräyksen muokkaus",
660662
)
661663

664+
for additional_information in regulation.additional_information:
665+
additional_information.plan_regulation_id = regulation_feature["id"]
666+
save_additional_information(additional_information)
667+
668+
return regulation_feature
669+
670+
671+
def save_additional_information(additional_information: AdditionalInformation) -> QgsFeature:
672+
feature = AdditionalInformationLayer.feature_from_model(additional_information)
673+
layer = AdditionalInformationLayer.get_from_project()
674+
675+
_save_feature(
676+
feature=feature,
677+
layer=layer,
678+
id_=additional_information.id_,
679+
edit_text="Lisätiedon lisäys" if additional_information.id_ is None else "Lisätiedon muokkaus",
680+
)
662681
return feature
663682

664683

0 commit comments

Comments
 (0)