7

I am developing a QGIS plugin in python.

I need to allow the user to select multiple features of the current active layer using an extent.

I am using this code to do that:

self.iface.activeLayer().selectByRect(extent)
selectedFeaturesList = list(self.iface.activeLayer().selectedFeatures())

The problem is my layer is categorized depending on an attribute, and sometimes the user shows/hides some of these categories. And I want the plugin to select only visible features.

So what I want is something like:

for feat in selectedFeaturesList:
    if feat.isVisible():
        visibleSelectedFeatures.append(feat)

I have searched a lot and couldn't find any way to determine the QgsFeature visibility.

0

2 Answers 2

6

Another solution, using willRenderFeature. This works regardless of the renderer type. You need to clone the renderer, and call willRenderFeature between startRender and stopRender, otherwise QGIS crashes (cf. pyQGIS API).

You can create the QgsRenderContexton the fly, but you need to call setFeature() on its expressionContext for each feature.

def getVisibleSelectedFeatures(layer):
    visibleFeatures = []
    renderer = layer.renderer().clone()
    ctx = QgsRenderContext()
    renderer.startRender(ctx, QgsFields())
    for feature in layer.selectedFeatures():
        ctx.expressionContext().setFeature(feature)
        if renderer.willRenderFeature(feature, ctx):
            visibleFeatures.append(feature)
    renderer.stopRender(ctx)
    return visibleFeatures

You can also slightly change the function to make it a generator, in case you need to work with a large set of data:

def getVisibleSelectedFeatures(layer):
    renderer = layer.renderer().clone()
    ctx = QgsRenderContext()
    renderer.startRender(ctx, QgsFields())
    for feature in layer.selectedFeatures():
        ctx.expressionContext().setFeature(feature)
        if renderer.willRenderFeature(feature, ctx):
            yield feature
    renderer.stopRender(ctx)

Usage:

for feature in getVisibleSelectedFeatures(iface.activeLayer()):
   # Do something with the feature
0
5

I can't find a simple way of doing what you ask but the following code seems to do the job by looking at the renderer of the active layer, looping through the categories, checking for any where the render state is false and adds the render category value to a list. It loops through the selected layers and tests the attribute used in the categorized symbology to see if it appears on the list, if not it adds the feature to the visibleSelectedFeatures list. The code runs from the python window in QGIS, tested using 3.4:

iface.activeLayer().selectByRect(extent)
selectedFeaturesList = list(iface.activeLayer().selectedFeatures())
visibleSelectedFeatures = []
layer = iface.activeLayer()
renderer = layer.renderer()
invisible = list()
for rc in renderer.categories():
    if rc.renderState() is False:
        invisible.append(rc.value())

columnindex=iface.activeLayer().fields().indexFromName(renderer.classAttribute())
for feat in selectedFeaturesList:
    attrs = feat.attributes()
    if attrs[columnindex] not in invisible:
        visibleSelectedFeatures.append(feat)
0

Your Answer

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

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