1

I'm trying to understand the QGIS Python API documentation - as an example I want to get the number of features with a specified legend key, but I don't get the correct result.

Under the layer variable I have a vector layer. To get a number of all features in the layer I call layer.featureCount() and I get the correct number.

Everything is fine, but now the fun starts when I want to fill in the legendKey parameter. The docs say that featureCount gets a legendKey parameter which is a string. So I try to pass my legendKey as a string:

layer.featureCount('myvalue')

which gives me -1. But in the layer, I have one feature whose value is equal to 'myvalue'.

Maybe that's not how I have to pass the string legendKey argument. So I try:

layer.featureCount(' "my_field" = \'myvalue\' ')

as maybe it will be the correct string to pass, but it still gives me -1.

I read the docs and it says:

Features must be first calculated by countSymbolFeatures()

Ok, so I calculate features with this method:

ftCounter = layer.countSymbolFeatures()

it returns QgsVectorLayerFeatureCounter. This class also has featureCount() method. So I try to call this method on the returned feature counter object:

ftCounter.featureCount()

and it gives me AttributeError:

'NoneType' object has no attribute 'featureCount'.

It turns out that under ftCounter I have nothing, None. The docs say it creates a new feature counter for the layer.

Maybe I have to create a feature counter by passing another argument as the docs of this class specify it - pass layer and context. After googling how to create exp. context I create smth like this:

exp = QgsExpression('"my_field"  =  \'myvalue\'')
context = QgsExpressionContext()
context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(layer))
ftCounter = QgsVectorLayerFeatureCounter(layer, context)
ftCounter.featureCount()

and I get TypeError:

QgsVectorLayerFeatureCounter.featureCount()`: not enough arguments.

So maybe now I have to pass this required string legendKey argument:

ftCounter.featureCount('"my_field"  =  \'muvalue\'')

Nope, it gave me -1.

Maybe I misunderstood the legendKey string parameter at the beginning but my core question is how to learn from QGIS Python API as it turns out to be one big maze and the only way to understand it is to copy&paste somebody's else working code as a solution. But as in my above example even with some googling I still don't get it.

1
  • 1
    You have two question marks in the title, and a bunch more in the Question body, which violates the One question per Question policy. Please Edit the Question to contain a single focused question.
    – Vince
    Oct 15, 2023 at 14:01

2 Answers 2

2

The legendKey string parameter is not a value in a field, it is a unique identifier to identify the legend item within the feature renderer.

So you need to first retrieve the legend keys used by the renderer. Since QGIS version 3.32, you can use the legendKeys() method of QgsFeatureRenderer(). It returns a set which you can cast to a list().

lyr_name = 'Your_Layer_Name'    
lyr = QgsProject.instance().mapLayersByName(lyr_name)[0]
renderer = lyr.renderer()
l_keys = list(renderer.legendKeys())

In older versions, you can do something like:

lyr_name = 'Your_Layer_Name'
lyr = QgsProject.instance().mapLayersByName(lyr_name)[0]
renderer = lyr.renderer()
sym_items = renderer.legendSymbolItems()
for i in sym_items:
    print(i.ruleKey())

As for countSymbolFeatures() method, the docs state:

Note: If the count features for symbols has been already done a nullptr is returned. If you need to wait for the results, you can call waitForFinished() on the feature counter.

That is why you can get the None type in your ftCounter variable.

You just need to check the return value, and if it is not None, call waitForFinished() on it.

Then you can do your feature counts for each legend key. Since QGIS 3.26, QgsFeatureRenderer() also has a legendKeyToExpression() method which attempts to convert the legend key to an expression.

lyr_name = 'Your_Layer_Name'

lyr = QgsProject.instance().mapLayersByName(lyr_name)[0]
renderer = lyr.renderer()
l_keys = list(renderer.legendKeys())

cntr = lyr.countSymbolFeatures()

if cntr:
    cntr.waitForFinished()

for k in l_keys:
    print(f'Legend key: {k}')
    # line below won't work in versions < 3.26
    print(f'Legend key expression: {renderer.legendKeyToExpression(k, lyr)}')
    print(f'Feature count for legend key: {lyr.featureCount(k)}')

The other way to deal with the countSymbolFeatures() result is to connect a slot to the QgsVectorLayer::symbolFeatureCountMapChanged() signal:

lyr = iface.activeLayer()
renderer = lyr.renderer().clone()
l_keys = list(renderer.legendKeys())

def getFeatureCount():
    # disconnect slot from signal so we don't end up with multiple connections
    lyr.symbolFeatureCountMapChanged.disconnect(getFeatureCount)
    for k in l_keys:
        print(f'Legend key: {k}')
        print(f'Legend key expression: {renderer.legendKeyToExpression(k, lyr)}')
        print(f'Feature count for legend key: {lyr.featureCount(k)}')
        
lyr.symbolFeatureCountMapChanged.connect(getFeatureCount)

# countSymbolFeatures() returns nullptr (None in Python) if the count has already been done
# so we check the return value and if None is returned, we call our getFeatureCount() method
# because in this case, the symbolFeatureCountMapChanged signal won't be fired
if not lyr.countSymbolFeatures():
    getFeatureCount()

On the other hand, if you want to count features based on a field value, you can just do:

lyr_name = 'Your_Layer_Name'
lyr = QgsProject.instance().mapLayersByName(lyr_name)[0]
print(len([ft for ft in lyr.getFeatures('"my_field"  LIKE  \'muvalue\'')]))

As for understanding the API, I'm afraid that I don't think you will find an easy shortcut to magically understand it. Like anything that has levels of complexity, it takes time, effort, practice and trial & error. The only advice I can give is to try to find working code to study such as Python plugins.

The unit tests can also be a handy source of working code examples. Some are written in Python, but mostly in C++, which brings me to another suggestion- work on studying the C++ API which will allow you to better understand not only the source code itself but also the Python API.

0
1

I use QGIS 3.28 so I don't have the legendKeys() method available. I've used legendSymbolItems() instead. And it turned out I don't need countSymbolFeatures() anyway. QGIS API docs cheated me.

As I used legendSymbolItems() I needed to change k to a string inside for loop, so I used the label() method.

lyr_name = 'Your_Layer_Name'
lyr = QgsProject.instance().mapLayersByName(lyr_name)[0] 
renderer = lyr.renderer() 
l_keys = list(renderer.legendSymbolItems())

for k in l_keys:
    print(f'Legend key: {k.label()}')
    # line below won't work in versions < 3.26
    print(f'Legend key expression: {renderer.legendKeyToExpression(k.label(), lyr)}')
    print(f'Feature count for legend key: {lyr.featureCount(k.label())}')

The result still is -1. legendKeyToExpression() returns False:

Legend key: a
Legend key expression: ('', False)
Feature count for legend key: -1

As I understand, legendKeyToExpression() and legenKey() require string argument, but it's not the name of the category, or in other words, not the value of an attribute I used to style my layers using unique symbols, is it? Why this code is probably working when legendKeys() is used which returns a set of strings, but is not working when I use legendSymbolItems() and label() I have no idea.

What I most wanted to get an answer to was how to use and understand the QGIS API. I didn't expect that the first example I gave - featureCount() - would be so problematic.

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.