10. Visualizzazione e Stampa di una Mappa
Suggerimento
I frammenti di codice in questa pagina hanno bisogno dei seguenti import:
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)
Ci sono generalmente due approcci quando i dati di input devono essere visualizzati come mappe: o farlo in modo rapido usando QgsMapRendererJob o produrre un output più preciso componendo la mappa con la classe QgsLayout
.
10.1. Visualizzazione Semplice
La visualizzazione avviene creando un oggetto QgsMapSettings
per definire le impostazioni di visualizzazione e costruendo poi un QgsMapRendererJob
con tali impostazioni. Quest’ultimo viene poi utilizzato per creare l’immagine risultante.
Ecco un esempio:
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. Visualizzare layer con diversi SR
Se hai più di un layer e questi hanno un SR diverso, il semplice esempio precedente probabilmente non funzionerà: per ottenere i valori giusti dai calcoli di estensione devi impostare esplicitamente il SR di destinazione
layers = [iface.activeLayer()]
settings = QgsMapSettings()
settings.setLayers(layers)
settings.setDestinationCrs(layers[0].crs())
10.3. Risultato con layout di stampa
Il layout di stampa è uno strumento molto utile se vuoi ottenere un output più sofisticato rispetto alla semplice rappresentazione mostrata sopra. Puoi creare layout cartografici complessi composti da viste della mappa, etichette, legende, tabelle e altri elementi solitamente presenti nelle mappe cartacee. I layout possono essere esportati in PDF, SVG, immagini raster o stampati direttamente su una stampante.
Il layout è costituito da un gruppo di classi. Tutte appartengono alla libreria principale. L’applicazione QGIS ha una comoda interfaccia grafica per il posizionamento degli elementi, anche se non è disponibile nella libreria GUI. Se non hai familiarità con il framework Qt Graphics View, ti consigliamo di consultare ora la documentazione, perché il layout si basa su di esso.
La classe principale del layout è la classe QgsLayout
, che deriva dalla classe Qt QGraphicsScene. Creiamo un’istanza di questa classe:
project = QgsProject.instance()
layout = QgsPrintLayout(project)
layout.initializeDefaults()
Questo metodo inizializza il layout con alcune impostazioni predefinite, in particolare aggiungendo una pagina A4 vuota al layout. Puoi creare layout senza chiamare il metodo initializeDefaults()
, ma dovrai occuparti personalmente di aggiungere pagine al layout.
Il codice precedente crea un layout «temporaneo» che non è visibile nella GUI. Può essere utile, ad esempio, per aggiungere rapidamente alcuni elementi ed esportare senza modificare il progetto stesso né esporre le modifiche all’utente. Se vuoi che il layout venga salvato/ripristinato insieme al progetto e sia disponibile nel gestore dei layout, aggiungi:
layout.setName("MyLayout")
project.layoutManager().addLayout(layout)
Ora possiamo aggiungere vari elementi (mappa, etichetta, …) al layout. Tutti questi oggetti sono rappresentati da classi che ereditano dalla classe base QgsLayoutItem
.
Ecco una descrizione di alcuni dei principali oggetti di layout che possono essere aggiunti a un layout.
map — Qui creiamo una mappa di dimensioni personalizzate e visualizziamo l’area mappa della mappa corrente.
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 — permetta la visualizzazione di etichette. É possibile modificarne il carattere, colore, allineamento e margine
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 di scala
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 basata su nodi
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)
Una volta aggiunto un oggetto al layout, è possibile spostarlo e ridimensionarlo:
item.attemptMove(QgsLayoutPoint(1.4, 1.8, QgsUnitTypes.LayoutCentimeters))
item.attemptResize(QgsLayoutSize(2.8, 2.2, QgsUnitTypes.LayoutCentimeters))
Per impostazione predefinita, attorno a ciascun elemento viene disegnata una cornice. Puoi rimuoverla come segue:
# for a composer label
label.setFrameEnabled(False)
Oltre a creare gli oggetti del layout a mano, QGIS supporta i modelli di layout, che sono essenzialmente composizioni con tutti i loro oggetti salvati in un file .qpt (con sintassi XML).
Una volta che la composizione è pronta (gli oggetti del layout sono stati creati e aggiunti alla composizione), possiamo procedere alla produzione di un output raster e/o vettoriale.
10.3.1. Controllo della validità del layout
Un layout è costituito da un insieme di elementi interconnessi e può accadere che queste connessioni vengano interrotte durante le modifiche (una legenda collegata a una mappa rimossa, un elemento immagine con un file sorgente mancante, …) o che si vogliano applicare vincoli personalizzati agli elementi del layout. La QgsAbstractValidityCheck
aiuta a raggiungere questo obiettivo.
Un controllo di base si presenta come segue:
@check.register(type=QgsAbstractValidityCheck.TypeLayoutCheck)
def my_layout_check(context, feedback):
results = ...
return results
Ecco un controllo che genera un avviso ogni volta che un elemento della mappa di layout è impostato sulla proiezione web mercator:
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
Ed ecco un esempio più complesso, che genera un avviso se uno qualsiasi degli elementi della mappa di layout è impostato su un SR valido solo al di fuori dell’estensione mostrata in quell’elemento della mappa:
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. Esporta il layout
Per esportare un layout, è necessario utilizzare la classe QgsLayoutExporter
.
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())
Usa exportToSvg()
o exportToImage()
nel caso in cui tui voglia esportare rispettivamente in un file SVG o in un file immagine invece che in un file PDF.
10.3.3. Esportare un layout atlante
Se vuoi esportare tutte le pagine da un layout che ha l’opzione atlante configurata e abilitata, devi usare il metodo atlas()
nell’esportatore (QgsLayoutExporter
) con piccole modifiche. Nell’esempio seguente, le pagine sono esportate in immagini PNG:
exporter.exportToImage(layout.atlas(), base_path, 'png', QgsLayoutExporter.ImageExportSettings())
Da notare che gli output saranno salvati nella cartella del percorso di base, utilizzando l’espressione del nome del file di output configurata su atlante.