2 Scripts python
2.1 Scripter ses traitements
L’intérêt de sauvegarder ses traitements dans des scripts est la reproductibilité.
Que ce soit pour soi ou pour un tiers, immédiatement ou dans un temps plus ou moins long, avec les mêmes données ou avec d’autres.
Le script demande plus de temps de rédaction mais au final dès lors qu’il est réutilisé, les nombreux clics épargnés permettent de gagner du temps à un opérateur.
Il peut être fastidieux de reproduire une analyse 6 mois plus tard. Scripter permet de garder une trace écrite (et commentée !) de ce qui a été fait. Et donc de gagner du temps sur la nouvelle analyse.
QGIS fournit un éditeur de script accessible depuis la console python:
Cela va faire apparaitre une fenêtre dans QGIS.
L’éditeur de script fournit de nombreuses fonctionnalités facilitant la création de script python pour QGIS:
- Charger un script
- Édition dans un éditeur externe
- Sauvegarde
- Lancement
- Copier/couper/coller
- Recherche
- Commenter/Décommenter
- Inspecteur d’objet
- Onglets
- Autocomplétion
- Coloration syntaxique
Quand un script est lancé, il est exécuté dans la console Python de QGIS.
2.2 Création d’un script simple
Créons un script simple qui va trouver les stations de métro à moins de 1000 mètres de la gare de Lyon Part-Dieu.
2.2.1 Importation des bibliothèques
Pour réaliser notre tâche, nous allons avoir besoin d’accéder aux algorithmes de géotraitements et de certaines méthodes du projet QGIS.
# chargement des algorithmes de la boite à outils
import processing
# chargement des méthodes spécifiques aux projets QGIS
from qgis.core import QgsProject
2.2.2 Chargement des données
Nous allons avoir besoin des couches des gares SNCF et des stations de métro TCL. Chargeons aussi les arrondissements de la ville de Lyon afin d’avoir un peu de contexte sur nos données.
## arrondissements
= "/home/niko/Téléchargements/lyon/arrondissements.shp"
uri = iface.addVectorLayer(uri,"arrondissements", "ogr")
arrondissements
## gares
= "/home/niko/Téléchargements/lyon/gares_sncf.shp"
uri = iface.addVectorLayer(uri,"gares", "ogr")
gares
## métros
= "/home/niko/Téléchargements/lyon/entrees_sorties_metro.shp"
uri = iface.addVectorLayer(uri,"entrees_sorties_metro", "ogr") metro
2.2.3 Contrôle des systèmes de référence de coordonnées
Nous allons faire des opérations sur la géométrie des entités, il faut vérifier que nos sources de données sont dans la même projection.
## Contrôle des projections
for layer in QgsProject.instance().mapLayers().values():
= layer.crs().authid()
crs print("{} : {}".format(layer.name(), crs))
Cette boucle va extraire les couches contenues dans l’instance du projet QGIS.
La méthode mapLayers
de l’instance du projet QGIS renvoie un tableau avec
des informations sur les couches.
Nous extrayons les valeurs contenues dans le tableau et pour chaque couche, nous
appelons la méthode crs
(qui renvoit le système de référence de coordonnées,
*Coordinate Reference System – CRS –) et la méthode authid
de l’objet crs
qui retourne l’identifiant du système de référence de coordonnées.
Il apparaît qu’une couche n’est pas projetée en RGF93/Lambert93 comme les autres, il convient de la reprojeter.
Note: Selon le catalogue utilisé (EPSG
ou IGNF
), la projection RGF93/Lambert93
pourra être désignée par EPSG:2154
ou IGNF:LAMB93
.
## gares sncf n'est pas dans la même projection,
# il faut la reprojeter
= processing.runAndLoadResults(
gares_reproject "native:reprojectlayer",
'INPUT':gares,
{'TARGET_CRS':QgsCoordinateReferenceSystem('IGNF:LAMB93'),
'OUTPUT':'TEMPORARY_OUTPUT'})['OUTPUT']
Une nouvelle couche portant le nom de Reprojeté
a été chargée dans le projet.
Vérifions que notre opération s’est bien déroulée:
## Contrôle de l'opération
for layer in QgsProject.instance().mapLayers().values():
= layer.crs().authid()
crs print("{} : {}".format(layer.name(), crs))
2.2.4 Extraction de la gare de la Part-Dieu
Pour cette étape, nous allons utiliser une expression que nous utiliserons avec l’algorithme Extract by expression
.
Les expressions sont un moyen puissant pour accomplir de nombreuses choses. Il est possible de les utiliser pour sélectionner des entités, générer des géométries, faire varier des styles.
La documentation de QGIS à propos des expressions est très riche.
# Extraction de la gare de Part-Dieu
= "nom = 'Gare de Lyon Part-Dieu'"
expression = processing.runAndLoadResults(
partdieu "native:extractbyexpression",
'INPUT':gares_reproject,
{'EXPRESSION': expression,
'OUTPUT':'TEMPORARY_OUTPUT'})['OUTPUT']
Une fois notre gare extraite, nous pouvons créer notre zone tampon de 1000 mètres.
2.2.5 Création de la zone tampon
# Création d'un tampon de 500m
= processing.runAndLoadResults(
buffer_partdieu "native:buffer",
'INPUT':partdieu,
{'DISTANCE':1000,
'SEGMENTS':5,
'END_CAP_STYLE':0,
'JOIN_STYLE':0,
'MITER_LIMIT':2,
'DISSOLVE':False,
'OUTPUT':'TEMPORARY_OUTPUT'})['OUTPUT']
Comme cette couche produit de nouvelles données, il peut être intéressant de la visualiser.
Nous pouvons maintenant intersecter cette zone tampon avec la couche contenant les entrées et sorties de métro.
# Intersection des stations de métros
= processing.runAndLoadResults(
metro_1000m "native:intersection",
'INPUT':metro,
{'OVERLAY':buffer_partdieu,
'INPUT_FIELDS':[],
'OVERLAY_FIELDS':[],
'OUTPUT':'TEMPORARY_OUTPUT'})['OUTPUT']
2.2.6 Renommer la couche finale
Pour renommer une couche nous devons l’identifier puis la retrouver dans la liste des couches portant le nom ‘Intersection’. Par chance, nous n’avons fait qu’une intersection, c’est donc le premier élément de la liste.
# Renommer la couche intersectée
## identifier la couche
= QgsProject.instance()
project print(project.mapLayers())
for id, layer in project.mapLayers().items():
print(layer.name())
## renommer
'Intersection')[0].setName("metro_1000m") project.mapLayersByName(
Une fois renommée, il est plus facile de l’identifier pour la sauvegarder par exemple.
2.2.7 Sauvegarde de la couche
Pour sauvegarder la couche, nous allons utiliser la méthode QgsVectorLayerExporter.exportLayer
.
Cette méthode attend 4 arguments:
- la couche vectorielle
- un chemin vers un fichier (appelé uri)
- un fournisseur (qui gère les entrées/sorties) : ici ogr
- un système de coordonnées
Note: OGR était un utilitaire permettant de lire et d’écrire dans des fichiers vectoriels. OGR a été intégré au projet GDAL mais le nom est resté.
# sauvegarder une couche
## Sélectionner une couche par son nom (premier élément de la liste retourné)
= project.mapLayersByName('metro_1000m')[0]
layer
# Chemin de sauvegarde via une chaîne de caractères
="/home/niko/Téléchargements/lyon/metro1000m.gpkg"
output_path
## Appel de la méthode avec précision du fournisseur 'ogr' et du SRC cible (RGF93/L93)
QgsVectorLayerExporter.exportLayer(= layer,
layer = output_path,
uri = 'ogr',
providerKey = QgsCoordinateReferenceSystem('EPSG:2154')) destCRS
2.2.8 Exercice
Avec les données fournies, trouver les zones de Lyon qui répondent aux critères suivants:
- à moins d’1 kilomètre d’un gare SNCF
- à moins de 300 mètres d’une station de métro
- à moins de 200 mètres d’un espace vert
- à moins de 500 mètres d’une piscine
Quel arrondissement offre le plus de surface répondant à ces critères ?
Étapes:
- chargement des données
- contrôle des projection et reprojection si nécessaire
- création des zones tampon
- intersection des zones tampon
- jointure des attributs par localisation
- regroupement sur l’attribut “arrondissement”
- calcul de la surface intéressante pour chaque arrondissement
2.3 Créer un algorithme de géotraitement à partir d’un script
Depuis QGIS 3.4, il est possible de créer de nouveaux algorithmes de géotraitements sous la forme de scripts Python. Ces algorithmes pourront être appelés depuis la boîte à outils Traitements de la même manière que ceux fournis nativement par QGIS.
Si un ensemble d’opération doit être répété régulièrement avec des nouveaux paramètres, il est pertinent de créer un outil qu’il est possible d’appeler depuis la boite à outils de traitements. Vous pouvez alors relancer le script en faisant varier aisément ses paramètres avec un meilleur confort (pas d’édition de code, liste des couches chargées, boite de dialogue pour la sortie).
Pour cela, nous allons partir d’un modèle de script afin de nous faciliter le travail.
Aller dans la boite à outils de traitement, et cliquez sur l’icône Python. Un menu déroulant doit apparaître, choisissez l’option “Créer un nouveau script depuis un modèle”.
Une nouvelle fenêtre d’édition de script devrait s’ouvrir avec un modèle de script.
Il contient déjà un certain nombre de lignes de code que nous allons pouvoir utiliser pour créer notre script paramétrable.
# -*- coding: utf-8 -*-
"""
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
from qgis.PyQt.QtCore import QCoreApplication
from qgis.core import (QgsProcessing,
QgsFeatureSink,
QgsProcessingException,
QgsProcessingAlgorithm,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterFeatureSink)from qgis import processing
class ExampleProcessingAlgorithm(QgsProcessingAlgorithm):
"""
This is an example algorithm that takes a vector layer and
creates a new identical one.
It is meant to be used as an example of how to create your own
algorithms and explain methods and variables used to do it. An
algorithm like this will be available in all elements, and there
is not need for additional work.
All Processing algorithms should extend the QgsProcessingAlgorithm
class.
"""
# Constants used to refer to parameters and outputs. They will be
# used when calling the algorithm from another algorithm, or when
# calling from the QGIS console.
= 'INPUT'
INPUT = 'OUTPUT'
OUTPUT
def tr(self, string):
"""
Returns a translatable string with the self.tr() function.
"""
return QCoreApplication.translate('Processing', string)
def createInstance(self):
return ExampleProcessingAlgorithm()
def name(self):
"""
Returns the algorithm name, used for identifying the algorithm. This
string should be fixed for the algorithm, and must not be localised.
The name should be unique within each provider. Names should contain
lowercase alphanumeric characters only and no spaces or other
formatting characters.
"""
return 'myscript'
def displayName(self):
"""
Returns the translated algorithm name, which should be used for any
user-visible display of the algorithm name.
"""
return self.tr('My Script')
def group(self):
"""
Returns the name of the group this algorithm belongs to. This string
should be localised.
"""
return self.tr('Example scripts')
def groupId(self):
"""
Returns the unique ID of the group this algorithm belongs to. This
string should be fixed for the algorithm, and must not be localised.
The group id should be unique within each provider. Group id should
contain lowercase alphanumeric characters only and no spaces or other
formatting characters.
"""
return 'examplescripts'
def shortHelpString(self):
"""
Returns a localised short helper string for the algorithm. This string
should provide a basic description about what the algorithm does and the
parameters and outputs associated with it..
"""
return self.tr("Example algorithm short description")
def initAlgorithm(self, config=None):
"""
Here we define the inputs and output of the algorithm, along
with some other properties.
"""
# We add the input vector features source. It can have any kind of
# geometry.
self.addParameter(
QgsProcessingParameterFeatureSource(self.INPUT,
self.tr('Input layer'),
[QgsProcessing.TypeVectorAnyGeometry]
)
)
# We add a feature sink in which to store our processed features (this
# usually takes the form of a newly created vector layer when the
# algorithm is run in QGIS).
self.addParameter(
QgsProcessingParameterFeatureSink(self.OUTPUT,
self.tr('Output layer')
)
)
def processAlgorithm(self, parameters, context, feedback):
"""
Here is where the processing itself takes place.
"""
# Retrieve the feature source and sink. The 'dest_id' variable is used
# to uniquely identify the feature sink, and must be included in the
# dictionary returned by the processAlgorithm function.
= self.parameterAsSource(
source
parameters,self.INPUT,
context
)
# If source was not found, throw an exception to indicate that the algorithm
# encountered a fatal error. The exception text can be any string, but in this
# case we use the pre-built invalidSourceError method to return a standard
# helper text for when a source cannot be evaluated
if source is None:
raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))
= self.parameterAsSink(
(sink, dest_id)
parameters,self.OUTPUT,
context,
source.fields(),
source.wkbType(),
source.sourceCrs()
)
# Send some information to the user
'CRS is {}'.format(source.sourceCrs().authid()))
feedback.pushInfo(
# If sink was not created, throw an exception to indicate that the algorithm
# encountered a fatal error. The exception text can be any string, but in this
# case we use the pre-built invalidSinkError method to return a standard
# helper text for when a sink cannot be evaluated
if sink is None:
raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))
# Compute the number of steps to display within the progress bar and
# get features from source
= 100.0 / source.featureCount() if source.featureCount() else 0
total = source.getFeatures()
features
for current, feature in enumerate(features):
# Stop the algorithm if cancel button has been clicked
if feedback.isCanceled():
break
# Add a feature in the sink
sink.addFeature(feature, QgsFeatureSink.FastInsert)
# Update the progress bar
int(current * total))
feedback.setProgress(
# To run another Processing algorithm as part of this algorithm, you can use
# processing.run(...). Make sure you pass the current context and feedback
# to processing.run to ensure that all temporary layer outputs are available
# to the executed algorithm, and that the executed algorithm can send feedback
# reports to the user (and correctly handle cancellation and progress reports!)
if False:
= processing.run("native:buffer", {
buffered_layer 'INPUT': dest_id,
'DISTANCE': 1.5,
'SEGMENTS': 5,
'END_CAP_STYLE': 0,
'JOIN_STYLE': 0,
'MITER_LIMIT': 2,
'DISSOLVE': False,
'OUTPUT': 'memory:'
=context, feedback=feedback)['OUTPUT']
}, context
# Return the results of the algorithm. In this case our only result is
# the feature sink which contains the processed features, but some
# algorithms may return multiple feature sinks, calculated numeric
# statistics, etc. These should all be included in the returned
# dictionary, with keys matching the feature corresponding parameter
# or output names.
return {self.OUTPUT: dest_id}
2.3.1 Modification du script
Le script importe un certain nombre de classes. Notamment les classes permettant les traitements et l’ajout de paramètres.
from qgis.PyQt.QtCore import QCoreApplication
from qgis.core import (QgsProcessing,
QgsFeatureSink,
QgsProcessingException,
QgsProcessingAlgorithm,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterFeatureSink)from qgis import processing
Attention: pour QGIS 3.4, from qgis import processing
devra être remplacé
par import processing
.
Ajoutez les classesQgsProcessingParameterVectorDestination
et
QgsProcessingParameterDistance
aux imports depuis qgis.core
Le script fournit aussi une classe ExampleProcessingAlgorithm()
.
class ExampleProcessingAlgorithm(QgsProcessingAlgorithm):
# Constants used to refer to parameters and outputs. They will be
# used when calling the algorithm from another algorithm, or when
# calling from the QGIS console.
= 'INPUT'
INPUT = 'OUTPUT'
OUTPUT
### Définitions de fonctions
Le modèle fournit de nombreuses fonctions, nous n’allons en modifier que quelques unes.
Commençons par renommer notre algorithme avec le nom ProximiteAlgorithm(QgsProcessingAlgorithm)
.
class ProximiteAlgorithm(QgsProcessingAlgorithm):
"""
Retourne les objets d'une couche à proximité d'une autre couche.
"""
Modifier les constantes:
= 'OUTPUT' # couche en sortie
OUTPUT = 'SOURCE' # couche qui sera intersectée:
SOURCE = 'SUPERPOSITION' # couche qui servira à créer le buffer
SUPERPOSITION = 'BUFFER_OUTPUT' # stockage du buffer BUFFER_OUTPUT
Modifiez les méthodes suivantes pour adapter les chaînes de caractères à notre algorithme.
def tr(self, string):
"""
Returns a translatable string with the self.tr() function.
"""
return QCoreApplication.translate('Processing', string)
def createInstance(self):
return ProximiteAlgorithm()
def name(self):
"""
Returns the algorithm name, used for identifying the algorithm.
"""
return 'proximite'
def displayName(self):
"""
Returns the translated algorithm name, which should be used for any
user-visible display of the algorithm name.
"""
return self.tr('Objets à proximité')
Il peut être utile de classer ses algorithmes dans des groupes spécifiques,
la modification des méthodes group
et groupId
permettent cela.
def group(self):
"""
Returns the name of the group this algorithm belongs to. This string
should be localised.
"""
return self.tr('Scripts PyQGIS')
def groupId(self):
"""
Returns the unique ID of the group this algorithm belongs to. This
string should be fixed for the algorithm, and must not be localised.
The group id should be unique within each provider. Group id should
contain lowercase alphanumeric characters only and no spaces or other
formatting characters.
"""
return 'scriptspyqgis'
Laissez les autres méthodes inchangées et rendez-vous à la méthode initAlgorithm()
.
C’est cette méthode qui va permettre de récupérer les paramètres.
Nous allons avoir besoin de charger 2 couches vectorielles, récupérer la taille de la zone tampon et la destination du résultat.
Supprimez les lignes contenues dans InitAlgorithm
et saisissez les nouvelles
lignes suivantes:
def initAlgorithm(self, config=None):
"""
Initialisation de l'algorithme: définition des
paramètres en entrée et en sortie
"""
# Données source
self.addParameter(
QgsProcessingParameterFeatureSource(self.SOURCE,
self.tr('Couche d\'intérêt'),
[QgsProcessing.TypeVectorAnyGeometry]
)
)
# Données superposées
self.addParameter(
QgsProcessingParameterFeatureSource(self.SUPERPOSITION,
self.tr('Couche sur laquelle sera faite le tampon'),
[QgsProcessing.TypeVectorAnyGeometry]
)
)
# Distance du tampon
self.addParameter(
QgsProcessingParameterDistance('BUFFERDIST',
self.tr('Distance du tampon'),
= 1000.0,
defaultValue # Make distance units match the INPUT layer units:
='SUPERPOSITION'
parentParameterName
)
)# Récupération de la destination.
self.addParameter(
QgsProcessingParameterVectorDestination(self.OUTPUT,
self.tr('Sortie')
) )
Le paramètre parentParameterName
permet d’hériter de certaines
informations d’un autre paramètre.
Dans cet exemple, le tampon sera créé avec les mêmes unités de
mesures que la couche passée dans le paramètre `SUPERPOSITION’.
Notre couche étant en RGF93/Lambert93, le tampon sera calculé en mètres.
Une fois nos paramètres obtenus, nous pouvons passer à l’algorithme.
Pour cela, nous devons modifier la méthode processAlgorithm
.
Dans un premier temps, nous récupérons les informations concernant le fichier de
sortie outputFile
et de la taille du tampon bufferdist
.
Puis nous insérons le traitement de la zone tampon puis celui faisant l’intersection entre la couche source et la zone tampon.
Enfin le résultat est retourné.
def processAlgorithm(self, parameters, context, feedback):
"""
Here is where the processing itself takes place.
"""
# récupération des paramètres
= self.parameterAsSource(parameters, self.SOURCE, context)
source = self.parameterAsSource(parameters, self.SUPERPOSITION, context)
superposition = self.parameterAsOutputLayer(parameters,self.OUTPUT, context)
outputFile = self.parameterAsDouble(parameters, 'BUFFERDIST', context)
bufferdist
# Vérification qu'il y a bien deux couches en entrée
# des erreurs sont levées si une couche est manquante
if source is None:
raise QgsProcessingException(self.invalidSourceError(parameters, self.SOURCE))
if superposition is None:
raise QgsProcessingException(self.invalidSourceError(parameters, self.SUPERPOSITION))
# Il est possible d'afficher des informations
# à destination de l'utilisateur
'Source CRS is {}'.format(source.sourceCrs().authid()))
feedback.pushInfo('Superposition CRS is {}'.format(superposition.sourceCrs().authid()))
feedback.pushInfo(
# Vérification que les couches ont un SRC compatibles
if source.sourceCrs().authid() != superposition.sourceCrs().authid():
# Si SRC différents, QGIS lève une exception et arrête l'algorithme.
raise QgsProcessingException(
self.tr('les couches doivent être dans le même système de référence de coordonnées.')
)
# Calcul du tampon
= processing.run(
tampon "native:buffer",{
'INPUT': parameters['SUPERPOSITION'],
'DISTANCE' : bufferdist,
'SEGMENTS' :5,
'END CAP STYLE' : 0,
'JOIN STYLE': 0,
'MITER LIMIT' :2,
'DISSOLVE' : False,
'OUTPUT' : 'TEMPORARY_OUTPUT'
},=context,
context=feedback)
feedback
## Intersection du tampon et de la couche source
= processing.run(
intersection "native:intersection",
'INPUT': parameters['SOURCE'],
{'OVERLAY': tampon['OUTPUT'],
'INPUT_FIELDS':[],
'OVERLAY_FIELDS':[],
'OUTPUT':outputFile})['OUTPUT']
# Return the results of the algorithm. In this case our only result is
# the intersection.
return {self.OUTPUT: intersection}
Attention: suivant la version de QGIS, 'TEMPORARY_BUFFER'
devra être remplacer
par 'BUFFER_OUTPUT'
.
2.3.2 Test du script
Tester votre script avec les couches suivantes:
entrees_sorties_metro
gare_partdieu
- extraction de la gare de la Part-Dieu
- reprojection en 2154
- à préparer avant de lancer l’algorithme
- taille du tampon:
1000 mètres
Depuis l’outil de script, cliquez sur la flèche verte pour lancer le script.
Le script doit fournir une interface utilisable:
Le résultat devrait ressembler à cela:
2.3.3 Ajout du script dans la boite à outils
Maintenant que nous avons un script fonctionnel, nous pouvons l’ajouter à la boite à outil pour le réutiliser quand nous en aurons besoin.
Pour cela, depuis la boite à outils, dépliez le menu déroulant situé
sous l’icône Python.
Cliquez sur Ajouter un script à la boite à outils
Puis allez chercher le script proximite.py
et cliquez sur Ouvrir
.
Le script est maintenant accessible depuis la boite à outils, depuis le menu
Scripts
-> Scripts PyQGIS
.
Pour plus d’informations sur la manière d’écrire des scripts dans le QGIS user manual
Il est possible d’écrire des scripts plus courts, à l’aide du décorateur @alg
néanmoins les scripts utilisant les décorateurs ne peuvent être utilisés qu’à
travers la boîte à outils Traitements.
Il n’est alors pas possible de les utiliser dans un plugin.
2.3.4 Exercice
A partir d’un modèle, créez l’outil permettant de définir la zone la plus propice suivant les critères vus précédemment (distances aux métros, piscines, gares, etc.).
Le script prend en entrée 4 couches vectorielles et pour chaque couche une distance en mètres.
Par commodité, ne fournissez que des couches dans la même projection.
Le script fournit en résultat une couche intersectant tous les tampons.
2.4 Depuis le modeleur
Il est possible d’exporter un modèle de traitement vers du code Python. Toutefois cette fonctionnalité a été désactivée lors du passage à QGIS 3.0 et n’a été réactivé qu’à partir de QGIS 3.6.
Cette fonctionnalité n’est pas abordée dans ce cours. Essayez-la !