Attendere il completamento del rendering della tela prima di salvare l'immagine


11

Sto tentando di scrivere una sceneggiatura che salverà un rendering di più livelli usando il compositore di mappe. Il problema che sto riscontrando è che lo script salva prima che qgis abbia terminato il rendering di tutti i livelli.

Sulla base di diverse altre risposte ( 1 , 2 , 3 ), ho tentato di utilizzare iface.mapCanvas.mapCanvasRefreshed.connect()e inserire il salvataggio dell'immagine all'interno di una funzione, ma sto ancora riscontrando lo stesso problema: le immagini non includono tutti i livelli.

Il codice che sto usando, così come le immagini di come appaiono la finestra principale e i rendering sono elencati di seguito.

Ho notato che se ho la finestra della console aperta e decommenta le tre print layerListrighe, il programma attenderà il completamento del rendering prima di salvare le immagini. Non sono sicuro se ciò sia dovuto al tempo di elaborazione aumentato o se sta cambiando il modo in cui il programma viene eseguito.

Come posso implementarlo correttamente in modo che tutti i livelli siano inclusi nell'immagine?

from qgis.core import *
from qgis.utils import *
from qgis.gui import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import os.path

##StackExchange Version=name
##Map_Save_Folder=folder
##Map_Save_Name=string roadmap

# Create save file location
mapName = "%s.png" %Map_Save_Name
outfile = os.path.join(Map_Save_Folder,mapName)
pdfName = "%s.pdf" %Map_Save_Name
outPDF = os.path.join(Map_Save_Folder,pdfName)

# Create point and line layers for later
URIstrP = "Point?crs=EPSG:3035"
layerP = QgsVectorLayer(URIstrP,"pointsPath","memory")
provP = layerP.dataProvider()
URIstrL = "LineString?crs=EPSG:3035"
layerL = QgsVectorLayer(URIstrL,"linePath","memory")
provL = layerL.dataProvider()

# Add points to point layer
feat1 = QgsFeature()
feat2 = QgsFeature()
feat3 = QgsFeature()
feat1.setGeometry(QgsGeometry.fromPoint(QgsPoint(5200000,2600000)))
feat2.setGeometry(QgsGeometry.fromPoint(QgsPoint(5300000,2800000)))
provP.addFeatures([feat1, feat2])

# Add line to line layer
feat3.setGeometry(QgsGeometry.fromPolyline([feat1.geometry().asPoint(),feat2.geometry().asPoint()]))
provL.addFeatures([feat3])

# Set symbology for line layer
symReg = QgsSymbolLayerV2Registry.instance()
metaRegL = symReg.symbolLayerMetadata("SimpleLine")
symLayL = QgsSymbolV2.defaultSymbol(layerL.geometryType())
metaL = metaRegL.createSymbolLayer({'width':'1','color':'0,0,0'})
symLayL.deleteSymbolLayer(0)
symLayL.appendSymbolLayer(metaL)
symRendL = QgsSingleSymbolRendererV2(symLayL)
layerL.setRendererV2(symRendL)

# Set symbology for point layer
metaRegP = symReg.symbolLayerMetadata("SimpleMarker")
symLayP = QgsSymbolV2.defaultSymbol(layerP.geometryType())
metaP = metaRegP.createSymbolLayer({'size':'3','color':'0,0,0'})
symLayP.deleteSymbolLayer(0)
symLayP.appendSymbolLayer(metaP)
symRendP = QgsSingleSymbolRendererV2(symLayP)
layerP.setRendererV2(symRendP)

# Load the layers
QgsMapLayerRegistry.instance().addMapLayer(layerP)
QgsMapLayerRegistry.instance().addMapLayer(layerL)
iface.mapCanvas().refresh()


# --------------------- Using Map Composer -----------------
def custFunc():
    mapComp.exportAsPDF(outPDF)
    mapImage.save(outfile,"png")
    mapCanv.mapCanvasRefreshed.disconnect(custFunc)
    return

layerList = []
for layer in QgsMapLayerRegistry.instance().mapLayers().values():
    layerList.append(layer.id())
#print layerList
#print layerList
#print layerList

mapCanv = iface.mapCanvas()
bound = layerP.extent()
bound.scale(1.25)
mapCanv.setExtent(bound)

mapRend = mapCanv.mapRenderer()
mapComp = QgsComposition(mapRend)
mapComp.setPaperSize(250,250)
mapComp.setPlotStyle(QgsComposition.Print)

x, y = 0, 0
w, h = mapComp.paperWidth(), mapComp.paperHeight()

composerMap = QgsComposerMap(mapComp, x, y, w, h)
composerMap.zoomToExtent(bound)
mapComp.addItem(composerMap)
#mapComp.exportAsPDF(outPDF)

mapRend.setLayerSet(layerList)
mapRend.setExtent(bound)

dpmm = dpmm = mapComp.printResolution() / 25.4
mapImage = QImage(QSize(int(dpmm*w),int(dpmm*h)), QImage.Format_ARGB32)
mapImage.setDotsPerMeterX(dpmm * 1000)
mapImage.setDotsPerMeterY(dpmm * 1000)

mapPaint = QPainter()
mapPaint.begin(mapImage)

mapRend.render(mapPaint)

mapComp.renderPage(mapPaint,0)
mapPaint.end()
mapCanv.mapCanvasRefreshed.connect(custFunc)
#mapImage.save(outfile,"png")

Come appare nella finestra principale di QGIS (c'è una mappa raster casuale su cui viene visualizzata): inserisci qui la descrizione dell'immagine

Cosa viene salvato: inserisci qui la descrizione dell'immagine

Come ulteriore informazione, sto usando QGIS 2.18.7 su Windows 7


Ho anche fatto riferimento a diverse pagine Web, 1 e 2 . Ho provato ad aggiungere questi per essere post, ma il mio rappresentante non è abbastanza alto
EastWest

Nella tua ultima ultima riga, prova a sostituire mapCanv.mapCanvasRefreshed.connect(custFunc)con mapCanv.renderComplete.connect(custFunc)?
Joseph,

@Joseph Sfortunatamente, non sembrava fare la differenza. Continuo a ottenere lo stesso risultato di cui sopra
EastWest il

Forse prova a eseguire il commit delle funzionalità che hai aggiunto al livello? (cioè layerP .commitChanges()). Anche se non vedo il motivo per cui dovrebbe essere utile poiché stai solo salvando l'immagine, ma vale la pena provare. Altrimenti si spera che altri possano consigliare :)
Joseph,

@Joseph ci ho provato commitChanges(), ma senza fortuna, sfortunatamente. Grazie per il suggerimento
EastWest,

Risposte:


5

Ci sono diversi problemi che emergono qui

Rendering su schermo vs rendering su un'immagine

Il segnale mapCanvasRefreshedviene emesso ripetutamente mentre la tela viene renderizzata sullo schermo. Per la visualizzazione su schermo questo fornisce un feedback più rapido che può essere utile per un utente vedere qualcosa in corso o aiutare nella navigazione.

Per il rendering fuori schermo come il salvataggio in un file, questo non è affidabile (poiché avrai un immagine completa solo se il rendering è stato abbastanza veloce).

Cosa si può fare: non è necessario il canvas della mappa per rendere l'immagine. Possiamo semplicemente copiare QgsMapSettingsdalla tela della mappa. Queste impostazioni sono i parametri che vengono inviati al renderer e definiscono esattamente cosa e come esattamente le cose dovrebbero essere convertite da tutti i fornitori di dati in un'immagine raster.

Registro dei livelli e tela della mappa

I livelli aggiunti al registro non finiscono immediatamente nell'area di disegno ma solo nella prossima esecuzione del ciclo degli eventi. Pertanto è meglio fare una delle due cose seguenti

  • Avvia il rendering dell'immagine in un timer. QTimer.singleShot(10, render_image)

  • Esegui QApplication.processEvents()dopo aver aggiunto il livello. Funziona ma è una chiamata pericolosa da usare (a volte porta a strani incidenti) e quindi dovrebbe essere evitato.

Mostrami il codice

Il seguente codice fa questo (leggermente modificato da QFieldSync , dai un'occhiata qui se sei interessato a più personalizzazioni)

from PyQt4.QtGui import QImage, QPainter

def render_image():
    size = iface.mapCanvas().size()
    image = QImage(size, QImage.Format_RGB32)

    painter = QPainter(image)
    settings = iface.mapCanvas().mapSettings()

    # You can fine tune the settings here for different
    # dpi, extent, antialiasing...
    # Just make sure the size of the target image matches

    # You can also add additional layers. In the case here,
    # this helps to add layers that haven't been added to the
    # canvas yet
    layers = settings.layers()
    settings.setLayers([layerP.id(), layerL.id()] + layers)

    job = QgsMapRendererCustomPainterJob(settings, painter)
    job.renderSynchronously()
    painter.end()
    image.save('/tmp/image.png')

# If you don't want to add additional layers manually to the
# mapSettings() you can also do this:
# Give QGIS give a tiny bit of time to bring the layers from 
# the registry to the canvas (the 10 ms do not matter, the important
# part is that it's posted to the event loop)

# QTimer.singleShot(10, render_image)

1
Qualche idea sul renderCompletesegnale che non funziona?
Joseph,

Grazie per tutte le informazioni! Sfortunatamente, ho provato ad inserire il codice suggerito nel mio script, sostituendo completamente la sezione del compositore di mappe e sto ancora riscontrando lo stesso problema. L'immagine salvata non include i livelli linea o punto, ma solo il raster precaricato.
EastWest,

Penso che il segnale che viene emesso tra il lavoro sia completo e l'immagine sia disegnata sullo schermo. C'è un parametro painteremesso con esso sul quale puoi ancora disegnare cose aggiuntive che finiranno sull'immagine finale (e da cui probabilmente potresti anche prendere l'immagine finale per far funzionare questo approccio).
Matthias Kuhn,

1
Questa sembra la soluzione. Grazie per tutto il vostro aiuto. Una breve nota se qualcun altro finisce per trovarlo: per far funzionare correttamente QTimer, lasciare la parentesi dopo render_image, oppure python lancia un avvertimento. Dovrebbe leggereQTimer.singleShot(10, render_image)
EastWest

Oops, ovviamente. Risolto nel codice sopra
Matthias Kuhn
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.