跳转至

本节代码片段需要导入以下模块:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import os

from qgis.core import (
    QgsGeometry,
    QgsMapSettings,
    QgsPrintLayout,
    QgsMapSettings,
    QgsMapRendererParallelJob,
    QgsLayoutItemLabel,
    QgsLayoutItemLegend,
    QgsLayoutItemMap,
    QgsLayoutItemPolygon,
    QgsLayoutItemScaleBar,
    QgsLayoutExporter,
    QgsLayoutItem,
    QgsLayoutPoint,
    QgsLayoutSize,
    QgsUnitTypes,
    QgsProject,
    QgsFillSymbol,
    QgsAbstractValidityCheck,
    check,
)

from qgis.PyQt.QtGui import (
    QPolygonF,
    QColor,
)

from qgis.PyQt.QtCore import (
    QPointF,
    QRectF,
    QSize,
)

10 地图渲染和打印⚓︎

当输入数据作为地图呈现时,通常有两种方法:使用QgsMapRendererJob快速进行,或者使用QgsLayout类组合地图来生成更精细的输出。

10.1 简单的渲染⚓︎

渲染完成后,创建一个QgsMapSettings对象来定义渲染选项,然后使用这些选项构建一个QgsMapRendererJob类。然后使用后者来创建图像。

这是一个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
image_location = os.path.join(QgsProject.instance().homePath(), "render.png")

vlayer = iface.activeLayer()
settings = QgsMapSettings()
settings.setLayers([vlayer])
settings.setBackgroundColor(QColor(255, 255, 255))
settings.setOutputSize(QSize(800, 600))
settings.setExtent(vlayer.extent())

render = QgsMapRendererParallelJob(options)

def finished():
    img = render.renderedImage()
    # 保存图像; e.g. img.save("/Users/myuser/render.png","png")
    img.save(image_location, "png")

render.finished.connect(finished)

# 开始渲染
render.start()

# 通常不需要循环,因为这里是单独使用
from qgis.PyQt.QtCore import QEventLoop
loop = QEventLoop()
render.finished.connect(loop.quit)
loop.exec_()

10.2 使用不同的CRS渲染图层⚓︎

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

1
2
3
4
layers = [iface.activeLayer()]
settings = QgsMapSettings()
settings.setLayers(layers)
render.setDestinationCrs(layers[0].crs())

10.3 使用打印布局输出⚓︎

如果你想要比上面显示的简单渲染更复杂的输出,打印布局是一个非常方便的工具。可以创建复杂的地图布局,包括地图视图,标签,图例,表格以及通常出现在纸质地图上的其他元素。然后可以将布局导出为PDF,SVG,栅格图像或直接打印在打印机上。

布局由一堆类组成。它们都属于核心库。QGIS应用程序有一个方便的GUI布局元素,虽然它在GUI库中不可用。如果你不熟悉 Qt Graphics View框架,那么建议你查看文档,因为布局是基于它的。

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

1
2
3
project = QgsProject.instance()
layout = QgsPrintLayout(project)
layout.initializeDefaults()

这将使用一些默认设置初始化布局,将空的A4页添加到布局中。你可以在不调用initializeDefaults()方法的情况下创建布局,但你需要自己向布局中添加页面。

之前的代码创建了在GUI中不可见的“临时”布局。它可以方便快速地添加某些项并导出,而不修改项本身,也不会向户公开这些更改。如果你希望将布局与项目一起保存或恢复,并使其在布局管理器中可用,添加:

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

现在我们可以在布局中添加各种元素(map,label,...)。所有这些对象都继承自基类QgsLayoutItem

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

  • 地图—— 在这里,我们创建一个地图并将其拉伸到整个纸张大小
1
2
3
4
5
6
7
map = QgsLayoutItemMap(layout)
# 设置地图项的位置和大小(默认是宽高都是0,位置在0,0)
map.attemptMove(QgsLayoutPoint(5,5, QgsUnitTypes.LayoutMillimeters))
map.attemptResize(QgsLayoutSize(200,200, QgsUnitTypes.LayoutMillimeters))
# 提供渲染范围
map.zoomToExtent(iface.mapCanvas().extent())
layout.addItem(map)
  • 标签——允许显示标签。可以修改其字体,颜色,对齐和边距
1
2
3
4
label = QgsLayoutItemLabel(layout)
label.setText("Hello world")
label.adjustSizeToText()
layout.addItem(label)
  • 图例
1
2
3
legend = QgsLayoutItemLegend(layout)
legend.setLinkedMap(map) # map是一个QgsLayoutItemMap实例
layout.addItem(legend)
  • 比例尺
1
2
3
4
5
item = QgsLayoutItemScaleBar(layout)
item.setStyle('Numeric') # 可选择修改样式
item.setLinkedMap(map)  # map是一个QgsLayoutItemMap实例
item.applyDefaultSize()
layout.addItem(item)
  • 箭头

  • 图片

  • 基本形状

  • 基于节点的形状

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
polygon = QPolygonF()
polygon.append(QPointF(0.0, 0.0))
polygon.append(QPointF(100.0, 0.0))
polygon.append(QPointF(200.0, 100.0))
polygon.append(QPointF(100.0, 200.0))

polygonItem = QgsLayoutItemPolygon(polygon, layout)
layout.addItem(polygonItem)

props = {}
props["color"] = "green"
props["style"] = "solid"
props["style_border"] = "solid"
props["color_border"] = "black"
props["width_border"] = "10.0"
props["joinstyle"] = "miter"

symbol = QgsFillSymbol.createSimple(props)
polygonItem.setSymbol(symbol)
  • 表格

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

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

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

1
label.setFrameEnabled(False)

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

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

10.3.1 检查布局有效性⚓︎

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

基本检查如下:

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

这里有一个检查,每当布局地图项目设置为WEB墨卡托投影时,就会发出警告:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@check.register(type=QgsAbstractValidityCheck.TypeLayoutCheck)
def layout_map_crs_choice_check(context, feedback):
  layout = context.layout
  results = []
  for i in layout.items():
    if isinstance(i, QgsLayoutItemMap) and i.crs().authid() == 'EPSG:3857':
      res = QgsValidityCheckResult()
      res.type = QgsValidityCheckResult.Warning
      res.title = 'Map projection is misleading'
      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())
      results.append(res)

  return results

这里有一个更复杂的例子,如果任何布局地图项被设置为CRS,则会发出警告,该CRS仅在该地图项中显示的范围之外有效:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@check.register(type=QgsAbstractValidityCheck.TypeLayoutCheck)
def layout_map_crs_area_check(context, feedback):
    layout = context.layout
    results = []
    for i in layout.items():
        if isinstance(i, QgsLayoutItemMap):
            bounds = i.crs().bounds()
            ct = QgsCoordinateTransform(QgsCoordinateReferenceSystem('EPSG:4326'), i.crs(), QgsProject.instance())
            bounds_crs = ct.transformBoundingBox(bounds)

            if not bounds_crs.contains(i.extent()):
                res = QgsValidityCheckResult()
                res.type = QgsValidityCheckResult.Warning
                res.title = 'Map projection is incorrect'
                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())
                results.append(res)

    return results

10.3.2 导出布局⚓︎

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

1
2
3
4
pdf_path = os.path.join(QgsProject.instance().homePath(), "output.pdf")

exporter = QgsLayoutExporter(layout)
exporter.exportToPdf(pdf_path, QgsLayoutExporter.PdfExportSettings())

如果要分别导出到SVG或图像文件,请使用exportToSvg()或者exportToImage()导出图像。

10.3.3 导出布局图集⚓︎

如果要从布局中导出所有页面(在配置中启用了图集选项),则需要在导出器(QgsLayoutExporter)中使用altas()方法,并进行少量调整。在以下示例中,页面导出为PNG图像:

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

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