Coverage for gws-app/gws/base/layer/tree.py: 46%
68 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-16 22:59 +0200
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-16 22:59 +0200
1"""Structures and utilities for tree layers."""
3from typing import Optional, Callable
5import gws
6import gws.gis.source
7import gws.config.parser
9from . import core
12class FlattenConfig(gws.Config):
13 """Layer hierarchy flattening"""
15 level: int
16 """Flatten level."""
17 useGroups: bool = False
18 """Use group names (true) or image layer names (false)."""
21class Config(gws.Config):
22 """Configuration for the layer tree."""
24 rootLayers: Optional[gws.gis.source.LayerFilter]
25 """Source layers to use as roots."""
26 excludeLayers: Optional[gws.gis.source.LayerFilter]
27 """Source layers to exclude."""
28 flattenLayers: Optional[FlattenConfig]
29 """Flatten the layer hierarchy."""
30 autoLayers: Optional[list[core.AutoLayersOptions]]
31 """Custom configurations for automatically created layers."""
34class TreeConfigArgs(gws.Data):
35 root: gws.Root
36 source_layers: list[gws.SourceLayer]
37 roots_slf: gws.gis.source.LayerFilter
38 exclude_slf: gws.gis.source.LayerFilter
39 flatten_config: FlattenConfig
40 auto_layers: list[core.AutoLayersOptions]
41 leaf_layer_maker: Callable
44def layer_configs_from_layer(layer: core.Object, source_layers: list[gws.SourceLayer], leaf_layer_maker: Callable) -> list[gws.Config]:
45 """Generate a config tree from a list of source layers and the main layer config."""
47 return layer_configs_from_args(
48 TreeConfigArgs(
49 root=layer.root,
50 source_layers=source_layers,
51 roots_slf=layer.cfg('rootLayers'),
52 exclude_slf=layer.cfg('excludeLayers'),
53 flatten_config=layer.cfg('flattenLayers'),
54 auto_layers=layer.cfg('autoLayers', default=[]),
55 leaf_layer_maker=leaf_layer_maker,
56 )
57 )
60def layer_configs_from_args(tca: TreeConfigArgs) -> list[gws.Config]:
61 """Generate a config tree from a list of source layers."""
63 # by default, take top-level layers as roots
64 roots_slf = tca.roots_slf or gws.gis.source.LayerFilter(level=1)
65 roots = gws.gis.source.filter_layers(tca.source_layers, roots_slf)
67 # make configs...
68 configs = gws.u.compact(_config(tca, sl, 0) for sl in roots)
70 # configs need to be reparsed so that defaults can be injected
71 layer_configs = []
72 ctx = gws.ConfigContext(
73 specs=tca.root.specs,
74 readOptions={gws.SpecReadOption.acceptExtraProps, gws.SpecReadOption.allowMissing},
75 )
77 for c in configs:
78 cfg = gws.config.parser.parse_dict(
79 gws.u.to_dict(c),
80 path='',
81 as_type='gws.ext.config.layer',
82 ctx=ctx,
83 )
84 if cfg:
85 layer_configs.append(cfg)
87 if ctx.errors:
88 raise ctx.errors[0]
90 return layer_configs
93def _config(tca: TreeConfigArgs, sl: gws.SourceLayer, depth: int):
94 cfg = _base_config(tca, sl, depth)
95 if not cfg:
96 return None
98 cfg = gws.u.merge(
99 gws.u.to_dict(cfg),
100 {
101 'title': sl.title,
102 'clientOptions': {
103 'hidden': not sl.isVisible,
104 'expanded': sl.isExpanded,
105 },
106 'opacity': sl.opacity or 1,
107 },
108 )
110 for cc in tca.auto_layers:
111 if gws.gis.source.layer_matches(sl, cc.applyTo):
112 cfg = gws.u.deep_merge(cfg, cc.config)
114 return gws.u.compact(cfg)
117def _base_config(tca: TreeConfigArgs, sl: gws.SourceLayer, depth: int):
118 # source layer excluded by the filter
119 if tca.exclude_slf and gws.gis.source.layer_matches(sl, tca.exclude_slf):
120 return None
122 # leaf layer
123 if not sl.isGroup:
124 return tca.leaf_layer_maker([sl])
126 # flattened group layer
127 # NB use the absolute level to compute flatness, could also use relative (=depth)
128 if tca.flatten_config and sl.aLevel >= tca.flatten_config.level:
129 if tca.flatten_config.useGroups:
130 return tca.leaf_layer_maker([sl])
132 slf = gws.gis.source.LayerFilter(isImage=True)
133 leaves = gws.gis.source.filter_layers([sl], slf)
134 if not leaves:
135 return None
136 return tca.leaf_layer_maker(leaves)
138 # ordinary group layer
139 layer_cfgs = gws.u.compact(_config(tca, sub, depth + 1) for sub in sl.layers)
140 if not layer_cfgs:
141 return None
142 return {
143 'type': 'group',
144 'layers': layer_cfgs,
145 }