Cette section résume les diverses actions possibles sur les couches vectorielles.
Vous pouvez récupérer les informations relatives aux champs associés à une couche vectorielle en appelant pendingFields() sur une instance QgsVectorLayer
# "layer" is a QgsVectorLayer instance
for field in layer.pendingFields():
print field.name(), field.typeName()
Dans QGIS Desktop, les entités peuvent être sélectionnées de plusieurs façons : l’utilisateur peut cliquer sur l’entité, dessiner un rectangle sur le canevas de carte ou utiliser une expression filtrante. Les entités sélectionnées sont généralement identifiées par une couleur différente (jaune par défaut) pour attirer l’attention de l’utilisateur sur la sélection. Il peut parfois être utile de programmer la sélection des entités et la couleur par défaut.
Pour changer la couleur de sélection vous pouvez utiliser la méthode setSelectionColor() de QgsMapCanvas comme montré dans l’exemple suivant
iface.mapCanvas().setSelectionColor( QColor("red") )
Pour ajouter des entités à la liste des entités sélectionnées pour une couche donnée, vous pouvez appeler setSelectedFeatures() en lui passant la liste de l’identifiant des entités
# 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()])
Pour effacer la sélection, passez simplement une liste vide
layer.setSelectedFeatures([])
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 référencés par leur nom.
print feature['name']
Autrement, les attributs peuvent être référencés par index. Ce sera un peu plus rapide que d’utiliser leur nom. Par exemple, pour obtenir le premier attribut
print feature[0]
Si vous n’avez besoin que des entités sélectionnées, vous pouvez utiliser la méthode selectedFeatures() de la couche vectorielle :
selection = layer.selectedFeatures()
print len(selection)
for feature in selection:
# do whatever you need with the feature
Une autre option est la méthode features() de Processing :
import processing
features = processing.features(layer)
for feature in features:
# do whatever you need with the feature
Par défaut, elle fera l’itération de toutes les entités de la couche, s’il n’y a pas de sélection, ou autrement, des entités sélectionnées. Notez que ce comportement peut être changé dans les options de Processing pour ignorer les sélections.
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 feature in layer.getFeatures(request):
# do whatever you need with the feature
Si vous avez besoin d’un filtre basé sur les attributs à la place (ou en addition) d’un filtre spatial comme montré dans l’exemple ci-dessus, vous pouvez construire un objet QgsExpression et lui passer le constructeur QgsFeatureRequest. Par exemple :
# 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)
La requête peut être utilisée pour définir les données à récupérer de chaque entité, de manière à ce que l’itérateur ne retourne que des données partielles pour toutes les entités.
# 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)
Astuce
Si vous n’avez besoin que d’un sous-ensemble d’attributs ou que vous n’ayez pas besoin des informations de géométrie, vous pouvez accroître significativement la vitesse de la requête d’entités en utilisant le drapeau QgsFeatureRequest.NoGeometry ou en spécifiant un sous-ensemble d’attributs (qui peut être vide) comme montré dans l’exemple ci-dessus.
La majorité des fournisseurs de données vecteurs gère l’édition des données. Parfois, ils gèrent uniquement certaines actions d’édition. Utilisez la fonction capabilities() pour trouver quelles sont les fonctionnalités gérées:
caps = layer.dataProvider().capabilities()
En utilisant l’une des méthodes qui suivent pour l’édition de couches vectorielles, les changements sont directement validés dans le dispositif de stockage d’informations 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.
Note
Si vous travaillez dans QGIS (soit à partir de la console, soit à partir d’une extension), il peut être nécessaire de forcer la mise à jour du canevas de cartes pour pouvoir voir les changements que vous avez effectués aux géométries, au style ou aux attributs
# 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()
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])
Pour supprimer des entités, il suffit d’indiquer une liste de leur identifiant
if caps & QgsVectorDataProvider.DeleteFeatures:
res = layer.dataProvider().deleteFeatures([5, 10])
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 })
Astuce
Si vous ne voulez changer que les géométries, vous pouvez considérer l’utilisation de QgsVectorLayerEditUtils qui fournit quelques méthodes utiles pour éditer les géométries (déplace, ajout ou bouge un sommet, etc.)
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 dans le pilote de données, les champs de la couche doivent être rafraîchis car les changements ne sont pas automatiquement propagés.
layer.updateFields()
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 la fonctionnalité “Annuler”:
layer.beginEditCommand("Feature triangulation")
# ... call layer's editing methods ...
if problem_occurred:
layer.destroyEditCommand()
return
# ... more editing ...
layer.endEditCommand()
La fonction beginEditCommand() crée et “active” une commande interne qui enregistrera les changements effectuée sur la couche vecteur. Lors de l’appel de 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ù quelque chose tournerait mal lors des changements, la méthode destroyEditCommand() 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.
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:
créez l’index spatial — le code qui suit créé un index vide
index = QgsSpatialIndex()
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)
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))
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
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:
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()
Spécifie que le fournisseur utilisera un index spatial
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)])
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()
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.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()
Lorsqu’une couche vecteur est en cours de rendu, l’apparence des données est assuré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. |
Des moteurs de rendu personnalisés peut également être disponibles et vous ne pouvez donc pas supposer qu’il n’y a que ces types. Vous pouvez interroger le singleton QgsRendererV2Registry pour savoir quels sont les moteurs de rendu disponibles
QgsRendererV2Registry.instance().renderersList()
# Prints:
[u'singleSymbol',
u'categorizedSymbol',
u'graduatedSymbol',
u'RuleRenderer',
u'pointDisplacement',
u'invertedPolygonRenderer',
u'heatmapRenderer']
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()
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).
Vous pouvez changer le symbole utilisé par une couche vectorielle donnée en appelant setSymbol() en lui passant l’instance du symbole requis. Les symboles pour les couches de points, lignes et polygones peuvent être créés en appelant la fonction createSimple() des classes correspondantes QgsMarkerSymbolV2, QgsLineSymbolV2 et QgsFillSymbolV2.
Le dictionnaire passé à createSimple() définit les propriétés de style du symbole.
Vous pouvez par exemple changer le symbole d’une couche points particulière en appelant setSymbol() en lui passant une instance de QgsMarkerSymbolV2 comme montré dans le code de l’exemple suivant
symbol = QgsMarkerSymbolV2.createSimple({'name': 'square', 'color': 'red'})
layer.rendererV2().setSymbol(symbol)
name indique la forme du marqueur, et peut être l’une des valeurs suivantes :
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()))
Où 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().
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)
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.
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.
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é !
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).
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) Explorer les registres des couches de symbole et des moteurs de rendu.