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
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
1"""The ``qgis`` layer."""
3from typing import Optional, cast
5import gws
6import gws.base.layer
7import gws.config.util
9from . import grabber, provider, flatlayer
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."""
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."""
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.
28 The child layers are created from the project layer tree. With
29 ``compositeRender``, the layer renders its visible children as one image.
30 """
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."""
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
46 def post_configure(self):
47 if self.compositeRender:
48 gws.base.layer.image.Object.post_configure_grabbers(self)
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]
59 def create_grabber(self, opts):
60 return grabber.Object(opts, provider=self.provider, params={})
62 def configure_provider(self):
63 return gws.config.util.configure_provider_for(self, provider.Object)
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
75 def configure_metadata(self):
76 if super().configure_metadata():
77 return True
78 self.metadata = self.provider.metadata
79 return True
81 def props(self, user):
82 p = super().props(user)
83 if not self.compositeRender:
84 return p
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
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
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)
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)
123 def composite_render_params(self, lri: gws.LayerRenderInput) -> Optional[dict]:
124 """Build the GetMap parameters for a composite render.
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.
130 Args:
131 lri: Render input.
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
140 layers = []
141 filters = []
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)
161 if not layers:
162 gws.log.debug(f'no layers')
164 params = {}
165 params['LAYERS'] = list(reversed(layers))
166 if filters:
167 params['FILTER'] = ';'.join(filters)
169 return params