Plugin QGIS d'intersection spatiale automatique pour l'analyse territoriale et la production de PDF multicouches.
1. Charger un projet QGIS
2. Sélectionner une parcelle
3. Cliquer sur "Réaliser l'intersection"
4. Exporter en PDF ou CSV
- ⚡ Quick Start
- 🎯 Présentation
- 👥 Contexte et besoin métier
- ✂️ Fonctionnalités
- 🌱 Prérequis
- 📦 Installation
- 🚀 Utilisation détaillée
- 🎨 Personnalisation des modèles QPT
- 🏗️ Architecture technique
- 🛠️ Développement
Sécateur est un plugin QGIS permettant d'automatiser des opérations d'intersection spatiale sur un grand nombre de couches géographiques.
Le plugin a été conçu pour répondre à des besoins d'instruction territoriale et réglementaire :
- identification automatique des zonages impactant une entitée cadastrale via le croisement rapide de données métiers ;
- génération de rapports exploitables via la production de PDF multicouches.
Le fonctionnement repose sur un principe simple :
- sélectionner une seule entité ;
- lancer l'intersection et générer automatiquement les couches résultats ;
- exporter les résultats au format PDF et/ou CSV.
Le plugin s'inspire directement des problématiques rencontrées lors de l'instruction d'application du droit des sols (ADS) en DDT, notamment dans le cadre de l'analyse :
- des Servitudes d'Utilité Publique (SUP),
- des zonages réglementaires,
- des risques,
- des contraintes environnementales,
- des documents d'urbanisme.
Dans de nombreux contextes métier, les instructeurs doivent consulter une grande quantité de couches thématiques afin de déterminer quels enjeux concernent une parcelle cadastrale donnée. Cette opération devient rapidement longue, répétitive et source d'erreurs lorsqu'elle est réalisée manuellement.
Le besoin fonctionnel identifié était donc :
- automatiser les intersections spatiales ;
- fiabiliser les analyses ;
- standardiser les exports ;
- produire des rapports cartographiques exploitables.
Le plugin reprend cette logique dans une approche plus modulaire et générique adaptée à QGIS 3.34+.
Les problématiques métier d'origine sont détaillées dans la notice DDT21 du plugin historique « Instruction_ADS ».
| Fonction | Description |
|---|---|
| ✂️ Intersection spatiale | Intersection de toutes les couches visibles |
| 🌍 Reprojection CRS | Harmonisation des projections |
| 🛠️ Correction géométries | Correction des géométries invalides |
| 📄 Export CSV | Export tabulaire des intersections |
| PDF multicouches | |
| 📚 Légende dynamique | Génération automatique |
| 🧱 WFS/WMS support | Compatible flux distants |
Le plugin crée automatiquement un groupe résultat : Résultats secateur et Objet cible afin d'organiser les données temporaires.
- un CSV par couche intersectée ;
- export des attributs ;
- format compatible tableurs et traitements externes.
- génération d'un PDF multicouches ;
- légende séparée ;
- prise en charge des basemaps ;
- export basé sur des modèles
.qpt.
Compatible avec :
- couches locales ;
- couches WFS/WMS ;
- couches métiers ;
- couches cadastrales ;
- orthophotos ;
- fonds raster.
- QGIS ≥ 3.34
Le plugin nécessite :
- un projet QGIS contenant des couches vectorielles ou rasteurs ;
- des couches métiers organisées dans le projet.
Les couches cadastrales peuvent provenir :
- du projet QGIS ;
- de flux WFS/wMS ;
- de la BD Parcellaire ;
- du plugin Cadreur ;
- d'autres sources IGN ou métiers.
Le plugin embarque certaines dépendances dans vendor/ afin de garantir un fonctionnement autonome (notamment pypdf). Cela augmente légèrement la taille du plugin mais évite toute installation manuelle.
- Télécharger le plugin en zip ;
- Ouvrir QGIS ;
- Aller dans :
Extensions → Installer/Gérer les extensions - Choisir :
Installer depuis un ZIP - Sélectionner l'archive ;
- Activer le plugin.
ln -s /chemin/vers/secateur \
~/.local/share/QGIS/QGIS3/profiles/default/python/plugins/secateurln -s /chemin/vers/secateur \
~/Library/Application\ Support/QGIS/QGIS3/profiles/default/python/plugins/secateurCréer un lien symbolique ou copier le dossier dans :
C:\Users\<user>\AppData\Roaming\QGIS\QGIS3\profiles\default\python\plugins\
Puis dans QGIS :
Extensions → Gérer/Installer les extensions → Sécateur → Activer
Le plugin fonctionne avec toutes les couches de QGIS.
[!WARNING]
Les exports utilisant des couches WFS/WMS ou des basemaps peuvent être significativement plus longs.
Dans certains cas :
- les temps d'export peuvent être multipliés par 10 ;
- QGIS peut sembler figé pendant certains traitements lourds.
Cela est normal, il faut requêter les couches.
Cliquer sur l'icône dans la barre d'outils QGIS :
Le panneau permet :
- le lancement des intersections ;
- le choix du fond cartographique ;
- la configuration des exports ;
- les exports PDF ;
- les exports CSV.
Le plugin fonctionne avec :
- une couche active ;
OU
- une seule entité sélectionnée.
Dans l'arbre des couches :
- cliquer sur une couche vectorielle ;
- la rendre active.
Utiliser l'outil QGIS :
Puis sélectionner une seule géométrie.
[!WARNING] Le plugin nécessite exactement 1 entité sélectionnée sinon l'exécution sera refusée.
Cliquer sur :
Réaliser l'intersection
Le plugin :
- détecte les couches visibles ;
- ignore les couches incompatibles ;
- reprojette les données ;
- corrige les géométries ;
- applique les intersections ;
- génère les couches résultats ;
- crée les groupes QGIS nécessaires.
Les résultats apparaissent dans :
Résultats secateur
[!WARNING] Le plugin fonctionne également avec les couches rasters, mais l'intersection est juste un filtre limité aux extents. Pour cela, cochez la case "Inclure les couches rasters".
Produit :
- un CSV par couche intersectée ;
- les attributs des objets intersectés.
Produit :
- un PDF multicouches ;
- une légende PDF séparée ;
- une mise en page basée sur des modèles QGIS
.qpt.
flowchart LR
A[Charger un projet]
--> B[Sélectionner une entité]
--> C[Lancer l'intersection spatiale]
--> D[Couches temporaires]
D --> E[Export CSV]
D --> F[Export PDF]
Le plugin produit des PDF multicouches pour une lecture et diffusion rapide.
[!NOTE] Fonctionnalité historique retirée : GeoPDF. Une version antérieure du plugin produisait aussi un GeoPDF (couches interactives, zoom, navigation cartographique dans le lecteur PDF). Cette fonctionnalité a été retirée (voir la branche archivée
archive/geopdf-export) et n'existe plus dans les versions actuelles du plugin. Les usages historiques sont décrits dans la documentation DDT21 du plugin « Instruction_ADS ».
Les légendes sont exportées séparément afin d'éviter :
- la surcharge graphique ;
- les crashs QGIS ;
- les limitations des layouts.
Le plugin :
- génère dynamiquement les légendes ;
- crée des pages adaptées ;
- fusionne les exports PDF.
Le plugin utilise deux modèles QGIS .qpt.
resources/report_page.qpt
resources/legend_layout.qpt
Dans QGIS (documentation) :
Projet → Gestionnaire de mises en page
Puis :
Nouvelle mise en page depuis un modèle
Ensuite :
- importer le
.qpt; - modifier les éléments souhaités ;
- sauvegarder le fichier au même emplacement.
Certains éléments doivent conserver leur ID.
| Élément | ID |
|---|---|
| Carte | Map 1 |
| Titre | title |
| Auteur | author |
| Date | date |
| Logo | logo |
| Élément | ID |
|---|---|
| Légende | legend |
secateur/
├── __init__.py
├── metadata.txt
├── plugin.py
├── compat.py
├── core/
│ ├── constants.py
│ ├── logger.py
│ ├── image_manager.py
│ ├── intersection/
│ │ ├── intersection_processing.py
│ │ ├── profiling.py
│ │ ├── intersection_results.py
│ │ ├── intersection_context.py
│ │ └── intersection_metrics.py
│ ├── export/
│ │ ├── csv/
│ │ │ └── export.py
│ │ └── pdf/
│ │ ├── common/
│ │ │ ├── models/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── metadata.py
│ │ │ │ └── pdf_export_options.py
│ │ │ ├── template_loader.py
│ │ │ ├── path_resolver.py
│ │ │ ├── pdf_export.py
│ │ │ ├── lifecycle/
│ │ │ │ ├── cleanup.py
│ │ │ │ └── refresh.py
│ │ │ ├── export/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── base_export_service.py
│ │ │ │ ├── base_export_config_factory.py
│ │ │ │ ├── collaborators.py
│ │ │ │ └── pdf_merger.py
│ │ │ └── layout/
│ │ │ ├── base_layout.py
│ │ │ ├── extent.py
│ │ │ ├── visibility.py
│ │ │ ├── metadata.py
│ │ │ └── metadata_items.py
│ │ ├── multi_pdf/
│ │ │ ├── __init__.py
│ │ │ ├── service.py
│ │ │ ├── config.py
│ │ │ ├── layout_factory.py
│ │ │ ├── items.py
│ │ │ ├── layout.py
│ │ │ └── page_builder.py
│ │ └── legend/
│ │ ├── __init__.py
│ │ ├── service.py
│ │ ├── config.py
│ │ ├── legend_tree.py
│ │ ├── pagination.py
│ │ ├── items.py
│ │ └── layout.py
│ └── utils/
│ ├── __init__.py
│ ├── feedback.py
│ ├── layers.py
│ ├── rendering.py
│ ├── visibility.py
│ ├── layer_resolver.py
│ ├── path.py
│ └── formatting.py
├── ui/
│ ├── panel.py
│ ├── service.py
│ ├── settings.py
│ └── widgets/
│ ├── basemap_combo.py
│ └── settings_dialog.py
├── resources/
├── docs/
├── tests/
│ └── test_service.py
└── vendor/
Le plugin suit une architecture :
- modulaire ;
- orientée services ;
- découplée UI / métier.
| Module | Rôle |
|---|---|
plugin.py |
Point d'entrée QGIS |
ui/panel.py |
Interface utilisateur |
ui/service.py |
Logique métier |
core/intersection/intersection_processing.py |
Intersections spatiales |
core/intersection/intersection_context.py |
Contexte d'intersection |
core/intersection/intersection_results.py |
Résultats d'intersection |
core/intersection/intersection_metrics.py |
Métriques d'intersection |
core/intersection/profiling.py |
Profiling d'intersection |
core/export/ |
Exports CSV/PDF |
core/utils/ |
Helpers et utilitaires |
core/constants.py |
Constantes du plugin |
core/logger.py |
Gestion des logs |
core/image_manager.py |
Gestion des images |
Diagramme des dépendances des modules
Chaque package important contient aussi un fichier AGENT.md détaillant
son rôle, son API publique, ses invariants et ses pièges connus :
AGENT.md (racine), core/AGENT.md,
core/export/AGENT.md,
core/export/pdf/AGENT.md,
core/intersection/AGENT.md,
core/utils/AGENT.md, ui/AGENT.md.
Le plugin repose principalement sur :
native:extractbylocationnative:fixgeometriesnative:reprojectlayergdal:warpreproject
Toutes les couches sont reprojetées dans le CRS du projet avant traitement.
La fonction compute_export_extent calcule un rectangle englobant (bbox) pour une couche vectorielle donnée, en ajoutant une marge de 5 % de la largeur et de la hauteur du rectangle d'origine, ce qui permet de prendre les entités adjacentes.
Le plugin utilise le système de logs natif de QGIS.
Les messages sont visibles dans :
Vue → Panneaux → Journal des messages
sequenceDiagram
participant U as User
participant P as Panel
participant S as Service
participant I as Intersector
participant X as Export
U->>P: Click "Réaliser l'intersection"
P->>S: select() - Validate layer selection
S->>P: Return selection result
P->>S: run() - Execute intersection
S->>I: intersect_layers() - Perform GIS operations
I->>S: Return processed layers
S->>P: Return process result
P->>P: Update UI state
U->>P: Click "Exporter CSV" or "Exporter PDF"
P->>X: Call export function
X->>P: Return export results
P->>U: Show status message
L'architecture observée repose sur cinq principes structurants :
- Délégation maximale à QGIS
- Découplage métier / UI
- Isolation des objets générés
- Optimisation des traitements géospatiaux
- Forte orientation export et reproductibilité
On se propose ici de détailler les 14 choix techniques prépondérants :
- Indépendance des formats de données et API.
- S'appuyer au maximum sur les fonctions QGIS.
- Utilisation de la visibilité des couches.
- Groupe temporaire pour la symbologie.
- Optimisation des traitements d'intersection.
- Gestion spécifique des couches raster.
- Fond de carte via groupe dédié.
- Utilisation massive de dataclass et séparation métier/service.
- UX pilotée par
set_status. - Interface volontairement minimaliste.
- Utilisation de modèles QPT pour les PDF.
- Pagination basée sur des seuils fixes.
- Utilisation de pypdf pour fusionner les pages.
- Utilisation du répertoire
vendor/. - Ajout d'une marge de 5 % à la bbox.
Fonctionner avec le plus de cas d'usage possible sans dépendre d'un format de données particulier ni d'une API externe.
- Le cœur applicatif manipule les abstractions QGIS :
QgsMapLayerQgsVectorLayerQgsRasterLayer
- Aucun couplage métier à :
- GeoJSON
- PostGIS
- API REST
- structure de données spécifique
- Les traitements travaillent sur des couches préparées (
PreparedLayer) indépendamment de leur provenance. - Utilisation extensive de
@dataclasspour représenter les états métier plutôt que des structures dépendantes du stockage.
Réutiliser les capacités natives de QGIS afin de réduire la complexité métier et faciliter la maintenance.
- Traitements géospatiaux délégués au moteur Processing :
processing.run("native:extractbylocation")processing.run("native:reprojectlayer")processing.run("native:fixgeometries")processing.run("gdal:warpreproject")
- Le plugin orchestre les traitements mais ne réimplémente pas les algorithmes SIG.
- Gestion des erreurs (logs) et fallback via QGIS plutôt que réimplémentation d'une logique propriétaire :
QgsProcessingFeedbackQgsMessageLog
Utiliser la visibilité des couches de QGIS comme mécanisme fonctionnel pour déterminer quelles couches participent aux traitements.
- Recherche des couches visibles uniquement :
find_layers()iter_visible_layers()
- Gestion temporaire de visibilité :
temporary_visible_layers()
- Pendant les exports :
- masquage global ;
- réactivation uniquement :
- des résultats ;
- du fond de carte.
- Les fonctions utilitaires :
clear_all_visibility()set_layer_and_parents_visible()
Créer des groupes temporaires pour isoler les couches générées et permettre leur manipulation par l'utilisateur, et leur nettoyage après export.
- Groupes dédiés :
"Objet cible""Résultats secateur"
- Fonctions :
get_created_objects_group()get_results_group()
- Les couches générées sont injectées dans ces groupes.
- Nettoyage automatique après utilisation.
Objectif :
- préserver l'état du projet utilisateur ;
- éviter de polluer l'arborescence QGIS.
Minimiser le coût des traitements spatiaux.
filter_layers_by_extent()- élimination des couches hors emprise.
TransformCache- évite les recalculs de transformation CRS.
_create_spatial_subset()request.setFilterRect()materialize()
_prepare_vector_layer()_prepare_raster_layer()
timed_call()- métriques :
bbox_secondsreproj_secondsextract_seconds
Considérer que l'intersection raster n'a pas de sens métier ; utiliser uniquement l'emprise.
- Distinction explicite :
- couches vectorielles → intersection réelle ;
- couches raster → filtre via l'emprise.
_prepare_raster_layer()produit une copie exploitable.- Utilisation de :
clone_raster_layer()
- Aucun traitement de découpe spatiale pour les rasters.
Centraliser les fonds de carte dans un groupe spécifique, créé au besoin.
- Constante :
BASEMAP_GROUP_NAME = "Fond de carte" - Gestion :
get_basemap_group()
- UI :
BasemapComboBox
- Le composant n'affiche que les couches appartenant au groupe (et pas au sous-groupe du groupe).
- Le composant se recharge dès qu'il est sélectionné.
Objectif :
- standardiser la sélection du fond utilisé dans les exports.
- éviter toute dépendance à une API en permettant à l'utilisateur de choisir son fond de carte.
Note:
- les éléments du groupe
"Fond de carte"ne sont pas intersectés.
Structurer les échanges internes et séparer logique métier de l'interface : comprendre qu'on cherche tout particulièrement à séparer métier vs. interface.
Exemples :
IntersectionExecutionContextLayerMetricsIntersectionMetricsLegendExportConfigMultiPagePdfExportConfigLayoutMetadataPdfExportOptions
SecateurPanel
↓
SecateurService
↓
Core Services
↓
Export Services
SecateurPanel→ interface.SecateurService→ orchestration métier.core/→ traitements.
Guider l'utilisateur pendant toutes les étapes et avoir une interface responsive.
- Méthode centrale :
_set_status() - Mise à jour :
status_label
- Messages pour :
- progression ;
- erreurs ;
- état courant ;
- succès export.
- Journalisation :
loggerQgsMessageLog
Objectif :
- rendre les traitements compréhensibles sans ouvrir la console.
Limiter le nombre d'actions visibles.
Parcours utilisateur :
1. Sélection
↓
2. Intersection
↓
3. Export
Boutons principaux :
- Réaliser l'intersection
- Exporter PDF
- Exporter CSV
Éléments secondaires :
- paramètres ;
- documentation.
Objectif :
- réduire la charge cognitive.
Permettre la personnalisation des exports.
- Chargement dynamique :
create_layout_from_template()
- Utilisation :
QDomDocumentlayout.loadFromTemplate()
- Templates utilisés :
report_page.qpt→ pour le PDF multipagelegend_layout.qpt→ pour la légénde
Conséquence :
- le rendu est configurable sans modifier le code.
- utilisation de IDs obligatoires dans les éléments du QPT.
Note :
- on injecte nos données dans les modèles, page par page, puis on fusionne.
Préférer des règles simples plutôt qu'un moteur de calcul complexe.
- Configuration :
max_legend_items_per_page: int = 20
- Pagination :
LegendPaginator.paginate()LegendCostCalculator
Cost is calculated based on: - Title (always 1) - Symbols (1 each) - Categories (1 each) - Ranges (1 each) - Rules (1 each) - Fallback (5) - Utilisation de seuils fixes pour :
- nombre d'éléments ;
- découpage des pages.
Objectif :
- conserver un comportement prévisible.
Note :
- utilisation de magic numbers non optimale : voir si comportement à conserver dans les prochaines versions de QGIS.
Assembler les exports PDF sans utiliser Atlas QGIS.
- Bibliothèque :
pypdf - Fonction :
PdfMerger.merge() - API :
PdfWriterappend()write()
Objectif :
- produire :
- pages cartes ;
- pages légendes ;
- document final.
Garantir la portabilité du plugin.
- Chargement dynamique :
try:
import pypdf
except ImportError:
sys.path.insert(0, vendor_path)- Dépendances embarquées :
vendor/pypdf
Objectif :
- exécution même si l'environnement QGIS/python ne contient pas les dépendances nécessaires.
Ajouter une marge de 5 % à la largeur et hauteur du rectangle englobant (bbox) lors du calcul de l'étendue d'export pour inclure les entités adjacentes.
- Fonction
compute_export_extentdanscore/export/pdf/common/layout/extent.py:- Calcule l'étendue initiale de la couche
- Ajoute 5 % de la largeur et de la hauteur à chaque côté
- Retourne un rectangle englobant agrandi
Cette marge permet de prendre en compte les entités adjacentes lors de l'export PDF, assurant ainsi une visualisation complète des données environnantes.
Le plugin est compatible avec Plugin Reloader
Source : https://github.com/borysiasty/plugin_reloader
uv syncOutils utilisés :
- Ruff
- Pyright
- pre-commit
- uv
uv run pre-commit install
uv run ruff check --fix .
uv run ruff format .
uv run pyrightLa suite de tests s'exécute avec pytest et
pytest-qgis, qui pilote une
vraie installation QGIS (≥ 3.34) plutôt que des mocks : les tests
créent de vraies couches en mémoire, exécutent la vraie intersection
spatiale (native:extractbylocation, etc.) et produisent de vrais
fichiers CSV/PDF dans un répertoire temporaire.
qgis/qgis.PyQt ne s'installent pas via pip : ils viennent de
l'installation QGIS elle-même. Le venv doit donc être créé avec accès aux
paquets système, en utilisant le même interpréteur Python que QGIS :
uv venv --system-site-packages
uv syncSur Windows, voir qgis-venv-creator pour relier correctement le venv à l'environnement Python de QGIS.
uv run pytestDétail de ce que couvre chaque fichier de test : tests/README.md.
[!NOTE]
tests/test_compat.pyne peut vérifier que la branche Qt5 decompat.pysur un poste QGIS 3.34 — la branche Qt6/QGIS4 est marquéeskip(pas silencieusement ignorée) faute d'installation QGIS 4 pour la vérifier réellement.
ui/panel.pyetui/widgets/(interactions widgets/dialogues) ne sont pas encore couverts par cette suite — voir core/AGENT.md et ui/AGENT.md pour l'état de la couverture par module. Testez manuellement dans QGIS avant de merger un changement touchant à ces fichiers.
zip -r secateur.zip secateur -x "*/.*" "*/docs/*"[!NOTE] Cette commande n'exclut pas
tests/,stubs/,uv.lock,pyproject.tomlni.pre-commit-config.yaml, qui n'ont pas leur place dans le ZIP livré à un utilisateur final. À affiner si un processus de release plus strict est mis en place.
- Ouvrir une issue ou discuter du changement avant une PR conséquente.
- Installer les hooks avant de committer :
uv run pre-commit install(ruff + pyright tournent automatiquement). - Garder les commits/PR petits et ciblés ; expliquer le "pourquoi" dans le message de commit plutôt que reformuler le diff.
- Chaque package important (
core/,core/export/pdf/,ui/, etc.) contient un fichierAGENT.mdavec son contexte technique (API publique, invariants, pièges connus) — le consulter avant de modifier un module, et le mettre à jour si le comportement documenté change.
Exemple de lien entre les collections thématiques et Sécateur : le plugin QGIS d’intersection spatiale automatique pour l’analyse territoriale et la production de GeoPDF multicouches.
Cause possible :
- Les couches ne sont pas visibles dans QGIS.
- La couche source n'est pas vectorielle.
Solution :
- Dans QGIS, cochez la case Visible pour chaque couche à analyser.
- Vérifiez que format des couches (vectoriel/raster).
- Redémarrez QGIS si le problème persiste.
Vérifier :
- qu'une couche active existe ;
- qu'une seule entité est sélectionnée.
Certaines couches peuvent :
- contenir des géométries invalides ;
- avoir trop de sommets ;
- provoquer des crashs QGIS.
Le plugin tente automatiquement une correction (native:fixgeometries) en
cas d'échec de l'intersection sur une couche. Si le problème persiste :
- exécuter manuellement :
Corriger les géométries
- éventuellement simplifier les géométries.
Certaines opérations peuvent être longues. :
- WFS/WMS ;
- exports PDF ;
- intersections massives ;
QGIS peut sembler bloqué pendant les traitements : patientez jusqu'à la fin du processus.
- traitements potentiellement longs sur gros projets ;
- exports WFS/WMS coûteux ;
- GeoPDF sensible aux géométries invalides ;
- certaines couches très lourdes peuvent provoquer des ralentissements.
- DDT21
- utilisateurs testeurs
- contributeurs et relecteurs
Développé dans le cadre des travaux autour des outils Ecosphères / QGIS et des problématiques d'analyse territoriale automatisée.





