PyQGIS开发--普通地图布局案例

前言

我一直在断断续续地尝试使用Python为QGIS3创建和导出打印布局。或简称PyQGIS 3。我终于弄清楚了这个过程的来龙去脉,希望这能为其他人节省大量的精力和时间。

由于一个打印布局可以进行几乎无限的定制,我将详细介绍很多小细节,并举例说明。你可以根据自己的需要进行混搭。我还将深入到文档中,向您展示我如何引用各种类方法、属性等。

  1. 布局内容

人们添加到打印布局中的常见内容包括地图、图例、标签、比例尺、北箭头等。在本例中,我将介绍地图、图例和标签。这里是对QgsLayoutItem类文档的参考。您将看到这个类是所有QgsLayoutItems的构造函数,其中有很多。下面是继承图:

  1. 新建布局

project = QgsProject.instance()         
    manager = project.layoutManager()       
    layout = QgsPrintLayout(project)        
    layoutName = "PrintLayout"

    #initializes default settings for blank print layout canvas
    layout.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 necessary
    map.setRect(20, 20, 20, 20)                                     
    
    #Set Map Extent
    #defines map extent using map coordinates
    rectangle = QgsRectangle(1355502, -46398, 1734534, 137094)
    map.setExtent(rectangle)
    layout.addLayoutItem(map)
    
    #Move & Resize map on print layout canvas
    map.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))
  1. 创建图例

与地图项一样,您必须首先创建一个 QgsLayoutItem 并指定要应用它的打印布局。 在这种情况下,我们的项目是一个图例,所以我们使用 QgsLayoutItemLegend()。 我添加了一个标题,这是可选的。 然后将图例项添加到打印布局中。 最后,我将图例移动到我希望它在地图上的位置。 这会在地图中添加一个包含所有图层的图例,包括那些已关闭的图层、附加表格等。根据您的 QGIS 项目,您可能会得到如下结果:

如您所见,图例溢出屏幕。 在我的 QGIS 项目中,我有很多图层。 他们中的大多数是不活跃的,不需要在图例中。 我真的只是想要一些东西。 所以我修改了我的脚本以在我的项目中只包含活动层。 让我们回顾一下(在我创建图例对象之前)。

#Checks layer tree objects and stores them in a list. This includes csv tables
    checked_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 list
    layersToAdd = [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 tree
        root.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 object
    exporter = QgsLayoutExporter(layout)                

    #this exports a pdf of the layout object
    exporter.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出专题图。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

倾城一少

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值