3 Créer une extension (Plugin)

3.1 Plugins utiles

Pour nous faciliter la tâche, nous allons utiliser deux plugins dédiés au développement de plugins QGIS:

Vérifier leur présence dans les plugins installés de QGIS:

Plugins installés

Si ils sont absents, installez-les via le gestionnaire d’extension.

3.2 Créer un plugin simple

Nous allons procéder aux étapes suivantes :

  1. Créer un plugin à partir d’un modèle
  2. Modifier l’interface
  3. Modifier le code Python

Le plugin présenté ici retournera les coordonnées du point cliqué à la souris. Il s’agit d’un exemple adapté de celui présenté dans le The PyQGIS Programmer’s Guide.

3.2.1 Créer une base de départ

Lancez Plugin Builder, une première fenêtre va s’ouvrir. Saisissez les informations suivantes, tous les champs sont obligatoires.

Définition du plugin Où suis-je

Validez, une deuxième fenêtre s’ouvre pour saisir des informations complémentaires sur le plugin. Là aussi, la saisie est obligatoire.

Fenêtre About

Ensuite nous devons choisir le modèle. Nous allons commencer avec le modèle Tool button with dialog qui est assez simple mais que nous compléxifierons.

Plusieurs modèles sont disponibles:

  • Tool button with dialog
  • Tool button with dock widgets
  • Processing Provider

Saisissez le texte que vous voulez voir apparaître dans le menu.

Un menu déroulant propose de choisir dans quel menu le plugin sera intégré. Il est possible de choisir parmi les menus suivants:

  • Plugins
  • Database
  • Raster
  • Vector
  • Web

Laissez Plugins proposé par défaut.

Choix du modèle

Cliquez sur Next, le Plugin Builder propose alors les éléments qu’il est possible d’inclure dans le plugin.

  • Internationalization (support pour les traductions)
  • Help (Génération de l’aide avec l’outil Sphinx)
  • Unit tests (tests unitaires)
  • Helper scripts (scripts d’aide durant le développement)
  • Makefile (GNU Makefile pour compiler le plugin)
  • pb_tool (outil Python en ligne de commande pour compiler, déployer et packager le plugin)

Laissez tout coché par défaut.

L’étape suivante propose de renseigner les liens pour la publication du plugin. Laissez les valeurs par défaut. Il s’agit de lien vers des dépôts GitHub ou Gitlab où les développeurs pourront auditer le code et proposer des améliorations ou reporter des problèmes.

Liens utiles du plugin

Ensuite le Plugin Builder demande le chemin vers un répertoire de travail pour y déposer l’architecture du futur plugin.

Ce dossier doit être différent du répertoire d’installation des plugins de QGIS.

Répertoire de travail du plugin

Enfin la dernière fenêtre récapitule les informations importantes et rappelle les prochaines étapes:

Fenêtre finale du Plugin Builder

Your plugin OuSuisJe was created in:
  /media/work/developpements/plugins/ousuisje 
Your QGIS plugin directory is located at:
  /home/niko/.local/share/QGIS/QGIS3/profiles/default/python/plugins 

Prochaines étapes:

  • Compiler resources.py avec pyrcc5 (cette étape n’està faire qu’une seule fois, à la création du plugin)
  • Eventuellement, tester les sources générées avec make test (ou avec un IDE)
  • Copier le répertoire complet dans le dossier contenant les plugins
  • tester le plugin en l’activant dans le plugin manager de QGIS
  • Modifier le plugin en éditant ousuisje.py
  • Éventuellement remplacer l’icone par une icone personnalisée
  • Modifier l’interface utilisateur en ouvrant ousuisje_dialog_base.ui dans Qt Designer

Le dossier contenant notre plugin contient un certain nombre de fichiers et dossiers:

Dossier du plugin Où suis-je ?

  • help: dossier contenant les fichiers permettant de générer l’aide
  • i18n : internationalisation (traductions)
  • icon.png : icone
  • __init__.py : fichier python initialisant le plugin
  • Makefile : fichier de configuration pour la compilation
  • metadata.txt : métadonnées
  • ousuisje_dialog_base.ui : fichier d’interface graphique
  • ousuisje_dialog.py : fichier Python d’interaction avec l’interface graphique
  • ousuisje.py : implémentation du plugin
  • pb_tool.cfg : configuration de l’outil pb_tool
  • plugin_upload.py : fichier Python pour le chargement du plugin dans QGIS
  • pylintrc : règles de mise en forme automatique du code
  • README.html : page web informative
  • README.txt : document texte informatif
  • resources.qrc : resources Qt
  • scripts : dossier contenant les scripts du plugin
  • test : tests unitaires

Le fichier resources.py est créé par pyrcc5 à partir des ressources. pyrcc5 est un outil en ligne de commande.

Sous Windows, pyrcc5 est fourni avec le shell OSGeo4W. Néanmoins le recours à un fichier .bat peut être utile pour charger les bons chemins vers les binaires. Dans un fichier compile.bat dans votre dossier de développement, copiez-collez les lignes suivantes:

@echo off
call "C:\Program Files\QGIS 3.16\bin\o4w_env.bat"
call "C:\Program Files\QGIS 3.16\bin\qt5_env.bat"
call "C:\Program Files\QGIS 3.16\bin\py3_env.bat"

@echo on
pyrcc5 -o resources.py resources.qrc

source: GIS stackexchange

Vérifiez bien que les chemins vers les exécutables correspondent à votre installation. Pour lancer la compilation, double-cliquez sur compile.bat.

Sous Linux, entrez la commande suivante dans un terminal:

pyrcc5 -o resources_rc.py resources.qrc

Un fichier ressources_rc.py devrait être créé.

Pour tester votre plugin, copiez le répertoire du plugin dans le répertoire des plugins de QGIS.

Répertoire des plugins de l’utilisateur

Fermez et relancez QGIS pour lui faire charger le plugin. En effet, au démarrage de QGIS, celui-ci scanne le répertoire des plugins. Il tente ensuite de charger tous les plugins présents en les initialisant avec un appel du __init__.py. Si le plugin charge sans erreur, l’interface graphique est chargée et le plugin est affiché dans la liste des plugins actifs. En cas d’erreurs, un message est affiché dans QGIS.

Une fois QGIS relancé, allez dans le gestionnaire de plugins et activez le plugin Où suis-je ?.

Activation du plugin

L’icone du plugin est maintenant affichée dans la barre d’outils plugin. Cliquez dessus pour lancer le plugin.

Plugin Où suis-je ?

Une fenêtre avec une interface simple apparait.

Interface par défaut

L’interface ne propose que deux boutons:

  • Ok
  • Cancel

Les deux boutons ferment la fenêtre mais envoient des signaux différents qui seront récupérés par QGIS.

Nous avons un plugin fonctionnel mais pas très utile. Modifions l’interface pour préparer la suite.

3.2.2 Modification de l’interface

Pour notre besoin (afficher des coordonnées), la fenêtre du plugin est trop grande. De plus, il nous faut y ajouter des éléments en modifiant le fichier d’interface (.ui) et le fichier d’implémentation (ousuisje.py).

Commençons par l’interface. Nous allons utiliser pour cela l’outil Qt Designer qui est installé avec QGIS.

Pour notre plugin, nous allons rester sur une interface simple:

  • une étiquette expliquant le résultat
  • une boite texte affichant les coordonnées du point cliqué
  • un bouton pour fermer la fenêtre

Ouvrez Qt Designer et ouvrez le fichier ousuisje_dialog_base.ui avec.

Ouverture de l’interface dans Qt Designer

Nous devons:

  1. Supprimer le groupe de bouton Ok/Cancel
  2. Ajouter un widget Label par cliquer-déposer
  3. En changer le texte et la mise en forme
  4. Ajouter un widget Line Edit que nous placerons dessous l’étiquette
  5. Un bouton Push Button en dessous
  6. Double-cliquez sur le bouton et inscrivez “Fermer”
  7. Redimensionnez la fenêtre pour enlever l’espace inutile

Nouveller interface

Nous avons notre nouvelle interface. Avant de fermer Qt Designer, il nous faut encore définir l’action du bouton Fermer. Pour cela nous devons définir le signal et le slot.

Dans le menu Edition, cliquez sur Éditer signaux/slots. Cliquez sur le bouton Fermer, maintenez appuyer et déplacer le curseur vers une partie vide de l’interface. Relachez, la boite de dialogue de configuration des connexions doit apparaitre.

Configuration des connexions

Dans le panneau gauche, cliquez sur pressed(). Dans le panneau droit, cliquez sur reject(). Cliquez sur Ok pour valider la connexion.

Côté code, un bouton suivant son type pourra avoir plusieurs attributs (coché/non coché, accessible ou non, etc.), il transmet aussi un signal (pressé, relaché, cliqué) qui sera exploité par le code Python.

Il faut que le signal défini dans l’interface et celui attendu dans le code Python coïncide.

Plus d’informations sur les boutons dans la documentation de PyQt.

Résultat de la connexion

Sauvegardez les changements sur le fichier ousuisje_dialog_base.ui.

Nous avons à présent une interface ainsi qu’un bouton de fermeture qui fonctionne, nous pouvons passer au code Python qui va intéragir avec notre interface.

3.2.3 Implémentation du code Python

L’initialisation du plugin se fait par le fichier ousuisje.py qui contient la méthode __init__(). Commençons par celui-ci, ouvrez-le avec une éditeur de texte.

3.2.3.1 ousuisje.py

Jetons un œil à ce fichier.

Il importe un certain nombre de module et défini la classe ousuisje qui contient la méthode __init__().

from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction

# Initialize Qt resources from file resources.py
from .resources import *
# Import the code for the dialog
from .ousuisje_dialog import OuSuisJeDialog
import os.path


class OuSuisJe:
    """QGIS Plugin Implementation."""

    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        # Save reference to the QGIS interface
        self.iface = iface
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)

[...]

Nous devons ajouter les lignes de code définissant notre plugin comme un map tool. Ainsi l’outil Où suis-je ?, quand cliqué, deviendra l’outil de carte actif et manipulera les clics sur la carte. Il restera actif tant qu’un autre outil de carte (zoom, pan, etc) n’est pas sélectionné.

Ajouter l’import suivant: from qgis.gui import QgsMapToolEmitPoint Cette classe implémente un outil de carte qui émet un point contenant des coordonnées.

Nous devons aussi modifier la méthode init pour récupérer le canvas courant et le lier à notre outil d’émission de point.

## Récupération du canvas courant
self.canvas = self.iface.mapCanvas()

## Création d'un outil émettant un point au clic
self.point_tool = QgsMapToolEmitPoint(self.canvas)

Pour le moment, notre fichier Python ressemble à cela:

from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction
from qgis.gui import QgsMapToolEmitPoint

# Initialize Qt resources from file resources.py
from .resources import *
# Import the code for the dialog
from .ousuisje_dialog import OuSuisJeDialog
import os.path


class OuSuisJe:
    """QGIS Plugin Implementation."""

    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        # Save reference to the QGIS interface
        self.iface = iface
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)
        
        # Récupération du canvas courant
        self.canvas = self.iface.mapCanvas()
        # Création de l'outil émettant un point au clic
        self.point_tool = QgsMapToolEmitPoint(self.canvas)

Nous devons aussi récupérer un signal quand le canvas est cliqué. Modifiez la méthode initGui comme suit:

    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""

        icon_path = ':/plugins/ousuisje/icon.png'
        self.add_action(
            icon_path,
            text=self.tr(u'Où suis-je ?'),
            callback=self.run,
            parent=self.iface.mainWindow())

        # will be set False in run()
        self.first_start = True
        # connecte le signal que le canvas a été cliqué
        self.point_tool.canvasClicked.connect(self.display_point)

Ensuite, modifions la méthode Run pour que Où suis-je devienne l’outil de carte. Pour cela, videz-la de son contenu et insérez les lignes suivantes.

    def run(self):
        """Run method that performs all the real work"""
        
        if self.first_start == True:
            self.first_start = False
            # Creer la boite de dialogue
            self.dlg = OuSuisJeDialog()
            # connecte le signal que le canvas a été cliqué
            self.point_tool.canvasClicked.connect(self.display_point)
        
        # Défini ousuisje comme étant l'outil de carte actif
        self.canvas.setMapTool(self.point_tool)

Nous avons maintenant besoin qu’une action soit réalisée au moment du clic et que les coordonnées du point soit calculée. Ajouter la méthode display_point à la fin de la classe.

    def display_point(self, point, button):
        # Affiche les coordonnées du clic
        
        self.dlg.hide() # cache la boite de dialogue
        coords = "{}, {}".format(point.x(), point.y()) ## formatage des coordonnées
        self.dlg.lineEdit.setText(coords) # affichage des coordonnées
        
        self.dlg.show() # affiche de nouveau la boite de dialogue

Vous remarquez qu’il est fait référence à dlg. Il s’agit de la boite de dialogue. Pour récupérer sa référence, ajouter cette ligne après les lignes de traduction.

        # Création de la boite de dialogue (après les traductions)
        self.dlg = OuSuisJeDialog()

Copiez le plugin dans le répertoire de QGIS et rechargez-le avec Plugin Reloader. Puis cliquez n’importe où sur la carte.

Plugin Où suis-je ?

3.3 Exercices

3.3.1 Plugin Où-suis-je ?

  • Changer l’icone pour une icone mieux adaptée (pensez à recompiler les ressources)
  • Afficher les coordonnées avec seulement 3 décimales
  • Créez un bouton qui copie les coordonnées dans le presse-papier quand il est cliqué. (Voir classe QClipboard dans la documentation de Qt).

3.3.2 Plugin Proximité

Transformer proximite.py en plugin, fonctionnalités attendues:

  • icone personnalisée
  • charger une couche de point d’intérêt
  • charger une couche de superposition
  • saisir une distance X
  • trouver les éléments de la couche de superposition à distance X des points d’intérêts de l’autre couche
  • la couche de résultat doit être chargée dans le projet à l’issue du calcul
  • sélectionner un chemin vers un fichier de sauvegarde (si vide, la couche sera conservée en mémoire)
  • gérer les erreurs de saisie de la distance (conversion , -> ‘.’, génération d’un message si caractère non valide)

Exemple d’interface pour le plugin Proximité

Pour l’interface, le Qt Designer fournit avec QGIS offre des outils dédiés à ce dernier, pensez à les explorer !

3.3.3 Plugin Lieux propices

  • Créer un plugin Lieux propices à partir des consignes et du code de l’exercice 2.2.8