Posts by Lutra Consulting

QGIS Layer Tree API (Part 3)

In the two previous blog posts we have learned how to query a layer tree and how to modify it. All these new APIs are available since QGIS 2.4 release. Today we will look into how to make layer trees available in GUI and connect them with a map canvas.

Model/View Overview

As we have seen earlier, a layer tree is a usual hierarchical data structure composed from nodes of two types - layers and groups. They do not provide any GUI functionality as they live in the QGIS core library. In order to visualize a layer tree, we will use Model/View approach from Qt framework. Readers not familiar with those concepts are recommended to read the Qt Model/View overview first.

Layer tree model/view

There is QgsLayerTreeModel class (derived from QAbstractItemModel) which as you may have guessed provides a model to access a layer tree. Instance of this class may be used in any QTreeView class, however it is recommended to use it together with QgsLayerTreeView because of the extra convenience functionality offerred by the custom view class.

Here is an example how to create another view of current project’s layer tree (try that in QGIS Python console):

from qgis.gui import *

root = QgsProject.instance().layerTreeRoot()
model = QgsLayerTreeModel(root)
view = QgsLayerTreeView()
view.setModel(model)
view.show()

Any changes that happen to the layer tree structure are automatically monitored by the model/view classes. After running the example above, try changing a layer’s name or add/remove/reorder some layers - all those actions will be immediately visible in all views.

As you can see, the layer tree view is just one way how to visualize the underlying layer tree - in the future it may be possible to have different ways to show the layer tree, for example using Qt’s QML framework (with all sorts of animated transitions known from mobile apps).

Plugin developers can access the layer tree view in the main window of QGIS through the following interface call:

view = iface.layerTreeView()

More About the Model

The model is meant to be flexible and it is possible to use it in various contexts. For example, by default the model also provides legend for the layers in the tree. This however may not be wanted in some cases. Similarly, sometimes the layer tree should be read-only while in other context it is desired that the user can reorder layers or change their names. These preferences can be passed to the model with flags:

model = QgsLayerTreeModel(root)
model.setFlag(QgsLayerTreeModel.AllowNodeReorder)
model.setFlag(QgsLayerTreeModel.AllowNodeChangeVisibility)

The setFlag() method has optional second parameter “enabled” (True by default). Flags can be also set all at once with setFlags() method which expects a combination of flags joined by binary OR operator. There are also flags() and testFlag() methods to query the current flags.

The QgsLayerTreeModel class also provides routines for conversion between QgsLayerTreeNode instances and corresponding QModelIndex objects used by Qt Model/View framework - index2node() and node2index() may come handy especially when working with views.

There is also some functionality related to legend display - it has been greatly extended in QGIS 2.6 release and we will try to cover that in a future blog post.

More About the View

As mentioned earlier, it is possible to use any QTreeView instance in combination with QgsLayerTreeModel to show the layer tree, but it is highly recommended to use QgsLayerTreeView class (a subclass of QTreeView) because of the additional functionality it provides (and more may be added in the future):

  • Easier handling of selection. Normally one needs to work with selection through view’s selection model. The QgsLayerTreeView class adds higher level methods that operate with QgsLayerTreeNode objects like currentNode() or with QgsMapLayer objects like currentLayer().

    def onChange(layer):
      QMessageBox.information(None, "Change", "Current Layer: "+str(layer))
    
    # connect to the signal
    view.currentLayerChanged.connect(onChange)
    # change selection to the top-most layer (onChange will be also called)
    view.setCurrentLayer( iface.mapCanvas().layers()[0] )
    
  • Display of context menu. It is possible to assign a menu provider to the view (subclass of QgsLayerTreeViewMenuProvider) - its createContextMenu() implementation will return a QMenu object with custom actions whenever user right-clicks in the view. Additionally, there is a factory class QgsLayerTreeViewDefaultActions that can create commonly use actions for use in the menu, such as “Add Group”, “Remove” or “Zoom to Layer”. The following example shows how to create a provider with one action that shows the current layer’s extent:

    class MyMenuProvider(QgsLayerTreeViewMenuProvider):
      def __init__(self, view):
        QgsLayerTreeViewMenuProvider.__init__(self)
        self.view = view
    
      def createContextMenu(self):
        if not self.view.currentLayer():
          return None
        m = QMenu()
        m.addAction("Show Extent", self.showExtent)
        return m
    
      def showExtent(self):
        r = self.view.currentLayer().extent()
        QMessageBox.information(None, "Extent", r.toString())
    
    provider = MyMenuProvider(view)
    view.setMenuProvider(provider)
    

Interaction with Canvas

The layer tree classes and map canvas class are separate components that are not dependent on each other. This is a good thing and a great step forward, because until QGIS 2.2 there was big internal monolithic QgsLegend view class that handled everything and it was directly connected to map canvas and various other components, making it impossible to reuse it elsewhere. With the new layer tree API this has been solved, now it is possible use map canvas without an associated layer tree view or vice versa - to use a layer tree view without map canvas. It is even possible to get creative and use one layer tree with several map canvas instances at once.

Layer tree and map canvas can be connected via QgsLayerTreeMapCanvasBridge class. It listens to signals from the given layer tree hierarchy and updates canvas accordingly. Let’s see how we could create a new map canvas that would show the same layers as the main canvas does:

canvas = QgsMapCanvas()
root = QgsProject.instance().layerTreeRoot()
bridge = QgsLayerTreeMapCanvasBridge(root, canvas)
canvas.zoomToFullExtent()
canvas.show()

That’s it! We have tied the new canvas with project’s layer tree. So any actions you do in the layer tree view (for example, add or remove layers, enable or disable layers) are automatically passed to the new canvas. And of course this does not work just with project’s layer tree - you could use any custom layer tree in your layer tree model/view or canvas.

The bridge class has advanced functionality worth mentioning. By default the ordering of layers in canvas is according to the order in the layer tree (with first layer in the tree being at the top), though there are API methods to override the default order.

For convenience, the bridge by default also configures some settings of the canvas (this can be disabled if necessary):

  • enable on-the-fly reprojections if layers have different coordinate reference system (CRS)
  • setup destination CRS and map units when first layers are added
  • zoom to full extent when first layers are added

Summary

I hope you have enjoyed the three blog posts introducing QGIS layer tree API. They should cover everything you need to know in order to start using the new functionality. In a future post we will have a look at the new legend API that nicely complements the layer tree API - stay tuned!

You may also like...

Mergin Maps, a field data collection app based on QGIS. Mergin Maps makes field work easy with its simple interface and cloud-based sync. Available on Android, iOS and Windows. Screenshots of the Mergin Maps mobile app for Field Data Collection
Get it on Google Play Get it on Apple store
Learn More

Getting Started Writing QGIS Python Plugins

This blog post is a QGIS plugin tutorial for beginners. It was written to support a workshop we ran for the Scottish QGIS user group here in the UK and aims to be a simple step-by-step guide.

In this tutorial you will develop your first QGIS plugin - a Map Tool for selecting the closest feature within multiple loaded vector layers. Knowledge of Python is recommended but not required.

The Goal

Before we get started let’s look at where we’re going.

We will develop a plugin that implements a new QGIS Map Tool. The Identify Features QGIS Identify Tool and Pan Map QGIS Pan Tool tools are both examples of QGIS Map Tools. A Map Tool is a tool which performs an action when used with the map canvas.

We will create a new Select Nearest Feature Map Tool Nearest Feature Tool which will sit in the plugins toolbar.

Our Select Nearest Feature Map Tool will allow the user to select the feature nearest a mouse click on the canvas. For example, clicking here:

Example Canvas Click

would select the following polygon:

Example Polygon Selection

The Starting Point

Before getting started:

The QGIS Plugin Builder plugin was used to create a base plugin which we’ll modify to fit our requirements.

This base plugin can be found in the zip file mentioned above under code/01__Empty Plugin/NearestFeature

code/01__Empty Plugin contains a batch file install.bat that can be used to copy the plugin into your QGIS plugins folder, making it available to QGIS.

Let’s now load and run this simple base plugin in QGIS.

  • Run install.bat
  • Restart QGIS if already open
  • Open the Plugin Manager: Plugins > Manage and Install Plugins
  • Enable the Nearest Feature plugin

A new action Nearest Feature Tool should now be visible in the plugins toolbar which opens the following dialog:

Empty Dialog

Creating a Basic Map Tool

When activated, our plugin currently shows a simple dialog (functionality provided by the Plugin Builder plugin. We’re going to adapt it to instead activate a Map Tool.

A basic Map Tool is included within the zip file mentioned above. It can be found in nearest_feature_map_tool.py in the Additional Files folder.

  • Copy nearest_feature_map_tool.py into the NearestFeature folder and open it in an editor.
  • Note that many of the code segments (highlighted in gray) below link to relevant parts of the API docs. Those links will open in a dedicated browser tab.

nearest_feature_tool.py defines a new NearestFeatureMapTool class (line 28) which inherits (is based on) QgsMapTool, the QGIS Map Tool class. Its __init__() method expects to be passed a reference to a QgsMapCanvas. This canvas reference is passed to the constructor of the underlying QgsMapTool class on line 32 and then stored on line 33 for later use. The QGIS API documentation describes the functionality made available by QgsMapTool.

On line 34 we define a simple, different-looking cursor (a QCursor based on Qt.CrossCursor) later used to indicate that the Map Tool is active.

Our class definition features a method called activate(). Notice the API documentation for QgsMapTool already defines a method with the same name. Any methods defined as virtual methods in the API documentation can be overwritten or redefined as they have been within this file. Here we have overwritten the default implementation of activate().

The activate() method is called when the tool is activated. The new cursor based on Qt.CrossCursor defined above is set with a call to QgsMapCanvas.setCursor().

For the moment, when activated, our Map Tool would simply change the cursor style - that’s all.

Great - next we’ll get our plugin to use the new Map Tool.

Connecting the Basic Map Tool

In this section we will modify the plugin to make use of our new Map Tool.

  • Open nearest_feature.py in a text editor.

We need to first import the NearestFeatureMapTool class before we can use it.

  • Add the following code towards the top of the file just before os.path is imported:
from nearest_feature_map_tool import NearestFeatureMapTool

Next we will create a new instance of the NearestFeatureMapTool class and store a reference to it in self.nearestFeatureMapTool.

  • Add the following code to the initGui() method just before the call to self.add_action() taking care to ensure the indentation is correct:
# Create a new NearestFeatureMapTool and keep reference
self.nearestFeatureMapTool = NearestFeatureMapTool(self.iface.mapCanvas())

Notice that a reference to the map canvas has been passed when creating the new NearestFeatureMapTool instance.

The run() method is called when our plugin is called by the user. It’s currently used to show the dialog we saw previously. Let’s overwrite its current implementation with the following:

# Simply activate our tool
self.iface.mapCanvas().setMapTool(self.nearestFeatureMapTool)

The QGIS map canvas (QgsMapCanvas class) provides the setMapTool() method for setting map tools. This method takes a reference to the new map tool, in this case a reference to a NearestFeatureMapTool.

To ensure that we leave things in a clean state when the plugin is unloaded (or reloaded) we should also ensure the Map Tool is unset when the plugin’s unload() method is called.

  • Add the following code to the end of the unload() method:
# Unset the map tool in case it's set
self.iface.mapCanvas().unsetMapTool(self.nearestFeatureMapTool)

Now let’s see the new map tool in action.

  • Save your files
  • Run install.bat to copy the updated files to the QGIS plugin folder
  • Configure the Plugin Reloader plugin to reload the NearestFeature plugin using its configure button, Configure Plugin Reloader Tool

Configure Plugin Reloader Dialog

  • Reload the Nearest Feature plugin using the Reload Plugin Tool button.
  • Click the Nearest Feature Tool button

When passing the mouse over the map canvas the cursor should now be shown as a simple cursor resembling a plus sign. Congratulations - the Map Tool is being activated.

Map Tool State

When you use the Identify Features Map Tool you’ll notice that its button remains depressed when the tool is in use. The button for our map tool does not yet act in this way. Let’s fix that.

The action (QAction) associated with our plugin is defined in the initGui() method with a call to self.add_action().

self.add_action() actually returns a reference to the new action that’s been added. We’ll make use of this behaviour to make the action / button associated with our Map Tool toggleable (checkable).

  • Modify the call to add_action() as follows:
action = self.add_action(
            icon_path,
            text=self.tr(u'Select nearest feature.'),
            callback=self.run,
            parent=self.iface.mainWindow())

action.setCheckable(True)

We now use the reference to the new action to make it checkable.

The QgsMapTool class has a setAction() method which can be used to associate a QAction with the Map Tool. This allows the Map Tool to handle making the associated button look pressed.

  • Add the following line to the end of initGui():
self.nearestFeatureMapTool.setAction(action)
  • Save your files and run install.bat
  • Reload the Nearest Feature plugin using the Reload Plugin button
  • Click the Nearest Feature Tool button

The button should now remain pressed, indicating that the tool is in use.

  • Activate the Identify Features Identify Features Tool tool

The Nearest Feature button should now appear unpressed.

Handling Mouse Clicks

The QgsMapTool class has a number of methods for handling user events such as mouse clicks and movement. We will override the canvasReleaseEvent() method to implement the search for the closest feature. canvasReleaseEvent() is called whenever the user clicks on the map canvas and is passed a QMouseEvent as an argument.

We will now write some functionality which:

  1. Loops through all visible vector layers and for each:
    • Deselects all features
    • Loops through all features and for each:
      • Determines their distance from the mouse click
      • Keeps track of the closest feature and its distance
  2. Determines the closest feature from all layers
  3. Selects that feature

 

  • Add the following method to the NearestFeatureMapTool class:
def canvasReleaseEvent(self, mouseEvent):
    """
    Each time the mouse is clicked on the map canvas, perform
    the following tasks:
        Loop through all visible vector layers and for each:
            Ensure no features are selected
            Determine the distance of the closes feature in the layer to the mouse click
            Keep track of the layer id and id of the closest feature
        Select the id of the closes feature
    """

    layerData = []

    for layer in self.canvas.layers():

        if layer.type() != QgsMapLayer.VectorLayer:
            # Ignore this layer as it's not a vector
            continue

        if layer.featureCount() == 0:
            # There are no features - skip
            continue

        layer.removeSelection()

The layers() method of QgsMapCanvas (stored earlier in self.canvas) returns a list of QgsMapLayer. These are references to all visible layers and could represent vector layers, raster layers or even plugin layers.

We use the type() and featureCount() methods to skip non-vector layers and empty vector layers.

Finally we use the layer’s removeSelection() method to clear any existing selection. layerData is a list that we’ll use in a moment.

Our plugin now clears the selection in all visible vector layers.

  • Open the Shapefiles included in the Data folder.
  • Make a selection of one or more layers.
  • Reload the plugin and ensure it is working as expected (removing any selection).

Accessing Features and Geometry

We now need access to each feature and its geometry to determine its distance from the mouse click.

  • Add the following code to canvasReleaseEvent() within the loop over layers:
# Determine the location of the click in real-world coords
layerPoint = self.toLayerCoordinates( layer, mouseEvent.pos() )

shortestDistance = float("inf")
closestFeatureId = -1

# Loop through all features in the layer
for f in layer.getFeatures():
    dist = f.geometry().distance( QgsGeometry.fromPoint( layerPoint) )
    if dist < shortestDistance:
        shortestDistance = dist
        closestFeatureId = f.id()

info = (layer, closestFeatureId, shortestDistance)
layerData.append(info)

The mouse click event (a QMouseEvent) is stored in mouseEvent. Its pos() method returns a QPoint describing the position of the mouse click relative to the map canvas (x and y pixel coordinates). To calculate its distance to each feature we’ll need to first convert the mouse click position into real world (layer) coordinates. This can be done using a call to QgsMapTool.toLayerCoordinates() which automatically deals with on-the-fly projection and returns a QPoint in layer coordinates.

The features of a vector layer can be accessed using the layer’s getFeatures() method which returns (by default) a list of all QgsFeature in the layer that we can iterate over using a simple loop.

With access to features we can easily gain access to geometry using QgsFeature.geometry(). The QgsGeometry class has a number of spatial relationship methods including distance() which returns the distance to a second QgsGeometry passed as an argument.

In the code above we loop over all features, keeping track of the feature id of the closest feature using QgsFeature.id(). The shortest distance and closest feature id are stored in shortestDistance and closestFeature. When we are finished iterating through all the features in this layer, we store a note of the layer, its closest feature id and associated distance into layerData.

Note that we convert layerPoint (a QgsPoint) into a QgsGeometry so we can use it directly in spatial relationship operations such as QgsGeometry.distance().

Completing canvasReleaseEvent

We’re almost done. At this point layerData is a list of tuples, one for each vector layer containing:

  1. A reference to the layer
  2. The id of the closest feature within that layer
  3. The distance of that closest feature from the mouse click

Now we can simply sort layerData by distance (its 3rd column) and make a selection based on the layer and feature in the first row of layerData.

  • Add the following code to canvasReleaseEvent() outside the outer for loop:
if not len(layerData) > 0:
    # Looks like no vector layers were found - do nothing
    return

# Sort the layer information by shortest distance
layerData.sort( key=lambda element: element[2] )

# Select the closest feature
layerWithClosestFeature, closestFeatureId, shortestDistance = layerData[0]
layerWithClosestFeature.select( closestFeatureId )

The code above returns early if no workable vector layers were found. It sorts layerData (the list of tuples) by the 3rd element (the distance).

The code then calls QgsVectorLayer.select() to select the closest feature by its feature id.

The plugin should now be finished.

  • Reload the plugin
  • Ensure it works as expected.

Summary

Within this tutorial we’ve worked briefly with the following parts of the QGIS API:

  • Map Tools
  • Map Canvas
  • Vector Layers
  • Features
  • Geometry

Hopefully this has been a useful tutorial. Please feel free to contact us with any specific questions.

You may also like...

Mergin Maps, a field data collection app based on QGIS. Mergin Maps makes field work easy with its simple interface and cloud-based sync. Available on Android, iOS and Windows. Screenshots of the Mergin Maps mobile app for Field Data Collection
Get it on Google Play Get it on Apple store
Learn More

QGIS Layer Tree API (Part 2)

In part 1 we covered how to access the project’s layer tree and read its data. Now let’s focus on building and manipulating layer trees. We’ll also look at how to receive updates about changes within a layer tree.

Starting with an empty project, first we will get access to the layer tree and create some memory layers for testing:

root = QgsProject.instance().layerTreeRoot()

layer1 = QgsVectorLayer("Point", "Layer 1", "memory")
layer2 = QgsVectorLayer("Polygon", "Layer 2", "memory")

Adding Nodes

Now let’s add some layers to the project’s layer tree. There are two ways of doing that:

  1. Explicit addition. This is done with the addLayer() or insertLayer() call of the QgsLayerTreeGroup class. The former appends to the group node, while the latter allows you to specify the index at which the layer should be added.

    # step 1: add the layer to the registry, False indicates not to add to the layer tree
    QgsMapLayerRegistry.instance().addMapLayer(layer1, False)
    # step 2: append layer to the root group node
    node_layer1 = root.addLayer(layer1)
    
  2. Implicit addition. The project’s layer tree is connected to the layer registry and listens for the addition and removal of layers. When a layer is added to the registry, it will be automatically added to the layer tree. It is therefore enough to simply add a layer to the map layer registry (leaving the second argument of addMapLayer() with its default value of True):

    QgsMapLayerRegistry.instance().addMapLayer(layer1)
    

    This behaviour is facilitated by the QgsLayerTreeRegistryBridge class. By default it inserts layers at the first position of the root node. The insertion point for new layers can be changed - within the QGIS application the insertion point is updated whenever the current selection in the layer tree view changes.

Groups can be added using the addGroup() or insertGroup() calls of the QgsLayerTreeGroup class:

node_group1 = root.insertGroup(0, "Group 1")
# add another sub-group to group1
node_subgroup1 = node_group1.addGroup("Sub-group 1")

There are also the general addChildNode(), insertChildNode() and insertChildNodes() calls that allow the addition of existing nodes:

QgsMapLayerRegistry.instance().addMapLayer(layer2, False)

node_layer2 = QgsLayerTreeLayer(layer2)
root.insertChildNode(0, node_layer2)

node_group2 = QgsLayerTreeGroup("Group 2")
root.addChildNode(node_group2)

Nodes that are being added must not have any parent yet (i.e. being part of some layer tree). On the other hand, the nodes that get inserted may already have children, so it is possible to create a whole sub-tree and then add it in one operation to the project’s layer tree.

Removing Nodes

The removal of nodes from a layer tree is always done from the parent group node. For example, nodes displayed as top-level items need to be removed from the root node. There are several ways of removing them. The most general form is to use the removeChildren() method that takes two arguments: the index of the first child node to be removed and how many child nodes to remove. Removal of a group node will also remove all of its children.

There are several convenience methods for removal:

root.removeChildNode(node_group2)

root.removeLayer(layer1)

There is one more way to remove layers from the project’s layer tree:

QgsMapLayerRegistry.instance().addMapLayer(layer1)

The project’s layer tree is notified when any map layers are being removed from the map layer registry and the layer nodes representing affected layers will be automatically removed from the layer tree. This is handled by the QgsLayerTreeRegistryBridge class mentioned earlier.

Moving Nodes

When managing the layer tree, it is often necessary to move some nodes to a different position - within the same parent node or to a different parent node (group). Moving a node is done in three steps: 1. clone the existing node, 2. add the cloned node to the desired place in layer tree, 3. remove the original node. The following code assumes that the existing node we move is a child of the root node:

cloned_group1 = node_group1.clone()
root.insertChildNode(0, cloned_group1)
root.removeChildNode(node_group1)

Modifying Nodes

There are a number of operations one can do with nodes:

  1. Rename. Both group and layer nodes can be renamed. For layer nodes this will modify the name directly inside the map layers.

    node_group1.setName("Group X")
    node_layer2.setLayerName("Layer X")
    
  2. Change visibility. This is actually a check state (checked or unchecked, for group nodes also partially checked) that is associated with the node and normally related to the visibility of layers and groups in the map canvas. In the GUI, the layer tree view is capable of showing a check box for changing the state.

    print node_group1.isVisible()
    node_group1.setVisible(Qt.Checked)
    node_layer2.setVisible(Qt.Unchecked)
    
  3. Change expanded state. The boolean expanded/collapsed state refers to how the node should be shown in layer tree view in the GUI - whether its children should be shown or hidden.

    print node_group1.isExpanded()
    node_group1.setExpanded(False)
    
  4. Change custom properties. Each node may have some associated custom properties. The properties are key-value pairs, keys being strings, values being of variant type (QVariant). They can be used by other components of QGIS or plugins to store additional data. Custom properties are preserved when a layer tree is saved and loaded.

    Use the customProperties() call to get a list of keys of custom properties, then the customProperty() method for getting the value of a particular key. To modify properties, there is a setCustomProperty() method which sets a key-value pair and a removeCustomProperty() method to remove a pair.

    node_group1.setCustomProperty("test_key", "test_value")
    print node_group1.customProperties()
    print node_group1.customProperty("test_key")
    node_group1.removeCustomProperty("test_key")
    print node_group1.customProperties()
    

Signals from Nodes

There are various signals emitted by nodes which may be used by client code to follow changes to the layer tree. Signals from children are automatically propagated to their parent node, so it is enough to connect to the root node to listen for changes from any level of the tree.

Modification of the Layer Tree Structure

The addition of new nodes always emits a pair of signals - before and after the actual addition. Signals pass information about which node is the parent node and the range of child indices:

  • willAddChildren(node, indexFrom, indexTo)
  • addedChildren(node, indexFrom, indexTo)

In order to access the newly added nodes, it is necessary to use the addedChildren signal.

The following code sample illustrates how to connect to signals emitted from the layer tree. When the last line is executed, two lines from the newly defined methods should be printed to the console:

def onWillAddChildren(node, indexFrom, indexTo):
  print "WILL ADD", node, indexFrom, indexTo
def onAddedChildren(node, indexFrom, indexTo):
  print "ADDED", node, indexFrom, indexTo

root.willAddChildren.connect(onWillAddChildren)
root.addedChildren.connect(onAddedChildren)

g = root.addGroup("My Group")

Removal of nodes is handled in a very similar manner to the addition - there is also a pair of signals:

  • willRemoveChildren(node, indexFrom, indexTo)
  • removedChildren(node, indexFrom, indexTo)

This time in order to access nodes being removed it is necessary to connect to the willRemoveChildren signal.

Modification of Tree Nodes

There are a few more signals that allow monitoring of internal changes to nodes:

  • a node is checked or unchecked: visibilityChanged(node, state)
  • a node’s custom property is defined or removed: customPropertyChanged(node, key)
  • a node gets expanded or collapsed: expandedChanged(node, expanded)

Summary

We have covered how to make changes to a layer tree structure and how to listen for changes possibly made by other pieces of code. In a future post we look at GUI components for displaying and modifying the layer tree and the connection between map canvas and layer tree.

You may also like...

Mergin Maps, a field data collection app based on QGIS. Mergin Maps makes field work easy with its simple interface and cloud-based sync. Available on Android, iOS and Windows. Screenshots of the Mergin Maps mobile app for Field Data Collection
Get it on Google Play Get it on Apple store
Learn More

QGIS Layer Tree API (Part 1)

This blog post will be about the QGIS component responsible for showing the list of layers. In the QGIS project we typically call this component the “legend widget”. People used to other GIS software may also use other names such as “table of contents (ToC)”.

Layers in the legend widget can be organised into groups. This grouping allows easier manipulation of layers. For example it is possible to toggle the visibility of all layers at once. In addition to layers, groups can also contain other groups, effectively creating a hierarchy of groups and layers. From now on, we will refer to this hierarchy as the layer tree.

The legend widget might look like this:

QGIS Legend Widget

Until QGIS 2.4, there has been only limited support for interacting with the legend widget using the QGIS API. There is a QgsLegendInterface class (which can be obtained with iface.legendInterface()) available for plugins. The legend interface has emerged in an ad-hoc way, leading to various issues when used in plugins. It is also worth noting that third-party applications based on QGIS have no access to the legend interface.

Layer Tree API

The layer tree API has been introduced in QGIS 2.4 to overcome these existing problems and add even more flexibility to the way the layer tree can be queried or modified.

The layer tree is a classical tree structure built of nodes. There are currently two types of nodes: group nodes and layer nodes. Group nodes can contain other (child) nodes, while layer nodes are ‘leaves’ of the tree, without any child nodes. The layer tree for the legend widget shown in the picture above looks like this:

Layer Tree Structure

The green nodes are group nodes (QgsLayerTreeGroup class) and the yellow nodes are layer nodes (QgsLayerTreeLayer class).

The legend widget also displays items using symbols, making it look like a real legend. The symbology is not part of the layer tree and will be discussed in an upcoming post.

To start working with the layer tree, we first need a reference to its root node. The project’s layer tree can be accessed easily:

root = QgsProject.instance().layerTreeRoot()

The root node is a group node - its children are shown as top-level items in the legend widget.

print root
print root.children()

This returns a list of the children of a node. The list includes only direct children - children of sub-groups need to be queried directly from those sub-groups.

Now let’s try to access the first child node in the tree and do a little bit of introspection:

child0 = root.children()[0]
print child0
print type(child0)
print isinstance(child0, QgsLayerTreeLayer)
print child0.parent()

With the children() and parent() methods it is possible to traverse the layer tree. A node is the root node of a tree if it has no parent:

print root.parent()

The following example shows how to list top-level items of the layer tree. For group nodes it will print the group name, for layer nodes it will print the layer name and ID.

for child in root.children():
  if isinstance(child, QgsLayerTreeGroup):
    print "- group: " + child.name()
  elif isinstance(child, QgsLayerTreeLayer):
    print "- layer: " child.layerName() + "  ID: " + child.layerId()

In order to traverse the full layer tree, it would be necessary to recursively call the same code for sub-groups.

There are some helper routines for common tasks like finding nodes representing layers in the tree. These take into account all descendants, not just top-level nodes.

ids = root.findLayerIds()
print ids
print root.findLayers()
print root.findLayer(ids[0])

It is assumed that a single layer is represented in a layer tree only once. There may however be temporary situations when a layer is represented by more than one node, for example when moving nodes (a new node is created before the old one is removed shortly after).

Similarly it is possible to search for group nodes by name:

print root.findGroup("POI")

Group names are not necessarily unique - if there are multiple groups with the same name, the first encountered during tree traversal will be returned.

Summary

In this blog post we have shown how to query the project’s layer tree. Upcoming blog entries will focus on modifying the layer tree and interacting with other parts of QGIS.

You may also like...

Mergin Maps, a field data collection app based on QGIS. Mergin Maps makes field work easy with its simple interface and cloud-based sync. Available on Android, iOS and Windows. Screenshots of the Mergin Maps mobile app for Field Data Collection
Get it on Google Play Get it on Apple store
Learn More

Getting started with QGIS

QGIS is a Free and Open Source Software, developed by a growing community of individuals and organisations.

Installation

You can download the latest version of QGIS from here. On that page, you can find the appropriate QGIS installation package for your operating system.

If you are a MS Windows user, you have 2 options: the standalone installer or the OSGeo4W installer, each of which has its own strengths:

  • OSGeo4W Installer Strengths
    • Access to the "master" (development) version of QGIS which means you can make use of the latest (yesterday's) cutting-edge features
    • Access to QGIS-Server (which allows you to publish your maps through a Web Mapping Service)
  • Standalone Installer Strengths
    • Simplest method of installation

Starting QGIS

Once you finish installing QGIS, you can find its icon on your desktop and/or Start menu. Launch QGIS and wait for the application to start. If you're a MS Windows user, QGIS might take some time to start up for the first time but subsequent loads will be much faster.

QGIS Start page

Arranging tool-bars

QGIS features a number of tool-bars. You can move them around by clicking and dragging the vertical or horizontal dotted bars separating the tool-bars (for example, the bar to the left of the help tool's icon in the image above).

Setting the Coordinate Reference System (CRS)

It is recommended to set the Coordinate Reference System (CRS) for your project before adding any data. CRS or SRS is a coordinate-based local, regional or global system used to locate geographical entities. Many CRSs are available and each is suited to a particular area of the globe. There is a comprehensive list of CRS codes available here. In this example, we will set the CRS to match the British National Grid coordinate reference system. The easiest way to search for a specific CRS is using its unique EPSG code. The EPSG code for British National Grid is 27700.

To set the CRS for your projects in QGIS, from the main menu, select Settings > Options. A new window will appear. Select CRS tab.

QGIS Options

CRS list

In the Search section, set Authority to "EPSG" and Search for to "ID". Type 27700 into the search box and click Find. Highlight the correct row in Coordinate Reference System section and click OK.

Adding data

GIS data is usually in either raster or vector format. QGIS supports a large number of GIS data formats through the GDAL/OGR library and other plugins. In the example below we will download and add some OS OpenData™ raster and vector datasets into QGIS.

Raster

Ordnance Survey released a number of OS OpenData raster datasets to the public under a very permissive license. You can download the data from here.

For this particular example, follow this link and browse to OS Street View®. Select SX from the map. Move towards the bottom of the page and click next. Fill in the required information and click continue. You should receive an email with a link to download osstvw_sx.zip (note: it is a 383.9 MB file - you can order a DVD instead if you have a slow internet connection). Once the download has finished, unzip the file. You should now have a new folder called OS Street View SX which contains 2 subfolders and a readme file.

Browse to Street View SX > data > georeferencing files > tfw. Select all the TFW files and move them to the Street View SX > data > sx folder. The TFW files contain georeferencing information describing the location of each TIF file.

In QGIS, from the main menu, select Layer > Add Raster Layer... and browse to the Street View SX > data > sx folder. Select sx99nw.tif, sx99ne.tif, sx99sw.tif and sx99se.tif. Click Open. You should now be able to see the raster tiles in the QGIS canvas and the Layers panel at the left side of the screen.

Raster files do not always contain CRS information. We can easily organise the layers and assign the correct CRS (EPSG:27700) with the help of groups. Create a new group by right-clicking on the blank space (not on the sx99 layers) in the Layers panel and selecting Add group. Set the name of the group to OS Street View. Next, move the sx99 layers into the new group by selecting them all and dragging them into the OS Street View group. Once all the sx99 layers are inside the OS Street View group, right-click on the group and select Set group CRS. A new dialog, similar to that seen in the Setting the CRS chapter will appear. Assign the British National Grid CRS (EPSG:27700) and click OK.

QGIS raster

Vector

Next, we'll bring some vector data into QGIS. Go to the OS OpenData Supply page and browse to OS VectorMap™ District (there are two OS VectorMap datasets on this page, for this example, ensure you select the vector version and not the raster version) and select SX from the map. Scroll to the bottom of the page and click next. Fill in the required information and click continue. Download vmdvec_sx.zip from the link you'll receive by email. Extract the contents of the ZIP file.

In QGIS, from the main menu, select Layer > Add Vector Layer... and browse to the OS VectorMap District (Vector) SX > data > SX. Select SX_Airport.shp, SX_RailwayTrack.shp and SX_Road.shp. Click Open. Click Open again.

To change the style of a vector layer, right-click on the layer in the Layers panel and select Properties. In the Style tab of the Layer Properties dialog, you can define exactly how the layer should look.

QGIS vector

Other Data

Internet based mapping can also be brought into QGIS, for example, a plugin exists that allows OpenStreetMap, Google, Bing and Yahoo maps to be added to QGIS.

Web map services (WMS) are another source of mapping data. In the next we'll add a WMS layer provided by British Geological Survey to our map. Please read the BGS WMS Terms of use. Another example of WMS is Ordnance Survey's OS OnDemand service. If you have OS OnDemand license, you can follow the instructions on Ordnance Survey's website (sorry, link no longer works) to add other useful WMS layers.

To add the BGS WMS, select Layer > Add WMS Layer... from the main menu. The Add Layer(s) from a Server dialog will appear. Click New.

wms add

Set the name to BGS and set the URL to the following:

http://maps.bgs.ac.uk/ArcGIS/services/BGS_Detailed_Geology/MapServer/WMS...

Click OK, and in the Add Layer(s) from a Server dialog, click Connect.

wms add layer

Select all the layers and click Add. Close the Add Layer(s) from a Server dialog. The BGS layer should become visible as you zoom-in to a scale of 1:50000 or closer. Alternatively, you can manually set the Scale in the status bar to 1:50000 and the BGS layer will appear.

wms BGS

Plugins

QGIS is written in a manner that makes it possible for anyone it extend its functionality through the use of plugins. As a result, there are many plugins available to the user, making QGIS highly modular and flexible.

Core plugins

Core plugins are plugins that are shipped with QGIS and can be optionally enabled through the QGIS Plugin Manager.  To access the QGIS Plugin Manager, from the main menu, Select Plugins > Manage Plugins...

qgis core plugins

Select the All tab and type OpenLayers Plugin into the Filter box. Select the plugin and click Install plugin. You should now be able to add OpenStreetMap, Google, Bing and Yahoo maps to your canvas using the Web > OpenLayers plugin menu.

qgis and OpenLayers

Further information

For further help using QGIS, you can always check the manual, user or developer mailing lists or QGIS forum.

If you'd like to master QGIS as quickly as possible, why not attend one of our training courses.

Troubleshooting

Installing OpenLayers Plugin

To install OpenLayers plugin, from the main menu, click Plugins > Manage and Install Plugins.... A new window will appear.

You should be able to search and install the OpenLayers plugin within your list.

Windows Installation

Although you can have different version of QGIS installed under Windows, it's recommended to uninstall old versions before attempting to install new versions.

On rare occasions, some anti-virus software has been known to remove the qgis.exe and python.exe files from the installation folder. If you're having problems running the QGIS shortcut, please ensure those 2 files exist in the installation folder.

If QGIS cannot find your Python folder, you may need to set the PYTHONPATH environment variable to your QGIS folder (\QGIS\apps\python).

Access to internet

To be able to access WMS, WFS and 3rd party plugins, you'll need to have internet access. In the event that you're behind a proxy server, you can enter the proxy server details in Settings > Options > Network:

qgis proxy

You may also like...

Mergin Maps, a field data collection app based on QGIS. Mergin Maps makes field work easy with its simple interface and cloud-based sync. Available on Android, iOS and Windows. Screenshots of the Mergin Maps mobile app for Field Data Collection
Get it on Google Play Get it on Apple store
Learn More

Finger Tired From Digitising? Try AutoTrace

My first brush with GIS was back in 2004. I was just getting into building river models and spent a considerable amount of time digitising model inputs. These would often include tens and sometimes hundreds of kilometres of river banks. Many of the input features (e.g. flood embankments) would be traced to snap exactly to existing polygon features.

This was not a big issue at the time as MapInfo had a really nice autotrace feature which made these tasks a doddle. However, when I moved to QGIS I could not find such a feature.

AutoTrace in Action

The image above shows AutoTrace in action. Please see the AutoTrace project page for full documentation and a list of co-funders.

Origins of AutoTrace Development

The idea to develop a plugin to do MapInfo-style tracing was born at the QGIS developer conference (AKA Hackfest) in Lisbon in early 2011. It was there that Paolo Cavalini pointed me towards the traceDigitize plugin already developed by Cédric Möri. traceDigitize already supported tracing but required the user to move the cursor along the edge of the feature being traced. I still had my heart set on achieving that MapInfo-style tracing.

Rather than re-inventing the wheel, traceDigitize was modified to add MapInfo-style tracing as an optional component. This was my development focus for the HF (and the flight back). At this stage the basic functionality was in-place but the plugin had many bugs and was not reliable to use. Unfortunately the plugin was mothballed until recently.

Release of AutoTrace

It was at the first QGIS UK User Group that I met Matt Travis (GIS Officer at Dartmoor NPA) and we got talking about AutoTrace. It transpired that there were a number of local government organisations interested in getting this auto-tracing functionality into QGIS. We soon had commitment from a number of organisations (including and organised by Kevin Williams at Neath Port Talbot Council) to fund the work required to release a stable version of the tool.

Future Plans

There's talk of extending the functionality of AutoTrace to also implement some of he more complex tracing techniques seen in ArcGIS. If this is something you'd be interested in then please let us know.

Many thanks to all those who have supported us in this project.

You may also like...

Mergin Maps, a field data collection app based on QGIS. Mergin Maps makes field work easy with its simple interface and cloud-based sync. Available on Android, iOS and Windows. Screenshots of the Mergin Maps mobile app for Field Data Collection
Get it on Google Play Get it on Apple store
Learn More

Lutra Co-host QGIS Developer Conference

Lutra Consulting and the University of Sussex are proud to host the 10th developer conference (or Hackfest) on the 12th to 16th of September. QGIS is a powerful and user-friendly Free and Open Source GIS capable of viewing, analysing and mapping Geographic Information.

The adoption of QGIS is accelerating globally in public, private and academic organisations. Developers from around the globe will meet in person to discuss, plan and implement various aspects of the QGIS project.

All university staff and students are welcome to join the Hackfest and meet the people behind QGIS! See the wiki page for more information.

Ongoing news and updates will be posted to this page during the hackfest.

Day 1

Live streaming can be viewed here

Find us here

</p>

You may also like...

Mergin Maps, a field data collection app based on QGIS. Mergin Maps makes field work easy with its simple interface and cloud-based sync. Available on Android, iOS and Windows. Screenshots of the Mergin Maps mobile app for Field Data Collection
Get it on Google Play Get it on Apple store
Learn More

NFCDD Updater

NFCDD Updater is a QGIS extension developed by Lutra Consulting for Halcrow Group to automate the process of updating geometry and attribute data in the Environment Agency's National Flood and Coastal Defence Database (NFCDD).

As part of their Ravensbourne Raised Defences project, Halcrow were required to update flood defence crest level and standard of protection data for defence assets along the rivers Ravensbourne and Quaggy, south-east London based on the results from extensive ISIS modelling.

NFCDD Updater User Interface

The updates were originally carried out as a manual GIS task, requiring GIS technicians to edit NFCDD geometry and attributes manually. The editing was prone to human error and was a complex and time-consuming process.

Lutra Consulting were commissioned to develop NFCDD Updater - a bespoke extension (plugin) to the free and open-source GIS platform QGIS. NFCDD Updater automates the task of updating NFCDD while allowing the user to maintain a high level of control over the process.

NFCDD Updater performs the following tasks:

  • Imports NFCDD dataset and reads user control file
  • Creates new defence features between ISIS nodes of interest based on existing geometry
  • Populates the new defence features with information from the user control file
  • Performs any necessary update of attributes to neighbouring 'parent' defence features (length etc.)
  • Exports the updated table in MapInfo format, ready to be imported back into NFCDD

NFCDD Updater was designed to be simple to use and to allow user-interaction where required, for example, prompting the user to visually select a single defence line to update when more than one candidate defence line has been selected for updating automatically.

NFCDD Updater performed very well in terms of reducing manual GIS work required to complete the project and allowed approximately 30% of the project budget to be saved. With minor modifications, NFCDD updater can be updated to automate manual GIS tasks on other Halcrow projects.

For more information on NFCDD Updater or to find out more about the development of bespoke QGIS plugins, contact Peter Wells.

You may also like...

Mergin Maps, a field data collection app based on QGIS. Mergin Maps makes field work easy with its simple interface and cloud-based sync. Available on Android, iOS and Windows. Screenshots of the Mergin Maps mobile app for Field Data Collection
Get it on Google Play Get it on Apple store
Learn More