9. Usando el Lienzo de Mapa

Consejo

Los fragmentos de código en esta página necesitan las siguientes adiciones si está fuera de la consola de 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)

El widget del lienzo del mapa es probablemente el widget más importante dentro de QGIS porque muestra el mapa integrado de capas de mapas superpuestos y permite la interacción con el mapa y las capas. El lienzo muestra siempre una parte del mapa definido por el alcance del lienzo actual. La interacción se realiza mediante el uso de herramientas de mapa: hay herramientas para desplazamiento, zum, la identificación de las capas, de medida, para editar vectores y otros. Al igual que en otros programas de gráficos, siempre hay una herramienta activa y el usuario puede cambiar entre las herramientas disponibles.

El lienzo del mapa está implementado con la clase QgsMapCanvas en el módulo qgis.gui . La implementación se basa en el marco Qt Graphics View. Este marco generalmente proporciona una superficie y una vista donde se colocan elementos gráficos personalizados y el usuario puede interactuar con ellos. Asumiremos que está lo suficientemente familiarizado con Qt para comprender los conceptos de escena, vista y elementos gráficos. Si no es así, lea la descripción general del marco. <https://doc.qt.io/qt-5/graphicsview.html>`_.

Cada vez que el mapa se ha desplazado, ampliado / reducido (o alguna otra acción que desencadena una actualización), el mapa se representa nuevamente dentro de la extensión actual. Las capas se representan en una imagen (utilizando la clase QgsMapRendererJob) y esa imagen se muestra en el lienzo. La clase QgsMapCanvas También controla la actualización del mapa representado. Además de este elemento que actúa como fondo, puede haber más elementos de lienzo de mapas.

Los elementos típicos del lienzo del mapa son bandas elásticas (utilizadas para medir, editar vectores, etc.) o marcadores de vértice. Los elementos del lienzo generalmente se usan para dar retroalimentación visual a las herramientas de mapa, por ejemplo, al crear un nuevo polígono, la herramienta de mapa crea un elemento de lienzo con banda elástica que muestra la forma actual del polígono. Todos los elementos del lienzo del mapa son subclases de QgsMapCanvasItem que agrega más funcionalidad a los objetos básicos QGraphicsItem.

Para resumir, la arquitectura del lienzo de mapa consiste en tres conceptos:

  • lienzo de mapa — para la visualización del mapa

  • Los elementos de lienzo de mapa — los elementos adicionales que se pueden desplegar en un lienzo de mapa

  • herramientas de mapa — para interactuar con el lienzo del mapa

9.1. Lienzo de mapa insertado

Map Canvas es un widget como cualquier otro widget de Qt, por lo que usarlo es tan simple como crearlo y mostrarlo.

canvas = QgsMapCanvas()
canvas.show()

Esto produce una ventana independiente con el lienzo de mapa. Puede también ser incrustado en un widget existente o ventana. Al utilizar archivo ui y Qt Designer, coloque un QWidget sobre el formulario y promuévalo a una nueva clase: establezca QgsMapCanvas como nombre de clase y qgis.gui como archivo de encabezado. La utilidad pyuic5 se hará cargo de ella. Esta es una forma conveniente de incrustar el lienzo. La otra posibilidad es escribir manualmente el código para construir el lienzo del mapa y otros widgets (como hijos de una ventana principal o diálogo) y crea un diseño.

Por defecto, el lienzo de mapa tiene un fondo negro y no utiliza anti-aliasing. Para establecer el fondo blanco y habilitar el anti-aliasing para suavisar la presentación

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

(En caso de que se esté preguntando, Qt viene del modulo PyQt.QtCore y Qt.white es uno de lo que predefine las instancias QColor.)

Ahora es el momento de agregar algunas capas del mapa. Primero abriremos una capa y la agregaremos al proyecto actual. Luego estableceremos la extensión del lienzo y la lista de capas para el lienzo.

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

Después de ejecutar estos comandos, el lienzo debe mostrar la capa que se ha cargado.

9.2. Bandas elásticas y marcadores de vértices

Para mostrar algunos datos adicionales en la parte superior del mapa en el lienzo, utilice los elementos del lienzo de mapa. Es posible crear clases de elementos del lienzo personalizada (cubiertas más abajo), sin embargo, hay dos clases de elementos de lienzo útiles para mayor comodidad QgsRubberBand para dibujar polilíneas o polígonos, y QgsVertexMarker para dibujar puntos. Ambos trabajan con coordenadas de mapa, por lo que la figura se mueve/ se escala de forma automática cuando el lienzo está siendo desplazado o haciendo zum.

Para mostrar una polilínea:

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

Para mostrar un polígono

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

Tenga en cuenta que los puntos de polígonos no es una lista simple: de hecho, es una lista de anillos que contienen lista de anillos del polígono: el primer anillo es el borde exterior, anillos adicionales (opcional) corresponden a los agujeros en el polígono.

Las bandas elásticas permiten algún tipo de personalizacion, es decir, para cambiar su color o ancho de línea

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

Los elementos del lienzo están vinculados a la escena del lienzo. Para ocultarlos temporalmente (y mostrarlos nuevamente), use el combo hide() and show(). Para eliminar completamente el elemento, debe eliminarlo de la escena del lienzo.

canvas.scene().removeItem(r)

(en C++ es posible simplemente eliminar el elemento, sin embargo en Python del r sería simplemente suprimir la referencia y el objeto aún existirá ya que es propiedad del lienzo)

La banda de goma también se puede usar para dibujar puntos, pero la clase QgsVertexMarker Es más adecuada para esto (QgsRubberBand solo dibujaría un rectángulo alrededor del punto deseado).

Puede usar el marcador de vértices así:

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

Esto dibujará una cruz roja en la posición [10,45]. Es posible personalizar el tipo de icono, tamaño, color y ancho del lápiz.

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

Para ocultar temporalmente los marcadores de vértice y eliminarlos del lienzo, utilice los mismos métodos que para las gomas elásticas.

9.3. Utilizar las herramientas del mapa con el lienzo

El siguiente ejemplo construye una ventana que contiene un lienzo de mapa y herramientas de mapa básicas para la panorámica y el zoom del mapa. Las acciones se crean para la activación de cada herramienta: la panorámica se realiza con QgsMapToolPan, acercando/alejando con un par de instancias QgsMapToolZoom. Las acciones se configuran como verificables y luego se asignan a las herramientas para permitir el manejo automático del estado verificado / no verificado de las acciones: cuando una herramienta de mapa se activa, su acción se marca como seleccionada y la acción de la herramienta de mapa anterior se deselecciona. Las herramientas de mapa se activan usando setMapTool() method.

 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)

Puede probar el código anterior en el editor de la consola de Python. Para invocar la ventana del lienzo, agregue las siguientes líneas para crear una instancia de la clase ``MyWnd””. Representarán la capa seleccionada actualmente en el lienzo recién creado

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

9.3.1. Selecciona una entidad usando QgsMapToolIdentifyFeature

Puedes usar la herramienta del mapa QgsMapToolIdentifyFeature para pedirle al usuario que seleccione una entidad que se enviará a una función de devolución de llamada.

 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. Add items to map canvas contextual menu

Interaction with map canvas can also be done through entries you may add to its contextual menu using the contextMenuAboutToShow signal.

The following code adds My menu ► My Action action next to default entries when you right-click over the map canvas.

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. Escribir herramientas de mapa personalizados

Puede escribir sus herramientas personalizadas, para implementar un comportamiento personalizado a las acciones realizadas por los usuarios en el lienzo.

Las herramientas de mapa deben heredar de QgsMapTool, clase o cualquier clase derivada, y seleccionada como herramientas activas en el lienzo utilizando el método setMapTool() como ya hemos visto.

Aquí esta un ejemplo de una herramienta de mapa para definir una extensión rectangular haciendo clic y arrastrando en el lienzo. Cuando se define el rectángulo, imprime su limite de coordenadas en la consola. Utiliza los elementos de la banda elástica descrita antes para mostrar el rectángulo seleccionado ya que se esta definiendo.

 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. Escribir elementos de lienzo de mapa personalizado

Aquí hay un ejemplo de un elemento de lienzo personalizado que dibuja un círculo:

 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)