Générer une carte
Nous allons maintenant générer une carte PDF à partir du canvas QGIS. À chaque déplacement dans le canvas, une nouvelle exécution du script final mettra à jour la carte.
Environnement de travail
Ouvez les couches peaks, eau, sol, IRIS et troncons_routes, fournies en bas de cet article, dans un projet QGIS vierge, ainsi qu'un fond de carte OpenStreetMap.
Vérifiez la bonne supersposition des couches (les projections), puis appliquez une symbologie évidente.
Enregistrez votre projet puis décochez les couches eau, sol, IRIS et troncons_routes.
Ouvrez également le Layouts Manager. De même pour les besoins de la démonstration, faîtes-en sorte que vos écrans vous permettent de visualiser en même temps votre projet QGIS, console et éditeur Python ouverts, le Layouts Manager, et encore un peu de place pour un futur layout.
Zoomez sur la couche peak. Nous allons exécuter les bribes de code suivantes de façon itérative, en les décryptant une par une. Au terme de ce chapitre, vous aurez un code complet vous permettant de générer une carte simple. Sauvegardez petit-à-petit l'intégralité du code dans un fichier texte.
Layout
Nous générons un layout vide, que vous allez voir apparaître dans le Layouts Manager.
project = QgsProject.instance() manager = project.layoutManager() layoutName = "PrintLayout1" # Vérification de la non-existence d'un layout de même nom layouts_list = manager.printLayouts() for layout in layouts_list: if layout.name() == layoutName: manager.removeLayout(layout) # Génération d'un layout vide layout = QgsPrintLayout(project) layout.initializeDefaults() layout.setName(layoutName) manager.addLayout(layout)
Encart cartographique
Nous tirons maintenant une carte vide dans la layout.
# Charger une carte vide map = QgsLayoutItemMap(layout) map.setRect(20, 20, 20, 20) # Mettre un canvas basique rectangle = QgsRectangle(1355502, -46398, 1734534, 137094) map.setExtent(rectangle) layout.addLayoutItem(map)
Canvas courant
Nous plaçons le canvas courant dans la carte.
# Mettre finalement le canvas courant canvas = iface.mapCanvas() map.setExtent(canvas.extent()) layout.addLayoutItem(map) # Redimensionner la carte map.attemptMove(QgsLayoutPoint(5, 27, QgsUnitTypes.LayoutMillimeters)) map.attemptResize(QgsLayoutSize(220, 178, QgsUnitTypes.LayoutMillimeters)) map.setFrameEnabled(True)
Légende dynamique
Ci-après le code d'affichage de la légende complète (une légende fixe, statique). Cette portion du code est commentée (encadré dans une balise """
, et donc inactive).
En effet juste en dessous, nous allons préférer ne retenir que les couches apparaissant dans la carte (cochées dans le panneau des couches).
""" # Mettre une légende complète legend = QgsLayoutItemLegend(layout) legend.setTitle("Légende") layout.addLayoutItem(legend) legend.attemptMove(QgsLayoutPoint(246, 5, QgsUnitTypes.LayoutMillimeters)) """ # Légende personnalisée tree_layers = project.layerTreeRoot().children() checked_layers = [layer.name() for layer in tree_layers if layer.isVisible()] print(f"Je vais ajouter uniquement {checked_layers} à la légende." ) layers_to_remove = [layer for layer in project.mapLayers().values() if layer.name() not in checked_layers] print(f"Je vais retirer {layers_to_remove} de la légende." ) legend = QgsLayoutItemLegend(layout) legend.setTitle("Légende") layout.addLayoutItem(legend) legend.attemptMove(QgsLayoutPoint(240, 24, QgsUnitTypes.LayoutMillimeters)) # Cette ligne permettra de ne pas sortir les couches inutilisées de votre panneau des calques QGIS, mais uniquement de la légende legend.setAutoUpdateModel(False) m = legend.model() g = m.rootGroup() for l in layers_to_remove: g.removeLayer(l) legend.adjustBoxSize()
Jouez avec les print
et la console python avec votre layout ouvert pour bien comprendre ce qui se passe.
Algorithmique
Pour la génération de notre légende dynamique ci-dessus, nous avons commencer par lister 3 variables.
- Une contenant toutes les couches (
tree_layers
) - Une autre contenant uniquement les couches affichées (
checked_layers
) - Enfin une dernière ne contenant que les couches non-cochées (
layers_to_remove
), grâce à une 1ère soustraction des couches cochées
En algorithmique :
layers_to_remove = tree_layers - checked_layers
- Ensuite nous affichons la légende complète puis en retirons
layers_to_remove
dans une 2nd soustraction.
Toujours en algorithmique :
my_legend = legend - layers_to_remove
- Note personnelle : on pourrait aller plus loin dans cette légende dynamique en masquant de la légende les couches dont aucune entité n'apparaît dans le canvas courant (la couche adresse notamment).
Titre
# Titre title = QgsLayoutItemLabel(layout) title.setText("Ma Jolie Carte") title.setFont(QFont("Verdana", 28)) title.adjustSizeToText() layout.addLayoutItem(title) title.attemptMove(QgsLayoutPoint(5, 4, QgsUnitTypes.LayoutMillimeters))
Tout comme avec la légende, vous connaissez maintenant la méthode attemptMove
qui vous permet de positioner les éléments dans votre layout.
Les chiffres en argument sont dans l'ordre : la distance à partir du bord gauche du layout, puis la distance à partir du bord haut.
Sous-titre
# Sous-titre subtitle = QgsLayoutItemLabel(layout) subtitle.setText("Est-elle belle ?") subtitle.setFont(QFont("Verdana", 17)) subtitle.adjustSizeToText() layout.addLayoutItem(subtitle) subtitle.attemptMove(QgsLayoutPoint(5, 19, QgsUnitTypes.LayoutMillimeters))
Échelle
# Échelle scalebar = QgsLayoutItemScaleBar(layout) scalebar.setStyle('Single Box') scalebar.setUnits(QgsUnitTypes.DistanceKilometers) scalebar.setNumberOfSegments(2) scalebar.setNumberOfSegmentsLeft(0) scalebar.setUnitsPerSegment(5) scalebar.setLinkedMap(map) scalebar.setUnitLabel('km') scalebar.setFont(QFont('Verdana', 20)) scalebar.update() layout.addLayoutItem(scalebar) scalebar.attemptMove(QgsLayoutPoint(10, 185, QgsUnitTypes.LayoutMillimeters))
Logo
# Logo Logo = QgsLayoutItemPicture(layout) Logo.setPicturePath("https://master-geomatique.org/templates/theme3005/images/logo-ucp-cyu.png") layout.addLayoutItem(Logo) Logo.attemptResize(QgsLayoutSize(40, 15, QgsUnitTypes.LayoutMillimeters)) Logo.attemptMove(QgsLayoutPoint(250,4, QgsUnitTypes.LayoutMillimeters))
Vous utilisez ici la méthode attemptResize
pour redimensionner l'élément. Les chiffres en argument sont dans l'ordre la largeur, puis la hauteur.
Flèche nord
Maintenant que vous savez mettre une image, ajoutez donc une flèche vers le nord correctement positionnée.
Texte
# Texte TextCustom = QgsLayoutItemLabel(layout) TextCustom.setText("Le massif des Écrins est un grand massif montagneux des Alpes françaises situé dans les Hautes-Alpes et en Isère.\ \n\nIl abrite d'importants glaciers, tant en nombre qu'en taille et possède deux sommets de plus de 4 000 mètres.\ \n\nIl était autrefois également nommé massif du Pelvoux.\ \n\nL'Oisans (bassin de la Romanche) au nord-ouest, le Champsaur (haut-bassin du Drac) au sud-ouest, et le Briançonnais (bassin de la Guisane) au nord-est recouvrent une partie du massif.") TextCustom.setFont(QFont("Verdana", 11)) layout.addLayoutItem(TextCustom) TextCustom.attemptResize(QgsLayoutSize(60, 120, QgsUnitTypes.LayoutMillimeters)) TextCustom.attemptMove(QgsLayoutPoint(230, 80, QgsUnitTypes.LayoutMillimeters))
Source
Maintenant que vous savez mettre un texte (un simple label
finalement, comme les titre et sous-titre), ajoutez la source.
- Note personnelle : on pourrait aller plus loin en allant chercher les sources directement dans les méta-données de nos couches.
Export
Vous connaissez déjà la manœuvre pour un export. Pensez à adapter le chemin utilisé ci-dessous.
# Export PDF manager = QgsProject.instance().layoutManager() layout = manager.layoutByName("PrintLayout1") exporter = QgsLayoutExporter(layout) exporter.exportToPdf("C:/Users/georg/Downloads/Ma Belle Carte.pdf",QgsLayoutExporter.PdfExportSettings())
En ouvrant le PDF dans votre navigateur, en vous déplaçant sur la carte, en cochant-décochant certaines couches puis en re-lançant l'intégralité du code ci-dessus, vous verrez votre PDF exporté être mis à jour en fonction.
- Note personnelle : on pourrait aller plus loin en ajoutant une vraie flèche nord, dynamique, suivant l'orientation du canvas.
Et maintenant ?
Maintenant vous savez :
- Faire une boucle
- Faire une sélection
- Faire une sélection itérative (sélectionner dans une boucle)
- Exporter une carte
Et bien que se passerait-il si vous mettiez la sélection et l'export dans une boucle ? La production de cartes en série, pardi🤘.
À vous de jouer !