Utiliser le Canevas de carte

L’objet canevas de carte est probalement l’objet le plus important de QGIS car c’est lui qui permet d’afficher la carte composée par la superposition des couches et d’interagir avec les cartes et les couches. Le canevas montre toujours une partie de la carte définie dans l’emprise courante du canevas. L’interaction est réalisée par l’utilisation d’outils cartographiques. Ces outils permettent: le déplacement, le zoom, l’identification des couches, les mesures, l’édition vectorielle, etc. Comme dans les autres logiciels graphiques, il y a toujours un outil actif et l’utilisateur peut migrer d’un outil à l’autre.

Le canevas de carte est implémenté dans la classe QgsMapCanvas du module qgis.gui. L’implémentation se base sur l’environnement de la Vue Graphique de Qt. Cette structure fournit généralement une surface ainsi qu’une vue où les objets graphiques personnalisés sont placés et avec lesquels l’utilisateur peur interagir. Nous assumerons que vous connaissez suffisamment Qt pour comprendre les concepts de scène graphique, de vue et d’éléments. Si ce n’est pas le cas, assurez-vous de lire aperçu rapide de l’environnement.

Lorsque la carte a été déplacée, zoomée (ou qu’un évènement a déclenché son rafraichissement), la carte est redessinée dans l’emprise courante. Les couches sont rendues dans une image (en utilisant la classe QgsMapRenderer) et cette image est ensuite affichée dans le canevas. L’objet graphique (en termes de structure de vue graphique Qt) responsable de l’affichage de la carte est la classe QgsMapCanvasMap. Elle contrôle également le rafraichissement de la carte rendue. En plus de cet objet qui fait office d’arrière plan, il peut y avoir plusieurs objets de canevas de carte. Typiquement, il peut exister des contours d’édition (utilisés pour faire des mesures, pour éditer des vecteurs,etc.) ou des symboles de sommets. Les objets du canevas sont généralement utilisés pour donner un retour visuel des outils de cartographique, par exemple, lorsqu’on créé un polygone, l’outil cartographique créé un contour d’édition qui affiche la forme actualisée du polygone. Tous les objets de canevas sont des sous-classes de QgsMapCanvasItem qui ajouter des fonctionnalités aux objets de la classe basique QGraphicsItem.

Pour résumer, l’architecture du canevas de carte repose sur trois concepts:

  • le canevas de carte — pour visualiser la carte

  • des objets de canevas — objets additionnels qui peuvent être affichés dans le canevas de carte

  • les outils cartographiques — pour interagir avec le canevas de carte

Intégrer un canevas de carte

Le canevas de carte est un objet comme tous les autres objets Qt, on peut donc l’utiliser simplement en le créant et en l’affichant:

canvas = QgsMapCanvas()
canvas.show()

Ce code créé une fenêtre indépendante avec un canevas de carte. Il peut également être intégré dans un objet ou une fenêtre existant. Lorsque vous utilisez des fichiers .ui avec Qt Designer, placez un QWidget dans le formulaire et transformez-le en une nouvelle classe. Utilisez QgsMapCanvas en tant que nom de classe et utilisez qgis.gui comme fichier d’en-tête. L’utilitaire pyuic4 le prendra en compte. C’est un moyen assez pratique pour intégrer un canevas. L’autre possibilité est d’écrire du code qui construira le canevas et les autres objets (comme fils de la fenêtre principale ou d’une boîte de dialogue) et de créer la 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 PyQt4.QtCore et Qt.white est une des instances prédéfinies de QColor.)

Il est maintenant temps d’ajouter des couches cartographiques. Nous allons d’abord ouvrir une couche et l’ajouter au registre de couches cartographiques. Ensuite, nous définirons l’emprise du canevas de carte et établirons la liste des couches pour le canevas

layer = QgsVectorLayer(path, name, provider)
if not layer.isValid():
  raise IOError, "Failed to open the layer"

# add layer to the registry
QgsMapLayerRegistry.instance().addMapLayer(layer)

# set extent to the extent of our layer
canvas.setExtent(layer.extent())

# set the map canvas layer set
canvas.setLayerSet([QgsMapCanvasLayer(layer)])

Après exécution de ces commandes, le canevas de carte devrait afficher la couche chargée.

Utiliser les outils cartographiques avec le canevas

L’exemple qui suit construit une fenêtre contenant un canevas de carte et des outils cartographiques sommaires pour se déplacer dans la carte et zoomer. Les actions sont créées pour l’activation de chaque outil: le déplacement est réalisé avec la classe QgsMapToolPan, le zoom avec une paire d’instances de la classe QgsMapToolZoom. Les actions sont paramétrées pour pouvoir être cochées et sont assignées ensuite aux outils pour gérer automatiquement l’état activé/désactivé des actions. Lorsqu’un outil cartographique est activé, son action est paramétrée comme sélectionnée et l’action du précédent outil cartographique est désélectionnée. Les outils cartographiques sont activés par la méthode setMapTool().

from qgis.gui import *
from PyQt4.QtGui import QAction, QMainWindow
from PyQt4.QtCore import SIGNAL, Qt, QString

class MyWnd(QMainWindow):
  def __init__(self, layer):
    QMainWindow.__init__(self)

    self.canvas = QgsMapCanvas()
    self.canvas.setCanvasColor(Qt.white)

    self.canvas.setExtent(layer.extent())
    self.canvas.setLayerSet([QgsMapCanvasLayer(layer)])

    self.setCentralWidget(self.canvas)

    actionZoomIn = QAction(QString("Zoom in"), self)
    actionZoomOut = QAction(QString("Zoom out"), self)
    actionPan = QAction(QString("Pan"), self)

    actionZoomIn.setCheckable(True)
    actionZoomOut.setCheckable(True)
    actionPan.setCheckable(True)

    self.connect(actionZoomIn, SIGNAL("triggered()"), self.zoomIn)
    self.connect(actionZoomOut, SIGNAL("triggered()"), self.zoomOut)
    self.connect(actionPan, SIGNAL("triggered()"), self.pan)

    self.toolbar = self.addToolBar("Canvas actions")
    self.toolbar.addAction(actionZoomIn)
    self.toolbar.addAction(actionZoomOut)
    self.toolbar.addAction(actionPan)

    # create the map tools
    self.toolPan = QgsMapToolPan(self.canvas)
    self.toolPan.setAction(actionPan)
    self.toolZoomIn = QgsMapToolZoom(self.canvas, False) # false = in
    self.toolZoomIn.setAction(actionZoomIn)
    self.toolZoomOut = QgsMapToolZoom(self.canvas, True) # true = out
    self.toolZoomOut.setAction(actionZoomOut)

    self.pan()

  def zoomIn(self):
    self.canvas.setMapTool(self.toolZoomIn)

  def zoomOut(self):
    self.canvas.setMapTool(self.toolZoomOut)

  def pan(self):
    self.canvas.setMapTool(self.toolPan)

Vous pouvez insérer le code ci-dessus dans un fichier, par exemple mywnd.py et l’exécuter dans la console Python de QGIS. Ce code mettra la couche actuellement sélectionnée dans un canevas de carte nouvellement créé

import mywnd
w = mywnd.MyWnd(qgis.utils.iface.activeLayer())
w.show()

Assurez-vous juste que le fichier mywnd.py est répertorié dans les chemins d’accès de Python (sys.path). Si ce n’est pas le cas, vous pouvez simplement l’y ajouter: sys.path.insert(0, '/my/path') — autrement, la déclaration d’import échouera, faute de trouver le module.

Contour d’édition et symboles de sommets

Utilisez les éléments du canevas de carte pour afficher des données supplémentaires au-dessus de la carte dans le canevas. Il est possible de créer ses propres classes d’éléments de canevas (traité ci-dessous) mais il existe deux classes d’éléments par défaut très utiles : QgsRubberBand pour dessiner des poli-lignes ou des polygones et QgsVertexMarker pour dessiner des points. Elles utilisent toutes les deux des coordonnées cartographiques et la forme est donc déplacée/ajustée automatiquement lorsque le canevas est déplacé ou zoomé.

Pour afficher une polyligne:

r = QgsRubberBand(canvas, False)  # False = not a polygon
points = [QgsPoint(-1, -1), QgsPoint(0, 1), QgsPoint(1, -1)]
r.setToGeometry(QgsGeometry.fromPolyline(points), None)

Pour afficher un polygone:

r = QgsRubberBand(canvas, True)  # True = a polygon
points = [[QgsPoint(-1, -1), QgsPoint(0, 1), QgsPoint(1, -1)]]
r.setToGeometry(QgsGeometry.fromPolygon(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 objets de canevas sont liés à la scène du canevas. Pour les cacher temporairement (et les afficher plus tard), utilisez les fonctions hide() et show(). Pour supprimer complètement un objet, 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).

Un contour d’édition peut être utilisé pour dessiner des points mais la classe QgsVertexMarker est plus appropriée pour ce travail (la classe QgsRubberBand se contentera de dessiner un rectangle autour du point désiré). Comment utiliser un symbole de sommet:

m = QgsVertexMarker(canvas)
m.setCenter(QgsPoint(0, 0))

Le code ci-dessus dessinera une croix rouge à la position [0,0]. Il est possible de personnaliser le type d’icône, la taille, la couleur et la taille du crayon:

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 des symboles de sommet et les supprimer du canevas, on peut utiliser les mêmes techniques que pour les contours d’édition.

Ecrire des outils cartographiques personnalisés

Vous pouvez écrire vos propres outils pour implémenter un comportement personnalisé aux actions lancées par les utilisateurs sur le canevas.

Les outils de carte doivent hériter de la classe QgsMapTool ou de toute autre classe dérivée et être sélectionnés comme outils actifs dans le canevas en utilisant la méthode setMapTool() que nous avons déjà rencontrée.

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.

class RectangleMapTool(QgsMapToolEmitPoint):
  def __init__(self, canvas):
      self.canvas = canvas
      QgsMapToolEmitPoint.__init__(self, self.canvas)
      self.rubberBand = QgsRubberBand(self.canvas, QGis.Polygon)
      self.rubberBand.setColor(Qt.red)
      self.rubberBand.setWidth(1)
      self.reset()

  def reset(self):
      self.startPoint = self.endPoint = None
      self.isEmittingPoint = False
      self.rubberBand.reset(QGis.Polygon)

  def canvasPressEvent(self, e):
      self.startPoint = self.toMapCoordinates(e.pos())
      self.endPoint = self.startPoint
      self.isEmittingPoint = True
      self.showRect(self.startPoint, self.endPoint)

  def canvasReleaseEvent(self, e):
      self.isEmittingPoint = False
      r = self.rectangle()
      if r is not None:
        print "Rectangle:", r.xMinimum(), r.yMinimum(), r.xMaximum(), r.yMaximum()

  def canvasMoveEvent(self, e):
      if not self.isEmittingPoint:
        return

      self.endPoint = self.toMapCoordinates(e.pos())
      self.showRect(self.startPoint, self.endPoint)

  def showRect(self, startPoint, endPoint):
      self.rubberBand.reset(QGis.Polygon)
      if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y():
        return

      point1 = QgsPoint(startPoint.x(), startPoint.y())
      point2 = QgsPoint(startPoint.x(), endPoint.y())
      point3 = QgsPoint(endPoint.x(), endPoint.y())
      point4 = QgsPoint(endPoint.x(), startPoint.y())

      self.rubberBand.addPoint(point1, False)
      self.rubberBand.addPoint(point2, False)
      self.rubberBand.addPoint(point3, False)
      self.rubberBand.addPoint(point4, True)    # true to update canvas
      self.rubberBand.show()

  def rectangle(self):
      if self.startPoint is None or self.endPoint is None:
        return None
      elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y() == self.endPoint.y():
        return None

      return QgsRectangle(self.startPoint, self.endPoint)

  def deactivate(self):
      QgsMapTool.deactivate(self)
      self.emit(SIGNAL("deactivated()"))

Ecrire des éléments de canevas de carte personnalisés

TODO

Comment créer un objet de canevas de carte ?

import sys
from qgis.core import QgsApplication
from qgis.gui import QgsMapCanvas

def init():
  a = QgsApplication(sys.argv, True)
  QgsApplication.setPrefixPath('/home/martin/qgis/inst', True)
  QgsApplication.initQgis()
  return a

def show_canvas(app):
  canvas = QgsMapCanvas()
  canvas.show()
  app.exec_()
app = init()
show_canvas(app)