9. Utilisation du canevas de carte
Indication
Les extraits de code sur cette page nécessitent les importations suivantes si vous êtes en dehors de la console pyqgis :
1from qgis.PyQt.QtGui import (
2 QColor,
3)
4
5from qgis.PyQt.QtCore import Qt, QRectF
6
7from qgis.PyQt.QtWidgets import QMenu
8
9from qgis.core import (
10 QgsVectorLayer,
11 QgsPoint,
12 QgsPointXY,
13 QgsProject,
14 QgsGeometry,
15 QgsMapRendererJob,
16 QgsWkbTypes,
17)
18
19from qgis.gui import (
20 QgsMapCanvas,
21 QgsVertexMarker,
22 QgsMapCanvasItem,
23 QgsMapMouseEvent,
24 QgsRubberBand,
25)
Le widget de canevas de carte est probablement le widget le plus important dans QGIS car il affiche la carte composée de couches superposées et permet l’interaction avec la carte et les couches. Le canevas montre toujours une partie de la carte définie par l’étendue courante de la carte. L’interaction est réalisée grâce aux outils cartographiques : il existe des outils pour se déplacer, zoomer, identifier les couches, mesurer, éditer des vecteurs… Comme pour les autres programmes graphiques, il y a toujours un outil actif et l’utilisateur peut passer d’un outil disponible à l’autre.
Le canevas de carte est implémenté avec la classe QgsMapCanvas
dans le module qgis.gui. L’implémentation est basée sur le framework Qt Graphics View. Ce cadre fournit généralement une surface et une vue où les éléments graphiques personnalisés sont placés et où l’utilisateur peut interagir avec eux. Nous supposerons que vous êtes suffisamment familier avec Qt pour comprendre les concepts de la scène graphique, de la vue et des éléments. Si ce n’est pas le cas, veuillez lire la aperçu du cadre.
A chaque fois que la carte a été déplacée, zoomée (ou toute autre action déclenchant un rafraîchissement), la carte est rendue de nouveau dans l’étendue courante. Les couches sont rendues sous forme d’image (en utilisant la classe QgsMapRendererJob
) et cette image est affichée sur la canevas. La classe QgsMapCanvas
contrôle aussi le rafraîchissement de la carte rendue. En plus de cet item qui joue le rôle d’arrière-plan, il peut y avoir d’autres items de canevas de carte.
Les éléments typiques d’un canevas de carte sont les élastiques (utilisés pour la mesure, l’édition vecteur, etc.) ou les marqueurs de sommet. Les éléments de canevas sont généralement utilisés pour donner un retour visuel aux outils cartographiques. Par exemple, lors de la création d’un nouveau polygone, l’outil cartographique crée un élément de canevas élastique qui montre la forme actuelle du polygone. Tous les éléments du canevas de carte sont des sous-classes de QgsMapCanvasItem
qui ajoute quelques fonctionnalités supplémentaires aux objets de base de QGraphicsItem
.
Pour résumer, l’architecture du canevas de carte repose sur trois concepts:
le canevas de carte — pour visualiser la carte
éléments du canevas de la carte — éléments supplémentaires pouvant être affichés sur le canevas de la carte
outils cartographiques — pour l’interaction avec le canevas de la carte
9.1. Intégrer un canevas de carte
Map canvas est un widget comme tous les autres widgets Qt, donc l’utiliser est aussi simple que de le créer et de le montrer.
canvas = QgsMapCanvas()
canvas.show()
On obtient ainsi une fenêtre autonome avec un canevas de carte. Elle peut également être intégrée dans un widget ou une fenêtre existante. Lorsque vous utilisez les fichiers .ui
et Qt Designer, placez un QWidget
sur le formulaire et promouvez-le dans une nouvelle classe : définissez QgsMapCanvas
comme nom de classe et définissez qgis.gui
comme fichier d’en-tête. L’utilitaire « pyuic5 » s’occupera de tout cela. C’est une façon très pratique d’intégrer le canevas. L’autre possibilité est d’écrire manuellement le code pour construire le canevas de la carte et d’autres widgets (comme les enfants d’une fenêtre principale ou d’un dialogue) et créer une mise en page.
Par défaut, le canevas de carte a un arrière-plan noir et n’utilise pas l’anticrénelage. Pour afficher un arrière-plan blanc et activer l’anticrénelage pour un rendu plus lisse:
canvas.setCanvasColor(Qt.white)
canvas.enableAntiAliasing(True)
(Au cas où vous vous poseriez la question, « Qt » vient du module « PyQt.QtCore » et « Qt.white » est l’une des instances prédéfinies de « QColor »).
Il est maintenant temps d’ajouter quelques couches cartographiques. Nous allons d’abord ouvrir une couche et l’ajouter au projet en cours. Ensuite, nous définirons l’étendue du canevas et la liste des couches pour le canevas.
1vlayer = QgsVectorLayer('testdata/airports.shp', "Airports layer", "ogr")
2if not vlayer.isValid():
3 print("Layer failed to load!")
4
5# add layer to the registry
6QgsProject.instance().addMapLayer(vlayer)
7
8# set extent to the extent of our layer
9canvas.setExtent(vlayer.extent())
10
11# set the map canvas layer set
12canvas.setLayers([vlayer])
Après exécution de ces commandes, le canevas de carte devrait afficher la couche chargée.
9.2. Contour d’édition et symboles de sommets
Pour afficher des données supplémentaires en haut de la carte dans le canevas, utilisez les éléments du canevas de la carte. Il est possible de créer des classes d’éléments de canevas personnalisés (voir ci-dessous), mais il existe deux classes d’éléments de canevas utiles pour des raisons de commodité : QgsRubberBand
pour dessiner des polylignes ou des polygones, et QgsVertexMarker
pour dessiner des points. Ils fonctionnent tous les deux avec des coordonnées cartographiques, de sorte que la forme est déplacée ou mise à l’échelle automatiquement lorsque le canevas fait l’objet d’un panoramique ou d’un zoom.
Pour montrer une polyligne :
r = QgsRubberBand(canvas, QgsWkbTypes.LineGeometry) # line
points = [QgsPoint(-100, 45), QgsPoint(10, 60), QgsPoint(120, 45)]
r.setToGeometry(QgsGeometry.fromPolyline(points), None)
Pour afficher un polygone:
r = QgsRubberBand(canvas, QgsWkbTypes.PolygonGeometry) # polygon
points = [[QgsPointXY(-100, 35), QgsPointXY(10, 50), QgsPointXY(120, 35)]]
r.setToGeometry(QgsGeometry.fromPolygonXY(points), None)
Veuillez noter que les points d’un polygone ne sont pas stockés dans une liste. En fait, il s’agit d’une liste d’anneaux contenants les anneaux linéaires du polygones: le premier anneau est la limite extérieure, les autres (optionnels) anneaux correspondent aux trous dans le polygone.
Les contours d’édition peut être personnalisés pour changer leur couleur ou la taille de la ligne:
r.setColor(QColor(0, 0, 255))
r.setWidth(3)
Les éléments du canevas sont liés à la scène du canevas. Pour les cacher temporairement (et les montrer à nouveau), utilisez la combinaison hide()
et show()
. Pour supprimer complètement l’élément, vous devez le retirer de la scène du canevas
canvas.scene().removeItem(r)
(en C++, il est possible de juste supprimer l’objet mais sous Python del r
détruira juste la référence et l’objet existera toujours étant donné qu’il appartient au canevas).
L’élastique peut également être utilisé pour dessiner des points, mais la classe QgsVertexMarker
est mieux adaptée pour cela (QgsRubberBand
ne dessinerait qu’un rectangle autour du point désiré).
Vous pouvez utiliser le marqueur de sommet comme ceci :
m = QgsVertexMarker(canvas)
m.setCenter(QgsPointXY(10,40))
Cela permettra de tracer une croix rouge sur la position [10,45]. Il est possible de personnaliser le type d’icône, la taille, la couleur et la largeur du stylo
m.setColor(QColor(0, 255, 0))
m.setIconSize(5)
m.setIconType(QgsVertexMarker.ICON_BOX) # or ICON_CROSS, ICON_X
m.setPenWidth(3)
Pour cacher temporairement les marqueurs de vertex et les retirer du canevas, utilisez les mêmes méthodes que pour les élastiques.
9.3. Utiliser les outils cartographiques avec le canevas
L’exemple suivant construit une fenêtre qui contient un canevas de carte et des outils cartographiques de base pour le panoramique et le zoom. Des actions sont créées pour l’activation de chaque outil : le panoramique est effectué avec une paire d’instances QgsMapToolPan
, le zoom avant/arrière avec une paire d’instances QgsMapToolZoom
. Les actions sont définies comme vérifiables et ensuite assignées aux outils pour permettre la gestion automatique de l’état vérifié/décoché des actions – quand un outil de carte est activé, son action est marquée comme sélectionnée et l’action de l’outil de carte précédent est désélectionnée. Les outils cartographiques sont activés en utilisant la méthode setMapTool()
.
1from qgis.gui import *
2from qgis.PyQt.QtWidgets import QAction, QMainWindow
3from qgis.PyQt.QtCore import Qt
4
5class MyWnd(QMainWindow):
6 def __init__(self, layer):
7 QMainWindow.__init__(self)
8
9 self.canvas = QgsMapCanvas()
10 self.canvas.setCanvasColor(Qt.white)
11
12 self.canvas.setExtent(layer.extent())
13 self.canvas.setLayers([layer])
14
15 self.setCentralWidget(self.canvas)
16
17 self.actionZoomIn = QAction("Zoom in", self)
18 self.actionZoomOut = QAction("Zoom out", self)
19 self.actionPan = QAction("Pan", self)
20
21 self.actionZoomIn.setCheckable(True)
22 self.actionZoomOut.setCheckable(True)
23 self.actionPan.setCheckable(True)
24
25 self.actionZoomIn.triggered.connect(self.zoomIn)
26 self.actionZoomOut.triggered.connect(self.zoomOut)
27 self.actionPan.triggered.connect(self.pan)
28
29 self.toolbar = self.addToolBar("Canvas actions")
30 self.toolbar.addAction(self.actionZoomIn)
31 self.toolbar.addAction(self.actionZoomOut)
32 self.toolbar.addAction(self.actionPan)
33
34 # create the map tools
35 self.toolPan = QgsMapToolPan(self.canvas)
36 self.toolPan.setAction(self.actionPan)
37 self.toolZoomIn = QgsMapToolZoom(self.canvas, False) # false = in
38 self.toolZoomIn.setAction(self.actionZoomIn)
39 self.toolZoomOut = QgsMapToolZoom(self.canvas, True) # true = out
40 self.toolZoomOut.setAction(self.actionZoomOut)
41
42 self.pan()
43
44 def zoomIn(self):
45 self.canvas.setMapTool(self.toolZoomIn)
46
47 def zoomOut(self):
48 self.canvas.setMapTool(self.toolZoomOut)
49
50 def pan(self):
51 self.canvas.setMapTool(self.toolPan)
Vous pouvez essayer le code ci-dessus dans l’éditeur de console Python. Pour invoquer la fenêtre de canevas, ajoutez les lignes suivantes pour instancier la classe « MyWnd ». Elles rendront la couche actuellement sélectionnée sur le canevas nouvellement créé
w = MyWnd(iface.activeLayer())
w.show()
9.3.1. Sélectionner les entités en utilisant QgsMapToolIdentifyFeature
Vous pouvez utiliser l’outil cartographique QgsMapToolIdentifyFeature
pour demander à l’utilisateur de sélectionner une entité qui sera envoyée à une fonction de rappel.
1def callback(feature):
2 """Code called when the feature is selected by the user"""
3 print("You clicked on feature {}".format(feature.id()))
4
5canvas = iface.mapCanvas()
6feature_identifier = QgsMapToolIdentifyFeature(canvas)
7
8# indicates the layer on which the selection will be done
9feature_identifier.setLayer(vlayer)
10
11# use the callback as a slot triggered when the user identifies a feature
12feature_identifier.featureIdentified.connect(callback)
13
14# activation of the map tool
15canvas.setMapTool(feature_identifier)
9.4. Ecrire des outils cartographiques personnalisés
Vous pouvez écrire vos outils personnalisés, pour mettre en œuvre un comportement personnalisé aux actions effectuées par les utilisateurs sur le canevas.
Les outils cartographiques doivent hériter de la classe QgsMapTool
, ou de toute classe dérivée, et être sélectionnés comme outils actifs dans le canevas en utilisant la méthode setMapTool()
comme nous l’avons déjà vu.
Voici un exemple d’outil cartographique qui permet de définir une emprise rectangulaire en cliquant et en déplaçant la souris sur le canevas. Lorsque le rectangle est dessiné, il exporte les coordonnées de ses limites dans la console. On utilise des éléments de contour d’édition décrits auparavant pour afficher le rectangle sélectionné au fur et à mesure de son dessin.
1class RectangleMapTool(QgsMapToolEmitPoint):
2 def __init__(self, canvas):
3 self.canvas = canvas
4 QgsMapToolEmitPoint.__init__(self, self.canvas)
5 self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry)
6 self.rubberBand.setColor(Qt.red)
7 self.rubberBand.setWidth(1)
8 self.reset()
9
10 def reset(self):
11 self.startPoint = self.endPoint = None
12 self.isEmittingPoint = False
13 self.rubberBand.reset(QgsWkbTypes.PolygonGeometry)
14
15 def canvasPressEvent(self, e):
16 self.startPoint = self.toMapCoordinates(e.pos())
17 self.endPoint = self.startPoint
18 self.isEmittingPoint = True
19 self.showRect(self.startPoint, self.endPoint)
20
21 def canvasReleaseEvent(self, e):
22 self.isEmittingPoint = False
23 r = self.rectangle()
24 if r is not None:
25 print("Rectangle:", r.xMinimum(),
26 r.yMinimum(), r.xMaximum(), r.yMaximum()
27 )
28
29 def canvasMoveEvent(self, e):
30 if not self.isEmittingPoint:
31 return
32
33 self.endPoint = self.toMapCoordinates(e.pos())
34 self.showRect(self.startPoint, self.endPoint)
35
36 def showRect(self, startPoint, endPoint):
37 self.rubberBand.reset(QgsWkbTypes.PolygonGeometry)
38 if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y():
39 return
40
41 point1 = QgsPointXY(startPoint.x(), startPoint.y())
42 point2 = QgsPointXY(startPoint.x(), endPoint.y())
43 point3 = QgsPointXY(endPoint.x(), endPoint.y())
44 point4 = QgsPointXY(endPoint.x(), startPoint.y())
45
46 self.rubberBand.addPoint(point1, False)
47 self.rubberBand.addPoint(point2, False)
48 self.rubberBand.addPoint(point3, False)
49 self.rubberBand.addPoint(point4, True) # true to update canvas
50 self.rubberBand.show()
51
52 def rectangle(self):
53 if self.startPoint is None or self.endPoint is None:
54 return None
55 elif (self.startPoint.x() == self.endPoint.x() or \
56 self.startPoint.y() == self.endPoint.y()):
57 return None
58
59 return QgsRectangle(self.startPoint, self.endPoint)
60
61 def deactivate(self):
62 QgsMapTool.deactivate(self)
63 self.deactivated.emit()
9.5. Ecrire des éléments de canevas de carte personnalisés
Voici un exemple d’un canevas personnalisé qui dessine un cercle :
1class CircleCanvasItem(QgsMapCanvasItem):
2 def __init__(self, canvas):
3 super().__init__(canvas)
4 self.center = QgsPoint(0, 0)
5 self.size = 100
6
7 def setCenter(self, center):
8 self.center = center
9
10 def center(self):
11 return self.center
12
13 def setSize(self, size):
14 self.size = size
15
16 def size(self):
17 return self.size
18
19 def boundingRect(self):
20 return QRectF(self.center.x() - self.size/2,
21 self.center.y() - self.size/2,
22 self.center.x() + self.size/2,
23 self.center.y() + self.size/2)
24
25 def paint(self, painter, option, widget):
26 path = QPainterPath()
27 path.moveTo(self.center.x(), self.center.y());
28 path.arcTo(self.boundingRect(), 0.0, 360.0)
29 painter.fillPath(path, QColor("red"))
30
31
32# Using the custom item:
33item = CircleCanvasItem(iface.mapCanvas())
34item.setCenter(QgsPointXY(200,200))
35item.setSize(80)