Coverage for gws-app/gws/plugin/qgis/layer.py: 0%

104 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-05 13:35 +0200

1"""The ``qgis`` layer.""" 

2 

3from typing import Optional, cast 

4 

5import gws 

6import gws.base.layer 

7import gws.config.util 

8 

9from . import grabber, provider, flatlayer 

10 

11 

12@gws.ext.config.layer('qgis') 

13class Config(gws.base.layer.Config, gws.base.layer.tree.Config): 

14 """Layer group that shows a QGIS project as a tree of its layers.""" 

15 

16 provider: Optional[provider.Config] 

17 """QGIS project the layers are loaded from.""" 

18 compositeRender: bool = False 

19 """Render all sublayers as one image instead of one image per layer.""" 

20 sqlFilters: Optional[dict] 

21 """SQL filters for source layers, passed to QGIS Server.""" 

22 

23 

24@gws.ext.object.layer('qgis') 

25class Object(gws.base.layer.group.Object): 

26 """Group layer that shows a QGIS project as a tree of layers. 

27 

28 The child layers are created from the project layer tree. With 

29 ``compositeRender``, the layer renders its visible children as one image. 

30 """ 

31 

32 provider: provider.Object 

33 """QGIS provider.""" 

34 compositeRender: bool = False 

35 """Render all visible sublayers as one image.""" 

36 sqlFilters: dict 

37 """SQL filters passed to the child layers, mapping a source layer name (or ``*``) to a filter expression.""" 

38 

39 def configure(self): 

40 self.compositeRender = self.cfg('compositeRender', default=False) 

41 self.sqlFilters = self.cfg('sqlFilters', default={}) 

42 if self.compositeRender: 

43 self.canRenderBox = True 

44 self.canRenderTile = True 

45 

46 def post_configure(self): 

47 if self.compositeRender: 

48 gws.base.layer.image.Object.post_configure_grabbers(self) 

49 

50 def create_cache_name(self, cache): 

51 return gws.u.sha256([ 

52 self.provider.cache_hash(), 

53 vars(self.imageFormat), 

54 list(self.wgsExtent), 

55 cache.requestBuffer, 

56 cache.requestTiles, 

57 ])[: gws.base.layer.image.CACHE_NAME_LENGTH] 

58 

59 def create_grabber(self, opts): 

60 return grabber.Object(opts, provider=self.provider, params={}) 

61 

62 def configure_provider(self): 

63 return gws.config.util.configure_provider_for(self, provider.Object) 

64 

65 def configure_group(self): 

66 if super().configure_group(): 

67 return True 

68 gws.base.layer.tree.configure_group_layers_for( 

69 self, 

70 self.provider.sourceLayers, 

71 self.provider.create_leaf_layer_config, 

72 ) 

73 return True 

74 

75 def configure_metadata(self): 

76 if super().configure_metadata(): 

77 return True 

78 self.metadata = self.provider.metadata 

79 return True 

80 

81 def props(self, user): 

82 p = super().props(user) 

83 if not self.compositeRender: 

84 return p 

85 

86 def _to_leaf(la): 

87 pla = la.props(user) 

88 if not pla: 

89 return pla 

90 if pla.get('type') == 'group': 

91 pla['layers'] = [_to_leaf(la) for la in pla['layers']] 

92 elif pla.get('type') in ('box', 'tile'): 

93 pla['type'] = 'compositeLeaf' 

94 return pla 

95 

96 p = gws.u.merge( 

97 p, 

98 type='compositeBox', 

99 url=self.url_path_for('box'), 

100 layers=[_to_leaf(la) for la in p['layers']], 

101 ) 

102 if self.displayMode == gws.LayerDisplayMode.tile: 

103 p.type = 'compositeTile' 

104 p.url = self.url_path_for('tile').replace('/z/', '/compositeLayerUids/{c}/z/') 

105 return p 

106 

107 def render_box(self, lri): 

108 if not self.compositeRender: 

109 return 

110 lri.renderParams = self.composite_render_params(lri) 

111 if not lri.renderParams: 

112 return 

113 return gws.base.layer.image.Object.render_box(self, lri) 

114 

115 def render_tile(self, lri): 

116 if not self.compositeRender: 

117 return 

118 lri.renderParams = self.composite_render_params(lri) 

119 if not lri.renderParams: 

120 return 

121 return gws.base.layer.image.Object.render_tile(self, lri) 

122 

123 def composite_render_params(self, lri: gws.LayerRenderInput) -> Optional[dict]: 

124 """Build the GetMap parameters for a composite render. 

125 

126 The parameters combine ``LAYERS`` and ``FILTER`` of the ``qgisflat`` 

127 descendants listed in ``compositeLayerUids`` of the extra render 

128 parameters. Layers the user cannot read are skipped. 

129 

130 Args: 

131 lri: Render input. 

132 

133 Returns: 

134 GetMap parameters, or ``None`` if no layers are requested. 

135 """ 

136 leaves = dict(lri.extraParams or {}).get('compositeLayerUids', []) 

137 if not leaves: 

138 return 

139 

140 layers = [] 

141 filters = [] 

142 

143 for la in self.find_descendants(gws.ext.object.layer): 

144 if la.uid not in leaves: 

145 continue 

146 if not lri.user.can_read(la): 

147 gws.log.debug(f'skipping {la.uid=} forbidden') 

148 continue 

149 if la.extType != 'qgisflat': 

150 gws.log.debug(f'skipping {la.uid=} {la.extType=}') 

151 continue 

152 p = cast(flatlayer.Object, la).render_params(lri, self.sqlFilters) 

153 if not p: 

154 gws.log.debug(f'skipping {la.uid=} no params') 

155 continue 

156 layers.extend(reversed(p.get('LAYERS', []))) 

157 f = p.get('FILTER') 

158 if f: 

159 filters.append(f) 

160 

161 if not layers: 

162 gws.log.debug(f'no layers') 

163 

164 params = {} 

165 params['LAYERS'] = list(reversed(layers)) 

166 if filters: 

167 params['FILTER'] = ';'.join(filters) 

168 

169 return params