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

1"""Structures and utilities for tree layers.""" 

2 

3from typing import Optional, Callable 

4 

5import gws 

6import gws.gis.source 

7import gws.config.parser 

8 

9from . import core 

10 

11 

12class FlattenConfig(gws.Config): 

13 """Layer hierarchy flattening""" 

14 

15 level: int 

16 """Flatten level.""" 

17 useGroups: bool = False 

18 """Use group names (true) or image layer names (false).""" 

19 

20 

21class Config(gws.Config): 

22 """Configuration for the layer tree.""" 

23 

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.""" 

32 

33 

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 

42 

43 

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.""" 

46 

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 ) 

58 

59 

60def layer_configs_from_args(tca: TreeConfigArgs) -> list[gws.Config]: 

61 """Generate a config tree from a list of source layers.""" 

62 

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) 

66 

67 # make configs... 

68 configs = gws.u.compact(_config(tca, sl, 0) for sl in roots) 

69 

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 ) 

76 

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) 

86 

87 if ctx.errors: 

88 raise ctx.errors[0] 

89 

90 return layer_configs 

91 

92 

93def _config(tca: TreeConfigArgs, sl: gws.SourceLayer, depth: int): 

94 cfg = _base_config(tca, sl, depth) 

95 if not cfg: 

96 return None 

97 

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 ) 

109 

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) 

113 

114 return gws.u.compact(cfg) 

115 

116 

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 

121 

122 # leaf layer 

123 if not sl.isGroup: 

124 return tca.leaf_layer_maker([sl]) 

125 

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]) 

131 

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) 

137 

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 }