Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kaavamääräysten lisätietojen tallennus #141

Merged
merged 15 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion arho-feature-template.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
"yaml.schemas": {
"./arho_feature_template/resources/template_libraries/schema/template_library.schema.json": [
"**/template_libraries/*.yaml"
],
"./arho_feature_template/resources/libraries/feature_templates/schema/lisatiedonlaji.schema.json": [
"**/libraries/additional_information.yaml"
]
}
},
Expand All @@ -55,7 +58,7 @@
"pathMappings": [
{
"localRoot": "${workspaceFolder}/arho_feature_template",
"remoteRoot": "${env:APPDATA}/QGIS/QGIS3/profiles/default/python/plugins/arho_feature_template"
"remoteRoot": "${env:APPDATA}/QGIS/QGIS3/profiles/arho-dev/python/plugins/arho_feature_template"
}
]
},
Expand Down
234 changes: 203 additions & 31 deletions arho_feature_template/core/models.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
from __future__ import annotations

import enum
import logging
import os
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING

import yaml

from arho_feature_template.exceptions import ConfigSyntaxError
from arho_feature_template.project.layers.code_layers import UndergroundTypeLayer
from arho_feature_template.project.layers.code_layers import AdditionalInformationTypeLayer, UndergroundTypeLayer
from arho_feature_template.qgis_plugin_tools.tools.resources import resources_path
from arho_feature_template.utils.misc_utils import LANGUAGE, get_layer_by_name, iface

Expand All @@ -23,14 +23,25 @@
logger = logging.getLogger(__name__)

DEFAULT_PLAN_REGULATIONS_CONFIG_PATH = Path(os.path.join(resources_path(), "libraries", "kaavamaaraykset.yaml"))


class ValueType(Enum):
DECIMAL = "desimaali"
POSITIVE_DECIMAL = "positiivinen desimaali"
POSITIVE_INTEGER = "positiivinen kokonaisluku"
POSITIVE_INTEGER_RANGE = "positiivinen kokonaisluku arvoväli"
VERSIONED_TEXT = "kieliversioitu teksti"
ADDITIONAL_INFORMATION_CONFIG_PATH = Path(os.path.join(resources_path(), "libraries", "additional_information.yaml"))


class AttributeValueDataType(str, enum.Enum):
LOCALIZED_TEXT = "LocalizedText"
TEXT = "Text"
NUMERIC = "Numeric"
NUMERIC_RANGE = "NumericRange"
POSITIVE_NUMERIC = "PositiveNumeric"
POSITIVE_NUMERIC_RANGE = "PositiveNumericRange"
DECIMAL = "Decimal"
DECIMAL_RANGE = "DecimalRange"
POSITIVE_DECIMAL = "PositiveDecimal"
POSITIVE_DECIMAL_RANGE = "PositiveDecimalRange"
CODE = "Code"
IDENTIFIER = "Identifier"
SPOT_ELEVATION = "SpotElevation"
TIME_PERIOD = "TimePeriod"
TIME_PERIOD_DATE_ONLY = "TimePeriodDateOnly"


class TemplateSyntaxError(Exception):
Expand Down Expand Up @@ -198,8 +209,10 @@ def initialize(
data = regulation_data.get(regulation_config.regulation_code)
if data:
regulation_config.category_only = data.get("category_only", False)
regulation_config.value_type = ValueType(data["value_type"]) if "value_type" in data else None
regulation_config.unit = data["unit"] if "unit" in data else None
regulation_config.default_value = AttributeValue(
value_data_type=(AttributeValueDataType(data["value_type"]) if "value_type" in data else None),
unit=data["unit"] if "unit" in data else None,
)

# Top-level, add to list
if not regulation_config.parent_id:
Expand Down Expand Up @@ -242,8 +255,7 @@ class RegulationConfig:

# Data from config file
category_only: bool = False
value_type: ValueType | None = None
unit: str | None = None
default_value: AttributeValue | None = None

# NOTE: Perhaps this ("model_from_feature") should be method of PlanTypeLayer class?
@classmethod
Expand All @@ -256,8 +268,8 @@ def from_feature(cls, feature: QgsFeature) -> RegulationConfig:
return cls(
id=feature["id"],
regulation_code=feature["value"],
name=feature["name"][LANGUAGE],
description=feature["description"][LANGUAGE] if feature["description"] else "",
name=feature["name"].get(LANGUAGE) if feature["name"] else None,
description=feature["description"].get(LANGUAGE) if feature["description"] else None,
status=feature["status"],
level=feature["level"],
parent_id=feature["parent_id"],
Expand All @@ -270,27 +282,161 @@ def add_to_dictionary(self, dictionary: dict[str, RegulationConfig]):
regulation.add_to_dictionary(dictionary)


@dataclass
class AdditionalInformationConfig:
# From layer
id: str
additional_information_type: str
name: str
description: str
status: str
level: int
parent_id: str | None

children: list[str] = field(default_factory=list)

# From config file
default_value: AttributeValue | None = None


@dataclass
class AdditionalInformationConfigLibrary:
version: str
configs: dict[str, AdditionalInformationConfig] = field(default_factory=dict)
top_level_codes: list[str] = field(default_factory=list)

_id_to_configs: dict[str, AdditionalInformationConfig] = field(default_factory=dict)
_instance: AdditionalInformationConfigLibrary | None = None

@classmethod
def get_instance(cls) -> AdditionalInformationConfigLibrary:
"""Get the singleton instance, if initialized."""
if cls._instance is None:
cls._instance = cls.initialize(ADDITIONAL_INFORMATION_CONFIG_PATH)
return cls._instance

@classmethod
def initialize(cls, config_fp: Path = ADDITIONAL_INFORMATION_CONFIG_PATH) -> AdditionalInformationConfigLibrary:
with config_fp.open(encoding="utf-8") as f:
data = yaml.safe_load(f)
if data.get("version") != 1:
msg = "Version must be 1"
raise ConfigSyntaxError(msg)
config_file_configs = {
ai_config_data["code"]: ai_config_data for ai_config_data in data["additional_information"]
}

code_to_configs: dict[str, AdditionalInformationConfig] = {}
id_to_cofigs: dict[str, AdditionalInformationConfig] = {}
for feature in AdditionalInformationTypeLayer.get_features():
ai_code = feature["value"]
congig_file_config = config_file_configs.get(ai_code)

default_value = (
AttributeValue(
value_data_type=AttributeValueDataType(congig_file_config["data_type"]),
unit=congig_file_config.get("unit"),
)
if congig_file_config is not None
else None
)

ai_config = AdditionalInformationConfig(
id=feature["id"],
additional_information_type=ai_code,
name=feature["name"].get(LANGUAGE) if feature["name"] else None,
description=feature["description"].get(LANGUAGE) if feature["description"] else None,
status=feature["status"],
level=feature["level"],
parent_id=feature["parent_id"],
default_value=default_value,
)
code_to_configs[ai_code] = ai_config
id_to_cofigs[feature["id"]] = ai_config

top_level_codes = []
for ai_config in code_to_configs.values():
if ai_config.parent_id:
id_to_cofigs[ai_config.parent_id].children.append(ai_config.additional_information_type)
else:
top_level_codes.append(ai_config.additional_information_type)

return cls(
version=data["version"],
configs=code_to_configs,
top_level_codes=top_level_codes,
_id_to_configs=id_to_cofigs,
)

@classmethod
def get_config_by_code(cls, code: str) -> AdditionalInformationConfig:
"""Get a regulation by it's regulation code.

Raises a KeyError if code not exists.
"""
return cls.get_instance().configs[code]

@classmethod
def get_config_by_id(cls, id_: str) -> AdditionalInformationConfig:
"""Get a regulation by it's regulation code.

Raises a KeyError if code not exists.
"""

return cls.get_instance()._id_to_configs[id_] # noqa: SLF001


@dataclass
class AttributeValue:
value_data_type: AttributeValueDataType | None = None

numeric_value: int | float | None = None
numeric_range_min: int | float | None = None
numeric_range_max: int | float | None = None

unit: str | None = None

text_value: str | None = None
text_syntax: str | None = None

code_list: str | None = None
code_value: str | None = None
code_title: str | None = None

height_reference_point: str | None = None


@dataclass
class AdditionalInformation:
config: AdditionalInformationConfig # includes code and unit among other needed data for saving feature

id_: str | None = None
plan_regulation_id: str | None = None
type_additional_information_id: str | None = None
value: AttributeValue | None = None


@dataclass
class Regulation:
config: RegulationConfig # includes regulation_code and unit among other needed data for saving feature
value: str | float | int | tuple[int, int] | None = None
additional_information: dict[str, str | float | int | None] | None = None
value: AttributeValue | None = None
additional_information: list[AdditionalInformation] = field(default_factory=list)
regulation_number: int | None = None
files: list[str] = field(default_factory=list)
theme: str | None = None
topic_tag: str | None = None
verbal_regulation_type_id: str | None = None
regulation_group_id_: int | None = None
id_: int | None = None
regulation_group_id: str | None = None
id_: str | None = None


@dataclass
class Proposition:
value: str
theme_id: str | None = None
proposition_number: int | None = None
regulation_group_id_: int | None = None
id_: int | None = None
regulation_group_id: str | None = None
id_: str | None = None


@dataclass
Expand All @@ -302,7 +448,33 @@ class RegulationGroup:
group_number: int | None = None
regulations: list[Regulation] = field(default_factory=list)
propositions: list[Proposition] = field(default_factory=list)
id_: int | None = None
id_: str | None = None

@staticmethod
def _additional_information_model_from_config(info_data: dict) -> AdditionalInformation:
ai_config = AdditionalInformationConfigLibrary.get_config_by_code(info_data["type"])
return AdditionalInformation(
config=ai_config,
value=AttributeValue(
value_data_type=info_data.get(
"value_data_type",
ai_config.default_value.value_data_type if ai_config.default_value is not None else None,
),
numeric_value=info_data.get("numeric_value"),
numeric_range_min=info_data.get("numeric_range_min"),
numeric_range_max=info_data.get("numeric_range_max"),
unit=info_data.get(
"unit",
ai_config.default_value.unit if ai_config.default_value is not None else None,
),
text_value=info_data.get("text_value"),
text_syntax=info_data.get("text_syntax"),
code_list=info_data.get("code_list"),
code_value=info_data.get("code_value"),
code_title=info_data.get("code_title"),
height_reference_point=info_data.get("height_reference_point"),
),
)

@classmethod
def from_config_data(cls, data: dict) -> RegulationGroup:
Expand All @@ -311,19 +483,19 @@ def from_config_data(cls, data: dict) -> RegulationGroup:
reg_code = reg_data["regulation_code"]
config = RegulationLibrary.get_regulation_by_code(reg_code)
if config:
info_data = reg_data.get("additional_information")
regulations.append(
Regulation(
config=config,
value=reg_data.get("value"),
additional_information={info["type"]: info.get("value") for info in info_data}
if info_data
else None,
additional_information=[
cls._additional_information_model_from_config(info)
for info in reg_data.get("additional_information", [])
],
regulation_number=reg_data.get("regulation_number"),
files=reg_data.get("files") if reg_data.get("files") else [],
theme=reg_data.get("theme"),
topic_tag=reg_data.get("topic_tag"),
regulation_group_id_=None,
regulation_group_id=None,
id_=None,
)
)
Expand Down Expand Up @@ -351,7 +523,7 @@ class PlanFeature:
description: str | None = None
regulation_groups: list[RegulationGroup] = field(default_factory=list)
plan_id: int | None = None
id_: int | None = None
id_: str | None = None

@classmethod
def from_config_data(cls, data: dict) -> PlanFeature:
Expand All @@ -373,7 +545,7 @@ class Plan:
general_regulations: list[RegulationGroup] = field(default_factory=list)
documents: list[Document] = field(default_factory=list)
geom: QgsGeometry | None = None
id_: int | None = None
id_: str | None = None


@dataclass
Expand All @@ -393,4 +565,4 @@ class Document:
confirmation_date: datetime | None = None
arrival_date: datetime | None = None
plan_id: int | None = None
id_: int | None = None
id_: str | None = None
Loading
Loading