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

81 lue kaavamääräyslajit QGISin tasolta #82

Merged
merged 4 commits into from
Dec 3, 2024
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
132 changes: 80 additions & 52 deletions arho_feature_template/core/plan_regulation_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from numbers import Number
from typing import Literal

from qgis.core import QgsFeature
from qgis.gui import QgisInterface

iface: QgisInterface = cast("QgisInterface", iface) # type: ignore[no-redef]
Expand Down Expand Up @@ -45,14 +46,14 @@ class ValueType(Enum):
VERSIONED_TEXT = "kieliversioitu teksti"


class Unit(Enum):
SQUARE_METERS = "k-m2"
CUBIC_METERS = "m3"
EFFICIENCY_RATIO = "k-m2/m2"
PERCENTAGE = "prosentti"
AREA_RATIO = "m2/k-m2"
DEGREES = "°"
DECIBEL = "dB"
# class Unit(Enum):
# SQUARE_METERS = "k-m2"
# CUBIC_METERS = "m3"
# EFFICIENCY_RATIO = "k-m2/m2"
# PERCENTAGE = "prosentti"
# AREA_RATIO = "m2/k-m2"
# DEGREES = "°"
# DECIBEL = "dB"


def get_name_mapping_for_plan_regulations(layer_name: str) -> dict[str, dict[str, str]] | None:
Expand All @@ -68,15 +69,15 @@ class PlanRegulationsSet:

version: str
regulations: list[PlanRegulationConfig]
regulations_dict: dict[str, PlanRegulationConfig] = field(default_factory=dict)
regulations_dict: dict[str, PlanRegulationConfig]

_instance: PlanRegulationsSet | None = None

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

@classmethod
Expand All @@ -101,71 +102,98 @@ def initialize(
type_of_plan_regulations_layer_name="Kaavamääräyslaji",
language: Literal["fin", "eng", "swe"] = "fin",
) -> PlanRegulationsSet:
# Initialize PlanRegulationsSet and PlanRegulationConfigs from config file
with config_path.open(encoding="utf-8") as f:
data = yaml.safe_load(f)
cls._instance = cls.from_dict(data)
# Initialize PlanRegulationsSet and PlanRegulationConfigs from QGIS layer and config file

regulations = cls.get_regulations()
regulations_dict: dict[str, PlanRegulationConfig] = {}
mapping = get_name_mapping_for_plan_regulations(type_of_plan_regulations_layer_name)
# 1. Read config file into a dict
with config_path.open(encoding="utf-8") as f:
config_data = yaml.safe_load(f)

# Add names to dictionary, add names to regulations
for regulation in regulations:
regulation.add_to_dictionary(regulations_dict)
if mapping:
regulation.add_name(mapping, language)
# 2. Read code layer
layer = get_layer_by_name(type_of_plan_regulations_layer_name)
if layer is None:
msg = f"Could not find layer {type_of_plan_regulations_layer_name}!"
raise KeyError(msg)

cls._instance.regulations_dict = regulations_dict
logger.info("PlanRegulationsSet initialized successfully.")
return cls._instance
# 3. Initialize regulation configs from layer. Storing them by their ID is handy for adding childs later
id_to_regulation_map: dict[str, PlanRegulationConfig] = {
feature["id"]: PlanRegulationConfig.from_feature(feature, language) for feature in layer.getFeatures()
}

@classmethod
def from_dict(cls, data: dict) -> PlanRegulationsSet:
file_version = data["version"]
# 4. Add information from config file (value, unit, category only) and link child regulations
try:
return cls(
version=file_version,
regulations=[PlanRegulationConfig.from_dict(config) for config in data["plan_regulations"]],
)
regulation_data: dict = {data["regulation_code"]: data for data in config_data["plan_regulations"]}
top_level_regulations: list[PlanRegulationConfig] = []
for regulation_config in id_to_regulation_map.values():
# Add possible information from config data file
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

# Top-level, add to list
if not regulation_config.parent_id:
top_level_regulations.append(regulation_config)
else:
# Add as child of another regulation
id_to_regulation_map[regulation_config.parent_id].child_regulations.append(regulation_config)
except KeyError as e:
raise ConfigSyntaxError(str(e)) from e

# 5. Create dictionary, useful when creating PlanRegulationDefinitions at least
regulations_dict: dict[str, PlanRegulationConfig] = {}
for reg in top_level_regulations:
reg.add_to_dictionary(regulations_dict)

# 5. Create instance
cls._instance = cls(
version=config_data["version"], regulations=top_level_regulations, regulations_dict=regulations_dict
)
logger.info("PlanRegulationsSet initialized successfully.")
return cls._instance


@dataclass
class PlanRegulationConfig:
"""Describes the configuration of a plan regulation."""
"""
Describes the configuration of a plan regulation.

Combination of information read from code layer in QGIS / DB and other information
from a configuration file.
"""

id: str
regulation_code: str
name: str
category_only: bool
value_type: ValueType | None
unit: Unit | None
child_regulations: list[PlanRegulationConfig]
description: str
status: str
level: int
parent_id: str | None
child_regulations: list[PlanRegulationConfig] = field(default_factory=list)

category_only: bool = False
value_type: ValueType | None = None
unit: str | None = None

@classmethod
def from_dict(cls, data: dict) -> PlanRegulationConfig:
def from_feature(cls, feature: QgsFeature, language: str = "fin") -> PlanRegulationConfig:
"""
Initialize PlanRegulationConfig from dict.
Initialize PlanRegulationConfig from QgsFeature.

Intializes child regulations recursively.
Child regulations, value type ,category only and unit need to be set separately.
"""
return cls(
regulation_code=data["regulation_code"],
name=data["regulation_code"],
category_only=data.get("category_only", False),
value_type=ValueType(data["value_type"]) if "value_type" in data else None,
unit=Unit(data["unit"]) if "unit" in data else None,
child_regulations=[PlanRegulationConfig.from_dict(config) for config in data.get("child_regulations", [])],
id=feature["id"],
regulation_code=feature["value"],
name=feature["name"][language],
description=feature["description"][language],
status=feature["status"],
level=feature["level"],
parent_id=feature["parent_id"],
)

def add_name(self, code_to_name_mapping: dict[str, dict[str, str]], language: Literal["fin", "eng", "swe"]):
language_to_name_dict = code_to_name_mapping.get(self.regulation_code)
self.name = language_to_name_dict[language] if language_to_name_dict else self.regulation_code
for regulation in self.child_regulations:
regulation.add_name(code_to_name_mapping, language)

def add_to_dictionary(self, dictionary: dict[str, PlanRegulationConfig]):
"""Add child regulations to dictionary too."""
dictionary[self.regulation_code] = self
for regulation in self.child_regulations:
regulation.add_to_dictionary(dictionary)
Expand Down
8 changes: 7 additions & 1 deletion arho_feature_template/gui/new_plan_regulation_group_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from qgis.PyQt import uic
from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtWidgets import QDialog, QTreeWidget, QTreeWidgetItem
from qgis.PyQt.QtWidgets import QDialog, QTextBrowser, QTreeWidget, QTreeWidgetItem

from arho_feature_template.core.plan_regulation_config import PlanRegulationConfig, PlanRegulationsSet
from arho_feature_template.gui.plan_regulation_widget import PlanRegulationWidget
Expand All @@ -28,10 +28,12 @@ def __init__(self):
self.plan_regulations_view: QTreeWidget
self.plan_regulations_scroll_area_contents: QWidget
self.plan_regulations_layout: QBoxLayout
self.plan_regulation_info: QTextBrowser

# INIT
self.initialize_plan_regulations()
self.plan_regulations_view.itemDoubleClicked.connect(self.add_selected_plan_regulation)
self.plan_regulations_view.itemClicked.connect(self.update_selected_plan_regulation)

def _initalize_regulation_from_config(self, config: PlanRegulationConfig, parent: QTreeWidgetItem | None = None):
tree_item = QTreeWidgetItem(parent)
Expand All @@ -50,6 +52,10 @@ def initialize_plan_regulations(self):
for config in PlanRegulationsSet.get_regulations():
self._initalize_regulation_from_config(config)

def update_selected_plan_regulation(self, item: QTreeWidgetItem, column: int):
config: PlanRegulationConfig = item.data(column, Qt.UserRole) # Retrieve the associated config
self.plan_regulation_info.setText(config.description)

def add_selected_plan_regulation(self, item: QTreeWidgetItem, column: int):
config: PlanRegulationConfig = item.data(column, Qt.UserRole) # Retrieve the associated config
if config.category_only:
Expand Down
5 changes: 2 additions & 3 deletions arho_feature_template/gui/new_plan_regulation_group_form.ui
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@
</widget>
</item>
<item>
<widget class="QTextBrowser" name="textBrowser">
<widget class="QTextBrowser" name="plan_regulation_info">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
Expand All @@ -159,9 +159,8 @@
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;ESIMERKKI&lt;/span&gt;: Ilmaisee, että kaavakohde kuvaa asumisen rakennuksille tai asunnoille tarkoitetun alueen. Käytetään asemakaavoissa ilmaisemaan asuinrakennusten alueita, joille voidaan rakentaa eri tyyppisiä asuinrakennuksia. Maakunta- ja yleiskaavoissa käytetään ilmaisemaan asuntoalueita, jolla kerrosalasta pääosa on tarkoitettu asumiseen. Koodi liittyy lähtökohtaisesti kaavakohteeseen, joka on geometrialtaan alue.&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Kaavamääräyksen lyhyt nimi: A (AK ja YK) &amp;amp; AA (MK)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
Expand Down
18 changes: 9 additions & 9 deletions arho_feature_template/gui/plan_regulation_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
QWidget,
)

from arho_feature_template.core.plan_regulation_config import PlanRegulationConfig, Unit, ValueType
from arho_feature_template.core.plan_regulation_config import PlanRegulationConfig, ValueType
from arho_feature_template.utils.misc_utils import get_additional_information_name, get_layer_by_name

if TYPE_CHECKING:
Expand Down Expand Up @@ -116,7 +116,7 @@ def _check_number_or_none(value: str | Number | None, error_msg: str):
raise ValueError(error_msg)

def _add_value_input(
self, value_type: ValueType, unit: Unit | None, default_value: str | Number | list[int] | None = None
self, value_type: ValueType, unit: str | None, default_value: str | Number | list[int] | None = None
):
base_error_msg = f"Invalid type for default value {type(default_value)}."
if value_type in [ValueType.DECIMAL, ValueType.POSITIVE_DECIMAL]:
Expand Down Expand Up @@ -214,7 +214,7 @@ def _add_widgets_to_form(self, label: QLabel, widget: QWidget):
if not self.expanded:
self._on_expand_hide_btn_clicked()

def add_decimal_input(self, value_type: ValueType, unit: Unit | None, default_value: Number | None = None):
def add_decimal_input(self, value_type: ValueType, unit: str | None, default_value: Number | None = None):
value_widget = QgsDoubleSpinBox()
label = QLabel("Arvo")
if value_type == ValueType.POSITIVE_DECIMAL:
Expand All @@ -225,24 +225,24 @@ def add_decimal_input(self, value_type: ValueType, unit: Unit | None, default_va
label.setToolTip("Tyyppi: desimaali")
value_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
if unit:
value_widget.setSuffix(f" {unit.value}")
value_widget.setSuffix(f" {unit}")
if default_value:
value_widget.setValue(default_value)
self._add_widgets_to_form(label, value_widget)

def add_positive_integer_input(self, unit: Unit | None, default_value: int | None = None):
def add_positive_integer_input(self, unit: str | None, default_value: int | None = None):
value_widget = QgsSpinBox()
value_widget.setMinimum(0)
value_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
if unit:
value_widget.setSuffix(f" {unit.value}")
value_widget.setSuffix(f" {unit}")
label = QLabel("Arvo")
label.setToolTip("Tyyppi: kokonaisluku (positiivinen)")
if default_value:
value_widget.setValue(default_value)
self._add_widgets_to_form(label, value_widget)

def add_positive_integer_range_input(self, unit: Unit | None, default_values: list[int] | None = None):
def add_positive_integer_range_input(self, unit: str | None, default_values: list[int] | None = None):
min_widget = QgsSpinBox()
min_widget.setMinimum(0)
min_label = QLabel("Arvo minimi")
Expand All @@ -253,8 +253,8 @@ def add_positive_integer_range_input(self, unit: Unit | None, default_values: li
max_label = QLabel("Arvo maksimi")
max_label.setToolTip("Tyyppi: kokonaisluku arvoväli (positiivinen)")
if unit:
min_widget.setSuffix(f" {unit.value}")
max_widget.setSuffix(f" {unit.value}")
min_widget.setSuffix(f" {unit}")
max_widget.setSuffix(f" {unit}")
if default_values:
min_widget.setValue(default_values[0])
max_widget.setValue(default_values[1])
Expand Down
4 changes: 0 additions & 4 deletions arho_feature_template/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

from arho_feature_template.core.feature_template_library import FeatureTemplater, TemplateGeometryDigitizeMapTool
from arho_feature_template.core.plan_manager import PlanManager
from arho_feature_template.core.plan_regulation_config import PlanRegulationsSet
from arho_feature_template.gui.new_plan_regulation_group_form import NewPlanRegulationGroupForm
from arho_feature_template.gui.plugin_settings import PluginSettings
from arho_feature_template.qgis_plugin_tools.tools.custom_logging import setup_logger, teardown_logger
Expand Down Expand Up @@ -44,9 +43,6 @@ def __init__(self) -> None:
self.actions: list[QAction] = []
self.menu = Plugin.name

# Initialize plan regulations set
PlanRegulationsSet.initialize()

def add_action(
self,
icon_path: str,
Expand Down
Loading
Loading