4

I have hundreds of rules specifying ranges of polygon areas. I would like to create a table of the ranges and their values outside of QGIS and since these results are exactly what I am after I would like to export them directly from the layer properties here. I know there are tools like statistics by category which are useful but it would be far easier and quicker if I could simply use these count results directly.

enter image description here

Kadir Şahbaz
  • 76,800
  • 56
  • 247
  • 389
Funzo
  • 71
  • 4

2 Answers2

4

You can access the filter expression of each rule with this script. Copy-paste it to the script editor of the Python console and click Run

Make sure the layer is selected in the Layers Panel, and that it is not in edit mode, else the filter will not be applied.

# get layer
lyr = iface.activeLayer()

get the rule-based rendered from the layer

r = lyr.renderer()

get the root rule

root = r.rootRule()

iterate through rules

for rule in root.children(): # get the rule filter expression exp = rule.filterExpression()

# set the rule filter on the layer
lyr.setSubsetString(exp)

# count filtered features
count = lyr.featureCount()

# reset the filter
lyr.setSubsetString('')

print(f'rule: {exp} | count: {count}')

## do something with rules (save to text file)

Output:

rule: "code" = 'A' | count: 12425
rule: "code" = 'B' | count: 12580
rule: "code" = 'C' | count: 12298
rule: "code" = 'D' | count: 12618
rule: "code" = 'E' | count: 12534
rule: "code" = 'F' | count: 12441
Matt
  • 16,843
  • 3
  • 21
  • 52
4

To elaborate on my other answer, and with the help of this excellent answer by Ben W, I came up with a script to save the rules, counts, and symbology to an Excel spreadsheet.

It relies on one non-native library, XlsxWriter. The easiest way to install it is to open the OsGeo4W shell (via the Start Menu on Windows) and use:

pip install XlsxWriter

enter image description here

Then run the script in the Script Editor in the QGIS Python console.

import os
import xlsxwriter
from io import BytesIO

path to save images

IMG_PATH = r'D:\OneDrive\geodata\symbols'

path to save the spreadsheet

EXPORT_PATH = r'D:\OneDrive\geodata\rules.xlsx'

workbook = xlsxwriter.Workbook(EXPORT_PATH) worksheet = workbook.add_worksheet()

def convert_icon(icon, size=None, fmt='PNG'): ''' This function modified from: https://stackoverflow.com/questions/40789856/python-convert-qt-qicon-into-jpeg Credit: stackoverflow member ekhumoro ''' if size is None: size = 18 pixmap = QPixmap.fromImage(icon) pixmap = pixmap.scaledToWidth(size)

array = QtCore.QByteArray()
buffer = QtCore.QBuffer(array)
buffer.open(QtCore.QIODevice.WriteOnly)
pixmap.save(buffer, fmt)
buffer.close()
return array.data()

write the spreadsheet headers

worksheet.write('A1', 'symbol') worksheet.write('B1', 'label') worksheet.write('C1', 'rule') worksheet.write('D1', 'count')

get layer

lyr = iface.activeLayer()

get the rule-based rendered from the layer

r = lyr.renderer()

get the root rule

root = r.rootRule()

set the spreadsheet row to enter data

sheet_row = 2

iterate through rules

for rule in root.children():

# get various attributes from rule
sym = rule.symbol()
lab = rule.label()
exp = rule.filterExpression()

# set the rule filter on the layer
lyr.setSubsetString(exp)
# count filtered features
count = lyr.featureCount()
# reset the filter
lyr.setSubsetString('')

# get rule symbol as image
img = sym.asImage(QSize(40,40))

# optionally save symbols as png
img.save(
    os.path.join(
        IMG_PATH, 
        (exp.replace('<=', 'lte')
            .replace('>=', 'gte')
            .replace('<>', 'not')
            .replace('<', 'lt')
            .replace('>', 'gt')
            .replace('=', 'eq')                
            .replace('.', '_')
            .replace('"','')
            .replace("'", '') + '.png')
    )
)

# convert QImage to QByteArray
img_data = convert_icon(img)

# convert to BytesIO object
io_data = BytesIO(img_data) 

# assign cells for data
icon_cell  = f'A{sheet_row}'
label_cell = f'B{sheet_row}'
rule_cell  = f'C{sheet_row}'
count_cell = f'D{sheet_row}'

# insert symbol and corresponding data into cells
worksheet.insert_image(icon_cell, 'image.png', {'image_data': io_data, 'x_offset': 15, 'y_offset': 3})
worksheet.write(label_cell, lab)
worksheet.write(rule_cell, exp)
worksheet.write(count_cell, count)

# set the width of specific columns to accommodate data
worksheet.set_column('B:B', 20)  
worksheet.set_column('C:C', 40)  

# go to next row of spreadsheet
sheet_row+=1

commit the changes to the workbook

workbook.close()

The spreadsheet:

enter image description here

The saved image files:

enter image description here

Matt
  • 16,843
  • 3
  • 21
  • 52
  • Thanks Matt, this looks fantastic. Unfortunately I'm having trouble running the script. I seem to have successfully installed xlsxwriter (ran pip.main(['install','XlsxWriter']) in the python console and output said successfully installed), then when I run the script it fails at the beginning with ModuleNotFoundError: No module named 'xlsxwriter' (I can't paste the output here as it goes over character limit). – Funzo Feb 20 '22 at 23:13
  • Ben details a few ways to install the module in his answer. I found the easiest way was through the OsGeo4W shell (see update to my answer). Is it a requirement that it must be installed via the script? – Matt Feb 20 '22 at 23:29
  • I tried first through OsGeo4W shell as Ben writes but doing that I got the error msg it is not recognized as an internal or external command, etc.

    I looked for other ways and found that the command python -m pip install XlsxWriter worked.

    I don't know if it has to be installed via a script, it would be good if there was perhaps another way (I haven't so far found one).

    – Funzo Feb 21 '22 at 00:02
  • Unfortunately, I cannot reproduce your issue, as using OsGeo4W shell works fine for me. I just tried with another library (flickrapi) and I can import it in the QGIS Python console directly after pip install flickrapi in the OsGeo4W shell. – Matt Feb 21 '22 at 11:41
  • Thanks Matt, got it up and running! What if there was another level of rules and counts within the rules ie. children of children, could that be shown as well? – Funzo Feb 23 '22 at 01:44