4  Créer une extension (Plugin)

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

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

4.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 (pas forcément dans cet ordre) :

  • Compiler resources.py avec pyrcc5 (cette étape n’est à faire qu’une seule fois, à la création du plugin)
  • Éventuellement, 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’icône 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

4.2.2 Tester le plugin vierge

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 apparaît.

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.

4.2.3 Changer l’icône

Vous pouvez changer l’icône de votre extension simplement mais une procédure est à réaliser pour qu’elle soit visible dans la barre d’outils et dans les menus.

Astuce

Le chemin vers icon.png étant appelé dans plusieurs fichiers, il est préférable de changer le fichier par un autre portant le même nom plutôt que de modifier tous ces chemins. Privilégiez un fichier PNG de format carré (24x24 ou 32x32 pixels).

Pour l’affichage dans la barre d’outil se fasse correctement, il faut créer/recompiler le fichier resources.py Ce fichier resources.py est créé par pyrcc5 à partir du chemin contenu dans resources.qrc. pyrcc5 est un outil en ligne de commande qui est installé en même temps que QGIS, la façon de l’appeler varie selon votre système d’exploitation.

Astuce

Cette étape est fastidieuse mais n’est à réaliser qu’une fois, quand vous changez l’icône de votre extension.

4.2.3.1 Windows

Sous Windows, pyrcc5 est fourni avec le shell (ligne de commande) OSGeo4W. Le shell OSGeo4W est disponible depuis le menu Démarrer, puis dans le dossier de votre version de QGIS.

Lancement du shell OSGeo4W

Quelques éléments complémentaires sur GIS stackexchange.

4.2.3.1.1 Tester la disponibilité de pyrcc5

Pour vérifier que la commande pyrcc5 est disponible, lancer la commande suivante :

pyrcc5 -version

Le résultat de la commande devrait afficher quelque chose similaire à ceci :

Lancement du shell OSGeo4W

Si ce n’est pas le cas, vérifiez que vous êtes bien dans le shell OSGeo4W. En cas de doute, contactez le formateur le plus proche.

4.2.3.1.2 Compiler resources.py

Changer le chemin location qui pointe vers le dossier où vous avez stocké le code de votre plugin. Puis utilisez pyrcc5 pour compiler resources.py :

set "location=D:\Roelandt\PROJETS\InProgress\11_Enseignement\2024_carthageo\ousuisje"
pyrcc5 -o %location%\resources.py %location%\resources.qrc
Astuce

D:\Roelandt\PROJETS\InProgress\11_Enseignement\2024_carthageo est le chemin à changer.

4.2.3.2 Linux

Sous Linux, entrez la commande suivante dans un terminal depuis le répertoire de votre plugin:

pyrcc5 -o resources.py resources.qrc

Un fichier ressources_rc.py devrait être créé ou mis à jour.

4.2.3.3 MacOS

Pour MacOS, les choses sont plus complexes. pyrcc5 devrait être disponible dans la ligne de commande mais un bug empêche la compilation.

Pour corriger ce bug, exécutez les opérations suivantes:

  • Editer le script pyrcc5 (localisé dans /Applications/QGIS-LTR.app/Contents/MacOS/bin/pyrcc5) avec TextEdit,
  • Remplacer 'dirname $0/python3.9' par dirname $0/python3.9 (supprimez les apostrophes encadrant la commande)
  • Sauvegardez le fichier
  • Ouvrez l’application Terminal (plus d’informations sur le site d’Apple)
  • Compilez resources.py:
pyrcc5 -o resources.py resources.qrc
Astuce

Compléments d’information:

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

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

4.2.5.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 (à la fin de la méthode __init__())
        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 ?

4.3 Exercices

4.3.1 Plugin Où-suis-je ?

  • Changer l’icône 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).