QGIS – Update Rule-Based Symbology

qgisrule-basedsymbology

Goal: be able to update ruled based symbology

Data (layer attribute table): a column with the ID of the individual, a column with the date of identification (Year-month), a column with the name of the origin of this individual, and of course the coordinates.

I made a rule-based symbology in order to be able to check/uncheck the features easily by month, and by origin -> this allows me to check for example a month that interests me and select only the individuals from such or such origin. (as you can see in this example:)

enter image description here

Problem: It works very well but the problem is that I have new data every week with new months and new origins and I don't know how to update this symbology without having to start over each time. It is easy to update categorized symbology for example but I couldn't find a solution for the rule-based category.

Does anyone know a way to easily update the rule-based category?

Best Answer

Based on multiple posts like here and here, here's a way to automate rule-based symbology creation, making sure it handes all unique values from two property fields from a given Vector layer (Date and Origin in your case).

This works from the Python console on the active layer:

  • It creates an empty Rule based renderer.
  • Selects all Origins as possible categories in a CategoryRenderer
  • Goes through all available Dates, adds a rule for data matching a given date and refines the rule with the above mentioned categories

I tested this code (with other field names) and it works. Some extra python code might be needed if your dates are not already in a YYYY-MM format. Also, for each date, all categories will be listed not only the ones that belond to that date range. This can be improved as well by reworking the script

from random import randrange
layer = iface.activeLayer()

symbol = QgsSymbol.defaultSymbol(layer.geometryType())
rb_renderer = QgsRuleBasedRenderer(symbol)
root_rule = rb_renderer.rootRule()

rule = root_rule.children()[0].clone()


dateindex = layer.dataProvider().fieldNameIndex("date_field")
unique_values_date = layer.uniqueValues(dateindex)


originindex = layer.dataProvider().fieldNameIndex("origin")
unique_values_origin = layer.uniqueValues(originindex)

categories = []
for unique_value in unique_values_origin:
    # initialize the default symbol for this geometry type
    symbol = QgsSymbol.defaultSymbol(layer.geometryType())

    # configure a symbol layer
    layer_style = {}
    layer_style['color'] = '%d, %d, %d' % (randrange(0, 256), randrange(0, 256), randrange(0, 256))
    layer_style['outline'] = '#000000'
    symbol_layer = QgsSimpleFillSymbolLayer.create(layer_style)

    # replace default symbol layer with the configured one
    if symbol_layer is not None:
        symbol.changeSymbolLayer(0, symbol_layer)

    # create renderer object
    category = QgsRendererCategory(unique_value, symbol, str(unique_value))
    # entry for the list of category items
    categories.append(category)

# create renderer object
renderercat = QgsCategorizedSymbolRenderer('origin', categories)
print(renderercat)
dates = []
for unique_date in unique_values_date:
    rule = root_rule.children()[0].clone()
    rule.setLabel(str(unique_date))
    rule.setFilterExpression(f""""date_field"='{unique_date}'""")
    QgsRuleBasedRenderer.refineRuleCategories(rule,renderercat)
    root_rule.appendChild(rule)

print(root_rule)
#QgsRuleBasedRenderer.refineRuleCategories(root_rule,renderercat)
print(rb_renderer)
# assign the created renderer to the layer
if rb_renderer is not None:
    layer.setRenderer(rb_renderer)

layer.triggerRepaint()

With this sample data: enter image description here

here's what you get after running the script:

enter image description here

Related Question