Utilisation de couches vectorielles

Cette section résume les diverses actions possibles sur les couches vectorielles.

Itérer sur une couche vecteur

Itérer sur les entités d’une couche vecteur est l’une des tâches les plus courantes. L’exemple ci-dessous est un code basique pour accomplir cette tâche et qui affiche des informations sur chaque entité. La variable layer est présumée être un objet 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

Les attributs peuvent être retrouvés selon leur index:

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

Itérer sur une sélection d’entités

Méthodes pratiques.

Pour les cas présentés ci-dessus, au cas où vous présumez qu’il y a une sélection en cours dans la couche vecteur, vous pouvez utiliser la méthode features() de l’extension interne Traitements de la manière suivant:

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

Ce code va itérer sur toutes les entités d’une couche, dans le cas où il n’y a pas de sélection, ou sur les entités sélectionnés dans le cas contraire.

Si vous avez uniquement besoin des entités sélectionées, vous pouvez utiliser la méthode :func: selectedFeatures de la couche vecteur:

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

Itérer sur un sous-ensemble d’entités

Si vous désirez itérer sur un sous-ensemble donné d’entités dans une couche, tel que celles situées dans une zone donnée, vous devez ajouter un objet QgsFeatureRequest à la fonction d’appel getFeatures(). Voici un exemple:

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

La requête peut être utilisée pour définir les données rappatriées pour chaque entité de manière à ce que l’itérateur retourne toutes les entités mais uniquement des données partielles pour chacune d’entre elles.

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

Modifier des couches vecteur

La majorité des fournisseurs de données vecteurs gère l’édition des données. Parfois, il gèrent uniquement certaines action d’édition. Utilisez la fonction capabilities() pour trouver quelles sont les fonctionnalités qui sont gérées:

caps = layer.dataProvider().capabilities()

En utilisant n’importe laquelle des méthodes qui suivent pour l’édition de couches vecteur, les changement sont directement validés dans le dispositif de stockage d’information sous-jacent (base de données, fichier,etc.). Si vous désirez uniquement faire des changements temporaires, passez à la section suivante qui explique comment réaliser des modifications à l’aide d’un tampon d”édition.

Ajout d’Entités

Créez quelques instance de QgsFeature et passez les sous forme de liste à la méthode addFeatures() du fournisseur. Elle vous renverra deux valeurs: le résultat (vrai/faux) et la liste des entités ajoutées (leur identifiant est paramétré pas le stockage de données).

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

Suppression d’Entités

Pour supprimer des entités, il suffit d’indiquer une liste de leur identifiant

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

Modifier des Entités

Il est possible de réaliser des changements soit sur la géométrie de l’entité, soit sur ses attributs. L’exemple qui suit modifie d’abord des valeurs d’attributs situés à l’index 0 et 1 puis modifie la géométrie de l’entité:

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

Ajout et Suppression de Champs

Pour ajouter des champs (attributs) vous devez indiquer une liste de définitions de champs. Pour la suppression de champs, fournissez juste une liste des index des champs.

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

Après l’ajout ou la suppression de champs, les champs de la couche doivent être rafraîchis dans le fournisseur de données car les changements ne sont pas automatiquement propagés.

layer.updateFields()

Modifier des couches vecteur à l’aide d’un tampon d’édition

Lorsque vous modifiez des vecteurs avec l’application QGIS, vous devez d’abord lancer le mode édition pour une couche donnée puis réaliser des modifications et enfin, sauvegarder (ou annuler) vos changements. Tous les changements que vous réalisez ne sont pas écrits tant que vous ne les avez pas validés, il reste alors dans le tampon d’édition en mémoire de la couche. Il est possible d’utiliser cette fonctionnalité en programmation, c’est juste une autre méthode pour éditer une couche vecteur qui complète l’utilisation directe des fournisseurs de données. Utilisez cette option lorsque vous fournissez des outils graphiques pour l’édition car cela permet à l’utilisateur de valider ou d’annuler ainsi que la possibilité de défaire/refaire. Lorsque les changements sont validés, toutes les modifications stockées dans le tampon d’édition sont sauvegardées dans le fournisseur de données.

Pour savoir si une couche est en mode édition, utilisez la fonction isEditing() — les fonctions d’éditions fonctionnent seulement lorsque le mode d’édition est activé. Utilisation des fonctions d’éditions:

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

Pour que les actions annuler/refaire fonctionnent correctement, les appels mentionnés plus haut doivent être encapsulés dans des commandes d’annulation. (si vous n’avez pas besoin d’annuler/refaire et que vous voulez envoyer les changements immédiatement, utilisez la méthode plus simple: editing with data provider.). Voici comment utiliser les fonctionnalités annuler:

layer.beginEditCommand("Feature triangulation")

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

if problem_occurred:
  layer.destroyEditCommand()
  return

# ... more editing ...

layer.endEditCommand()

La fonction beginEditCommand() créera une commande interne “activée” et enregistrera les changements à suivre de la couche vecteur. Lors de l’appel à la fonction endEditCommand(), la commande est poussée sur la pile d’annulation et l’utilisateur peut alors cliquer sur les boutons Annuler/Refaire. Au cas où quelquechose tournerait mal lors des changements, la méthode destroyEditCommand() method supprimera la commande de la pile et annulera tous les changements réalisés depuis que la commande est active.

Pour lancer le mode édition, il existe la méthode startEditing(). Pour arrêter l’édition, vous pouvez utiliser commitChanges() et rollback(). Néanmoins, vous n’avez pas besoin de les utiliser et vous pouvez laisser cette fonctionnalité activable par l’utilisateur.

Utilisation des index spatiaux

Les index spatiaux peuvent améliorer fortement les performances de votre code si vous réalisez de fréquentes requêtes sur une couche vecteur. Imaginez par exemple que vous écrivez un algorithme d’interpolation et que pour une position donnée, vous devez déterminer les 10 points les plus proches dans une couche de points, dans l’objectif d’utiliser ces points pour calculer une valeur interpolée. Sans index spatial, la seule méthode pour QGIS de trouver ces 10 points est de calculer la distance entre tous les points de la couche et l’endroit indiqué et de comparer ces distances entre-elles. Cela peut prendre beaucoup de temps spécialement si vous devez répeter l’opération sur plusieurs emplacements. Si index spatial existe pour la couche, l’opération est bien plus efficace.

Vous pouvez vous représenter une couche sans index spatial comme un annuaire dans lequel les numéros de téléphone ne sont pas ordonnés ou indexés. Le seul moyen de trouver le numéro de téléphone d’une personne est de lire l’annuaire en commençant du début jusqu’à ce que vous le trouviez.

Les index spatiaux ne sont pas créés par défaut pour une couche vecteur mais vous pouvez le faire facilement de cette manière:

  1. créez l’index spatial — le code qui suit créé un index vide

    index = QgsSpatialIndex()
    
  2. ajouter les entités à l’index – l’index utilise des objets QgsFeature et les ajoute dans sa structure de données interne. Vous pouvez créer les objets manuellement ou utiliser ceux qui sont issus de la méthode nextFeature() du fournisseur de données:

    index.insertFeature(feat)
    
  3. Une fois que l’index est rempli avec des valeurs, vous pouvez lancer vos requêtes:

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

Ecrire dans des couches vecteur

Vous pouvez générer des fichiers de couche vecteur en utilisant la classe QgsVectorFileWriter. Elle gère tous les formats vecteurs gérés par QGIS (fichier Shape, GeoJSON, KML, etc.).

Il y a deux façons d’exporter une couche vectorielle:

  • A partir d’une instance de la classe 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!"
    

    Le troisième paramètre indique l’encodage du texte en sortie. Seuls certains pilotes ont besoin de ce paramètre pour fonctionner correctement, les fichiers Shape sont dans ce cas. Néanmoins, vous ne devriez pas rencontrer de problèmes tant que vous n’utilisez pas un jeu de caractères international. Le quatrième paramètre que nous avons laissé à None peut indiquer un SCR de destination, si une instance valide de QgsCoordinateReferenceSystem est utilisée, la couche est transformée dans ce SCR.

    Consultez les formats gérés par OGR pour trouver les noms de pilote valides. Vous ne devez indiquer cette valeur dans la colonne “Code”. Vous pouvez indiquer optionnellement d’exporter uniquement les entités sélectionnées ou utiliser des options de création spécifiques à chaque pilote ou encore indiquer au pilote de ne pas créer d’attributs. Consultez la documentation pour la syntaxe complète.

  • Directement depuis les entités

    # 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
    

Fournisseur de données en mémoire

Le fournisseur de données en mémoire est utilisable principalement par des extensions ou des applications tierces. Il ne stocke pas de données sur disque ce qui permet aux développeurs de l’utiliser comme support rapide pour des couches temporaires.

Le fournisseur gère les champs en chaînes de caractères, en entiers et en réels.

Le fournisseur de données en mémoire gère également l’indexation spatiale qui est activée en appelant la fonction createSpatialIndex() du fournisseur. Une fois l’index spatial créé, vous pourrez itérer sur les entités d’emplacements donnés plus rapidement (car il n’est plus nécessaire de traverser toutes les entités mais uniquement celles qui se trouvent dans le rectangle).

Un fournisseur de données en mémoire est créé en indiquant 'memory" dans la chaîne de fournisseur du constructeur d’un objet QgsVectorLayer.

Le constructeur utilise également une URI qui définit le type de géométrie de la couche parmi: "Point", "LineString", "Polygon", "MultiPoint", "MultiLineString", ou "MultiPolygon".

L’URI peut également indiquer un système de coordonnée de référence, des champs et l’indexation. La syntaxe est la suivante:

crs=définition

Spécifie le système de coordonnée de référence, où définition peut être sous n’importe laquelle des formes acceptées par QgsCoordinateReferenceSystem.createFromString()

index=yes

Spécifie que le fournisseur utilisera un index spatial

field=nom:type(longueur,précision)

Spécifie un attribut de la couche. L’attribut dispose d’un nom et optionnellement d’un type (integer, double ou string), d’une longueur et d’une précision. Il peut y avoir plusieurs définitions de champs.

L’exemple suivant montre une URI intégrant toutes ces options

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

L’exemple suivant illustre la création et le remplissage d’un fournisseur de données en mémoire

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

Finalement, vérifions que tout s’est bien déroulé

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

Apparence (Symbologie) des couches vecteur

Lorsqu’une couche vecteur est en cours de rendu, l’apparence des données est donnée par un moteur de rendu et des symboles associés à la couche. Les symboles sont des classes qui gèrent le dessin de la représentation visuelle des entités alors que les moteurs de rendu déterminent quel symbole doit être utilisé pour une entité particulière.

Le moteur de rendu de chaque couche peut être obtenu comme présenté ci-dessous:

renderer = layer.rendererV2()

Munis de cette référence, faisons un peu d’exploration:

print "Type:", rendererV2.type()

Il existe plusieurs types de moteurs de rendu dans la bilbiothèque de base de QGIS:

Type

Classe

Description
singleSymbol QgsSingleSymbolRendererV2

Affiche toutes les entités avec le même symbole.

categorizedSymbol QgsCategorizedSymbolRendererV2

Affiche les entités en utilisant un symbole différent pour chaque catégorie.

graduatedSymbol QgsGraduatedSymbolRendererV2

Affiche les entités en utilisant un symbole différent pour chaque plage de valeurs.

Il peut également exister des moteurs de rendu personnalisés et vous ne pouvez donc pas considérer qu’il existe juste ces trois types. Vous pouvez interroger le singleton QgsRendererV2Registry pour savoir quels sont les moteurs de rendu disponibles.

Il est possible d’obtenir un extrait du contenu d’un moteur de rendu sous forme de texte, ce qui peut être utile lors du débogage:

print rendererV2.dump()

Moteur de rendu à symbole unique

Vous pouvez obtenir le symbole utilisé pour le rendu en appelant la méthode symbol() et le modifier avec la méthode setSymbol() (pour les développeurs C++, le moteur de rendu devient propriétaire du symbole).

Moteur de rendu à symboles catégorisés

Vous pouvez interroger et indiquer le nom de l’attribut qui sera utilisé pour la classification en utilisant les méthodes classAttribute() et setClassAttribute().

Pour obtenir la liste des catégories

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

value() est la valeur utilisée pour la discrimination ente les catégories, label() est un texte utilisé pour la description des catégories et la méthode symbol() renvoie le symbole associé.

Le moteur de rendu stocke généralement le symbole originel et la rampe de couleur qui ont été utilisés pour la classification. On peut les obtenir par les méthodes sourceColorRamp() and sourceSymbol().

Moteur de rendu à symboles gradués

Ce moteur de rendu est très similaire au moteur de rendu par symbole catégorisé ci-dessus mais au lieu d’utiliser une seule valeur d’attribut par classe, il utilise une classification par plages de valeurs et peut donc être employé uniquement sur des attributs numériques.

Pour avoir plus d’informations sur les plages utilisées par le moteur de rendu:

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

Vous pouvez à nouveau utiliser classAttribute() pour trouver le nom de l’attribut de classification ainsi que les méthodes sourceSymbol() et sourceColorRamp(). Il existe en plus une méthode mode() qui permet de déterminer comment les classes ont été créées: en utilisant des intervalles égaux, des quantiles ou tout autre méthode.

Si vous souhaitez créer votre propre moteur de rendu gradué, vous pouvez utiliser l’extrait de code qui est présenté dans l’exemple ci-dessous (qui créé simplement un arrangement en deux classes):

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)

Travailler avec les symboles

Pour la représentation des symboles, il existe la classe de base QgsSymbolV2 qui est dérivée en trois sous-classes:

  • QgsMarkerSymbolV2 — pour les entités ponctuelles.

  • QgsLineSymbolV2 — pour les entités linéaires.

  • QgsFillSymbolV2 — pour les entités polygonales.

Chaque symbole est constitué d’une ou plusieurs couche de symboles (classes dérivées de QgsSymbolLayerV2). Les couches de symboles font le rendu, la classe du symbole sert seulement de conteneur pour les couches de symbole.

Il est possible d’explorer une instance de symbole (récupérée depuis un moteur de rendu): la méthode type() indique s’il s’agit d’un symbole de marqueur, de ligne ou remplissage. Il existe une méthode dump() qui renvoie une brève description du symbole. Pour obtenir la liste des couches de symbole:

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

Pour trouver la couleur du symbole, utilisez la méthode color() et la méthode setColor() pour la changer. Avec les symboles de marqueurs vous pouvez également interroger la taille et la rotation du symbole à l’aide des méthodes size() et angle(). Pour les symboles de ligne, la méthode width() renvoie la largeur de la ligne.

La taille et la largeur sont exprimées en millimètres par défaut, les angles sont en degrés.

Travailler avec des couches de symboles

Comme évoqué auparavant, les couches de symboles (sous-classe de QgsSymbolLayerV2) déterminent l’apparence des entités. Il existe plusieurs couches de symboles de base pour l’utilisation courante. Il est possible d’implémenter de nouveaux types de symboles et de personnaliser l’affichage des entités. La méthode layerType() identifie uniquement la classe de la couche de symboles. Celles qui sont présentes par défaut sont les types SimpleMarker, SimpleLine et SimpleFill.

Vous pouvez obtenir une liste complète des types de couches de symbole pour une classe donnée de symbole de la manière suivante:

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

Sortie

EllipseMarker
FontMarker
SimpleMarker
SvgMarker
VectorField

la classe QgsSymbolLayerV2Registry gère une base de données de tous les types de symboles de couche disponibles.

Pour accéder à la donnée de la couche de symbole, utilisez la méthode properties() qui retourne un dictionnaire des propriétés définissant l’apparence du symbole. Chaque type de couche de symbole comporte un jeu de propriétés. Il existe également des méthode génériques color(), size(), angle(), width() accompagnées de leur équivalent d’attribution de valeur. La taille et l’angle sont disponibles uniquement pour les couches de symbole de marqueurs et la largeur, pour les couches de symbole de ligne.

Créer des types personnalisés de couches de symbole

Imaginons que vous souhaitez personnaliser la manière dont sont affichées les données. Vous pouvez créer votre propre classe de couche de symbole qui dessinera les entités de la manière voulue. Voici un exemple de marqueur qui dessine des cercles rouges avec un rayon spécifique.

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)

La méthode layerType() détermine le nom de la couche de symbole. Elle doit être unique parmi toutes les couches de symbole déjà existantes. Des propriétés sont utilisées pour la persistance des attributs. La méthode clone() renvoie une copie de la couche de symbole avec des attributs complètement identiques. Enfin, il reste les méthodes de rendu: startRender() est appelée avant le rendu de la première entité, stopRender() lorsque le rendu est terminé. La méthode renderPoint() s’occupe du rendu. Les coordonnées du ou des point(s) sont déjà transformées dans le SCR de sortie.

Pour les polylignes et les polygones, la seule différence est la méthode de rendu: vous utiliserez renderPolyline() qui reçoit une liste de lignes et resp. renderPolygon() qui reçoit une liste de points pour définir l’enveloppe extérieure en premier paramètre et une liste des trous (ou None) dans le deuxième paramètre.

En général, il est pratique d’ajouter une interface graphique pour paramétrer les attributs des couches de symbole pour permettre aux utilisateurs de personnaliser l’apparence. Dans le cadre de notre exemple ci-dessus, nous laissons l’utilisateur paramétrer le rayon du cercle. Le code qui suit implémente une telle interface:

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

Cette interface peut être incorporée dans la boîte de dialogue sur les propriétés de symbole. Lorsque le type couche de symbole est sélectionné dans la boîte de dialogue des propriétés de symbole, cela créé une instance de la couche de symbole et une instance de l’interface. Ensuite, la méthode setSymbolLayer() est appelée pour affecter la couche de symbole à l’interface. Dans cette méthode, l’interface doit rafraîchir l’environnement graphique pour afficher les attributs de la couche de symbole. La fonction symbolLayer() est utilisée pour retrouver la couche de symbole des propriétés de la boîte de dialogue afin de l’utiliser pour le symbole.

A chaque changement d’attributs, l’interface doit émettre le signal changed() pour laisser les propriétés de la boîte de dialogue mettre à jour l’aperçu de sumbole.

Maintenant, il nous manque un dernier détail: informer QGIS de ces nouvelles classes. On peut le faire en ajoutant la couche de symbole au registre. Il est possible d’utiliser la couche de symbole sans l’ajouter au registre mais certaines fonctionnalités ne fonctionneront pas comme le chargement de fichiers de projet avec une couche de symbole personnalisée ou l’impossibilité d’éditer les attributs de la couche dans l’interface graphique.

Nous devons ensuite créer les métadonnées de la couche de symbole.

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

Vous devez renseigner le type de couche (la même renvoyée par la couche) et le type de symbole (marker/line/fill) au constructeur de la classe parent. La méthode createSymbolLayer() s’occupe de créer l’instance d’une couche de symbole avec les attributs indiqués dans le dictionnaire props. (Attention, les clefs sont des instances QString et non des objets Python “str”). Et il existe également la méthode createSymbolLayerWidget() qui renvoie l’interface de paramétrage pour ce type de couche de symbole.

La dernière étape consiste à ajouter la couche de symbole au registre et c’est terminé !

Créer ses propres moteurs de rendu

Il est parfois intéressant de créer une nouvelle implémentation de moteur de rendu si vous désirez personnaliser les règles de sélection des symboles utilisés pour l’affichage des entités. Voici quelques exemples d’utilisation: le symbole est déterminé par une combinaison de champs, la taille des symboles change selon l’échelle courante, etc.

Le code qui suit montre un moteur de rendu personnalisé simple qui crée deux symboles de marqueur et choisit au hasard l’un d’entre eux pour chaque entité.

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)

Le constructeur de la classe parente QgsFeatureRendererV2 nécessite un nom de moteur de rendu (qui doit être unique parmi tous les moteurs de rendu). La méthode symbolForFeature() est celle qui décide du symbole qui sera utilisé pour une entité particulière. startRender() et stopRender() gèrent l’initialisation et la finalisation du rendu des symboles. La méthode usedAttributes() renvoie une liste des noms de champs dont a besoin le moteur de rendu. Enfin la fonction clone() renvoie une copie du moteur de rendu.

Comme avec les couches de symbole, il est possible d’attacher une interface graphique pour la configuration du moteur de rendu. Elle doit être dérivée de la classe QgsRendererV2Widget. L’exemple qui suit crée un bouton qui permet à l’utilisateur de paramétrer le symbole du premier symbole.

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

Le constructeur reçoit les instances de la couche active (QgsVectorLayer), le style global (QgsStyleV2) ainsi que le moteur de rendu courant. S’il n’y a pas de moteur de rendu ou si le moteur de rendu est d’un type différent, il sera remplacé par notre nouveau moteur de rendu, sinon, le moteur de rendu actuel (qui dispose déjà du bon type). Le contenu de l’interface doit être mis à jour pour refléter l’état du moteur de rendu. Lorsque la boîte de dialogue du moteur de rendu est acceptée, la méthode renderer() de l’interface est appelée pour récupérer le moteur de rendu actuel, qui sera affecté à la couche.

Le dernier élément qui manque concerne les métadonnées du moteur ainsi que son enregistrement dans le registre. Sans ces éléments, le chargement de couches avec le moteur de rendu ne sera pas possible et l’utilisateur ne pourra pas le sélectionner dans la liste des moteurs de rendus. Finissons notre exemple sur 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())

De la même manière que pour les couches de symbole, le constructeur des métadonnées attend le nom du moteur de rendu, le nom visible pour les utilisateurs et optionnellement le nom des icônes du moteur de rendu. La méthode createRenderer() fait passer une instance de QDomElement qui peut être utilisée pour restaurer l’état du moteur de rendu en utilisant un arbre DOM. La méthode createRendererWidget() créé l’interface graphique de configuration. Elle n’est pas obligatoire et peut renvoyer None si le moteur de rendu n’a pas d’interface graphique.

Pour associer une icône au moteur de rendu, vous pouvez en déclarer une dans le constructeur de QgsRendererV2AbstractMetadata dans le troisième (optionnel) argument. La fonction __init__() du constructeur de la classe de base de RandomRendererMetadata devient alors:

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

L’icône peut être également associée à n’importe quel moment en utilisant la méthode setIcon() de la classe de métadonnées. L’icône peut être chargée depuis un fichier (comme montré ci-dessus) ou peut être chargée depuis une ressource Qt (PyQt4 inclut un compilateur Python de fichiers .qrc).

Sujets complémentaires

**A FAIRE : **

Créer/modifier des symboles qui fonctionnent avec un style (QgsStyleV2) basé sur les rampes de couleur (QgsVectorColorRampV2). Moteur de rendu basé sur les ensembles de règles (voir`cet article <http://snorf.net/blog/2014/03/04/symbology-of-vector-layers-in-qgis-python-plugins>`_), explorer les registres des couches de symbole et des moteurs de rendu.