1
1
from __future__ import annotations
2
2
3
+ import enum
3
4
import logging
4
5
import os
5
6
from dataclasses import dataclass , field
6
- from enum import Enum
7
7
from pathlib import Path
8
8
from typing import TYPE_CHECKING
9
9
10
10
import yaml
11
11
12
12
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
14
14
from arho_feature_template .qgis_plugin_tools .tools .resources import resources_path
15
15
from arho_feature_template .utils .misc_utils import LANGUAGE , get_layer_by_name , iface
16
16
23
23
logger = logging .getLogger (__name__ )
24
24
25
25
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"
34
45
35
46
36
47
class TemplateSyntaxError (Exception ):
@@ -198,8 +209,10 @@ def initialize(
198
209
data = regulation_data .get (regulation_config .regulation_code )
199
210
if data :
200
211
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
+ )
203
216
204
217
# Top-level, add to list
205
218
if not regulation_config .parent_id :
@@ -242,8 +255,7 @@ class RegulationConfig:
242
255
243
256
# Data from config file
244
257
category_only : bool = False
245
- value_type : ValueType | None = None
246
- unit : str | None = None
258
+ default_value : AttributeValue | None = None
247
259
248
260
# NOTE: Perhaps this ("model_from_feature") should be method of PlanTypeLayer class?
249
261
@classmethod
@@ -270,11 +282,145 @@ def add_to_dictionary(self, dictionary: dict[str, RegulationConfig]):
270
282
regulation .add_to_dictionary (dictionary )
271
283
272
284
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
+
273
419
@dataclass
274
420
class Regulation :
275
421
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 )
278
424
regulation_number : int | None = None
279
425
files : list [str ] = field (default_factory = list )
280
426
theme : str | None = None
@@ -304,21 +450,47 @@ class RegulationGroup:
304
450
propositions : list [Proposition ] = field (default_factory = list )
305
451
id_ : str | None = None
306
452
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
+
307
479
@classmethod
308
480
def from_config_data (cls , data : dict ) -> RegulationGroup :
309
481
regulations = []
310
482
for reg_data in data ["plan_regulations" ]:
311
483
reg_code = reg_data ["regulation_code" ]
312
484
config = RegulationLibrary .get_regulation_by_code (reg_code )
313
485
if config :
314
- info_data = reg_data .get ("additional_information" )
315
486
regulations .append (
316
487
Regulation (
317
488
config = config ,
318
489
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
+ ],
322
494
regulation_number = reg_data .get ("regulation_number" ),
323
495
files = reg_data .get ("files" ) if reg_data .get ("files" ) else [],
324
496
theme = reg_data .get ("theme" ),
0 commit comments