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:

Lancer l’éditeur de script

Lancer l’éditeur de script

Cela va faire apparaitre une fenêtre dans QGIS.

Editeur de script

Editeur de script

L’éditeur de script founit de nombreuses fonctionnalités facilitant la création de script python pour QGIS:

  • Charger un script
  • Edition dans un éditeur externe
  • Sauvegarde
  • Lancement
  • Copier/couper/coller
  • Recherche
  • Commenter/Décommenter
  • Inspecteur d’objet
  • Onglets
  • Autocomplétion
  • Coloration syntaxique
Fonctionnalités de l’éditeur de script

Fonctionnalités de l’éditeur de script

Quand un script est lancé, il est exécuté dans la console.

Exécution d’un script

Exécution d’un script

2.2 Création d’un script simple

Créeons 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.

2.2.3 Contrôle des projections

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.

Il apparait qu’une couche n’est pas projetée en Lambert93 comme les autres, il convient de la reprojeter.

Selon la version de QGIS, la projection RGF93/Lambert93 pourra être désignée par EPSG:2154 ou IGNF:LAMB93.

Vérifions que la projection s’est bien déroulée:

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.

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
  • une chemin vers un fichier (appelé uri)
  • un fournisseur (qui gère les entrées/sorties) : ici ogr
  • un système de coordonnées

2.3 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 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 “arrrondissement”

2.4 Créer un script pouvant être appelé depuis le boite à outils

Scripter et enchainer ses commandes est pratique, mais si les données changent souvent, il est alors pertinent de créer un outil qu’il est possible d’appeler depuis la boite à outils de traitements. En ajoutant des paramètres en entrée, vous pouvez alors relancer le script en faisant varier aisément ces derniers.

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’icone Python. Un menu déroulant doit apparaitre, choisissez l’option “Créer un nouveau script depuis un modèle”.

Créer un nouveau script à l’aide d’un modèle

Créer un nouveau script à l’aide d’un modèle

Une nouvelle fenêtre d’édition de script devrait s’ouvrir avec une ébauche de script.

Nouveau script depuis modèle

Nouveau script depuis modèle

Il contient déjà un certain nombre de lignes de code que nous allons pouvoir utiliser pour créer notre script paramétrisable.

# -*- 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.
        source = self.parameterAsSource(
            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))

        (sink, dest_id) = self.parameterAsSink(
            parameters,
            self.OUTPUT,
            context,
            source.fields(),
            source.wkbType(),
            source.sourceCrs()
        )

        # Send some information to the user
        feedback.pushInfo('CRS is {}'.format(source.sourceCrs().authid()))

        # 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
        total = 100.0 / source.featureCount() if source.featureCount() else 0
        features = source.getFeatures()

        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
            feedback.setProgress(int(current * total))

        # 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:
            buffered_layer = processing.run("native:buffer", {
                'INPUT': dest_id,
                'DISTANCE': 1.5,
                'SEGMENTS': 5,
                'END_CAP_STYLE': 0,
                'JOIN_STYLE': 0,
                'MITER_LIMIT': 2,
                'DISSOLVE': False,
                'OUTPUT': 'memory:'
            }, context=context, feedback=feedback)['OUTPUT']

        # 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.4.1 Modification du script

Le script importe un certain nombre de classes. Notamment les classes permettant les traitements et l’ajout de paramètres.

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

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

Modifier les constantes:

Modifiez les méthodes suivantes pour adapter les chaines de caractères à notre algortihme.

Laissez les autres méthodes 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 saissisez les nouvelles lignes suivantes:

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

Attention: suivant la version de QGIS, 'BUFFER_OUTPUT' devra être remplacer par 'TEMPORARY_BUFFER'.

2.4.2 Test du script

Tester votre script avec les couches suivantes:

  • entrees_sorties_metro
  • gare_partdieu
  • taille du tampon: 1000 mètres

Préparez l’extration de la gare Part-Dieu au préalable (extraction de la gare de la Part-Dieu et reprojection en 2154).

Depuis l’outil de script, cliquez sur la flèche verte pour lancer le script.

Lancement du script

Lancement du script

Le script doit fournir une interface utilisable:

Interface de l’outil

Interface de l’outil

Le résultat devrait ressembler à cela:

Résultat du traitement

Résultat du traitement

2.4.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’icone Python. Cliquez sur Ajouter un script à la boite à outils

Ajouter un script à la boite à outils

Ajouter un script à la boite à outils

Puis allez chercher le script proximite.py et cliquez sur Ouvrir.

Charger le script proximite.py

Charger le script proximite.py

Le script est maintenant accessible depuis la boite à outils, depuis le menu Scripts -> Example scripts.

Script Objets à proximité disponible dans la boite à outils.

Script Objets à proximité disponible dans la boite à outils.

Pour plus d’informations sur la manière d’écrire des scripts dans le QGIS user manual

2.4.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.5 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.

QGIS 3.4 étant encore très répandue, cette fonctionnalité ne sera pas abordée.