Viktigt

Översättning är en gemenskapsinsats du kan gå med i. Den här sidan är för närvarande översatt till 100.00%.

9. Använda Map Canvas

Råd

Kodsnuttarna på den här sidan behöver följande import om du befinner dig utanför pyqgis-konsolen:

 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)

Widgeten Map canvas är förmodligen den viktigaste widgeten i QGIS eftersom den visar kartan sammansatt av överlagrade kartlager och tillåter interaktion med kartan och lagren. Kanvasen visar alltid en del av kartan som definieras av den aktuella kanvasträckningen. Interaktionen sker genom användning av kartverktyg: det finns verktyg för panorering, zoomning, identifiering av lager, mätning, vektorredigering och annat. I likhet med andra grafikprogram är det alltid ett verktyg som är aktivt och användaren kan växla mellan de tillgängliga verktygen.

Map Canvas implementeras med klassen QgsMapCanvas i modulen qgis.gui. Implementeringen är baserad på Qt Graphics View-ramverket. Detta ramverk tillhandahåller i allmänhet en yta och en vy där anpassade grafiska objekt placeras och användaren kan interagera med dem. Vi antar att du är tillräckligt bekant med Qt för att förstå begreppen grafikscen, vy och objekt. Om inte, läs gärna `översikten över ramverket <https://doc.qt.io/qt-5/graphicsview.html>`_.

När kartan har panorerats, zoomats in/ut (eller någon annan åtgärd som utlöser en uppdatering), renderas kartan igen inom den aktuella omfattningen. Lagren renderas till en bild (med hjälp av klassen QgsMapRendererJob) och den bilden visas på duken. Klassen QgsMapCanvas kontrollerar också uppfräschning av den återgivna kartan. Förutom det här objektet som fungerar som bakgrund kan det finnas fler map canvas-objekt.

Typiska objekt i kartans canvas är gummiband (används för mätning, vektorredigering etc.) eller vertexmarkörer. Canvasobjekten används vanligtvis för att ge visuell feedback till kartverktyg, t.ex. när en ny polygon skapas skapar kartverktyget ett canvasobjekt med gummiband som visar polygonens aktuella form. Alla kartans canvasobjekt är underklasser till QgsMapCanvasItem som lägger till lite mer funktionalitet till de grundläggande QGraphicsItem-objekten.

Sammanfattningsvis består Map Canvas-arkitekturen av tre koncept:

  • kartduk — för visning av kartan

  • map canvas items — ytterligare objekt som kan visas på kartbilden

  • kartverktyg — för interaktion med kartbilden

9.1. Inbäddning av Map Canvas

Map canvas är en widget som alla andra Qt-widgetar, så det är lika enkelt att använda den som att skapa och visa den.

canvas = QgsMapCanvas()
canvas.show()

Detta skapar ett fristående fönster med en kartbild. Det kan också bäddas in i en befintlig widget eller ett fönster. När du använder .ui-filer och Qt Designer, placera en QWidget på formuläret och befordra den till en ny klass: ange QgsMapCanvas som klassnamn och ange qgis.gui som header-fil. Verktyget pyuic5 kommer att ta hand om det. Detta är ett mycket bekvämt sätt att bädda in canvas. Den andra möjligheten är att manuellt skriva koden för att konstruera map canvas och andra widgets (som barn till ett huvudfönster eller en dialogruta) och skapa en layout.

Som standard har Map Canvas svart bakgrund och använder inte anti-aliasing. Så här ställer du in vit bakgrund och aktiverar anti-aliasing för smidig rendering

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

(Om du undrar så kommer Qt från modulen PyQt.QtCore och Qt.white är en av de fördefinierade QColor-instanserna)

Nu är det dags att lägga till några kartlager. Vi öppnar först ett lager och lägger till det i det aktuella projektet. Sedan ställer vi in canvasens utsträckning och listan med lager för canvasen.

 1vlayer = QgsVectorLayer("testdata/data/data.gpkg|layername=airports", "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])

När du har utfört dessa kommandon ska duken visa det lager som du har laddat.

9.2. Gummiband och spetsmarkörer

För att visa ytterligare data ovanpå kartan i canvas, använd map canvas-objekt. Det är möjligt att skapa egna klasser för canvasobjekt (beskrivs nedan), men det finns två användbara klasser för canvasobjekt för bekvämlighetens skull: QgsRubberBand för att rita polylinjer eller polygoner, och QgsVertexMarker för att rita punkter. De arbetar båda med kartkoordinater, så formen flyttas/skalas automatiskt när duken panoreras eller zoomas.

För att visa en polylinje:

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

För att visa en polygon

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

Observera att points for polygon inte är en vanlig lista: i själva verket är det en lista med ringar som innehåller linjära ringar av polygonen: den första ringen är den yttre gränsen, ytterligare (valfria) ringar motsvarar hål i polygonen.

Gummiband tillåter viss anpassning, nämligen att ändra färg och linjebredd

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

Canvas-objekten är bundna till canvas-scenen. För att tillfälligt dölja dem (och visa dem igen) använder du kombinationerna hide() och show(). För att helt ta bort objektet måste du ta bort det från canvasens scen

canvas.scene().removeItem(r)

(i C++ är det möjligt att bara ta bort objektet, men i Python skulle del r bara ta bort referensen och objektet kommer fortfarande att existera eftersom det ägs av canvas)

Gummiband kan också användas för att rita punkter, men klassen QgsVertexMarker är bättre lämpad för detta (QgsRubberBand skulle bara rita en rektangel runt den önskade punkten).

Du kan använda vertexmarkören så här:

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

Detta kommer att rita ett rött kors på position [10,45]. Det är möjligt att anpassa ikontyp, storlek, färg och pennbredd

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

Använd samma metoder som för gummiband för att tillfälligt dölja vertexmarkörer och ta bort dem från duken.

9.3. Använda kartverktyg med Canvas

I följande exempel konstrueras ett fönster som innehåller en kartduk och grundläggande kartverktyg för panorering och zoomning av kartan. Åtgärder skapas för aktivering av varje verktyg: panorering görs med QgsMapToolPan, zoomning in/ut med ett par QgsMapToolZoom-instanser. Åtgärderna är inställda som kontrollerbara och tilldelas senare verktygen för att möjliggöra automatisk hantering av åtgärdernas markerade/okryssade tillstånd – när ett kartverktyg aktiveras markeras dess åtgärd som vald och åtgärden för det föregående kartverktyget avmarkeras. Kartverktygen aktiveras med hjälp av setMapTool()-metoden.

 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)

Du kan prova ovanstående kod i Python-konsolredigeraren. För att anropa canvasfönstret, lägg till följande rader för att instansiera klassen MyWnd. De kommer att återge det för närvarande valda lagret på den nyskapade duken

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

9.3.1. Välj en funktion med hjälp av QgsMapToolIdentifyFeature

Du kan använda kartverktyget QgsMapToolIdentifyFeature för att be användaren att välja en funktion som skickas till en återuppringningsfunktion.

 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. Lägg till objekt i den kontextuella menyn i kartbilden

Interaktion med kartbilden kan också ske genom poster som du kan lägga till i dess kontextuella meny med hjälp av signalen contextMenuAboutToShow.

Följande kod lägger till My menu ► My Action action bredvid standardposter när du högerklickar över kartbilden.

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. Skriva anpassade kartverktyg

Du kan skriva dina egna verktyg för att implementera ett anpassat beteende för åtgärder som utförs av användare på Canvas.

Kartverktyg bör ärva från QgsMapTool, klassen eller någon härledd klass, och väljas som aktiva verktyg i canvas med setMapTool()-metoden som vi redan har sett.

Här är ett exempel på ett kartverktyg som gör det möjligt att definiera en rektangulär utsträckning genom att klicka och dra på duken. När rektangeln är definierad skrivs dess gränskoordinater ut i konsolen. Det använder de gummibandselement som beskrivs tidigare för att visa den valda rektangeln medan den definieras.

 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. Skriva anpassade Map Canvas-objekt

Här är ett exempel på ett anpassat canvasobjekt som ritar en cirkel:

 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)