Робота з векторними шарами

Цей розділ описує операції які можна виконувати з векторними шарами.

Retrieving informations about attributes

You can retrieve informations about the fields associated with a vector layer by calling pendingFields() on a QgsVectorLayer instance:

# "layer" is a QgsVectorLayer instance
for field in layer.pendingFields():
    print field.name(), field.typeName()

Selecting features

In QGIS desktop, features can be selected in different ways, the user can click on a feature, draw a rectangle on the map canvas or use an expression filter. Selected fatures are normally higlighted in a different color (default is yellow) to draw user’s attention on the selection. Sometimes can be useful to programmatically select features or to change the default color.

To change the selection color you can use setSelectionColor() method of QgsMapCanvas as shown in the following example:

iface.mapCanvas().setSelectionColor( QColor("red") )

To add add features to the selected features list for a given layer, you can call setSelectedFeatures() passing to it the list of features IDs:

# Get the active layer (must be a vector layer)
layer = iface.activeLayer()
# Get the first feature from the layer
feature = layer.getFeatures().next()
# Add this features to the selected list
layer.setSelectedFeatures([feature.id()])

To clear the selection, just pass an empty list:

layer.setSelectedFeatures([])

Перегляд об’єктів векторного шару

Перегляд об’єктів векторного шару — одна з найчастіших операцій. Нижче показано простий код для цієї вирішення задачі, а також для відображення деякої інформації про кожний об’єкт. Вважається, що змінна layer містить об’єкт QgsVectorLayer.

iter = layer.getFeatures()
for feature in iter:
    # retrieve 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

Accessing attributes

Attributes can be referred to by their name.

print feature['name']

Alternatively, attributes can be referred to by index. This is will be a bit faster than using the name. For example, to get the first attribute:

print feature[0]

Перегляд вибраних об’єктів

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

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

Another option is the Processing features() method:

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

By default, this will iterate over all the features in the layer, in case there is no selection, or over the selected features otherwise. Note that this behavior can be changed in the Processing options to ignore selections.

Перегляд певної множини об’єктів

Якщо необхідно переглянути лише певну множину об’єктів шару, наприклад, об’єкти, що попадають у задану область, слід додати параметр QgsFeatureRequest у виклик методу getFeatures(). Приклад

request = QgsFeatureRequest()
request.setFilterRect(areaOfInterest)
for feature in layer.getFeatures(request):
    # do whatever you need with the feature

If you need an attribute-based filter instead (or in addition) of a spatial one like shown in the example above, you can build an QgsExpression object and pass it to the QgsFeatureRequest constructor. Here’s an example

# The expression will filter the features where the field "location_name" contains
# the word "Lake" (case insensitive)
exp = QgsExpression('location_name ILIKE \'%Lake%\'')
request = QgsFeatureRequest(exp)

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

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

Порада

If you only need a subset of the attributes or you don’t need the geometry informations, you can significantly increase the speed of the features request by using QgsFeatureRequest.NoGeometry flag or specifying a subset of attributes (possibly empty) like shown in the example above.

Редагування векторних шарів

Більшість провайдерів векторних даних підтримує редагування. Іноді вони дозволяють виконували лише деякі операції редагування. Отримати список доступних операцій можна за допомогою метода capabilities()

caps = layer.dataProvider().capabilities()

By using any of the following methods for vector layer editing, the changes are directly committed to the underlying data store (a file, database etc). In case you would like to do only temporary changes, skip to the next section that explains how to do modifications with editing buffer.

Примітка

If you are working inside QGIS (either from the console or from a plugin), it might be necessary to force a redraw of the map canvas in order to see the changes you’ve done to the geometry, to the style or to the attributes:

# If caching is enabled, a simple canvas refresh might not be sufficient
# to trigger a redraw and you must clear the cached image for the layer
if iface.mapCanvas().isCachingEnabled():
    layer.setCacheImage(None)
else:
    iface.mapCanvas().refresh()

Створення об’єктів

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

Видалення об’єктів

Для видалення об’єктів достатньо передати список їх ідентифікаторів

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

Редагування об’єктів

Можна редагувати як геометрію об’єкта, так і його атрибути. Наступний приклад спочатку модифікує значення атрибутів з індексами 0 та 1, а потім модифікує геометрію

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

Порада

If you only need to change geometries, you might consider using the QgsVectorLayerEditUtils which provides some of useful methods to edit geometries (translate, insert or move vertex etc.)

Створення та видалення полів

Щоб створити поля (атрибути), необхідно створити список з описом полів. Для видалення полів достатньо надати список з їх індексами

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)

Для того, щоб операції повтору/скасування працювали правильно, описані вище методи повинні бути розміщені всередині пакета змін. (Якщо вам не потрібен функціонал повтору/скасування змін і треба зберігати зміни негайно, все зводиться до редагування через провайдер). Ось приклад використання можливості скасування змін

layer.beginEditCommand("Feature triangulation")

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

if problem_occurred:
    layer.destroyEditCommand()
   return

# ... more editing ...

layer.endEditCommand()

Метод beginEndCommand() створює внутрішню «активну» команду та записує всі зміни у векторному шарі. Після виклику endEditCommand() ця команда буде розміщена у стеку скасування і користувач зможе скасувати або повторити її через GUI. Якщо в процесі редагування трапилась помилка, метод destroyEditCommand() видалить команду та скасує всі зроблені зміни, що сталися з моменту активації цієї команди.

To start editing mode, there is startEditing() method, to stop editing there are commitChanges() and rollback() — however normally you should not need these methods and leave this functionality to be triggered by the user.

Використання просторового індексу

Просторовий індекс може значно збільшити продуктивність вашого коду, якщо він часто виконує операції читання векторного шару. Уявіть наприклад, що ви реалізуєте алгоритм інтерполяції і для заданої точки необхідно знайти 10 найближчих об’єктів точкового шару аби використати їх для обчислення інтерпольованого значення. Без просторового індексу єдиний спосіб зробити це в QGIS — обчислити відстані від всіх точок до заданої а потім порівняти їх між собою. Це може бути досить тривалою операцією, особливо якщо її необхідно повторити для декількох точок. Набагато ефективніше цю задачу можна вирішити за допомогою просторового індексу.

Шар без просторового індексу можна порівняти з телефонним довідником, у якому телефонні номери не відсортовані або не впорядковані якимось чином. Єдиний спосіб знайти потрібний номер в такому випадку — переглядати довідник сторінка за сторінкою, поки потрібна інформація не буде знайдена.

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. створення просторового індексу — наступний код створить пустий індекс

    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. після заповнення індексу можна переходити до виконання запитів

    # 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 та інші).

Існує два способи збереження векторних даних:

  • з екземпляра 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.

  • з окремих об’єктів

    # 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 провадера достатньо вказати "memory" в якості ідентифікатора провайдера у конструкторі QgsVectorLayer.

У конструктор також передається URI, що задає тип геометрії шару. Це може бути: "Point", "LineString", "Polygon", "MultiPoint", "MultiLineString" або "MultiPolygon".

URI також може містити інформацію про систему координат, поля, та настройки просторового індексування. Використовується наступний синтаксис:

crs=definition

Задає систему координат шару, в якості definition допускається використання будь-якого формату, що приймається QgsCoordinateReferenceSystem.createFromString()

index=yes

Вказує чи буде провайдер використовувати просторовий індекс

field=name:type(length,precision)

Описує атрибути шару. Кожний атрибут має ім’я та, небов’язково, тип (цілий, з плаваючою комою або текст), довжину так точність. Може бути декілька таких описів.

Нижче показано URI, який містить всі ці параметри

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

Наступний фрагмент коду демонструє створення та заповнення даними memory провайдера

# 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)])
vl.updateFields() # tell the vector layer to fetch changes from the provider

# 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()

Нарешті, перевіримо чи все пройшло успішно

# show some stats
print "fields:", len(pr.fields())
print "features:", pr.featureCount()
e = layer.extent()
print "extent:", e.xMiniminum(), e.yMinimum(), e.xMaximum(), e.yMaximum()

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

Зовнішній вигляд (стиль) векторних шарів

Під час візуалізації векторного шару, зовнішній вигляд даних визначається рендерером та символами, які асоційовані з шаром. Символи це класи, які займаються візуалізацією об’єктів, а рендерер визначає який саме символ буде використовуватися для певного об’єкта.

Отримати ренедерер шару можна так:

renderer = layer.rendererV2()

Тепер можна переглянути інформацію про рендерер

print "Type:", rendererV2.type()

У бібліотеці ядра QGIS реалізовано декілька рендерерів:

Тип

Клас

Коментар

singleSymbol QgsSingleSymbolRendererV2

Візуалізує всі об’єкти одним і тим же символом

categorizedSymbol QgsCategorizedSymbolRendererV2

Візуалізує об’єкти з використанням різних символів для кожної категорії

graduatedSymbol QgsGraduatedSymbolRendererV2

Візуалізує об’єкти з використанням різних символів для кожного діапазону значень

There might be also some custom renderer types, so never make an assumption there are just these types. You can query QgsRendererV2Registry singleton to find out currently available renderers:

QgsRendererV2Registry.instance().renderersList()
# Prints:
[u'singleSymbol',
u'categorizedSymbol',
u'graduatedSymbol',
u'RuleRenderer',
u'pointDisplacement',
u'invertedPolygonRenderer',
u'heatmapRenderer']

Існує можливість отримати дамп вмісту рендерера у текстовому вигляді — це може бути корисним під час зневадження

print rendererV2.dump()

Простий знак

Отримати символ, що використовується для візуалізації можна за допомогою метода symbol(), а для його модифікації служить метод setSymbol() (примітка для розробників на C++: рендерер стає власником символа).

You can change the symbol used by a particular vector layer by calling setSymbol() passing an instance of the appropriate symbol instance. Symbols for point, line and polygon layers can be created by calling the createSimple() function of the corresponding classes QgsMarkerSymbolV2, QgsLineSymbolV2 and QgsFillSymbolV2.

The dictionary passed to createSimple() sets the style properties of the symbol.

For example you can change the symbol used by a particular point layer by calling setSymbol() passing an instance of a QgsMarkerSymbolV2 as in the following code example:

symbol = QgsMarkerSymbolV2.createSimple({'name': 'square', 'color': 'red'})
layer.rendererV2().setSymbol(symbol)

name indicates the shape of the marker, and can be any of the following:

  • circle
  • square
  • rectangle
  • diamond
  • pentagon
  • triangle
  • equilateral_triangle
  • star
  • regular_star
  • arrow
  • filled_arrowhead

Рендерер категоріями

Дізнатися та встановити ім’я атрибута, який буде використовуватися для класифікації можна за допомогою методів classAttribute() та setClassAttribute().

А так отримують список категорій

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

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

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

Градуйований знак

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

Отримати інформацію про діапазони, що використовуються, можна так

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

Як і у попередньому випадку доступні методи classAttribute() для отримання імені атрибута класифікації, sourceSymbol() та sourceColorRamp() для отримання вихідного символа та кольорової шкали. Крім того, додатковий метод mode() дозволяє дізнатися який алгоритм використовувася для створення діапазонів: рівні інтервали, квантилі або щось інше.

Якщо ви хочете створити свій рендерер категоріями, можете взяти за основу наступний код. Тут показано простий поділ об’єктів на два класи

from qgis.core import *

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 — для точок

  • QgsLineSymbolV2 — для ліній

  • QgsFillSymbolV2 — для полігонів

Кожний символ складається з одного чи декількох символьних шарів (похідні класи від QgsSymbolLayerV2). Всю роботу з візуалізації виконують саме символьні шари, символ лише контейнер для них.

Отримавши екземпляр символа (наприклад, від рендерера), можна вивчити його. Метод type() розкаже маркер це, чи лінія або полігон. Метод dump() поверне короткий опис символа. Отримати список шарів символа можна так

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 and SimpleFill.

Отримати повний список символьних шарів, які можна використовувати у заданому символьному шарі можна так

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

Результат

EllipseMarker
FontMarker
SimpleMarker
SvgMarker
VectorField

Клас QgsSymbolLayerV2Registry управляє базою даних всіх доступних символьних шарів.

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

Власні символьні шари

Уявіть, що вам необхідно налаштувати процес візуалізації своїх даних. Ви можете створити власний клас символьного шару, який буде відображати об’єкти саме так, як вам потрібно. Ось приклад маркера, який малює червоні кола заданого радіуса

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) другим параметром.

Гарною практикою є реалізація інтерфейсу для настройки атрибутів символьного шару, що дозволяє користувачам налаштовувати зовнішній вигляд. Так, у нашому прикладі можна надати користувачам можливість змінювати радіус кола. Реалізувати це можна так

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 про існування цих класів. Для цього достатньо додати символьний шар до відповідного реєстру. Звичайно, можно використовувати символьний шар і без внесення у реєстр, але тоді деякий функціонал буде недоступний. Наприклад: завантаження проектів з додатковими символьними шарами або неможливість редагування атрибутів символьного шару.

Спочатку необхідно створити метадані символьного шару

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() повинен повертати віджет настройок цього символьного шару.

Останнім рядком ми включаємо символьний шар у реєстр. На цьому все.

Власні рендерери

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

Наступний код демонструє простий рендерер, який створює два маркери та випадковим чином вибирає одни з них для візуалізації кожного об’єкта

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() повинен повертати копію рендерера.

Як і у випадку символьних шарів, рендерер може мати графічний інтерфейс для настройки параметрів. Він успадковується від класу QgsRendererV2Widget. Наступний код створює кнопку, яка дозволяє змінювати один з символів

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() віджета щоб отримати поточний рендерер — він буде підключений до шару.

Залишилось підготувати метадані рендерера та внести його у реєстр інакше завантажити шари з цим рендерером не вдасться, а користувач не побачить його у списку доступних рендерерів. Завершуємо наш приклад з RandomRenderer

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

Як і у випадку з символьними шарами, абстрактний конструктор метаданих повинен отримати ім’я рендерера, видиме ім’я рендерера (використовується в GUI) та, за бажанням, шлях до іконки рендерера. В метод createRenderer() передається екземпляр QDomElement, який може використовуватися для відновлення стану рендерера з дерева DOM. Метод createRendererWidget() відповідає за створенняє віджета настройки рендерера. Якщо рендерер не має віджета настройки, цей метод може бути відсутнім або просто повертати None.

Встановити іконку рендерера можна, передавши її у конструктор QgsRendererV2AbstractMetadata третім (необов’язковим) параметром — конструтор базового класу в функціх func:__init__ класу RandomRendererMetadata буде мати вигляд

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

Іконку можна призначити і пізніше за допомогою метода setIcon() класа метаданих. Іконка завантажується або з файлу (як показано вище) або з ресурсів Qt (у складі PyQt4 є компілятор ресурсів .qrc для Python).

Інші теми

TODO:
creating/modifying symbols working with style (QgsStyleV2) working with color ramps (QgsVectorColorRampV2) rule-based renderer (see this blogpost) exploring symbol layer and renderer registries