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.
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 and Pan Map 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 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:
would select the following polygon:
The Starting Point
Before getting started:
- Install a text editor (e.g. Geany)
- Download and extract this code and data
- Install QGIS >= 2.4
- Install the Plugin Reloader plugin
- Open the QGIS API documentation
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 should now be visible in the plugins toolbar which opens the following 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
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
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
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
- 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.
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
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
# 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,
- Reload the Nearest Feature plugin using the button.
- Click the 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() 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
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.
- Add the following line to the end of
- Save your files and run install.bat
- Reload the Nearest Feature plugin using the button
- Click the button
The button should now remain pressed, indicating that the tool is in use.
- Activate the Identify Features tool
The Nearest Feature button should now appear unpressed.
Handling Mouse Clicks
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:
- 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
- Determines the closest feature from all layers
- 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()
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.
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
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.
With access to features we can easily gain access to geometry using
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
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
We’re almost done. At this point
layerData is a list of tuples, one for each vector layer containing:
- A reference to the layer
- The id of the closest feature within that layer
- 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
- 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 ) # Select the closest feature layerWithClosestFeature, closestFeatureId, shortestDistance = layerData 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.
Within this tutorial we’ve worked briefly with the following parts of the QGIS API:
- Map Tools
- Map Canvas
- Vector Layers
Hopefully this has been a useful tutorial. Please feel free to contact us with any specific questions.