22 min read

In this article by Erik Westra, author of the book Building Mapping Applications with QGIS, we will learn how QGIS symbols and renderers are used to control how vector features are displayed on a map. In addition to this, we will also learn saw how symbol layers work. The features within a vector map layer are displayed using a combination of renderer and symbol objects. The renderer chooses which symbol is to be used for a given feature, and the symbol does the actual drawing.

There are three basic types of symbols defined by QGIS:

  • Marker symbol: This displays a point as a filled circle
  • Line symbol: This draws a line using a given line width and color
  • Fill symbol: This draws the interior of a polygon with a given color

These three types of symbols are implemented as subclasses of the qgis.core.QgsSymbolV2 class:

  • qgis.core.QgsMarkerSymbolV2
  • qgis.core.QgsLineSymbolV2
  • qgis.core.QgsFillSymbolV2

You might be wondering why all these classes have “V2” in their name. This is a historical quirk of QGIS. Earlier versions of QGIS supported both an “old” and a “new” system of rendering, and the “V2” naming refers to the new rendering system. The old rendering system no longer exists, but the “V2” naming continues to maintain backward compatibility with existing code.

Internally, symbols are rather complex, using “symbol layers” to draw multiple elements on top of each other. In most cases, however, you can make use of the “simple” version of the symbol. This makes it easier to create a new symbol without having to deal with the internal complexity of symbol layers. For example:

symbol = QgsMarkerSymbolV2.createSimple({'width' : 1.0,
                                        'color' : "255,0,0"})

While symbols draw the features onto the map, a renderer is used to choose which symbol to use to draw a particular feature. In the simplest case, the same symbol is used for every feature within a layer. This is called a single symbol renderer, and is represented by the qgis.core.QgsSingleSymbolRenderV2class. Other possibilities include:

  • Categorized symbol renderer (qgis.core.QgsCategorizedSymbolRendererV2): This renderer chooses a symbol based on the value of an attribute. The categorized symbol renderer has a mapping from attribute values to symbols.
  • Graduated symbol renderer (qgis.core.QgsGraduatedSymbolRendererV2): This type of renderer has a series of ranges of attribute values, and maps each range to an appropriate symbol.

Using a single symbol renderer is very straightforward:

symbol = ...
renderer = QgsSingleSymbolRendererV2(symbol)
layer.setRendererV2(renderer)

To use a categorized symbol renderer, you first define a list of qgis.core.QgsRendererCategoryV2 objects, and then use that to create the renderer. For example:

symbol_male = ...
symbol_female = ...
 
categories = []
categories.append(QgsRendererCategoryV2("M", symbol_male, "Male"))
categories.append(QgsRendererCategoryV2("F", symbol_female,
                                       "Female"))
 
renderer = QgsCategorizedSymbolRendererV2("", categories)
renderer.setClassAttribute("GENDER")
layer.setRendererV2(renderer)

Notice that the QgsRendererCategoryV2 constructor takes three parameters: the desired value, the symbol to use, and the label used to describe that category.

Finally, to use a graduated symbol renderer, you define a list of qgis.core.QgsRendererRangeV2 objects and then use that to create your renderer. For example:

symbol1 = ...
symbol2 = ...
 
ranges = []
ranges.append(QgsRendererRangeV2(0, 10, symbol1, "Range 1"))
ranges.append(QgsRendererRange(11, 20, symbol2, "Range 2"))
 
renderer = QgsGraduatedSymbolRendererV2("", ranges)
renderer.setClassAttribute("FIELD")
layer.setRendererV2(renderer)

Working with symbol layers

Internally, symbols consist of one or more symbol layers that are displayed one on top of the other to draw the vector feature:

Building Mapping Applications with QGIS

The symbol layers are drawn in the order in which they are added to the symbol. So, in this example, Symbol Layer 1 will be drawn before Symbol Layer 2. This has the effect of drawing the second symbol layer on top of the first. Make sure you get the order of your symbol layers correct, or you may find a symbol layer completely obscured by another layer.

While the symbols we have been working with so far have had only one layer, there are some clever tricks you can perform with multilayer symbols.

When you create a symbol, it will automatically be initialized with a default symbol layer. For example, a line symbol (an instance of QgsLineSymbolV2) will be created with a single layer of type QgsSimpleLineSymbolLayerV2. This layer is used to draw the line feature onto the map.

To work with symbol layers, you need to remove this default layer and replace it with your own symbol layer or layers. For example:

symbol = QgsSymbolV2.defaultSymbol(layer.geometryType())
symbol.deleteSymbolLayer(0) # Remove default symbol layer.
 
symbol_layer_1 = QgsSimpleFillSymbolLayerV2()
symbol_layer_1.setFillColor(QColor("yellow"))
 
symbol_layer_2 = QgsLinePatternFillSymbolLayer()
symbol_layer_2.setLineAngle(30)
symbol_layer_2.setDistance(2.0)
symbol_layer_2.setLineWidth(0.5)
symbol_layer_2.setColor(QColor("green"))
 
symbol.appendSymbolLayer(symbol_layer_1)
symbol.appendSymbolLayer(symbol_layer_2)

The following methods can be used to manipulate the layers within a symbol:

  • symbol.symbolLayerCount(): This returns the number of symbol layers within this symbol
  • symbol.symbolLayer(index): This returns the given symbol layer within the symbol. Note that the first symbol layer has an index of zero.
  • symbol.changeSymbolLayer(index, symbol_layer): This replaces a given symbol layer within the symbol
  • symbol.appendSymbolLayer(symbol_layer): This appends a new symbol layer to the symbol
  • symbol.insertSymbolLayer(index, symbol_layer): This inserts a symbol layer at a given index
  • symbol.deleteSymbolLayer(index): This removes the symbol layer at the given index

Remember that to use the symbol once you’ve created it, you create an appropriate renderer and then assign that renderer to your map layer. For example:

renderer = QgsSingleSymbolRendererV2(symbol)
layer.setRendererV2(renderer)

The following symbol layer classes are available for you to use:

PyQGIS class

Description

Example

QgsSimpleMarkerSymbolLayerV2

This displays a point geometry as a small colored circle.

 Building Mapping Applications with QGIS

QgsEllipseSymbolLayerV2

This displays a point geometry as an ellipse.

 Building Mapping Applications with QGIS

QgsFontMarkerSymbolLayerV2

This displays a point geometry as a single character. You can choose the font and character to be displayed.

 Building Mapping Applications with QGIS

QgsSvgMarkerSymbolLayerV2

This displays a point geometry using a single SVG format image.

 Building Mapping Applications with QGIS

QgsVectorFieldSymbolLayer

This displays a point geometry by drawing a displacement line. One end of the line is the coordinate of the point, while the other end is calculated using attributes of the feature.

 Building Mapping Applications with QGIS

QgsSimpleLineSymbolLayerV2

This displays a line geometry or the outline of a polygon geometry using a line of a given color, width, and style.

 Building Mapping Applications with QGIS

QgsMarkerLineSymbolLayerV2

This displays a line geometry or the outline of a polygon geometry by repeatedly drawing a marker symbol along the length of the line.

 Building Mapping Applications with QGIS

QgsSimpleFillSymbolLayerV2

This displays a polygon geometry by filling the interior with a given solid color and then drawing a line around the perimeter.

 Building Mapping Applications with QGIS

QgsGradientFillSymbolLayerV2

This fills the interior of a polygon geometry using a color or grayscale gradient.

 Building Mapping Applications with QGIS

QgsCentroidFillSymbolLayerV2

This draws a simple dot at the centroid of a polygon geometry.

 Building Mapping Applications with QGIS

QgsLinePatternFillSymbolLayer

This draws the interior of a polygon geometry using a repeated line. You can choose the angle, width, and color to use for the line.

 Building Mapping Applications with QGIS

QgsPointPatternFillSymbolLayer

This draws the interior of a polygon geometry using a repeated point.

 Building Mapping Applications with QGIS

QgsSVGFillSymbolLayer

This draws the interior of a polygon geometry using a repeated SVG format image.

 Building Mapping Applications with QGIS

These predefined symbol layers, either individually or in various combinations, give you enormous flexibility in how features are to be displayed. However, if these aren’t enough for you, you can also implement your own symbol layers using Python. We will look at how this can be done later in this article.

Combining symbol layers

By combining symbol layers, you can achieve a range of complex visual effects. For example, you could combine an instance of QgsSimpleMarkerSymbolLayerV2 with a QgsVectorFieldSymbolLayer to display a point geometry using two symbols at once:

Building Mapping Applications with QGIS

One of the main uses of symbol layers is to draw different LineString or PolyLine symbols to represent different types of roads. For example, you can draw a complex road symbol by combining multiple symbol layers, like this:

Building Mapping Applications with QGIS

This effect is achieved using three separate symbol layers:

Building Mapping Applications with QGIS

Here is the Python code used to generate the above map symbol:

symbol = QgsLineSymbolV2.createSimple({})
symbol.deleteSymbolLayer(0) # Remove default symbol layer.
 
symbol_layer = QgsSimpleLineSymbolLayerV2()
symbol_layer.setWidth(4)
symbol_layer.setColor(QColor("light gray"))
symbol_layer.setPenCapStyle(Qt.FlatCap)
symbol.appendSymbolLayer(symbol_layer)
 
symbol_layer = QgsSimpleLineSymbolLayerV2()
symbol_layer.setColor(QColor("black"))
symbol_layer.setWidth(2)
symbol_layer.setPenCapStyle(Qt.FlatCap)
symbol.appendSymbolLayer(symbol_layer)
 
symbol_layer = QgsSimpleLineSymbolLayerV2()
symbol_layer.setWidth(1)
symbol_layer.setColor(QColor("white"))
symbol_layer.setPenStyle(Qt.DotLine)
symbol.appendSymbolLayer(symbol_layer)

As you can see, you can set the line width, color, and style to create whatever effect you want. As always, you have to define the layers in the correct order, with the back-most symbol layer defined first. By combining line symbol layers in this way, you can create almost any type of road symbol that you want.

You can also use symbol layers when displaying polygon geometries. For example, you can draw QgsPointPatternFillSymbolLayer on top of QgsSimpleFillSymbolLayerV2 to have repeated points on top of a simple filled polygon, like this:

Building Mapping Applications with QGIS

Finally, you can make use of transparency to allow the various symbol layers (or entire symbols) to blend into each other. For example, you can create a pinstripe effect by combining two symbol layers, like this:

symbol = QgsFillSymbolV2.createSimple({})
symbol.deleteSymbolLayer(0) # Remove default symbol layer.
 
symbol_layer = QgsGradientFillSymbolLayerV2()
symbol_layer.setColor2(QColor("dark gray"))
symbol_layer.setColor(QColor("white"))
symbol.appendSymbolLayer(symbol_layer)
 
symbol_layer = QgsLinePatternFillSymbolLayer()
symbol_layer.setColor(QColor(0, 0, 0, 20))
symbol_layer.setLineWidth(2)
symbol_layer.setDistance(4)
symbol_layer.setLineAngle(70)
symbol.appendSymbolLayer(symbol_layer)

The result is quite subtle and visually pleasing:

Building Mapping Applications with QGIS

In addition to changing the transparency for a symbol layer, you can also change the transparency for the symbol as a whole. This is done by using the setAlpha() method, like this:

symbol.setAlpha(0.3)

The result looks like this:

Building Mapping Applications with QGIS

Note that setAlpha() takes a floating point number between 0.0 and 1.0, while the transparency of a QColor object, like the ones we used earlier, is specified using an alpha value between 0 and 255.

Implementing symbol layers in Python

If the built-in symbol layers aren’t flexible enough for your needs, you can implement your own symbol layers using Python. To do this, you create a subclass of the appropriate type of symbol layer (QgsMarkerSymbolLayerV2, QgsLineSymbolV2, or QgsFillSymbolV2) and implement the various drawing methods yourself. For example, here is a simple marker symbol layer that draws a cross for a Point geometry:

class CrossSymbolLayer(QgsMarkerSymbolLayerV2):
   def __init__(self, length=10.0, width=2.0):
       QgsMarkerSymbolLayerV2.__init__(self)
       self.length = length
       self.width = width
 
def layerType(self):
       return "Cross"
 
def properties(self):
       return {'length' : self.length,
              'width' : self.width}
 
   def clone(self):
return CrossSymbolLayer(self.length, self.width)
 
   def startRender(self, context):
       self.pen = QPen()
       self.pen.setColor(self.color())
self.pen.setWidth(self.width)
 
   def stopRender(self, context):
self.pen = None
 
def renderPoint(self, point, context):
       left = point.x() - self.length
       right = point.x() + self.length
       bottom = point.y() - self.length
       top = point.y() + self.length
 
       painter = context.renderContext().painter()
       painter.setPen(self.pen)
       painter.drawLine(left, bottom, right, top)
       painter.drawLine(right, bottom, left, top)

Using this custom symbol layer in your code is straightforward:

symbol = QgsMarkerSymbolV2.createSimple({})
symbol.deleteSymbolLayer(0)
 
symbol_layer = CrossSymbolLayer()
symbol_layer.setColor(QColor("gray"))
 
symbol.appendSymbolLayer(symbol_layer)

Running this code will draw a cross at the location of each point geometry, as follows:

Building Mapping Applications with QGIS

Of course, this is a simple example, but it shows you how to use custom symbol layers implemented in Python. Let’s now take a closer look at the implementation of the CrossSymbolLayer class, and see what each method does:

  • __init__(): Notice how the __init__ method accepts parameters that customize the way the symbol layer works. These parameters, which should always have default values assigned to them, are the properties associated with the symbol layer. If you want to make your custom symbol available within the QGIS Layer Properties window, you will need to register your custom symbol layer and tell QGIS how to edit the symbol layer’s properties. We will look at this shortly.
  • layerType(): This method returns a unique name for your symbol layer.
  • properties(): This should return a dictionary that contains the various properties used by this symbol layer. The properties returned by this method will be stored in the QGIS project file, and used later to restore the symbol layer.
  • clone(): This method should return a copy of the symbol layer. Since we have defined our properties as parameters to the __init__ method, implementing this method simply involves creating a new instance of the class and copying the properties from the current symbol layer to the new instance.
  • startRender(): This method is called before the first feature in the map layer is rendered. This can be used to define any objects that will be required to draw the feature. Rather than creating these objects each time, it is more efficient (and therefore faster) to create them only once to render all the features. In this example, we create the QPen object that we will use to draw the Point geometries.
  • stopRender(): This method is called after the last feature has been rendered. This can be used to release the objects created by the startRender() method.
  • renderPoint(): This is where all the work is done for drawing point geometries. As you can see, this method takes two parameters: the point at which to draw the symbol, and the rendering context (an instance of QgsSymbolV2RenderContext) to use for drawing the symbol.
  • The rendering context provides various methods for accessing the feature being displayed, as well as information about the rendering operation, the current scale factor, etc. Most importantly, it allows you to access the PyQt QPainter object needed to actually draw the symbol onto the screen.

The renderPoint() method is only used for symbol layers that draw point geometries. For line geometries, you should implement the renderPolyline() method, which has the following signature:

def renderPolyline(self, points, context):

The points parameter will be a QPolygonF object containing the various points that make up the LineString, and context will be the rendering context to use for drawing the geometry.

If your symbol layer is intended to work with polygons, you should implement the renderPolygon() method, which looks like this:

def renderPolygon(self, outline, rings, context):

Here, outline is a QPolygonF object that contains the points that make up the exterior of the polygon, and rings is a list of QPolygonF objects that define the interior rings or “holes” within the polygon. As always, context is the rendering context to use when drawing the geometry.

A custom symbol layer created in this way will work fine if you just want to use it within your own external PyQGIS application. However, if you want to use a custom symbol layer within a running copy of QGIS, and in particular, if you want to allow end users to work with the symbol layer using the Layer Properties window, there are some extra steps you will have to take, which are as follows:

  • If you want the symbol to be visually highlighted when the user clicks on it, you will need to change your symbol layer’s renderXXX() method to see if the feature being drawn has been selected by the user, and if so, change the way it is drawn. The easiest way to do this is to change the geometry’s color. For example:
    if context.selected():
       color = context.selectionColor()
    else:
       color = self.color
  • To allow the user to edit the symbol layer’s properties, you should create a subclass of QgsSymbolLayerV2Widget, which defines the user interface to edit the properties. For example, a simple widget for the purpose of editing the length and width of a CrossSymbolLayer can be defined as follows:
    class CrossSymbolLayerWidget(QgsSymbolLayerV2Widget):
       def __init__(self, parent=None):
           QgsSymbolLayerV2Widget.__init__(self, parent)
           self.layer = None
     
           self.lengthField = QSpinBox(self)
           self.lengthField.setMinimum(1)
           self.lengthField.setMaximum(100)
           self.connect(self.lengthField,
                         SIGNAL("valueChanged(int)"),
                         self.lengthChanged)
     
           self.widthField = QSpinBox(self)
           self.widthField.setMinimum(1)
           self.widthField.setMaximum(100)
           self.connect(self.widthField,
                         SIGNAL("valueChanged(int)"),
                         self.widthChanged)
     
           self.form = QFormLayout()
           self.form.addRow('Length', self.lengthField)
           self.form.addRow('Width', self.widthField)
     
           self.setLayout(self.form)
     
       def setSymbolLayer(self, layer):
           if layer.layerType() == "Cross":
               self.layer = layer
               self.lengthField.setValue(layer.length)
               self.widthField.setValue(layer.width)
     
       def symbolLayer(self):
           return self.layer
     
       def lengthChanged(self, n):
           self.layer.length = n
           self.emit(SIGNAL("changed()"))
     
       def widthChanged(self, n):
           self.layer.width = n
           self.emit(SIGNAL("changed()"))

    We define the contents of our widget using the standard __init__() initializer. As you can see, we define two fields, lengthField and widthField, which let the user change the length and width properties respectively, for our symbol layer.

    The setSymbolLayer() method tells the widget which QgsSymbolLayerV2 object to use, while the symbolLayer() method returns the QgsSymbolLayerV2 object this widget is editing. Finally, the two XXXChanged() methods are called when the user changes the value of the fields, allowing us to update the symbol layer’s properties to match the value set by the user.

  • Finally, you will need to register your symbol layer. To do this, you create a subclass of QgsSymbolLayerV2AbstractMetadata and pass it to the QgsSymbolLayerV2Registry object’s addSymbolLayerType() method. Here is an example implementation of the metadata for our CrossSymbolLayer class, along with the code to register it within QGIS:

    class CrossSymbolLayerMetadata(QgsSymbolLayerV2AbstractMetadata):

       def __init__(self):
           QgsSymbolLayerV2AbstractMetadata.__init__(self, "Cross", "Cross marker", QgsSymbolV2.Marker)
     
       def createSymbolLayer(self, properties):
           if "length" in properties:
               length = int(properties['length'])
           else:
               length = 10
           if "width" in properties:
               width = int(properties['width'])
           else:
               width = 2
           return CrossSymbolLayer(length, width)
     
       def createSymbolLayerWidget(self, layer):
           return CrossSymbolLayerWidget()
     
    registry = QgsSymbolLayerV2Registry.instance()
    registry.addSymbolLayerType(CrossSymbolLayerMetadata())

Note that the parameters of QgsSymbolLayerV2AbstractMetadata.__init__() are as follows:

  • The unique name for the symbol layer, which must match the name returned by the symbol layer’s layerType() method.
  • A display name for this symbol layer, as shown to the user within the Layer Properties window.
  • The type of symbol that this symbol layer will be used for.

The createSymbolLayer() method is used to restore the symbol layer based on the properties stored in the QGIS project file when the project was saved. The createSymbolLayerWidget() method is called to create the user interface widget that lets the user view and edit the symbol layer’s properties.

Implementing renderers in Python

If you need to choose symbols based on more complicated criteria than what the built-in renderers will provide, you can write your own custom QgsFeatureRendererV2 subclass using Python. For example, the following Python code implements a simple renderer that alternates between odd and even symbols as point features are displayed:

class OddEvenRenderer(QgsFeatureRendererV2):
   def __init__(self):
QgsFeatureRendererV2.__init__(self, "OddEvenRenderer")
       self.evenSymbol = QgsMarkerSymbolV2.createSimple({})
       self.evenSymbol.setColor(QColor("light gray"))
       self.oddSymbol = QgsMarkerSymbolV2.createSimple({})
       self.oddSymbol.setColor(QColor("black"))
       self.n = 0
 
   def clone(self):
       return OddEvenRenderer()
 
   def symbolForFeature(self, feature):
       self.n = self.n + 1
       if self.n % 2 == 0:
           return self.evenSymbol
       else:
           return self.oddSymbol
 
   def startRender(self, context, layer):
       self.n = 0
       self.oddSymbol.startRender(context)
       self.evenSymbol.startRender(context)
 
   def stopRender(self, context):
       self.oddSymbol.stopRender(context)
       self.evenSymbol.stopRender(context)
 
   def usedAttributes(self):
       return []

Using this renderer will cause the various point geometries to be displayed in alternating colors, for example:

Building Mapping Applications with QGIS

Let’s take a closer look at how this class was implemented, and what the various methods do:

  • __init__(): This is your standard Python initializer. Notice how we have to provide a unique name for the renderer when calling the QgsFeatureRendererV2.__init__() method; this is used to keep track of the various renderers within QGIS itself.
  • clone(): This creates a copy of this renderer. If your renderer uses properties to control how it works, this method should copy those properties into the new renderer object.

  • symbolForFeature(): This returns the symbol to use for drawing the given feature.
  • startRender(): This prepares to start rendering the features within the map layer. As the renderer can make use of multiple symbols, you need to implement this so that your symbols are also given a chance to prepare for rendering.
  • stopRender(): This finishes rendering the features. Once again, you need to implement this so that your symbols can have a chance to clean up once the rendering process has finished.
  • usedAttributes():This method should be implemented to return the list of attributes that the renderer requires if your renderer makes use of feature attributes to choose between the various symbols,.

If you wish, you can also implement your own widget that lets the user change the way the renderer works. This is done by subclassing QgsRendererV2Widget and setting up the widget to edit the renderer’s various properties in the same way that we implemented a subclass of QgsSymbolLayerV2Widget to edit the properties for a symbol layer. You will also need to provide metadata about your new renderer (by subclassing QgsRendererV2AbstractMetadata) and use the QgsRendererV2Registry object to register your new renderer. If you do this, the user will be able to select your custom renderer for new map layers, and change the way your renderer works by editing the renderer’s properties.

Summary

In this article, we learned how QGIS symbols and renderers are used to control how vector features are displayed on a map. We saw that there are three standard types of symbols: marker symbols for drawing points, line symbols for drawing lines, and fill symbols for drawing the interior of a polygon. We then learned how to instantiate a “simple” version of each of these symbols for use in your programs.

We next looked at the built-in renderers, and how these can be used to choose the same symbol for every feature (using the QgsSingleSymbolRenderV2 class), to select a symbol based on the exact value of an attribute (using QgsCategorizedSymbolRendererV2), and to choose a symbol based on a range of attribute values (using the QgsGraduatedSymbolRendererV2 class).

We then saw how symbol layers work, and how to manipulate the layers within a symbol. We looked at all the different types of symbol layers built into QGIS, and learned how they can be combined to produce sophisticated visual effects. Finally, we saw how to implement our own symbol layers using Python, and how to write your own renderer from scratch if one of the existing renderer classes doesn’t meet your needs.

Using these various PyQGIS classes, you have an extremely powerful set of tools at your disposal for displaying vector data within a map. While simple visual effects can be achieved with a minimum of fuss, you can produce practically any visual effect you want using an appropriate combination of built-in or custom-written QGIS symbols and renderers.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here