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