9. Het kaartvenster gebruiken

Hint

De codesnippers op deze pagina hebben de volgende import nodig als u buiten de console van PyQGIS bent:

 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)

De widget Kaartvenster is waarschijnlijk de meest belangrijke widget in QGIS, omdat het de samengestelde kaart weergeeft uit op elkaar gelegde kaartlagen en interactie mogelijk maakt met de kaart en de lagen. Het kaartvenster geeft altijd een gedeelte van de kaart weer, gedefinieerd door het huidige bereik van het kaartvenster. De interactie wordt gedaan door middel van het gebruiken van gereedschappen voor de kaart: er zijn gereedschappen pannen, zoomen, identificeren van lagen, meten, bewerken van vector en andere. Soortgelijk aan andere grafische programma’s is er altijd één gereedschap actief en de gebruiker kan tussen de verschillende gereedschappen schakelen.

Het kaartvenster wordt geïmplementeerd met de klasse QgsMapCanvas in de module qgis.gui De implementatie is gebaseerd op het framework Qt Graphics View. Dat raamwerk verschaft in het algemeen een oppervlak en een weergave waar aangepaste grafische items zijn geplaatst en waarmee de gebruiker interactief kan werken. We gaan er van uit dat u bekend genoeg bent met Qt om de concepten van de grafische scene, weergave en items te begrijpen. Indien niet, zorg er dan voor overview of the framework te hebben gelezen.

Altijd als de kaart is verplaatst, is in-/uitgezoomd (of enkele andere acties die een verversing activeren), wordt de kaart opnieuw gerenderd binnen het huidige bereik. De lagen worden gerenderd naar een afbeelding (met behulp van de klasse QgsMapRendererJob) en die afbeelding wordt weergegeven in het kaartvenster. De klasse QgsMapCanvas beheert ook het verversen van de gerenderde kaart. Naast dit item, dat optreedt als een achtergrond, kunnen er meer items voor het kaartvenster zijn.

Typische items voor het kaartvenster zijn elastieken banden (gebruikt voor meten, bewerken van vectoren etc.) of markeringen van punten. De items voor het kaartvenster worden gewoonlijk gebruikt om een bepaalde visuele terugkoppeling te geven voor gereedschappen voor de kaart, bijvoorbeeld, bij het maken van een nieuwe polygoon, maakt het gereedschap voor de kaart een item elastieken band die de huidige vorm van de polygoon weergeeft. Alle items voor het kaartvenster zijn sub-klassen van QgsMapCanvasItem die iets meer functionaliteit toevoegt aan de basisobjecten QGraphicsItem.

Samenvattend, de architectuur van het kaartvenster bestaat uit drie concepten:

  • kaartvenster — voor het bekijken van de kaart

  • items voor het kaartvenster — aanvullende items die kunnen worden weergegeven in het kaartvenster

  • gereedschappen voor de kaart — voor interactie met het kaartvenster

9.1. Kaartvenster inbedden

Kaartvenster is een widget net als elk ander widget van Qt, dus het gebruiken ervan is zo eenvoudig als het maken en weergeven ervan.

canvas = QgsMapCanvas()
canvas.show()

Dit produceert een zelfstandig venster met een kaartvenster. Het kan ook worden ingebed in een bestaand widget of venster. Plaats een QWidget op het formulier en promoveer dat tot een nieuwe klasse: stel QgsMapCanvas in als naam voor de klasse en stel qgis.gui in als kopbestand. De functionaliteit pyuic5 zal er zorg voor dragen. Dit is een handige manier om het kaartvenster in te bedden. De andere mogelijkheid is om handmatig de code te schrijven door het kaartvenster en andere widgets (als kinderen van een hoofdvenster of dialoogvenster) te construeren en een lay-out te maken.

Standaard heeft kaartvenster een zwarte achtergrond en gebruikt geen anti-aliasing. Een witte achtergrond instellen en anti-aliasing inschakelen voor glad renderen

canvas.setCanvasColor(Qt.white)
canvas.enableAntiAliasing(True)

(Voor het geval u zich dat afvraagt, Qt komt van de module PyQt.QtCore en Qt.white is één van de voorgedefinieerde instanties van QColor.)

Nu is het tijd om enkele kaartlagen toe te voegen. We zullen eerst een laag openen en die toevoegen aan het huidige project. Daarna zullen we het bereik van het kaartvenster instellen en de lijst met lagen voor het kaartvenster.

 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])

Nadat deze opdrachten zijn uitgevoerd, zou het kaartvenster de laag moeten weergeven die u heeft geladen.

9.2. Elastieken banden en markeringen voor punten

Gebruik items voor het kaartvenster om enkele aanvullende gegevens bovenop de kaart in het kaartvenster weer te geven. Het is mogelijk om aangepaste klassen voor items voor het kaartvenster te maken (hieronder behandeld), er zijn voor het gemak echter twee handige klassen voor items voor het kaartvenster: QgsRubberBand voor het tekenen van polylijnen of polygonen, en QgsVertexMarker voor het tekenen van punten. Zij werken beide met coördinaten op de kaart, dus de vorm wordt automatisch verplaatst/geschaald als het kaartvenster wordt verschoven of als er wordt gezoomd.

Een polylijn weergeven:

r = QgsRubberBand(canvas, QgsWkbTypes.LineGeometry)  # line
points = [QgsPoint(-100, 45), QgsPoint(10, 60), QgsPoint(120, 45)]
r.setToGeometry(QgsGeometry.fromPolyline(points), None)

Een polygoon weregeven

r = QgsRubberBand(canvas, QgsWkbTypes.PolygonGeometry)  # polygon
points = [[QgsPointXY(-100, 35), QgsPointXY(10, 50), QgsPointXY(120, 35)]]
r.setToGeometry(QgsGeometry.fromPolygonXY(points), None)

Onthoud dat de punten voor polygoon geen platte lijst is: in feite is het een lijst van ringen die lineaire ringen van de polygoon bevat: de eerste ring is de buitenste grens, verdere (optionele) ringen corresponderen met gaten in de polygoon.

Elastieken banden maken enige aanpassingen mogelijk, namelijk om hun kleur en lijndikte te wijzigen

r.setColor(QColor(0, 0, 255))
r.setWidth(3)

De items voor het kaartvenster zijn gebonden aan de scene van het kaartvenster. Gebruik de combinatie hide() en show() om ze tijdelijk te verbergen (en weer opnieuw weer te geven). U moet het uit de scene van het kaartvenster verwijderen om het item volledig te verwijderen

canvas.scene().removeItem(r)

(in C++ is het mogelijk het item eenvoudigweg te verwijderen, in Python echter zou del r slechts de verwijzing verwijderen en zou het object nog steeds bestaan omdat het eigendom is van het kaartvenster)

Een elastieken band kan ook gebruikt worden om punten te tekenen, maar de klasse QgsVertexMarker is beter geschikt hiervoor (QgsRubberBand zou alleen een rechthoek rondom het gewenste punt tekenen).

U kunt de markering voor punten als volgt gebruiken:

m = QgsVertexMarker(canvas)
m.setCenter(QgsPointXY(10,40))

Dit zal een rood kruis tekenen op de positie [10,45]. Het is mogelijk om het type pictogram, de grootte, de kleur en de dikte van de pen aan te passen

m.setColor(QColor(0, 255, 0))
m.setIconSize(5)
m.setIconType(QgsVertexMarker.ICON_BOX) # or ICON_CROSS, ICON_X
m.setPenWidth(3)

Gebruik dezelfde methode als voor elastieken banden om markeringen voor punten tijdelijk te verbergen en ze uit het kaartvenster te verwijderen.

9.3. Gereedschappen voor de kaart gebruiken in het kaartvenster

Het volgende voorbeeld maakt een venster dat een kaartvenster bevat en basisgereedschappen voor het verschuiven van en zoomen op de kaart. Acties zijn gemaakt voor het activeren van elk gereedschap: verschuiven (pannen) wordt gedaan met QgsMapToolPan, in/uitzoomen met een paar instances van QgsMapToolZoom. De acties zijn ingesteld als te selecteren en later toegewezen aan het gereedschap om de automatische afhandeling van de status geselecteerd/niet geselecteerd van de acties mogelijk te maken – wanneer een gereedschap voor de kaart wordt geactiveerd, wordt de actie daarvan gemarkeerd als geselecteerd en de actie van het vorige gereedschap voor de kaart wordt gedeselecteerd. De gereedschappen voor de kaart worden geactiveerd met behulp van de methode 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)

U kunt bovenstaande code proberen in de bewerker van de console voor Python. Voeg de volgende regels toe om de klasse MyWnd te instantiëren om het kaartvenster te activeren. Dat zal de huidige geselecteerde laagr in het nieuw gemaakte kaartvenster renderen

w = MyWnd(iface.activeLayer())
w.show()

9.3.1. Een object selecteren met QgsMapToolIdentifyFeature

U kunt het kaartgereedschap QgsMapToolIdentifyFeature gebruiken om de gebruiker te vragen een object te selecteren dat moet worden verzonden naar een later aan te roepen functie.

 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.3.2. Items toevoegen aan het contextmenu van het kaartvenster

Interactie met het kaartvenster kan ook worden uitgevoerd via items die zou hebben toegevoegd tot het contextmenu daarvan met het signaal contextMenuAboutToShow.

De volgende code voegt de actie My menu ► My Action naast de standaard items in als u met rechts klikt in het kaartvenster.

1# a slot to populate the context menu
2def populateContextMenu(menu: QMenu, event: QgsMapMouseEvent):
3    subMenu = menu.addMenu('My Menu')
4    action = subMenu.addAction('My Action')
5    action.triggered.connect(lambda *args:
6                             print(f'Action triggered at {event.x()},{event.y()}'))
7
8canvas.contextMenuAboutToShow.connect(populateContextMenu)
9canvas.show()

9.4. Aangepaste gereedschappen voor de kaart schrijven

U kunt aangepaste gereedschappen schrijven, om een aagepast gedrag te implementeren voor acties die door gebruikers op het kaartvenster worden uitgevoerd.

Gereedschappen voor de kaart zouden moeten erven van de klasse QgsMapTool of een daarvan afgeleide klasse, en in het kaartvenster moeten worden geselecteerd als actief gereedschap met behulp van de methode setMapTool() zoals we al eerder hebben gezien.

Hier is een voorbeeld van een gereedschap voor de kaart dat het mogelijk maakt een rechthoekig bereik te definiëren door te klikken en te slepen in het kaartvenster. Wanneer de rechthoek is gedefinieerd, zal het de coördinaten voor de begrenzing afdrukken in de console. Het gebruikt de elementen voor elastieken banden zoals eerder beschreven om de geselecteerde rechthoek weer te geven als die wordt gedefinieerd.

 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. Aangepaste items voor het kaartvenster schrijven

Hier is een voorbeeld van een aangepast item voor het kaartvenster dat een cirkel tekent:

 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)