Usando el Lienzo de Mapa

Advertencia

Despite our constant efforts, information beyond this line may not be updated for QGIS 3. Refer to https://qgis.org/pyqgis/master for the python API documentation or, give a hand to update the chapters you know about. Thanks.

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.

The map canvas is implemented with the QgsMapCanvas class in the qgis.gui module. The implementation is based on the Qt Graphics View framework. This framework generally provides a surface and a view where custom graphics items are placed and user can interact with them. We will assume that you are familiar enough with Qt to understand the concepts of the graphics scene, view and items. If not, please read the overview of the framework.

Whenever the map has been panned, zoomed in/out (or some other action that triggers a refresh), the map is rendered again within the current extent. The layers are rendered to an image (using the QgsMapRendererJob class) and that image is displayed on the canvas. The QgsMapCanvas class also controls refreshing of the rendered map. Besides this item which acts as a background, there may be more map canvas items.

Typical map canvas items are rubber bands (used for measuring, vector editing etc.) or vertex markers. The canvas items are usually used to give visual feedback for map tools, for example, when creating a new polygon, the map tool creates a rubber band canvas item that shows the current shape of the polygon. All map canvas items are subclasses of QgsMapCanvasItem which adds some more functionality to the basic QGraphicsItem objects.

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

Lienzo de mapa insertado

El lienzo de mapa es un widget como cualquier otro widget Qt, por lo que utilizarlo es tan sencillo 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 tiempo de añadir algunas capas. Primero, abriremos una capa y lo añadiremos al proyecto actual. A continuación, vamos a establecer la extensión del lienzo y la lista de capas para el lienzo

path_to_ports_layer = os.path.join(QgsProject.instance().homePath(),
                                   "data", "ports", "ports.shp")

vlayer = QgsVectorLayer(path_to_ports_layer, "Ports layer", "ogr")
if not vlayer.isValid():
    print("Layer failed to load!")

# add layer to the registry
QgsProject.instance().addMapLayer(vlayer)

# set extent to the extent of our layer
canvas.setExtent(vlayer.extent())

# set the map canvas layer set
canvas.setLayers([vlayer])

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

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, False)  # False = not a polygon
points = [QgsPoint(-100, 45), QgsPoint(10, 60), QgsPoint(120, 45)]
r.setToGeometry(QgsGeometry.fromPolyline(points), None)

Para mostrar un polígono

r = QgsRubberBand(canvas, True)  # True = a 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)

The canvas items are bound to the canvas scene. To temporarily hide them (and show them again), use the hide() and show() combo. To completely remove the item, you have to remove it from the scene of the canvas

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)

Rubber band can be also used for drawing points, but the QgsVertexMarker class is better suited for this (QgsRubberBand would only draw a rectangle around the desired point).

You can use the vertex marker like this:

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

This will draw a red cross on position [10,45]. It is possible to customize the icon type, size, color and pen width

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

For temporary hiding of vertex markers and removing them from canvas, use the same methods as for rubber bands.

Utilizar las herramientas del mapa con el lienzo

The following example constructs a window that contains a map canvas and basic map tools for map panning and zooming. Actions are created for activation of each tool: panning is done with QgsMapToolPan, zooming in/out with a pair of QgsMapToolZoom instances. The actions are set as checkable and later assigned to the tools to allow automatic handling of checked/unchecked state of the actions – when a map tool gets activated, its action is marked as selected and the action of the previous map tool is deselected. The map tools are activated using setMapTool() method.

from qgis.gui import *
from qgis.PyQt.QtWidgets import QAction, QMainWindow
from qgis.PyQt.QtCore import Qt

class MyWnd(QMainWindow):
    def __init__(self, layer):
        QMainWindow.__init__(self)

        self.canvas = QgsMapCanvas()
        self.canvas.setCanvasColor(Qt.white)

        self.canvas.setExtent(layer.extent())
        self.canvas.setLayers([layer])

        self.setCentralWidget(self.canvas)

        self.actionZoomIn = QAction("Zoom in", self)
        self.actionZoomOut = QAction("Zoom out", self)
        self.actionPan = QAction("Pan", self)

        self.actionZoomIn.setCheckable(True)
        self.actionZoomOut.setCheckable(True)
        self.actionPan.setCheckable(True)

        self.actionZoomIn.triggered.connect(self.zoomIn)
        self.actionZoomOut.triggered.connect(self.zoomOut)
        self.actionPan.triggered.connect(self.pan)

        self.toolbar = self.addToolBar("Canvas actions")
        self.toolbar.addAction(self.actionZoomIn)
        self.toolbar.addAction(self.actionZoomOut)
        self.toolbar.addAction(self.actionPan)

        # create the map tools
        self.toolPan = QgsMapToolPan(self.canvas)
        self.toolPan.setAction(self.actionPan)
        self.toolZoomIn = QgsMapToolZoom(self.canvas, False) # false = in
        self.toolZoomIn.setAction(self.actionZoomIn)
        self.toolZoomOut = QgsMapToolZoom(self.canvas, True) # true = out
        self.toolZoomOut.setAction(self.actionZoomOut)

        self.pan()

    def zoomIn(self):
        self.canvas.setMapTool(self.toolZoomIn)

    def zoomOut(self):
        self.canvas.setMapTool(self.toolZoomOut)

    def pan(self):
        self.canvas.setMapTool(self.toolPan)

You can try the above code in the Python console editor. To invoke the canvas window, add the following lines to instantiate the MyWnd class. They will render the currently selected layer on the newly created canvas

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

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.

Map tools should inherit from the QgsMapTool, class or any derived class, and selected as active tools in the canvas using the setMapTool() method as we have already seen.

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.

class RectangleMapTool(QgsMapToolEmitPoint):
  def __init__(self, canvas):
    self.canvas = canvas
    QgsMapToolEmitPoint.__init__(self, self.canvas)
    self.rubberBand = QgsRubberBand(self.canvas, True)
    self.rubberBand.setColor(Qt.red)
    self.rubberBand.setWidth(1)
    self.reset()

  def reset(self):
    self.startPoint = self.endPoint = None
    self.isEmittingPoint = False
    self.rubberBand.reset(True)

  def canvasPressEvent(self, e):
    self.startPoint = self.toMapCoordinates(e.pos())
    self.endPoint = self.startPoint
    self.isEmittingPoint = True
    self.showRect(self.startPoint, self.endPoint)

  def canvasReleaseEvent(self, e):
    self.isEmittingPoint = False
    r = self.rectangle()
    if r is not None:
      print("Rectangle:", r.xMinimum(),
            r.yMinimum(), r.xMaximum(), r.yMaximum()
           )

  def canvasMoveEvent(self, e):
    if not self.isEmittingPoint:
      return

    self.endPoint = self.toMapCoordinates(e.pos())
    self.showRect(self.startPoint, self.endPoint)

  def showRect(self, startPoint, endPoint):
    self.rubberBand.reset(QGis.Polygon)
    if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y():
      return

    point1 = QgsPoint(startPoint.x(), startPoint.y())
    point2 = QgsPoint(startPoint.x(), endPoint.y())
    point3 = QgsPoint(endPoint.x(), endPoint.y())
    point4 = QgsPoint(endPoint.x(), startPoint.y())

    self.rubberBand.addPoint(point1, False)
    self.rubberBand.addPoint(point2, False)
    self.rubberBand.addPoint(point3, False)
    self.rubberBand.addPoint(point4, True)    # true to update canvas
    self.rubberBand.show()

  def rectangle(self):
    if self.startPoint is None or self.endPoint is None:
      return None
    elif (self.startPoint.x() == self.endPoint.x() or \
          self.startPoint.y() == self.endPoint.y()):
      return None

      return QgsRectangle(self.startPoint, self.endPoint)

  def deactivate(self):
    QgsMapTool.deactivate(self)
    self.deactivated.emit()

Escribir elementos de lienzo de mapa personalizado

PENDIENTE:

cómo crear un elemento de lienzo de mapa

import sys
from qgis.core import QgsApplication
from qgis.gui import QgsMapCanvas

def init():
  a = QgsApplication(sys.argv, True)
  QgsApplication.setPrefixPath('/home/martin/qgis/inst', True)
  QgsApplication.initQgis()
  return a

def show_canvas(app):
  canvas = QgsMapCanvas()
  canvas.show()
  app.exec_()
app = init()
show_canvas(app)