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
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-23 20:06 +0200
1"""The ``qgisflat`` layer."""
3from typing import Optional
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
13from . import grabber, provider
16@gws.ext.config.layer('qgisflat')
17class Config(gws.base.layer.Config):
18 """Layer that renders selected QGIS project layers as a single image."""
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)"""
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.
34 The image is rendered by QGIS Server. The layer also provides search,
35 feature models and a legend for its source layers.
36 """
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."""
47 def configure(self):
48 self.sqlFilters = self.cfg('sqlFilters', default={})
49 self.configure_layer()
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]
61 def create_grabber(self, opts):
62 return grabber.Object(opts, provider=self.provider, params=self.render_params(gws.LayerRenderInput()))
64 def configure_provider(self):
65 return gws.config.util.configure_provider_for(self, provider.Object)
67 def configure_sources(self):
68 if super().configure_sources():
69 return True
71 gws.u.require(self.provider, 'failed to configure service provider')
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)
77 def configure_source_layers(self):
78 """Select the visible image layers of the project.
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 )
90 def configure_models(self):
91 return gws.config.util.configure_models_for(self, with_default=True)
93 def create_model(self, cfg):
94 """Create a ``qgis`` model for the layer's queryable source layers.
96 Args:
97 cfg: Model configuration, or ``None`` for the default model.
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 )
110 def configure_extent(self):
111 if super().configure_extent():
112 return True
113 self.wgsExtent = self.provider.wgsExtent
114 return True
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
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')
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
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
154 def configure_templates(self):
155 return gws.config.util.configure_templates_for(self)
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
164 def create_finder(self, cfg):
165 """Create a ``qgis`` finder for the layer's queryable source layers.
167 Args:
168 cfg: Finder configuration, or ``None`` for the default finder.
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 )
181 ##
183 def render_params(self, lri: gws.LayerRenderInput, parent_sql_filters: dict=None) -> dict:
184 """Build the GetMap parameters for the layer.
186 The parameters contain ``LAYERS`` and, if any SQL filters apply,
187 ``FILTER``, on top of the extra parameters of the render input.
189 Args:
190 lri: Render input.
191 parent_sql_filters: SQL filters of the parent layer, which override the layer's own filters.
193 Returns:
194 GetMap parameters.
195 """
196 params = dict(lri.extraParams or {})
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))
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)
211 return params