Les extraits de code sur cette page nécessitent les importations suivantes si vous êtes en dehors de la console pyqgis :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from qgis.core import (
  QgsApplication,
  QgsDataSourceUri,
  QgsCategorizedSymbolRenderer,
  QgsClassificationRange,
  QgsPointXY,
  QgsProject,
  QgsExpression,
  QgsField,
  QgsFields,
  QgsFeature,
  QgsFeatureRequest,
  QgsFeatureRenderer,
  QgsGeometry,
  QgsGraduatedSymbolRenderer,
  QgsMarkerSymbol,
  QgsMessageLog,
  QgsRectangle,
  QgsRendererCategory,
  QgsRendererRange,
  QgsSymbol,
  QgsVectorDataProvider,
  QgsVectorLayer,
  QgsVectorFileWriter,
  QgsWkbTypes,
  QgsSpatialIndex,
  QgsVectorLayerUtils
)

from qgis.core.additions.edit import edit

from qgis.PyQt.QtGui import (
    QColor,
)

6. Utilisation de couches vectorielles

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

La plupart des exemples de cette section sont basés sur des méthodes de la classe QgsVectorLayer.

6.1. Récupérer les informations relatives aux attributs

Vous pouvez récupérer les informations associées aux champs d’une couche vecteur en appelant la méthode fields() d’un objet QgsVectorLayer:

vlayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
for field in vlayer.fields():
    print(field.name(), field.typeName())
1
2
3
4
5
ID Integer64
fk_region Integer64
ELEV Real
NAME String
USE String

Les méthodes displayField() et mapTipTemplate() de la classe QgsVectorLayer fournissent des informations sur le champ et le modèle utilisés dans l’onglet Onglet Infobulles.

Lorsque vous chargez une couche vecteur, un champ est toujours choisi par QGIS comme « Nom d’affichage », alors que le « maptip HTML » est vide par défaut. Avec ces méthodes, vous pouvez facilement obtenir les deux :

vlayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
print(vlayer.displayField())
NAME

Note

Si vous changez le nom affichage d’un champ en une expression, vous devez utiliser displayExpression() au lieu de displayField().

6.2. Itérer sur une couche vecteur

Parcourir les enregistrements d’une couche vecteur est l’une des tâches les plus basique. L’exemple de code ci-dessous vous montre comment le faire pour montrer quelques informations de chaque enregistrement. Ici, la variable layer doit être une instance de l’objet QgsVectorLayer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# "layer" is a QgsVectorLayer instance
layer = iface.activeLayer()
features = layer.getFeatures()

for feature in features:
    # retrieve every feature with its geometry and attributes
    print("Feature ID: ", feature.id())
    # fetch geometry
    # show some information about the feature geometry
    geom = feature.geometry()
    geomSingleType = QgsWkbTypes.isSingleType(geom.wkbType())
    if geom.type() == QgsWkbTypes.PointGeometry:
        # the geometry type can be of single or multi type
        if geomSingleType:
            x = geom.asPoint()
            print("Point: ", x)
        else:
            x = geom.asMultiPoint()
            print("MultiPoint: ", x)
    elif geom.type() == QgsWkbTypes.LineGeometry:
        if geomSingleType:
            x = geom.asPolyline()
            print("Line: ", x, "length: ", geom.length())
        else:
            x = geom.asMultiPolyline()
            print("MultiLine: ", x, "length: ", geom.length())
    elif geom.type() == QgsWkbTypes.PolygonGeometry:
        if geomSingleType:
            x = geom.asPolygon()
            print("Polygon: ", x, "Area: ", geom.area())
        else:
            x = geom.asMultiPolygon()
            print("MultiPolygon: ", x, "Area: ", geom.area())
    else:
        print("Unknown or invalid geometry")
    # fetch attributes
    attrs = feature.attributes()
    # attrs is a list. It contains all the attribute values of this feature
    print(attrs)
    # for this test only print the first feature
    break
Feature ID:  1
Point:  <QgsPointXY: POINT(7 45)>
[1, 'First feature']

6.3. Sélection des entités

Dans QGIS Desktop, vous pouvez sélectionner des enregistrements de différentes façons : vous pouvez cliquer sur un objet, dessiner un rectangle sur la carte ou encore filtrer une couche avec des expressions. Les objets sélectionnés apparaissent normalement en surbrillance dans une couleur différente (le jaune par défaut) pour permettre à l’utilisateur de les distinguer.

Parfois, il peut être utile de sélectionner des entités à l’aide de code ou de changer la couleur de sélection par défaut.

pour sélectionner toutes les entités d’une couche vecteur, vous pouvez utiliser la méthode selectAll()

# Get the active layer (must be a vector layer)
layer = iface.activeLayer()
layer.selectAll()

Pour sélectionner des entités à l’aide d’une expression, utilisez la méthode selectByExpression()

# Assumes that the active layer is points.shp file from the QGIS test suite
# (Class (string) and Heading (number) are attributes in points.shp)
layer = iface.activeLayer()
layer.selectByExpression('"Class"=\'B52\' and "Heading" > 10 and "Heading" <70', QgsVectorLayer.SetSelection)

Pour changer la couleur par défaut des objets séléctionnés, vous pouvez utiliser la méthode setSelectionColor() de la classe QgsMapCanvas, comme montré dans l’exemple suivant :

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

Pour ajouter des entités à celles déjà sélectionnées vous pouvez appeler la méthode select() en lui passant une liste d’ID d’entités :

1
2
3
4
5
6
7
8
9
selected_fid = []

# Get the first feature id from the layer
for feature in layer.getFeatures():
    selected_fid.append(feature.id())
    break

# Add these features to the selected list
layer.select(selected_fid)

Pour vider votre sélection :

layer.removeSelection()

6.3.1. Accès aux attributs

Les attributs peuvent être invoqués par leur nom :

print(feature['name'])
First feature

D’une autre façon, les attributs peuvent être invoqués par le ID. Cette méthode est légèrement plus rapide que d’utiliser le nom. Par exemple, pour récupérer le second attribut :

print(feature[1])
First feature

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

Si vous avez besoin uniquement de sélectionner des entités, vous pouvez utiliser la méthode selectedFeatures() depuis une couche vecteur :

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

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

Si vous voulez parcourir un sous-ensemble d’enregistrements d’une couche, par exemple sur une zone donnée, vous devez ajouter une classe QgsFeatureRequest à l’appel de la méthode getFeatures(). Par exemple :

1
2
3
4
5
6
7
areaOfInterest = QgsRectangle(450290,400520, 450750,400780)

request = QgsFeatureRequest().setFilterRect(areaOfInterest)

for feature in layer.getFeatures(request):
    # do whatever you need with the feature
    pass

Pour des questions de rapidité, la recherche d’intersection est souvent réalisée à partir du rectangle d’encombrement minimum des objets. Il existe toutefois l’option ExactIntersect qui permet de s’assurer que seul les objets intersectés soit renvoyés.

request = QgsFeatureRequest().setFilterRect(areaOfInterest) \
                             .setFlags(QgsFeatureRequest.ExactIntersect)

Avec la méthode setLimit(), vous pouvez limiter le nombre d’entités sélectionnées. Par exemple :

request = QgsFeatureRequest()
request.setLimit(2)
for feature in layer.getFeatures(request):
    print(feature)
<qgis._core.QgsFeature object at 0x7f9b78590948>

Si vous avez besoin d’un filtre basé sur des attributs à la place (ou en plus) d’un filtre spatial, comme montré dans les exemples précédents, vous pouvez construire un objet QgsExpression et le passer à la classe constructeur QgsFeatureRequest :

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

Vous pouvez vous référer à la section Expressions, Filtrage et Calcul de valeurs pour plus de détails sur la syntaxe employée dans QgsExpression.

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Only return selected fields to increase the "speed" of the request
request.setSubsetOfAttributes([0,2])

# More user friendly version
request.setSubsetOfAttributes(['name','id'],layer.fields())

# Don't return geometry objects to increase the "speed" of the request
request.setFlags(QgsFeatureRequest.NoGeometry)

# Fetch only the feature with id 45
request.setFilterFid(45)

# The options may be chained
request.setFilterRect(areaOfInterest).setFlags(QgsFeatureRequest.NoGeometry).setFilterFid(45).setSubsetOfAttributes([0,2])

6.4. Modifier des couches vecteur

La plupart des sources vectorielles supportent les fonctions d’édition. Parfois, ces possibilités sont limitées. Vous pouvez utiliser la fonction capabilities() pour voir quelles fonctionnalités sont supportées.

caps = layer.dataProvider().capabilities()
# Check if a particular capability is supported:
if caps & QgsVectorDataProvider.DeleteFeatures:
    print('The layer supports DeleteFeatures')
The layer supports DeleteFeatures

Pour une liste complète des possibilités, merci de se référer à la documentation de l’API QgsVectorDataProvider.

Pour afficher les possibilités d’édition d’une couche dans une liste séparé par des virgules, vous pouvez utiliser la méthode capabilitiesString() comme montré dans l’exemple suivant :

1
2
3
4
5
6
caps_string = layer.dataProvider().capabilitiesString()
# Print:
# 'Add Features, Delete Features, Change Attribute Values, Add Attributes,
# Delete Attributes, Rename Attributes, Fast Access to Features at ID,
# Presimplify Geometries, Presimplify Geometries with Validity Check,
# Transactions, Curved Geometries'

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

1
2
3
4
5
6
# 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.triggerRepaint()
else:
    iface.mapCanvas().refresh()

6.4.1. Ajout d’Entités

Créez quelques instances de QgsFeature et passez une liste de celles-ci à la méthode addFeatures() du fournisseur. Elle retournera deux valeurs : le résultat (True ou False) et la liste des entités ajoutées (leur ID est défini par le magasin de données).

Pour configurer les attributs d’entite, vous pouvez soit initialiser la fonctionnalité en passant un objet QgsFields (vous pouvez obtenir cela par la méthode fields() de la couche vecteur) ou appeler initAttributes() en passant le nombre de champs que vous voulez ajouter.

1
2
3
4
5
6
7
8
if caps & QgsVectorDataProvider.AddFeatures:
    feat = QgsFeature(layer.fields())
    feat.setAttributes([0, 'hello'])
    # Or set a single attribute by key or by index:
    feat.setAttribute('name', 'hello')
    feat.setAttribute(0, 'hello')
    feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(123, 456)))
    (res, outFeats) = layer.dataProvider().addFeatures([feat])

6.4.2. Suppression d’Entités

Pour supprimer certaines entités, il suffit de fournir une liste de leurs identifiants.

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

6.4.3. Modifier des Entités

Il est possible de modifier la géométrie des entités ou de changer certains attributs. L’exemple suivant modifie d’abord les valeurs des attributs avec les index 0 et 1, puis la géométrie de l’entité.

1
2
3
4
5
6
7
8
9
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.fromPointXY(QgsPointXY(111,222))
    layer.dataProvider().changeGeometryValues({ fid : geom })

Astuce

Favoriser la classe QgsVectorLayerEditUtils pour les modifications de géométrie uniquement

Si vous avez seulement besoin de modifier des géométries, vous pouvez utiliser la classe QgsVectorLayerEditUtils qui fournit quelques méthodes utiles pour modifier des géométries (traduire, insérer ou déplacer un sommet, etc.).

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

Lorsque vous éditez des vecteurs dans l’application QGIS, vous devez d’abord lancer le mode d’édition pour une couche particulière, puis faire quelques modifications et enfin valider (ou annuler) les changements. Toutes les modifications que vous faites ne sont pas écrites avant que vous les validiez — elles restent dans le tampon d’édition en mémoire de la couche. Il est possible d’utiliser cette fonctionnalité également par programmation — c’est juste une autre méthode d’édition des couches vecteur qui complète l’utilisation directe des fournisseurs de données. Utilisez cette option lorsque vous fournissez des outils d’interface graphique pour l’édition de la couche vecteur, car cela permet à l’utilisateur de décider s’il veut valider/rétablir et d’utiliser les fonctions annuler/rétablir. Lorsque les modifications sont validées, toutes les modifications du tampon d’édition sont enregistrées dans le fournisseur de données.

Les méthodes sont similaires à celles que nous avons vues dans le fournisseur, mais elles sont appelées sur l’objet QgsVectorLayer à la place.

Pour que ces méthodes fonctionnent, la couche doit être en mode édition. Pour lancer le mode d’édition, utilisez la méthode startEditing(). Pour arrêter l’édition, utilisez les méthodes commitChanges() ou rollBack(). La première va valider tous vos changements à la source de données, tandis que la seconde va les rejeter et ne modifiera pas du tout la source de données.

Pour savoir si une couche est en mode édition, utilisez la méthode isEditable().

Vous trouverez ici quelques exemples qui montrent comment utiliser ces méthodes de mise à jour.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from qgis.PyQt.QtCore import QVariant

feat1 = feat2 = QgsFeature(layer.fields())
fid = 99
feat1.setId(fid)

# 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
geometry = QgsGeometry.fromWkt("POINT(7 45)")
layer.changeGeometry(fid, geometry)
# update an attribute with given field index (int) to a given value
fieldIndex =1
value ='My new name'
layer.changeAttributeValue(fid, fieldIndex, value)

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

Pour que l’annulation/rétablissement fonctionne correctement, les appels mentionnés ci-dessus doivent être enveloppés dans des commandes d’annulation. (Si vous ne vous souciez guère de pouvoir annuler/rétablir et que vous voulez que les modifications soient enregistrées immédiatement, alors vous aurez un travail plus facile en éditant via le fournisseur de données.)

Voici comment vous pouvez utiliser la fonction d’annulation :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
layer.beginEditCommand("Feature triangulation")

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

if problem_occurred:
  layer.destroyEditCommand()
  # ... tell the user that there was a problem
  # and return

# ... more editing ...

layer.endEditCommand()

La méthode beginEditCommand() créera une commande interne « active » et enregistrera les changements ultérieurs dans la couche vecteur. Avec l’appel à endEditCommand() la commande est poussée sur la pile d’annulation et l’utilisateur pourra annuler/rétablir depuis l’interface graphique. Au cas où quelque chose se serait mal passé lors des modifications, la méthode destroyEditCommand() supprimera la commande et annulera toutes les modifications effectuées alors que cette commande était active.

Vous pouvez également utiliser la déclaration with edit (layer) pour englober dans un bloc de code plus sémantique, les instructions de modification et d’annulation. Une illustration dans l’exemple ci-dessous:

with edit(layer):
  feat = next(layer.getFeatures())
  feat[0] = 5
  layer.updateFeature(feat)

Cela appellera automatiquement commitChanges() à la fin. Si une exception se produit, il appellera rollBack() tous les changements. Si un problème est rencontré dans commitChanges() (lorsque la méthode retourne False), une exception QgsEditError sera levée.

6.4.5. 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.

1
2
3
4
5
6
7
8
9
from qgis.PyQt.QtCore import QVariant

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])
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Alternate methods for removing fields
# first create temporary fields to be removed (f1-3)
layer.dataProvider().addAttributes([QgsField("f1",QVariant.Int),QgsField("f2",QVariant.Int),QgsField("f3",QVariant.Int)])
layer.updateFields()
count=layer.fields().count() # count of layer fields
ind_list=list((count-3, count-2)) # create list

# remove a single field with an index
layer.dataProvider().deleteAttributes([count-1])

# remove multiple fields with a list of indices
layer.dataProvider().deleteAttributes(ind_list)

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

Astuce

Sauvegarder directement les modifications à l’aide de with

En utilisant with edit(layer): les modifications seront automatiquement validées en appelant commitChanges() à la fin. Si une exception se produit, il appliquera rollBack() pour annuler tous les changements. Voir Modifier des couches vecteur à l’aide d’un tampon d’édition.

6.5. 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 vectorielle QGIS, mais vous pouvez les créer facilement. C’est ce que vous devez faire:

  • créer un index spatial en utilisant la classe QgsSpatialIndex :

    index = QgsSpatialIndex()
    
  • ajouter un index aux entités — l’index prend QgsFeature et l’ajoute à la structure interne des données. Vous pouvez créer l’objet manuellement ou utiliser celui d’un précédent appel à la méthode getFeatures() du fournisseur de données.

    index.addFeature(feat)
    
  • Autre alternative, vous pouvez charger toutes les entités de la couche en une fois en utilisant un chargement en bloc.

    index = QgsSpatialIndex(layer.getFeatures())
    
  • Une fois que l’index est rempli avec des valeurs, vous pouvez lancer vos requêtes:

    1
    2
    3
    4
    5
    # returns array of feature IDs of five nearest features
    nearest = index.nearestNeighbor(QgsPointXY(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 également utiliser l’index spatial QgsSpatialIndexKDBush. Cet index est similaire à l’index standard QgsSpatialIndex mais :

  • ne prend en charge que les entités ponctuelles

  • est statique (aucun élément supplémentaire ne peut être ajouté à l’index après la construction)

  • est plus rapide!

  • permet l’accès direct aux entités ponctuelles d’origine, sans recourrir à des requêtes supplémentaires

  • prend vraiment en charge les recherches basées sur la distance, c’est-à-dire qu’il renvoie tous les points dans un rayon de recherche autour d’un autre point

6.6. La classe QgsVectorLayerUtils

La classe QgsVectorLayerUtils contient quelques méthodes bien utiles pour manipuler les couches vectorielles.

Par exemple, la méthode createFeature() prépare une QgsFeature à ajouter à une couche vectorielle, tout en conservant toutes les pontielles contraintes et valeurs par défaut de chaque champ :

vlayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
feat = QgsVectorLayerUtils.createFeature(vlayer)

La méthode getValues() permet de rapidement obtenir les valeurs d’un champ ou d’une expression :

1
2
3
4
5
vlayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
# select only the first feature to make the output shorter
vlayer.selectByIds([1])
val = QgsVectorLayerUtils.getValues(vlayer, "NAME", selectedOnly=True)
print(val)
(['AMBLER'], True)

6.7. Création de couches vecteur

Il existe plusieurs façons de générer un jeu de données de couche vecteur :

  • la classe QgsVectorFileWriter : Une classe pratique pour écrire des fichiers vecteur sur le disque, en utilisant soit un appel statique à writeAsVectorFormat() qui sauvegarde toute la couche vecteur, soit en créant une instance de la classe et en lançant des appels à addFeature(). Cette classe supporte tous les formats vecteur qu’OGR supporte (GeoPackage, Shapefile, GeoJSON, KML et autres).

  • la classe QgsVectorLayer : instancie un fournisseur de données qui interprète le chemin d’accès (url) fourni de la source de données pour se connecter et accéder aux données. Elle peut être utilisée pour créer des couches temporaires en mémoire (memory) et se connecter à des ensembles de données OGR (ogr), des bases de données (postgres, spatialite, mysql, mssql) et plus encore (wfs, gpx, delimitedtext…).

6.7.1. A partir d’une instance de QgsVectorFileWriter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# SaveVectorOptions contains many settings for the writer process
save_options = QgsVectorFileWriter.SaveVectorOptions()
transform_context = QgsProject.instance().transformContext()
# Write to a GeoPackage (default)
error = QgsVectorFileWriter.writeAsVectorFormatV2(layer,
                                                  "testdata/my_new_file.gpkg",
                                                  transform_context,
                                                  save_options)
if error[0] == QgsVectorFileWriter.NoError:
    print("success!")
else:
  print(error)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Write to an ESRI Shapefile format dataset using UTF-8 text encoding
save_options = QgsVectorFileWriter.SaveVectorOptions()
save_options.driverName = "ESRI Shapefile"
save_options.fileEncoding = "UTF-8"
transform_context = QgsProject.instance().transformContext()
error = QgsVectorFileWriter.writeAsVectorFormatV2(layer,
                                                  "testdata/my_new_shapefile",
                                                  transform_context,
                                                  save_options)
if error[0] == QgsVectorFileWriter.NoError:
    print("success again!")
else:
  print(error)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Write to an ESRI GDB file
save_options = QgsVectorFileWriter.SaveVectorOptions()
save_options.driverName = "FileGDB"
# if no geometry
save_options.overrideGeometryType = QgsWkbTypes.Unknown
save_options.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer
save_options.layerName = 'my_new_layer_name'
transform_context = QgsProject.instance().transformContext()
gdb_path = "testdata/my_example.gdb"
error = QgsVectorFileWriter.writeAsVectorFormatV2(layer,
                                                gdb_path,
                                                transform_context,
                                                save_options)
if error[0] == QgsVectorFileWriter.NoError:
  print("success!")
else:
  print(error)

Vous pouvez également convertir des champs pour les rendre compatibles avec différents formats en utilisant la classe FieldValueConverter. Par exemple, pour convertir des types de variables de tableau (par exemple dans Postgres) en un type de texte, vous pouvez faire ce qui suit :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
LIST_FIELD_NAME = 'xxxx'

class ESRIValueConverter(QgsVectorFileWriter.FieldValueConverter):

  def __init__(self, layer, list_field):
    QgsVectorFileWriter.FieldValueConverter.__init__(self)
    self.layer = layer
    self.list_field_idx = self.layer.fields().indexFromName(list_field)

  def convert(self, fieldIdxInLayer, value):
    if fieldIdxInLayer == self.list_field_idx:
      return QgsListFieldFormatter().representValue(layer=vlayer,
                                                    fieldIndex=self.list_field_idx,
                                                    config={},
                                                    cache=None,
                                                    value=value)
    else:
      return value

  def fieldDefinition(self, field):
    idx = self.layer.fields().indexFromName(field.name())
    if idx == self.list_field_idx:
      return QgsField(LIST_FIELD_NAME, QVariant.String)
    else:
      return self.layer.fields()[idx]

converter = ESRIValueConverter(vlayer, LIST_FIELD_NAME)
opts = QgsVectorFileWriter.SaveVectorOptions()
opts.fieldValueConverter = converter

Un SCR de destination peut également être spécifié — si une instance valide de QgsCoordinateReferenceSystem est passée comme quatrième paramètre, la couche est reprojetée dans ce SCR.

Pour les pilotes valides, vous pouvez appeler la méthode meth:supportedFiltersAndFormats() <qgis.core.QgsVectorFileWriter.supportedFiltersAndFormats> ou consulter les formats supportés par OGR — vous devez passer la valeur dans la colonne « Code » comme nom de pilote.

Vous pouvez choisir d’exporter uniquement certaines entités, de passer des options de création spécifiques au pilote ou de demander à ne pas créer d’attributs… Il existe un certain nombre d’autres paramètres (optionnels); voir la documentation de la classe QgsVectorFileWriter pour plus de détails.

6.7.2. Directement à partir des entités

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from qgis.PyQt.QtCore import QVariant

# define fields for feature attributes. A QgsFields object is needed
fields = QgsFields()
fields.append(QgsField("first", QVariant.Int))
fields.append(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. field map
3. geometry type - from WKBTYPE enum
4. layer's spatial reference (instance of
   QgsCoordinateReferenceSystem)
5. coordinate transform context
6. save options (driver name for the output file, encoding etc.)
"""

crs = QgsProject.instance().crs()
transform_context = QgsProject.instance().transformContext()
save_options = QgsVectorFileWriter.SaveVectorOptions()
save_options.driverName = "ESRI Shapefile"
save_options.fileEncoding = "UTF-8"

writer = QgsVectorFileWriter.create(
  "testdata/my_new_shapefile.shp",
  fields,
  QgsWkbTypes.Point,
  crs,
  transform_context,
  save_options
)

if writer.hasError() != QgsVectorFileWriter.NoError:
    print("Error when creating shapefile: ",  writer.errorMessage())

# add a feature
fet = QgsFeature()

fet.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(10,10)))
fet.setAttributes([1, "text"])
writer.addFeature(fet)

# delete the writer to flush features to disk
del writer

6.7.3. Depuis une instance de QgsVectorLayer

Parmi tous les fournisseurs de données supportés par la classe QgsVectorLayer, concentrons-nous sur les couches en mémoire. Le fournisseur de données en mémoire est destiné à être utilisé principalement par les développeurs d’extensions ou d’applications tierces. Il ne stocke pas de données sur le disque, ce qui permet aux développeurs de l’utiliser comme un support rapide pour certaines couches temporaires.

Le fournisseur gère les champs de type chaîne de caractères, entier ou réel.

Le fournisseur de données en mémoire prend également en charge l’indexation spatiale, qui est activée en appelant la fonction createSpatialIndex(). Une fois l’index spatial créé, vous pourrez itérer plus rapidement sur des éléments situés dans des régions plus petites (puisqu’il n’est pas nécessaire de parcourir toutes les entités, seulement celles qui se trouvent dans un rectangle spécifié).

Un fournisseur de données en mémoire est créé en passant "memory" comme chaîne de provider au constructeur QgsVectorLayer.

Le constructeur prend également une URI définissant le type de géométrie de la couche, comme "Point", "Linestring", "Polygon", "MultiPoint", "MultiLineString", "MultiPolygon" ou "none".

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ées de référence, où la définition peut être l’une 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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
from qgis.PyQt.QtCore import QVariant

# 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.fromPointXY(QgsPointXY(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é

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# show some stats
print("fields:", len(pr.fields()))
print("features:", pr.featureCount())
e = vl.extent()
print("extent:", e.xMinimum(), e.yMinimum(), e.xMaximum(), e.yMaximum())

# iterate over features
features = vl.getFeatures()
for fet in features:
    print("F:", fet.id(), fet.attributes(), fet.geometry().asPoint())
fields: 3
features: 1
extent: 10.0 10.0 10.0 10.0
F: 1 ['Johny', 2, 0.3] <QgsPointXY: POINT(10 10)>

6.8. Apparence (Symbologie) des couches vecteur

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 rendu pour une couche donnée peut être obtenu comme indiqué ci-dessous :

renderer = layer.renderer()

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

print("Type:", renderer.type())
Type: singleSymbol

Il y a plusieurs types de moteurs de rendu connus disponibles dans core centrale de QGIS :

Type

Classe

Description

singleSymbol

QgsSingleSymbolRenderer

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

categorizedSymbol

QgsCategorizedSymbolRenderer

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

graduatedSymbol

QgsGraduatedSymbolRenderer

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

Il peut aussi y avoir des types de rendus personnalisés, alors ne supposez jamais qu’il n’y a que ces types. Vous pouvez interroger la classe QgsRendererRegistry de l’application pour connaître les moteurs de rendu actuellement disponibles :

print(QgsApplication.rendererRegistry().renderersList())
['nullSymbol', 'singleSymbol', 'categorizedSymbol', 'graduatedSymbol', 'RuleRenderer', 'pointDisplacement', 'pointCluster', 'invertedPolygonRenderer', 'heatmapRenderer', '25dRenderer']

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:

renderer.dump()
SINGLE: MARKER SYMBOL (1 layers) color 190,207,80,255

6.8.1. Moteur de rendu à symbole unique

Vous pouvez obtenir le symbole utilisé pour le rendu en appelant la méthode symbol() et le changer avec la méthode setSymbol() (note pour les développeurs C++ : le moteur de rendu prend la propriété du symbole.)

Vous pouvez changer le symbole utilisé par une couche vecteur particulière en appelant setSymbol() en passant une instance du symbole approprié. Les symboles des couches point, line et polygon peuvent être créés en appelant la fonction createSimple() des classes correspondantes QgsMarkerSymbol, QgsLineSymbol et QgsFillSymbol.

Le dictionnaire passé à createSimple() définit les propriétés de style du symbole.

Par exemple, vous pouvez remplacer le symbole utilisé par une couche point particulière en appelant setSymbol() en passant une instance de QgsMarkerSymbol, comme dans l’exemple de code suivant :

symbol = QgsMarkerSymbol.createSimple({'name': 'square', 'color': 'red'})
layer.renderer().setSymbol(symbol)
# show the change
layer.triggerRepaint()

name indique la forme du marqueur, et peut être l’une des valeurs suivantes :

  • circle

  • square

  • cross

  • rectangle

  • diamond

  • pentagon

  • triangle

  • equilateral_triangle

  • star

  • regular_star

  • arrow

  • filled_arrowhead

  • x

Pour obtenir la liste complète des propriétés de la première couche de symbole d’une instance de symbole, vous pouvez suivre l’exemple de code :

print(layer.renderer().symbol().symbolLayers()[0].properties())
{'angle': '0', 'color': '255,0,0,255', 'horizontal_anchor_point': '1', 'joinstyle': 'bevel', 'name': 'square', 'offset': '0,0', 'offset_map_unit_scale': '3x:0,0,0,0,0,0', 'offset_unit': 'MM', 'outline_color': '35,35,35,255', 'outline_style': 'solid', 'outline_width': '0', 'outline_width_map_unit_scale': '3x:0,0,0,0,0,0', 'outline_width_unit': 'MM', 'scale_method': 'diameter', 'size': '2', 'size_map_unit_scale': '3x:0,0,0,0,0,0', 'size_unit': 'MM', 'vertical_anchor_point': '1'}

Cela peut être utile si vous souhaitez modifier certaines propriétés:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# You can alter a single property...
layer.renderer().symbol().symbolLayer(0).setSize(3)
# ... but not all properties are accessible from methods,
# you can also replace the symbol completely:
props = layer.renderer().symbol().symbolLayer(0).properties()
props['color'] = 'yellow'
props['name'] = 'square'
layer.renderer().setSymbol(QgsMarkerSymbol.createSimple(props))
# show the changes
layer.triggerRepaint()

6.8.2. Moteur de rendu à symboles catégorisés

Lorsque vous utilisez un moteur de rendu catégorisé, vous pouvez interroger et définir l’attribut qui est utilisé pour la classification : utilisez les méthodes classAttribute() et setClassAttribute().

Pour obtenir la liste des catégories

1
2
3
4
5
6
7
8
9
categorized_renderer = QgsCategorizedSymbolRenderer()
# Add a few categories
cat1 = QgsRendererCategory('1', QgsMarkerSymbol(), 'category 1')
cat2 = QgsRendererCategory('2', QgsMarkerSymbol(), 'category 2')
categorized_renderer.addCategory(cat1)
categorized_renderer.addCategory(cat2)

for cat in categorized_renderer.categories():
    print("{}: {} :: {}".format(cat.value(), cat.label(), cat.symbol()))
1: category 1 :: <qgis._core.QgsMarkerSymbol object at 0x7f378ffcd9d8>
2: category 2 :: <qgis._core.QgsMarkerSymbol object at 0x7f378ffcd9d8>

value() est la valeur utilisée pour la discrimination entre les catégories, label() est un texte utilisé pour la description de la catégorie et symbol() renvoie le symbole attribué.

Le moteur de rendu stocke généralement aussi le symbole original et la rampe de couleur qui ont été utilisés pour la classification : sourceColorRamp() et sourceSymbol().

6.8.3. 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
graduated_renderer = QgsGraduatedSymbolRenderer()
# Add a few categories
graduated_renderer.addClassRange(QgsRendererRange(QgsClassificationRange('class 0-100', 0, 100), QgsMarkerSymbol()))
graduated_renderer.addClassRange(QgsRendererRange(QgsClassificationRange('class 101-200', 101, 200), QgsMarkerSymbol()))

for ran in graduated_renderer.ranges():
    print("{} - {}: {} {}".format(
        ran.lowerValue(),
        ran.upperValue(),
        ran.label(),
        ran.symbol()
      ))
0.0 - 100.0: class 0-100 <qgis._core.QgsMarkerSymbol object at 0x7f8bad281b88>
101.0 - 200.0: class 101-200 <qgis._core.QgsMarkerSymbol object at 0x7f8bad281b88>

vous pouvez à nouveau utiliser les méthodes classAttribute() (pour trouver le nom de l’attribut de classification), sourceSymbol() et sourceColorRamp(). Il existe également la méthode mode() qui détermine comment les plages ont été créées : en utilisant des intervalles égaux, des quantiles ou une 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ée simplement une mise en forme en deux classes):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from qgis.PyQt import QtGui

myVectorLayer = QgsVectorLayer("testdata/airports.shp", "airports", "ogr")
myTargetField = 'ELEV'
myRangeList = []
myOpacity = 1
# Make our first symbol and range...
myMin = 0.0
myMax = 50.0
myLabel = 'Group 1'
myColour = QtGui.QColor('#ffee00')
mySymbol1 = QgsSymbol.defaultSymbol(myVectorLayer.geometryType())
mySymbol1.setColor(myColour)
mySymbol1.setOpacity(myOpacity)
myRange1 = QgsRendererRange(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 = QgsSymbol.defaultSymbol(
     myVectorLayer.geometryType())
mySymbol2.setColor(myColour)
mySymbol2.setOpacity(myOpacity)
myRange2 = QgsRendererRange(myMin, myMax, mySymbol2, myLabel)
myRangeList.append(myRange2)
myRenderer = QgsGraduatedSymbolRenderer('', myRangeList)
myClassificationMethod = QgsApplication.classificationMethodRegistry().method("EqualInterval")
myRenderer.setClassificationMethod(myClassificationMethod)
myRenderer.setClassAttribute(myTargetField)

myVectorLayer.setRenderer(myRenderer)

6.8.4. Travailler avec les symboles

Pour la représentation des symboles, il y a la classe de base QgsSymbol, avec trois classes dérivées :

Chaque symbole est constitué d’une ou plusieurs couches de symboles (classes dérivées de QgsSymbolLayer). Les couches de symboles font le rendu réel, la classe de symbole elle-même sert uniquement de conteneur pour les couches de symboles.

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

marker_symbol = QgsMarkerSymbol()
for i in range(marker_symbol.symbolLayerCount()):
    lyr = marker_symbol.symbolLayer(i)
    print("{}: {}".format(i, lyr.layerType()))
0: SimpleMarker

Pour connaître la couleur d’un symbole, utilisez la méthode color() et setColor() pour changer sa couleur. Avec les symboles de marqueurs, vous pouvez également demander la taille et la rotation des symboles avec les 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.

6.8.4.1. Travailler avec des couches de symboles

Comme indiqué précédemment, les couches de symboles (sous-classes de QgsSymbolLayer) déterminent l’apparence des entités. Il existe plusieurs classes de couches de symboles de base pour un usage général. Il est possible d’implémenter de nouveaux types de couches de symboles et donc de personnaliser arbitrairement la façon dont les entités seront rendues. La méthode layerType() identifie uniquement la classe de couche de symboles — les types de couches de symboles de base et par défaut sont SimpleMarker, SimpleLine et SimpleFill.

Vous pouvez obtenir une liste complète des types de couches de symboles que vous pouvez créer pour une classe de couches de symboles donnée avec le code suivant :

1
2
3
4
5
from qgis.core import QgsSymbolLayerRegistry
myRegistry = QgsApplication.symbolLayerRegistry()
myMetadata = myRegistry.symbolLayerMetadata("SimpleFill")
for item in myRegistry.symbolLayersForType(QgsSymbol.Marker):
    print(item)
1
2
3
4
5
6
7
8
9
EllipseMarker
FilledMarker
FontMarker
GeometryGenerator
MaskMarker
RasterMarker
SimpleMarker
SvgMarker
VectorField

La classe QgsSymbolLayerRegistry gère une base de données de tous les types de couches de symboles disponibles.

Pour accéder aux données de la couche de symboles, utilisez sa méthode properties() qui renvoie un dictionnaire clés-valeurs des propriétés qui déterminent l’apparence. Chaque type de couche de symboles possède un ensemble spécifique de propriétés qu’il utilise. En outre, il existe des méthodes génériques color(), size(), angle() et width(), avec leurs homologues. Bien entendu, la taille et l’angle ne sont disponibles que pour les couches de symboles de marqueurs et la largeur pour les couches de symboles de lignes.

6.8.4.2. 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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from qgis.core import QgsMarkerSymbolLayer
from qgis.PyQt.QtGui import QColor

class FooSymbolLayer(QgsMarkerSymbolLayer):

  def __init__(self, radius=4.0):
      QgsMarkerSymbolLayer.__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 symboles ; il doit être unique parmi toutes les couches de symboles. La méthode properties() est utilisée pour la persistance des attributs. La méthode clone() doit renvoyer une copie de la couche de symboles avec tous les attributs exactement identiques. Enfin, il existe des méthodes de rendu : startRender() est appelé avant de rendre la première entité, stopRender() lorsque le rendu est terminé, et renderPoint() est appelé pour effectuer le rendu. Les coordonnées du (des) point(s) sont déjà transformées en coordonnées de sortie.

Pour les polylignes et les polygones, la seule différence réside dans la méthode de rendu : vous utiliseriez renderPolyline() qui reçoit une liste de lignes, tandis que renderPolygon() reçoit une liste de points pour l’anneau extérieur comme premier paramètre et une liste d’anneaux intérieurs (ou None) comme second 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from qgis.gui import QgsSymbolLayerWidget

class FooSymbolLayerWidget(QgsSymbolLayerWidget):
    def __init__(self, parent=None):
        QgsSymbolLayerWidget.__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()"))

Ce widget peut être intégré dans le dialogue des propriétés des symboles. Lorsque le type de couche de symboles est sélectionné dans le dialogue des propriétés des symboles, il crée une instance de la couche de symboles et une instance du widget de couche de symboles. Ensuite, il appelle la méthode setSymbolLayer() pour assigner la couche de symboles au widget. Dans cette méthode, le widget doit mettre à jour l’interface utilisateur pour refléter les attributs de la couche de symboles. La méthode symbolLayer() est utilisée pour récupérer la couche de symbole à nouveau via la fenêtre des propriétés et l’utiliser pour le symbole.

À chaque changement d’attribut, le widget doit émettre le signal changed() pour permettre au dialogue des propriétés de mettre à jour l’aperçu du symbole.

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from qgis.core import QgsSymbol, QgsSymbolLayerAbstractMetadata, QgsSymbolLayerRegistry

class FooSymbolLayerMetadata(QgsSymbolLayerAbstractMetadata):

  def __init__(self):
    super().__init__("FooMarker", "My new Foo marker", QgsSymbol.Marker)

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

fslmetadata = FooSymbolLayerMetadata()
QgsApplication.symbolLayerRegistry().addSymbolLayerType(fslmetadata)

Vous devez passer le type de couche (le même que celui renvoyé par la cocuhe) et le type de symbole (marqueur/ligne/remplissage) au constructeur de la classe mère. La méthode createSymbolLayer() prend soin de créer une instance de couche de symboles avec les attributs spécifiés dans le dictionnaire props. Et il y a la méthode createSymbolLayerWidget() qui renvoie le widget 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é !

6.8.5. 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é.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import random
from qgis.core import QgsWkbTypes, QgsSymbol, QgsFeatureRenderer


class RandomRenderer(QgsFeatureRenderer):
  def __init__(self, syms=None):
    super().__init__("RandomRenderer")
    self.syms = syms if syms else [
      QgsSymbol.defaultSymbol(QgsWkbTypes.geometryType(QgsWkbTypes.Point)),
      QgsSymbol.defaultSymbol(QgsWkbTypes.geometryType(QgsWkbTypes.Point))
    ]

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

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

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

  def usedAttributes(self, context):
    return []

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

Le constructeur de la classe parent QgsFeatureRenderer a besoin d’un nom de moteur de rendu (qui doit être unique parmi les moteurs de rendu). La méthode symbolForFeature() est celle qui décide quel symbole sera utilisé pour une fonctionnalité particulière. startRender() et stopRender() s’occupent de l’initialisation/finalisation du rendu des symboles. La méthode usedAttributes() peut renvoyer une liste de noms de champs que le moteur de rendu s’attend à voir présents. Enfin, la méthode clone() devrait retourner une copie du moteur de rendu.

Comme pour les couches de symboles, il est possible d’attacher une interface graphique pour la configuration du moteur de rendu. Elle doit être dérivée de QgsRendererWidget. L’exemple de code suivant crée un bouton qui permet à l’utilisateur de définir le premier symbole

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from qgis.gui import QgsRendererWidget, QgsColorButton


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

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

  def renderer(self):
    return self.r

Le constructeur reçoit des instances de la couche active (QgsVectorLayer), du style global (QgsStyle) et du moteur de rendu courant. S’il n’y a pas de moteur de rendu ou si le moteur de rendu a un type différent, il sera remplacé par notre nouveau moteur de rendu, sinon nous utiliserons le moteur de rendu actuel (qui a déjà le type dont nous avons besoin). Le contenu du widget doit être mis à jour pour montrer l’état actuel du moteur de rendu. Lorsque le dialogue du moteur de rendu est accepté, la méthode renderer() du widget est appelée pour obtenir le moteur de rendu actuel — il 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
from qgis.core import (
  QgsRendererAbstractMetadata,
  QgsRendererRegistry,
  QgsApplication
)

class RandomRendererMetadata(QgsRendererAbstractMetadata):

  def __init__(self):
    super().__init__("RandomRenderer", "Random renderer")

  def createRenderer(self, element):
    return RandomRenderer()

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

rrmetadata = RandomRendererMetadata()
QgsApplication.rendererRegistry().addRenderer(rrmetadata)

De même que pour les couches de symboles, le constructeur de métadonnées attend le nom du moteur de rendu, le nom visible pour les utilisateurs et éventuellement le nom de l’icône du moteur de rendu. La méthode createRenderer() passe une instance QDomElement qui peut être utilisée pour restaurer l’état du moteur de rendu à partir de l’arbre DOM. La méthode createRendererWidget() crée le widget de configuration. Il n’a pas besoin d’être présent ou peut renvoyer None si le moteur de rendu n’est pas fourni avec l’interface graphique.

Pour associer une icône au moteur de rendu, vous pouvez l’assigner dans le constructeur QgsRendererAbstractMetadata comme troisième argument (facultatif) — le constructeur de la classe de base dans la fonction RandomRendererMetadata __init__() devient

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

L’icône peut également être associée à tout moment par la suite en utilisant la méthode setIcon() de la classe de métadonnées. L’icône peut être chargée à partir d’un fichier (comme indiqué ci-dessus) ou à partir d’une ressource Qt (PyQt5 comprend le compilateur .qrc pour Python).

6.9. Sujets complémentaires

A FAIRE :

  • création/modification des symboles

  • travailler avec le style (QgsStyle)

  • travailler avec des rampes de couleur (QgsColorRamp)

  • Explorer les couches de symboles et les registres de rendus