QGIS dans Python¶
Lorsque l'on a l'habitude d'utiliser un logiciel SIG comme QGIS, il peut se révéler pertinent de chercher à exploiter ses potentialités dans un langage de programmation comme Python. Cela est possible avec PyQGIS. Néanmoins, la plupart du temps, cet apprentissage se fait au travers de QGIS [1][2][3]. Dans ce notebook, nous allons voir comment utiliser PyQGIS (donc QGIS) depuis un environnement Python plus classique.
Pour ce notebook, il convient d'exécuter en tant qu'administrateur le prompt Anaconda (ou le Powershell Prompt). Les données à utiliser sont disponibles ici.
Pour installer PyQGIS dans Python, on doit installer QGIS... Et pour maximiser vos chances que cela fonctionne, il convient de faire une installation spécifique de QGIS via Python. Comme cela est lourd et que la gestion des dépendances des différentes bibliothèques est complexe, il convient de protéger votre environnement Python et de créer un environnement nouveau contenant QGIS. Pour cela, il faut aller dans le prompt Anaconda (ou le Powershell Prompt) et taper la ligne de code suivante :
conda create --name qgis_stable
Il est possible de préciser la version Python à utiliser (conda create -–name qgis_stable python=3.11). Ensuite, il faut activer ce nouvel environnement.
conda activate qgis_stable
Il est possible d'installer Jupyter pour faire fonctionner PyQGIS depuis cette solution.
conda install jupyter
Et bien entendu, il ne faut pas oublier ici d'installer QGIS.
conda install qgis --channel conda-forge
Il est possible de préciser la version QGIS à installer (conda install qgis=3.28.5 --channel conda-forge). Désormais vous pouvez lancer Jupyter avec la ligne de commande suivante et vous pourrez utiliser PyQGIS :
jupyter notebook
Une fois la première utilisation terminée de ce notebook, il est possible de passer par le prompt Anaconda (ou le Powershell Prompt) pour relancer cet environnement :
conda activate qgis_stable
jupyter notebook
Dans un notebook, il est possible de tester l'installation de PyQGIS en important PyQGIS...
import qgis
--------------------------------------------------------------------------- ModuleNotFoundError Traceback (most recent call last) Cell In[1], line 1 ----> 1 import qgis ModuleNotFoundError: No module named 'qgis'
Néanmoins, il se peut que lorsque vous importez PyQGIS vous obteniez un message d’erreur. Il faut alors retourner dans le prompt Anaconda et ouvrir la version de QGIS téléchargée. Dans la console Python de QGIS, on cherche alors à récupérer les paths de la version de QGIS installée par anaconda. Pour cela, dans la console Python de QGIS vous pouvez taper les lignes suivantes :
import sys
sys.path
['C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\python312.zip', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\DLLs', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Lib', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable', '', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Lib\\site-packages', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Lib\\site-packages\\win32', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Lib\\site-packages\\win32\\lib', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Lib\\site-packages\\Pythonwin', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Lib\\site-packages\\setuptools\\_vendor']
Repassez dans Jupyter, puis copiez le résultat de la console Python de QGIS et placez la liste de chemins dans un objet comme ci-dessous. Ensuite, on utilise une boucle pour ajouter les chemins à votre path.
import sys
a = ['C:/Users/Serge/anaconda3/envs/qgis_stable/Library/./python', 'C:/Users/Serge/AppData/Roaming/QGIS/QGIS3\\profiles\\default/python', 'C:/Users/Serge/AppData/Roaming/QGIS/QGIS3\\profiles\\default/python/plugins', 'C:/Users/Serge/anaconda3/envs/qgis_stable/Library/./python/plugins', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\python312.zip', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\DLLs', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Lib', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Library\\bin', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Lib\\site-packages', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Lib\\site-packages\\win32', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Lib\\site-packages\\win32\\lib', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Lib\\site-packages\\Pythonwin', 'C:/Users/Serge/AppData/Roaming/QGIS/QGIS3\\profiles\\default/python']
for i in a :
sys.path.append(i)
import qgis
Il faudra alors toujours recommencer cette mise dans le path de ces chemins. Donc conservez ce bout de codes et placez le toujours en amont de vos nouveaux codes utilisant PyQGIS. Si cela vous agace, il faudra modifier le path de manière permanente. Dans les faits, cette dernière manipulation montre que si vous avez déjà une version de QGIS installée sur votre ordinateur, pour interagir avec cette version, il suffit en théorie de bien gérer les paths pour que Pyhton accède à QGIS (donc à PyQGIS).
Reproduire les connaissances PyQGIS dans Jupyter¶
Ici, nous allons chercher à reproduire nos connaissances PyQGIS dans l'environnement Python/Jupyter. Pour cela, il n'y a qu'une modification majeure à faire : il faut créer un projet en PyQGIS (ce projet est créé automatiquement lorsque l'on ouvre QGIS, dans la console Python de QGIS cette partie est donc en théorie inutile). Pour cela, on peut taper les lignes suivantes :
import qgis
from qgis.core import *
QgsProject = qgis._core.QgsProject
project = QgsProject.instance()
Il est alors possible de définir un système de projection à ce projet.
crs = QgsCoordinateReferenceSystem("EPSG:27572")
project.setCrs(crs)
Il faudra charger vos couches à la main dans ce projet.
vlayer = QgsVectorLayer("C:/Users/Serge/Desktop/Reseau/Villes.shp", "Villes")
vlayer2 = QgsVectorLayer("C:/Users/Serge/Desktop/Reseau/Autoroutes.shp","Routes")
project.addMapLayer(vlayer)
project.addMapLayer(vlayer2)
<QgsVectorLayer: 'Routes' (ogr)>
Après, presque rien ne change si on reprend par exemple le TD1. On peut récupérer les couches d'un projet comme cela :
print(QgsProject.instance().mapLayers())
print(list(QgsProject.instance().mapLayers().values()))
couche_noeud = QgsProject.instance().mapLayersByName('Villes')[0]
{'Routes_a9946856_a60a_45b5_9d11_b5df077ded61': <QgsVectorLayer: 'Routes' (ogr)>, 'Villes_55eeb07a_8b2e_42bc_8d0f_96a73170a77b': <QgsVectorLayer: 'Villes' (ogr)>} [<QgsVectorLayer: 'Routes' (ogr)>, <QgsVectorLayer: 'Villes' (ogr)>]
Pour interagir avec ces couches, on passera par la création d'un provider.
provider = couche_noeud.dataProvider()
nbcol = provider.fields().count()
for i in range(nbcol) :
print(provider.fields()[i].name())
provider.fieldNameIndex('Nom')
nameid = provider.fieldNameIndex('Nom')
ID Nom
On pourra parcourir et récupérer les informations d'une table attributaire comme ci-dessous :
feat = QgsFeature()
table = []
fit = provider.getFeatures()
while fit.nextFeature(feat):
table = table + [str(feat.attributes()[nameid])]
print(table)
['Calais', 'Lille', 'Strasbourg', 'Metz', 'Reims', 'Amiens', 'Rouen', 'Caen', 'Cherbourg', 'Brest', 'Rennes', 'Le Mans', 'Chartres', 'Paris', 'Troyes', 'Nancy', 'Mulhouse', 'Langres', 'Besancon', 'Dijon', 'Sens', 'Orleans', 'Angers', 'Vannes', 'Nantes', 'Tours', 'Vierzon', 'La Rochelle', 'Saintes', 'Niort', 'Poitiers', 'Limoges', 'Vichy', 'Clermont-Fd', 'Macon', 'Bourg-en-Bresse', 'Geneve', 'Lyon', 'Grenoble', 'Valence', 'St-Etienne', 'Brive', 'Bordeaux', 'Montauban', 'Millau', 'Avignon', 'Digne-les-bains', 'Nice', 'Toulon', 'Marseille', 'Nimes', 'Montpellier', 'Narbonne', 'Perpignan', 'Toulouse', 'Pau', 'Bayonne']
Tout est identique et on peut appeler d'autres bibliothèques Python pour faire des calculs.
import networkx as nx
couche_arc = project.mapLayersByName('Routes')[0]
provider2 = couche_arc.dataProvider()
feat = QgsFeature()
fit = provider2.getFeatures()
table_arc = []
while fit.nextFeature(feat):
dist = float(feat.attributes()[0])
dep = int(feat.attributes()[2])
arr = int(feat.attributes()[3])
table_arc = table_arc + [[dep, arr, dist]]
G = nx.Graph()
G.add_weighted_edges_from(table_arc)
bet = nx.betweenness_centrality(G,weight='weight')
print(bet)
{10: 0.0, 11: 0.05909090909090909, 8: 0.045454545454545456, 9: 0.0, 24: 0.013636363636363636, 7: 0.05064935064935065, 12: 0.06948051948051948, 13: 0.18831168831168832, 23: 0.03571428571428571, 25: 0.07662337662337662, 26: 0.08766233766233766, 31: 0.047402597402597405, 30: 0.11428571428571428, 28: 0.0, 29: 0.10909090909090909, 22: 0.17857142857142858, 27: 0.11948051948051948, 32: 0.031818181818181815, 42: 0.12142857142857143, 43: 0.12727272727272726, 57: 0.00909090909090909, 44: 0.11428571428571428, 55: 0.11103896103896103, 56: 0.008441558441558441, 53: 0.09610389610389611, 54: 0.0, 52: 0.11688311688311688, 45: 0.045454545454545456, 51: 0.10194805194805195, 46: 0.04935064935064935, 40: 0.05714285714285714, 38: 0.2564935064935065, 50: 0.05064935064935065, 49: 0.0, 48: 0.0, 47: 0.05519480519480519, 39: 0.08441558441558442, 41: 0.14025974025974025, 33: 0.12597402597402596, 34: 0.21818181818181817, 35: 0.16298701298701299, 36: 0.02857142857142857, 37: 0.0, 20: 0.22077922077922077, 19: 0.048051948051948054, 17: 0.01948051948051948, 3: 0.0, 16: 0.024675324675324677, 4: 0.033766233766233764, 18: 0.06688311688311688, 15: 0.044805194805194806, 5: 0.05649350649350649, 2: 0.004545454545454545, 1: 0.0025974025974025974, 6: 0.032467532467532464, 14: 0.2506493506493506, 21: 0.16688311688311688}
Il est possible que le chargement des bibliothèques pose des problèmes, car lors de la création de votre nouvel environnement, toutes celles que vous aviez dans votre base n'ont pas forcément été ajoutées. Pour corriger cela, il faut simplement aller dans le prompt Anaconda, puis activer votre environnement QGIS (conda activate qgis_stable) et y installer les bibliothèques dont vous avez besoin (conda install networkx).
La gestion de la cartographie¶
Le point le plus complexe à gérer dans l'environnement Python/Jupyter concerne sans doute la partie cartographique. En effet, dans QGIS cette cartographie est très souvent gérée par QGIS et la plupart des développeurs se contenteront donc de rafraichir le canevas et de faire quelques changements sémiologiques. Néanmoins, dans Jupyter, un simple plot() devient un brin plus complexe.
Ci-dessous, la séquence de codes permet de palier à ce problème en définissant une application, dans laquelle on a un canevas QGIS, qui s'appuie sur nos couches. On va alors afficher ce canevas et exécuter l'application.
from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QColor
from qgis.gui import QgsMapCanvas
app = QApplication([])
canvas = QgsMapCanvas()
canvas.setExtent(vlayer.extent())
canvas.setLayers(list(QgsProject.instance().mapLayers().values()))
canvas.show()
app.exec_()
0
Il faudra alors faire obligatoirement de la sémiologie directement dans le code.
from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QColor
from qgis.gui import QgsMapCanvas
app = QApplication([])
canvas = QgsMapCanvas()
canvas.setExtent(vlayer.extent())
canvas.setLayers(list(QgsProject.instance().mapLayers().values()))
vlayer.renderer().symbol().setColor(QColor("red"))
vlayer.triggerRepaint()
vlayer2.renderer().symbol().setColor(QColor("blue"))
vlayer2.triggerRepaint()
canvas.show()
app.exec_()
0
Néanmoins, cette solution ouvre une application extérieure à Jupyter. Pour avoir une image dans Jupyter ou Spyder, on pourra utiliser la séquence IPython ci-dessous.
from IPython.display import Image
def saveImage(path, show=True):
canvas.saveAsImage(path)
if show: return Image(path)
saveImage("plot.png")
Les deux solutions sont un peu lourdes. La solution la plus simple pour faire de la cartographie consiste peut-être à utiliser QGIS uniquement pour effectuer les traitements que l'on maitrise avec cet outil, puis d'utiliser geopandas pour gérer l’affichage et faire ainsi la partie cartographique. Ainsi, ici on peut utiliser QGIS pour produire un shapefile de buffers de 50 km autour des villes, puis on affiche les trois shapefiles avec geopandas.
import processing
from processing.core.Processing import Processing
import matplotlib.pyplot as plt
import geopandas as gpd
param = {'INPUT' : 'C:/Users/Serge/Desktop/Reseau/Villes.shp',
'DISTANCE' : 50000, 'DISSOLVE' : False,
'OUTPUT' : 'C:/Users/Serge/Desktop/Reseau/Buffer.shp'}
Processing.initialize()
processing.run('qgis:buffer', param)
gdf = gpd.read_file('C:/Users/Serge/Desktop/Reseau/Villes.shp')
gdf2 = gpd.read_file('C:/Users/Serge/Desktop/Reseau/Autoroutes.shp')
gdf3 = gpd.read_file('C:/Users/Serge/Desktop/Reseau/Buffer.shp')
fig, ax = plt.subplots()
gdf3.plot(ax=ax, color='lightblue')
gdf2.plot(ax=ax, color='grey')
gdf.plot(ax=ax, color='red')
plt.show()
Coder son propre SIG¶
En conjuguant la maitrise des outils QGIS et PyQT, il est alors possible de créer son propre environnement SIG. Honnêtement, l'utilité de faire cela est contestable. Néanmoins, en termes d'apprentissage, je pense que c'est pertinent.
Bon, au départ, il faut maitriser un peu PyQt, notamment les objets application (QApplication), fenêtre (QMainWindow), layout et panneau (QWidget). Ici, on va créer une application (dans les faits, il ne peut y avoir qu'une application par programme), cette application comporte une fenêtre (une application peut comporter plusieurs fenêtres, mais généralement elle possède une fenêtre principale qui contient la barre de menus, la barre d’outils, la barre d’état...). Au sein de ces fenêtres, on peut placer ce que l'on peut considérer comme des panneaux (ce seront les widgets, ils servent souvent à regrouper des éléments liés fonctionnellement, comme des outils ou des options). La disposition des widgets (boutons, étiquettes, listes, etc.) à l’intérieur d’une fenêtre est géré par le layout (QVBoxLayout disposition verticale, QHBoxLayout disposition horizontale et QGridLayout disposition grille).
Lancer régulièrement des applications depuis des lignes de commande Jupyter ou Spyder peut générer des erreurs. Le noyau (kernel) peut crasher. Après avoir relancé le noyau, n'oubliez pas que QGIS doit bien être chargé, ce qui peut nécessiter de recharger le code suivant :
import sys
a = ['C:/Users/Serge/anaconda3/envs/qgis_stable/Library/./python', 'C:/Users/Serge/AppData/Roaming/QGIS/QGIS3\\profiles\\default/python', 'C:/Users/Serge/AppData/Roaming/QGIS/QGIS3\\profiles\\default/python/plugins', 'C:/Users/Serge/anaconda3/envs/qgis_stable/Library/./python/plugins', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\python312.zip', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\DLLs', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Lib', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Library\\bin', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Lib\\site-packages', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Lib\\site-packages\\win32', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Lib\\site-packages\\win32\\lib', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Lib\\site-packages\\Pythonwin', 'C:/Users/Serge/AppData/Roaming/QGIS/QGIS3\\profiles\\default/python']
for i in a :
sys.path.append(i)
import qgis
Ci-dessous la structure la plus minimaliste pour créer une application (QApplication([])), avec une fenêtre principale (QMainWindow()), comportant un widget central (QWidget()) disposé verticalement (QVBoxLayout()) (mais bon pour l'instant ça ne se voit pas...).
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
# Créer l'application PyQt
app = QApplication([])
# Créer la fenêtre principale minimaliste
window = QMainWindow()
window.setWindowTitle("Fenêtre")
central_widget = QWidget()
layout = QVBoxLayout(central_widget)
window.setCentralWidget(central_widget)
# Afficher la fenêtre
window.show()
app.exec_()
0
On peut ajouter un qgscanvas au layout du panneau central.
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
from qgis.gui import QgsMapCanvas
# Créer l'application PyQt
app = QApplication([])
# Créer la fenêtre principale minimaliste
window = QMainWindow()
window.setWindowTitle("Fenêtre")
central_widget = QWidget()
layout = QVBoxLayout(central_widget)
window.setCentralWidget(central_widget)
# Créer un canvas pour la carte
canvas = QgsMapCanvas()
layout.addWidget(canvas)
# Afficher la fenêtre
window.show()
app.exec_()
0
A l'aide de vos compétences en PyQGIS, vous pouvez afficher ce que vous voulez dans le panneau central qui contient donc un canvas cartographique QGIS. Ici, on reprend le code de la partie précédente qui crée un projet dans lequel on charge nos deux shapefiles. A noter que vous pouvez vous déplacer dans ce canvas à l'aide de drag & drop. Si vous avez une molette, vous pouvez même zoomer.
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
from qgis.gui import QgsMapCanvas
from qgis.core import *
# Créer l'application PyQt
app = QApplication([])
# Créer la fenêtre principale minimaliste
window = QMainWindow()
window.setWindowTitle("Fenêtre")
central_widget = QWidget()
layout = QVBoxLayout(central_widget)
window.setCentralWidget(central_widget)
# Créer un canvas pour la carte
canvas = QgsMapCanvas()
layout.addWidget(canvas)
# Créer un projet et ajouter des couches dans le canvas
QgsProject = qgis._core.QgsProject
project = QgsProject.instance()
vlayer = QgsVectorLayer("C:/Users/Serge/Desktop/Reseau/Villes.shp", "Villes")
vlayer2 = QgsVectorLayer("C:/Users/Serge/Desktop/Reseau/Autoroutes.shp","Routes")
project.addMapLayer(vlayer)
project.addMapLayer(vlayer2)
canvas.setLayers(list(QgsProject.instance().mapLayers().values()))
canvas.zoomToFullExtent()
# Afficher la fenêtre
window.show()
app.exec_()
0
SIG gérant le zoom et les déplacements¶
Désormais, on peut ajouter des panneaux (des widgets) contenant des boutons (QPushButton) qui permettent de gérer le zoom (QgsMapToolZoom) si on n'a pas de molette (car effectivement on peut zoomer à l’aide de la molette dans l'application précédente). Si on veut se déplacer horizontalement et verticalement, il faut aussi créer un bouton qui permet ce déplacement (QgsMapToolPan). Pour lier les actions de l'utilisateur (les clics) sur les boutons avec les outils correspondant à ces boutons, il faut utiliser la fonction clicked.connect().
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton
from qgis.gui import QgsMapCanvas, QgsMapToolZoom, QgsMapToolPan
from qgis.core import *
# Créer l'application PyQt
app = QApplication([])
# Créer la fenêtre principale
window = QMainWindow()
window.setWindowTitle("Carte avec outils de zoom et de déplacement")
central_widget = QWidget()
layout = QVBoxLayout(central_widget)
window.setCentralWidget(central_widget)
# Créer un canvas pour la carte
canvas = QgsMapCanvas()
layout.addWidget(canvas)
# Ajouter des boutons de zoom et de déplacement
zoom_in_button = QPushButton("Zoom avant")
zoom_out_button = QPushButton("Zoom arrière")
pan_button = QPushButton("Déplacement")
layout.addWidget(zoom_in_button)
layout.addWidget(zoom_out_button)
layout.addWidget(pan_button)
# Définir les outils de zoom et de déplacement
zoom_in_tool = QgsMapToolZoom(canvas, False) # False pour zoom avant
zoom_out_tool = QgsMapToolZoom(canvas, True) # True pour zoom arrière
pan_tool = QgsMapToolPan(canvas)
# Connecter les boutons aux actions
zoom_in_button.clicked.connect(lambda: canvas.setMapTool(zoom_in_tool))
zoom_out_button.clicked.connect(lambda: canvas.setMapTool(zoom_out_tool))
pan_button.clicked.connect(lambda: canvas.setMapTool(pan_tool))
# Créer un projet et ajouter des couches dans le canvas
QgsProject = qgis._core.QgsProject
project = QgsProject.instance()
vlayer = QgsVectorLayer("C:/Users/Serge/Desktop/Reseau/Villes.shp", "Villes")
vlayer2 = QgsVectorLayer("C:/Users/Serge/Desktop/Reseau/Autoroutes.shp","Routes")
project.addMapLayer(vlayer)
project.addMapLayer(vlayer2)
canvas.setLayers(list(QgsProject.instance().mapLayers().values()))
canvas.zoomToFullExtent()
# Afficher la fenêtre
window.show()
app.exec_()
0
SIG avec gestion des couches¶
Plutôt que de zoomer et se déplacer dans le canvas, on peut se demander comment gérer l'affichage des couches. Pour cela, on peut créer sous la carte un widget qui affiche les couches du canvas, en cliquant sur les cases des couches on peut afficher ou masquer une couche. Bref, on peut reproduire le panneau couches de QGIS... Il faut alors créer un panneau avec une liste qui possède des cases à cocher (QListWidget()). Pour ajouter les couches à cette liste, il faut créer une fonction qui ajoute des items à cette liste (QListWidgetItem()) et qui s'appuie sur les noms des couches du projet qu’il faudra chercher à récupérer. Pour gérer l'affichage des couches (la mise à jour de la visibilité) de cette liste, il faut une deuxième fonction, qui met dans une liste les cases (les items) qui sont cochées pour les afficher (setLayers()). Ensuite, il faut associer les actions de l'utilisateur sur les cases à cocher avec cette fonction d'affichage (itemChanged.connect).
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QListWidget, QListWidgetItem, QCheckBox
from qgis.gui import QgsMapCanvas
from qgis.core import *
# Créer l'application PyQt
app = QApplication([])
# Créer la fenêtre principale
window = QMainWindow()
window.setWindowTitle("Carte avec gestion des couches")
central_widget = QWidget()
layout = QVBoxLayout(central_widget)
window.setCentralWidget(central_widget)
# Créer un canvas pour la carte
canvas = QgsMapCanvas()
layout.addWidget(canvas)
# Gestionnaire de couches (liste avec cases à cocher)
layer_list = QListWidget()
layout.addWidget(layer_list)
# Ajouter les couches à la liste avec des cases à cocher
def add_layer_to_list(layer):
item = QListWidgetItem(layer.name())
item.setCheckState(2) # 2 pour "coché"
layer_list.addItem(item)
# Mettre à jour la visibilité des couches
def update_layer_visibility():
layers = []
for index in range(layer_list.count()):
item = layer_list.item(index)
layer = QgsProject.instance().mapLayersByName(item.text())[0]
if item.checkState() == 2: # 2 pour "coché"
layers.append(layer)
canvas.setLayers(layers)
canvas.refresh()
# Connecter la mise à jour de la visibilité
layer_list.itemChanged.connect(update_layer_visibility)
# Créer un projet et ajouter des couches dans le canvas
QgsProject = qgis._core.QgsProject
project = QgsProject.instance()
vlayer = QgsVectorLayer("C:/Users/Serge/Desktop/Reseau/Villes.shp", "Villes")
vlayer2 = QgsVectorLayer("C:/Users/Serge/Desktop/Reseau/Autoroutes.shp","Routes")
project.addMapLayer(vlayer)
project.addMapLayer(vlayer2)
canvas.setLayers(list(QgsProject.instance().mapLayers().values()))
canvas.zoomToFullExtent()
# Afficher toutes les couches au démarrage
for layer in QgsProject.instance().mapLayers().values():
add_layer_to_list(layer)
update_layer_visibility()
# Afficher la fenêtre
window.show()
app.exec_()
0
SIG chargeant des shapefiles à l'aide d'un menu¶
Désormais, on peut chercher à ajouter une barre de menus, dans laquelle on pourra notamment charger des couches SIG. C'est un peu la base pour un SIG... On aurait pu commencer par cela pour un SIG, mais cela demande selon moi un peu plus de compétences que de lancer un simple bouton pour se déplacer par exemple. En effet, il faut ajouter à notre interface une barre de menus (menuBar()) puis un menu (addMenu()). Il faut notamment avoir compris que ces boutons menus, sont comme des boutons, il faut donc les associer à des actions utilisateurs (QAction(), généralement associés à un sous-menu) quand il clique dessus par exemple (triggered.connect()). La fonction addAction() permet d'associer cette action à la fonction du code qui permet de charger le shapefile. Cette fonction a la particularité d'appeler une fenêtre devant récupérer l'emplacement du fichier (QFileDialog.getOpenFileName()), sinon l'utilisateur devrait la taper à la main... Ensuite, ce n'est pas trop compliqué d'ajouter cette couche au canvas, on fait cela depuis le début.
Ici, on peut repartir d'un fichier ne comportant au départ aucune couche géographique.
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QFileDialog, QAction, QMessageBox
from qgis.gui import QgsMapCanvas
from qgis.core import *
# Créer l'application PyQt
app = QApplication([])
# Créer la fenêtre principale
window = QMainWindow()
window.setWindowTitle("Carte avec gestionnaire de couches")
central_widget = QWidget()
layout = QVBoxLayout(central_widget)
window.setCentralWidget(central_widget)
# Créer un canvas pour la carte
canvas = QgsMapCanvas()
layout.addWidget(canvas)
# Créer une barre de menu avec un menu "Fichier"
menu_bar = window.menuBar()
file_menu = menu_bar.addMenu("Fichier")
# Charger et ajouter une couche depuis un fichier SHP
def load_shapefile():
file_path, _ = QFileDialog.getOpenFileName(window, "Charger une couche SHP", "", "Shapefiles (*.shp)")
if file_path:
layer_name = file_path.split("/")[-1].replace(".shp", "")
layer = QgsVectorLayer(file_path, layer_name, "ogr")
if not layer.isValid():
QMessageBox.critical(window, "Erreur", f"Impossible de charger la couche : {file_path}")
return
QgsProject.instance().addMapLayer(layer)
canvas.setLayers(list(QgsProject.instance().mapLayers().values()))
canvas.refresh()
canvas.zoomToFullExtent()
# Ajouter une action pour charger des couches SHP
load_action = QAction("Charger des couches SHP", window)
load_action.triggered.connect(load_shapefile)
file_menu.addAction(load_action)
# Afficher la fenêtre
window.show()
app.exec_()
0
SIG réalisant des géotraitements (buffers)¶
Il est bien entendu possible de réaliser des géotraitements en utilisant QGIS, par exemple pour créer des buffers. Pour cela, on peut passer une nouvelle fois par un menu. Pour copier QGIS, on peut créer un menu nommé Vecteur. On va aussi réutiliser nos connaissances précédentes pour récupérer l'adresse du fichier shapefile sur lequel s'appuyer pour réaliser les buffers (on pourrait se servir des noms des couches chargées, mais bon il faut faire des choix). Pour appeler les algorithmes de QGIS, il faut avoir recours à la bibliothèque processing. Il faudra l'initialiser dans le code (Processing.initialize()), par exemple dans la fonction qui charge les buffers. On met dans un objet param les paramètres pour faire fonctionner le buffer. Il faut notamment une distance que l'on peut demander à l'utilisateur à l'aide d'un Input Dialog (QInputDialog.getText()). La commande pour lancer l'algorithme buffer de QGIS est celui-ci : processing.run('qgis:buffer', param).
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton, QFileDialog, QAction, QMessageBox, QInputDialog
from qgis.gui import QgsMapCanvas
from qgis.core import *
import processing
from processing.core.Processing import Processing
# Créer l'application PyQt
app = QApplication([])
# Créer la fenêtre principale
window = QMainWindow()
window.setWindowTitle("Carte avec outils de zoom")
central_widget = QWidget()
layout = QVBoxLayout(central_widget)
window.setCentralWidget(central_widget)
# Créer un canvas pour la carte
canvas = QgsMapCanvas()
layout.addWidget(canvas)
# Créer une barre de menu avec un menu "Vecteur"
menu_bar = window.menuBar()
file_menu = menu_bar.addMenu("Vecteur")
# Créer des buffers depuis un fichier SHP
def load_buffer():
file_path, _ = QFileDialog.getOpenFileName(window, "Buffer SHP", "", "Shapefiles (*.shp)")
if file_path:
layer_name = file_path.split("/")[-1].replace(".shp", "")
layer = QgsVectorLayer(file_path, layer_name, "ogr")
if not layer.isValid():
QMessageBox.critical(window, "Erreur", f"Impossible de charger la couche : {file_path}")
return
dist, ok = QInputDialog.getText(window, "Distance du Buffer", "Entrez la distance du Buffer en mètres :")
param = {'INPUT' : layer, 'DISTANCE' : dist, 'DISSOLVE' : False, 'OUTPUT' : 'memory:'}
Processing.initialize()
layerbuf = processing.run('qgis:buffer', param)
QgsProject.instance().addMapLayer(layerbuf['OUTPUT'])
#add_layer_to_list(layer)
#update_layer_visibility()
canvas.setLayers(list(QgsProject.instance().mapLayers().values()))
canvas.refresh()
canvas.zoomToFullExtent()
# Ajouter une action pour créer des buffers
load_action = QAction("Buffer SHP", window)
load_action.triggered.connect(load_buffer)
file_menu.addAction(load_action)
# Afficher la fenêtre
window.show()
app.exec_()
0
SIG avec boutons de zoom et de déplacement, gestion des couches, menu chargeant des shapefiles et un menu proposant un géotraitement buffer.¶
Enfin, on peut assembler l'ensemble de ces briques pour avoir un SIG plus complet et pouvant effectuer plusieurs tâches.
import sys
a = ['C:/Users/Serge/anaconda3/envs/qgis_stable/Library/./python', 'C:/Users/Serge/AppData/Roaming/QGIS/QGIS3\\profiles\\default/python', 'C:/Users/Serge/AppData/Roaming/QGIS/QGIS3\\profiles\\default/python/plugins', 'C:/Users/Serge/anaconda3/envs/qgis_stable/Library/./python/plugins', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\python312.zip', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\DLLs', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Lib', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Library\\bin', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Lib\\site-packages', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Lib\\site-packages\\win32', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Lib\\site-packages\\win32\\lib', 'C:\\Users\\Serge\\anaconda3\\envs\\qgis_stable\\Lib\\site-packages\\Pythonwin', 'C:/Users/Serge/AppData/Roaming/QGIS/QGIS3\\profiles\\default/python']
for i in a :
sys.path.append(i)
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton, QListWidget, QListWidgetItem, QCheckBox, QFileDialog, QAction, QMessageBox, QInputDialog
from qgis.gui import QgsMapCanvas, QgsMapToolZoom, QgsMapToolPan
from qgis.core import *
import processing
from processing.core.Processing import Processing
# Créer l'application PyQt
app = QApplication([])
# Créer la fenêtre principale
window = QMainWindow()
window.setWindowTitle("SIG")
central_widget = QWidget()
layout = QVBoxLayout(central_widget)
window.setCentralWidget(central_widget)
# Créer un canvas pour la carte
canvas = QgsMapCanvas()
layout.addWidget(canvas)
# Créer une barre de menu avec un menu "Fichier"
menu_bar = window.menuBar()
file_menu = menu_bar.addMenu("Fichier")
# Créer une barre de menu avec un menu "Vecteur"
file_menu2 = menu_bar.addMenu("Vecteur")
# Ajouter des boutons de zoom
zoom_in_button = QPushButton("Zoom avant")
zoom_out_button = QPushButton("Zoom arrière")
pan_button = QPushButton("Déplacement")
layout.addWidget(zoom_in_button)
layout.addWidget(zoom_out_button)
layout.addWidget(pan_button)
# Gestionnaire de couches (liste avec cases à cocher)
layer_list = QListWidget()
layout.addWidget(layer_list)
# Définir les outils de zoom
zoom_in_tool = QgsMapToolZoom(canvas, False) # False pour zoom avant
zoom_out_tool = QgsMapToolZoom(canvas, True) # True pour zoom arrière
pan_tool = QgsMapToolPan(canvas)
# Connecter les boutons aux actions
zoom_in_button.clicked.connect(lambda: canvas.setMapTool(zoom_in_tool))
zoom_out_button.clicked.connect(lambda: canvas.setMapTool(zoom_out_tool))
pan_button.clicked.connect(lambda: canvas.setMapTool(pan_tool))
# Charger et ajouter une couche depuis un fichier SHP
def load_shapefile():
file_path, _ = QFileDialog.getOpenFileName(window, "Charger une couche SHP", "", "Shapefiles (*.shp)")
if file_path:
layer_name = file_path.split("/")[-1].replace(".shp", "")
layer = QgsVectorLayer(file_path, layer_name, "ogr")
if not layer.isValid():
QMessageBox.critical(window, "Erreur", f"Impossible de charger la couche : {file_path}")
return
QgsProject.instance().addMapLayer(layer)
add_layer_to_list(layer)
update_layer_visibility()
canvas.setLayers(list(QgsProject.instance().mapLayers().values()))
canvas.refresh()
canvas.zoomToFullExtent()
# Ajouter une action pour charger des couches SHP
load_action = QAction("Charger des couches SHP", window)
load_action.triggered.connect(load_shapefile)
file_menu.addAction(load_action)
# Créer des buffers depuis un fichier SHP
def load_buffer():
file_path, _ = QFileDialog.getOpenFileName(window, "Buffer SHP", "", "Shapefiles (*.shp)")
if file_path:
layer_name = file_path.split("/")[-1].replace(".shp", "")
layer = QgsVectorLayer(file_path, layer_name, "ogr")
if not layer.isValid():
QMessageBox.critical(window, "Erreur", f"Impossible de charger la couche : {file_path}")
return
dist, ok = QInputDialog.getText(window, "Distance du Buffer", "Entrez la distance du Buffer en mètres :")
param = {'INPUT' : layer, 'DISTANCE' : dist, 'DISSOLVE' : False, 'OUTPUT' : 'memory:'}
Processing.initialize()
layerbuf = processing.run('qgis:buffer', param)
QgsProject.instance().addMapLayer(layerbuf['OUTPUT'])
add_layer_to_list(layerbuf['OUTPUT'])
update_layer_visibility()
canvas.setLayers(list(QgsProject.instance().mapLayers().values()))
canvas.refresh()
canvas.zoomToFullExtent()
# Ajouter une action pour créer des buffers
load_action = QAction("Buffer SHP", window)
load_action.triggered.connect(load_buffer)
file_menu2.addAction(load_action)
# Ajouter les couches à la liste avec des cases à cocher
def add_layer_to_list(layer):
item = QListWidgetItem(layer.name())
item.setCheckState(2) # 2 pour "coché"
layer_list.addItem(item)
for layer in QgsProject.instance().mapLayers().values():
add_layer_to_list(layer)
# Mettre à jour la visibilité des couches
def update_layer_visibility():
layers = []
for index in range(layer_list.count()):
item = layer_list.item(index)
layer = QgsProject.instance().mapLayersByName(item.text())[0]
if item.checkState() == 2: # 2 pour "coché"
layers.append(layer)
canvas.setLayers(layers)
canvas.refresh()
# Connecter la mise à jour de la visibilité
layer_list.itemChanged.connect(update_layer_visibility)
# Afficher la fenêtre
window.show()
app.exec_()
0