9. Usando a Tela do Mapa

Dica

Os trechos de código desta página precisam das seguintes importações se você estiver fora do console do 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)

O widget de tela Mapa é provavelmente o widget mais importante no QGIS, pois mostra o mapa composto de camadas de mapas sobrepostas e permite a interação com o mapa e as camadas. A tela sempre mostra uma parte do mapa definida pela extensão atual da tela. A interação é feita através do uso de ferramentas de mapa: existem ferramentas para panorâmica, zoom, identificação de camadas, medição, edição de vetores e outras. Semelhante a outros programas gráficos, sempre há uma ferramenta ativa e o usuário pode alternar entre as ferramentas disponíveis.

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.

Sempre que o mapa for panorâmico, tem o zoom aumentado/diminuído (ou alguma outra ação que desencadeia uma atualização), o mapa é renderizado novamente na extensão atual. As camadas são renderizadas em uma imagem (usando a classe QgsMapRendererJob) e essa imagem é exibida na tela. A classe QgsMapCanvas classe também controla a atualização do mapa renderizado. Além deste item que atua como plano de fundo, pode haver mais itens de tela de mapa.

Os itens típicos da tela de mapa são rubber bands (usados ​​para medição, edição de vetor etc.) ou marcadores de vértice. Os itens de tela geralmente são usados ​​para fornecer feedback visual para as ferramentas de mapa; por exemplo, ao criar um novo polígono, a ferramenta de mapa cria um item de tela rubber band que mostra a forma atual do polígono. Todos os itens da tela de mapa são subclasses de QgsMapCanvasItem, que adiciona mais funcionalidade aos objetos básicos QGraphicsItem.

Para resumir, a arquitetura do mapa na tela são constituídas por três conceitos:

  • tela do mapa — para visualização do mapa

  • itens da tela de mapa — itens adicionais que podem ser mostrados na tela do mapa

  • ferramentas de mapa — para interação com a tela do mapa

9.1. Incorporar o Mapa da Tela

A tela de mapa é um widget como qualquer outro widget Qt, portanto, usá-lo é tão simples quanto criar e mostrar.

canvas = QgsMapCanvas()
canvas.show()

Isso produz uma janela independente com tela de mapa. Também pode ser incorporado a um widget ou janela existente. Ao usar arquivos .ui e o Qt Designer, coloque um QWidget no formulário e promova-o para uma nova classe: defiina` QgsMapCanvas` como nome da classe e defina qgis.gui como arquivo de cabeçalho. O pyuic5 cuidará dele. Essa é uma maneira muito conveniente de incorporar a tela. A outra possibilidade é escrever manualmente o código para construir a tela do mapa e outros widgets (como filhos de uma janela ou caixa de diálogo principal) e criar um layout.

Por padrão, o mapa na tela tem fundo preto e não usa anti-aliasing. Para definir o fundo branco e permitir anti-aliasing para renderização suave

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

(Caso você esteja se perguntando, Qt vem do módulo PyQt.QtCore e Qt.white é uma das instâncias predefinidas do QColor).

Agora é hora de adicionar algumas camadas do mapa. Primeiro abriremos uma camada e a adicionaremos ao projeto atual. Em seguida, definiremos a extensão da tela e a lista de camadas para a tela.

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

Depois de executar esses comandos, a tela deve mostrar a camada que você carregou.

9.2. Bandas raster e fazedor de vértices

Para mostrar alguns dados adicionais na parte superior do mapa na tela, use os itens da tela do mapa. É possível criar classes de item de tela personalizadas (abordadas abaixo), no entanto, existem duas classes úteis de itens de tela por conveniência: QgsRubberBand para desenhar polilinhas ou polígonos e QgsVertexMarker para pontos de desenho. Os dois trabalham com coordenadas do mapa, para que a forma seja movida/redimensionada automaticamente quando a tela for panorâmica ou ampliada.

Para mostrar uma polilinha:

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

Para mostrar o poligono

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

Note-se que aponta para polígono não é uma lista simples: na verdade, é uma lista de anéis contendo anéis lineares do polígono: primeiro anel é a borda externa, ainda mais (opcional) anéis correspondem aos buracos no polígono.

As Bandas Raster permitem alguma personalização, ou seja, para mudar sua cor e linha de largura

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

Os itens da tela são vinculados à cena da tela. Para ocultá-los temporariamente (e mostrá-los novamente), use as combinações hide() e show(). Para remover completamente o item, você deve removê-lo da cena da tela

canvas.scene().removeItem(r)

(em C ++ é possível simplesmente apagar o item, no entanto, em Python `` del r`` seria apenas suprimir a referência eo objeto continuará a existir, uma vez que é de propriedade da tela)

Rubber band também pode ser usado para desenhar pontos, mas a classe QgsVertexMarker é mais adequada para isso (QgsRubberBand apenas desenha um retângulo ao redor do ponto desejado).

Você pode usar o marcador de vértice assim:

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

Isto irá desenhar uma cruz vermelha na posição [10,45]. É possível personalizar o tipo de ícone, tamanho, cor e largura da caneta

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

Para ocultar temporariamente os marcadores de vértice e removê-los da tela, use os mesmos métodos usados ​​para rubber bands.

9.3. Usando Ferramentas de Mapas na Tela

O exemplo a seguir constrói uma janela que contém uma tela de mapa e ferramentas básicas de mapa para panorâmica e zoom do mapa. As ações são criadas para a ativação de cada ferramenta: as panôramicas são feitas com QgsMapToolPan, aumentando/diminuindo o zoom com algumas instâncias QgsMapToolZoom. As ações são definidas como verificáveis ​​e, posteriormente, atribuídas às ferramentas para permitir o manuseio automático do estado marcado/desmarcado das ações - quando uma ferramenta de mapa é ativada, sua ação é marcada como selecionada e a ação da ferramenta de mapa anterior é desmarcada. As ferramentas de mapa são ativadas usando o método 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)

Você pode tentar o código acima no editor de console do Python. Para chamar a janela da tela, adicione as seguintes linhas para instanciar a classe MyWnd. Eles renderizarão a camada atualmente selecionada na tela recém-criada

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

9.3.1. Select a feature using QgsMapToolIdentifyFeature

You can use the map tool QgsMapToolIdentifyFeature for asking to the user to select a feature that will be sent to a callback function.

 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. Desenhar ferramenta de mapa personalizada

Você pode escrever suas ferramentas personalizadas, para implementar um comportamento personalizado nas ações executadas pelos usuários na tela.

As ferramentas de mapa devem herdar de QgsMapTool, classe ou qualquer classe derivada, e selecionadas como ferramentas ativas na tela usando o setMapTool () como já vimos.

Aqui está um exemplo de uma ferramenta de mapa que permite definir uma medida retangular, clicando e arrastando na tela. Quando o retângulo é definido, ele imprime coordena seu limite no console. Ele utiliza os elementos de banda de borracha descritos antes para mostrar o retângulo selecionado, uma vez que está a ser definida.

 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. Desenhar itens da tela do mapa

Aqui está um exemplo de um item de tela personalizado que desenha um 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)