10. Renderização em impressão de mapas

Dica

Os trechos de código nesta página precisam das seguintes importações:

 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)

Geralmente, existem duas abordagens em que os dados de entrada devem ser renderizados como um mapa: seja rápido usando QgsMapRendererJob ou produza uma saída mais ajustada compondo o mapa com a classe QgsLayout.

10.1. Renderização simples

A renderização é feita criando um objeto QgsMapSettings para definir as configurações de renderização e, em seguida, construindo um QgsMapRendererJob com essas configurações. O último é usado para criar a imagem resultante.

He aquí un ejemplo:

 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. Renderizando camadas com CRS diferente

Se você tiver mais de uma camada e eles tiverem SRC diferente, o exemplo simples acima provavelmente não funcionará: para obter os valores corretos a partir dos cálculos de extensão, você deve definir explicitamente o SRC de destino

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

10.3. Saída usando layout de impressão

Print layout is a very handy tool if you would like to do a more sophisticated output than the simple rendering shown above. It is possible to create complex map layouts consisting of map views, labels, legend, tables and other elements that are usually present on paper maps. The layouts can be then exported to PDF, SVG, raster images or directly printed on a printer.

O layout consiste de várias classes. Todos eles pertencem à biblioteca principal. O aplicativo QGIS possui uma GUI conveniente para a colocação dos elementos, embora não esteja disponível na biblioteca da GUI. Se você não estiver familiarizado com a estrutura Qt Graphics View, recomendamos que verifique a documentação agora, porque o layout é baseado nela .

A classe central do layout é a classe QgsLayout, que é derivada da classe Qt QGraphicsScene. Vamos criar uma instância dele:

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

Isto inicializa o layout com algumas configurações padrão, especificamente adicionando uma página A4 vazia ao layout. Você pode criar layouts sem chamar o método initializeDefaults(), mas você mesmo terá que se encarregar de adicionar páginas ao layout.

The previous code creates a “temporary” layout that is not visible in the GUI. It can be handy to e.g. quickly add some items and export without modifying the project itself nor expose these changes to the user. If you want the layout to be saved/restored along with the project and available in the layout manager, then add:

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

Agora podemos adicionar vários elementos (mapa, rõtulo, …) ao layout. Todos esses objetos são representados por classes que herdam da classe base QgsLayoutItem.

Aqui está uma descrição de alguns dos principais itens de layout que podem ser adicionados a um layout.

  • mapa — Aqui nós criamos um mapa de um tamanho personalizado e renderizamos a tela do mapa atual

    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 — permite exibir rótulos. É possível modificar a sua fonte, cor, alinhamento e margem

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

    legend = QgsLayoutItemLegend(layout)
    legend.setLinkedMap(map) # map is an instance of QgsLayoutItemMap
    layout.addLayoutItem(legend)
    
  • barra de escala

    1item = QgsLayoutItemScaleBar(layout)
    2item.setStyle('Numeric') # optionally modify the style
    3item.setLinkedMap(map) # map is an instance of QgsLayoutItemMap
    4item.applyDefaultSize()
    5layout.addLayoutItem(item)
    
  • forma baseada em nós

     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)
    

Depois que um item é adicionado ao layout, ele pode ser movido e redimensionado:

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

Uma moldura é desenhada ao redor de cada item por padrão. Você pode removê-la da seguinte forma:

# for a composer label
label.setFrameEnabled(False)

Além de criar os itens de layout manualmente, o QGIS oferece suporte a modelos de layout que são essencialmente composições com todos os itens salvos em um arquivo .qpt (com sintaxe XML).

Quando a composição estiver pronta (os itens de layout foram criados e adicionados à composição), podemos continuar produzindo uma saída raster e/ou vetorial.

10.3.1. Checking layout validity

A layout is a made of a set of interconnected items and it can happen that these connections are broken during modifications (a legend connected to a removed map, an image item with missing source file,…) or you may want to apply custom constraints to the layout items. The QgsAbstractValidityCheck helps you achieve this.

A basic check looks like:

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

Here’s a check which throws a warning whenever a layout map item is set to the web mercator projection:

 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

And here’s a more complex example, which throws a warning if any layout map items are set to a CRS which is only valid outside of the extent shown in that map item:

 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. Exportando o layout

Para exportar um layout, a classe :class: QgsLayoutExporter <qgis.core.QgsLayoutExporter> deve ser usada.

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())

Use exportToSvg() or exportToImage() in case you want to export to respectively an SVG or image file instead of a PDF file.

10.3.3. Exportando um atlas como layout

Se você deseja exportar todas as páginas de um layout que tenha a opção atlas configurada e ativada, use o método atlas () no exportador (:class:` QgsLayoutExporter <qgis.core.QgsLayoutExporter>`) com pequenos ajustes. No exemplo a seguir, as páginas são exportadas para imagens PNG:

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

Observe que as saídas serão salvas na pasta do caminho base, usando a expressão do nome do arquivo de saída configurada no atlas.