Symbology of vector layers in QGIS Python Plugins

Posted on

The symbology of a layer describes how the layer’s data should be displayed on the map canvas. A layers symbology, or style, is composed of two things: a symbol (or multiple symbols) and a renderer.

The layer’s renderer decides which features should be drawn using which symbols. The renderer can be a simple thing, giving all features the same symbol, or something more complex, giving each feature a different symbol depending on its attributes. The renderer also assigns labels to symbols for use in the legend.

A symbol describes how an individual feature is drawn. In QGIS, a symbol is a container for symbol layers. Each symbol layer contributes to the overall appearance of a symbol. For example, a polygon with a grey fill and red hatched lines is created using two layers: a grey fill layer and a red line pattern layer.

This post describes how to work with layer symbologies in QGIS using Python.

The hierarchy of objects describing vector layer symbology is as follows:

  • Layer, QgsVectorLayer
    • Renderer, e.g. QgsSingleSymbolRendererV2
      • Symbols, e.g. QgsMarkerSymbolV2
        • Symbol layers, e.g. QgsSimpleMarkerSymbolLayerV2

The QGIS cookbook has a section of vector layer symbology, which this post is partly based on.

The QGIS API reference is also useful when working with symbology.

Colors

QGIS uses the standard QtGui.QColor object for representing colors. See the QColor class reference for more information.

A new QColor object can be created using a color name (e.g. magenta), a color hex value (e.g. #ff00ff) or RGB values (e.g. 255, 0, 255):

# initalise an empty color object
color = QtGui.QColor()

# initalise a color object by color name
color = QtGui.QColor('red')

# initalise a color object by RGB values
color = QtGui.QColor(255, 0, 0)

# initalise a color object by hex value
color = QtGui.QColor('#ff0000')

The value of an existing QColor object can be retrieved as RGB values or as a hex value.

# get individual RGB values
red, green, blue = color.red(), color.green(), color.blue()

# get color name (e.g. '#ff00ff')
name = color.name()

Similarily the value of an existing QColor obejct can be modified using a color name, a color hex value , or RGB values:

# set color value by name
color.setNamedColor('magenta')

# set color value by rgb hex
color.setNamedColor('#ff00ff')

# set color value by rgb values
color.setRgb(255, 0, 255)

# set color value by individual rgb values
color.setRed(255)
color.setGreen(0)
color.setBlue(0)

Symbols and symbol layers

There are two ways to create a symbol layer: initalise it with a dictionary of properties, or create a default symbol layer and modify it using methods. Both approaches have their own benefits and drawbacks, but you can mix and match them to work around this, i.e. initalise a symbol layer using properties, then modify it later using methods.

At the time of writing not all of the C++ object methods have been made accessible via Python. This means that some features of a symbol layer are only accessible via the properties dict approach.

The best way to find the properties you need for a particular symbol is to explore! Create the symbol in the QGIS GUI, then investigate the different properties it sets via the Python console.

Creating symbol layers using a properties dictionary

To initalise a new symbol layer with a dict of properties call the create method with a dictionary as the only argument (see below). All of the elements in the dictionary (both keys and values) should be strings.

# define the layer properties as a dict
properties = {'size': '5.0', 'color': '255,0,0,255'}

# initalise a new symbol layer with those properties
symbol_layer = QgsSimpleMarkerSymbolLayerV2.create(properties)

# replace the default symbol layer with the new symbol layer
layer.rendererV2().symbols()[0].changeSymbolLayer(0, symbol_layer)

The properties can be accessed later using the properties dict:

# get the symbol layer
symbol_layer = layer.rendererV2().symbols()[0].symbolLayer(0)

# get the properties of the symbol layer
properties = symbol_layer.properties()

print 'Size:', properties['size']
print 'Color:', properties['color']

Creating a default symbol layer then modifying it

To create a new symbol layer initalise it as you would any other class:

# create a new symbol layer with default properties
symbol_layer = QgsSimpleMarkerSymbolLayerV2()

# set the size and color using methods
symbol_layer.setSize(5.0)
symbol_layer.setColor(QColor(255,0,0,255))

# replace the default symbol layer with the new symbol layer
layer.rendererV2().symbols()[0].changeSymbolLayer(0, symbol_layer)

The properties can be accessed later using methods:

# get the symbol layer
symbol_layer = layer.rendererV2().symbols()[0].symbolLayer(0)

# get the properties of the symbol layer
print 'Size:', symbol_layer.size()
print 'Color:', symbol_layer.color().name()

Point symbols

  • Simple marker symbol, QgsSimpleMarkerSymbolLayerV2
  • Ellipse symbol, QgsEllipseSymbolLayerV2
  • Font symbol, QgsFontMarkerSymbolLayerV2
  • SVG marker, QgsSvgMarkerSymbolLayerV2
  • Vector field marker, QgsVectorFieldSymbolLayer

Line symbols

  • Simple line, QgsSimpleLineSymbolLayerV2
  • Marker line, QgsMarkerLineSymbolLayerV2

Polygon symbols

  • Simple fill, QgsSimpleFillSymbolLayerV2
  • Gradient fill, QgsFillSymbolLayerV2
  • Centroid fill, QgsCentroidFillSymbolLayerV2
  • Line pattern fill, QgsLinePatternFillSymbolLayer
  • Point pattern fill, QgsPointPatternFillSymbolLayer
  • SVG fill, QgsSVGFillSymbolLayer
  • Outline: marker line, QgsMarkerLineSymbolLayerV2
  • Outline: simple line, QgsSimpleLineSymbolLayerV2

Multi layered symbols

Symbols can be composed of multiple symbol layers to create complex effects. The example below shows a gradient fill layer (blue to green) with two line fill layers (grey and black).

The number of symbol layers in a symbol can be accessed using:

layer_count = symbol.symbolLayerCount()

Symbol layers are indexed in the order they are drawn. For example, if a symbol has two layers the 0th layer will be drawn first, then the 1st layer will be drawn on top.

A symbol layer can be appended to the top of the stack using appendSymbolLayer:

symbol.appendSymbolLayer(symbol_layer)

To insert a symbol layer at any other level in the stack use insertSymbolLayer. The first argument is the index the new layer will be inserted before. For example, the code below will insert the new symbol layer at the bottom of the stack.

symbol.insertSymbolLayer(0, symbol_layer)

To remove a symbol layer from the stack use deleteSymbolLayer:

symbol.deleteSymbolLayer(0)

Note than a symbol must always contain at least one symbol layer.

Custom symbols

See the section in the QGIS cookbook.

Transparency (alpha)

Transparency can be set for an entire layer:

# get the transparency of a layer (0 = completely opaque, 100 = completely transparent)
transparency = layer.layerTransparency()

# set the transparency of a layer
layer.setLayerTransparency(42)

Alternatively, transparency can be set on a per-symbol basis:

# get the transparency of a symbol (1 = completely opaque, 0 = completely transparent)
transparency = symbol.alpha()

# set the transparency of a symbol
symbol.setAlpha(0.56)

Layer blending

Layer blending modes can be accessed via the blendMode and setBlendMode methods. The functions requires that you use the composition mode constants from the QPainter object which are explained in the Qt documentation.

from PyQt4.QtGui import QPainter

# get the current blend mode
blend_mode = layer.blendMode()

# set the blend mode to multiply
layer.setBlendMode(QPainter.CompositionMode_Multiply)

Renderers

To access the existing renderer of a layer:

renderer = layer.rendererV2()

The type of the renderer can be accessed via the type method:

renderer_type = renderer.type()

To get a list of the available renderer types use the QgsRendererV2Registry.

renderer_types = QgsRendererV2Registry().renderersList()

The table below shows the renderer types available by default.

NameTypeClass
Single symbol renderersingleSymbolQgsSingleSymbolRendererV2
Categorized symbol renderercategorizedSymbolQgsCategorizedSymbolRendererV2
Graduated symbol renderergraduatedSymbolQgsGraduatedSymbolRendererV2
Rule based rendererRuleRendererQgsRuleBasedRendererV2
Point displacement rendererpointDisplacementQgsFeatureRendererV2

In addition to these defaults there may be other custom renderers registered by plugins.

To create a new renderer and assign it to a layer use the setRendererV2 method. New renderers must be initalised with a symbol, e.g. QgsMarkerSymbolV2. The following sections describe how to use each of the renderer types.

Single symbol renderer

The single symbol renderer is the simplest renderer. It assigns the same symbol to all of the features in a layer.

# create a new single symbol renderer
symbol = QgsSymbolV2.defaultSymbol(layer.geometryType())
renderer = QgsSingleSymbolRendererV2(symbol)

# create a new simple marker symbol layer, a green circle with a black border
properties = {'color': 'green', 'color_border': 'black'}
symbol_layer = QgsSimpleMarkerSymbolLayerV2.create(properties)

# assign the symbol layer to the symbol
renderer.symbols()[0].changeSymbolLayer(0, symbol_layer)

# assign the renderer to the layer
layer.setRendererV2(renderer)

Categorized symbol renderer

The categorized symbol renderer styles features based on unique values in the layer’s attribute table.

The example below demonstrates how to create a categorized symbol renderer. Each type of animal is given a different color and label. For example, features with ‘cat’ in the ‘animal’ field are colored red and labelled ‘Small cat’ in the legend. To match any features not matched by another category an empty string is used for the value.

# define a lookup: value -> (color, label)
animals = {
    'cat': ('#f00', 'Small cat'),
    'dog': ('#0f0', 'Big dog'),
    'sheep': ('#fff', 'Fluffy sheep'),
    '': ('#000', 'Unknown'),
}

# create a category for each item in animals
categories = []
for animal_name, (color, label) in animals.items():
    symbol = QgsSymbolV2.defaultSymbol(layer.geometryType())
    symbol.setColor(QColor(color))
    category = QgsRendererCategoryV2(animal_name, symbol, label)
    categories.append(category)

# create the renderer and assign it to a layer
expression = 'animal' # field name
renderer = QgsCategorizedSymbolRendererV2(expression, categories)
layer.setRendererV2(renderer)

The first argument to the categorized symbol renderer is an expression (in the form a string). The expression can be a single field name (as an the example above), or something more complex as demonstrated below:

# categorize features based on their "animal" and "size" attributes
expression = '''concat("animal", '_', "size")'''

To get a list of unique values for a given field for use in a categorized symbol renderer use the uniqueValues method. The example below searches the ‘animal’ field the first 1000 features for unique values. The default value of limit is -1, which will search all features in a layer.

field_index = layer.dataProvider().fieldNameIndex('animal')
unique_values = layer.uniqueValues(field_index, limit=1000)

The categories of an existing layer can be accessed using the categories method. The example below prints the value, label and color name of each category in a layer.

categories = layer.rendererV2().categories()
for category in categories:
    print category.value(), category.label(), category.symbol().color().name()

The value and label of an existing category can be modified using the setValue and setLabel methods.

Graduated symbol renderer

The graduated symbol renderer styles features based on values in the layer’s attribute table. Unlike the categorized symbol renderer, the graduated symbol renderer defines a range of numerical values defined by upper and lower limits which constitute a ‘match’. For example, a value of 3.1 would be matched by a range with an upper limit of 4 and a lower limit of 2.

The example below defines four ranges for the price of coffee (free, cheap, average and expensive) and creates a graduated symbol renderer to style a layer based on these ranges and the ‘cost’ field of each feature.

# define ranges: label, lower value, upper value, color name
coffee_prices = (
    ('Free', 0.0, 0.0, 'green'),
    ('Cheap', 0.0, 1.5, 'yellow'),
    ('Average', 1.5, 2.5, 'orange'),
    ('Expensive', 2.5, 999.0, 'red'),
)

# create a category for each item in animals
ranges = []
for label, lower, upper, color in coffee_prices:
    symbol = QgsSymbolV2.defaultSymbol(layer.geometryType())
    symbol.setColor(QColor(color))
    rng = QgsRendererRangeV2(lower, upper, symbol, label)
    ranges.append(rng)

# create the renderer and assign it to a layer
expression = 'cost' # field name
renderer = QgsGraduatedSymbolRendererV2(expression, ranges)
layer.setRendererV2(renderer)

As with the categorized symbol renderer, the expression can be more than just a field name. The example below converts costs in from USD to GBP before calculating which range the value belongs to:

# 1 USD = 0.60 GBP on 2014-03-03
expression = '"cost" * 0.60'

The ranges of an existing layer can be accessed using the ranges method. The example below prints the upper and lower values of a range, as well as the range label and symbol color name for each range in a layer. Note the use of the rng variable, which avoids overriding the Python range function.

ranges = layer.rendererV2().ranges()
for rng in ranges:
    print rng.lowerValue(), rng.upperValue(), rng.label(), rng.symbol().color().name()

Python string formatting can be used to make setting labels easier. The example below creates a label showing dollars to two decimal places with a thousand separator based on the upper and lower value of an existing range.

label = '$ {:,.2f} - {:,.2f}'.format(rng.lowerValue(), rng.upperValue())
rng.setLabel(rng)

Rule-based renderer

The rule-based renderer styles features using a list of rules, defined by expressions. The rules are processed sequentially, with the first matching rule used to render a feature.

The example below defines a new rule-based renderer for different kinds of road features. Major roads are shown in orange, minor roads are shown in black at scales larger than 1:2,500 and residential roads are shown in grey at scales between 1:100 and 1:1,000.

# define some rules: label, expression, color name, (min scale, max scale)
road_rules = (
    ('Major road', '"type" LIKE \'major\'', 'orange', None),
    ('Minor road', '"type" LIKE \'minor\'', 'black', (0.0, 2500.0,)),
    ('Residential road', '"type" LIKE \'residential\'', 'grey', (100.0, 1000.0,)),
)

# create a new rule-based renderer
symbol = QgsSymbolV2.defaultSymbol(layer.geometryType())
renderer = QgsRuleBasedRendererV2(symbol)

# get the "root" rule
root_rule = renderer.rootRule()

for label, expression, color_name, scale in road_rules:
    # create a clone (i.e. a copy) of the default rule
    rule = root_rule.children()[0].clone()
    # set the label, expression and color
    rule.setLabel(label)
    rule.setFilterExpression(expression)
    rule.symbol().setColor(QColor(color_name))
    # set the scale limits if they have been specified
    if scale is not None:
        rule.setScaleMinDenom(scale[0])
        rule.setScaleMaxDenom(scale[1])
    # append the rule to the list of rules
    root_rule.appendChild(rule)

# delete the default rule
root_rule.removeChildAt(0)

# apply the renderer to the layer
layer.setRendererV2(renderer)

Custom renderers

See the section in the QGIS cookbook.

Misc.

This section details various hints and tips for working with symbologies not covered elsewhere.

Updating the legend symbology

When the symbology of a layer is changed the changes are not automatically propagated to the legend. The code below shows how to refresh the symbology for a layer in the legend, where iface is an instance of QgisInterface.

iface.legendInterface().refreshLayerSymbology(layer)

Copying symbology from one layer to another

Copying the symbology from one layer to another is as simple as getting the renderer from one layer and setting it for the other.

renderer = layer1.rendererV2()
layer2.setRenderer(renderer)

Importing/Exporting symbologies

Importing and exporting layer symbology to/from a QML file is as simple as:

# save layer symbology to a QML file
layer.saveNamedStyle('/path/to/style.qml')

# load layer symbology from an existing QML file
layer.loadNamedStyle('/path/to/style.qml')

Data defined properties

Data defined properties were introduced in QGIS 2.0. The properties of a symbol layer, such as size or rotation, can be defined on a per-feature basis using an expression.

To set the expression used for a data defined property, use the setDataDefinedProperty method. The example below sets the ‘size’ property equal to the ‘magnitude’ attribute + 2.

symbol_layer.setDataDefinedProperty('size', '"magnitude" + 2')

The value of an existing property can be retrieved with:

symbol_layer.dataDefinedProperty('size')

A data defined property can be removed with:

symbol_layer.removeDataDefinedProperty('size')

The names of available properties can be found using the properties method of an existing symbol layer:

symbol_layer.properties()

Data defined properties will show in the properties dict with the _expression prefix.

Symbols can be initalised with data defined properties:

symbol_layer = QgsSimpleMarkerSymbolLayerV2.create({'size_expression': '"magnitude"'})

Default symbols

The default symbol for any geometry type can be retrieved using QgsSymbolV2.defaultSymbol. This is useful if you want to write something to style a layer that will work with any geometry type (points, lines or polygons).

symbol = QgsSymbolV2.defaultSymbol(layer.geometryType())

Symbol previews

The preview of a symbol as shown in the style dialog is available as a QImage via the bigSymbolPreviewImage method.

image = symbol.bigSymbolPreviewImage()
image.save('symbol.png')

Crashes

When working with symbology you must be careful not to use objects whos parent has been destroyed; doing so will cause QGIS to crash.