10. Rendu cartographique et Impression

Indication

Les extraits de code sur cette page nécessitent les importations suivantes :

 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)

Il y a généralement deux approches lorsque les données d’entrée doivent être rendues sous forme de carte : soit le faire rapidement en utilisant QgsMapRendererJob, soit produire une sortie plus fine en composant la carte avec la classe QgsLayout.

10.1. Rendu simple

Le rendu est effectué en créant un objet QgsMapSettings pour définir les paramètres de rendu, puis en construisant un objet QgsMapRendererJob avec ces paramètres. Ce dernier est ensuite utilisé pour créer l’image résultante.

Voici un exemple:

 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. Rendu des couches ayant différents SCR

Si vous avez plus d’une couche et qu’elles ont un CRS différent, l’exemple simple ci-dessus ne fonctionnera probablement pas : pour obtenir les bonnes valeurs à partir des calculs d’étendue, vous devez définir explicitement le CRS de destination

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

10.3. Sortie en utilisant la mise en page

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.

La mise en page consiste en un ensemble de classes. Elles appartiennent toutes à la bibliothèque centrale. L’application QGIS dispose d’une interface graphique pratique pour le placement des éléments, bien qu’elle ne soit pas disponible dans la bibliothèque GUI. Si vous n’êtes pas familiés avec le Qt Graphics View framework, alors vous êtes encouragé à consulter la documentation dès maintenant, car la mise en page est basée sur celui-ci.

La classe centrale de la mise en page est la classe QgsLayout, qui est dérivée de la classe Qt QGraphicsScene. Créons une instance de celle-ci :

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

Cette méthode initialise la mise en page avec certains paramètres par défaut, spécifiquement en ajoutant une page A4 vide à la mise en page. Vous pouvez créer des mises en page sans appeler la méthode initializeDefaults(), mais vous devrez vous charger vous-même d’ajouter des pages à la mise en page.

Le code précédent crée une mise en page « temporaire » qui n’est pas visible dans l’interface graphique. Cela peut être pratique pour, par exemple, ajouter rapidement quelques éléments et exporter sans modifier le projet lui-même ni exposer ces changements à l’utilisateur. Si vous voulez que la mise en page soit sauvegardée/restaurée avec le projet et disponible dans le gestionnaire de mise en page, alors ajoutez :

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

Nous pouvons maintenant ajouter divers éléments (carte, étiquette, …) à la mise en page. Tous ces objets sont représentés par des classes qui héritent de la classe de base QgsLayoutItem.

Voici une description de certains des principaux éléments qui peuvent être ajoutés à une mise en page.

  • map — Ici nous créons une carte d’une taille personnalisée et rendons le canevas de la carte actuelle

    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)
    
  • étiquette — permet d’afficher des étiquettes. Il est possible d’en modifier la police, la couleur, l’alignement et les marges:

    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)
    
  • Échelle graphique

    1item = QgsLayoutItemScaleBar(layout)
    2item.setStyle('Numeric') # optionally modify the style
    3item.setLinkedMap(map) # map is an instance of QgsLayoutItemMap
    4item.applyDefaultSize()
    5layout.addLayoutItem(item)
    
  • Forme basée sur les nœuds

     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)
    

Une fois qu’un élément est ajouté à la mise en page, il peut être déplacé et redimensionné :

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

Par défaut, un cadre est dessiné autour de chaque élément. Vous pouvez le supprimer comme suit

# for a composer label
label.setFrameEnabled(False)

Outre la création manuelle des éléments de mise en page, QGIS prend en charge les modèles de mise en page qui sont essentiellement des compositions dont tous les éléments sont enregistrés dans un fichier .qpt (avec une syntaxe XML).

Une fois que la composition est prête (les éléments de mise en page ont été créés et ajoutés à la composition), nous pouvons procéder à la production d’une sortie raster et/ou vecteur.

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. Exporter la mise en page

Pour exporter une mise en page, la classe QgsLayoutExporter doit être utilisée.

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. Exporter un atlas

Si vous souhaitez exporter toutes les pages d’une mise en page pour laquelle l’option atlas est configurée et activée, vous devez utiliser la méthode atlas() dans l’exportateur (QgsLayoutExporter) avec de petits ajustements. Dans l’exemple suivant, les pages sont exportées en image PNG :

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

Notez que les sorties seront enregistrées dans le dossier du chemin de base, en utilisant l’expression du nom de fichier de sortie configurée sur l’atlas.