
Organizing the QGIS Python libraries
Now that we can understand the C++-oriented documentation, let's see how the PyQGIS libraries are structured. All of the PyQGIS libraries are organized under a package named qgis
. You wouldn't normally import qgis
directly, however, as all the interesting libraries are subpackages within this main package; here are the five packages that make up the PyQGIS library:

The first two packages (qgis.core
and qgis.gui
) implement the most important parts of the PyQGIS library, and it's worth spending some time to become more familiar with the concepts and classes they define. Let's take a closer look at these two packages now.
The qgis.core package
The qgis.core
package defines fundamental classes used throughout the QGIS system. A large part of this package is dedicated to working with vector and raster format geospatial data, and displaying these types of data within a map. Let's see how this is done.
A map consists of multiple layers drawn one on top of the other:

There are three types of map layers supported by QGIS:
Each of these types of map layers has a corresponding class within the qgis.core
library. For example, a vector map layer will be represented by an object of type qgis.core.QgsVectorLayer
.
We will take a closer look at vector and raster map layers shortly. Before we do this, though, we need to learn how geospatial data (both vector and raster data) is positioned on a map.
Since the Earth is a three-dimensional object, while maps represent the Earth's surface as a two-dimensional plane, there has to be a way of translating from points on the Earth's surface into (x,y) coordinates within a map. This is done using a Coordinate Reference System (CRS):

Globe image courtesy Wikimedia (http://commons.wikimedia.org/wiki/File:Rotating_globe.gif)
A CRS has two parts: an ellipsoid, which is a mathematical model of the Earth's surface, and a projection, which is a formula that converts points on the surface of the spheroid into (x,y) coordinates on a map.
Fortunately, most of the time you can simply select the appropriate CRS that matches the CRS of the data you are using. However, because many different coordinate reference systems have been devised over the years, it is vital that you use the correct CRS when plotting your geospatial data. If you don't do this, your features will be displayed in the wrong place or have the wrong shape.
The majority of geospatial data available today uses the EPSG 4326 coordinate reference system (sometimes also referred to as WGS84). This CRS defines coordinates as latitude and longitude values. This is the default CRS used for new data imported into QGIS. However, if your data uses a different coordinate reference system, you will need to create and use a different CRS for your map layer.
The qgis.core.QgsCoordinateReferenceSystem
class represents a CRS. Once you create your coordinate reference system, you can tell your map layer to use that CRS when accessing the underlying data. For example:
crs = QgsCoordinateReferenceSystem(4326, QgsCoordinateReferenceSystem.EpsgCrsId) layer.setCrs(crs)
Note that different map layers can use different coordinate reference systems. Each layer will use its CRS when drawing the contents of the layer onto the map.
A vector layer draws geospatial data onto a map in the form of points, lines, polygons, and so on. Vector-format geospatial data is typically loaded from a vector data source such as a shapefile or database. Other vector data sources can hold vector data in memory, or load data from a web service across the Internet.
A vector-format data source has a number of features, where each feature represents a single record within the data source. The qgis.core.QgsFeature
class represents a feature within a data source. Each feature has the following components:
- ID: This is the feature's unique identifier within the data source
- Geometry: This is the underlying point, line, polygon, and so on, which represents the feature on the map. For example, a city data source would have one feature for each city, and the geometry would typically be either a point that represents the center of the city, or a polygon (or a multipolygon) that represents the city's outline.
- Attributes: These are key/value pairs that provide additional information about the feature. For example, a city data source representing cities might have attributes such as
total_area
,population
,elevation
, and so on. Attribute values can be strings, integers, or floating point numbers.
In QGIS, a data provider allows the vector layer to access the features within the data source. The data provider, an instance of qgis.core.QgsVectorDataProvider
, includes:
- A geometry type that is stored in the data source
- A list of fields that provide information about the attributes stored for each feature
- The ability to search through the features within the data source, using the
getFeatures()
method and theQgsFeatureRequest
class
You can access the various vector (and also raster) data providers by using the qgis.core.QgsProviderRegistry
class.
The vector layer itself is represented by a qgis.core.QgsVectorLayer
object. Each vector layer includes:
Let's take a closer look at the concept of a renderer and how features are displayed within a vector map layer.
The features within a vector map layer are displayed using a combination of renderer and symbol objects. The renderer chooses the symbol that has to be used for a given feature, and the symbol that does the actual drawing.
There are three basic types of symbols defined by QGIS:
These three types of symbols are implemented as subclasses of the qgis.core.QgsSymbolV2
class:
qgis.core.QgsMarkerSymbolV2
qgis.core.QgsLineSymbolV2
qgis.core.QgsFillSymbolV2
Note
You might be wondering why all these classes have "V2" in their name. This is a historical quirk of QGIS. Earlier versions of QGIS supported both an "old" and a "new" system of rendering, and the "V2" naming refers to the new rendering system. The old rendering system no longer exists, but the "V2" naming continues to maintain backward compatibility with existing code.
Internally, symbols are rather complex, using "symbol layers" to draw multiple elements on top of each other. In most cases, however, you can make use of the "simple" version of the symbol. This makes it easier to create a new symbol without having to deal with the internal complexity of symbol layers. For example:
symbol = QgsMarkerSymbolV2.createSimple({'width' : 1.0, 'color' : "255,0,0"})
While symbols draw the features onto the map, a renderer is used to choose which symbol to use to draw a particular feature. In the simplest case, the same symbol is used for every feature within a layer. This is called a single symbol renderer, and is represented by the qgis.core.QgsSingleSymbolRenderV2
class. Other possibilities include:
- Categorized symbol renderer (
qgis.core.QgsCategorizedSymbolRendererV2
): This renderer chooses a symbol based on the value of an attribute. The categorized symbol renderer has a mapping from attribute values to symbols. - Graduated symbol renderer (
qgis.core.QgsGraduatedSymbolRendererV2
): This type of renderer uses ranges of attribute values, and maps each range to an appropriate symbol.
Using a single symbol renderer is very straightforward:
symbol = ... renderer = QgsSingleSymbolRendererV2(symbol) layer.setRendererV2(renderer)
To use a categorized symbol renderer, you first define a list of qgis.core.QgsRendererCategoryV2
objects, and then use that to create the renderer. For example:
symbol_male = ... symbol_female = ... categories = [] categories.append(QgsRendererCategoryV2("M", symbol_male, "Male")) categories.append(QgsRendererCategoryV2("F", symbol_female, "Female")) renderer = QgsCategorizedSymbolRendererV2("", categories) renderer.setClassAttribute("GENDER") layer.setRendererV2(renderer)
Notice that the QgsRendererCategoryV2
constructor takes three parameters: the desired value, the symbol used, and the label used to describe that category.
Finally, to use a graduated symbol renderer, you define a list of qgis.core.QgsRendererRangeV2
objects and then use that to create your renderer. For example:
symbol1 = ... symbol2 = ... ranges = [] ranges.append(QgsRendererRangeV2(0, 10, symbol1, "Range 1")) ranges.append(QgsRendererRange(11, 20, symbol2, "Range 2")) renderer = QgsGraduatedSymbolRendererV2("", ranges) renderer.setClassAttribute("FIELD") layer.setRendererV2(renderer)
In addition to displaying the contents of a vector layer within a map, you can use Python to directly access the underlying data. This can be done using the data provider's getFeatures()
method. For example, to iterate over all the features within the layer, you can do the following:
provider = layer.dataProvider() for feature in provider.getFeatures(QgsFeatureRequest()): ...
If you want to search for features based on some criteria, you can use the QgsFeatureRequest
object's setFilterExpression()
method, as follows:
provider = layer.dataProvider() request = QgsFeatureRequest() request.setFilterExpression('"GENDER" = "M"') for feature in provider.getFeatures(QgsFeatureRequest()): ...
Once you have the features, it's easy to get access to the feature's geometry, ID, and attributes. For example:
geometry = feature.geometry() id = feature.id() name = feature.attribute("NAME")
The object returned by the feature.geometry()
call, which will be an instance of qgis.core.QgsGeometry
, represents the feature's geometry. This object has a large number of methods you can use to extract the underlying data and perform various geospatial calculations.
In the previous section, we searched for features based on their attribute values. There are times, though, when you might want to find features based on their position in space. For example, you might want to find all features that lie within a certain distance of a given point. To do this, you can use a spatial index, which indexes features according to their location and extent. Spatial indexes are represented in QGIS by the QgsSpatialIndex
class.
For performance reasons, a spatial index is not created automatically for each vector layer. However, it's easy to create one when you need it:
provider = layer.dataProvider() index = QgsSpatialIndex() for feature in provider.getFeatures(QgsFeatureRequest()): index.insertFeature(feature)
Don't forget that you can use the QgsFeatureRequest.setFilterExpression()
method to limit the set of features that get added to the index.
Once you have the spatial index, you can use it to perform queries based on the position of the features. In particular:
- You can find one or more features that are closest to a given point using the
nearestNeighbor()
method. For example:features = index.nearestNeighbor(QgsPoint(long, lat), 5)
Note that this method takes two parameters: the desired point as a
QgsPoint
object and the number of features to return. - You can find all features that intersect with a given rectangular area by using the
intersects()
method, as follows:features = index.intersects(QgsRectangle(left, bottom, right, top))
Raster-format geospatial data is essentially a bitmapped image, where each pixel or "cell" in the image corresponds to a particular part of the Earth's surface. Raster data is often organized into bands, where each band represents a different piece of information. A common use for bands is to store the red, green, and blue component of the pixel's color in a separate band. Bands might also represent other types of information, such as moisture level, elevation, or soil type.
There are many ways in which raster information can be displayed. For example:
- If the raster data only has one band, the pixel value can be used as an index into a palette. The palette maps each pixel value maps to a particular color.
- If the raster data has only one band but no palette is provided, the pixel values can be used directly as a grayscale value; that is, larger numbers are lighter and smaller numbers are darker. Alternatively, the pixel values can be passed through a pseudocolor algorithm to calculate the color to be displayed.
- If the raster data has multiple bands, then typically, the bands would be combined to generate the desired color. For example, one band might represent the red component of the color, another band might represent the green component, and yet another band might represent the blue component.
- Alternatively, a multiband raster data source might be drawn using a palette, or as a grayscale or a pseudocolor image, by selecting a particular band to use for the color calculation.
Let's take a closer look at how raster data can be drawn onto the map.
The drawing style associated with the raster band controls how the raster data will be displayed. The following drawing styles are currently supported:

To set the drawing style, use the layer.setDrawingStyle()
method, passing in a string that contains the name of the desired drawing style. You will also need to call the various setXXXBand()
methods, as described in the preceding table, to tell the raster layer which bands contain the value(s) to use to draw each pixel.
Note that QGIS doesn't automatically update the map when you call the preceding functions to change the way the raster data is displayed. To have your changes displayed right away, you'll need to do the following:
- Turn off raster image caching. This can be done by calling
layer.setImageCache(None)
. - Tell the raster layer to redraw itself, by calling
layer.triggerRepaint()
.
As with vector-format data, you can access the underlying raster data via the data provider's identify()
method. The easiest way to do this is to pass in a single coordinate and retrieve the value or values at that coordinate. For example:
provider = layer.dataProvider() values = provider.identify(QgsPoint(x, y), QgsRaster.IdentifyFormatValue) if values.isValid(): for band,value in values.results().items(): ...
As you can see, you need to check whether the given coordinate exists within the raster data (using the isValid()
call). The values.results()
method returns a dictionary that maps band numbers to values.
Using this technique, you can extract all the underlying data associated with a given coordinate within the raster layer.
The qgis.gui package
The qgis.gui
package defines a number of user-interface widgets that you can include in your programs. Let's start by looking at the most important qgis.gui
classes, and follow this up with a brief look at some of the other classes that you might find useful.
QgisInterface
represents the QGIS system's user interface. It allows programmatic access to the map canvas, the menu bar, and other parts of the QGIS application. When running Python code within a script or a plugin, or directly from the QGIS Python console, a reference to QgisInterface
is typically available through the iface
global variable.
Some of the more important things you can do with the QgisInterface
object are:
- Get a reference to the list of layers within the current QGIS project via the
legendInterface()
method. - Get a reference to the map canvas displayed within the main application window, using the
mapCanvas()
method. - Retrieve the currently active layer within the project, using the
activeLayer()
method, and set the currently active layer by using thesetActiveLayer()
method. - Get a reference to the application's main window by calling the
mainWindow()
method. This can be useful if you want to create additional Qt windows or dialogs that use the main window as their parent. - Get a reference to the QGIS system's message bar by calling the
messageBar()
method. This allows you to display messages to the user directly within the QGIS main window.
The map canvas is responsible for drawing the various map layers into a window. The QgsMapCanvas
class represents a map canvas. This class includes:
- A list of the currently shown map layers. This can be accessed using the
layers()
method.Tip
Note that there is a subtle difference between the list of map layers available within the map canvas and the list of map layers included in the
QgisInterface.legendInterface()
method. The map canvas's list of layers only includes the list of layers currently visible, whileQgisInterface.legendInterface()
returns all the map layers, including those that are currently hidden. - The map units used by this map (meters, feet, degrees, and so on). The map's map units can be retrieved by calling the
mapUnits()
method. - An extent, which is the area of the map currently shown within the canvas. The map's extent will change as the user zooms in and out, and pans across the map. The current map extent can be obtained by calling the
extent()
method. - A current map tool that is used to control the user's interaction with the contents of the map canvas. The current map tool can be set using the
setMapTool()
method, and you can retrieve the current map tool (if any) by calling themapTool()
method. - A background color used to draw the background behind all the map layers. You can change the map's background color by calling the
canvasColor()
method. - A coordinate transform that converts from map coordinates (that is, coordinates in the data source's coordinate reference system) to pixels within the window. You can retrieve the current coordinate transform by calling the
getCoordinateTransform()
method.
A map canvas item is an item drawn on top of the map canvas. The map canvas item will appear in front of the map layers. While you can create your own subclass of QgsMapCanvasItem
if you want to draw custom items on top of the map canvas, you will find it easier to use an existing subclass that does much of the work for you. There are currently three subclasses of QgsMapCanvasItem
that you might find useful:
QgsVertexMarker
: This draws an icon (an "X", a "+", or a small box) centered around a given point on the map.QgsRubberBand
: This draws an arbitrary polygon or polyline onto the map. It is intended to provide visual feedback as the user draws a polygon onto the map.QgsAnnotationItem
: This is used to display additional information about a feature, in the form of a balloon that is connected to the feature. TheQgsAnnotationItem
class has various subclasses that allow you to customize the way the information is displayed.
A map tool allows the user to interact with and manipulate the map canvas, capturing mouse events and responding appropriately. A number of QgsMapTool
subclasses provide standard map interaction behavior such as clicking to zoom in, dragging to pan the map, and clicking on a feature to identify it. You can also create your own custom map tools by subclassing QgsMapTool
and implementing the various methods that respond to user-interface events such as pressing down the mouse button, dragging the canvas, and so on.
Once you have created a map tool, you can allow the user to activate it by associating the map tool with a toolbar button. Alternatively, you can activate it from within your Python code by calling the mapCanvas.setMapTool(...)
method.
We will look at the process of creating your own custom map tool in the section Using the PyQGIS library.