Learn how to work with map tools in QGIS using Python from this section as part of this preview chapter, Creating Dynamic Maps from QGIS Python Programming CookBook. With 140 short, reusable recipes to automate geospatial processes in QGIS, the QGIS Python Programming CookBook teaches readers how to use Python and QGIS to create and transform data, produce appealing GIS visualizations, and build complex map layouts.
Adding Standard Map Tools to the Canvas
In this recipe we’ll learn how to add standard map navigation tools to a standalone map canvas. Creating the simplest possible interactive application provides a framework to begin building specialized geospatial applications using QGIS as a library.
Getting ready
Download the following zipped shapefile and extract it to your qgis_data directory into a folder named ms.
https://geospatialpython.googlecode.com/files/Mississippi.zip
Free weekly newsletter
Fill out your e-mail address to receive our newsletter!
By entering your email address you agree to receive our newsletter and agree with our privacy policy.
You may unsubscribe at any time.
How to do it…
We will step through creating a map canvas, add a layer to it, and then add some tools to zoom and pan the map.
- First, because we are working outside of the QGIS python interpreter, we need to import some QGIS and Qt libraries:
fromqgis.gui import *
fromqgis.core import *
from PyQt4.QtGui import *
from PyQt4.QtCore import SIGNAL, Qt
import sys, os
- The we must set the location of our main QGIS application directory. This setting is platform dependent:
# OSX:
QgsApplication.setPrefixPath(“/Applications/QGIS.app/Contents/MacOS/”, True)
# Windows:
# app.setPrefixPath(“C:/Program Files/QGIS Valmiera/apps/qgis”, True)
- Next we begin initializing the class:
classMyWnd(QMainWindow):
def __init__(self):
- Now we can initialize the application and create the map canvas:
QMainWindow.__init__(self)
QgsApplication.setPrefixPath(“/Applications/QGIS.app/Contents/MacOS/”, True)
QgsApplication.initQgis()
self.canvas = QgsMapCanvas()
self.canvas.setCanvasColor(Qt.white)
- Then we can load the shapefile layer and add it to the canvas:
self.lyr = QgsVectorLayer(“/Users/joellawhead/qgis_data/ms/mississippi.shp”, “Mississippi”, “ogr”)
QgsMapLayerRegistry.instance().addMapLayer(self.lyr)
self.canvas.setExtent(self.lyr.extent())
self.canvas.setLayerSet([QgsMapCanvasLayer(self.lyr)])
self.setCentralWidget(self.canvas)
- Next we define the buttons that will be on the toolbar:
actionZoomIn = QAction(“Zoom in”, self)
actionZoomOut = QAction(“Zoom out”, self)
actionPan = QAction(“Pan”, self)
actionZoomIn.setCheckable(True)
actionZoomOut.setCheckable(True)
actionPan.setCheckable(True)
- Now we connect the signal created when the buttons are clicked, to the python methods that will provide each tool’s functionality:
actionZoomIn.triggered.connect(self.zoomIn)
actionZoomOut.triggered.connect(self.zoomOut)
actionPan.triggered.connect(self.pan)
- Next create our toolbar and add the buttons:
self.toolbar = self.addToolBar(“Canvas actions”)
self.toolbar.addAction(actionZoomIn)
self.toolbar.addAction(actionZoomOut)
self.toolbar.addAction(actionPan)
- Then we connect the buttons to applications states:
self.toolPan = QgsMapToolPan(self.canvas)
self.toolPan.setAction(actionPan)
self.toolZoomIn = QgsMapToolZoom(self.canvas, False) # false = in
self.toolZoomIn.setAction(actionZoomIn)
self.toolZoomOut = QgsMapToolZoom(self.canvas, True) # true = out
self.toolZoomOut.setAction(actionZoomOut)
- Then we define which button will be selected when the application loads:
self.pan()
- Now we define the python methods that control the application’s behavior for each tool:
defzoomIn(self):
self.canvas.setMapTool(self.toolZoomIn)
defzoomOut(self):
self.canvas.setMapTool(self.toolZoomOut)
def pan(self):
self.canvas.setMapTool(self.toolPan)
- Then we create a Qt application which uses our application window class:
class MainApp(QApplication):
def __init__(self):
QApplication.__init__(self,[],True)
wdg = MyWnd()
wdg.show()
self.exec_()
- Finally we enter the program’s main loop:
if __name__ == “__main__”:
import sys
app = MainApp()
How it works…
An application is a continuously running program loop that ends only when we quit the application. QGIS is based on the Qt windowing library so our application class inherits from the main window class which provides the canvas and the ability to create toolbars and dialogs.There is a lot of set up, even for an extremely simple application, but once the framework for an application is complete, it becomes much easier to extend.
A Map Tool to Draw Points on the Canvas
QGIS contains built in functionality to zoom and pan the map in custom applications. It also contains the basic hooks to build your own interactive tools. In this recipe, we’ll create an interactive point tool that let’s you mark locations on the map by clicking a point.
Getting ready
We will use the application framework from the previous recipe, Adding Standard Map Tools to the Canvas, so complete that recipe. We will extend that application with a new tool.A complete version of this application is available in the code samples provided with this book.
How to do it…
We will set up the button, signal trigger, and actions as we do with all map tools. But because we are building a new tool, we must also define a class to define exactly what the tool does.
- First we define our point tool’s button in the actions portion of our application. Place this line after QAction(“Pan”) button:
actionPoint = QAction(“Point”, self)
- In the next section, we make sure that when you click the button, it stays selected:
actionPoint.setCheckable(True)
- In the section after that, we define the method used when the button is triggered:
self.connect(actionPoint, SIGNAL(“triggered()”), self.point)
- Now we add the button to the toolbar along with the other buttons:
self.toolbar.addAction(actionPoint)
- Then we link the application to our specialized tool class:
self.toolPoint = PointMapTool(self.canvas)
self.toolPoint.setAction(actionPoint)
- We set the point tool to be selected when the application loads:
self.point()
- Now we define the method in the main application class for our tool:
def point(self):
self.canvas.setMapTool(self.toolPoint)
- Now we create a class that describes what type of tool we have and what it outputs. The output is a point on the canvas defined in the method canvasPressEvent that receives the button click-event. We will inherit from a generic tool for creating points called the QgsMapToolEmitPoint:
classPointMapTool(QgsMapToolEmitPoint):
def __init__(self, canvas):
self.canvas = canvas
QgsMapToolEmitPoint.__init__(self, self.canvas)
self.point = None
defcanvasPressEvent(self, e):
self.point = self.toMapCoordinates(e.pos())
printself.point.x(), self.point.y()
m = QgsVertexMarker(self.canvas)
m.setCenter(self.point)
m.setColor(QColor(0,255,0))
m.setIconSize(5)
m.setIconType(QgsVertexMarker.ICON_BOX) # or ICON_CROSS, ICON_X
m.setPenWidth(3)
How it works…
For custom tools, PyQGIS provides a set of generic tools for common functions that you can extend and piece together.In this case the EmitPoint tool handles the details of events and map functionality when you click at a location on the map.
A Map Tool to Draw Polygons or Lines on the Canvas
In this recipe we’ll create a tool to draw polygons on the canvas. This tool is an important tool because it opens the door to even more advanced tools. Once you have a polygon on the canvas you can do all sorts of operations involving querying and geometry.
Getting ready
We will use the application framework from the recipe in this chapter titled Adding Standard Map Tools to the Canvas, so complete that recipe. We will extend that application with a new tool. A complete version of this application is available in the code samples provided with this book.
How to do it…
We will add a new tool to the toolbar and also create a class describing our polygon tool.
- First we define our polygon tool’s button in the actions portion of our application. Place this line after QAction(“Pan”) button:
actionPoly = QAction(“Polygon”, self)
- In the next section, we make sure that when you click the button, it stays selected:
actionPoly.setCheckable(True)
- In the section after that, we define the method used when the button is triggered:
self.connect(actionPoly, SIGNAL(“triggered()”), self.poly)
- Now we add the button to the toolbar along with the other buttons:
self.toolbar.addAction(actionPoly)
- Then we link the application to our specialized tool class:
self.toolPoly = PolyMapTool(self.canvas)
self.toolPoly.setAction(actionPoly)
- We set the point tool to be selected when the application loads:
self.poly()
- Now we define the method in the main application class for our tool:
def poly(self):
self.canvas.setMapTool(self.toolPoly)
Now we create a class that describes what type of tool we have and what it outputs. The output is a point on the canvas defined in the method canvasPressEventthat receives the button click-event and the method showPoly. We will inherit from a generic tool for creating points called the QgsMapToolEmitPointwe will also use an object called QgsRubberBand for handling polygons:
classPolyMapTool(QgsMapToolEmitPoint):
def __init__(self, canvas):
self.canvas = canvas
QgsMapToolEmitPoint.__init__(self, self.canvas)
self.rubberband = QgsRubberBand(self.canvas, QGis.Polygon)
self.rubberband.setColor(Qt.red)
self.rubberband.setWidth(1)
self.point = None
self.points = []
defcanvasPressEvent(self, e):
self.point = self.toMapCoordinates(e.pos())
m = QgsVertexMarker(self.canvas)
m.setCenter(self.point)
m.setColor(QColor(0,255,0))
m.setIconSize(5)
m.setIconType(QgsVertexMarker.ICON_BOX)
m.setPenWidth(3)
self.points.append(self.point)
self.isEmittingPoint = True
self.showPoly()
defshowPoly(self):
self.rubberband.reset(QGis.Polygon)
for point in self.points[:-1]:
self.rubberband.addPoint(point, False)
self.rubberband.addPoint(self.points[-1], True)
self.rubberband.show()
How it works…
All of the settings for the polygon are contained in the custom class. There is a key property called is EmittingPoint which we use to detect if we are still adding points to the polygon. That value starts out as false. If that is the case we reset our polygon object and begin drawing a new one. The following screenshot shows a polygon drawn with this tool on a map.
Building a Custom Selection Tool
We will build a custom tool in this recipe that both draws a shape on the map but also interacts with other features on the map. These two basic functions are the basis for almost any map tool you would want to build in a standalone QGIS application like this one, or by extending the QGIS Desktop Application with a plugin.
Getting ready
We will use the application framework from the recipe in this chapter titled Adding Standard Map Tools to the Canvas, so complete that recipe. We will extend that application with a new tool. A complete version of this application is available in the code samples provided with this book. It will also be beneficial to study the other two tool-related recipes in this chapter named A Map Tool to Draw Polygons or Lines on the Canvas and A Map Tool to Draw Points on the Canvas as this recipe builds on them as well.
You will also need the following zipped shapefile:
https://geospatialpython.googlecode.com/files/NYC_MUSEUMS_GEO.zip
Download and extract it to your qgis_data directory.
How to do it…
We will add a new tool to the toolbar and also create a class describing our selection tool including how to draw the selection polygon, and how to select the features.
- First we define our polygon tool’s button in the actions portion of our application. Place this line after QAction(“Pan”) button:
actionSelect = QAction(“Select”, self)
- In the next section, we make sure that when you click the button, it stays selected:
actionSelect.setCheckable(True)
- In the section after that, we define the method used when the button is triggered:
self.connect(actionSelect, SIGNAL(“triggered()”), self.select)
- Now we add the button to the toolbar along with the other buttons:
self.toolbar.addAction(actionSelect)
- Then we link the application to our specialized tool class:
self.toolSelect = SelectMapTool(self.canvas, self.lyr)
self.toolSelect.setAction(actionSelect)
- We set the point tool to be selected when the application loads:
self.select()
- Now we define the method in the main application class for our tool:
def select(self):
self.canvas.setMapTool(self.toolSelect)
- Now we create a class that describes what type of tool we have and how it works. The output is a point on the canvas defined in the method canvasPressEvent that receives the button click-event and the method selectPoly. We will inherit from a generic tool for creating points called the QgsMapToolEmitPointwe will also use an object called QgsRubberBand for handling polygons. But we must also perform the selection process to highlight the features that fall within our selection polygon:
classSelectMapTool(QgsMapToolEmitPoint):
def __init__(self, canvas, lyr):
self.canvas = canvas
self.lyr = lyr
QgsMapToolEmitPoint.__init__(self, self.canvas)
self.rubberband = QgsRubberBand(self.canvas, QGis.Polygon)
self.rubberband.setColor(QColor(255,255,0,50))
self.rubberband.setWidth(1)
self.point = None
self.points = []
defcanvasPressEvent(self, e):
self.point = self.toMapCoordinates(e.pos())
m = QgsVertexMarker(self.canvas)
m.setCenter(self.point)
m.setColor(QColor(0,255,0))
m.setIconSize(5)
m.setIconType(QgsVertexMarker.ICON_BOX)
m.setPenWidth(3)
self.points.append(self.point)
self.isEmittingPoint = True
self.selectPoly()
defselectPoly(self):
self.rubberband.reset(QGis.Polygon)
for point in self.points[:-1]:
self.rubberband.addPoint(point, False)
self.rubberband.addPoint(self.points[-1], True)
self.rubberband.show()
iflen(self.points) > 2:
g = self.rubberband.asGeometry()
featsPnt = self.lyr.getFeatures(QgsFeatureRequest().setFilterRect(g.boundingBox()))
forfeatPnt in featsPnt:
iffeatPnt.geometry().within(g):
self.lyr.select(featPnt.id())
How it works…
QGIS has a generic tool for highlighting features, but in this case we can use the standard selection functionality, which simplifies our code. With the exception of a dialog to load new layers and the ability to show attributes, we have a very basic but nearly complete standalone GIS application.The following screenshot shows the selection tool in action.
Creating a Mouse Coordinate Tracking Tool
In this recipe, we’ll build a tool that tracks and displays the mouse coordinates in real time. This tool will also demonstrate how to interact with the status bar of a QGIS application.
Getting ready
We will use the application framework from the recipe in this chapter titled Adding Standard Map Tools to the Canvas, so complete that recipe. We will extend that application with the coordinate tracking tool. A complete version of this application is available in the code samples provided with this book. It will also be beneficial to study the other two tool-related recipes in this chapter named A Map Tool to Draw Polygonsor Lines on the Canvas and A Map Tool to Draw Points on the Canvas as this recipe builds on them as well.
How to do it…
We will add an event filter to the basic standalone QGIS application and use it to grab the current mouse coordinates and update the status bar.
- As the last line of our application__init__ method, insert the following line to create a default status bar message when the application loads:
self.statusBar().showMessage(u”x: –, y: –“)
- Immediately after the application’s __init__ method, we will add the following event filter method:
defeventFilter(self, source, event):
ifevent.type() == QEvent.MouseMove:
ifevent.buttons() == Qt.NoButton:
pos = event.pos()
x = pos.x()
y = pos.y()
p = self.canvas.getCoordinateTransform().toMapCoordinates(x, y)
self.statusBar().showMessage(u”x: %s, y: %s” % (p.x(), p.y()))
else:
pass
returnQMainWindow.eventFilter(self, source, event)
- In the MainApp class as the second to last line, we must install the event filter using the following code:
self.installEventFilter(wdg)
How it works…
In the Qt framework, in order to watch for mouse events, we must insert an event filter that allows us to monitor all events in the application. Within the default event filter method we can then process any event we want. In this case we watch for any movement of the mouse.
QGIS Python Programming CookBook
<– Previous: Labels and Transparency | Intro: Creating Dynamic Maps in QGIS –>