
C’est une scène qui revient à chaque formation Python. Un stagiaire me montre son écran : 47 couches chargées une par une, à la main, tous les lundis matin. Je lui montre 15 lignes de PyQGIS. Silence dans la salle, puis la question que j’adore : “Mais… on peut automatiser quoi d’autre, comme ça ?”
À peu près tout ce qui vous fait perdre du temps, en fait.
Cette page rassemble les 10 scripts de ma série d’août : 10 automatisations PyQGIS à copier-coller dans la console Python de QGIS. Je les ai tous testés sur un même cas d’étude, réaliste et complet : l’identification de zones d’implantation photovoltaïque autour de Carcassonne. Parcelles candidates, postes sources, zones d’exclusion, la totale.
Gardez cette page dans vos favoris : elle est faite pour être rouverte un lundi matin, le jour où on vous livrera un dossier de 47 fichiers.
Tous ces scripts se lancent dans la console Python de QGIS (Ctrl+Alt+P, ou Cmd+Alt+P sur Mac), directement ou via son éditeur de scripts. Les lignes marquées “a adapter” attendent vos chemins et vos noms de champs. Pour les dossiers de sortie, j’utilise os.path.expanduser("~/...") : cette écriture pointe vers votre dossier personnel et fonctionne telle quelle sous Windows, macOS et Linux, sans les soucis de droits d’écriture des dossiers synchronisés (iCloud, OneDrive et compagnie, qui bloquent parfois les écritures des applications). Enfin, certains scripts modifient vos données : travaillez toujours sur une copie la première fois.
Une question qui revient souvent : “Ai-je vraiment besoin de tous ces imports ?” Dans la console Python de QGIS, pas toujours : l’environnement précharge déjà la plupart des objets, et QgsProject passe souvent sans rien importer. Les scripts de cette page gardent pourtant leurs imports, pour trois raisons. Ils fonctionnent partout : console, éditeur de scripts, plugin ou exécution externe. Ils ne coûtent que trois lignes. Et surtout, ils vous montrent d’où vient chaque objet que vous manipulez : quand vous voudrez aller plus loin dans la documentation PyQGIS, vous saurez exactement où chercher. Trois lignes qui transforment un copier-coller en apprentissage.
On vous livre un dossier de fichiers en vrac : shapefiles, GeoPackage, GeoJSON, rasters. Le réflexe classique coûte 20 minutes de glisser-déposer. Celui-ci coûte 10 secondes : le script parcourt le dossier, charge tout ce qui est valide et range le résultat dans un groupe dédié.
import os
from qgis.core import QgsProject, QgsVectorLayer, QgsRasterLayer
dossier = r"C:/mes_donnees" # a adapter
ext_vecteur = ('.shp', '.gpkg', '.geojson')
ext_raster = ('.tif', '.tiff')
racine = QgsProject.instance().layerTreeRoot()
groupe = racine.insertGroup(0, os.path.basename(dossier))
for fichier in sorted(os.listdir(dossier)):
chemin = os.path.join(dossier, fichier)
nom = os.path.splitext(fichier)[0]
if fichier.lower().endswith(ext_vecteur):
couche = QgsVectorLayer(chemin, nom, "ogr")
elif fichier.lower().endswith(ext_raster):
couche = QgsRasterLayer(chemin)
else:
continue
if couche.isValid():
QgsProject.instance().addMapLayer(couche, False)
groupe.addLayer(couche)
else:
print(f"Ignore (invalide) : {fichier}")
Du Lambert 93, du WGS 84, un vieux Lambert II étendu qui traîne : les projets qui mélangent les SCR produisent des mesures fausses et des géotraitements qui hurlent. Ce script parcourt toutes les couches vecteur du projet, reprojette celles qui ne sont pas en EPSG:2154 et enregistre des GeoPackage propres.
import os
import processing
from qgis.core import QgsProject, QgsVectorLayer
epsg_cible = "EPSG:2154"
dossier_sortie = os.path.expanduser("~/sortie_2154") # a adapter
os.makedirs(dossier_sortie, exist_ok=True)
for couche in list(QgsProject.instance().mapLayers().values()):
if not isinstance(couche, QgsVectorLayer):
continue
if couche.crs().authid() == epsg_cible:
continue
sortie = os.path.join(dossier_sortie, couche.name() + ".gpkg")
processing.run("native:reprojectlayer", {
"INPUT": couche,
"TARGET_CRS": epsg_cible,
"OUTPUT": sortie
})
nouvelle = QgsVectorLayer(sortie, couche.name() + " (2154)", "ogr")
QgsProject.instance().addMapLayer(nouvelle)
print(f"Reprojete : {couche.name()}")
OBJECTID, Shape_Leng, Shape_Area : ces champs vous suivent de couche en couche sans que personne ne les utilise. Le script les supprime sur toutes les couches du projet où ils existent, et vous dit exactement ce qu’il a nettoyé. Attention, c’est le script destructif de la page : il modifie les fichiers sources, testez sur une copie.
from qgis.core import QgsProject, QgsVectorLayer
champs_a_supprimer = ["OBJECTID", "Shape_Leng", "Shape_Area"] # a adapter
for couche in QgsProject.instance().mapLayers().values():
if not isinstance(couche, QgsVectorLayer):
continue
index = [couche.fields().indexOf(n) for n in champs_a_supprimer]
index = [i for i in index if i >= 0]
if index:
couche.startEditing()
couche.deleteAttributes(index)
couche.commitChanges()
print(f"{couche.name()} : {len(index)} champ(s) supprime(s)")
L’IA est un amplificateur. Un géomaticien qui maîtrise ses scripts devient redoutable. Un géomaticien qui les survole devient dangereux, en plus rapide.
Un projet, 23 shapefiles, 6 dossiers : on a tous hérité de ce projet-là. Ce script empaquette toutes les couches vecteur du projet dans un GeoPackage unique, styles inclus. Un seul fichier à archiver, à envoyer, à versionner. Stylez vos couches avant de le lancer : les styles voyagent avec.
import os
import processing
from qgis.core import QgsProject, QgsVectorLayer
couches = [c for c in QgsProject.instance().mapLayers().values()
if isinstance(c, QgsVectorLayer)]
processing.run("native:package", {
"LAYERS": couches,
"OUTPUT": os.path.expanduser("~/projet_consolide.gpkg"), # a adapter
"OVERWRITE": True,
"SAVE_STYLES": True
})
print(f"{len(couches)} couches consolidees dans un seul GeoPackage")
Une couche, 16 communes, une livraison attendue commune par commune : à la main, l’après-midi y passe. Ce script génère un fichier par valeur du champ de votre choix, nommé automatiquement. Même recette pour 101 départements, des clients ou des types d’équipements. Sélectionnez la couche dans le panneau avant de lancer.
import os
import processing
from qgis.utils import iface
couche = iface.activeLayer()
champ = "nom" # a adapter
processing.run("native:splitvectorlayer", {
"INPUT": couche,
"FIELD": champ,
"FILE_TYPE": 0, # 0 = GeoPackage
"OUTPUT": os.path.expanduser("~/decoupage") # a adapter
})
print("Decoupage termine, un fichier par valeur du champ")
Votre géotraitement plante “sans raison” ? La raison a un nom : géométries invalides. Polygones papillons, auto-intersections, anneaux mal fermés, invisibles à l’œil nu et mortels pour vos analyses. Ce script scanne la couche active, liste les entités fautives et génère une version réparée en mémoire (exportez-la d’un clic droit si elle vous convient). À lancer avant chaque grosse analyse.
import processing
from qgis.core import QgsProject
from qgis.utils import iface
couche = iface.activeLayer()
invalides = []
for ent in couche.getFeatures():
g = ent.geometry()
if g.isEmpty() or not g.isGeosValid():
invalides.append(ent.id())
print(f"{len(invalides)} geometrie(s) invalide(s)")
print(f"Identifiants : {invalides[:20]}")
if invalides:
resultat = processing.run("native:fixgeometries", {
"INPUT": couche,
"OUTPUT": "memory:version_reparee"
})
QgsProject.instance().addMapLayer(resultat["OUTPUT"])
print("Couche reparee ajoutee au projet")
Votre chef ne veut pas une carte, il veut un tableau. Ce script transforme la couche active en rapport chiffré : effectifs, totaux, moyennes, mini et maxi par catégorie, exportés en CSV prêt à joindre à un mail. Le point-virgule en séparateur garantit l’ouverture propre dans Excel en français.
import csv
import os
from collections import defaultdict
from qgis.core import NULL
from qgis.utils import iface
couche = iface.activeLayer()
champ_categorie = "type" # a adapter
champ_valeur = "surface_ha" # a adapter
fichier_sortie = os.path.expanduser("~/rapport.csv") # a adapter
stats = defaultdict(list)
for ent in couche.getFeatures():
val = ent[champ_valeur]
if val != NULL:
stats[str(ent[champ_categorie])].append(float(val))
with open(fichier_sortie, "w", newline="", encoding="utf-8") as f:
w = csv.writer(f, delimiter=";")
w.writerow(["categorie", "nb", "total", "moyenne", "mini", "maxi"])
for cat, valeurs in sorted(stats.items()):
w.writerow([
cat, len(valeurs),
round(sum(valeurs), 2),
round(sum(valeurs) / len(valeurs), 2),
round(min(valeurs), 2),
round(max(valeurs), 2)
])
print(f"Rapport ecrit : {fichier_sortie}")
Charger 30 tables PostGIS une par une, c’est un bizutage. Ce script se connecte à votre base, détecte les tables spatiales d’un schéma et les charge toutes dans le projet. Si vos données sérieuses vivent en base (et elles devraient), il devient votre porte d’entrée quotidienne.
from qgis.core import (QgsDataSourceUri, QgsVectorLayer,
QgsProject, QgsProviderRegistry)
uri = QgsDataSourceUri()
uri.setConnection("localhost", "5432", "ma_base", "utilisateur", "mot_de_passe") # a adapter
schema = "public" # a adapter
md = QgsProviderRegistry.instance().providerMetadata("postgres")
conn = md.createConnection(uri.uri(False), {})
nb = 0
for table in conn.tables(schema):
if not table.geometryColumn():
continue
uri.setDataSource(schema, table.tableName(), table.geometryColumn())
couche = QgsVectorLayer(uri.uri(False), table.tableName(), "postgres")
if couche.isValid():
QgsProject.instance().addMapLayer(couche)
nb += 1
print(f"{nb} table(s) spatiale(s) chargee(s) depuis le schema {schema}")
Deux remarques de terrain. D’abord, si psql fonctionne dans votre terminal mais que QGIS refuse la connexion : c’est normal, le terminal passe par un canal local sans mot de passe alors que QGIS passe par le réseau, qui en exige un (un ALTER USER ... WITH PASSWORD règle la question). Ensuite, la sécurité : le mot de passe en clair dans le script, c’est acceptable pour apprendre sur votre machine, jamais en production. Préférez alors un service PostgreSQL (pg_service.conf) ou la connexion enregistrée de QGIS, et ne partagez jamais un script contenant un mot de passe.
Le duo gagnant de la mise en série : un atlas configuré dans votre mise en page (couche de couverture, expression de nom de fichier) et une quinzaine de lignes de Python qui exportent chaque page en PNG nommé automatiquement. Seize cartes communales pendant la pause café, deux cents demain avec le même script.
import os
from qgis.core import QgsProject, QgsLayoutExporter
nom_mise_en_page = "carte_commune" # a adapter
dossier = os.path.expanduser("~/cartes") # a adapter
os.makedirs(dossier, exist_ok=True)
layout = QgsProject.instance().layoutManager().layoutByName(nom_mise_en_page)
atlas = layout.atlas()
atlas.beginRender()
exporteur = QgsLayoutExporter(layout)
for i in range(atlas.count()):
atlas.seekTo(i)
layout.refresh()
chemin = os.path.join(dossier, f"{atlas.currentFilename()}.png")
exporteur.exportToImage(chemin, QgsLayoutExporter.ImageExportSettings())
print(f"Exporte : {chemin}")
atlas.endRender()
print("Export termine")
Le dernier script ne se lance pas dans la console : il s’enregistre via Boîte à outils > Créer un nouveau script, et devient un véritable outil QGIS avec son interface et ses paramètres. Utilisable par vos collègues qui ne liront jamais une ligne de Python, partageable dans toute votre organisation. C’est le vrai passage de niveau : de celui qui bricole à celui qui outille son service.
from qgis.processing import alg
from qgis import processing
@alg(name="tampon_dissous", label="Tampon dissous en un clic",
group="mes_outils", group_label="Mes outils")
@alg.input(type=alg.SOURCE, name="INPUT", label="Couche d'entree")
@alg.input(type=alg.DISTANCE, name="DISTANCE", label="Distance", default=100)
@alg.input(type=alg.SINK, name="OUTPUT", label="Couche de sortie")
def tampon_dissous(instance, parameters, context, feedback, inputs):
"""Cree un tampon dissous en un clic, pret a partager."""
resultat = processing.run("native:buffer", {
"INPUT": parameters["INPUT"],
"DISTANCE": parameters["DISTANCE"],
"DISSOLVE": True,
"SEGMENTS": 25,
"END_CAP_STYLE": 0,
"JOIN_STYLE": 0,
"MITER_LIMIT": 2,
"OUTPUT": parameters["OUTPUT"]
}, context=context, feedback=feedback, is_child_algorithm=True)
return {"OUTPUT": resultat["OUTPUT"]}
Dix scripts, dix corvées éliminées. Mais le vrai déclic n’est pas dans le copier-coller : il est dans le réflexe. La prochaine fois qu’une tâche QGIS vous prend plus de 10 minutes de clics répétitifs, posez-vous LA question : “Un script ferait ça en combien de secondes ?” C’est ce réflexe qui sépare celui qui subit ses données de celui qui outille son service. Et c’est exactement par là que passe la suite… dont on reparle très vite.
Rejoignez la communauté des Géomagiciens : la première communauté IA × Géomatique francophone. Formation, workshops en petit groupe, et 100 prompts SIG testés pour commencer dès aujourd'hui.