使用地图画布¶

Docs in progress for 'QGIS testing'. Visit https://docs.qgis.org/3.4 for QGIS 3.4 docs and translations.

警告

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.

地图画布小部件可能是QGIS中最重要的小部件,因为它显示由重叠的地图层组成的地图,并允许与地图和层交互。画布始终显示由当前画布范围定义的地图的一部分。交互是通过使用 地图工具: 有平移、缩放、识别图层、测量、矢量编辑等工具。与其他图形程序类似,始终有一个工具处于活动状态,用户可以在可用工具之间切换。

地图画布是用 QgsMapCanvas 类中 qgis.gui 模块。该实现基于qt图形视图框架。这个框架通常提供一个放置自定义图形项的表面和视图,用户可以与之交互。我们假设您对qt足够熟悉,能够理解图形场景、视图和项的概念。如果没有,请阅读 overview of the framework .

每当对地图进行平移、放大/缩小(或其他触发刷新的操作)时,将在当前范围内再次呈现地图。层渲染为图像(使用 QgsMapRendererJob 类),该图像将显示在画布上。这个 QgsMapCanvas 类还控制渲染映射的刷新。除了作为背景的这个项目,可能还有更多 映射画布项.

典型的地图画布项目是橡皮筋(用于测量、矢量编辑等)或顶点标记。画布项通常用于为地图工具提供视觉反馈,例如,在创建新多边形时,地图工具创建一个橡皮筋画布项,显示多边形的当前形状。所有地图画布项都是 QgsMapCanvasItem 它为基本的 QGraphicsItem 物体。

总而言之,地图画布架构由三个概念组成:

嵌入地图画布

地图画布和其他任何Qt小部件一样是一个小部件,因此使用它就像创建和显示它一样简单。

canvas = QgsMapCanvas()
canvas.show()

这将生成一个带有地图画布的独立窗口。它也可以嵌入到现有的小部件或窗口中。使用.ui文件和qt设计器时,将 QWidget 在窗体上并将其升级为新类:set QgsMapCanvas 作为类名和集合 qgis.gui 作为头文件。这个 pyuic5 公用事业公司会负责的。这是嵌入画布的一种非常方便的方法。另一种可能性是手动编写代码来构造地图画布和其他小部件(作为主窗口或对话框的子窗口)并创建布局。

默认情况下,地图画布具有黑色背景,不使用消除混叠。设置白色背景并启用平滑渲染的消除混叠

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

(如果你想知道, Qt 来自 PyQt5.QtCore 模块和 Qt.white 是预定义的 QColor 实例)

现在是时候添加一些地图图层了。我们将首先打开一个层并将其添加到当前项目中。然后我们将设置画布范围并设置画布的层列表

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

执行这些命令后,画布应该显示已加载的层。

橡皮筋和顶点标记

要在画布中的地图顶部显示一些附加数据,请使用地图画布项。可以创建自定义画布项类(如下所述),但是为了方便起见,有两个有用的画布项类: QgsRubberBand 用于绘制多段线或多边形,以及 QgsVertexMarker 用于绘制点。它们都使用地图坐标,因此当画布被平移或缩放时,形状会自动移动/缩放。

显示多段线的步骤

r = QgsRubberBand(canvas, False)  # False = not a polygon
points = [QgsPoint(-100, 45), QgsPoint(10, 60), QgsPoint(120, 45)]
r.setToGeometry(QgsGeometry.fromPolyline(points), None)

显示多边形

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

注意,多边形的点不是一个简单的列表:事实上,它是一个包含多边形线性环的环列表:第一个环是外部边界,其他(可选)环对应于多边形中的孔。

橡皮筋允许一些定制,也就是改变它们的颜色和线条宽度。

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

画布项绑定到画布场景。要临时隐藏它们(并再次显示),请使用 hide()show() 联合体。要完全删除该项,必须将其从画布的场景中删除。

canvas.scene().removeItem(r)

(在C++中,可以在Python中只删除该项。 del r 只需删除引用,对象仍将存在,因为它属于画布)

橡皮筋也可用于绘制点,但 QgsVertexMarker 班级更适合这个( QgsRubberBand 只在所需点周围绘制一个矩形)。

您可以这样使用顶点标记:

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

这将在位置**[10,45]**上画一个红十字。可以自定义图标类型、大小、颜色和笔宽

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

要临时隐藏顶点标记并将其从画布中移除,请使用与橡皮筋相同的方法。

将地图工具与画布一起使用

下面的示例构建一个窗口,其中包含地图画布和用于地图平移和缩放的基本地图工具。为激活每个工具创建操作:使用 QgsMapToolPan ,放大/缩小一对 QgsMapToolZoom 实例。这些操作被设置为可选中,稍后分配给工具,以允许自动处理操作的已选中/未选中状态——当地图工具被激活时,其操作被标记为选中,并且取消选择上一个地图工具的操作。地图工具是使用 setMapTool() 方法。

from qgis.gui import *
from PyQt5.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)

您可以在Python控制台编辑器中尝试上述代码。要调用画布窗口,请添加以下行以实例化 MyWnd 班级。它们将在新创建的画布上呈现当前选定的层

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

编写自定义地图工具

您可以编写自定义工具,以实现用户在画布上执行的操作的自定义行为。

映射工具应继承自 QgsMapTool ,类或任何派生类,并使用 setMapTool() 方法,正如我们已经看到的。

下面是一个地图工具的示例,它允许通过单击和拖动画布来定义矩形范围。定义矩形后,将在控制台中打印其边界坐标。它使用前面描述的橡皮筋元素在定义所选矩形时显示它。

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

正在写入自定义地图画布项

待办事项:
如何创建地图画布项
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)