Gilberto Schwertner Eacbf9e PPG
Gilberto Schwertner Eacbf9e PPG
Gilberto Schwertner Eacbf9e PPG
The
PyQGIS
Programmer’s Guide
Extending QGIS 2.x with Python
Credits & Copyright
T H E P Y QGIS P RO G R A M M E R ’ S G U I D E
E X T E N D I N G QGIS 2. X WITH P Y T H O N (Version: c11dd8c49d)
by Gary Sherman
C O P Y R I G H T © 2014 L O C AT E P R E S S LLC
ISBN: 978-0989421720
A L L R I G H T S R E S E RV E D .
1 Introduction 13
1.1 Requirements . . . . . . . . . . . . . . . . . . . . . . . . 13
1.2 Sample Data . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.3 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.4 Conventions Used in this Book . . . . . . . . . . . . . . . 14
1.5 Your First PyQGIS experiment . . . . . . . . . . . . . . . 15
1.6 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2 Python Basics 19
2.1 Getting Help . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.2 Data Structures . . . . . . . . . . . . . . . . . . . . . . . 20
2.3 Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.4 Strings, Ranges, and Other Handy Things . . . . . . . . . 28
2.5 Installing Packages . . . . . . . . . . . . . . . . . . . . . 32
2.6 Documenting Your Code . . . . . . . . . . . . . . . . . . 33
2.7 Resources to Learn More . . . . . . . . . . . . . . . . . . 36
2.8 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . 36
7 Running Scripts 75
7.1 Standalone Scripts in the Console . . . . . . . . . . . . . 75
7.2 Running Scripts with Script Runner . . . . . . . . . . . . 81
7.3 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . 86
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
6 CO N T E N T S
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
CONTENTS 7
17 Index 193
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
List of Figures
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
Listings
2.1 simplepoint.py . . . . . . . . . . . . . . . . . . . . . . . 24
2.2 point3d_1.py . . . . . . . . . . . . . . . . . . . . . . . . 25
2.3 point3d.py . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.4 point.py . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
7.1 first_script.py . . . . . . . . . . . . . . . . . . . . . . . . 76
7.2 no_methods.py . . . . . . . . . . . . . . . . . . . . . . . 79
7.3 first_script_class.py . . . . . . . . . . . . . . . . . . . . . 79
7.4 first_script_sr.py . . . . . . . . . . . . . . . . . . . . . . . 82
7.5 first_script_sr_documented.py . . . . . . . . . . . . . . . 85
9.1 wrapper.py: The Imports . . . . . . . . . . . . . . . . . . 119
9.2 wrapper.py: addLayer Function . . . . . . . . . . . . . . . 120
9.3 wrapper.py: addOgrLayer and addGdalLyer Functions . . 120
9.4 wrapper.py: Remove Layer . . . . . . . . . . . . . . . . . 123
9.5 wrapper.py: Using the addLayer Function . . . . . . . . . 124
9.6 wrapper.py: Changing Color and Transparency . . . . . . 125
9.7 wrapper.py: Changing Colors . . . . . . . . . . . . . . . . 126
9.8 wrapper.py: Complete Script . . . . . . . . . . . . . . . . 127
10.1 whereami.py: Imports and Init Method . . . . . . . . . . . 145
10.2 whereami.py: Adding the Map Tool . . . . . . . . . . . . 146
10.3 whereami.py: display_point Method . . . . . . . . . . . . 147
10.4 whereamidialog.py . . . . . . . . . . . . . . . . . . . . . 149
10.5 whereami.py: Modified display_point Method . . . . . . . 149
11.1 compile.py . . . . . . . . . . . . . . . . . . . . . . . . . 156
12.1 interactive.py . . . . . . . . . . . . . . . . . . . . . . . . 162
12.2 interactive_qgis.py . . . . . . . . . . . . . . . . . . . . . 164
12.3 ourmainwindow_1.py . . . . . . . . . . . . . . . . . . . . 166
12.4 our_app_1.py . . . . . . . . . . . . . . . . . . . . . . . . 167
LISTINGS 11
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1
Introduction
1.1 Requirements
To learn PyQGIS you need several pieces of software: You must use Python 2.x; QGIS does
not support Python 3.x.
• A working QGIS 2.0 install on Linux, Mac, or Windows
• Python 2.x
• Qt2
• PyQt3 2
http://loc8.cc/pq/qt
3
http://loc8.cc/pq/pyqt
1.2 Sample Data
In order to work through the examples in the book you’ll need some data.
For vector data, it will be best for you to use the sample data used through-
out the book. You can download the sample vector data set at:
14 C H A P T E R 1 . I N T RO D U C T I O N
http://locatepress.com/ppg/data_code
For raster layers, download one of the Natural Earth rasters available at:
If you are familiar with vector and raster data, feel free to use your own and
adapt the commands/examples accordingly.
1.3 Code
All of the code examples in this book are available for download at:
http://locatepress.com/ppg/data_code
You are encouraged to work through the examples on your own, but feel
free to copy and paste.
Code listings:
Code listings are presented in a fixed width font and may or may not include
line numbers:
1 """
2 ScriptRunner is the main plugin class that initializes the QGIS
3 plugin, initializes the GUI, and performs the work.
4 """
5
6 def __init__(self, iface):
7 """
8 Save reference to the QGIS interface
9 """
10 self.iface = iface
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 . 5 . YO U R F I R S T P Y Q G I S E X P E R I M E N T 15
Input lines in an interactive session (something you typed) are prefaced with
the >>> prompt. Indented lines in a block are preceded with ... and output
from the interpreter is shown without any leading characters:
>>> my_list = [’north’, ’south’, ’east’, ’west’]
>>> my_list[1]
’south’
>>> for d in my_list:
... print d
...
north
south
east
west
Notes/Tips:
Open QGIS and load the world_borders.shp file using the Add Vector
Layer menu.
For this experiment we’ll be using methods (think functions) defined by the
QgisInterface class:
• zoomFull()
• zoomToPrevious()
• zoomToNext()
• showAttributeTable()
• showLayerProperties()
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
16 C H A P T E R 1 . I N T RO D U C T I O N
When you open the console, there is a hint on getting help on using the
iface object. Using the built-in help function you can get a list of the
methods and functions defined in the QgisInterface class:
help(iface)
Help on QgisInterface in module qgis.gui object:
class QgisInterface(PyQt4.QtCore.QObject)
| QgisInterface()
|
| Method resolution order:
| QgisInterface
| PyQt4.QtCore.QObject
| sip.wrapper
| sip.simplewrapper
| __builtin__.object
|
| Methods defined here:
|
| composerAdded = <unbound signal composerAdded>
| composerWillBeRemoved = <unbound signal composerWillBeRemoved>
| currentLayerChanged = <unbound signal currentLayerChanged>
| initializationCompleted = <unbound signal initializationCompleted>
| newProjectCreated = <unbound signal newProjectCreated>
| projectRead = <unbound signal projectRead>
| ------------------------------------------------------------------
| Data and other attributes defined here:
|
| actionAbout = <built-in function actionAbout>
| actionAddAllToOverview = <built-in function actionAddAllToOverview>
| actionAddFeature = <built-in function actionAddFeature>
| actionAddOgrLayer = <built-in function actionAddOgrLayer>
|
| ...
|
| zoomFull = <built-in function zoomFull>
| zoomToActiveLayer = <built-in function zoomToActiveLayer>
| zoomToNext = <built-in function zoomToNext>
| zoomToPrevious = <built-in function zoomToPrevious>
We only listed the first few and last four functions supported by iface.
Later we’ll show you another way to get at this information using the QGIS
online API documentation.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 . 5 . YO U R F I R S T P Y Q G I S E X P E R I M E N T 17
Let’s use iface to manipulate the view. With the mouse, zoom into any
area of the world you like. Now we will use the console to zoom back to
the full extent:
iface.zoomFull()
When you press enter, the map is zoomed back out to the full extent of the
world_borders layer.
Now try:
iface.zoomToPrevious()
This zooms us to the previous view, which happens to be the area you
zoomed to with the mouse. If you try iface.zoomToNext(), you’ll find
it gives the same result as iface.zoomFull().
To illustrate a couple of other methods, we’ll open the attribute table and the
layer properties dialog for the active layer. First we have to get a reference
to the active layer. Make sure the world_borders layer is highlighted, then
in the console enter:
active_layer = iface.activeLayer()
Once we have a reference to the active layer, we can use the following to
open both its attribute table and the layer properties:
iface.showAttributeTable(active_layer)
iface.showLayerProperties(active_layer)
>>> iface
<qgis.gui.QgisInterface object at 0x115d78170>
>>> iface.zoomFull()
>>> iface.zoomToPrevious()
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
18 C H A P T E R 1 . I N T RO D U C T I O N
>>> iface.zoomToNext()
>>> active_layer = iface.activeLayer()
>>> active_layer
<qgis.core.QgsVectorLayer object at 0x1116c4440>
>>> iface.showAttributeTable(active_layer)
>>> iface.showLayerProperties(active_layer)
If you look at the documentation for the QgisInterface class you’ll see it
has a lot more methods than those we just used. We’ll talk more about the
QGIS API in a later chapter. This tour of the console has given you a small
taste of what you can accomplish using PyQGIS. We’ll dive in further when
we get to Chapter 6, Using the Console, on page 63.
1.6 Exercises
To complete the exercises, you’ll need to look at the QGIS API docu-
mentation found at http://doc.qgis.org/api/classQgisInterface.
html.
2. Determine which method you would use to add a raster layer to the
map.
3. Adding a shapefile to the map requires the full path to the layer, a
short name (basename), and a provider key (’ogr’). Using the console,
add the world_borders shapefile to the map.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
2
Python Basics
The help function can be used on individual methods to provide more de-
tail. In exercises for the last chapter, we used the addVectorLayer method
to add a shapefile to the map. If we examine the help for this method we
get:
help(iface.addVectorLayer)
Help on built-in function addVectorLayer:
20 C H A P T E R 2 . P Y T H O N BA S I C S
addVectorLayer(...)
QgisInterface.addVectorLayer(QString, QString, QString)
-> QgsVectorLayer
This not only shows us that the method requires three string arguments,
but also tells us it returns a QgsVectorLayer object. While helpful, your
best resource is the QGIS API online documentation, which we cover in
Chapter 5, Navigating the QGIS API, on page 51.
• Lists
• Dictionaries
• Classes
Here you can see we created a list containing five items, four strings and
one integer. A list uses a zero-based index to refer to an item. As you
can see from the above example, you can use the colon notation to refer to
a range of items in the list. To get the last item in a list, use [-1].
You can mix object types in a list, including adding other lists:
>>> new_list = ["apple", my_list]
>>> new_list
[’apple’, [’GIS’, ’QGIS’, ’Python’, 1, ’open source’]]
>>> new_list[1]
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
2 . 2 . DATA S T RU C T U R E S 21
The tuple
While we can change the value of an item in a list, you can’t do that with a
tuple:
>>> my_list
[’GIS’, ’QGIS’, ’Python’, 1, ’open source’]
>>> my_list[1] = ’QGIS 1.8’
>>> my_list
[’GIS’, ’QGIS 1.8’, ’Python’, 1, ’open source’]
>>> my_tuple
(’GIS’, ’QGIS’, ’Python’, 1, ’open source’)
>>> my_tuple[1] = ’QGIS 1.8’
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
22 C H A P T E R 2 . P Y T H O N BA S I C S
Tuples are faster than lists; use them when you need to iterate over a data
Using Tuples - Examples where you structure that never needs to change.
might want to use a tuple include days
of the week or months of the year. Lastly, you can convert between tuples and lists:
These never change and you can it-
erate over them quicker with a tuple. >>> a_tuple = tuple(my_list)
>>> a_tuple
(’GIS’, ’QGIS 1.8’, ’Python’, 1, ’open source’)
>>> a_list = list(my_tuple)
>>> a_list
[’GIS’, ’QGIS’, ’Python’, 1, ’open source’]
The dict
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
2 . 2 . DATA S T RU C T U R E S 23
c++/python
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
24 C H A P T E R 2 . P Y T H O N BA S I C S
The alternative (and often preferred) method is to wrap the code block in a
try/except block:
>>> try:
... print my_dictionary[’postgis’]
... except:
... print "Key not found"
...
Key not found
2.3 Classes
Classes are an integral part of Python and you’ll be working with them ex-
tensively when programming PyQGIS. Python classes, just like their coun-
terparts in other object oriented languages, can have methods and attributes,
allowing you to model objects. In QGIS there are a large number of classes
If you look back to the first chap- representing map layers, data stores, legends, symbology, and much more.
ter, you’ll recognize that we already
worked with some QGIS classes When creating a class, you need to think in terms of attributes that describe
when exploring the Python console.
it and what it can “do”. Here is a simple Python class that begins to model
a point:
The Point class has one attribute marker_size and two methods: draw
and move. We can create and use the class as follows:
>>> from simplepoint import Point
>>> my_point = Point()
>>> my_point.marker_size
4
>>> my_point.draw()
drawing the point
>>> my_point.move()
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
2.3. CLASSES 25
This trivial example of a class illustrates the basic concepts. Once you
instantiate your class, you can access the attributes and call methods.
We’ll use classes both in writing scripts that run from the console and in
plugins.
Subclassing a Class
Frequently you may need to create a class that inherits behavior from an
existing class. You do this by subclassing the class, which results in our
new class inheriting all the features of its parent.
Take for example the QgsPoint class. Using dir(QgsPoint) we can get a
list of its attributes:
Let’s create a new class that adds a Z value to the QgsPoint class by sub-
classing it:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
26 C H A P T E R 2 . P Y T H O N BA S I C S
11 self.z_value = z
12
13 def z(self):
14 return self.z_value
First we have to make sure we have imported the QgsPoint class from the
qgis.core module. Then in line 4 we define our new class Point3D, in-
heriting from QgsPoint. This will give our new class all the methods and
attributes of QgsPoint, plus those we will add.
In line 7 we call the base class __init__ method, passing it the values of x
and y. In line 8 we set z_value using the z argument that was passed.
If you look back at the attributes of QgsPoint, you’ll see there are methods
to set the X and Y values, as well as return them. We need the same methods
for our Z value. Lines 10 and 11 set the Z value and in lines 13 and 14 we
provide the code needed to return it. Here is an example of how we can use
our new class:
You can see our added methods work as expected. If we list the attributes
of Point3D, you’ll see all those of QgsPoint, as well as our new methods:
>>> dir(Point3D)
[’__class__’, ’__delattr__’, ’__dict__’, ’__doc__’,
’__eq__’, ’__format__’, ’__ge__’, ’__getattribute__’,
’__getitem__’, ’__gt__’, ’__hash__’, ’__init__’, ’__le__’,
’__len__’, ’__lt__’, ’__module__’, ’__ne__’, ’__new__’,
’__reduce__’, ’__reduce_ex__’, ’__repr__’, ’__setattr__’,
’__sizeof__’, ’__str__’, ’__subclasshook__’,
’__weakref__’, ’azimuth’, ’multiply’, ’onSegment’, ’set’,
’setX’, ’setY’, ’setZ’, ’sqrDist’, ’sqrDistToSegment’,
’toDegreesMinutes’, ’toDegreesMinutesSeconds’, ’toString’,
’wellKnownText’, ’x’, ’y’, ’z’]
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
2.3. CLASSES 27
There is a more work to do if we want to add full “Z” support to our new
point class, such as overriding the toString and wellKnownText methods to
include Z values. For example, if we call the toString method on our new
class, it returns the X and Y values, but our Z value is nowhere to be found:
>>> pt.toString()
u’100, 100’
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
28 C H A P T E R 2 . P Y T H O N BA S I C S
We can easily create our own map tool that emits the coordinates of a click
on the map canvas by inheriting from QgsMapToolEmitPoint. This gives
us access to the canvasClicked method (a signal) that provides us with both
the location of the click and the mouse button that triggered it:
canvasClicked (const QgsPoint &point, Qt::MouseButton button)
The deeper you go into PyQGIS programming, the more you’ll find cases
where subclassing the QGIS API objects provides the functionality you
need.
There’s more to Python classes such as private variables and private meth-
ods. If you want to dig into the details, refer to some of the resources at the
end of this chapter.
Strings
You’ll use strings everywhere in your code. In this section we’ll use an
interactive Python session to illustrate some of the concepts.
>>> s = "QGIS loves Python"
>>> # split on whitespace
... s.split()
[’QGIS’, ’loves’, ’Python’]
>>> # split into variables
... (a, b, c) = s.split()
>>> print a, b, c
QGIS loves Python
>>> # slice
... s[0:1]
’Q’
>>> s[-1:]
’n’
>>> s[:-1]
’QGIS loves Pytho’
>>> # split on a character
... s.split(’o’)
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
2 . 4 . S T R I N G S , R A N G E S , A N D OT H E R H A N DY T H I N G S 29
Notice in all the string operations above, the result is a Python list.
Python comes with powerful string formatting capabilities. You can use
either the % operator or the format method:
>>> s = ’loves’
>>> "QGIS %s Python" % s
’QGIS loves Python’
>>> "QGIS {} Python".format(s)
’QGIS loves Python’
>>> # with two strings:
... s2 = ’Python’
>>> "QGIS %s %s" % (s, s2)
’QGIS loves Python’
>>> "QGIS {} {}".format(s, s2)
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
30 C H A P T E R 2 . P Y T H O N BA S I C S
Which should you use? It depends on whether you need the full power
or format or just need some simple formatting. The format method has a
lot of options for formatting strings and numbers, as well as using named
replacement fields:
Ranges are handy when you need a list of integers to use in a for loop:
>>> range(0, 10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> range(1, 10, 2)
[1, 3, 5, 7, 9]
>>> range(100, 0, -10)
[100, 90, 80, 70, 60, 50, 40, 30, 20, 10]
The third argument in the range function specifies the step—by default 1.
In a function with default arguments you specify a value for each optional
argument. Let’s say we have a function that draws a circle:
def draw_circle(radius, color=’blue’, line_width=1):
print radius, color, line_width
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
2 . 4 . S T R I N G S , R A N G E S , A N D OT H E R H A N DY T H I N G S 31
>>> draw_circle(10)
10 blue 1
>>> draw_circle(10, ’red’)
10 red 1
>>> draw_circle(10, ’red’, 2)
10 red 2
Using keyword arguments, we can call the same function like this:
>>> draw_circle(10, color=’red’)
10 red 1
>>> draw_circle(10, line_width=2)
10 blue 2
>>> draw_circle(10, line_width=2, color=’red’)
10 red 2
>>> draw_circle(line_width=2, color=’red’, radius=15)
15 red 2
The error seems misleading—the function takes at least one argument and
we gave it two, yet it still complained. This is because radius is mandatory
and we didn’t supply it.
Another useful way to call our function is using a dictionary with the **
operator:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
32 C H A P T E R 2 . P Y T H O N BA S I C S
20 red 1.5
>>> args = {’color’ : ’red’, ’radius’ : 20}
>>> draw_circle(**args)
20 red 1
Here we passed the args dictionary to the function and it works similar
to using named arguments. Notice in the last example, we didn’t add the
line_width to our dictionary and the function used the default value we
specified in the function definition.
We’ve only touched on a small part of Python, but you’ll see some of the
things in the previous examples when we get into writing code. As always,
refer to the documentation at http://docs.python.org.
This confirms that the package exists and we have the correct name. In-
stalling it is simple:
ophir:pyqgis gsherman$ pip install sphinx
Downloading/unpacking sphinx
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
2 . 6 . D O C U M E N T I N G YO U R C O D E 33
Notice pip isn’t case sensitive---it was happy to install Sphinx even though
we used lower case in the install command. In addition to installing Sphinx,
pip also installed two dependencies for us: Pygments and docutils. 7
pip freeze generates a list suit-
able for a requirements.txt file
You can see which packages are installed on your system using pip list. which can be used by pip to install
a set of packages using the -r or
Older versions of pip don’t support the list command—in this case you can --requirement switch.
use pip freeze instead7 .
Using pip list will generate an often lengthy list of all the packages in-
stalled on your system, along with the version number:
docutils (0.10)
docutils-ext (0.2d)
pdfrw (0.1)
pydns (2.3.6)
Pygments (1.6)
reportlab (2.7)
rst2pdf (0.93.dev)
see (1.0.1)
validate-email (1.1)
wsgiref (0.1.2)
Remember our example Point class? If we import it and look at the help we
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
34 C H A P T E R 2 . P Y T H O N BA S I C S
get this:
>>> from point import Point
>>> help(Point)
class Point
| Methods defined here:
|
| draw(self)
|
| move(self, new_x, new_y)
|
| ---------------------------------------
| Data and other attributes defined here:
|
| marker_size = 4
This is nice, but it is also sparse. We see the methods and attributes defined
but no description of what they actually do. Here is a new, documented
version of our code:
Now when we use the help function we can see our documentation for each
method
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
2 . 6 . D O C U M E N T I N G YO U R C O D E 35
class Point
| Class to model a point in 2D space.
|
| Methods defined here:
|
| draw(self)
| Draw the point on the map canvas
|
| move(self, new_x, new_y)
| Move the point to a new location on the
| map canvas
|
| ---------------------------------------
| Data and other attributes defined here:
|
| marker_size = 4
This gives us more information about what each method does. Many docu-
mentation generators (for example, Sphinx) use these docstrings to generate
a nicely formatted set of documentation for your code.
Keeping it Clean
As you write Python code, remember to keep it consistent and nicely for-
matted. One way is to use the pep8 tool to check your code. You can install
pep8 using pip or easy_install. To use it, run it from the command line
and pass the name of your Python script:
pep8 point.py
If you don’t get any results, it means your code passes, otherwise a list of
potential problems will be presented for you to consider and correct:
$ pep8 simplepoint.py
simplepoint.py:2:1: W293 blank line contains whitespace
Many of the errors may seem inconsequential, but it doesn’t hurt to keep
your code as clean as possible. For more information on formatting your
Python code, see the Style Guide for Python Code8 . 8
http://www.python.org/dev/
peps/pep-0008
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
36 C H A P T E R 2 . P Y T H O N BA S I C S
In addition, there are a number of fine textbooks and tutorials available from
your online or local bookseller.
In reality, you can get started with PyQGIS with a modicum of Python
knowledge and pick up the rest along the way. Feel free to dig in and get
started.
2.8 Exercises
1. Write a function to accept an x and y value and using string format-
ting, print it with a precision of four decimal places.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
3
Setting Up Your Development Tools
1. Python 2.x
3. PyQt
3.1 Python
Depending on your operating system, you may find that you already have
Python installed.
Python is usually installed as part of Linux and OS X. You can easily test
this by opening a terminal or shell prompt and typing:
python
$ python
Python 2.7.2+ (default, Oct 4 2011, 20:06:09)
[GCC 4.6.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>
If your version number is 2.x then you are set to go. If not, or attempting to
run python gives an error, you’ll have to install a proper version of Python.
On Linux, use your package manager to locate and install Python. Mac OS
X version through Mountain Lion come with Python 2.x.
Windows
Unless you build your own version of QGIS from source, you’ll use either
the Standalone or OSGeo4W installer9 . This will provide you with a version
9
http://loc8.cc/pq/installer of Python and associated libraries/modules that work with QGIS.
When installing, do yourself a favor and choose a path that doesn’t contain
spaces to house your new QGIS installation. This will make setting up for
your PyQGIS work much simpler.
If you are using Windows, installing QGIS using the OSGeo4W Installer
will give you everything you need to develop with PyQGIS.
If you choose to use an editor, make sure you choose one that provides
syntax highlighting and indentation for your code. Here are some of the
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
3 . 2 . I D E O R E D I TO R 39
• Vim - http://www.vim.org
• Emacs - http://www.gnu.org/software/emacs
• Notepad++ - http://notepad-plus-plus.org
• jEdit - http://www.jedit.org
There are a bunch of editors out there that will do the job. Choose one you
are comfortable with and makes coding easier. For a comprehensive list,
see the Python Wiki10 . 10
http://loc8.cc/pq/editors
Using an IDE
While you can write code in an editor and debug it from a shell, an IDE
can make the process easier, especially if you want to debug your plugin in
QGIS. There are a lot of Python IDE’s to choose from—some of them are
free and others are not. Let’s take a look at a couple of them.
PyDev
PyDev is a freely available IDE for developing with Python. Below you
will find instructions for installing the Eclipse framework and PyDev.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
40 C H A P T E R 3 . S E T T I N G U P YO U R D E V E L O P M E N T TO O L S
7. Click OK
8. Once the list is populated, click the checkbox next to PyDev and click
the Next button
10. Once the install is complete, you will be prompted to restart Eclipse
11. Once Eclipse restarts, open the PyDev perspective by choosing Window->Open
Perspective->Other from the menu
PyCharm
PyCharm provides code completion for both PyQt and the QGIS modules,
making it easier to get started writing code. It also has a Vim emulator for
those of us that cherish that means of editing code.
As I said, there are a lot of other Python IDE’s out there; some free and
some not. For a fairly comprehensive collection, see the list on the Python
12
http://loc8.cc/pq/ide Wiki12 .
3.3 Qt/PyQt
PyQt is the Python API interface to Qt, the C++ framework that QGIS is
based on. Because QGIS is built on the Qt framework, we use PyQt when
creating plugins to provide all the GUI elements we might need.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
3.3. QT/PYQT 41
Installing PyQt
If you are using the Standalone or OSGeo4W Windows installers, you al-
ready have Qt and PyQt installed as part of the QGIS installation.
For Mac users, the Kyngchaos binaries include both the Qt and PyQt run-
time libraries. If you want to create user interfaces using Qt Designer, you’ll
have to install the Qt Tools package that is part of the full Qt install for
Mac.13 13
http://download.qt-project.
org
On Linux, you can use your package manager to install Qt and PyQt. If you
want to develop user interfaces, be sure to get both the Qt and PyQt tools
packages.
Next up, we’ll look at the QGIS/Python “ecosystem” and how plugins work
with QGIS.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
4
The QGIS/Python Ecosystem
This chapter explains a bit of history and how Python and QGIS work to-
gether. You’ll also learn how to manage Python plugins in QGIS.
4.1 History
Early in the development of QGIS, a plugin architecture was designed to
provide a way to dynamically add new features and capabilities. This archi-
tecture was created using C++. Since writing a QGIS plugin in C++ is not
a trivial task, the number of potential contributors was limited.
With the first release of Python support, the number of contributed plugins
grew at an exceptional rate, contributing to the overall growth of QGIS as a
first-class desktop GIS.
“sip” files are compiled as part of QGIS to provide the Python interface to
14
http://pypi.python.org/ the classes. The Python support is divided into several modules:
pypi/SIP
• qgis.core - core classes
• qgis.gui - graphical user interface classes
• qgis.analysis - analysis related classes
• qgis.networkanalysis - classes for network analysis
To use the QGIS classes in one of the modules, you simply import it in
Python, then call the desired methods and/or access the attributes:
>>> from qgis.core import *
>>> v = QgsVectorLayer()
>>> v.setTitle(’Sample Layer’)
>>> v.title()
PyQt4.QtCore.QString(u’Sample Layer’)
In this book, we’ll focus on the use of the first two modules. As a PyQGIS
developer, you generally don’t need to be concerned about how the integra-
tion works; everything you need is built-in to your QGIS distribution.
Contributed plugins can become core plugins if they are found to provide
key functionality advantageous to the majority of QGIS users.
You can find and install contributed plugins from one or more plugin repos-
itories.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
4 . 4 . P L U G I N R E P O S I TO R I E S 45
Anyone can contribute a Python plugin to the official repository. Later we’ll
see how to build and package your plugin so it is ready to be shared with
other QGIS users.
Figure 4.1, on the next page, shows what the Plugin Manager looks like
when first opened from the menu. The default view is a list of installed
plugins, along with some management functions to enable/disable, upgrade,
uninstall, and reinstall a plugin. Plugins that have a check mark are those
that are currently enabled. Additionally, the right-panel gives you some
information about the installed plugins and how to enable/disable them.
You can distinguish between core and contributed plugins by examining the
directory where each is installed. Core plugins are installed in the same di-
rectory as the QGIS application. Contributed (Python) plugins are installed
in a subdirectory of your HOME directory (see Section 4.6, Python Plugin
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
46 CHAPTER 4. THE QGIS/PYTHON ECOSYSTEM
Plugins are installed using the Plugin Manager. When you first open the
Plugin Manager, the configured repositories are queried and the list of plu-
gins is updated and displayed by category: Installed, Get more, Upgrade-
able, and Invalid.
To install a new plugin, select Get more from the left panel and locate the
plugin of interest in the list, highlight it, and click the Install plugin button
to complete the install
Figure 4.2, on the next page shows the Plugin Manger ready to install the
PinPoint plugin.
Use the Search box to narrow the list of plugins based on name, descrip-
tion, tags, or author.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
4 . 5 . M A NAG I N G P L U G I N S 47
Clicking Settings in the left panel of the Plugin Manger brings up the option
panel shown in Figure 4.3, on the following page.
You can control how often QGIS checks for plugin updates by selecting an
interval from the drop-down box.
Some plugins are tagged as experimental by their authors. This means they
may not be ready for prime time, have an incomplete feature set, or be
buggy. You can choose to have these shown in the Plugin Manager if you
like by clicking the checkbox.
The last setting block allows you to add repositories. Out of the box, QGIS
has the master repository configured for you. If you need access to other
repositories (such as one you set up for development purposes), add them
to the list by giving them a name and the URL that points to the XML file
describing the repository. We’ll show you how to set up your own repository
in Section 8.14, Setting Up a Repository, on page 116.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
48 CHAPTER 4. THE QGIS/PYTHON ECOSYSTEM
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
4.7. EXERCISES 49
ment practices and how to package a plugin in Chapter 10, Writing Plugins,
on page 131.
4.7 Exercises
1. Run QGIS and use the Plugin Manager to install the following plug-
ins (we’ll need them later on):
a. ScriptRunner
b. Plugin Builder
4. Use the Plugin Manager to disable ScriptRunner and note the changes
to the menu and plugin toolbar.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
5
Navigating the QGIS API
Before we can really get into PyQGIS programming, we need a basic un-
derstanding of how to use both the QGIS and Qt API documentation. Since
there are over 500 QGIS classes listed in the documentation, we obviously
won’t be going through them all. In this chapter we’ll try to give you a
broad overview, as well as specifics on how to decipher the documentation.
The documentation for the constructor (the way we create the object) for
QgsVectorLayer looks like this:
Since PyQt “maps” Python types to C++ types, we can just use Python
strings to create the layer.
Python and C++ Types
Here are some Python equivalents to the Qt/C++ types you’ll see in the API
documentation:
Qt/C++ Python
QList, QSet, QVector list
QMap dict
QString string
bool bool
Notice we didn’t specify a value of loadDefaultStyleFlag. In the docu-
mentation, any argument followed by and equal sign is optional. By not
specifying loadDefaultStyleFlag, it defaulted to True. In fact, we can create
a QgsVectorLayer without specifying any arguments:
layer = QgsVectorLayer()
This is because all of the arguments in the constructor are optional. Al-
though we can create a layer in this manner, it isn’t very useful:
>>> layer = QgsVectorLayer()
>>> layer.isValid()
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
5 . 3 . S I G NA L S A N D S L OT S 53
False
Although layer is a valid object, it isn’t a valid layer since no data source or
provider was specified in its creation. Just because a constructor or method
has default values doesn’t mean we should always depend on them.
Let’s take a look at another QGIS class: QgsPoint, which represents a sim-
ple 2D point. In the QGIS documentation, we find three ways to construct
a point:
QgsPoint ()
QgsPoint (const QgsPoint &p)
QgsPoint (double x, double y)
The first method creates an “empty” point object at 0,0. The second creates
a new point from an existing one, and the last creates a point from supplied
x and y values:
>>> p1 = QgsPoint()
>>> p1
(0,0)
>>> p2 = QgsPoint(21.2, 100.9)
>>> p2
(21.2,100.9)
>>> p3 = QgsPoint(p2)
>>> p3
(21.2,100.9)
Now that we have a basic idea of how individual classes are documented,
let’s take a brief side trip into the world of Signals and Slots.
Menu and toolbar items in Qt (and therefore QGIS) are usually created
using a QAction object:
self.zoomin_action = QAction(
QIcon(":/ourapp/zoomin_icon"),
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
54 C H A P T E R 5 . NAV I G AT I N G T H E Q G I S A P I
"Zoom In",
self)
This creates an action with an icon and a text label of “Zoom In”. An action
must be added to a menu and/or toolbar before it is of any practical use.
If we look at the PyQt documentation for QAction15 , we find a number of
signals associated with it:
• changed()
• hovered()
• toggled(bool)
15
http://pyqt.sourceforge. • triggered(bool = 0)
net/Docs/PyQt4/qaction.html
Although the other signals may be of interest, when an action is placed on a
menu and/or toolbar we are primarily concerned with the triggered signal.
This signal is emitted when the user clicks on the menu item or toolbar
button or presses the action’s keyboard shortcut.
This means that when our zoomin_action is triggered, the zoom_in method
(defined elsewhere in our code) will be called.
You can think of it as a listener that waits for a signal to be emitted and then
does something in response. For an example of an action connected to a
slot, see Section 12.4, Adding Map Tools to the Application, on page 168.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
5 . 4 . T H E L AYO U T O F Q G I S A N D P Y Q T D O C U M E N TAT I O N 55
Public Slots
These are methods we can connect to from a QAction or some other Qt GUI
element such as a drop-down box or checkbox.
While these are slots, they can be called like any other method in the class:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
56 C H A P T E R 5 . NAV I G AT I N G T H E Q G I S A P I
>>> lyr.metadata()
u’<html><body><p class="subheaderglossy">General</p>\n<p
class="glossy">Storage type of this layer</p>\n<p>ESRI
Shapefile</p>\n<p class="glossy">Description of this
provider</p>\n<p>OGR data provider (compiled against GDAL/OGR
library version 1.10.0, running against GDAL/OGR library
version 1.10.0)</p>\n<p class="glossy">Source for this
layer</p>\n<p>/data/alaska.shp</p>\n<p
class="glossy">Geometry type of the features in this
layer</p>\n<p>Polygon</p>\n<p class="glossy">The number of
features in this layer</p>\n<p>653</p>\n<p
class="glossy">Editing capabilities of this layer</p>\n<p>Add
Features, Delete Features, Change Attribute Values, Add
Attributes, Delete Attributes, Create Spatial Index, Fast
Access to Features at ID, Change Geometries</p>\n<p
class="subheaderglossy">Extents</p>\n<p class="glossy">In
layer spatial reference system units</p>\n<p>xMin,yMin
-7115212.98,1368239.61 : xMax,yMax
4895579.81,7805331.22</p>\n<p class="glossy">Layer Spatial
Reference System</p>\n<p>+proj=aea +lat_1=55 +lat_2=65
+lat_0=50 +lon_0=-154 +x_0=0 +y_0=0 +datum=NAD27 +units=us-ft
+no_defs</p>\n</body></html>’
The metadata() method returns the metadata for our layer in HTML format.
If you were developing a plugin or standalone application and want to dis-
play the metadata for a layer in a QTextBrowser you could connect a button
click to the slot to fetch the HTML, then insert it into your QTextBrowser.
Signals
QgsVectorLayer has a fair number of signals; here a few of them from the
documentation:
void attributeAdded (int idx)
Will be emitted, when a new attribute has been added to this
vector layer.
void attributeDeleted (int idx)
Will be emitted, when an attribute has been deleted from this
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
5 . 4 . T H E L AYO U T O F Q G I S A N D P Y Q T D O C U M E N TAT I O N 57
vector layer.
void attributeValueChanged (QgsFeatureId fid, int idx, const QVariant &)
void beforeCommitChanges ()
Is emitted, before changes are commited to the data provider.
void beforeRollBack ()
Is emitted, before changes are rolled back.
...
void editingStarted ()
Is emitted, when editing on this layer has started.
void editingStopped ()
Is emitted, when edited changes successfully have been written
to the data provider.
Any time you want to perform an action based on some event in your plugin
or application, you would connect one of these signals to the appropriate
method or function in your code.
We won’t list the methods here—just remember that when you want to
know what a QGIS class can do, look at the public member functions.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
58 C H A P T E R 5 . NAV I G AT I N G T H E Q G I S A P I
Static functions or methods are different than the methods we’ve mentioned
previously. Most of the methods require an instance of the class before you
can access them. In this snippet of code, layer is the variable that holds an
instance of QgsVectorLayer:
layer = QgsVectorLayer(’/data/alaska.shp’, ’Alaska’, ’ogr’)
Since the static method in QgsVectorLayer isn’t very suitable for our ex-
ample, let’s illustrate using QgsLogger. QgsLogger is a class that writes
information to the console; not the Python console, but the command shell
that QGIS was started from. Here is a list of static methods available in
QgsLogger:
static void critical (const QString &msg)
Goes to qCritical.
static void debug (const QString &var, int val, int debuglevel=1,
const char *file=NULL, const char *function=NULL, int line=-1)
Similar to the previous method, but prints a variable int-value pair.
static void debug (const QString &var, double val, int debuglevel=1,
const char *file=NULL, const char *function=NULL, int line=-1)
Similar to the previous method, but prints a variable double-value pair.
template<typename T >
static void debug (const QString &var, T val, const char *file=0,
const char *function=0, int line=-1, int debuglevel=1)
Prints out a variable/value pair for types with overloaded operator<<.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
5 . 4 . T H E L AYO U T O F Q G I S A N D P Y Q T D O C U M E N TAT I O N 59
void about ( QWidget * parent, const QString & title, const QString & text )
void aboutQt ( QWidget * parent, const QString & title = QString() )
StandardButton critical ( QWidget * parent, const QString & title, const QString & text,
StandardButtons buttons = Ok, StandardButton defaultButton = NoButton )
StandardButton information ( QWidget * parent, const QString & title, const QString & text,
StandardButtons buttons = Ok, StandardButton defaultButton = NoButton )
StandardButton question ( QWidget * parent, const QString & title, const QString & text,
StandardButtons buttons = Ok, StandardButton defaultButton = NoButton )
StandardButton warning ( QWidget * parent, const QString & title, const QString & text,
StandardButtons buttons = Ok, StandardButton defaultButton = NoButton )
Which gives us the simple about box shown in Figure 5.1, on the following
page.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
60 C H A P T E R 5 . NAV I G AT I N G T H E Q G I S A P I
This static method gives us a little warning message as shown in Figure 5.2.
Static public methods are quick and easy to use and you’ll find them through-
out both the QGIS and Qt API. Be sure to look through the API documen-
tation for a class to familiarize yourself with the methods available.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
5 . 5 . C H O O S I N G B E T W E E N T H E Q T A N D P Y Q T D O C U M E N TAT I O N 61
and Python. In C++ many of the methods “fill” a variable you provide to
the method. In these situations, Python returns a list.
Let’s look at a simple example using the static method getText in QInputDialog.
The getText method allows you to present a simple dialog box to the user
and get a string in return.
If we provide the ok argument, getText will fill it with true if the user
pushes the OK button and false if they cancel the dialog. In C++ we
would do something like:
bool ok;
QString text = QInputDialog::getText(this, "Enter Your Name"),
"Name:", QLineEdit::Normal,
’Nobody’, &ok);
When the dialog is closed, we could check to see if ok is true and then
proceed to use the string returned in our text variable.
Notice the return value. Instead of getText returning a string, it returns a list
containing the user entered string (if any), and a boolean value indicating
which button was clicked: True for OK or False for Cancel.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
62 C H A P T E R 5 . NAV I G AT I N G T H E Q G I S A P I
Next we’ll fire up the QGIS Python console and start using some of our
PyQGIS skills.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
6
Using the Console
The QGIS Python console is great for doing one-off tasks or experimenting
with the API. Sometimes you might want to automate a task using a small
script, and do it without writing a full blown plugin.
In this chapter we’ll take a look at using the console to explore the workings
of PyQGIS.
The lower panel of the console is the input area; results of input are dis-
played in the upper panel.
On the left of the console you’ll see a toolbar that contains the following
tools, top to bottom:
Clear console: Clears the console of all previous commands and output
Console Options
Clicking the Settings button brings up the console options dialog as shown
in Figure 6.2, on the next page.
You can set the font for both the Console and the Editor, as well as enabling
autocompletion. For the Editor, you can enable the object inspector which
allows you to get a nice view of your code.
Lastly, you can choose to either use the preloaded API files or untick the box
and manually add your own. Generally you can stick with the preloaded
files as they contain the information needed for autocompletion in the PyQt
and QGIS API.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
6 . 2 . U S I N G T H E C O N S O L E E D I TO R 65
writing concise scripts, prototyping, and testing. Usually you will want to
use your favorite editor or IDE when working on a larger project.
The Toolbar
The Console Editor toolbar is arranged vertically, with the following tools
top to bottom:
Open file: Open a Python script located on disk. Multiple files can be
opened, each assigned to a new tab.
Save As...: Save the current script to a with a new name or location
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
66 CHAPTER 6. USING THE CONSOLE
Find Text: Search the current script for occurences of a give text string
Paste: Paste the contents of the clipboard at the current cursor location
4. Using the Editor, add the my_function function to the bottom of simple_point.py
6. Open the Object Inspector and click the ’+’ to expand the Point node
>>> my_function()
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
6 . 2 . U S I N G T H E C O N S O L E E D I TO R 67
When we click Run script in the Editor, it executes our code in the console.
Since our code just defines a class and one funciton, there is no output in
the console, however our class and function are now available for use.
In the console panel (left side), you can see the output from our commands.
We’ll take another look at this when we get to Section 7.1, Standalone
Scripts in the Console, on page 75.
Now that we have an overview of the console and editor, let’s put it to use.
In the Introduction, we used the console to manipulate the map view in
QGIS using methods exposed by the qgis.util.iface object. We’ll take it a
bit further now to actually load some data and work with the interface.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
68 CHAPTER 6. USING THE CONSOLE
With QGIS running, open the console using the Plugins->Python Console
menu. Since the qgis.core and qgis.gui modules are imported at startup,
we can start using the API immediately.
path:
The full path to the layer
basename:
A name to be used in the legend
providerLib:
The data provider to be used with this layer
loadDefaultStyleFlag:
Use the default style when rendering the layer. A style file has a .qml
extension with same name as the layer and is stored in the same location.
It is possible to create a vector layer that isn’t valid. For example, we can
specify a bogus path to a shapefile:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
6 . 3 . L OA D I N G A V E C TO R L AY E R 69
Notice there is no complaint from QGIS, even though the shapefile doesn’t
exist. For this reason, you should always check the validity of a layer before
adding it to the map canvas:
>>> bogus.isValid()
False
If the isValid() method returns False, there is something amiss with the layer
and it can’t be added to the map.
Getting back to our valid, world_borders layer, you’ll notice nothing hap-
pened on the map canvas. We created a layer, however, for it to draw, we
need to add to the list of map layers. To do this, we call a method in the
QgsMapLayerRegistry:
QgsMapLayerRegistry.instance().addMapLayer(wb)
Once we do that, the layer is drawn on the map as shown in Figure 6.4.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
70 CHAPTER 6. USING THE CONSOLE
The layer is removed from both the map canvas and the legend, then the
map is redrawn.
To get a bit of information about the symbol, we can use the dump() method:
>>> symbol.dump()
u’FILL SYMBOL (1 layers) color 134,103,53,255’
The output shows us that our layer is rendered using a fill symbol (which
we already knew) using a color with RGB values of 134, 103, 53 and no
transparency. Let’s change the color to a dark red and refresh the map:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
6 . 4 . E X P L O R I N G V E C TO R S Y M B O L O G Y 71
Let’s analyze what we did here. To be able to change the color, we imported
the QColor class from the PyQt4.QtGui module. The setColor method takes
a QColor object as an argument.
If you try the first method, you’ll get an error because we haven’t imported
Qt. The fix is to import it prior to referencing Qt.red:
from PyQt4.QtCore import Qt
The last method is interesting in that it includes a value for the alpha-
channel (transparency). Using this method of creating the color, we can
also set the transparency of the layer. Each of these methods is described in
the QColor documentation.
You’ll notice that nothing happens on the map canvas when we change the
color. We must tell QGIS to update the map canvas to reflect the changes to
our layer:
>>> iface.mapCanvas().refresh()
This should redraw the map and our layer is now filled with a dark red color.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
72 CHAPTER 6. USING THE CONSOLE
The other is to test to see if there is a cached image for the layer and, if so,
invalidate it and then repaint:
In practice, the first method should work in all situations, even if it is a bit
“tacky” if render caching is not enabled.
Now that the layer is rendered in our new color, take a look at the legend—it
still shows the previous color. To update it we need to call the refreshLay-
erSymbology method of the legend interface:
iface.legendInterface().refreshLayerSymbology(wb)
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
6 . 6 . DATA P ROV I D E R S 73
At present there over ten data providers included with QGIS, covering a
large portion of the data stores you will encounter, from file-based to rela-
tional databases.
You may be wondering what data providers are available and how you
specify them when loading layers. The QgsProviderRegistry class is re-
sponsible for registering all the data providers when QGIS starts up—the
providerList() method will return a list of all providers. From the console
you can display all the available provider keys:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
74 CHAPTER 6. USING THE CONSOLE
In QGIS, you can use the Providers tab in the About dialog to view a de-
scription of all the available providers. The list of provider keys above
corresponds to:
6.7 Exercises
1. Using the console, add the world borders layer to the map canvas,
then:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
7
Running Scripts
In this chapter we’ll take a look at running standalone scripts and then use
both the Python Console and the Script Runner plugin to load, manage, and
run our scripts.
Our task list may look a bit ambitious, however it won’t take much code to
accomplish:
Let’s take a look at how this script works. In lines 1 through 4 we import the
modules we’ll need. When writing anything other than a trivial script, you’ll
find you usually need to import PyQt4.QtCore, PyQt4.QtGui, qgis.core, and
Depending on how we run our script, qgis.gui. In this case we didn’t need the qgis.gui module. We also imported
we may not need the qgis.utils.iface
qgis.utils.iface since it provides access to some of the functions we’ll need.
import. If executed from the Python
console editor, the import is not
needed. To make the script flexible,
The script is divided up into three methods that do the work:
it’s best to include it.
• load_layer
• change_color
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
7 . 1 . S TA N DA L O N E S C R I P T S I N T H E C O N S O L E 77
• open_attribute_table
All of the methods used in the script were explained in the previous chap-
ters, so let’s look at how to actually run this script in the QGIS Python
console.
The first thing we need to do is make sure QGIS (and Python) can find our
script. If you load and run your script from the editor you don’t have to
worry about setting the path, however, you may want to run scripts from the
console prompt. In this case, there are a number of ways to set the path:
• Linux: /home/gsherman/pyqgis_scripts
• Mac: /Users/gsherman/pyqgis_scripts
• Windows: C:/pyqgis_scripts
Let’s take a look at each of these methods and see how we might use them.
If we want to run this on Windows, the only difference is the append state-
ment:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
78 C H A P T E R 7 . RU N N I N G S C R I P T S
>>> sys.path.append(’C:/pyqgis_scripts’)
If you get errors when importing your script, check to make sure you have
the path to pyqgis_scripts specified correctly in either the sys.path or
with the PYTHONPATH environment variable.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
7 . 1 . S TA N DA L O N E S C R I P T S I N T H E C O N S O L E 79
When you call each of the methods in our script, you’ll see that the world_borders
layer is added to the map, the color is changed to red and the legend is up-
dated. The last two methods open the attribute table and layer properties
dialog respectively.
We can make our script a bit more elegant by refactoring it into a Python
class. This allows us to persist data during the lifetime of the class and
makes the code more friendly. The method(s) of setting the Python path
remain the same, but the way in which we use the class to execute the code
is a bit different. Here is the original script, refactored into a class:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
80 C H A P T E R 7 . RU N N I N G S C R I P T S
Let’s take a look at the class to see how it’s different than our original
script. First notice that we have added a new method named __init__. This
method is called when you create an instance of a class. Since a reference to
qgis.utils.iface is stored (self.iface) we no longer need to import iface from
qgis.utils. Now when a new instance of FirstScript is created, we will have
access to the iface attribute:
>>> fs = FirstScript(qgis.utils.iface)
>>> fs.iface
<qgis.gui.QgisInterface object at 0x1130d0440>
The remaining methods in our class look similar to those in the original
script, except each is defined with a self argument as seen in lines 11, 15,
and 23 of first_script_class.py. This is the standard way of defin-
ing class methods. The other difference is: we use self.iface to access the
activeLayer, refreshLayerSymbology, and showAttributeTable methods.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
7 . 2 . RU N N I N G S C R I P T S W I T H S C R I P T RU N N E R 81
>>> fs.change_color()
>>> fs.open_attribute_table()
This provides us with a bit cleaner and more object oriented approach to
our script.
Figure 7.2 shows the Script Runner plugin after you first start it up.
To run an existing script, you first add it to Script Runner using the Add
Existing Script tool on the toolbar. This will add it to a list in the left panel
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
82 C H A P T E R 7 . RU N N I N G S C R I P T S
and Script Runner will remember the next time you start it up. You can
remove a script using the Remove Script tool. This just removes it from the
list; it does nothing to the script file on disk.
Once you have a script loaded, you can click the Script Info tool to populate
the Info and Source tabs in the panel on the right. The Info tab contains the
docstring from your module and then a list of the classes, methods, and
functions found in the script. Having a proper docstring at the head of
your script will help you determine the purpose of script. You can view the
source of the script on the Source tab. This allows you to quickly confirm
that you are using the right script and it does what you think it will.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
7 . 2 . RU N N I N G S C R I P T S W I T H S C R I P T RU N N E R 83
28 fs = FirstScript(iface)
29 fs.load_layer()
30 fs.change_color()
31 fs.open_attribute_table()
Note that the run_script method is not indented the same as the other meth-
ods and is not part of the FirstScript class.
We can now add our script to Script Runner by clicking the Add Script
button and choosing it from our scripts directory. Once it is added, click
on the Info tab to get some basic information about the script as shown in
Figure 7.3.
Notice that it tells us we have no Docstring and we “really should add one.”
Let’s learn how to fix that now.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
84 C H A P T E R 7 . RU N N I N G S C R I P T S
class FirstScript:
...
If we reload the script in Script Runner, the docstring is displayed in the Info
tab. You can also document your class and methods, however Script Runner
doesn’t display them. Once documented, we can use help in the Python
interpreter or the QGIS Python console to display the documentation. You
can also use help to display the docstring for individual methods:
>>> help(fs.change_color)
Help on method change_color in module first_script_sr:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
7 . 2 . RU N N I N G S C R I P T S W I T H S C R I P T RU N N E R 85
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
86 C H A P T E R 7 . RU N N I N G S C R I P T S
You may have realized that using Script Runner has several advantages. For
one, we don’t have to fiddle with our Python path to run our script. Script
Runner takes care of all that once you add a script. Additionally, having all
your scripts organized in a neat list is handy, regardless of where they are
actually located on disk. The information and source viewing features of
the plugin are also useful.
Best of all, enabling an existing script to work with Script Runner is a sim-
ple matter of adding the run_script method, creating an instance of your
class, then calling one or more methods to do the job.
7.3 Exercises
1. The first_script.py script doesn’t check to see if the data layer is
valid. Modify first_script.py to implement this check and handle
any failures gracefully. Test your changes in the console.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8
Tips and Techniques
OGR Layers
This creates a layer from a shapefile and adds it to the map. Here are details:
imports:
qgis.core
88 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
classes:
QgsMapLayerRegistry, QgsVectorLayer
provider:
OGR
This example will work with any OGR supported layer. For example, we
can load a GML layer using:
gml_lyr = QgsVectorLayer(’/data/qgis_sample_data/gml/lakes.gml’,
’lakes’,
’ogr’)
QgsMapLayerRegistry.instance().addMapLayer(gml_lyr)
Memory Layers
QGIS also supports what’s known as a memory layer which can be very use-
ful when writing plugins and scripts. A memory layer doesn’t exist on disk
and disappears when QGIS exits, but behaves like any other vector layer.
If needed, a memory layer can be saved to disk in any format supported by
QGIS.
The second method is much quicker and easier to use because we can spec-
ify the coordinate system (CRS) and the fields all at once:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 1 . L OA D I N G V E C TO R L AY E R S 89
mem_layer = QgsVectorLayer(
"LineString?crs=epsg:4326&field=id:integer"
"&field=road_name:string&index=yes",
"Roads",
"memory")
This creates a linestring memory layer with two fields (id and road_name)
using the WGS84 coordinate system. We also included the ’index=yes’
keyword which tells the provider to create a spatial index for the features.
QgsMapLayerRegistry.instance().addMapLayer(mem_layer)
If we open the layer properties dialog and look at the Fields, we can see the
results as shown in Figure 8.1.
Looking at the Metadata also confirms that our layer was created success-
fully:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
90 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
Our new memory layer fully supports editing of both features, attributes,
and geometries. Since we just created it, there are no features and QGIS
reports the extent as unknown.
There are a couple of important points to note in the listing. First, in order
to add a feature to the layer we need to call the startEditing() method.
In line 3 we create a new QgsFeature object and in line 4 set the geometry
by passing the points list to the QgsGeometry.fromPolyline method.
Line 5 adds the attributes for our new feature: id of 1 and the road_name of
’QGIS Lane’.
In line 6 we add the feature to the memory layer and then commit the
changes in line 7. Now if we zoom to the layer extent we will see a sin-
gle road. Opening the attribute table confirms that the feature was created
and the attributes properly added.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 2 . L OA D I N G R A S T E R L AY E R S 91
Using iface to Load Layers
From a plugin or the console, you can add both vector and raster lay-
ers using iface methods (addVectorLayer and addRasterLayer). For
example:
Using this method saves you a step; the layer is added to the map without
having to call QgsMapLayerRegistry.instance().addMapLayer(). In our
examples, we used the two step method to help you understand the process.
When developing a standalone application using the QGIS API, you
won’t have access to the iface object.
raster_lyr = QgsRasterLayer(’/data/qgis_sample_data/raster/landcover.img’,
’Land Cover’)
QgsMapLayerRegistry.instance().addMapLayer(raster_lyr)
raster_lyr = iface.addRasterLayer(’/data/qgis_sample_data/raster/landcover.img’,
’Land Cover’)
The easiest way to figure out how to add a PostGIS layer is to add it using
the QGIS interface, then look at the connection information in the Metadata
tab of the layer properties dialog. This will give you all the parameters
needed to make a successful connection and add a layer.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
92 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
When we added disk-based layers (e.g. shapefile, GML), we used the full
path to the layer—the three lines above do the same for our database layer.
The remaining arguments are the streets3, the name that will appear in
the legend, and postgres, the provider key.
Sometimes you may want to send SQL to your PostgreSQL database from
a plugin or PyQGIS script. To do this, you need a connection to your
database. Since QGIS uses its own low-level interface to PostgreSQL, it
can’t provide a connection for general query use. You can, however, use the
information from the QGIS PostgreSQL data provider to create your own
connection using QSqlDatabase, a Qt class.
You could use QSettings to read the stored connection information from
your settings, but this may not always work if the password isn’t stored
with the connection.
Here is an example that uses the active layer in QGIS to get the connection
parameters, create a connection, and execute a query that returns some data:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 4 . WO R K I N G W I T H S Y M B O L O G Y 93
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
94 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
Simple Symbology
Each of the setColor statements above will set the symbol color to red.
After setting the color we must refresh the map canvas and the legend:
iface.mapCanvas().refresh()
iface.legendInterface().refreshLayerSymbology(layer)
Setting Transparency
This sets the transparency for the symbol to 50%. Be sure to refresh the
canvas and legend after making changes.
Customizing Symbols
For each symbol type (marker, line, fill) we can set additional properties to
control the appearance using the createSimple method. This method is
available for:
• QgsMarkerSymbolV2
• QgsLineSymbolV2
• QgsFillSymbolV2
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 4 . WO R K I N G W I T H S Y M B O L O G Y 95
angle
Specified in radians
color
Specified using any of the QColor constructor methods
color_border
Specified using any of the QColor constructor methods
horizontal_anchor_point
Integer value
name
Name of the marker (e.g. circle, square, etc)
offset
Specified as an x,y integer offset
offset_unit
MM or MapUnit
outline_width
Integer value
outline_width_unit
MM or MapUnit
scale_method
diameter or area
size
Integer value
size_unit
MM or MapUnit
vertical_anchor_point
Integer value
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
96 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
capstyle
’square’, ’flat’, or ’round’
color
Specified using any of the QColor constructor methods
customdash
Length and spacing of dash, separated by a semi-colon; e.g. 8;4
customdash_unit
MM or MapUnit
joinstyle
’bevel’, ’miter’, or ’round’
offset
Specified as an x,y integer offset
offset_unit
MM or MapUnit
penstyle
’no’, ’solid’, ’dash’, ’dot’, ’dash dot’, ’dash dot dot’
use_custom_dash
1 to use the custom dash
width
Integer value
width_unit
MM or MapUnit
border_width_unit
MM or MapUnit
color
Specified using any of the QColor constructor methods
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 4 . WO R K I N G W I T H S Y M B O L O G Y 97
color_border
Specified using any of the QColor constructor methods
offset
Specified as an x,y integer offset
offset_unit
MM or MapUnit
style
’solid’, ’horizontal’, ’vertical’, ’cross’, ’b_diagonal’, ’f_diagonal’, ’di-
agonal_x’, ’dense1’, ’dense2’, ’dense3’, ’dense4’, ’dense5’, ’dense6’,
’dense7’
style_border
’no’, ’solid’, ’dash’, ’dot’, ’dash dot’, ’dash dot dot’
width_border
Integer value
For example, to create a blue circle marker with a size of 8 and outline width
of 2 we would use:
sym = QgsMarkerSymbolV2.createSimple({
’name’:’circle’,
’color’:’blue’,
’size’:’8’,
’outline_width’:’2’
})
renderer = layer.rendererV2()
renderer.setSymbol(sym)
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
98 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
After setting the symbol, we must refresh the map canvas and legend for the
changes to be visible.
There are also properties that can be set to create a symbol using an ex-
pression that references data defined values. The keywords for each symbol
types are as follows:
QgsMarkerSymbolV2:
angle_expression
color_border_expression
color_expression
horizontal_anchor_point_expression
name_expression
offset_expression
outline_width_expression
size_expression
vertical_anchor_point_expression
QgsLineSymbolV2:
color_expression
width_expression
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 4 . WO R K I N G W I T H S Y M B O L O G Y 99
offset_expression
customdash_expression
joinstyle_expression
capstyle_expression
QgsFillSymbolV2:
color_expression
color_border_expresssion
width_border_expression
These expressions are similar to what you would set using the Data de-
fined properties button on the Layer properties dialog. For example, here is
how to create a simple marker that uses the MARKER_SIZE field in a layer’s
attribute table to set the symbol size.
sym = QgsMarkerSymbolV2.createSimple({
’name’:’circle’,
’color’:’blue’,
’size_expression’:’MARKER_SIZE’,
’outline_width’:’1’
})
renderer = layer.rendererV2()
renderer.setSymbol(sym)
Using Styles
• saveNamedStyle
• loadNamedStyle
• saveDefaultStyle
• loadDefaultStyle
• saveSldStyle
• loadSldStyle
For example, you can symbolize a layer the way you want it, then save the
style information to a file:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
100 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
layer = iface.activeLayer()
layer.saveNamedStyle(’/tmp/mystyle.qml’)
Note we have to refresh both the canvas and the legend in order to see the
applied style. Using the saveSldStyle/loadSldStyle methods do the same
thing, but save the style as a Styled Layer Descriptor file.
Using saveDefaultStyle creates a qml file on disk that will be applied each
time the layer. For example, if we load /gis_data/alaska.shp, style
it, and use saveDefaultStyle, it creates /gis_data/alaska.qml. The next
time we load alaska.shp, the style will be automatically applied.
We can access the attributes of a feature using either an index or the name.
For the world_borders layer we can get the country name using either of
these two methods:
name = feature[’CNTRY_NAME’]
name = feature[2]
Note we can use either upper or lower case and QGIS will recognize the
field name.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 5 . S E L E C T I N G A N D WO R K I N G W I T H F E AT U R E S 101
request = QgsFeatureRequest().setFilterFid(3201)
feature = layer.getFeatures(request).next()
print feature[’cntry_name’]
layer.selectAll()
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
102 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
Directly editing is easier, but doesn’t allow for a rollback of changes. For
the next two examples, assume we have a layer with a name and a city
field.
If we know the feature id, we can update the name without even entering
edit mode:
fid = 1
new_name = { 2: ’My New Name’}
layer.dataProvider().changeAttributeValues({fid: new_name})
Here the name field (index of 2) for feature with id of 1 is updated. This
requires knowing the field index in advance. We can also get the field index
number by first fetching the feature we want to modify:
features = layer.getFeatures(QgsFeatureRequest().setFilterFid(1))
feature = features.next()
new_name = { feature.fieldNameIndex(’name’): ’My New Name’}
25
See http://docs.python.org/ layer.dataProvider().changeAttributeValues({feature.id(): new_name})
2/tutorial/datastructures.
html for information on list and dict We can also update multiple attributes and save them using a dict comprehension25 :
comprehensions.
1 layer = iface.activeLayer()
2 provider = layer.dataProvider()
3 features = layer.getFeatures(QgsFeatureRequest().setFilterFid(1))
4 feature = features.next()
5 feature[’name’] = ’My New Name’
6 feature[’city’] = ’Seattle’
7 field_map = provider.fieldNameMap()
8 attrs = {field_map[key]: feature[key] for key in field_map}
9 layer.dataProvider().changeAttributeValues({feature.id(): attrs})
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 7 . S AV I N G IM AG E S 103
In line 3 we get all features with id of 1 and then get the single feature
returned in line 4. We can then modify the attributes using the syntax shown
in lines 5 and 6. Rather than manually build up an attribute list, in line 7 we
get the field map from the data provider. This map is just a dict of key, value
pairs, where the key is the field name and the value is its index number. In
line 8 we use a dict comprehension to create our attribute dictionary. This
gives us a new dict (attrs) with the field index number as the key and our
feature attributes as the values. We can then use this dict in line 9 to update
the feature.
The advantage here is a simpler syntax for assigning new values and ease
in building up the dict needed by changeAttributeValues.
Using an edit buffer is best suited to situations where you are providing
a custom editing experience via a GUI. This gives the user the chance to
rollback or cancel changes. For an example of using an editing buffer, see
the PyQGIS Cookbook.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
104 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
• BMP
• JPG
• JPEG
• PNG
• PPM
• XBM
• XPM
A world file is also created in the same location as your image. This allows
you to later add the image to QGIS as a georeferenced raster.
From the console, we can get a summary of the paths used in QGIS using
showSettings:
print QgsApplication.showSettings()
Application state:
QGIS_PREFIX_PATH env var:
Prefix: /dev1/apps/qgis
Plugin Path: /dev1/apps/qgis/lib/qgis/plugins
Package Data Path: /dev1/apps/qgis/share/qgis
Active Theme Name: default
Active Theme Path: :/images/themes/default/
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 8 . G E T T I N G Q G I S PAT H S 105
Often you need to know where your plugin is installed, so you can access
additional resources such as templates and help files. There are is more
than one way to get the path to your plugin, but the surest way is to use
QgsApplication.qgisUserDbFilePath:
>>> QgsApplication.qgisUserDbFilePath()
u’/Users/gsherman/.qgis2//qgis.db’
This gives us the path to the qgis.db that is created for each user. Earlier
we talked about where plugins are stored and learned, regardless of operat-
ing system, the .qgis2/python/plugins portion of the path is the same. os.path.join assembles path compo-
nents in an intelligent, OS compatible
Knowing this, we can create the full path to where our plugin is installed
way.
using os.path.join and QFileInfo:
>>> QFileInfo(QgsApplication.qgisUserDbFilePath()).path()
u’/Users/gsherman/.qgis2/’
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
106 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
• QMessageBox: Pops up a message dialog box that the user must take
action on
• QMessageLog: Writes a message to the log message window
• QMessageBar: Puts a message bar at the top of the map canvas
Using QMessageBox
• QMessageBox.information
• QMessageBox.warning
• QMessageBox.critical
There are some optional arguments we can supply, however for a simple
popup message, the functions are called like this:
The parent argument is the GUI element that “owns” the message box. Typ-
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 9 . M E S S AG E S A N D F E E D BAC K 107
ically you can specify None and it will work fine. To make the box a child
of the main QGIS window, you can use iface.mainwWindow():
QMessageBox.information(iface.mainwWindow(),
’Important Information’,
’This is an important message’)
The difference between information, warning, and critical is the icon dis-
played in the box.
Using QgsMessageLog
QGIS logs messages to a special panel. You can access the message panel
from the View->Panels->Log Messages menu or by clicking on the yel-
low triangle icon at the bottom right of the status bar. The messages are
organized by tabs that indicate the source of the message.
To log a message to your own custom tab, use something like this:
The first argument is the message, the second the tag that will be used as
the tab name, and the last is the message level.
You can see from Figure 8.2, on the next page that entering the above ex-
ample from the console creates a new tab named SuperZoom and writes the
message and level to it.
Using QgsMessageBar
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
108 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
This will place a message bar at the top of the map canvas. The message
consists of a title, the message, and a button to dismiss the message. The
look of the message is controlled by the level argument and the duration is
specified by the last argument, specified in seconds.
Figure 8.3, on the facing page shows the result when we enter the following
in the console:
iface.messageBar().pushMessage("SuperZoom",
"You specified an invalid zoom level",
QgsMessageBar.CRITICAL,
10)
The message has a red background, a “critical” icon, and stays on screen
for 10 seconds.
If you notice the console commands in Figure 8.3, on the next page, we
imported the QgsMessageBar class prior to displaying the message bar:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 9 . M E S S AG E S A N D F E E D BAC K 109
Any time you get a NameError when working with the API, it means
you need to import the module containing the object you are referencing.
This only needs to be done once in a script or console session.
These are the C++ definitions, but we won’t let that bother us. The first
implementation supports only a message, whereas the second supports a
title and message. Both methods include the level and duration. When you
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
110 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
To implement these features we need the following methods from the QgsMap-
ToolEmitPoint base class:
• canvasPressEvent
• canvasMoveEvent
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 1 1 . C R E AT I N G A M A P TO O L 111
First we need some of the usual imports (lines 1-4), along with one we
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
112 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
In line 7 we see the class declaration for ConnectTool and note that it sub-
classes QgsMapToolEmitPoint. As we said, this gives us all the functional-
ity of the base class, along with the ability to override it to customize the
behavior.
We also declare and set our start, end, and rubberband attributes to None
in lines 11-13.
Next our __init__ method (lines 15-17) sets things up, including storing a
reference to the map canvas (which we must pass to it). You’ll notice on line
17 that we are calling the init method of the parent class. This is important
to get things initialized properly.
We need a starting point for our rubberband; we check for that in line 20,
and if it exists, we get the click point in line 21. Next we check to see if
we have a rubberband object in line 22. If we do, we reset it (because it is
going to change), otherwise we create the rubberband and set its color to
red (lines 24-26).
To set the rubberband’s geometry, we use the start_point and the current
location of the mouse cursor, as contained in the event. Line 28 creates
the list needed to set the geometry, which is done in lines 29 and 30. This
will cause the rubberband to appear on the canvas. As we move the mouse,
canvasMoveEvent is called repeatedly and the rubberband’s geometry is up-
dating, making it appear to move with the cursor.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 1 1 . C R E AT I N G A M A P TO O L 113
The last method from our base class that we will override is the canvasPres-
sEvent (lines 32-41). This event is fired whenever there is a mouse click on
the map canvas and we use it to set our start and end points. The event in-
cludes information about the location of the click, as well as which button
was used and any keyboard modifiers in play. For our purposes, we’re only
interested in two things: the fact that a mouse button was clicked and the
location of the click.
In line 33 we check to see if we don’t yet have a start point and if so, set
it in line 34. Otherwise, the click is our end point and we set it, reset the
rubberband, emit the line_complete signal, then clear our start/end points
(lines 35-41).
In line 40 we emit our custom signal. The signal, along the two arguments
(start and end points) is emitted and any class or method that has connected
to the signal will catch it. The start and end points can then be used to create
a new feature or do something else useful.
To use our new map tool we’ll create a class that implements the map tool
and created the line based on the two points clicked:
1. Create it:
map_tool = ConnectTool(self.canvas)
self.connect_action = QAction(
QIcon(":/ourapp/connect_icon"),
"Connect",
self)
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
114 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
For example, let’s say we have a toolbar in our plugin class referenced as
self.toolbar and our plugin also has stashed the qgis.utils.iface ob-
ject in self.iface. Here is how we would add the select by rectangle tool
to our toolbar in the initialization of the GUI:
rect_select = self.iface.actionSelectRectangle()
self.toolbar.addAction(rect_select)
That’s all there is to it. The action is added to our toolbar and shows up with
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 1 3 . AC C E S S I N G A N E X I S T I N G P L U G I N 115
the same icon as in the QGIS Manage Layers toolbar. The tool is ready to
use and performs exactly like the original.
• create_pin_layer()
• place_pin(point, button)
The first method creates a memory layer for the pins and the second method
places the pin based on the location of the user click. In order to use the
methods of the PinPoint plugin, we first need to make sure it is loaded and
active. The qgis.utils.plugins dict contains all loaded plugins, keyed
by name. We can access PinPoint as follows:
>>> pp = qgis.utils.plugins[’pinpoint’]
>>> pp
<pinpoint.pinpoint.PinPoint instance at 0x9efba4c>
While this works when PinPoint is loaded, we don’t fare so well when it’s
not:
pp = qgis.utils.plugins[’pinpoint’]
Traceback (most recent call last):
File "<input>", line 1, in <module>
KeyError: ’pinpoint’
if ’pinpoint’ in qgis.utils.plugins:
pp = qgis.utils.plugins[’pinpoint’]
pp
<pinpoint.pinpoint.PinPoint instance at 0x9efba4c>
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
116 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
Now that we know how to get a reference to the PinPoint plugin, here’s the
code we would use to create the pin layer and place a pin at coordinate 100,
100:
if ’pinpoint’ in qgis.utils.plugins:
pp = qgis.utils.plugins[’pinpoint’]
pp.create_pin_layer()
pp.place_pin(QgsPoint(100,100), 1)
The call to place_pin will popup a dialog so we can enter the name for the
pin and when we click OK, the pin is placed on our newly created memory
layer.
2. Uploading the plugin zip file to a web directory for download by the
QGIS Plugin Manager
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 1 4 . S E T T I N G U P A RE P O S I TO RY 117
7 <qgis_minimum_version>2.0</qgis_minimum_version>
8 <homepage></homepage>
9 <file_name>scriptrunner.zip</file_name>
10 <author_name>Gary Sherman</author_name>
11 <download_url>http://example.com/qgis_plugins/scriptrunner.zip</download_url>
12 <uploaded_by>gsherman</uploaded_by>
13 <create_date>2013-07-05</create_date>
14 <update_date>None</update_date>
15 <experimental>False</experimental>
16 </pyqgis_plugin>
17 </plugins>
This example defines one plugin: ScriptRunner. Each plugin in the repos-
itory is defined within a set of pyqgis_plugin tags. The requirements are
pretty much self-explanatory. In order for QGIS to be able to fetch and in-
stall the plugin, the zip file must be correctly specified in the download_url
tag.
Once you have setup the repository, add it to the Plugin Manager using the
Settings tab (see Figure 8.4).
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
118 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
When Plugin Manager fetches from the configured repositories, your plu-
gin(s) will show up in the list of available plugins.
8.15 Exercises
1. Find the qgis.utils module in your QGIS install and open it in a
text editor. Examine the methods and data structures available.
2. Create a point memory layer with an id and name field, add some
features to it, and add it to the map canvas.
3. Write a script to turn on labeling for the point layer and label each
feature using the name field.
4. Using the point layer, write a script that allows you to edit the name
for a selected feature by prompting for the new name, then saving it
to the attribute table.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
9
Extending the API
In this chapter, we will work up a little “wrapper” script that makes working
with layers and colors a bit easier. Our goal here is to simplify the adding
of vector and raster layers. In addition, we’ll write a function to simplify
changing the color of a layer and updating the legend.
We need the PyQt4.QtGui module because we are going to use the QColor
class to set the color for our vector layers. We’ll also use some classes from
the qgis.core and qgis.iface modules. Lastly, the ogr and gdal mod-
ules are needed so we can identify what type of layer we are adding—this
will become clear in the next couple of code snippets.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
9 . 3 . T H E A D D O G R L AY E R A N D A D D G DA L L AY E R F U N C T I O N S 121
In lines 35-37 we check to see if a custom name for they layer was passed.
If so we use it, otherwise the filename is used in the legend.
In line 39 the layer is created by passing the path, name, and the data
provider key (’ogr’) to QgsVectorLayer. This creates our layer. In line 40
we add it to the map using the addMapLayer method of the QgsMapLayer-
Registry class and return a reference to the layer.
Adding a raster works in much the same way in lines 43-50, passing the
path and name to QgsRasterLayer, adding it to the map, and returning a
reference to it.
Let’s see how we would use what we have so far to add both a vector and
raster layer to the map.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
122 CHAPTER 9. EXTENDING THE API
The last method might seem easy, but when you upgrade or uninstall QGIS,
your script may be lost.
In our example, we’ll use the second method just to illustrate how it’s done.
We’ve placed all our QGIS scripts in a convenient location:
/home/gsherman/development/qgis_scripts
Here is the script that allows us to add the world_borders vector layer and
the natural_earth raster to our map canvas:
1 import sys
2 sys.path.append(’/home/gsherman/development/qgis_scripts’)
3 from wrapper import wrapper
4 lyr_vector = wrapper.addLayer(’/data/world_borders.shp’, ’World Borders’)
5 lyr_raster = wrapper.addLayer(’/data/HYP_50M_SR_W.tif’, ’Natural Earth’)
If you have a script or plugin that needs to import additional modules located
in the same directory, you can do so by adding the following statements to
your code:
import os
import sys
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
This adds the directory where your script resides (contained in __file__) to
the Python path in a fairly portable manner.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
9 . 4 . U S I N G T H E A D D L AY E R F U N C T I O N 123
We can load our little script in the Console Editor and run it using the Run
script button. The result can be seen in Figure 9.1. Notice the order in
which we executed our statements resulted in the raster being loaded over
the vector layer. You should now realize that using addLayer is much easier
than the step-by-step way of loading a layer.27 27
Well maybe not a lot easier, but cer-
tainly lazier.
Let’s make it easy to remove a layer—in this case we’ll use it to remove
the natural_earth layer so we can see the world_borders. Here’s the
function we need to add to wrapper.py:
The removeLayer function uses the layer’s id to remove it from the map.
While we can add layers without storing a reference to them, doing so
prevents us from working further with them. In our test script we stored
references to the vector and raster layers in lyr_vector and lyr_raster
respectively.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
124 CHAPTER 9. EXTENDING THE API
Running a script from the Console Editor also makes the variables and
objects accessible in the console. For example, you can run the script to add
vector and raster layers from the editor and then switch to the console and
interactively use the wrapper.removeLayer function to remove a layer.
Here’s our wrapper script so far—next we’ll add a couple functions to help
us change the color and transparency of a vector layer:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
9 . 5 . C H A N G I N G T H E C O L O R A N D T R A N S PA R E N C Y O F A V E C TO R L AY E R 125
The createRGBA function accepts a color definition string using four, comma
separated integers ranging from 0 to 255 that represent the red, green, blue,
and alpha components. It then returns a QColor object constructed from
each of the components.
For example, to create a red color with 50% transparency, we would use:
wrapper.createRGBA(’255, 0, 0, 128’)
Line 59 creates the color object using QColor.fromRgb and returns it.
Now that we can create a custom RGBA color, let’s look at the function that
actually changes the color of a vector layer on both the map canvas and in
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
126 CHAPTER 9. EXTENDING THE API
the legend:
You may remember that we took a look at the ways in which we can cre-
ate a QColor object back in Section 6.4, Exploring Vector Symbology, on
page 70. There we saw how to create a QColor object using:
• QColor(Qt.red)
• QColor(’red’)
• QColor(’#ff0000’)
• QColor(255,0,0,255)
In lines 65-71 we check to see if an RGBA string has been passed in the
color parameter and if so, we create the color using createRGBA and set
the transparency value (line 68). Otherwise we create the color directly in
line 70 and set the transparency to None.
With the color created, we get a reference to the symbol for the layer and
set it (and transparency if present) in lines 73-77.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
9 . 5 . C H A N G I N G T H E C O L O R A N D T R A N S PA R E N C Y O F A V E C TO R L AY E R 127
The last thing to do is refresh the layer and the legend; this is done using
refresh in line 79 and refreshLayerSymbology in line 80. Notice also we
invalidated the image cache in line 78 just in case it exists—this ensures the
repaint will succeed.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
128 CHAPTER 9. EXTENDING THE API
41
42
43 def addGdalLayer(layerpath, name=None):
44 """Add a GDAL layer and return a reference to it"""
45 if not name:
46 (path, filename) = os.path.split(layerpath)
47 name = filename
48
49 lyr = QgsRasterLayer(layerpath, name)
50 return QgsMapLayerRegistry.instance().addMapLayer(lyr)
51
52
53 def removeLayer(layer):
54 QgsMapLayerRegistry.instance().removeMapLayer(layer.id())
55
56
57 def createRGBA(color):
58 (red, green, blue, alpha) = color.split(’,’)
59 return QColor.fromRgb(int(red), int(green), int(blue), int(alpha))
60
61
62 def changeColor(layer, color):
63 """ Change the color of a layer using
64 Qt named colors, RGBA, or hex notation."""
65 if ’,’ in color:
66 # assume rgba color
67 color = createRGBA(color)
68 transparency = color.alpha() / 255.0
69 else:
70 color = QColor(color)
71 transparency = None
72
73 renderer = layer.rendererV2()
74 symb = renderer.symbol()
75 symb.setColor(color)
76 if transparency:
77 symb.setAlpha(transparency)
78 layer.setCacheImage(None)
79 iface.mapCanvas().refresh()
80 iface.legendInterface().refreshLayerSymbology(layer)
This chapter gives you a simple example of how you can wrap up various
QGIS API classes and methods to create new functions. You may have
already thought of enhancements that could be implemented in the wrapper.
Some suggestions follow in the next section.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
9.6. EXERCISES 129
9.6 Exercises
1. Identify areas in wrapper.py that are potential failure points and add
appropriate error checks. For example, what happens if you pass an
invalid color specification to changeColor?
2. Add a function to reorder the layers in the legend and test it by load-
ing world_borders.shp, natural_earth.tif, and then switching
their order.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
10
Writing Plugins
In this chapter we dive into creating a simple plugin from scratch, but first
we need to say a few words about the plugin architecture in QGIS.
|-- testplugin
|-- __init__.py
|-- icon.png
|-- metadata.txt
|-- resources.qrc
|-- resources_rc.py
|-- testplugin.py
|-- testplugindialog.py
|-- ui_testplugin.py
|-- ui_testplugin.ui
testplugin
Top-level directory of the plugin.
132 CHAPTER 10. WRITING PLUGINS
__init__.py
This script contains one method (classFactory) that initializes the plu-
gin class and makes it known to QGIS.
icon.png
The icon to be used for the plugin when displayed on a QGIS toolbar.
The icon should be 24x24 pixels in PNG format.
metadata.txt
The metadata file contains information about the plugin, including the
name, description, version, icon, and minimum QGIS version. This file
is necessary for QGIS to recognize the plugin.
resources.qrc
Describes resources (e.g. icon.png), used by the plugin and its GUI
forms.
resources_rc.py
The Python file generated from resources.py by the PyQt resource
compiler, pyrcc4
testplugin.py
The main implementation of your plugin that handles loading, unload-
ing, and execution of the plugin’s functions.
testplugindialog.py
The main GUI dialog for the plugin.
ui_testplugin.py
The Python file generated from ui_testplugin.ui by the PyQt inter-
face compiler, pyuic4.
ui_testplugin.ui
The GUI interface file created by Qt Designer.
We’ll look at the details of these plugin components, as well as some addi-
tional ones shortly.
Once you have a plugin that works locally, you can package it up in a num-
ber of ways. The steps to do it manually are as follows:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
10.1. PYTHON PLUGIN ARCHITECTURE 133
2. Copy only the necessary files for your plugin to the distribution di-
rectory. If you are using a version control system (VCS) during devel-
opment, you should do an export to avoid distributing files associated
with your VCS.
3. Package the plugin by making a zip archive of it, including the direc-
tory
The zip file is now ready for upload to the QGIS repository, but you should
probably test it by unzipping into the location of your QGIS plugins to make
sure it loads/unloads properly.
Along with the command line, you can use any tool you like to create the
archive, including popular zip managers on Windows. The important point
is to use zip to package the file and be sure to include the directory in the
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
134 CHAPTER 10. WRITING PLUGINS
archive.
When QGIS starts up, it examines the contents of your plugin directory to
create a list of all valid plugins. Each plugin you have previously enabled is
then started by calling the classFactory method in __init__.py. If the
plugin starts successfully, the initGui method is called to add entries to the
menu and toolbars. The plugin is then added to the list of active plugins.
Any failure during the startup process will result in an exception and an
error message will be displayed in QGIS. These errors can be helpful when
developing a plugin as they show the file name and line number of the error.
Before you protest, sure we can see the mouse coordinates in the QGIS
status bar. We can even tab to the box where they are displayed and copy
them but that’s not the point—we want to implement it as a plugin to form
the basis for a more advance functionality later. We’ll start with the Plugin
Builder.
Back in the old days (around QGIS version 0.9) we had to create all the
boilerplate for a Python plugin by hand. This was tedious and basically
the same for each plugin. Fortunately that’s no longer the case—we can
generate a plugin template using the Plugin Builder.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 0 . 3 . C R E AT I N G A S I M P L E P L U G I N 135
The Plugin Builder is itself a Python plugin that takes some input from you
and creates all the files needed for a new plugin. It’s then up to you to
customize things and add the code that does the real work. If you did the
exercises back in Chapter 4, The QGIS/Python Ecosystem, on page 43, you
already have Plugin Builder installed. If not, install it now using the Plugin
Manager by clicking on the Plugins->Manage and Install Plugins
menu.
Let’s generate the structure for our Where Am I? plugin by clicking on the
Plugin Builder tool or menu item. We are presented with a dialog that
contains all the fields needed to create the plugin. On the left side of the
plugin dialog you’ll see some hints about what is expected for each field.
Figure 10.1 shows all the fields needed to generate the plugin.
When we click OK, the Plugin Builder generates the files needed for your
plugin. In addition to the ones we saw in Section 10.1, Python Plugin Ar-
chitecture, on page 131, several additional files have been created for us:
Makefile
This is a GNU makefile that can be used to compile the resource file
resources.qrc and the user interface file (.ui). This requires gmake
and works on both Linux and Mac OS X and should also work with the
OSGeo4W shell on Windows.
help
This directory contains the files needed to begin documenting your plu-
Sphinx is a Python documentation gin using Sphinx.
generator available at http://sphinx-
doc.org i18n
Empty directory to be used for creating translations of your plugin.
plugin_upload.py
A Python script to upload the plugin to the QGIS plugin repository. Typ-
ically, you would use the web interface at http://plugins.qgis.org
instead of this script.
You’ll notice the naming of a number of the files is based on a lower case
version of the name you provide for your plugin, in this case whereami.
After the plugin generates the needed files, a results dialog is shown that
contains some helpful information, as shown in Figure 10.2, on the facing
page.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 0 . 3 . C R E AT I N G A S I M P L E P L U G I N 137
Our generated plugin is almost fully functional. The only thing we need to
do is compile the resource and user interface files, then place whereami in
our plugin directory:
4. Copy the entire whereami plugin directory to your QGIS plugins di-
rectory. If you need a reminder, the location as listed in both the
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
138 CHAPTER 10. WRITING PLUGINS
README files.
Now, start QGIS and open the Plugin Manager—you should see Where Am
Plugin Manager lists plugins by their I in the list of installed plugins. Click the checkbox next to it to enable it
description, not the plugin name.
and then click OK. You should now find a new icon on the Plugins toolbar,
as well as a menu entry in Plugins->Where Am I?.
Clicking on the tool or the menu item brings up the plugin as shown in
Figure 10.3.
It’s not much to look at yet, but it is fully functional in the following ways:
We’ll talk more about the accept() and reject() signals later on.
We also need to tweak the GUI to add a single box where we will report the
coordinates of the map click.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 0 . 4 . C U S TO M I Z I N G T H E I C O N 139
This is fine for getting started, but you really don’t want your plugin using
the same icon as dozen of others. You have two options for changing the
icon:
Of course, you don’t have to change the icon during development—the de-
fault created by Plugin Builder works fine. For our example, here is a simple
“question mark” icon I created using Gimp28 : 28
http://gimp.org
Now all we need to do is to modify the resources file to use our new icon.
First, let’s take a look at what’s in the resource file (resources.qrc) that
Plugin Builder created for us:
<RCC>
<qresource prefix="/plugins/whereami" >
<file>icon.png</file>
</qresource>
</RCC>
This resource file uses a prefix to prevent naming clashes with other plugins.
It’s good to make sure your prefix will be unique—usually using the name
of your plugin is adequate (Plugin Builder created the prefix for you based
on the plugin name). To add our customized icon, we need to replace the
generated file name (icon.png) with our customized icon, which I named
whereami.png:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
140 CHAPTER 10. WRITING PLUGINS
<RCC>
<qresource prefix="/plugins/whereami" >
<file>whereami.png</file>
</qresource>
</RCC>
Since we are using a new file name for our icon, we need to make a couple
of changes in our code, specifically in metadata.txt and whereami.py.
icon=whereami.png
In whereami.py, the icon is loaded from the graphic file specified in line 4:
1 def initGui(self):
2 # Create action that will start plugin configuration
3 self.action = QAction(
4 QIcon(":/plugins/whereami/icon.png"),
5 u"Where Am I?", self.iface.mainWindow())
6 ...
With the changes complete and the resource file saved, we need to compile
it in order for it to be used our new plugin:
pyrcc4 -o resources.py resources.qrc
The -o switch is used to define the output file. If you don’t include it, the
output of pyrcc4 will be written to the terminal, which is not really what
we’re after here. Now that we have the resources compiled, we need to
build the GUI to display the map coordinates when we click on a point.
If you choose to use the default icon created by Plugin Builder you don’t
have to modify the resources file, but you do have to compile it.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 0 . 5 . C U S TO M I Z I N G T H E G U I 141
To fix up our GUI, we’ll use the same tool that the QGIS C++ developers
use: Qt Designer29 . This is a visual design tool that allows you to create
dialog boxes and main windows by dragging and dropping widgets and
defining their properties. Designer is normally installed along with Qt, so
it should be already available on your machine30 . 29
Designer is now part of Qt Cre-
ator (See http://doc.qt.digia.
Let’s take a look at what it takes to polish up our GUI for WhereAmI. This com/qtcreator-2.5/index.html)
30
On some Linux distributions you
will be a quick tour—we won’t go into all the intricacies of Designer. If
may have to install additional pack-
you want to get into the nitty-gritty, see the excellent documentation on ages using your package manager.
Designer on the Qt website and in your Qt documentation directory.
To begin, we open our generated dialog box in Designer using the File
menu and selecting ui_whereami.ui.
In Figure 10.4, on the next page, you can see the dialog box as generated by
Plugin Builder in Designer, along with the widget palette and the property
editor.
2. Drag and drop a Label widget (found under Display Widgets) to the
dialog box
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
142 CHAPTER 10. WRITING PLUGINS
4. Drag and drop a Line Edit widget (found under Input Widgets) under
the label and resize it to near the width of the dialog box
5. Drag and drop a Push Button widget (found under Buttons) on the
dialog box just under the Line Edit widget
Our dialog is complete with one exception—the Close button is not hooked
up to do anything when clicked. By default, the Close/OK button group is
wired up to manage accepting (OK) and rejecting (Close) the dialog. We
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 0 . 5 . C U S TO M I Z I N G T H E G U I 143
could have used the button group by deleting just the OK button, however
to illustrate wiring up a button to a method we’ll do it by hand.
To make our new Close button work, we use the Signal/Slot editor in Designer:
2. Click and hold on the Close button, then drag the mouse cursor to the
empty space on the dialog box and release the mouse to bring up the
Configure Connection box
Figure 10.5, on the following page shows the connection made between the
button’s pressed signal and the dialog’s reject slot. What this means is,
when the button is pressed, the dialog receives the signal and directs it to
the reject slot (which is really just a method), and closes the dialog.
You don’t need to know the details of how the signal/slot mechanism in Qt
works to create simple dialogs like the one for the WhereAmI plugin. As
you develop more sophisticated PyQGIS plugins or applications, you will
want to delve into it a bit more. We will be making connections manually
when we add some code to the WhereAmI plugin.
Once we have all the controls on the form, we’re ready to generate some
code from it. To convert our completed dialog box to Python, we use the
PyQt pyuic4 command to compile it:
pyuic4 -o ui_whereami.py ui_whereami.ui
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
144 CHAPTER 10. WRITING PLUGINS
If the compiled dialog is not named properly the plugin will fail to initialize.
Our GUI is now ready for use. All we need to write now is the Python code
to interact with the QGIS map canvas to get the coordinates when the mouse
is clicked.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
10.6. WRITING THE PLUGIN CODE 145
First let’s look at the code generated for us by Plugin Builder, starting with
the import and init method in the main Python source file, whereami.py:
In lines 1-3, we import the PyQt libraries and the QGIS core library. Line 5
imports our resources file and in line 7, the code needed to load and initialize
our GUI dialog is imported. This code is all generated by Plugin Builder.
If you need additional Python modules for your plugin, you’ll add them in
this section of code—for WhereAmI we don’t need anything further.
Line 10 starts the class definition of WhereAmI, the __init__ method being
defined first. You’ll see some of housekeeping items are taken care of in
lines 13-20. Again, we don’t need to change anything in these lines of
code. We do however, have to add some additional code to make our plugin
work with the map canvas.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
146 CHAPTER 10. WRITING PLUGINS
The first step is to create the map tool using the QgsMapToolEmitPoint
class from the QGIS API. This class implements a map tool that, when
clicked, emits a point containing the map coordinates. To create the tool
and store it as an attribute of the WhereAmI class we need to import it by
adding a new statement to our imports:
With these changes, the first section of our code now looks like this, with
addtions at line 5 and lines 24-27:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
10.6. WRITING THE PLUGIN CODE 147
Let’s look at using the old method to connect a click of our tool to our
display method:
# connect signal that the canvas was clicked
result = QObject.connect(self.pointTool,
SIGNAL("canvasClicked(const QgsPoint &, Qt::MouseButton)"),
self.display_point)
Using the old method requires us to know how to write the arguments in the
proper form. The new method is much simpler and we’ll use it:
# connect signal that the canvas was clicked
self.pointTool.canvasClicked.connect(self.display_point)
Now when the WhereAmI tool is selected and the map canvas is clicked, the
framework will capture the click and the coordinates (as a QgsPoint object),
and pass control off to our as yet unwritten display_point method.
We’ll go back and put this all together shortly, but let’s look at the display_point
method first:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
148 CHAPTER 10. WRITING PLUGINS
In line 3 we hide the dialog so when we show it again it will pop up on top
of the main window, otherwise subsequent clicks would result in our dialog
always being hidden.
Line 4 creates a string using a format specification and the X and Y values
from the QgsPoint object using the x() and y() methods. This results in a
formatted string with lots of decimal places:
-152.661636888, 65.6374837099
Line 5 sets the value of the line edit box in our dialog to our formatted result
string. To refer to any GUI element we prefix the name with self.dlg.ui.
For example, to refer to the label, we would use self.dlg.ui.label. If
you’re wondering how we know it’s label, the names of all GUI elements
on the dialog can be viewed in the Object Inspector in Designer, as seen in
Figure 10.5, on page 144.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
10.7. ONE LAST TWEAK 149
First let’s look at the dialog code needed to implement the change:
We added line 13 to initialize an attribute that will store the position of the
result dialog, setting it to None, the Python way of expressing “absence of
value.” In other words, we haven’t set self.userPos to anything yet—that
happens in the moveEvent method on lines 15 and 16.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
150 CHAPTER 10. WRITING PLUGINS
In lines 7 and 8 we check to see if the user position has been set and, if so,
move the dialog before showing it.
If you try the plugin, you’ll notice that you can move the dialog to a con-
venient position after the first use and it will stay there with all subsequent
uses during your QGIS session. Since we didn’t write any code to persist
the settings, when you exit QGIS, the dialog position is lost.
Figure 10.6 shows the result of using the WhereAmI plugin. Note the results
in the plugin match those displayed in the coordinate box of the QGIS status
bar.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
10.8. EXERCISES 151
10.8 Exercises
1. Format the results of the WhereAmI plugin so only three decimal
places are shown.
3. Add a button to the right of the Label or Line Edit widget that copies
the results to the clipboard when clicked (hint: see the QClipboard
class in the Qt documentation).
4. Add a layout to the plugin dialog box so when it is resized, the wid-
gets resize appropriately.
5. Using QSettings and the closeEvent of the dialog, save the dialog
position and restore it each time the plugin is loaded.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
11
Creating a Development Workflow
Now that we have developed a simple plugin, let’s talk a bit about the de-
velopment process and how to establish a workflow that suits you best.
find it in order to test it. Let’s examine the options for setting up a workflow.
1. Copy or move the plugin files to your QGIS plugin directory and
develop from there.
5. Use git to commit your changes then pull to your QGIS plugin di-
rectory each time you want to test
This method (option one above) is very convenient as long as you don’t
test the uninstall feature of your plugin. If you do, the Plugin Manager
will happily delete your entire plugin directory, and with it all your source
code—not what we really want.
This method is safer than developing directly in your plugin directory, how-
ever to test your plugin you have to copy from your development directory
to your QGIS plugin directory every time you want to test. If you are on
a Unix based system or are using the OSGeo4W install of QGIS, you can
33
Plugin Builder creates a Makefile create a Makefile to deploy the plugin for you.33
you can customize for building and de-
ploying your plugin. Using the QGIS_PLUGINPATH Environment Variable
With this method (option three), you work with your code in a separate
directory but use the QGIS_PLUGINPATH environment variable to point to
your development directory. When present, QGIS_PLUGINPATH tells QGIS
to search additional directories for plugins. Going this route allows you
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 1 . 1 . C H O OS I N G A D E V E L O P M E N T M E T H O D O L O G Y 155
to develop in the directory created by Plugin Builder and test your plugin
without any copying or pulling. The upside to this is since your plugin
wasn’t installed through the Plugin Installer it can’t be uninstalled acciden-
tally. When you are ready to test the uninstall and unloading of your plugin
you can copy it to the main QGIS plugin directory.
This method (option four) can be useful but requires a bit of work upfront.
You setup a plugin repository (see Section 8.14, Setting Up a Repository,
on page 116), then deploy your plugin to it. You can then install onto any
machine for testing. I use this method when testing the cross-platform com-
patibility of a plugin, usually during the latter stages of development.
Using git
The last option in our list of suggested methodologies is to use git to pull
changes into your QGIS plugin directory. The steps to use this method are
roughly:
1. Create your plugin in any source directory you desire (preferably us-
ing Plugin Builder)
4. Develop your plugin and commit the changes using git commit
5. Change to your QGIS plugin directory (see Section 4.6, Python Plu-
gin Specifics, on page 48)
6. Clone your source repository that contains the plugin. For example,
if your source code is in /home/myname/myplugin:
git clone /home/myname/myplugin
8. Make more edits in the source directory and commit, then change to
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
156 C H A P T E R 1 1 . C R E AT I N G A D E V E L O P M E N T WO R K F L OW
Regardless of which method you use for development, keeping your source
code under version control and pushed to an off-site repository (such as
Github or BitBucket) is a good idea.
So which method should you use? This is of course a matter of opinion, but
here goes. I would suggest the following workflow, which combines a few
of the options we discussed above:
git init .
git add *
git commit -am ’Initial commit of my great plugin’
The Plugin Reloader plugin can be very useful when developing your
plugin. It allows you to reload your plugin after changes are made without
having to restart QGIS.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 1 . 2 . D E BU G G I N G 157
import glob
import os
import subprocess
ui_files = glob.glob(’*.ui’)
for ui in ui_files:
(name, ext) = os.path.splitext(ui)
print "pyuic4 -o {}.py {}".format(name, ui)
subprocess.call(["pyuic4.bat", "-o", "{}.py".format(name), ui])
rc_files = glob.glob(’*.qrc’)
for rc in rc_files:
(name, ext) = os.path.splitext(rc)
print "pyrcc4.exe -o {}_rc.py {}".format(name, rc)
subprocess.call(["pyrcc4.exe", "-o", "{}_rc.py".format(name), rc])
Note you must run the script from the OSGeo4W shell.
11.2 Debugging
When log messages and print statements aren’t enough to troubleshoot your
PyQGIS code, you may have to resort to interactive debugging. There are a
several options for debugging your code:
Using pdb
import pdb
When you want to cause your code to stop and drop into the debugger, place
these lines at the desired breakpoint:
pyqtRemoveInputHook()
pdb.set_trace()
The key to using pdb is to start QGIS from a terminal. On Linux, you
simply run qgis from the command line.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
158 C H A P T E R 1 1 . C R E AT I N G A D E V E L O P M E N T WO R K F L OW
open /Applications/QGIS.app/Contents/MacOS/QGIS
The Windows version of QGIS doesn’t allow us to start it in a way that pdb
can attach to the process—you’ll have to use one of the other methods listed
below.
Once pdb is active in your terminal, you can use commands to list the
source, set breakpoints, view the contents of variables, and step through
your code. Typing help gives you a summary of the available commands,
many of which have one letter abbreviations:
(Pdb) help
Undocumented commands:
======================
retval rv
For more information and details on using pdb, see the documentation at:
http://docs.python.org/2/library/pdb.html
There are a number of IDEs that support remote debugging, meaning they
can attach to a running QGIS project and provide debug capability. Here
are two that are known to work with QGIS:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 1 . 2 . D E BU G G I N G 159
• PyDev (Eclipse)
• PyCharm (commercial)
We talked about installing PyDev in Section 3.2, Using an IDE, on page 39.
For a complete description on configuring and using PyDev to debug a
QGIS plugin, see:
http://linfiniti.com/2011/12/remote-debugging-qgis-python-plugins-with-pydev/
This blog post contains a complete example starting with creating a simple
plugin to creating the PyDev project and using the remote debugger.
If you don’t use an IDE or yours doesn’t support remote debugging you
can use Winpdb.34 Despite its name, Winpdb is a cross platform debugging
tool that works on Linux, Mac OS X, and Windows. It does require the
installation of wxPython which is available for each of the three operating
systems.35 34
http://winpdb.org
35
http://www.wxpython.org
As with pdb, we need to add a few lines of code to enable debugging:
import rpdb2
rpdb2.start_embedded_debugger(password)
We can use the tools to step through the code, examine variables, and set
additional breakpoints.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
160 C H A P T E R 1 1 . C R E AT I N G A D E V E L O P M E N T WO R K F L OW
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
12
Writing a Standalone Application
Using Qt and the QGIS API, you can create your own standalone GIS ap-
plication that contains only the functionality you need. Some reasons to do
this include:
• You need a custom, streamlined data collection application for field use
• You want to restrict functionality to provide a simple or secure applica-
tion
• You want to include QGIS functionality in a larger application that isn’t
necessarily GIS-centric
1. Use Qt Designer to layout the main window and other GUI elements
We’ll use the second method as it will help you understand more about
what’s going on “under the hood”.
Just as you can use the interactive shell to experiment with Python basics,
you can also use it to bring up a simple Qt application. Here is a short
example that we can try from the Python shell—we’ll use this as a starting
point on which to build our application.
If you enter these statements (or run them from a script) from the Python
shell, you’ll get a simple application shown in Figure 12.1, on the facing
page.
Let’s take a quick look at what’s going on in the code. In line 1 we import
the PyQt4.QtGui module since it contains the classes we need to create our
little application. Here’s an annotated list of lines 3-7:
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 2 . 1 . D E S I G N I N G T H E A P P L I C AT I O N 163
Line 6: Set the frame as the central widget of our main window (every main
window has one)
Line 7: Create a grid layout that has the frame as its parent
These statements setup the basics of our application and window. The grid
layout will allow us to have PyQt dynamically resize all our child widgets
when the main window is resized.
Now we are ready to add the core of our application; a text edit control in
line 9. Notice we create it without specifying a parent widget (if it had a par-
ent, it would have been specified as an argument to QtGui.QTextEdit()).
In lines 10-13 we add some text to our editor, just so we can make sure it’s
working when we run the app. In line 14 we add our text editor to the grid
layout widget, which will handle the dynamic layout for us.
The last two things are to show the main window in line 15 and then add
line 17 in case we want to run this code as a script from the command line.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
164 C H A P T E R 1 2 . W R I T I N G A S TA N DA L O N E A P P L I C AT I O N
This gives us the little application shown in Figure 12.2, on the next page.
It isn’t much to look at—no toolbars, menus, map controls, or legend, just a
map canvas with a single layer. Let’s take a deeper look at the code required
to get the app up and running.
We started out with the basic text editor app and substituted it with a QgsMap-
Canvas. To get that to work, we have to do a bit of setup first.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 2 . 2 . C R E AT I N G A M I N I M A L P Y Q G I S A P P L I C AT I O N 165
Lines 9 through 12 setup the main window and grid layout and are the same
as in our simple PyQt app.
We want the canvas to fill the application window and do that by adding it
to the grid layout in line 15.
By default the map canvas has a black background. Line 16 uses the set-
CanvasColor method to set it to white.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
166 C H A P T E R 1 2 . W R I T I N G A S TA N DA L O N E A P P L I C AT I O N
To make sure our added layer is visible, we zoom the canvas to the full
extent of the shapefile in line 24.
The last thing to do is show the main window in line 26. Since we want
to run the code as a script instead of individually entering statements in the
Python shell, we need the app.exe_() call in line 29, otherwise our applica-
tion will come up and immediately exit.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 2 . 3 . C R E AT I N G O U R OW N MA I N W I N D OW C L A S S 167
The OurMainWindow class contains much of the code from our original
script, but we’ve made a start on making it more modular and easier to
understand.
The first thing you should notice is that our class is a subclass of QMain-
Window (line 9). This allows us to extend QMainWindow and add additional
functionality to it.
The __init__ method sets up our GUI, adds a shapefile, and zooms to full
extent. Ultimately we would want to add methods to our class to choose the
shapefile we want to load.
The setupGui method creates the GUI and adds the map canvas to our main
window.
Lastly, the add_ogr_layer method takes the path to our shapefile and adds
it to the map, much the way our earlier script did.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
168 C H A P T E R 1 2 . W R I T I N G A S TA N DA L O N E A P P L I C AT I O N
12 mw = OurMainWindow()
13 mw.show()
14
15 app.exec_()
16
17 # "delete" our main window
18 mw = None
19 # clean up QGIS
20 QgsApplication.exitQgis()
This app needs a lot more work to make it useful. Let’s add some map tools
to allow us to control the map view.
There is more than one way to add a menu or tool in Qt, however the most
flexible way is to use a QAction. This allows us to add the action to both
the menu and the toolbar, rather than creating code to do both. Here is our
action:
self.zoomin_action = QAction(
QIcon(":/ourapp/zoomin_icon"),
"Zoom In",
self)
This creates the action, but it doesn’t do anything yet because it isn’t con-
nected to a method to actually zoom the canvas. Here is the new version of
ourmainwindow.py:
ourmainwindow_2.py
1 import os
2
3 from PyQt4.QtGui import *
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 2 . 4 . A D D I N G MA P TO O L S TO T H E A P P L I CAT I O N 169
4
5 from qgis.gui import *
6 from qgis.core import *
7
8 import resources
9
10
11 class OurMainWindow(QMainWindow):
12 def __init__(self):
13 QMainWindow.__init__(self)
14
15 self.setupGui()
16
17 self.add_ogr_layer(’/data/alaska.shp’)
18 self.map_canvas.zoomToFullExtent()
19
20 def setupGui(self):
21 frame = QFrame(self)
22 self.setCentralWidget(frame)
23 self.grid_layout = QGridLayout(frame)
24
25 self.map_canvas = QgsMapCanvas()
26 self.map_canvas.setCanvasColor(QColor(255, 255, 255))
27 self.grid_layout.addWidget(self.map_canvas)
28
29 # setup action(s)
30 self.zoomin_action = QAction(
31 QIcon(":/ourapp/zoomin_icon"),
32 "Zoom In",
33 self)
34 # create toolbar
35 self.toolbar = self.addToolBar("Map Tools")
36 self.toolbar.addAction(self.zoomin_action)
37
38 # connect the tool(s)
39 self.zoomin_action.triggered.connect(self.zoom_in)
40
41 # create the map tool(s)
42 self.tool_zoomin = QgsMapToolZoom(self.map_canvas, False)
43
44 def add_ogr_layer(self, path):
45 (name, ext) = os.path.basename(path).split(’.’)
46 layer = QgsVectorLayer(path, name, ’ogr’)
47 QgsMapLayerRegistry.instance().addMapLayer(layer)
48 canvas_layer = QgsMapCanvasLayer(layer)
49 self.map_canvas.setLayerSet([canvas_layer])
50
51 def zoom_in(self):
52 self.map_canvas.setMapTool(self.tool_zoomin)
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
170 C H A P T E R 1 2 . W R I T I N G A S TA N DA L O N E A P P L I C AT I O N
Let’s look at the changes needed to get our Zoom In tool visible and work-
ing. In line 8 we import our resources file. This file contains the definition
of resources needed in our app, in this case just an icon for the Zoom In tool.
The resources.py file is created by compiling resources.qrc using the
pyrcc4 tool, just as we did in Section 10.4, Modifying the Resources File,
on page 139.
We also need a graphics file for the toolbar icon—we created a resources
We used mActionZoomIn.png found subdirectory and copied mActionZoomIn.png into it.
in the QGIS Documentation source:
https://github.com/qgis/QGIS- Then compiling the resources file gives us resources.py:
Documentation/tree/master/resources/
en/docs/common pyrcc4 -o resources.py -o resources.qrc
Lines 29 through 42 create the action and setup the zoom in tool. In line 31
we reference the icon specified in our resource file by using its alias.
In addition to the action, we need to create the QGIS map tool. This is done
in line 42, where we create a QgsMapToolZoom object, setting its parent
to the map canvas and specifying False as the second argument to make it
zoom in (setting to True would make a zoom out tool).
The last thing we need is the zoom_in method which simply sets the current
map canvas tool to our zoom in tool (lines 51 and 52).
With that, we can run the app and, as we see in Figure 12.3, on the facing
page, we now have a toolbar with our zoom in tool and we can use it to
manipulate the display. Note the cursor has changed to indicate the zoom
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 2 . 4 . A D D I N G MA P TO O L S TO T H E A P P L I CAT I O N 171
in tool is active. You might also have noticed that each time we run the
application, the fill color of our Alaska shapefile is different. This is because
QGIS supplies a random color when a layer is loaded. It will take some
additional work to select a color when the layer is loaded.
• Map tools to zoom out, pan, zoom to extent, and zoom full
• Set layer colors
• Select the shapefile to load by providing a file selection dialog box
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
172 C H A P T E R 1 2 . W R I T I N G A S TA N DA L O N E A P P L I C AT I O N
Packaging Your Standalone App
Packaging your standalone application can be a challenge, but there are some
utilities that can help:
• Linux: Freeze http://wiki.python.org/moin/Freeze
• Mac: py2app https://pypi.python.org/pypi/py2app/
• Windows: py2exe http://www.py2exe.org/
12.5 Exercises
1. Add a title to the main window of the app
2. Add some additional map tools to the app: zoom out, pan, and zoom
full
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
13
Answers to Exercises
1. addProject
2. addRasterLayer
3. iface.addVectorLayer(’/path/to/world_borders.shp’, ’world_borders’,
’ogr’)
• Plugins->ScriptRunner->ScriptRunner
• Plugins->Plugin Builder...->Plugin Builder
3. Use the mouse to hover over each icon in the Plugins toolbar to
locate each plugin
d. Click OK
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 3 . 5 . E X E R C I S E S : RU N N I N G S C R I P T S 175
2. Change the color to green with 50% transparency. You need to import
QColor in order for this work:
if ’,’ in color:
(red, green, blue, alpha) = color.split(’,’)
new_color = QColor.fromRgb(int(red), int(green), int(blue), int(alpha))
transparency = color.alpha() / 255.0
else:
new_color = QColor(color)
transparency = None
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
176 C H A P T E R 1 3 . A N S W E R S TO E X E R C I S E S
2. Use the URI method found in Section 8.1, Memory Layers, on page 88.
4. Use a QInputDialog.getText to get the new name, then get the object
(QgsFeature) for the selected feature. Modify the name and update
the attribute table using the data provider method. See Section 8.6,
Editing Attributes, on page 102 for hints.
2. Using Qt Designer remove the Line Edit widget and replace it with
a Label widget. Modify the code to reference the new widget in the
display_point function. Be sure to compile your UI changes using
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 3 . 9 . E X E R C I S E S : W R I T I N G A S TA N DA L O N E A P P L I C AT I O N 177
pyuic4.
3. Use the setSelection method of QLabel to select the text when the
new QToolButton is clicked, then copy it to the clipboard using QClip-
board. You will need to connect the triggered signal of the button to
a new method that selects the text and copies it to the clipboard. Test
your work by pasting into your text editor.
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
14
Appendix A: Installing QGIS
The installation instructions in this appendix are geared towards getting you
a functional install to support programming with PyQGIS.
3. All the defaults for the install are fine, with the exception of installa-
tion directory. Be sure to choose C:\qgis_2.0 or another directory
name that does not contain spaces.
set PATH=%PATH%;C:\qgis_2.0\apps\qgis\bin
180 C H A P T E R 1 4 . A P P E N D I X A : I N S TA L L I N G Q G I S
set PYTHONPATH=C:\qgis_2.0\apps\qgis\python
To use Python from the command line, run the OSGeo4W shell that was
installed with QGIS and then execute the pyqgis.cmd script. You now
have full access to the PyQGIS API from the interactive Python shell.
You might want to put PyQGIS.cmd in your path to make it easy to run
from any directory.
To install QGIS, first download and install the GDAL Complete framework
package, available from the same page. Then install the appropriate QGIS
package.
In order to access the QGIS libraries from Python (outside of QGIS), you’ll
need to add the following to your environment:
• QGISBASE=/Applications/QGIS.app/Contents
• export DYLD_LIBRARY_PATH=$QGISBASE/MacOS/lib
• export PYTHONPATH=$QGISBASE/Resources/python
You can do this from a shell script that you source each time you want to use
the libraries, or add them permanently to your $HOME/.bash_profile. Be
sure to set QGISBASE to the location of you QGIS application (by default
/Applications/QGIS.app).
• Debian
• Fedora
• RHEL / CentOS / Scientific Linux
• openSUSE
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 4 . 4 . BU I L D I N G Q G I S 181
• Mandriva
• Ubuntu
• Slackware
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
15
Appendix B: Code Listings
You can download all the code listed in this book at http://pyqgis.
com/code.
15.1 wrapper.py
24 else:
25 return None
26
27
28 def addOgrLayer(layerpath, name=None):
29 """ Add an OGR layer and return a reference to it.
30 If name is not passed, the filename will be used
31 in the legend.
32
33 User should check to see if layer is valid before
34 using it."""
35 if not name:
36 (path, filename) = os.path.split(layerpath)
37 name = filename
38
39 lyr = QgsVectorLayer(layerpath, name, ’ogr’)
40 return QgsMapLayerRegistry.instance().addMapLayer(lyr)
41
42
43 def addGdalLayer(layerpath, name=None):
44 """Add a GDAL layer and return a reference to it"""
45 if not name:
46 (path, filename) = os.path.split(layerpath)
47 name = filename
48
49 lyr = QgsRasterLayer(layerpath, name)
50 return QgsMapLayerRegistry.instance().addMapLayer(lyr)
51
52
53 def removeLayer(layer):
54 QgsMapLayerRegistry.instance().removeMapLayer(layer.id())
55
56
57 def createRGBA(color):
58 (red, green, blue, alpha) = color.split(’,’)
59 return QColor.fromRgb(int(red), int(green), int(blue), int(alpha))
60
61
62 def changeColor(layer, color):
63 """ Change the color of a layer using
64 Qt named colors, RGBA, or hex notation."""
65 if ’,’ in color:
66 # assume rgba color
67 color = createRGBA(color)
68 transparency = color.alpha() / 255.0
69 else:
70 color = QColor(color)
71 transparency = None
72
73 renderer = layer.rendererV2()
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
15.2. WHEREAMI PLUGIN 185
74 symb = renderer.symbol()
75 symb.setColor(color)
76 if transparency:
77 symb.setAlpha(transparency)
78 layer.setCacheImage(None)
79 iface.mapCanvas().refresh()
80 iface.legendInterface().refreshLayerSymbology(layer)
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
186 CHAPTER 15. APPENDIX B: CODE LISTINGS
2 """
3 /**************************************************************************
4 WhereAmI
5 A QGIS plugin
6 Display coordinates of a map click
7 -------------------
8 begin : 2013-12-07
9 copyright : (C) 2014 by gsherman
10 email : gsherman@geoapt.com
11 *************************************************************************/
12
13 /*************************************************************************
14 * *
15 * This program is free software; you can redistribute it and/or modify *
16 * it under the terms of the GNU General Public License as published by *
17 * the Free Software Foundation; either version 2 of the License, or *
18 * (at your option) any later version. *
19 * *
20 *************************************************************************/
21 """
22 import os
23 # Import the PyQt and QGIS libraries
24 from PyQt4.QtCore import *
25 from PyQt4.QtGui import *
26 from qgis.core import *
27 from qgis.gui import QgsMapToolEmitPoint
28 # Initialize Qt resources from file resources.py
29 import resources_rc
30 # Import the code for the dialog
31 from whereamidialog import WhereAmIDialog
32
33
34 class WhereAmI:
35
36 def __init__(self, iface):
37 # Save reference to the QGIS interface
38 self.iface = iface
39 # Create the dialog and keep reference
40 self.dlg = WhereAmIDialog()
41 # initialize plugin directory
42 self.plugin_dir = os.path.join(
43 QFileInfo(QgsApplication.qgisUserDbFilePath()).path(),
44 "/python/plugins/whereami")
45
46 # Store reference to the map canvas
47 self.canvas = self.iface.mapCanvas()
48 # Create the map tool using the canvas reference
49 self.pointTool = QgsMapToolEmitPoint(self.canvas)
50
51 # initialize locale
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
15.2. WHEREAMI PLUGIN 187
52 localePath = ""
53 locale = QSettings().value("locale/userLocale")[0:2]
54
55 if QFileInfo(self.plugin_dir).exists():
56 localePath = self.plugin_dir + "/i18n/whereami_" + locale + ".qm"
57
58 if QFileInfo(localePath).exists():
59 self.translator = QTranslator()
60 self.translator.load(localePath)
61
62 if qVersion() > ’4.3.3’:
63 QCoreApplication.installTranslator(self.translator)
64
65 def initGui(self):
66 # Create action that will start plugin configuration
67 self.action = QAction(
68 QIcon(":/plugins/whereami/whereami_icon.png"),
69 u"Where Am I?", self.iface.mainWindow(),
70 toolTip=’Show me where I am’,
71 triggered=self.run)
72 # connect signal that the canvas was clicked
73 self.pointTool.canvasClicked.connect(self.display_point)
74
75 # Add toolbar button and menu item
76 self.iface.addToolBarIcon(self.action)
77 self.iface.addPluginToMenu(u"&Where Am I?", self.action)
78
79 def unload(self):
80 # Remove the plugin menu item and icon
81 self.iface.removePluginMenu(u"&Where Am I?", self.action)
82 self.iface.removeToolBarIcon(self.action)
83
84 def display_point(self, point, button):
85 # report map coordinates from a canvas click
86 self.dlg.hide()
87 coords = "{}, {}".format(point.x(), point.y())
88 self.dlg.ui.lineEdit.setText(str(coords))
89 # show the dialog
90 if self.dlg.userPos is not None:
91 self.dlg.move(self.dlg.userPos)
92 self.dlg.show()
93
94 # run method that performs all the real work
95 def run(self):
96 # set the map tool
97 self.canvas.setMapTool(self.pointTool)
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
188 CHAPTER 15. APPENDIX B: CODE LISTINGS
2 """
3 /*************************************************************************
4 WhereAmIDialog
5 A QGIS plugin
6 Display coordinates of a map click
7 -------------------
8 begin : 2013-12-07
9 copyright : (C) 2014 by gsherman
10 email : gsherman@geoapt.com
11 *************************************************************************/
12
13 /*************************************************************************
14 * *
15 * This program is free software; you can redistribute it and/or modify *
16 * it under the terms of the GNU General Public License as published by *
17 * the Free Software Foundation; either version 2 of the License, or *
18 * (at your option) any later version. *
19 * *
20 *************************************************************************/
21 """
22
23 from PyQt4 import QtCore, QtGui
24 from ui_whereami import Ui_WhereAmI
25 # create the dialog for zoom to point
26
27
28 class WhereAmIDialog(QtGui.QDialog):
29 def __init__(self):
30 QtGui.QDialog.__init__(self)
31 # Set up the user interface from Designer.
32 self.ui = Ui_WhereAmI()
33 self.ui.setupUi(self)
34 # attribute for storing the position of the dialog
35 self.userPos = None
36
37 def moveEvent(self, event):
38 self.userPos = event.pos()
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 5 . 3 . S TA N DA L O N E A P P L I C AT I O N 189
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
190 CHAPTER 15. APPENDIX B: CODE LISTINGS
27 self.grid_layout.addWidget(self.map_canvas)
28
29 # setup action(s)
30 self.zoomin_action = QAction(
31 QIcon(":/ourapp/zoomin_icon"),
32 "Zoom In",
33 self)
34 # create toolbar
35 self.toolbar = self.addToolBar("Map Tools")
36 self.toolbar.addAction(self.zoomin_action)
37
38 # connect the tool(s)
39 self.zoomin_action.triggered.connect(self.zoom_in)
40
41 # create the map tool(s)
42 self.tool_zoomin = QgsMapToolZoom(self.map_canvas, False)
43
44 def add_ogr_layer(self, path):
45 (name, ext) = os.path.basename(path).split(’.’)
46 layer = QgsVectorLayer(path, name, ’ogr’)
47 QgsMapLayerRegistry.instance().addMapLayer(layer)
48 canvas_layer = QgsMapCanvasLayer(layer)
49 self.map_canvas.setLayerSet([canvas_layer])
50
51 def zoom_in(self):
52 self.map_canvas.setMapTool(self.tool_zoomin)
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
16
Appendix C: Porting Scripts to 2.0
Porting scripts from 1.x to 2.0 can be a matter of trial and error. The QGIS
wiki has a list of the changes from 1.8 to 2.038 , as well as a summary of
changes in the API39 . 38
http://hub.qgis.org/wiki/
quantum-gis/Python_plugin_
API_changes_from_18_to_20
16.1 Changes in __init__.py and metadata.txt 39
http://hub.qgis.org/
projects/quantum-gis/wiki/
At 2.0, metadata.txt contains the information needed to recognize and
API_changes_for_version_20
initialize a plugin in Plugin Manager. Previously the __init__.py script
contains all this information. Now it only needs the classFactory(iface)
function.
# Mandatory items:
[general]
name=Display coordinates of a map click
qgisMinimumVersion=2.0
description=Display coordinates of a map click
version=0.1
author=gsherman
192 C H A P T E R 1 6 . A P P E N D I X C : P O RT I N G S C R I P T S TO 2 . 0
email=gsherman@geoapt.com
# Optional items:
homepage=
tracker=
repository=
icon=whereami_icon.png
# experimental flag
experimental=False
# deprecated flag (applies to the whole plugin, not just a single version
deprecated=False
To ensure your plugin can be loaded, the mandatory items must be present.
If you specify a qgisMinimumVersion less than 2.0, it won’t show up in the
Plugin Manager.
These and other changes are detailed in the Python Plugin API Changes
from 1.8 to 2.0 document41 . It provides examples and of changes required
41
http://hub.qgis.org/wiki/ to migrate a plugin from 1.8 to 2.0.
17/Python_plugin_API_changes_
from_18_to_20
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
17
Index
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
195
Requirements, 13 connecting, 54
subclassing, 25
Sample data, 13
Scripts Text editors, 38
documenting, 84–86 Tools
running, 75–86 adding existing, 114–115
running in console, 75
running with Script Runner, 81–83 Vector Layers
using a class, 79–81 loading, 87
Signal OGR, 87
G E N E R AT E D F O R G I L B E RT O S C H W E RT N E R O N 2 0 1 4 - 0 1 - 0 8 — T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
Books from Locate Press
The Geospatial Desktop provides a foundational level of knowledge for under-
standing GIS and the open source desktop mapping applications that are available for
use, for free, today.
Learn about vector and raster data, how
to convert data, interacting with spatial
databases, creating new map data, geopro-
cessing, scripting, and more.
Special sections include focused learning on
the Quantum GIS and GRASS GIS software
platforms as well as an introduction to other
packages.
The Geospatial Desktop is written by the
founder of the Quantum GIS project, so you
can rest assured that you will be led by one of the most knowledgeable authors on
the subject.
The Quantum GIS Training Manual Get the jump-start you need
to learn this incredibly popular free desktop mapping and GIS toolset.
Comprehensive and structured, your intro-
duction begins with a quick download of ex-
ample data, making it easy for you to work
your way through the concepts and practical
exercises, complete with answers and exam-
ples.
Ideal for classroom instruction and self-
guided learning, included are all the materi-
als needed to run a five day course on Quan-
tum GIS, PostgreSQL and PostGIS. Content
is structured for novice, intermediate and advanced users alike. Seasoned Quantum
GIS users will also find tips and new techniques to apply to every mapping project.
Windows, Mac OS X, or Linux? It’s your choice, this book works for all.
Geospatial Power Tools Everyone loves power tools. The GDAL and OGR
utilities are the power tools of the GIS world, and best of all, they’re free.
The utilities include tools for examining, con-
verting, transforming, building and analysing
data. This book is a collection of the
GDAL and OGR documentation, but also in-
cludes substantial new content designed to
help guide you in using the utilities to solve
your current data problems.
Inside you’ll find a quick reference for look-
ing up the right syntax and example usage
quickly. The book is divided into three parts:
• Part I - Workflows and examples
• Part II - GDAL raster utilities
• Part III - OGR vector utilities
Once you get a taste of the power the GDAL/OGR suite provides, you’ll wonder
how you ever got along without them. This book will get you on the fast track to
becoming more efficient in your GIS data processing efforts.