8 使用地图画布
目录
地图画布控件可能是QGIS中最重要的控件,因为它显示了由重叠的地图图层组成的地图,并允许与地图和图层进行交互。画布始终显示由当前画布范围定义的地图的一部分。通过使用地图工具完成交互:有平移,缩放,识别图层,测量,矢量编辑等工具。与其他图形程序类似,总有一个工具处于活动状态,用户可以在可用工具之间切换。
地图画布由qgis.gui
模块中的QgsMapCanvas
类实现。该类基于Qt Graphics View框架。该框架通常提供表面和视图,其中放置自定义图形项并且用户可以与它们交互。我们假设您对Qt足够熟悉,以了解图形场景,视图和项的概念。如果没有,请阅读框架概述。
无论何时平移地图,放大/缩小(或触发刷新的其他操作),地图都会在当前范围内再次渲染。图层将渲染为图像(使用QgsMapRendererJob
类),图像将显示在画布上。QgsMapCanvas
类还控制渲染图的刷新。除了作为这个项的背景,可能还有更多的地图画布项。
典型的地图画布项目是橡皮条(用于测量,矢量编辑等)或顶点标记。画布项通常用于为地图工具提供视觉反馈,例如,在创建新多边形时,地图工具会创建一个橡皮条画布项,显示多边形的当前形状。所有地图画布项都是QgsMapCanvasItem
的子类,它为基类QGraphicsItem
对象添加了更多功能。
总而言之,地图画布架构包含三个概念:
-
地图画布——用于查看地图
-
地图画布项目——可以在地图画布上显示的其他项目
-
地图工具——用于与地图画布交互
8.1 嵌入地图画布
地图画布是一个控件,就像任何其他Qt控件一样,因此使用它就像创建和显示它一样简单
canvas = QgsMapCanvas()
canvas.show()
这将生成一个带有地图画布的独立窗口。它也可以嵌入到现有的控件或窗口中。使用.ui
文件和Qt Designer时,QWidget
在表单上放置一个并将其提升为新类:设置QgsMapCanvas
为类名并设置qgis.gui
为头文件。pyuic5
程序将负责(编译为py文件)它。这是嵌入画布的一种非常方便的方法。另一种可能性是手动编写代码以构造地图画布和其他控件(作为主窗口或对话框的子窗口)并创建布局。
默认情况下,地图画布具有黑色背景,不使用消除锯齿。设置白色背景并启用抗锯齿以实现平滑渲染
canvas.setCanvasColor(Qt.white)
canvas.enableAntiAliasing(True)
(如果您想知道,Qt
来自PyQt.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("图层加载失败!")
# 将图层添加到注册表
QgsProject.instance().addMapLayer(vlayer)
# 缩放到图层
canvas.setExtent(vlayer.extent())
# 设置地图画布的图层集
canvas.setLayers([vlayer])
执行这些命令后,画布应显示已加载的图层。
8.2 橡皮条和顶点标记
要在画布上的地图顶部显示一些其他数据,请使用地图画布项。可以创建自定义画布项类(如下所述),但为方便起见,有两个有用的画布项类:QgsRubberBand
用于绘制折线或多边形,QgsVertexMarker
绘制点。它们都使用地图坐标,因此在平移或缩放画布时会自动移动/缩放形状。
显示折线
r = QgsRubberBand(canvas, False) # False = not a polygon
points = [QgsPointXY(-100, 45), QgsPointXY(10, 60), QgsPointXY(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)
要临时隐藏顶点标记并从画布中删除它们,请使用与橡皮条相同的方法。
8.3 在画布中使用地图工具
以下示例构造一个窗口,其中包含用于地图平移和缩放的地图画布和基本地图工具。激活每个工具:平移工具QgsMapToolPan
,放大缩小工具QgsMapToolZoom
。设置为可被选中,允许自动处理选中/未选中的操作状态——当激活地图工具时,一个工具被选中时,取消选中上一个工具。使用setMapTool()
方法激活地图工具。
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)
# 创建地图工具
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()
8.4 编写自定义地图工具
您可以编写自定义工具,以实现用户在画布上执行自定义行为的操作。
地图工具应继承自QgsMapTool
类或任何派生类,并使用setMapTool()
在画布中选择为活动工具。
下面是一个地图工具示例,它允许通过在画布上单击并拖动来定义矩形范围。定义矩形后,它会在控制台中打印其边界坐标。它使用前面描述的橡皮条元素来显示所定义的矩形。
class RectangleMapTool(QgsMapToolEmitPoint):
def __init__(self, canvas):
self.canvas = canvas
QgsMapToolEmitPoint.__init__(self, self.canvas)
self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry)
self.rubberBand.setColor(QColor(255, 0, 0, 100))
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(QgsWkbTypes.PolygonGeometry)
if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y():
return
point1 = QgsPointXY(startPoint.x(), startPoint.y())
point2 = QgsPointXY(startPoint.x(), endPoint.y())
point3 = QgsPointXY(endPoint.x(), endPoint.y())
point4 = QgsPointXY(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):
super(RectangleMapTool, self).deactivate()
self.deactivated.emit()
self.reset()
8.5 编写自定义地图画布项目
TODO:
如何创建地图画布项
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)
写在最后:文中难免有翻译错误,欢迎您的指正。完整版翻译请移步PyQGIS开发者手册-完整版