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
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +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."""
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.
32 The image is rendered by QGIS Server. The layer also provides search,
33 feature models and a legend for its source layers.
34 """
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."""
45 def configure(self):
46 self.sqlFilters = self.cfg('sqlFilters', default={})
47 self.configure_layer()
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]
59 def create_grabber(self, opts):
60 return grabber.Object(opts, provider=self.provider, params=self.render_params(gws.LayerRenderInput()))
62 def configure_provider(self):
63 return gws.config.util.configure_provider_for(self, provider.Object)
65 def configure_sources(self):
66 if super().configure_sources():
67 return True
69 gws.u.require(self.provider, 'failed to configure service provider')
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)
75 def configure_source_layers(self):
76 """Select the visible image layers of the project.
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 )
88 def configure_models(self):
89 return gws.config.util.configure_models_for(self, with_default=True)
91 def create_model(self, cfg):
92 """Create a ``qgis`` model for the layer's queryable source layers.
94 Args:
95 cfg: Model configuration, or ``None`` for the default model.
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 )
108 def configure_extent(self):
109 if super().configure_extent():
110 return True
111 self.wgsExtent = self.provider.wgsExtent
112 return True
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
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')
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
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
152 def configure_templates(self):
153 return gws.config.util.configure_templates_for(self)
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
162 def create_finder(self, cfg):
163 """Create a ``qgis`` finder for the layer's queryable source layers.
165 Args:
166 cfg: Finder configuration, or ``None`` for the default finder.
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 )
179 ##
181 def render_params(self, lri: gws.LayerRenderInput, parent_sql_filters: dict=None) -> dict:
182 """Build the GetMap parameters for the layer.
184 The parameters contain ``LAYERS`` and, if any SQL filters apply,
185 ``FILTER``, on top of the extra parameters of the render input.
187 Args:
188 lri: Render input.
189 parent_sql_filters: SQL filters of the parent layer, which override the layer's own filters.
191 Returns:
192 GetMap parameters.
193 """
194 params = dict(lri.extraParams or {})
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))
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)
209 return params