Quand l’intelligence artificielle rencontre QGIS : un assistant LLM pour accélérer vos analyses spatiales (1)


Introduction : replacer le contexte

Dans le monde de la géomatique, les outils SIG comme QGIS permettent de traiter, visualiser et analyser une quantité considérable de données spatiales.
Mais face à la complexité croissante des analyses, des expressions ou des scripts Python, même les utilisateurs expérimentés se heurtent parfois à un mur : quelle commande utiliser ? quel algorithme de traitement choisir ? comment automatiser cette tâche ?

C’est précisément là qu’intervient l’intelligence artificielle, et plus particulièrement les modèles de langage — ou LLM (Large Language Models).



Qu’est-ce qu’un LLM ?

Un LLM est un modèle d’intelligence artificielle entraîné sur des milliards de mots pour comprendre et générer du texte humain.
Il peut rédiger des explications, corriger du code, traduire, interpréter des erreurs, ou encore rédiger un script complet à partir d’une simple demande en langage naturel.

Concrètement, un LLM ne “sait” rien par lui-même : il prédit le mot ou la phrase la plus probable en fonction du contexte et de ce qu’il a appris.
Mais appliqué à un domaine spécialisé — comme la géomatique — il devient un formidable assistant, capable de :

  • expliquer les concepts de QGIS ou de Python,
  • générer des scripts PyQGIS complets,
  • déboguer ou corriger des erreurs,
  • proposer des workflows de traitement,
  • traduire un besoin métier en automatisation concrète.


Pourquoi l’intégrer à QGIS ?

QGIS est une plateforme libre et puissante, mais son écosystème peut parfois intimider :

  • les expressions de calculs attributaires sont très riches mais peu connues ;
  • les algorithmes du “Processing Toolbox” sont nombreux ;
  • et l’écriture de scripts PyQGIS requiert de bien connaître les classes, méthodes et imports.

En intégrant un LLM directement dans l’interface QGIS, on offre une interface conversationnelle entre l’utilisateur et l’environnement SIG :

Sélectionne toutes les parcelles situées à moins de 100 mètres des rivières »
devient
un script PyQGIS ou une expression prête à exécuter.

Autrement dit, le LLM devient un assistant pédagogique et productif, à la fois traducteur, formateur et automate de code.


Une adaptation pensée pour QGIS

Le plugin que je développe, QGIS LLM Assistant, vise justement à rendre cette interaction fluide et adaptée au monde géospatial.

Il ne s’agit pas d’un simple chatbot intégré, mais d’un outil :

  • qui comprend le contexte de QGIS (projet actif, couches chargées, algorithmes disponibles) ;
  • qui génère du code PyQGIS exécutable directement dans QGIS ;
  • qui corrige automatiquement les erreurs fréquentes (imports manquants, fonctions obsolètes, différences de version) ;
  • et qui surveille les ressources CPU/GPU pour éviter les blocages.

Ce plugin illustre une idée simple :
l’intelligence artificielle ne remplace pas le géomaticien, elle amplifie ses capacités.


Vers une nouvelle manière d’apprendre et de travailler

Pour les étudiants, formateurs ou professionnels, un assistant LLM intégré à QGIS ouvre de nouvelles perspectives :

  • apprendre par l’exemple, en générant et en exécutant du code commenté ;
  • comprendre les erreurs sans quitter l’environnement QGIS ;
  • créer des scripts personnalisés plus rapidement ;
  • documenter et partager les workflows en langage naturel.

C’est une évolution comparable à ce que furent les calculateurs automatiques dans Excel : un outil qui démultiplie la productivité et rend le travail plus exploratoire, plus intuitif, plus collaboratif.


Conclusion : un dialogue entre humain et machine

En associant la puissance analytique de QGIS à l’intelligence linguistique d’un LLM, on ouvre une nouvelle ère d’interaction entre l’utilisateur et son logiciel :
une ère où l’on explique son intention plutôt que de mémoriser des commandes.

L’assistant LLM pour QGIS est une première étape vers cette vision : un environnement SIG plus intelligent, plus accessible, et plus humain.


Installation et préparation de l’environnement

Avant d’utiliser le plugin LLM Assistant dans QGIS, il est nécessaire de préparer un environnement adapté pour exécuter et tester les modèles de langage. Cette étape garantit que les scripts générés par le LLM s’exécutent correctement et en toute sécurité.

1. Créer un environnement Python dédié

Pour éviter les conflits avec les dépendances de QGIS ou d’autres projets Python, il est fortement recommandé d’utiliser un environnement virtuel (venv) :

# Créer l’environnement virtuel
python -m venv venv

# Activer l’environnement
# Windows
venv\Scripts\activate
# macOS / Linux
source venv/bin/activate

# Mettre à jour pip
pip install --upgrade pip

L’avantage d’un venv est de contrôler exactement les packages installés et d’isoler les bibliothèques nécessaires pour le LLM et le serveur Flask.


2. Installer les dépendances Python

Ensuite, il faut installer les bibliothèques principales :

pip install requests flask langchain_ollama psutil gputil

  • requests : pour communiquer avec le serveur Flask depuis le plugin QGIS.
  • flask : pour créer le serveur local qui interagit avec le modèle LLM.
  • langchain_ollama : interface avec les modèles Ollama.
  • psutil et GPUtil : pour surveiller la RAM CPU et la VRAM GPU et choisir le modèle adapté automatiquement.


3. Installer et vérifier les modèles Ollama

Avant de lancer le serveur, il faut s’assurer que les modèles LLM souhaités sont installés localement. Par exemple :

environnement virtuel

# Liste des modèles disponibles
ollama list

# Exemple de modèles installés :
# gemma:2b-instruct-q4_K_M
# gemma:2b
# mistral:latest
# llama3:latest

Chaque modèle a ses exigences en CPU et GPU, que le plugin utilisera pour sélectionner automatiquement le modèle le plus adapté selon la machine.


4. Préparer le serveur Flask (server.py)

Le plugin QGIS communique avec un serveur local qui exécute le LLM. Le fichier server.py contient :

  • Le choix automatique du modèle selon la mémoire disponible.
  • Le routage de la requête depuis le plugin via l’endpoint /analyze.
  • Le renvoi d’un JSON contenant :

    • la réponse textuelle du LLM,
    • le modèle utilisé,
    • la mémoire CPU et GPU disponible,
    • le temps de traitement.

server.py

from flask import Flask, request, jsonify
from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate
import psutil
import GPUtil
import subprocess
import time

app = Flask(__name__)

# --- Mémoire et modèles ---
MODEL_REQUIREMENTS = {
    "gemma:2b-instruct-q4_K_M": {"cpu": 8, "gpu": 6},
    "gemma:2b": {"cpu": 6, "gpu": 4},
    "mistral:latest": {"cpu": 8, "gpu": 4},
    "llama3:latest": {"cpu": 4, "gpu": 2}
}

def get_available_memory_gb():
    """Retourne la RAM CPU disponible en Go"""
    return psutil.virtual_memory().available / (1024 ** 3)

def get_available_vram_gb():
    """Retourne la VRAM GPU disponible en Go (0 si pas de GPU)"""
    gpus = GPUtil.getGPUs()
    if not gpus:
        return 0
    return max(gpu.memoryFree for gpu in gpus) / 1024  # MB → GB

def get_installed_models():
    """Retourne la liste des modèles Ollama installés."""
    try:
        result = subprocess.run(["ollama", "list"], capture_output=True, text=True, check=True)
        lines = result.stdout.splitlines()[1:]  # ignorer l’en-tête
        models = []
        for line in lines:
            parts = line.split()
            if parts:
                models.append(parts[0])
        return models
    except Exception:
        return []

def choose_model():
    """Choisit le meilleur modèle installé selon RAM CPU et GPU."""
    installed_models = get_installed_models()
    avail_cpu = get_available_memory_gb()
    avail_gpu = get_available_vram_gb()

    # Filtrer les modèles réellement installés
    candidates = {name: req for name, req in MODEL_REQUIREMENTS.items() if name in installed_models}

    if not candidates:
        return None  # aucun modèle installé trouvé

    # Si pas de GPU, ne proposer que des modèles CPU-friendly
    if avail_gpu == 0:
        sorted_models = sorted(candidates.items(), key=lambda x: x[1]["cpu"], reverse=True)
        for model_name, req in sorted_models:
            if avail_cpu >= req["cpu"]:
                return model_name
        return min(candidates.keys(), key=lambda x: candidates[x]["cpu"])  # fallback

    # Si GPU présent, tri décroissant GPU puis CPU
    sorted_models = sorted(candidates.items(), key=lambda x: (x[1]["gpu"], x[1]["cpu"]), reverse=True)
    for model_name, req in sorted_models:
        if avail_cpu >= req["cpu"] and avail_gpu >= req["gpu"]:
            return model_name

    return min(candidates.keys(), key=lambda x: candidates[x]["cpu"])  # fallback

# --- Endpoint Flask ---
@app.route("/analyze", methods=["POST"])
def analyze():
    try:
        start_time = time.time()
        data = request.get_json()
        prompt_text = data.get("prompt", "")

        # Choix automatique du modèle
        selected_model = choose_model()
        if not selected_model:
            return jsonify({"error": "Aucun modèle Ollama installé trouvé."}), 500

        model = ChatOllama(model=selected_model)

        # Préparer le prompt
        chat_prompt = ChatPromptTemplate.from_template(
            "You are a marine AI assistant. {prompt}"
        )
        messages = chat_prompt.format_messages(prompt=prompt_text)

        # ⚡ Utilisation correcte : invoke()
        response_msg = model.invoke(messages)  # renvoie AIMessage
        text_response = response_msg.content  # extraire le texte pour JSON

        elapsed = round(time.time() - start_time, 2)

        return jsonify({
            "response": text_response,
            "model_used": selected_model,
            "available_cpu_gb": round(get_available_memory_gb(), 1),
            "available_gpu_gb": round(get_available_vram_gb(), 1),
            "processing_time_s": elapsed
        })

    except Exception as e:
        return jsonify({"error": str(e)}), 500

if __name__ == "__main__":
    app.run(debug=False)

Exemple minimal pour lancer le serveur :

python server.py

Le serveur écoutera par défaut sur http://127.0.0.1:5000.


5. Installation du plugin QGIS LLM Assistant

Pour commencer à utiliser le plugin :

  1. Téléchargez le plugin
    Téléchargez le fichier ZIP du plugin depuis le lien suivant : Télécharger le plugin
  2. Installer depuis QGIS

    • Ouvrez QGIS.
    • Allez dans le menu Extensions → Installer depuis un fichier ZIP.
    • Sélectionnez le fichier ZIP téléchargé et cliquez sur Installer.

  3. Activer le plugin

    • Une fois installé, le plugin apparaît dans le menu Extensions ou dans la barre d’outils.
    • Cliquez sur LLM Assistant pour ouvrir le dock.

Astuce : pour les utilisateurs avancés, vous pouvez aussi décompresser le ZIP dans le dossier ~/.qgis3/python/plugins/ puis activer le plugin via Extensions → Gestionnaire d’extensions.


6. Configurer le plugin QGIS

Dans QGIS :

  1. Après avoir Installé le plugin LLM Assistant:

    • Accéder aux paramètres et indiquer l’URL du serveur Flask (http://127.0.0.1:5000).
    • Vérifier que le plugin peut récupérer les informations sur les couches chargées dans le projet.


Cette préparation garantit :

  • Que le plugin peut communiquer avec le LLM.
  • Que l’exécution des scripts générés est sécurisée et isolée.
  • Que le choix du modèle est automatique et adapté à votre configuration.

Tester le plugin et poser sa première question

Une fois l’environnement préparé et le serveur Flask lancé, il est temps de tester le plugin LLM Assistant dans QGIS et de voir comment il réagit aux requêtes. Cette étape montre concrètement la puissance de l’auto-fix et de la validation progressive du code.


1. Ouvrir le plugin

Dans QGIS :

  1. Aller dans le menu Plugins → LLM Assistant → LLM Assistant.
  2. Le panneau latéral du plugin s’ouvre (DockWidget) avec :

    • un champ de saisie pour votre question,
    • un bouton Send,
    • une zone pour afficher la réponse du LLM,
    • une zone pour afficher le code PyQGIS suggéré,
    • un bouton pour exécuter le code et un pour copier le code.


2. Préparer une question

Le plugin prend en compte les couches actuellement chargées dans le projet. Il est donc recommandé de charger quelques couches test :

  • Exemple : un raster multispectral (Palmar_MS.tif) et un shapefile de points (points_coral.shp).

Ensuite, dans le champ de saisie, on peut poser une question :

Calcule l’index corallien à partir du raster Palmar_MS et des points de référence points_coral.shp


3. Envoyer la requête

  • Cliquer sur Send.
  • Le plugin envoie la requête au serveur Flask.
  • La réponse du LLM apparaît dans la zone “Assistant Response”.

Exemple de retour :

Model used: gemma:2b
CPU available: 5.4 GB
GPU available: 0 GB
Processing time: 15.4 s

Response:
import qgis
import numpy as np

raster_layer = QgsRasterLayer("Palmar_MS.tif", "RasterLayer")
vector_layer = QgsVectorLayer("points_coral.shp", "VectorLayer", "ogr")
coral_index = (raster_layer.band(5) - raster_layer.band(3)) / (raster_layer.band(5) + raster_layer.band(3))

Le plugin affiche également le code PyQGIS suggéré dans la zone inférieure, prêt à être exécuté ou copié.


4. L’auto-fix en action

Si le LLM génère un code incomplet ou incompatible :

  • Le plugin applique des corrections automatiques intelligentes, comme :

    • remplacer qgis.open_raster() par QgsRasterLayer(),
    • corriger iface.activeProject() en QgsProject.instance(),
    • remplacer evaluateExpression() par sample().

  • Chaque ligne du code est surlignée :

    • vert = exécutée avec succès,
    • jaune = corrigée automatiquement,
    • rouge = bloquée (erreur non corrigible).

Cette approche permet de tester des scripts générés par le LLM sans craindre de crash QGIS.


5. Exécuter et vérifier

  • Cliquer sur ▶ Run Code pour lancer l’exécution.
  • Le code corrigé est mis à jour dans la zone “Suggested PyQGIS Code”.
  • Les messages d’erreur et le nombre de lignes exécutées ou corrigées sont affichés dans la zone Assistant Response.

Grâce à ce mécanisme :

  • Les utilisateurs peuvent expérimenter librement avec le code généré.
  • Les erreurs fréquentes sont traitées automatiquement, tout en restant transparentes.
  • Le flux de travail devient itératif et pédagogique : on voit ce que le LLM propose et comment le plugin le rend exécutable.


Exemples d’utilisation du QGIS LLM Assistant

1. Obtenir un résumé rapide du projet QGIS

Prompt :

“Donne-moi un résumé du contenu de mon projet QGIS actuel.”

Réponse typique :

“Voici le résultat :

Deux couches vectorielles (routes et seuil1_poly) avec respectivement 73965 features, 338 champs et CRS EPSG:2154, ainsi que 300 features, 2 champs et CRS EPSG:2154.”

Vous avez 5 couches (layers) dans votre projet :

Une couche vectorielle ( CLC90_RCOR_RGF ) avec 4782 features, 4 champs et une référence spatiale (CRS) IGNF:LAMB93.

Quatre couches raster (corine, corse, fzy_corine, fzy_corse) avec chacune une bande (1 band) et des CRS différents : IGNF:LAMB93 pour les deux premières et EPSG:2154 pour les deux dernières.

Utilité :
Parfait pour vérifier la cohérence du projet avant d’entamer une analyse.


2. Calculer un indice spectral (NDVI, NDBI, Coral Index, etc.)

Prompt :

“Calcule le NDVI à partir de la couche Sentinel2.tif et enregistre-le comme NDVI.tif.”

Code suggéré :

from qgis.core import QgsRasterLayer
from qgis.analysis import QgsRasterCalculator, QgsRasterCalculatorEntry

raster = QgsRasterLayer("C:\projetflou\Palmar_MS.tif", "Palmar_MS")
entries = []
red = QgsRasterCalculatorEntry()
red.ref = "red@3"
red.raster = raster
red.bandNumber = 3
entries.append(red)
nir = QgsRasterCalculatorEntry()
nir.ref = "nir@4"
nir.raster = raster
nir.bandNumber = 4
entries.append(nir)
calc = QgsRasterCalculator(
    "(nir@4 - red@3) / (nir@4 + red@3)",
    "C:/data/NDVIB.tif",
    "GTiff",
    raster.extent(),
    raster.width(),
    raster.height(),
    entries
)
calc.processCalculation()

En cliquant sur « Run Code » on retrouve le tif résultant dans le répertoire C:/data

Utilité :
Gain de temps considérable pour ceux qui oublient la syntaxe exacte du calcul raster.


3. Extraire des statistiques zonales par polygone

Prompt :

“Donne-moi le code pour calculer la moyenne du NDVI dans chaque polygone de ma couche ‘zones_agricoles’.”

Réponse typique :

import processing
processing.run(
    "qgis:zonalstatistics",
    {
        'INPUT_RASTER': 'NDVI.tif',
        'RASTER_BAND': 1,
        'INPUT_VECTOR': 'zones_agricoles.shp',
        'COLUMN_PREFIX': 'NDVI_',
        'STATISTICS': [2]  # Moyenne
    }
)

Utilité :
Facilite la génération automatique de scripts de traitements batch.


4. Charger automatiquement une série de rasters

Prompt :

“Charge toutes les images GeoTIFF du dossier C:/projetflou/ dans mon projet.”

Code suggéré :

import os
from qgis.core import QgsRasterLayer, QgsProject

folder = "C:/projetflou"
for f in os.listdir(folder):
    if f.endswith(".tif"):
        path = os.path.join(folder, f)
        layer = QgsRasterLayer(path, os.path.basename(f))
        if layer.isValid():
            QgsProject.instance().addMapLayer(layer)

Utilité :
Parfait pour les analystes SIG qui manipulent de grands jeux d’images.


5. Créer un tampon autour des points d’intérêt

Prompt :

“Donne moi un code python qui crée un buffer de 200 mètres autour de ma couche panneaux.”

Code suggéré :

import processing
processing.run(
    "native:buffer",
    {
        'INPUT': 'c:/data/panneaux.shp',
        'DISTANCE': 200,
        'SEGMENTS': 5,
        'END_CAP_STYLE': 0,
        'JOIN_STYLE': 0,
        'DISSOLVE': False,
        'OUTPUT': 'c:/data/buffers_200m.shp'
    }
)

Utilité :
Simplifie les traitements classiques sans passer par les boîtes de dialogue.


6. Identifier les erreurs topologiques

Prompt :

“je voudrais un code Python qui vérifie les chevauchements dans la couche zones_agricoles”

Réponse typique :

import processing
processing.run("qgis:checkvalidity", {
    'INPUT_LAYER': 'C:/data/zones_agricoles.shp',
    'METHOD': 2,  # OGR
    'VALID_OUTPUT': 'C:/data/valid_parcelles.shp',
    'INVALID_OUTPUT': 'C:/data/invalid_parcelles.shp'
})

Utilité :
Gain de temps pour la validation géométrique d’un lot de données.


Limites actuelles et appel à retours d’expérience

Ce plugin n’est pas un outil « fini » au sens traditionnel du terme.
Il s’agit d’un prototype fonctionnel, fourni tel quel, dont l’objectif principal est d’explorer le potentiel d’interaction entre QGIS et un modèle de langage (LLM).

L’idée n’est pas encore d’offrir une solution de production, mais de tester des scénarios d’usage, d’évaluer la qualité des réponses générées et d’identifier les ajustements nécessaires pour un futur développement plus robuste.

Je publie donc ce plugin dans l’esprit du libre : pour favoriser la discussion, la contribution et le retour d’expérience des utilisateurs.
Vos remarques, suggestions ou signalements d’erreurs sont donc les bienvenus — ils aideront à orienter les prochaines évolutions.

Points à connaître et limites actuelles

  • Différents modèles, différents résultats : selon le modèle LLM utilisé (GPT-4, Mistral, Gemini, etc.), les réponses et corrections de code peuvent varier sensiblement. Certains modèles sont plus précis sur le code QGIS, d’autres sur le texte explicatif.
  • Exécution locale ou distante : le comportement dépend du mode d’accès au modèle (API, serveur local, ou instance privée). Des différences de latence et de compatibilité peuvent survenir.
  • Support partiel de l’environnement QGIS : certaines classes ou méthodes ne sont pas reconnues correctement par les modèles, notamment lorsqu’elles changent entre versions (ex. QGIS 3.28 → 3.44).
  • Erreur de contexte persistante : après plusieurs exécutions, le modèle peut réutiliser un code incorrect précédemment produit. Une réinitialisation du contexte ou une relance du plugin peut être nécessaire.
  • Pas de validation de sécurité : le plugin exécute le code Python proposé, mais il ne vérifie pas la sécurité de ce code. Il convient de rester prudent et de lire le code avant exécution.
  • Absence de supervision humaine : les corrections automatiques (« auto-fix ») sont expérimentales ; elles peuvent résoudre des erreurs simples mais aussi introduire de nouveaux bugs.


Vers une version communautaire

L’objectif à moyen terme est de faire évoluer ce plugin vers une version communautaire, développée et améliorée collectivement.
Plusieurs pistes sont déjà envisagées :

  • meilleure gestion du contexte QGIS pour éviter les erreurs de compatibilité,
  • intégration de plusieurs modèles de langage sélectionnables depuis l’interface,
  • sauvegarde des échanges et du code testé,
  • documentation collaborative des cas d’usage réussis.

Comme souvent dans l’univers open source, c’est l’usage réel et les contributions qui feront progresser l’outil.
Que vous soyez formateur, développeur, chercheur ou simple curieux de l’intelligence artificielle appliquée à la géomatique, vos retours ont une vraie valeur.


Conclusion générale

L’intelligence artificielle générative ouvre une nouvelle étape dans la relation entre l’utilisateur et QGIS : celle du dialogue assisté par le langage naturel.
Ce plugin expérimental n’est pas une solution aboutie, mais une porte entrouverte sur ce que pourraient devenir les outils géospatiaux de demain — des environnements interactifs où la connaissance métier, le code et la cartographie se rencontrent sans friction.

L’enjeu n’est pas seulement technique : il s’agit aussi de réinventer la manière d’apprendre, de transmettre et de collaborer autour du géospatial libre.
À ce titre, chaque essai, chaque retour, chaque amélioration contribuera à tracer la voie vers des usages plus intelligents, plus accessibles, et toujours ouverts.


Vous pouvez télécharger le plugin et partager vos retours directement sur le dépôt GitHub : https://github.com/SigEtTerritoires/qgis-llm-codeassistant. N’hésitez pas à signaler les bugs, proposer des améliorations ou contribuer au développement : vos retours permettront d’améliorer le plugin et de le rendre plus robuste pour la communauté QGIS.


Si cet article vous a intéressé et que vous pensez qu'il pourrait bénéficier à d'autres personnes, n'hésitez pas à le partager sur vos réseaux sociaux en utilisant les boutons ci-dessous. Votre partage est apprécié !

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *