9. Utilizzare l’Area di Mappa

Suggerimento

I frammenti di codice di questa pagina necessitano delle seguenti importazioni se sei al di fuori della console pyqgis:

 1from qgis.PyQt.QtGui import (
 2    QColor,
 3)
 4
 5from qgis.PyQt.QtCore import Qt, QRectF
 6
 7from qgis.core import (
 8    QgsVectorLayer,
 9    QgsPoint,
10    QgsPointXY,
11    QgsProject,
12    QgsGeometry,
13    QgsMapRendererJob,
14    QgsWkbTypes,
15)
16
17from qgis.gui import (
18    QgsMapCanvas,
19    QgsVertexMarker,
20    QgsMapCanvasItem,
21    QgsRubberBand,
22)

Il widget Mappa è probabilmente il widget più importante di QGIS, perché mostra la mappa composta da layer sovrapposti e consente l’interazione con la mappa e i layer. L’area di disegno della mappa mostra sempre una parte della mappa definita dall’estensione corrente dell’area. L’interazione avviene attraverso l’uso di strumenti mappa: ci sono strumenti per la panoramica, lo zoom, l’identificazione dei layer, la misurazione, l’editing vettoriale e altri. Come in altri programmi di grafica, c’è sempre uno strumento attivo e l’utente può passare da uno strumento all’altro.

L’area di mappa è implementata con la classe QgsMapCanvas nel modulo qgis.gui. L’implementazione è basata sul framework Qt Graphics View. Questo framework fornisce generalmente una superficie e una vista in cui vengono inseriti elementi grafici personalizzati con cui l’utente può interagire. Si presuppone che si abbia abbastanza familiarità con Qt da comprendere i concetti di scena grafica, vista e oggetti. In caso contrario, si consiglia di leggere overview of the framework.

Ogni volta che la mappa viene spostata, ingrandita o rimpicciolita (o qualsiasi altra azione che attivi un refresh), la mappa viene nuovamente visualizzata all’interno dell’estensione corrente. I layer vengono resi in un’immagine (utilizzando la classe QgsMapRendererJob) e l’immagine viene visualizzata sulla area mappa. La classe QgsMapCanvas controlla anche il refresh della mappa visualizzata. Oltre a questo elemento che funge da sfondo, ci possono essere altri oggetti della area mappa.

I tipici elementi della area mappa sono gli elastici (usati per la misurazione, la modifica vettoriale e così via) o i marcatori di vertici. Gli oggetti mappa sono solitamente utilizzati per fornire un feedback visivo agli strumenti di mappa; ad esempio, quando si crea un nuovo poligono, lo strumento di mappa crea un elemento mappa ad elastico che mostra la forma corrente del poligono. Tutti gli oggetti in mappa delle mappe sono sottoclassi di QgsMapCanvasItem che aggiungono alcune funzionalità agli oggetti QGraphicsItem di base.

In sintesi, l’architettura dell’area mappa si compone di tre concetti:

  • area mappa — per la visualizzazione della mappa

  • oggetti dell’area mappa — oggetti aggiuntivi che possono essere visualizzati sull’area mappa

  • strumenti mappa — per interagire con la area mappa

9.1. Incorporare l’area mappa

L’area di disegno della mappa è un widget come qualsiasi altro widget di Qt, quindi utilizzarlo è semplice come crearlo e visualizzarlo.

canvas = QgsMapCanvas()
canvas.show()

Produce una finestra indipendente con l’area di disegno della mappa. Può anche essere incorporata in un widget o in una finestra esistente. Quando si usano i file .ui e Qt Designer, posizionare un QWidget sul modulo e promuoverlo a una nuova classe: impostare QgsMapCanvas come nome della classe e impostare qgis.gui come file di intestazione. L’utilità pyuic5 se ne occuperà. Questo è un modo molto comodo di incorporare l’area mappa. L’altra possibilità è scrivere manualmente il codice per costruire l’area di disegno della mappa e altri widget (come figli di una finestra principale o di una finestra di dialogo) e creare un layout.

Per impostazione predefinita, l’area mappa ha uno sfondo nero e non utilizza l’anti-aliasing. Per impostare uno sfondo bianco e abilitare l’anti-aliasing per una visualizzazione ottimale

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

(Nel caso ve lo stiate chiedendo, Qt proviene dal modulo PyQt.QtCore e Qt.white è una delle istanze predefinite di QColor).

Ora è il momento di aggiungere alcuni layer della mappa. Per prima cosa apriremo un layer e lo aggiungeremo al progetto corrente. Quindi si imposterà l’estensione della area mappa e si imposterà l’elenco dei layer per tale area.

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

Dopo aver eseguito questi comandi, l’area di disegno dovrebbe mostrare il layer caricato.

9.2. Bande elastiche e marcatori di vertice

Per mostrare alcuni dati aggiuntivi sopra la mappa nell’area di disegno della mappa, usa oggetti di mappa. Puoi creare classi di oggetti mappa personalizzate (descritte di seguito), tuttavia esistono due classi di oggetti mappa utili per comodità: QgsRubberBand per disegnare polilinee o poligoni e QgsVertexMarker per disegnare punti. Entrambi funzionano con le coordinate della mappa, quindi la forma viene spostata/ridimensionata automaticamente quando l’area mappa viene spostata o ingrandita.

Per mostrare una polilinea:

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

Per mostrare un poligono

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

Si noti che i punti per il poligono non sono un semplice elenco: si tratta infatti di un elenco di anelli contenenti anelli lineari del poligono: il primo anello è il bordo esterno, gli altri anelli (facoltativi) corrispondono ai fori del poligono.

Le bande ad elastico consentono una certa personalizzazione, ovvero la modifica del colore e della larghezza della linea.

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

Gli oggetti dell’area di disegno mappa sono legati alla scena dell’area di disegno. Per nasconderli temporaneamente (e mostrarli di nuovo), utilizza le combinazioni hide() e show(). Per rimuovere completamente l’elemento, devi rimuoverlo dalla scena del dell’area di disegno

canvas.scene().removeItem(r)

(in C++ è possibile cancellare semplicemente l’oggetto, ma in Python del r cancellerebbe solo il riferimento e l’oggetto continuerebbe a esistere, essendo di proprietà dell’area mappa).

Gli elastici possono essere usati anche per disegnare punti, ma la classe QgsVertexMarker è più adatta a questo scopo (QgsRubberBand disegnerebbe solo un rettangolo attorno al punto desiderato).

Puoi utilizzare il marcatore di vertici in questo modo:

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

Questo disegnerà una croce rossa sulla posizione [10,45]. È possibile personalizzare il tipo di icona, le dimensioni, il colore e la larghezza della penna.

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

Per nascondere temporaneamente i marcatori di vertici e rimuoverli dall” area di disegno, utilizza gli stessi metodi utilizzati per gli elastici.

9.3. Utilizzo degli strumenti di mappa con con l’area mappa

L’esempio seguente costruisce una finestra che contiene una area mappa e gli strumenti di base per il panning e lo zoom della mappa. Vengono create azioni per l’attivazione di ogni strumento: la panoramica viene eseguita con QgsMapToolPan, lo zoom in/out con una coppia di istanze QgsMapToolZoom. Le azioni sono impostate come controllabili e successivamente assegnate agli strumenti, per consentire la gestione automatica dello stato controllato/non controllato delle azioni: quando uno strumento di mappa viene attivato, la sua azione viene contrassegnata come selezionata e l’azione dello strumento di mappa precedente viene deselezionata. Gli strumenti delle mappe vengono attivati utilizzando il metodo 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)

Puoi provare il codice precedente nell’editor della console di Python. Per richiamare la finestra dell’are di disegno mappa, aggiungi le righe seguenti per istanziare la classe MyWnd. Esse visualizzeranno il layer attualmente selezionato sulla area di disegno appena creata

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

9.3.1. Seleziona un elemento usando QgsMapToolIdentifyFeature

Puoi utilizzare lo strumento mappa QgsMapToolIdentifyFeature per chiedere all’utente di selezionare un elemento che sarà inviato a una funzione di callback.

 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. Scrivere Strumenti Mappa Personalizzati

Puoi scrivere strumenti personalizzati, per implementare un comportamento personalizzato alle azioni eseguite dagli utenti sull’area di disegno della mappa.

Gli strumenti di mappa devono essere ereditati dalla classe QgsMapTool, o da qualsiasi classe derivata, e devono essere selezionati come strumenti attivi nell’area di disegno utilizzando il metodo setMapTool(), come abbiamo già visto.

Ecco un esempio di strumento mappa che consente di definire un’estensione rettangolare facendo clic e trascinando sull’area di disegno. Quando il rettangolo è definito, ne stampa le coordinate di confine nella console. Utilizza gli elementi a elastico descritti in precedenza per mostrare il rettangolo selezionato mentre viene definito.

 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 = QgsPoint(startPoint.x(), startPoint.y())
42    point2 = QgsPoint(startPoint.x(), endPoint.y())
43    point3 = QgsPoint(endPoint.x(), endPoint.y())
44    point4 = QgsPoint(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. Scrittura Oggetti Personalizzati Disegno Mappa

Ecco un esempio di un oggetto personalizzato che disegna un cerchio sull’area di disegno mappa:

 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)