Attendez que le canevas termine le rendu avant d'enregistrer l'image

11

J'essaie d'écrire un script qui enregistrera un rendu de plusieurs couches à l'aide du compositeur de carte. Le problème que je rencontre est que le script enregistre avant que qgis ait fini de rendre toutes les couches.

Sur la base de plusieurs autres réponses ( 1 , 2 , 3 ), j'ai essayé d'utiliser iface.mapCanvas.mapCanvasRefreshed.connect()et de mettre l'enregistrement d'image à l'intérieur d'une fonction, mais je rencontre toujours le même problème - les images n'incluent pas tous les calques.

Le code que j'utilise, ainsi que des images de l'apparence de la fenêtre principale et des rendus sont répertoriés ci-dessous.

J'ai remarqué que si la fenêtre de la console est ouverte et que les trois print layerListlignes ne sont pas commentées , le programme attendra la fin du rendu avant d'enregistrer les images. Je ne sais pas si cela est dû à l'augmentation du temps de traitement ou si cela change la façon dont le programme s'exécute.

Comment puis-je implémenter cela correctement pour que tous les calques soient inclus dans l'image?

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

À quoi il ressemble dans la fenêtre principale de QGIS (il y a une carte raster aléatoire sur laquelle il est affiché): entrez la description de l'image ici

Ce qui est enregistré: entrez la description de l'image ici

Pour plus d'informations, j'utilise QGIS 2.18.7 sur Windows 7

Est Ouest
la source
J'ai également référencé plusieurs pages Web, 1 et 2 . J'ai essayé de les ajouter pour être postés, mais mon représentant n'est pas assez élevé
EastWest
Dans votre avant-dernière ligne, essayez de remplacer mapCanv.mapCanvasRefreshed.connect(custFunc)par mapCanv.renderComplete.connect(custFunc)?
Joseph
@Joseph Malheureusement, cela n'a pas semblé faire de différence. J'obtiens toujours le même résultat que ci
EastWest
Essayez peut-être de valider les fonctionnalités que vous avez ajoutées à la couche? (c. layerP .commitChanges()-à-d.). Bien que je ne vois pas pourquoi cela devrait aider, car vous enregistrez uniquement l'image, mais cela vaut la peine d'essayer, je suppose. Sinon, j'espère que d'autres pourront vous conseiller :)
Joseph
@Joseph J'ai essayé commitChanges(), mais malheureusement pas de chance. Merci pour la suggestion.
EastWest

Réponses:

5

Il y a différents problèmes qui se posent ici

Rendu à l'écran vs rendu à une image

Le signal mapCanvasRefreshedest émis à plusieurs reprises pendant le rendu du canevas à l'écran. Pour l'affichage à l'écran, cela donne un retour plus rapide, ce qui peut être agréable pour un utilisateur de voir quelque chose en cours ou d'aider à la navigation.

Pour le rendu hors écran comme l'enregistrement dans un fichier, ce n'est pas fiable (car vous n'aurez une image complète que si le rendu était assez rapide).

Ce qui peut être fait: nous n'avons pas besoin du canevas de carte pour rendre votre image. Nous pouvons simplement copier le QgsMapSettingsdepuis le canevas de la carte. Ces paramètres sont les paramètres qui sont envoyés au rendu et définissent exactement et comment exactement les choses doivent être converties de tous les fournisseurs de données en une image raster.

Registre de couches vs canevas de carte

Les couches ajoutées au registre ne se retrouvent pas immédiatement sur le canevas, mais uniquement lors de la prochaine exécution de la boucle d'événements. Par conséquent, vous feriez mieux de faire l'une des deux choses suivantes

  • Démarrez le rendu de l'image dans une minuterie. QTimer.singleShot(10, render_image)

  • Exécutez QApplication.processEvents()après avoir ajouté le calque. Cela fonctionne mais c'est un appel dangereux à utiliser (conduit parfois à des plantages étranges) et doit donc être évité.

Montrez-moi le code

Le code suivant fait cela (légèrement ajusté de QFieldSync , jetez un œil là-dedans si vous êtes intéressé par plus de personnalisation)

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)
Matthias Kuhn
la source
1
Une idée que le renderCompletesignal ne fonctionne pas?
Joseph
Merci pour toutes les informations! Malheureusement, j'ai essayé d'insérer le code suggéré dans mon script, remplaçant complètement la section de composition de carte, et je rencontre toujours le même problème. L’image enregistrée ne comprend pas les couches de lignes ou de points, mais uniquement le raster préchargé.
EastWest
Je pense que le signal est émis entre le travail est terminé et l'image est dessinée à l'écran. Il y a un paramètre painterémis avec lui sur lequel vous pouvez toujours dessiner des choses supplémentaires qui se retrouveront sur l'image finale (et à partir de laquelle vous pourriez probablement aussi prendre l'image finale pour faire fonctionner cette approche).
Matthias Kuhn
1
Cela ressemble à la solution. Merci pour votre aide. Une note rapide si quelqu'un d'autre finit par trouver cela - pour que le QTimer fonctionne correctement, omettez les parenthèses après render_image, ou bien python lance un avertissement. Devrait lireQTimer.singleShot(10, render_image)
EastWest
Oups, bien sûr. Corrigé dans le code ci
Matthias Kuhn