10. 地图绘制和打印

提示

此页面上的代码片段需要以下导入:

 1import os
 2
 3from qgis.core import (
 4    QgsGeometry,
 5    QgsMapSettings,
 6    QgsPrintLayout,
 7    QgsMapSettings,
 8    QgsMapRendererParallelJob,
 9    QgsLayoutItemLabel,
10    QgsLayoutItemLegend,
11    QgsLayoutItemMap,
12    QgsLayoutItemPolygon,
13    QgsLayoutItemScaleBar,
14    QgsLayoutExporter,
15    QgsLayoutItem,
16    QgsLayoutPoint,
17    QgsLayoutSize,
18    QgsUnitTypes,
19    QgsProject,
20    QgsFillSymbol,
21    QgsAbstractValidityCheck,
22    check,
23)
24
25from qgis.PyQt.QtGui import (
26    QPolygonF,
27    QColor,
28)
29
30from qgis.PyQt.QtCore import (
31    QPointF,
32    QRectF,
33    QSize,
34)

通常有两种方法可以将输入数据渲染为地图:或者使用以下两种快速方法 QgsMapRendererJob 或通过将地图与 QgsLayout 班级。

10.1. 简单渲染

渲染已完成,创建了一个 QgsMapSettings 对象定义呈现设置,然后构造一个 QgsMapRendererJob 有了这些设置。然后使用后者来创建结果图像。

下面是一个例子:

 1image_location = os.path.join(QgsProject.instance().homePath(), "render.png")
 2
 3vlayer = iface.activeLayer()
 4settings = QgsMapSettings()
 5settings.setLayers([vlayer])
 6settings.setBackgroundColor(QColor(255, 255, 255))
 7settings.setOutputSize(QSize(800, 600))
 8settings.setExtent(vlayer.extent())
 9
10render = QgsMapRendererParallelJob(settings)
11
12def finished():
13    img = render.renderedImage()
14    # save the image; e.g. img.save("/Users/myuser/render.png","png")
15    img.save(image_location, "png")
16
17render.finished.connect(finished)
18
19# Start the rendering
20render.start()
21
22# The following loop is not normally required, we
23# are using it here because this is a standalone example.
24from qgis.PyQt.QtCore import QEventLoop
25loop = QEventLoop()
26render.finished.connect(loop.quit)
27loop.exec_()

10.2. 使用不同的CRS渲染层

如果您有多个图层,而它们具有不同的CRS,则上面的简单示例可能不起作用:要从范围计算中获得正确的值,您必须显式设置目标CRS

layers = [iface.activeLayer()]
settings = QgsMapSettings()
settings.setLayers(layers)
settings.setDestinationCrs(layers[0].crs())

10.3. 使用打印布局输出

如果您想要进行比上面所示的简单渲染更复杂的输出,那么打印布局是一个非常方便的工具。可以创建由地图视图、标签、图例、表格和其他通常出现在纸质地图上的元素组成的复杂地图布局。然后可以将版面导出为PDF、SVG、栅格图像或直接在打印机上打印。

布局由一系列类组成。它们都属于核心类库。QGIS应用程序有一个用于放置元素的方便的图形用户界面,尽管它在图形用户界面库中不可用。如果您不熟悉 Qt Graphics View framework ,然后鼓励您现在查看文档,因为布局是基于它的。

布局的中心类是 QgsLayout 类,它是从Qt派生的 QGraphicsScene 班级。让我们创建它的一个实例:

project = QgsProject.instance()
layout = QgsPrintLayout(project)
layout.initializeDefaults()

这将使用一些默认设置来初始化布局,特别是通过向布局添加一个空的A4页面。可以创建布局,而无需调用 initializeDefaults() 方法,但您需要自己将页面添加到布局中。

前面的代码创建了一个在图形用户界面中不可见的“临时”布局。例如,可以在不修改项目本身的情况下快速添加一些项并导出,也不向用户公开这些更改,这可能很方便。如果希望将布局与项目一起保存/恢复并在布局管理器中可用,请添加:

layout.setName("MyLayout")
project.layoutManager().addLayout(layout)

现在我们可以添加各种元素(地图、标签等)到布局。所有这些对象都由从基类继承的类表示 QgsLayoutItem 班级。

以下是可以添加到布局中的一些主要布局项目的说明。

  • 地图-这里我们创建自定义大小的地图并渲染当前地图画布

    1map = QgsLayoutItemMap(layout)
    2# Set map item position and size (by default, it is a 0 width/0 height item placed at 0,0)
    3map.attemptMove(QgsLayoutPoint(5,5, QgsUnitTypes.LayoutMillimeters))
    4map.attemptResize(QgsLayoutSize(200,200, QgsUnitTypes.LayoutMillimeters))
    5# Provide an extent to render
    6map.zoomToExtent(iface.mapCanvas().extent())
    7layout.addLayoutItem(map)
    
  • 标签-允许显示标签。可以修改其字体、颜色、对齐方式和页边距

    label = QgsLayoutItemLabel(layout)
    label.setText("Hello world")
    label.adjustSizeToText()
    layout.addLayoutItem(label)
    
  • 传说

    legend = QgsLayoutItemLegend(layout)
    legend.setLinkedMap(map) # map is an instance of QgsLayoutItemMap
    layout.addLayoutItem(legend)
    
  • 比例尺

    1item = QgsLayoutItemScaleBar(layout)
    2item.setStyle('Numeric') # optionally modify the style
    3item.setLinkedMap(map) # map is an instance of QgsLayoutItemMap
    4item.applyDefaultSize()
    5layout.addLayoutItem(item)
    
  • 基于节点的形状

     1polygon = QPolygonF()
     2polygon.append(QPointF(0.0, 0.0))
     3polygon.append(QPointF(100.0, 0.0))
     4polygon.append(QPointF(200.0, 100.0))
     5polygon.append(QPointF(100.0, 200.0))
     6
     7polygonItem = QgsLayoutItemPolygon(polygon, layout)
     8layout.addLayoutItem(polygonItem)
     9
    10props = {}
    11props["color"] = "green"
    12props["style"] = "solid"
    13props["style_border"] = "solid"
    14props["color_border"] = "black"
    15props["width_border"] = "10.0"
    16props["joinstyle"] = "miter"
    17
    18symbol = QgsFillSymbol.createSimple(props)
    19polygonItem.setSymbol(symbol)
    

将项目添加到布局后,可以移动该项目并调整其大小:

item.attemptMove(QgsLayoutPoint(1.4, 1.8, QgsUnitTypes.LayoutCentimeters))
item.attemptResize(QgsLayoutSize(2.8, 2.2, QgsUnitTypes.LayoutCentimeters))

默认情况下,每个项目周围都会绘制一个边框。您可以按如下方式删除它:

# for a composer label
label.setFrameEnabled(False)

除了手动创建布局项目外,QGIS还支持布局模板,这些模板本质上是将其所有项目保存到.qpt文件(使用XML语法)的组合。

一旦构图准备就绪(布局项目已创建并添加到构图中),我们就可以继续生成栅格和/或矢量输出。

10.3.1. 正在检查布局有效性

布局是由一组相互连接的项目组成的,这些连接可能会在修改过程中断开(连接到已移除地图的图例、缺少源文件的图像项目等)或者,您可能希望对布局项应用自定义约束。这个 QgsAbstractValidityCheck 帮助您实现这一点。

一张基本的支票如下:

@check.register(type=QgsAbstractValidityCheck.TypeLayoutCheck)
def my_layout_check(context, feedback):
  results = ...
  return results

下面的检查在布局地图项目设置为Web墨卡托投影时抛出警告:

 1@check.register(type=QgsAbstractValidityCheck.TypeLayoutCheck)
 2def layout_map_crs_choice_check(context, feedback):
 3  layout = context.layout
 4  results = []
 5  for i in layout.items():
 6    if isinstance(i, QgsLayoutItemMap) and i.crs().authid() == 'EPSG:3857':
 7      res = QgsValidityCheckResult()
 8      res.type = QgsValidityCheckResult.Warning
 9      res.title = 'Map projection is misleading'
10      res.detailedDescription = 'The projection for the map item {} is set to <i>Web Mercator (EPSG:3857)</i> which misrepresents areas and shapes. Consider using an appropriate local projection instead.'.format(i.displayName())
11      results.append(res)
12
13  return results

下面是一个更复杂的示例,如果将任何布局地图项设置为仅在该地图项中显示的范围之外有效的CRS,则会抛出警告:

 1@check.register(type=QgsAbstractValidityCheck.TypeLayoutCheck)
 2def layout_map_crs_area_check(context, feedback):
 3    layout = context.layout
 4    results = []
 5    for i in layout.items():
 6        if isinstance(i, QgsLayoutItemMap):
 7            bounds = i.crs().bounds()
 8            ct = QgsCoordinateTransform(QgsCoordinateReferenceSystem('EPSG:4326'), i.crs(), QgsProject.instance())
 9            bounds_crs = ct.transformBoundingBox(bounds)
10
11            if not bounds_crs.contains(i.extent()):
12                res = QgsValidityCheckResult()
13                res.type = QgsValidityCheckResult.Warning
14                res.title = 'Map projection is incorrect'
15                res.detailedDescription = 'The projection for the map item {} is set to \'{}\', which is not valid for the area displayed within the map.'.format(i.displayName(), i.crs().authid())
16                results.append(res)
17
18    return results

10.3.2. 正在导出布局

若要导出布局,请使用 QgsLayoutExporter 必须使用类。

1base_path = os.path.join(QgsProject.instance().homePath())
2pdf_path = os.path.join(base_path, "output.pdf")
3
4exporter = QgsLayoutExporter(layout)
5exporter.exportToPdf(pdf_path, QgsLayoutExporter.PdfExportSettings())

使用 exportToSvg()exportToImage() 如果您想要分别导出为SVG或图像文件,而不是PDF文件。

10.3.3. 导出版面地图集

如果要从配置并启用地图集选项的布局中导出所有页面,则需要使用 atlas() 在导出器中使用 (QgsLayoutExporter )进行小幅调整。在下面的示例中,页面被导出为PNG图像:

exporter.exportToImage(layout.atlas(), base_path, 'png', QgsLayoutExporter.ImageExportSettings())

请注意,输出将使用atlas上配置的输出文件名表达式保存在基本路径文件夹中。