Open nyalldawson opened 3 years ago
I might have overlooked this point, but what will happen in the legend. Will it be a single symbol for the group or will items still be displayed individually? I guess the first option will be hard to implement and the second will cause some confusion.
I would think that this would have no impact on the legend, and that the legend will still render normally as individual items. In the case where a layer is masking out another (the airplane being used to hide an aerial to show osm underneath, the legend item may have to be manually removed from the legend unless there's a relatively easy/efficient way to 100% know that it shouldn't be in the legend.
Will it be a single symbol for the group or will items still be displayed individually?
Displayed individually.
Small question regarding identifying features: will it continue to work per layer or will it need to be implemented?
sorry to come late.
the naming of QgsGroupLayer vs QgsLayerTreeGroup makes it a bit difficult in the layer tree part of the code. Have you thought about something less generic like QgsRenderingLayerGroup
or it's meant to be generic ?
@3nids I'd be happy with QgsGroupRenderingLayer, if you want to do the rename
ok, let's do that when you have your work merged.
QGIS Enhancement: Render layers as groups
Date 2021/10/07
Author Nyall Dawson @nyalldawson
Contact nyall.dawson@gmail.com
maintainer nyall.dawson@gmail.com
Version QGIS 3.24
Summary
While QGIS supports grouping layers within the layer tree as a means of structuring projects, these groups have no impact on how the component layers are rendered. This proposal concerns adding an option for layer groups to "Render as group", which would cause all component layers to be rendered as a single flattened object during map renders.
Rendering as a flattened group opens new possibilities for map styling, including:
Here's another image demonstrating the resultant group render of the QGIS test data using a 50% transparent group layer:
Blend modes (like multiply, overlay, etc) can be applied on a group level. Just like opacity, setting a blend mode for an entire group would cause the contained layers to first be composited before rendering the flattened result using the desired blending mode. Using this approach would result in child layers obscuring each other completely, before rendering the result using a blend mode which reveals and modifies layers sitting below the group layer.
The scope of blend modes for CHILD layers from a group will be restricted to only affecting OTHER CHILD LAYERS from that group, and not other layers sitting below the whole group. E.g. a multiply mode applied to the top layer in a group would only multiply blend that layer with the other child layers from the group, before compositing the whole result WITHOUT the blend mode on top of other underlying map layers. While interesting in itself, this would also allow us to expose additional blending modes which are supported by Qt but not currently exposed in QGIS. Specifically, the blending modes which mask layers are not available in current QGIS versions because these modes affect ALL content rendered below the the layer without exceptions, including even things like the map background color! By offering a means to limit the scope of blending modes so that they only apply to other group children, these masking modes suddenly have practical use! Some examples:
Using a "destination out" blending mode for the airplane layer on the top of a group causes the content from the rest of the group to be "cut out" (or "masked") where the airplane point symbols are: (to be precise the opacity of the airplane layer is inverted and then applied to the content from the underlying layers):
And here's the same group when the blend mode for the airplane layer is set to "destination in" (the opacity of the airplane layer is applied directly to the underlying layers, i.e. their content is clipped to the airplane shapes):
Note that this works regardless of the layer type. Here's an example where "destination in" blend mode is applied to the airplane child layer from a group which consists of the airplane layer and a xyz basemap, with the group layer drawn on top of a standard raster layer (the aerial image):
Or let's get a bit more creative! Here's a group consisting of two child layers of the same polygon feature source. The bottom layer is drawn using a normal line pattern fill, and then the top child layer is drawn using a shapeburst fill which transitions from opaque at the polygon exteriors to transparent inside the polygon. I then set the top layer (the shapeburst) to the "destination in" blend mode, so that ONLY the opacity from the shapeburst is transferred to the line pattern fill. As a result the line pattern fill fades smoothly from the exterior to the interior of the polygon.
Here's a similar approach used to mask out a linear gradient fill from top left of the polygon to bottom right:
Effects like this are totally impossible to achieve in current QGIS versions.
This has previously been discussed here: http://lists.osgeo.org/pipermail/qgis-developer/2014-March/031884.html
Proposed Solution
QgsGroupLayer
A new
QgsMapLayer
subclass for grouped layers would be created, calledQgsGroupLayer
. This subclass will implement all the required pure virtual methods fromQgsMapLayer
, and contain in addition methods for setting and retrieving the group's child layers:As noted above, setting the child layers for a group does NOT transfer ownership of these layers. Rather, ownership is retained by the parent
QgsProject
or other data structure.Internally,
QgsGroupLayer
stores the list of child layers as aQList< QgsMapLayerRef >
list of weak pointers. (i.e. they will be automatically nulled if a layer is deleted). This list will be written to xml as a list of the layer ID strings. When a layer is restored the list will be re-populated using the stored layer IDs, and theQgsMapLayerRef
objects resolved to matching map layers in an override ofQgsMapLayer::resolveReferences
. E.g.:QgsGroupLayerRenderer
A new
QgsMapLayerRenderer
subclass forQgsGroupLayerRenderer
will be created. When aQgsGroupLayerRenderer
is constructed, the following preparation steps will occur (on the main thread) for each child layer in the group:childLayer->createMapRenderer( context )
. These child layer renderers will be stored in astd::vector< std::unique_ptr< QgsMapLayerRenderer > >
member for the group layer renderer.When the
QgsGroupLayerRenderer
is asked torender()
(e.g. on a background thread), the following steps will occur:QgsPaintEffect::begin
will be called on this effectQgsMapLayerRenderer
(corresponding to each group child):QgsMapLayerRenderer::render
will be called for the child layer renderer to do the actual child layer renderingWhen rendering a map which contains group layers, ONLY the group layer should be added to the QgsMapSettings layers -- it is not necessary to also add the group children (as this would cause them to be drawn twice. Once as part of the group, and once as an individual layer!).
Impact on multi-threaded rendering
As described above, group children will always be rendered sequentially in order to correctly apply interactions between the child layer's contents. This could potentially negatively affect the rendering speed of group layers containing many children (when compared to rendering these children as individual map layers). However, the group-based rendering will be completely optional and non-default, so any performance impact will be entirely optional.
Initially, rendering of group children will NOT utilise previous cached renders of layers. This is a potential optimisation for future development however, as it may be possible in certain circumstances to utilise a previously cached child layer render when rendering a group. (Noting that the existing caching logic will still apply to the WHOLE group itself -- ie. if NONE of the group's children require a redraw then the whole group layer will just be composited from a cached copy and not re-rendered).
Interaction with layer tree and layer order panel
By default, groups created in the layer tree will remain as structural only groups and will not affect rendering operations. A user must right click a group, and from a new "group properties" dialog opt in to "Render layers as a group". (Users will also be able to set group level properties such as the group opacity, blend mode and paint effect from this dialog).
As soon as a layer tree group is set to "render layers as a group", a corresponding
QgsGroupLayer
will be constructed and added to the project. These are NOT user visible nor will be shown as new entities in the layer tree. Logic will be added so that the layers set for map canvases will include the group layers (and not their individual children).When a group is set to "render layers as a group", then ONLY the group will be shown in the "layer order" panel list. Group children will NOT be visible in this order list, as their ordering is determined by the placement of the group layer.
Additional composition modes
Logic will be added so that the "masking" composition modes which are currently not exposed in QGIS will be available ONLY for layers which are children of groups. Specifically, the follow modes will added:
See the following image from the Qt docs for a visual demonstration of these modes (not that some modes will remain un-exposed, e.g. "clear", which has little discernible use!!, and source over/destination over which would instead be achieved through rearranging the order of map layers)
Affected Files
New classes for QgsGroupLayer and QgsGroupLayerRenderer will be added. Logic will be added to the layer tree to handle the creation/destruction of QgsGroupLayers, and new UI classes for group layer properties will be created.
Performance Implications
Noted above
Further Considerations/Improvements
(optional)
Backwards Compatibility
(required)
Issue Tracking ID(s)
https://github.com/qgis/QGIS/issues/24860 https://github.com/qgis/QGIS/issues/19648
Votes
(required)