5

I'd like to test whether a given feature is displayed, i.e. visible on the map canvas, inside a QGIS expression. Any way to do this?

I'm essentially looking for an analog to the is_selected() function, which tests whether a feature has been selected, but querying whether it's displayed or not. For my use case, I'm more concerned about whether it meets the user's current filtering criteria in the layer tree (e.g. which categories are set to display in the categorized renderer) than whether it is within the displayed canvas extent, but I could live with both. I don't see such an analog in the list of functions in the QGIS documentation, however.

As further context, I'm building an expression that aggregates across points inside polygons. Simplified version is

aggregate(layer:='YetiSites',
aggregate:='sum',
expression:=NumberOfYetiObserved,
filter:=intersects($geometry,geometry(@parent)))

which adds up the number of yeti observed at all sites that within the zone defined by the current (polygon) feature. See below, where it's assumed this expression is used in the labels for the zone polygons.

Example no problems

However, the YetiSites layer also has an attribute Observer, and in the layer tree users can choose which observers' sightings they want to see on the map. If the display settings exclude, say, Bill's observations, I want my sum to exclude them too. So I'd like to be able to write something like

filter:=(intersects($geometry,geometry(@parent)) and is_displayed($currentfeature))

but I'm trying to find how to code the (ficticious) is_displayed(...) part. Without this clause, I get the following, with 6 rather than the desired 5 shown for Zone A since the aggregation picks up Bill's point even though it is not being displayed.

enter image description here

I can of course emulate this by running a processing algorithm, or defining the display filter upstream and then using it both when displaying YetiSites and in my aggregation expression. But I'd love to just be responsive to what the user is selecting in the layer tree to display.

2
  • A screenshot could help understanding the structure of your project (layers, attributes). The is_layer_visible( ) function does not help in your case, right?
    – Babel
    Feb 10, 2021 at 17:35
  • @Babel, added a pic. Thanks, but is_layer_visible answers whether the whole layer is visible; I need whether a specific feature on it is.
    – Houska
    Feb 10, 2021 at 18:44

1 Answer 1

6

To check if a feature is actually rendered, you can implement parts of this answer: https://gis.stackexchange.com/a/350958/107424 as a custom function:

from qgis.core import *
from qgis.gui import *

@qgsfunction(args='auto', group='Custom')
def is_visible(inputlayername, feature, parent):
    layer = QgsProject.instance().mapLayersByName(inputlayername)[0]
    isvisible = 0 # reset indicator
    renderer = layer.renderer().clone()
    ctx = QgsRenderContext()
    renderer.startRender(ctx, QgsFields())
    ctx.expressionContext().setFeature(feature)
    if QgsProject.instance().layerTreeRoot().findLayer(layer.id()).isVisible(): # check if layer is visible
        if renderer.willRenderFeature(feature, ctx): # check if category is visible
            isvisible = 1
    renderer.stopRender(ctx)
    
    return isvisible

and use it as is_visible(@layer_name). It returns 1 if the feature is visible (checked in the layer tree) and 0 if not.

If you also want to check if a feature is in the current map canvas extent, and therefore visible or not, you can add something like:

...
currentextent = QgsGeometry.fromWkt(qgis.utils.iface.mapCanvas().extent().asWktPolygon())
...
    if renderer.willRenderFeature(feature, ctx):
        if feature.geometry().intersects(currentextent): # check if feature is inside current map canvas extent and therefore visible
            isvisible = 1
...
4
  • 1
    Why use currentextent ? I feel you don't need that part.
    – lejedi76
    Feb 10, 2021 at 22:44
  • Yeah. But to not mislead people, you should remove that part, else you can think this fonction return true when the feature is in the canvas extent
    – lejedi76
    Feb 10, 2021 at 23:01
  • Great suggestion, will try. Smart idea to re-run a clone of the renderer, and I appreciate the legwork (e.g. the need for startRender and stopRender) has already been done! For my own purposes, the .intersects(currentextent) is not necessary, but I appreciate its addition. It makes for a more broadly applicable is_visible() function. Or I may modify it to code in the intersection with the polygon; will explore if the hybrid of testing for visibility in a user-defined function AND intersection with a geometry in an aggregate(...) expression is a performance hit in my situation.
    – Houska
    Feb 11, 2021 at 0:14
  • 1
    OK, I've tested and works great (and accepted the answer). For greater applicability, I added a featuretocheck argument in addition to inputlayername, and use that instead of feature in the setFeature and willRenderFeature arguments so that it's not limited to $currentfeature. I was somewhat concerned about the overhead of creating and destroying a renderer copy at every invocation (esp. in a more complex aggregate filter:= clause), but so far no issues. If some show up later, can always code more of the filter inside the function.
    – Houska
    Feb 11, 2021 at 19:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.