Coverage for gws-app/gws/plugin/qgis/flatlayer.py: 84%

98 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-10-23 20:06 +0200

1"""The ``qgisflat`` layer.""" 

2 

3from typing import Optional 

4 

5import gws 

6import gws.base.layer 

7import gws.config.util 

8import gws.lib.bounds 

9import gws.gis.source 

10import gws.gis.zoom 

11import gws.base.metadata 

12 

13from . import grabber, provider 

14 

15 

16@gws.ext.config.layer('qgisflat') 

17class Config(gws.base.layer.Config): 

18 """Layer that renders selected QGIS project layers as a single image.""" 

19 

20 provider: Optional[provider.Config] 

21 """QGIS project the layer is rendered from.""" 

22 sourceLayers: Optional[gws.gis.source.LayerFilter] 

23 """Source layers to render.""" 

24 sqlFilters: Optional[dict] 

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

26 withCache: Optional[bool] = False 

27 """Store rendered tiles in the persistent cache. (changed in 8.5)""" 

28 

29 

30@gws.ext.object.layer('qgisflat') 

31class Object(gws.base.layer.image.Object): 

32 """Image layer that renders selected QGIS project layers as one image. 

33 

34 The image is rendered by QGIS Server. The layer also provides search, 

35 feature models and a legend for its source layers. 

36 """ 

37 

38 provider: provider.Object 

39 """QGIS provider.""" 

40 sqlFilters: dict 

41 """SQL filters, mapping a source layer name (or ``*`` for all layers) to a filter expression.""" 

42 imageLayers: list[gws.SourceLayer] 

43 """Source layers to render.""" 

44 searchLayers: list[gws.SourceLayer] 

45 """Queryable source layers.""" 

46 

47 def configure(self): 

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

49 self.configure_layer() 

50 

51 def create_cache_name(self, cache): 

52 return gws.u.sha256([ 

53 self.provider.cache_hash(), 

54 self.render_params(gws.LayerRenderInput()), 

55 vars(self.imageFormat), 

56 list(self.wgsExtent), 

57 cache.requestBuffer, 

58 cache.requestTiles, 

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

60 

61 def create_grabber(self, opts): 

62 return grabber.Object(opts, provider=self.provider, params=self.render_params(gws.LayerRenderInput())) 

63 

64 def configure_provider(self): 

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

66 

67 def configure_sources(self): 

68 if super().configure_sources(): 

69 return True 

70 

71 gws.u.require(self.provider, 'failed to configure service provider') 

72 

73 self.configure_source_layers() 

74 self.imageLayers = gws.gis.source.filter_layers(self.sourceLayers, is_image=True) 

75 self.searchLayers = gws.gis.source.filter_layers(self.sourceLayers, is_queryable=True) 

76 

77 def configure_source_layers(self): 

78 """Select the visible image layers of the project. 

79 

80 Returns: 

81 Always ``True``. 

82 """ 

83 return gws.config.util.configure_source_layers_for( 

84 self, 

85 self.provider.sourceLayers, 

86 is_image=True, 

87 is_visible=True, 

88 ) 

89 

90 def configure_models(self): 

91 return gws.config.util.configure_models_for(self, with_default=True) 

92 

93 def create_model(self, cfg): 

94 """Create a ``qgis`` model for the layer's queryable source layers. 

95 

96 Args: 

97 cfg: Model configuration, or ``None`` for the default model. 

98 

99 Returns: 

100 The model. 

101 """ 

102 return self.create_child( 

103 gws.ext.object.model, 

104 cfg, 

105 type='qgis', 

106 _defaultProvider=self.provider, 

107 _defaultSourceLayers=self.searchLayers, 

108 ) 

109 

110 def configure_extent(self): 

111 if super().configure_extent(): 

112 return True 

113 self.wgsExtent = self.provider.wgsExtent 

114 return True 

115 

116 def configure_zoom_bounds(self): 

117 if super().configure_zoom_bounds(): 

118 return True 

119 b = gws.gis.source.combined_bounds(self.sourceLayers, self.mapCrs) 

120 if b: 

121 self.zoomBounds = b 

122 return True 

123 

124 def configure_resolutions(self): 

125 if super().configure_resolutions(): 

126 return True 

127 self.resolutions = gws.gis.zoom.resolutions_from_source_layers(self.sourceLayers, self.cfg('_parentResolutions'), self.mapCrs) 

128 if not self.resolutions: 

129 raise gws.Error(f'layer {self.uid!r}: no matching resolutions') 

130 

131 def configure_legend(self): 

132 # cannot use super() here, because the config must be extended with defaults 

133 if not self.cfg('withLegend'): 

134 return True 

135 cc = self.cfg('legend') 

136 options = gws.u.merge(self.provider.defaultLegendOptions, cc.options if cc else {}) 

137 self.legend = self.create_child( 

138 gws.ext.object.legend, 

139 cc, 

140 type='qgis', 

141 options=options, 

142 _defaultProvider=self.provider, 

143 _defaultSourceLayers=self.imageLayers, 

144 ) 

145 return True 

146 

147 def configure_metadata(self): 

148 if super().configure_metadata(): 

149 return True 

150 if len(self.sourceLayers) == 1: 

151 self.metadata = self.sourceLayers[0].metadata 

152 return True 

153 

154 def configure_templates(self): 

155 return gws.config.util.configure_templates_for(self) 

156 

157 def configure_search(self): 

158 if super().configure_search(): 

159 return True 

160 if self.searchLayers: 

161 self.finders.append(self.create_finder(None)) 

162 return True 

163 

164 def create_finder(self, cfg): 

165 """Create a ``qgis`` finder for the layer's queryable source layers. 

166 

167 Args: 

168 cfg: Finder configuration, or ``None`` for the default finder. 

169 

170 Returns: 

171 The finder. 

172 """ 

173 return self.create_child( 

174 gws.ext.object.finder, 

175 cfg, 

176 type='qgis', 

177 _defaultProvider=self.provider, 

178 _defaultSourceLayers=self.searchLayers, 

179 ) 

180 

181 ## 

182 

183 def render_params(self, lri: gws.LayerRenderInput, parent_sql_filters: dict=None) -> dict: 

184 """Build the GetMap parameters for the layer. 

185 

186 The parameters contain ``LAYERS`` and, if any SQL filters apply, 

187 ``FILTER``, on top of the extra parameters of the render input. 

188 

189 Args: 

190 lri: Render input. 

191 parent_sql_filters: SQL filters of the parent layer, which override the layer's own filters. 

192 

193 Returns: 

194 GetMap parameters. 

195 """ 

196 params = dict(lri.extraParams or {}) 

197 

198 layers = [sl.name for sl in self.imageLayers] 

199 # NB reversed: see the note in plugin/ows_client/wms/provider.py 

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

201 

202 filters = [] 

203 sqlf = gws.u.merge({}, self.sqlFilters, parent_sql_filters) 

204 for name in layers: 

205 flt = sqlf.get(name) or sqlf.get('*') 

206 if flt: 

207 filters.append(name + ': ' + flt) 

208 if filters: 

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

210 

211 return params