Building Mapping Applications with QGIS
上QQ阅读APP看书,第一时间看更新

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.

Maps and map layers

A map consists of multiple layers drawn one on top of the other:

Maps and map layers

There are three types of map layers supported by QGIS:

  • Vector layer: This layer draws geospatial features such as points, lines, and polygons
  • Raster layer: This layer draws raster (bitmapped) data onto a map
  • Plugin layer: This layer allows a plugin to draw directly onto a map

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.

Coordinate reference systems

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):

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.

Vector layers

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 the QgsFeatureRequest 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:

  • Data provider: This is the connection to the underlying file or database that holds the geospatial information to be displayed
  • Coordinate reference system: This indicates which CRS the geospatial data uses
  • Renderer: This chooses how the features are to be displayed

Let's take a closer look at the concept of a renderer and how features are displayed within a vector map layer.

Displaying vector data

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:

  • Marker symbol: This displays a point as a filled circle
  • Line symbol: This draws a line using a given line width and color
  • Fill symbol: This draws the interior of a polygon with a given color

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)

Accessing vector data

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.

Spatial indexes

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 layers

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.

How raster data is displayed

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:

  1. Turn off raster image caching. This can be done by calling layer.setImageCache(None).
  2. Tell the raster layer to redraw itself, by calling layer.triggerRepaint().

Accessing raster data

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.

Tip

You can also use the provider.block() method to retrieve the band data for a large number of coordinates all at once. We will look at how to do this later in this chapter.

Other useful qgis.core classes

Apart from all the classes and functionality involved in working with data sources and map layers, the qgis.core library also defines a number of other classes that you might find useful:

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.

The QgisInterface class

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.

Note

The QgisInterface object is only available when running the QGIS application itself. If you are running an external application and import the PyQGIS library into your application, QgisInterface won't be available.

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 the setActiveLayer() 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 QgsMapCanvas class

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, while QgisInterface.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 the mapTool() 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.

The QgsMapCanvasItem class

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. The QgsAnnotationItem class has various subclasses that allow you to customize the way the information is displayed.

The QgsMapTool class

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.

Other useful qgis.gui classes

While the qgis.gui package defines a large number of classes, the ones you are most likely to find useful are given in the following table: