Outdated version of the documentation. Find the latest one here.

Работа с векторными слоями

Этот раздел описывает различные действия, которые можно выполнять с векторными слоями.

Обход объектов векторного слоя

Iterating over the features in a vector layer is one of the most common tasks. Below is an example of the simple basic code to perform this task and showing some information about each feature. the layer variable is assumed to have a QgsVectorLayer object

iter = layer.getFeatures()
for feature in iter:
  # retreive every feature with its geometry and attributes
    # fetch geometry
    geom = feature.geometry()
    print "Feature ID %d: " % feature.id()

    # show some information about the feature
    if geom.type() == QGis.Point:
      x = geom.asPoint()
      print "Point: " + str(x)
    elif geom.type() == QGis.Line:
      x = geom.asPolyline()
      print "Line: %d points" % len(x)
    elif geom.type() == QGis.Polygon:
      x = geom.asPolygon()
      numPts = 0
      for ring in x:
        numPts += len(ring)
      print "Polygon: %d rings with %d points" % (len(x), numPts)
    else:
      print "Unknown"

    # fetch attributes
    attrs = feature.attributes()

    # attrs is a list. It contains all the attribute values of this feature
    print attrs

Attributes can be refered by index.

idx = layer.fieldNameIndex('name')
print feature.attributes()[idx]

Iterating over selected features

Convenience methods

For the above cases, and in case you need to consider selection in a vector layer in case it exist, you can use the features() method from the buil-in processing plugin, as follows:

import processing
features = processing.features(layer)
for feature in features:
  #Do whatever you need with the feature

This will iterate over all the features in the layer, in case there is no selection, or over the selected features otherwise.

if you only need selected features, you can use the :func: selectedFeatures method from vector layer:

selection = layer.selectedFeatures()
print len(selection)
for feature in selection:
  #Do whatever you need with the feature

Iterating over a subset of features

If you want to iterate over a given subset of features in a layer, such as those within a given area, you have to add a QgsFeatureRequest object to the getFeatures() call. Here’s an example

request=QgsFeatureRequest()
request.setFilterRect(areaOfInterest)
  for f in layer.getFeatures(request):
    ...

The request can be used to define the data retrieved for each feature, so the iterator returns all features, but return partial data for each of them.

request.setSubsetOfFields([0,2])                  # Only return selected fields
request.setSubsetOfFields(['name','id'],layer.fields())  # More user friendly version
request.setFlags( QgsFeatureRequest.NoGeometry )  # Don't return geometry objects

Редактирование векторных слоёв

Most vector data providers support editing of layer data. Sometimes they support just a subset of possible editing actions. Use the capabilities() function to find out what set of functionality is supported:

caps = layer.dataProvider().capabilities()

При использовании любого из следующих методов редактирования слоя, изменения будут применяться к соответствующему набору данных (файлу, базе данных и т.д) сразу же. Если необходимо произвести временные изменения, следующий раздел можно пропустить и перейти сразу к разделу, описывающему редактирование с использованием буфера изменений.

Добавление объектов

Create some QgsFeature instances and pass a list of them to provider’s addFeatures() method. It will return two values: result (true/false) and list of added features (their ID is set by the data store):

if caps & QgsVectorDataProvider.AddFeatures:
  feat = QgsFeature()
  feat.addAttribute(0,"hello")
  feat.setGeometry(QgsGeometry.fromPoint(QgsPoint(123,456)))
  (res, outFeats) = layer.dataProvider().addFeatures( [ feat ] )

Удаление объектов

To delete some features, just provide a list of their feature IDs:

if caps & QgsVectorDataProvider.DeleteFeatures:
  res = layer.dataProvider().deleteFeatures([ 5, 10 ])

Изменение объектов

It is possible to either change feature’s geometry or to change some attributes. The following example first changes values of attributes with index 0 and 1, then it changes the feature’s geometry:

fid = 100   # ID of the feature we will modify

if caps & QgsVectorDataProvider.ChangeAttributeValues:
  attrs = { 0 : "hello", 1 : 123 }
  layer.dataProvider().changeAttributeValues({ fid : attrs })

if caps & QgsVectorDataProvider.ChangeGeometries:
  geom = QgsGeometry.fromPoint(QgsPoint(111,222))
  layer.dataProvider().changeGeometryValues({ fid : geom })

Добавление и удаление полей

To add fields (attributes), you need to specify a list of field defnitions. For deletion of fields just provide a list of field indexes.

if caps & QgsVectorDataProvider.AddAttributes:
  res = layer.dataProvider().addAttributes( [ QgsField("mytext", \
    QVariant.String), QgsField("myint", QVariant.Int) ] )

if caps & QgsVectorDataProvider.DeleteAttributes:
  res = layer.dataProvider().deleteAttributes( [ 0 ] )

After adding or removing fields in the data provider the layer’s fields need to be updated because the changes are not automatically propagated.

layer.updateFields()

Редактирование векторных слоёв с использованием буфера изменений

При редактировании векторных данных в QGIS, сначала необходимо перевести соответствущий слой в режим редактирования, затем внести изменения и, наконец, зафиксировать (или отменить) эти изменения. Все сделанные изменения не применяются до тех пор, пока вы их не зафиксируете — они хранятся в буфере изменений слоя. Данную возможность можно использовать и программно — это всего лишь другой способ редактирования векторных слоёв, дополняющий прямой доступ к провайдеру. Использовать этот функционал стоит тогда, когда пользователю предоставляются графические инструменты редактирования, чтобы он мог решить когда фиксировать/отменять изменения, а также мог использовать инструменты повтора/отмены. При фиксации изменений, все имеющиеся в буфере операции будут переданы провайдеру.

To find out whether a layer is in editing mode, use isEditing() — the editing functions work only when the editing mode is turned on. Usage of editing functions:

# add two features (QgsFeature instances)
layer.addFeatures([feat1,feat2])
# delete a feature with specified ID
layer.deleteFeature(fid)

# set new geometry (QgsGeometry instance) for a feature
layer.changeGeometry(fid, geometry)
# update an attribute with given field index (int) to given value (QVariant)
layer.changeAttributeValue(fid, fieldIndex, value)

# add new field
layer.addAttribute(QgsField("mytext", QVariant.String))
# remove a field
layer.deleteAttribute(fieldIndex)

In order to make undo/redo work properly, the above mentioned calls have to be wrapped into undo commands. (If you do not care about undo/redo and want to have the changes stored immediately, then you will have easier work by editing with data provider.) How to use the undo functionality

layer.beginEditCommand("Feature triangulation")

# ... call layer's editing methods ...

if problem_occurred:
  layer.destroyEditCommand()
  return

# ... more editing ...

layer.endEditCommand()

The beginEndCommand() will create an internal “active” command and will record subsequent changes in vector layer. With the call to endEditCommand() the command is pushed onto the undo stack and the user will be able to undo/redo it from GUI. In case something went wrong while doing the changes, the destroyEditCommand() method will remove the command and rollback all changes done while this command was active.

Для актививации режима редактирования используется метод startEditing(), за завершение редактирования отвечают commitChanges() и rollback() — однако в общем случае эти методы вам не нужны, т.к. вызываться они должны конечным пользователем.

Использование пространственного индекса

Spatial indexes can dramatically improve the performance of your code if you need to do frequent queries to a vector layer. Imagin, for instance, that you are writing an interpolation algorithm, and that for a given location you need to know the 10 closest point from a points layer,, in order to use those point for calculating the interpolated value. Without a spatial index, the only way for QGIS to find those 10 points is to compute the distance from each and every point to the specified location and then compare those distances. This can be a very time consuming task, specilly if it needs to be repeated fro several locations. If a spatial index exists for the layer, the operation is much more effective.

Think of a layer withou a spatial index as a telephone book in which telephone number are not orderer or indexed. The only way to find the telephone number of a given person is to read from the beginning until you find it.

Spatial indexes are not created by default for a QGIS vector layer, but you can create them easily. This is what you have to do.

  1. create spatial index — the following code creates an empty index:

    index = QgsSpatialIndex()
    
  2. add features to index — index takes QgsFeature object and adds it to the internal data structure. You can create the object manually or use one from previous call to provider’s nextFeature()

    index.insertFeature(feat)
    
  3. once spatial index is filled with some values, you can do some queries:

    # returns array of feature IDs of five nearest features
    nearest = index.nearestNeighbor(QgsPoint(25.4, 12.7), 5)
    
    # returns array of IDs of features which intersect the rectangle
    intersect = index.intersects(QgsRectangle(22.5, 15.3, 23.1, 17.2))
    

Запись векторных слоёв

Для записи векторных данных на диск служит класс QgsVectorFileWriter. Он позволяет создавать векторные файлы в любом, поддерживаемом OGR, формате (shape-файлы, GeoJSON, KML и другие).

Существует два способа записать векторные данные в файл:

  • from an instance of QgsVectorLayer:

    error = QgsVectorFileWriter.writeAsVectorFormat(layer, "my_shapes.shp", \
      "CP1250", None, "ESRI Shapefile")
    
    if error == QgsVectorFileWriter.NoError:
      print "success!"
    
    error = QgsVectorFileWriter.writeAsVectorFormat(layer, "my_json.json", \
      "utf-8", None, "GeoJSON")
    if error == QgsVectorFileWriter.NoError:
      print "success again!"
    

    The third parameter specifies output text encoding. Only some drivers need this for correct operation - shapefiles are one of those — however in case you are not using international characters you do not have to care much about the encoding. The fourth parameter that we left as None may specify destination CRS — if a valid instance of QgsCoordinateReferenceSystem is passed, the layer is transformed to that CRS.

    For valid driver names please consult the supported formats by OGR — you should pass the value in `the “Code” column as the driver name. Optionally you can set whether to export only selected features, pass further driver-specific options for creation or tell the writer not to create attributes — look into the documentation for full syntax.

  • directly from features:

    # define fields for feature attributes. A list of QgsField objects is needed
    fields = [QgsField("first", QVariant.Int),
              QgsField("second", QVariant.String) ]
    
    # create an instance of vector file writer, which will create the vector file.
    # Arguments:
    # 1. path to new file (will fail if exists already)
    # 2. encoding of the attributes
    # 3. field map
    # 4. geometry type - from WKBTYPE enum
    # 5. layer's spatial reference (instance of
    #    QgsCoordinateReferenceSystem) - optional
    # 6. driver name for the output file
    writer = QgsVectorFileWriter("my_shapes.shp", "CP1250", fields, \
      QGis.WKBPoint, None, "ESRI Shapefile")
    
    if writer.hasError() != QgsVectorFileWriter.NoError:
      print "Error when creating shapefile: ", writer.hasError()
    
    # add a feature
    fet = QgsFeature()
    fet.setGeometry(QgsGeometry.fromPoint(QgsPoint(10,10)))
    fet.setAttributes([1, "text"])
    writer.addFeature(fet)
    
    # delete the writer to flush features to disk (optional)
    del writer
    

Memory провайдер

Memory провайдер в основном предназначен для использования разработчиками расширений или сторонних приложений. Этот провайдер не хранит данные на диске, что позволят разработчикам использовать его в качестве быстрого хранилища для временных слоёв.

Провайдер поддерживает строковые и целочисленные поля, а также поля с плавающей запятой.

Memory провайдер помимо всего прочего поддерживает и пространственное индексирование, пространственный индекс можно создать вызовав функцию createSpatialIndex() провайдера. После создания пространственного индекса обход объектов в пределах небольшой области станет более быстрым (поскольку обращение будет идти только к объектам, попадающим в заданный прямоугольник).

Memory провайдер будет использоваться если в качестве идентификатора провайдера при вызове конструктора QgsVectorLayer указана строка "memory".

В конструктор также передается URI, описывающий геометрию слоя, это может быть: "Point", "LineString", "Polygon", "MultiPoint", "MultiLineString" или "MultiPolygon".

The URI can also specify the coordinate reference system, fields, and indexing of the memory provider in the URI. The syntax is:

crs=definition

Задает используемую систему координат, definition может принимать любой вид, совместимый с QgsCoordinateReferenceSystem.createFromString()

index=yes

Определяет будет ли провайдер использовать пространственный индекс

field=name:type(length,precision)

Задает атрибуты слоя. Каждый атрибут имеет имя и, опционально, тип (целое число, вещественное число или строка), длину и точность. Таких описаний может быть несколько.

The following example of a URI incorporates all these options:

"Point?crs=epsg:4326&field=id:integer&field=name:string(20)&index=yes"

The following example code illustrates creating and populating a memory provider:

# create layer
vl = QgsVectorLayer("Point", "temporary_points", "memory")
pr = vl.dataProvider()

# add fields
pr.addAttributes( [ QgsField("name", QVariant.String),
                    QgsField("age",  QVariant.Int),
                    QgsField("size", QVariant.Double) ] )

# add a feature
fet = QgsFeature()
fet.setGeometry( QgsGeometry.fromPoint(QgsPoint(10,10)) )
fet.setAttributes(["Johny", 2, 0.3])
pr.addFeatures([fet])

# update layer's extent when new features have been added
# because change of extent in provider is not propagated to the layer
vl.updateExtents()

Finally, let’s check whether everything went well:

# show some stats
print "fields:", len(pr.fields())
print "features:", pr.featureCount()
e = layer.extent()
print "extent:", e.xMin(),e.yMin(),e.xMax(),e.yMax()

# iterate over features
f = QgsFeature()
features = vl.getFeatures()
for f in features:
  print "F:",f.id(), f.attributes(), f.geometry().asPoint()

Внешний вид (символика) векторных слоёв

При отрисовке векторного слоя, внешний вид данных определяется рендером и символами, ассоциированными со слоем. Символы это классы, занимающиеся отрисовкой визуального представления объектов, а рендер опеределяет какой символ будет использован для отдельного объекта.

The renderer for a given layer can obtained as shown below:

renderer = layer.rendererV2()

And with that reference, let us explore it a bit:

print "Type:", rendererV2.type()

В библиотеке ядра QGIS реализовано несколько рендеров:

Тип

Класс

Описание

singleSymbol QgsSingleSymbolRendererV2

Отрисовывает все объекты одним и тем же символом

categorizedSymbol QgsCategorizedSymbolRendererV2

Отрисовывает объекты, используя разные символы для каждой категории

graduatedSymbol QgsGraduatedSymbolRendererV2

Отрисовывает объекты, используя разные символы для каждого диапазона значений

Кроме того, могут быть доступны пользовательские рендеры, поэтому не стоит предполагать, что присутствуют только вышеназванные типы. Узнать список доступных рендеров можно обратившись к синглтону QgsRendererV2Registry.

It is possible to obtain a dump of a renderer contents in text form — can be useful for debugging:

print rendererV2.dump()

Получить символ, используемый для отрисовки, можно вызвав метод symbol(), а для его изменения служит метод setSymbol() (примечание для пишущих на C++: рендер становится владельцем символа).

Узнать и задать поле атрибутивной таблицы, используемое для классификации можно при помощи методов classAttribute() и setClassAttribute() соответственно.

To get a list of categories:

for cat in rendererV2.categories():
  print "%s: %s :: %s" % (cat.value().toString(), cat.label(), str(cat.symbol()))

Здесь value() — величина, используемая для разделения категорий, label() — описание категории, а метод symbol() возвращает назначеный символ.

Также рендер обычно сохраняет оригинальный символ и цветовую шкалу, которые использовались для классификации, получить их можно вызвав методы sourceColorRamp() и sourceSymbol() соответственно.

Этот рендер очень похож на рендер уникальными значениями, описанный выше, но вместо одного значения атрибута для класса он оперирует диапазоном значений и следовательно, может использоваться только с числовыми атрибутами.

To find out more about ranges used in the renderer:

for ran in rendererV2.ranges():
  print "%f - %f: %s %s" % (
      ran.lowerValue(),
      ran.upperValue(),
      ran.label(),
      str(ran.symbol())
      )

Как и в предыдущем случае, доступны методы classAttribute() для получения имени атрибута классификации, sourceSymbol() и sourceColorRamp() чтобы узнать оригинальный символ и цветовую шкалу. Кроме того, дополнительный метод mode() позволяет узнать какой алгоритм использовался для создания диапазонов: равные интервалы, квантили или что-то другое.

If you wish to create your own graduated symbol renderer you can do so as illustrated in the example snippet below (which creates a simple two class arrangement):

from qgis.core import  (QgsVectorLayer,
                        QgsMapLayerRegistry,
                        QgsGraduatedSymbolRendererV2,
                        QgsSymbolV2,
                        QgsRendererRangeV2)

myVectorLayer = QgsVectorLayer(myVectorPath, myName, 'ogr')
myTargetField = 'target_field'
myRangeList = []
myOpacity = 1
# Make our first symbol and range...
myMin = 0.0
myMax = 50.0
myLabel = 'Group 1'
myColour = QtGui.QColor('#ffee00')
mySymbol1 = QgsSymbolV2.defaultSymbol(
           myVectorLayer.geometryType())
mySymbol1.setColor(myColour)
mySymbol1.setAlpha(myOpacity)
myRange1 = QgsRendererRangeV2(
                myMin,
                myMax,
                mySymbol1,
                myLabel)
myRangeList.append(myRange1)
#now make another symbol and range...
myMin = 50.1
myMax = 100
myLabel = 'Group 2'
myColour = QtGui.QColor('#00eeff')
mySymbol2 = QgsSymbolV2.defaultSymbol(
           myVectorLayer.geometryType())
mySymbol2.setColor(myColour)
mySymbol2.setAlpha(myOpacity)
myRange2 = QgsRendererRangeV2(
                myMin,
                myMax,
                mySymbol2
                myLabel)
myRangeList.append(myRange2)
myRenderer = QgsGraduatedSymbolRendererV2(
                '', myRangeList)
myRenderer.setMode(
        QgsGraduatedSymbolRendererV2.EqualInterval)
myRenderer.setClassAttribute(myTargetField)

myVectorLayer.setRendererV2(myRenderer)
QgsMapLayerRegistry.instance().addMapLayer(myVectorLayer)

Символы представлены базовым классом QgsSymbolV2 и тремя классами наследниками:

  • QgsMarkerSymbolV2 - for point features
  • QgsLineSymbolV2 - for line features
  • QgsFillSymbolV2 - for polygon features

Каждый символ состоит из одного и более символьных слоёв (классы, унаследованные от QgsSymbolLayerV2). Всю работу по отрисовке выполняют слои символа, а символ служит только контейнером для них.

Having an instance of a symbol (e.g. from a renderer), it is possible to explore it: type() method says whether it is a marker, line or fill symbol. There is a dump() method which returns a brief description of the symbol. To get a list of symbol layers:

for i in xrange(symbol.symbolLayerCount()):
  lyr = symbol.symbolLayer(i)
  print "%d: %s" % (i, lyr.layerType())

Узнать цвет символа можно вызвав метод color(), а чтобы изменить его — setColor(). У символов типа маркер присутствуют дополнительные методы size() и angle(), позволяющие узнать размер символа и угол поворота, а у линейных символов есть метод width(), возвращающий толщину линии.

Размер и толщина по умолчанию задаются в миллиметрах, а углы — в градусах.

Как уже было сказано, слои символа (наследники QgsSymbolLayerV2) определяют внешний вид объектов. Существует несколько базовых классов символьных слоёв. Кроме того, можно создавать новые символьные слои и таким образом влиять на отрисовку объектов в достаточно широких пределах. Метод layerType() однозначно идентифицирует класс символьного слоя — основными и доступными по умолчанию являются символьные слои SimpleMarker, SimpleLine и SimpleFill.

You can get a complete list of the types of symbol layers you can create for a given symbol layer class like this:

from qgis.core import QgsSymbolLayerV2Registry
myRegistry = QgsSymbolLayerV2Registry.instance()
myMetadata = myRegistry.symbolLayerMetadata("SimpleFill")
for item in myRegistry.symbolLayersForType(QgsSymbolV2.Marker):
  print item

Output:

EllipseMarker
FontMarker
SimpleMarker
SvgMarker
VectorField

Класс QgsSymbolLayerV2Registry управляет базой всех доступных символьных слоёв.

Получить доступ к данным символьного слоя можно при помощи метода properties(), который возвращает словарь (пары ключ-значение) свойств, влияющих на внешний вид. Символьные слои каждого типа имеют свой набор свойств. Кроме того, существуют общие для всех типов методы color(), size(), angle(), width() и соответсвующие им сеттеры. Следует помнить, что размер и угол поворота доступны только для символьных слоёв типа маркер, а толщина — только для слоёв типа линия.

Imagine you would like to customize the way how the data gets rendered. You can create your own symbol layer class that will draw the features exactly as you wish. Here is an example of a marker that draws red circles with specified radius:

class FooSymbolLayer(QgsMarkerSymbolLayerV2):

  def __init__(self, radius=4.0):
    QgsMarkerSymbolLayerV2.__init__(self)
    self.radius = radius
    self.color = QColor(255,0,0)

  def layerType(self):
    return "FooMarker"

  def properties(self):
    return { "radius" : str(self.radius) }

  def startRender(self, context):
    pass

  def stopRender(self, context):
    pass

  def renderPoint(self, point, context):
    # Rendering depends on whether the symbol is selected (Qgis >= 1.5)
    color = context.selectionColor() if context.selected() else self.color
    p = context.renderContext().painter()
    p.setPen(color)
    p.drawEllipse(point, self.radius, self.radius)

  def clone(self):
    return FooSymbolLayer(self.radius)

Метод layerType() определяет имя символьного слоя, которое должно быть уникальным. Чтобы все атрибуты были неизменными, используются свойства. Метод clone() должен возвращать копию символьного слоя с точно такими же атрибутами. И наконец, методы отрисовки: startRender() вызывается перед отрисовкой первого объекта, а stopRender() — после окончания отрисовки. За собственно отрисовку отвечает метод renderPoint(). Координаты точки (точек) должны быть трансформирваны в выходные координаты.

Для полининий и полигонов единственное отличие будет в методе отрисовки: необходимо использовать renderPolyline(), принимающий список линий, или renderPolygon() в качестве первого аргумента принимающий список точек, образующих внешнее кольцо, и список внутренних колец (или None) вторым аргументом.

Usually it is convenient to add a GUI for setting attributes of the symbol layer type to allow users to customize the appearance: in case of our example above we can let user set circle radius. The following code implements such widget:

class FooSymbolLayerWidget(QgsSymbolLayerV2Widget):
  def __init__(self, parent=None):
    QgsSymbolLayerV2Widget.__init__(self, parent)

    self.layer = None

    # setup a simple UI
    self.label = QLabel("Radius:")
    self.spinRadius = QDoubleSpinBox()
    self.hbox = QHBoxLayout()
    self.hbox.addWidget(self.label)
    self.hbox.addWidget(self.spinRadius)
    self.setLayout(self.hbox)
    self.connect( self.spinRadius, SIGNAL("valueChanged(double)"), \
      self.radiusChanged)

  def setSymbolLayer(self, layer):
    if layer.layerType() != "FooMarker":
      return
    self.layer = layer
    self.spinRadius.setValue(layer.radius)

  def symbolLayer(self):
    return self.layer

  def radiusChanged(self, value):
    self.layer.radius = value
    self.emit(SIGNAL("changed()"))

Этот виджет можно встроить в диалог свойств символа. Когда символьный слой выделяется в диалоге свойств символа, создается экземпляр символьного слоя и экземпляр виджета символьного слоя. Затем вызывается метод setSymbolLayer() чтобы привязать символьный слой к виджету. В этом методе виджет должен обновить интерфейс, чтобы отразить атрибуты символьного слоя. Функция symbolLayer() используется диалогом свойств для получения измененного символьного слоя для дальнейшего использования.

При каждом изменении атрибутов виджет должен посылать сигнал changed(), чтобы диалог свойств мог обновить предпросмотр символа.

Остался последний штрих: рассказать QGIS о существовании этих новых классов. Для этого достаточно добавить символьный слой в реестр. Конечно, можно использовать символьный слой и не добавляя его в реестр, но тогда некоторые возможности будут недоступны: например, загрузка проекта с пользовательскими символьными слоями или невозможность редактировать свойства слоя.

We will have to create metadata for the symbol layer:

class FooSymbolLayerMetadata(QgsSymbolLayerV2AbstractMetadata):

  def __init__(self):
    QgsSymbolLayerV2AbstractMetadata.__init__(self, "FooMarker", QgsSymbolV2.Marker)

  def createSymbolLayer(self, props):
    radius = float(props[QString("radius")]) if QString("radius") in props else 4.0
    return FooSymbolLayer(radius)

  def createSymbolLayerWidget(self):
    return FooSymbolLayerWidget()

QgsSymbolLayerV2Registry.instance().addSymbolLayerType( FooSymbolLayerMetadata() )

В конструктор родительского класса необходимо передать тип слоя (тот же, что сообщает слой) и тип символа (маркер/линия/заливка). createSymbolLayer() создаёт экземпляр символьного слоя с атрибутами, указаными в словаре props. (Будьте внимательны, ключи являются экземплярами QString, а не объектами “str”). Метод createSymbolLayerWidget() должен возвращать виджет настроек этого символьного слоя.

Последней конструкцией мы добавляем символьный слой в реестр — на этом все.

Возможность создать свой рендер может быть полезной, если требуется изменить правила выбора символов для отрисовки объектов. Примерами таких ситуаций могут быть: символ должен определяться на основании значений нескольких полей, размер символа должен зависеть от текущего масштаба и т.д.

The following code shows a simple custom renderer that creates two marker symbols and chooses randomly one of them for every feature:

import random

class RandomRenderer(QgsFeatureRendererV2):
  def __init__(self, syms=None):
    QgsFeatureRendererV2.__init__(self, "RandomRenderer")
    self.syms = syms if syms else [ QgsSymbolV2.defaultSymbol(QGis.Point), \
      QgsSymbolV2.defaultSymbol(QGis.Point) ]

  def symbolForFeature(self, feature):
    return random.choice(self.syms)

  def startRender(self, context, vlayer):
    for s in self.syms:
      s.startRender(context)

  def stopRender(self, context):
    for s in self.syms:
      s.stopRender(context)

  def usedAttributes(self):
    return []

  def clone(self):
    return RandomRenderer(self.syms)

В конструктор родительского класса QgsFeatureRendererV2 необходимо передать имя ренедера (должно быть уникальным). Метод symbolForFeature() определяет какой символ будет использоваться для конкретного объекта. startRender() и stopRender() выполняют инициализацию/финализацию отрисовки символа. Метод usedAttributes() может возвращать список имен полей, которые необходимы рендеру. И, наконец, функция clone() должна возвращать копию рендера.

Like with symbol layers, it is possible to attach a GUI for configuration of the renderer. It has to be derived from QgsRendererV2Widget. The following sample code creates a button that allows user to set symbol of the first symbol:

class RandomRendererWidget(QgsRendererV2Widget):
  def __init__(self, layer, style, renderer):
    QgsRendererV2Widget.__init__(self, layer, style)
    if renderer is None or renderer.type() != "RandomRenderer":
      self.r = RandomRenderer()
    else:
      self.r = renderer
    # setup UI
    self.btn1 = QgsColorButtonV2("Color 1")
    self.btn1.setColor(self.r.syms[0].color())
    self.vbox = QVBoxLayout()
    self.vbox.addWidget(self.btn1)
    self.setLayout(self.vbox)
    self.connect(self.btn1, SIGNAL("clicked()"), self.setColor1)

  def setColor1(self):
    color = QColorDialog.getColor( self.r.syms[0].color(), self)
    if not color.isValid(): return
    self.r.syms[0].setColor( color );
    self.btn1.setColor(self.r.syms[0].color())

  def renderer(self):
    return self.r

В конструктор передается экземпляры активного слоя (QgsVectorLayer), глобальный стиль (QgsStyleV2) и текущий рендер. Если рендер не задан или имеет другой тип, он будет заменен нашим рендером, в противном случае мы будем использовать текущий рендер (который нам и нужен). Необходимо обновить содержимое виджета, чтобы отразить текущее состояние рендера. При закрытии диалога ренедера, вызывается метод renderer() виджета чтобы получить текущий рендер — он будет назначен слою.

The last missing bit is the renderer metadata and registration in registry, otherwise loading of layers with the renderer will not work and user will not be able to select it from the list of renderers. Let us finish our RandomRenderer example:

class RandomRendererMetadata(QgsRendererV2AbstractMetadata):
  def __init__(self):
    QgsRendererV2AbstractMetadata.__init__(self, "RandomRenderer", "Random renderer")

  def createRenderer(self, element):
    return RandomRenderer()
  def createRendererWidget(self, layer, style, renderer):
    return RandomRendererWidget(layer, style, renderer)

QgsRendererV2Registry.instance().addRenderer(RandomRendererMetadata())

Так же, как и в случае символьных слоёв, абстрактный конструктор метаданных должен получить имя рендера, отображаемое имя и, по желанию, название иконки рендера. Метод createRenderer() получает экземпляр QDomElement, который может использоваться для восстановления состояния рендера из дерева DOM. Метод createRendererWidget() отвечает за создание виджета настройки. Он может отсутствовать или возвращать None, если рендер не имеет интрерфейса.

To associate an icon with the renderer you can assign it in QgsRendererV2AbstractMetadata constructor as a third (optional) argument — the base class constructor in the RandomRendererMetadata __init__() function becomes:

QgsRendererV2AbstractMetadata.__init__(self,
    "RandomRenderer",
    "Random renderer",
    QIcon(QPixmap("RandomRendererIcon.png", "png")) )

Иконку можно назначить и позже, воспользовавшись методом setIcon() класса метаданных. Иконка может загружаться из файла (как показано выше) или из ресурсов Qt (в составе PyQt4 присутствует компилятор .qrc для Python).

re TODO: