我一直在断断续续地尝试使用Python为QGIS3创建和导出打印布局。或简称PyQGIS 3。我终于弄清楚了这个过程的来龙去脉,希望这能为其他人节省大量的精力和时间。
由于一个打印布局可以进行几乎无限的定制,我将详细介绍很多小细节,并举例说明。你可以根据自己的需要进行混搭。我还将深入到文档中,向您展示我如何引用各种类方法、属性等。
人们添加到打印布局中的常见内容包括地图、图例、标签、比例尺、北箭头等。在本例中,我将介绍地图、图例和标签。这里是对QgsLayoutItem类文档的参考。您将看到这个类是所有QgsLayoutItems的构造函数,其中有很多。下面是继承图:

project = QgsProject.instance() manager = project.layoutManager() layout = QgsPrintLayout(project) layoutName = "PrintLayout"#initializes default settings for blank print layout canvaslayout.initializeDefaults() layout.setname(layoutName)manager.addLayout(layout)我创建了一个项目,它是对当前Qgs项目的引用。manager对象包含当前项目的布局管理器(layoutManager)。我创建一个布局并初始化打印布局的默认设置(大小、布局等)。
如果您运行上述代码,请在“项目”>“打印布局”下查看,您将看到一个新的打印布局,该布局当前为空。

在我继续之前,上面的代码是有效的,但只有一次。再次看到我创建了一个名为layoutName的打印布局对象。如果我再次运行此代码,将收到一条错误消息。一种解决方法是重命名打印布局(例如“PrintLayout2”),但我觉得这很烦人。此外,在测试中,您可能会多次运行此代码,创建一个不断增长的测试打印布局列表,稍后您必须清理这些列表。因此,让我们解决这个问题:
layouts_list = manager.printLayouts()for layout in layouts_list:if layout.name() == layoutName:manager.removeLayout(layout)此快速修复程序查看管理器对象中的打印布局,并将其存储在layouts_list中。然后我循环浏览每个布局,如果layoutName==是现有布局的名称,请删除它。现在我可以创建一个新的打印布局,有效地覆盖我所拥有的内容。
每个项目都有自己的属性、方法等,这些都很难导航(至少对我来说)。让我们从向打印布局添加地图对象开始。请参见以下代码:
map = QgsLayoutItemMap(layout)#I have no idea what this does, but it is necessarymap.setRect(20, 20, 20, 20) #Set Map Extent#defines map extent using map coordinatesrectangle = QgsRectangle(1355502, -46398, 1734534, 137094)map.setExtent(rectangle)layout.addLayoutItem(map)#Move & Resize map on print layout canvasmap.attemptMove(QgsLayoutPoint(5, 27, QgsUnitTypes.LayoutMillimeters))map.attemptResize(QgsLayoutSize(239, 178, QgsUnitTypes.LayoutMillimeters))所以在前面的代码中我创建了一个地图,它是一个 QgsLayoutItemMap 对象。 我很抱歉,但 map.setRect() 方法让我感到困惑,老实说,我无法解释它的作用或为什么它在那里。 但地图必须存在。 参数 (20, 20, 20, 20) 似乎对地图没有影响,但如果不调用此方法,您会收到错误消息。 接下来,我设置地图的范围。 我已经创建了一个 QgsRectangle 对象,并使用我项目中的地图坐标手动定义了范围。 您也可以使用以下方法定义范围,将范围设置为界面地图画布的范围,从而允许您动态更改它。
canvas = iface.mapCanvas()map.setExtent(canvas.extent())layout.addLayoutItem(map)最后,我移动地图对象并调整其大小。 这两种方法的论点都是不言自明的。 我以毫米为单位移动和调整地图对象的大小。 如果你想做这样的事情,你可以选择以英寸或其他单位工作:
map.attemptMove(QgsLayoutPoints(1, 2, QgsUnitTypes.LayoutInches))
map.attemptResize(QgsLayoutSize(239, 178, QgsUnitTypes.LayoutMillimeters))现在我得到了一张漂亮的地图,它被框起来并位于打印布局中我想要的位置。 我的看起来像这样:

让我们继续布局项目。 与地图一样,创建我的布局非常棘手。 进行通用布局的基本代码如下所示:
legend = QgsLayoutItemLegend(layout)legend.setTitle("Legend")layout.addLayoutItem(legend)legend.attemptMove(QgsLayoutPoint(246, 5, QgsUnitTypes.LayoutMillimeters))与地图项一样,您必须首先创建一个 QgsLayoutItem 并指定要应用它的打印布局。 在这种情况下,我们的项目是一个图例,所以我们使用 QgsLayoutItemLegend()。 我添加了一个标题,这是可选的。 然后将图例项添加到打印布局中。 最后,我将图例移动到我希望它在地图上的位置。 这会在地图中添加一个包含所有图层的图例,包括那些已关闭的图层、附加表格等。根据您的 QGIS 项目,您可能会得到如下结果:

如您所见,图例溢出屏幕。 在我的 QGIS 项目中,我有很多图层。 他们中的大多数是不活跃的,不需要在图例中。 我真的只是想要一些东西。 所以我修改了我的脚本以在我的项目中只包含活动层。 让我们回顾一下(在我创建图例对象之前)。
#Checks layer tree objects and stores them in a list. This includes csv tableschecked_layers = [layer.name() for layer in QgsProject().instance().layerTreeRoot().children() if layer.isVisible()]print(f"Adding {checked_layers} to legend." )#get map layer objects of checked layers by matching their names and store those in a listlayersToAdd = [layer for layer in QgsProject().instance().mapLayers().values() if layer.name() in checked_layers]在上面的代码片段中,我创建了一个名为 checked_layers 的列表。 在 checked_layers 中,我检查了 QgsProject().instance(),这是我正在使用的当前 QGIS 项目。然后查看 layerTreeRoot(),它包含项目中的所有图层。 最后,.children() 是 layerTreeRoot() 中的实际层本身。 我添加了一个打印语句来打印我正在添加的层,这些层将打印在控制台中。
现在我创建一个新对象 layersToAdd。 在这个对象中,我查看 QgsProject().instance().mapLayers(),它们是 QgsProject 实例中的所有层。 然后我添加 .mapLayers().values()(来自所有地图图层的值)如果这些对象在 checked_layers 中。 我认为这可能是捷径,但现在我有一个所有活动地图层的列表,存储在 layersToAdd 中。
现在,我们可以回去创建图例对象。 我们只想将活动层添加到图例中,所以让我们这样做:
legend = QgsLayoutItemLegend(layout)legend.setTitle("Legend")root = QgsLayerTree()for layer in layersToAdd:#add layer objects to the layer treeroot.addLayer(layer)legend.model().setRootGroup(root)layout.addLayoutItem(legend)legend.attemptMove(QgsLayoutPoint(246, 5, QgsUnitTypes.LayoutMillimeters))所有行都是相同的,除了我创建一个根对象(它是一个 QgsLayerTree,保存层)并循环遍历 layersToAdd,将它们添加到根对象。 最后,我在图例上设置了 .setRootGroup,它重置了图例模型并使用了一个新模型。
那么让我们看看图例现在的样子,我只是在示例中打开了一层。 但是你可以看到,现在只有活动层被添加到图例中。 这真是太好了!

最后,我要在我的地图中添加一些标签项目。 我发现这些更容易使用,但仍然有它们自己的怪癖。 人们添加到地图的常见内容包括标题和副标题,所以让我们添加它们:
"""This adds labels to the map"""title = QgsLayoutItemLabel(layout)title.setText("Title Here")title.setFont(QFont("Arial", 28))title.adjustSizeToText()layout.addLayoutItem(title)title.attemptMove(QgsLayoutPoint(10, 4, QgsUnitTypes.LayoutMillimeters))subtitle = QgsLayoutItemLabel(layout)subtitle.setText("Subtitle Here")subtitle.setFont(QFont("Arial", 17))subtitle.adjustSizeToText()layout.addLayoutItem(subtitle)subtitle.attemptMove(QgsLayoutPoint(11, 20, QgsUnitTypes.LayoutMillimeters)) #allows moving text box首先,请注意我们现在正在使用 QgsLayoutItemLabel。 每个 QgsLayoutItem 都有自己的类,如前面的文档继承图中所示。 我们使用非常明显的方法,如 .setText 来定义标签项的内容,并使用 .attemptMove 来定位打印布局上的标签。 我使用 .setFont 设置字体,选择字体样式和大小。 这里比较奇怪的是 .adjustSizeToText()。 基本上你必须定义布局项的大小。 这个 .adjustSizeToText 完全按照它说的去做,将大小设置为相应的文本。 我想不出为什么要将标签对象设置为任何其他大小的情况,但我确信存在这种情况。
让我们来看看我们的打印布局。 事情进展顺利。 我的看起来像这样:

我们的最后一步是将打印布局导出为 .pdf 或图像文件,您可以与他人共享。 我发现这是该过程中最简单的部分。
"""This exports a Print Layout as an image"""#this accesses a specific layout, by name (which is a string)layout = manager.layoutByName(layoutName)#this creates a QgsLayoutExporter objectexporter = QgsLayoutExporter(layout) #this exports a pdf of the layout objectexporter.exportToPdf('/Users/ep9k/Desktop/TestLayout.pdf', QgsLayoutExporter.PdfExportSettings()) #this exports an image of the layout object#exporter.exportToImage('/Users/ep9k/Desktop/TestLayout.png', QgsLayoutExporter.ImageExportSettings()) 我举例说明了如何导出为 .pdf 和图像文件 (.png)。 我提供了保存对象的桌面路径。
有关如何创建打印布局和导出最终地图的分步演练。 正如我在一开始所说的,这比我预期的要复杂得多,所以希望它能帮助你的学习开源GIS出专题图。
上一篇:马斯克试图阻止OpenAI在中东进行大型人工智能交易 马斯克欲阻止OpenAI成为营利性企业 马斯克欲打破OpenAI与微软合作关系
下一篇:“艰难的权衡”!美联储会议纪要:美国资产避风港地位减弱,关注通胀和就业风险 美联储货币政策会议纪要公布 2019年美联储3月份会议纪要