Kaartvenster gebruiken

De widget Kaartvenster is waarschijnlijk de meest belangrijke widget in QGIS, omdat het de samengestelde kaart weergeeft uit op elkaar gelegde kaartlagen en interactie mogelijk maakt met de kaart en de lagen. Het kaartvenster geeft altijd een gedeelte van de kaart weer, gedefinieerd door het huidige bereik van het kaartvenster. De interactie wordt gedaan door middel van het gebruiken van gereedschappen voor de kaart: er zijn gereedschappen pannen, zoomen, identificeren van lagen, meten, bewerken van vector en andere. Soortgelijk aan andere grafische programma’s is er altijd één gereedschap actief en de gebruiker kan tussen de verschillende gereedschappen schakelen.

Kaartvenster wordt geïmplementeerd als klasse QgsMapCanvas in de module qgis.gui. De implementatie is gebaseerd op het framework Qt Graphics View. Dat raamwerk verschaft in het algemeen een oppervlak en een weergave waar aangepaste grafische items zijn geplaatst en waarmee de gebruiker interactief kan werken. We gaan er van uit dat u bekend genoeg bent met Qt om de concepten van de grafische scene, weergave en items te begrijpen. Indien niet, zorg er dan voor overview of the framework te hebben gelezen.

Wanneer de kaart wordt verschoven, in of uit wordt gezoomd (of een andere actie die een verversing activeert), wordt de kaart opnieuw gerenderd binnen de huidige inhoud. De lagen worden tot een afbeelding gerenderd (met behulp van de klasse QgsMapRenderer) en ie afbeelding wordt dan weergegeven in het kaartvenster. Het item voor de afbeelding (in termen van de grafische weergave van het framework van Qt) dat verantwoordelijk is voor het weergeven van de kaart is de klasse QgsMapCanvasMap. Deze klasse beheert ook het vernieuwen van de gerenderde kaart. Naast het item dat dat optreedt als een achtergrond, kunnen er meer items voor het kaartvenster zijn. Typische items voor het kaartvenster zijn elastieken banden (gebruikt voor meten, bewerken van vectoren etc.) of markeringen van punten. De items voor het kaartvenster worden gewoonlijk gebruikt om een bepaalde visuele terugkoppeling te geven voor gereedschappen voor de kaart, bijvoorbeeld, bij het maken van een nieuwe polygoon,maakt het gereedschap voor de kaart een item elastieken band die de huidige vorm van de polygoon weergeeft. Alle items voor het kaartvenster zijn sub-klassen van QgsMapCanvasItem die iets meer functionaliteit toevoegt aan de kasisobjecten QGraphicsItem.

Samenvattend, de architectuur van het kaartvenster bestaat uit drie concepten:

  • kaartvenster — voor het bekijken van de kaart

  • items voor het kaartvenster — aanvullende items die kunnen worden weergegeven in het kaartvenster

  • gereedschappen voor de kaart — voor interactie met het kaartvenster

Kaartvenster inbedden

Kaartvenster is een widget net als elk ander widget van Qt, dus het gebruiken ervan is zo eenvoudig als het maken en weergeven ervan

canvas = QgsMapCanvas()
canvas.show()

Dit produceert een zelfstandig venster met een kaartvenster. Het kan ook worden ingebed in een bestaand widget of venster. Plaats een QWidget op het formulier en promoveer dat tot een nieuwe klasse: stel QgsMapCanvas in als naam voor de klasse en stel qgis.gui in als kopbestand bij het gebruiken van .ui-bestanden en Qt Designer. De functionaliteit pyuic4 zal er zorg voor dragen. Dit is een handige manier om het kaartvenster in te bedden. De andere mogelijkheid is om handmatig de code te schrijven door het kaartvenster en andere widgets (als kinderen van een hoofdvenster of dialoogvenster) te construeren en een lay-out te maken.

Standaard heeft kaartvenster een zwarte achtergrond en gebruikt geen anti-aliasing. Een witte achtergrond instellen en anti-aliasing inschakelen voor glad renderen

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

(Voor het geval u zich dat afvraagt, Qt komt van de module PyQt4.QtCore en Qt.white is één van de voorgedefinieerde instances QColor.)

Nu is het tijd om enkele kaartlagen toe te voegen. We zullen eerst een laag openen en die toevoegen aan het register van de kaartlaag. Daarna zullen we het bereik van het kaartvenster instellen alsmede de lijst met lagen voor het kaartvenster

layer = QgsVectorLayer(path, name, provider)
if not layer.isValid():
  raise IOError, "Failed to open the layer"

# add layer to the registry
QgsMapLayerRegistry.instance().addMapLayer(layer)

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

# set the map canvas layer set
canvas.setLayerSet([QgsMapCanvasLayer(layer)])

Nadat deze opdrachten zijn uitgevoerd, zou het kaartvenster de laag moeten weergeven die u heeft geladen.

Gereedschappen voor de kaart gebruiken in het kaartvenster

Het volgende voorbeeld maakt een venster dat een kaartvenster bevat en basisgereedschappen voor het verschuiven van en zoomen op de kaart. Acties zijn gemaakt voor het activeren van elk gereedschap: verschuiven (pannen) wordt gedaan met QgsMapToolPan, in/uitzoomen met een paar instances van QgsMapToolZoom. De acties zijn ingesteld als te selecteren en later toegewezen aan het gereedschap om de automatische afhandeling van de status geselecteerd/niet geselecteerd van de acties mogelijk te maken – wanneer een gereedschap voor de kaart wordt geactiveerd, wordt de actie daarvan gemarkeerd als geselecteerd en de actie van het vorige gereedschap voor de kaart wordt gedeselecteerd. De gereedschappen voor de kaart worden geactiveerd met behulp van de methode setMapTool().

from qgis.gui import *
from PyQt4.QtGui import QAction, QMainWindow
from PyQt4.QtCore import SIGNAL, Qt, QString

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.setLayerSet([QgsMapCanvasLayer(layer)])

    self.setCentralWidget(self.canvas)

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

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

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

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

    # create the map tools
    self.toolPan = QgsMapToolPan(self.canvas)
    self.toolPan.setAction(actionPan)
    self.toolZoomIn = QgsMapToolZoom(self.canvas, False) # false = in
    self.toolZoomIn.setAction(actionZoomIn)
    self.toolZoomOut = QgsMapToolZoom(self.canvas, True) # true = out
    self.toolZoomOut.setAction(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)

U kunt de bovenstaande code plaatsen in een bestand, bijv. mywnd.py en het uitproberen in de console van Python binnen QGIS. Deze code zal de huidige geselecteerde laag in een nieuw gemaakt kaartvenster plaatsen

import mywnd
w = mywnd.MyWnd(qgis.utils.iface.activeLayer())
w.show()

Zorg er echter voor dat het bestand mywnd.py is geplaatst binnen het zoekpad van Python (sys.path). Als dat niet zo is kunt u het eenvoudig toevoegen: sys.path.insert(0, '/my/path') — anders zal het argument voor het importeren falen, het vindt de module niet.

Elastieken banden en markeringen voor punten

Gebruik items voor het kaartvenster om enkele aanvullende gegevens bovenop de kaart in het kaartvenster weer te geven. Het is mogelijk om aangepaste klassen voor items voor het kaartvenster te maken (hieronder behandeld), er zijn voor het gemak echter twee handige klassen voor items voor het kaartvenster: QgsRubberBand voor het tekenen van polylijnen of polygonen, en QgsVertexMarker voor het tekenen van punten. Zij werken beide met coördinaten op de kaart, dus de vorm wordt automatisch verplaatst/geschaald als het kaartvenster wordt verschoven of als er wordt gezoomd.

Een polylijn weergeven

r = QgsRubberBand(canvas, False)  # False = not a polygon
points = [QgsPoint(-1, -1), QgsPoint(0, 1), QgsPoint(1, -1)]
r.setToGeometry(QgsGeometry.fromPolyline(points), None)

Een polygoon weregeven

r = QgsRubberBand(canvas, True)  # True = a polygon
points = [[QgsPoint(-1, -1), QgsPoint(0, 1), QgsPoint(1, -1)]]
r.setToGeometry(QgsGeometry.fromPolygon(points), None)

Onthoud dat de punten voor polygoon geen platte lijst is: in feite is het een lijst van ringen die lineaire ringen van de polygoon bevat: de eerste ring is de buitenste grens, verdere (optionele) ringen corresponderen met gaten in de polygoon.

Elastieken banden maken enige aanpassingen mogelijk, namelijk om hun kleur en lijndikte te wijzigen

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

De items voor het kaartvenster zij gebonden aan de scene van het kaartvenster. Gebruik de combinatie hide() en show() om ze tijdelijk te verbergen (en weer opnieuw weer te geven). U moet het uit de scene van het kaartvenster verwijderen om het item volledig te verwijderen

canvas.scene().removeItem(r)

(in C++ is het mogelijk het item eenvoudigweg te verwijderen, in Python echter zou del r slechts de verwijzing verwijderen en zou het object nog steeds bestaan omdat het eigendom is van het kaartvenster)

Een elastieken band kan ook gebruikt worden om punten te tekenen, de klasse QgsVertexMarker is echter beter geschikt hiervoor (QgsRubberBand zou alleen een rechthoek rondom het gewenste punt tekenen). Hoe de markering voor punten te gebruiken

m = QgsVertexMarker(canvas)
m.setCenter(QgsPoint(0, 0))

Dit zal een rood kruis tekenen op de positie [0,0]. Het is mogelijk om het type pictogram, de grootte, de kleur en de dikte van de pen aan te passen

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

Voor het tijdelijk verbergen van markeringen voor punten en ze uit het kaartvenster te verwijderen, is hetzelfde van toepassing als voor elastieken banden.

Aangepaste gereedschappen voor de kaart schrijven

U kunt aangepaste gereedschappen schrijven, om een aagepast gedrag te implementeren voor acties die door gebruikers op het kaartvenster worden uitgevoerd.

Gereedschappen voor de kaart zouden moeten ervan van de klasse QgsMapTool of een daarvan afgeleide klasse, en in het kaartvenster moeten worden geselecteerd als actief gereedschap met behulp van de methode setMapTool() zoals we al eerder hebben gezien.

Hier is een voorbeeld van een gereedschap voor de kaart dat het mogelijk maakt een rechthoekig bereik te definiëren door te klikken en te slepen in het kaartvenster. Wanneer de rechthoek is gedefinieerd, zal het de coördinaten voor de begrenzing afdrukken in de console. Het gebruikt de elementen voor elastieken banden zoals eerder beschreven om de geselecteerde rechthoek weer te geven als die wordt gedefinieerd.

class RectangleMapTool(QgsMapToolEmitPoint):
  def __init__(self, canvas):
      self.canvas = canvas
      QgsMapToolEmitPoint.__init__(self, self.canvas)
      self.rubberBand = QgsRubberBand(self.canvas, QGis.Polygon)
      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(QGis.Polygon)

  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.emit(SIGNAL("deactivated()"))

Aangepaste items voor het kaartvenster schrijven

TODO:

hoe een item voor het kaartvenster te maken

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)