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

96 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-05 13:35 +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 

27 

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

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

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

31 

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

33 feature models and a legend for its source layers. 

34 """ 

35 

36 provider: provider.Object 

37 """QGIS provider.""" 

38 sqlFilters: dict 

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

40 imageLayers: list[gws.SourceLayer] 

41 """Source layers to render.""" 

42 searchLayers: list[gws.SourceLayer] 

43 """Queryable source layers.""" 

44 

45 def configure(self): 

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

47 self.configure_layer() 

48 

49 def create_cache_name(self, cache): 

50 return gws.u.sha256([ 

51 self.provider.cache_hash(), 

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

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=self.render_params(gws.LayerRenderInput())) 

61 

62 def configure_provider(self): 

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

64 

65 def configure_sources(self): 

66 if super().configure_sources(): 

67 return True 

68 

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

70 

71 self.configure_source_layers() 

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

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

74 

75 def configure_source_layers(self): 

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

77 

78 Returns: 

79 Always ``True``. 

80 """ 

81 return gws.config.util.configure_source_layers_for( 

82 self, 

83 self.provider.sourceLayers, 

84 is_image=True, 

85 is_visible=True, 

86 ) 

87 

88 def configure_models(self): 

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

90 

91 def create_model(self, cfg): 

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

93 

94 Args: 

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

96 

97 Returns: 

98 The model. 

99 """ 

100 return self.create_child( 

101 gws.ext.object.model, 

102 cfg, 

103 type='qgis', 

104 _defaultProvider=self.provider, 

105 _defaultSourceLayers=self.searchLayers, 

106 ) 

107 

108 def configure_extent(self): 

109 if super().configure_extent(): 

110 return True 

111 self.wgsExtent = self.provider.wgsExtent 

112 return True 

113 

114 def configure_zoom_bounds(self): 

115 if super().configure_zoom_bounds(): 

116 return True 

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

118 if b: 

119 self.zoomBounds = b 

120 return True 

121 

122 def configure_resolutions(self): 

123 if super().configure_resolutions(): 

124 return True 

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

126 if not self.resolutions: 

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

128 

129 def configure_legend(self): 

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

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

132 return True 

133 cc = self.cfg('legend') 

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

135 self.legend = self.create_child( 

136 gws.ext.object.legend, 

137 cc, 

138 type='qgis', 

139 options=options, 

140 _defaultProvider=self.provider, 

141 _defaultSourceLayers=self.imageLayers, 

142 ) 

143 return True 

144 

145 def configure_metadata(self): 

146 if super().configure_metadata(): 

147 return True 

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

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

150 return True 

151 

152 def configure_templates(self): 

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

154 

155 def configure_search(self): 

156 if super().configure_search(): 

157 return True 

158 if self.searchLayers: 

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

160 return True 

161 

162 def create_finder(self, cfg): 

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

164 

165 Args: 

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

167 

168 Returns: 

169 The finder. 

170 """ 

171 return self.create_child( 

172 gws.ext.object.finder, 

173 cfg, 

174 type='qgis', 

175 _defaultProvider=self.provider, 

176 _defaultSourceLayers=self.searchLayers, 

177 ) 

178 

179 ## 

180 

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

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

183 

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

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

186 

187 Args: 

188 lri: Render input. 

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

190 

191 Returns: 

192 GetMap parameters. 

193 """ 

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

195 

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

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

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

199 

200 filters = [] 

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

202 for name in layers: 

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

204 if flt: 

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

206 if filters: 

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

208 

209 return params