Warten Sie, bis die Leinwand fertig gerendert ist, bevor Sie das Bild speichern

11

Ich versuche, ein Skript zu schreiben, das das Rendern mehrerer Ebenen mit dem Map Composer speichert. Das Problem, auf das ich stoße, ist, dass das Skript gespeichert wird, bevor qgis alle Ebenen gerendert hat.

Basierend auf mehreren anderen Antworten ( 1 , 2 , 3 ) habe ich versucht, iface.mapCanvas.mapCanvasRefreshed.connect()die Bildspeicherung zu verwenden und in eine Funktion zu integrieren, aber ich habe immer noch das gleiche Problem: Die Bilder enthalten nicht alle Ebenen.

Der von mir verwendete Code sowie Bilder des Hauptfensters und der Renderings sind unten aufgeführt.

Ich habe festgestellt print layerList, dass das Programm wartet, bis das Rendern abgeschlossen ist , wenn das Konsolenfenster geöffnet und die drei Zeilen auskommentiert werden, bevor die Bilder gespeichert werden. Ich bin nicht sicher, ob dies auf die erhöhte Verarbeitungszeit zurückzuführen ist oder ob sich die Ausführung des Programms ändert.

Wie implementiere ich das richtig, damit alle Ebenen im Bild enthalten sind?

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

Wie es im QGIS-Hauptfenster aussieht (es gibt eine zufällige Rasterkarte, auf der es angezeigt wird): Geben Sie hier die Bildbeschreibung ein

Was wird gespeichert: Geben Sie hier die Bildbeschreibung ein

Als weitere Information verwende ich QGIS 2.18.7 unter Windows 7

Ost-West
quelle
Ich habe auch auf mehrere Webseiten verwiesen, 1 und 2 . Ich habe versucht, diese als Post hinzuzufügen, aber mein Repräsentant ist nicht hoch genug
EastWest
In Ihrem zweiten letzte Zeile, versuchen Sie ersetzen mapCanv.mapCanvasRefreshed.connect(custFunc)mit mapCanv.renderComplete.connect(custFunc)?
Joseph
@ Joseph Leider schien es keinen Unterschied zu machen. Ich bekomme immer noch das gleiche Ergebnis wie oben
EastWest
Versuchen Sie vielleicht, die Funktionen zu übernehmen, die Sie der Ebene hinzugefügt haben? (dh layerP .commitChanges()). Ich verstehe zwar nicht, warum es helfen sollte, da Sie nur das Bild speichern, aber einen Versuch wert sind, denke ich. Ansonsten können hoffentlich andere raten :)
Joseph
@ Joseph Ich habe es versucht commitChanges(), aber leider kein Glück. Danke für den Vorschlag.
EastWest

Antworten:

5

Hier tauchen verschiedene Probleme auf

Rendern auf dem Bildschirm vs. Rendern in ein Bild

Das Signal mapCanvasRefreshedwird wiederholt ausgegeben, während die Leinwand auf dem Bildschirm gerendert wird. Für die Bildschirmanzeige gibt dies ein schnelleres Feedback, das für einen Benutzer hilfreich sein kann, um etwas zu sehen oder bei der Navigation zu helfen.

Für das Rendern außerhalb des Bildschirms wie das Speichern in einer Datei ist dies nicht zuverlässig (da Sie nur dann ein vollständiges Bild erhalten, wenn das Rendern schnell genug war).

Was kann getan werden: Wir benötigen keine Kartenleinwand, um Ihr Bild zu rendern. Wir können das einfach QgsMapSettingsvon der Kartenleinwand kopieren . Diese Einstellungen sind die Parameter, die an den Renderer gesendet werden und definieren, was genau und wie genau Dinge von allen Datenanbietern in ein Rasterbild konvertiert werden sollen.

Ebenenregistrierung gegen Kartenleinwand

Zur Registrierung hinzugefügte Ebenen landen nicht sofort auf der Zeichenfläche, sondern erst beim nächsten Durchlauf der Ereignisschleife. Daher ist es besser, eines der folgenden beiden Dinge zu tun

  • Starten Sie die Bildwiedergabe in einem Timer. QTimer.singleShot(10, render_image)

  • Führen Sie QApplication.processEvents()nach der Schicht hinzugefügt wird . Dies funktioniert, ist jedoch ein gefährlicher Aufruf (führt manchmal zu seltsamen Abstürzen) und sollte daher vermieden werden.

Zeig mir den Code

Der folgende Code tut dies (leicht angepasst von QFieldSync , schauen Sie dort nach, wenn Sie an weiteren Anpassungen interessiert sind).

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
quelle
1
Irgendeine Idee, dass das renderCompleteSignal nicht funktioniert?
Joseph
Danke für alle Informationen! Leider habe ich versucht, den vorgeschlagenen Code in mein Skript einzufügen, wodurch der Abschnitt "Map Composer" vollständig ersetzt wurde, und es tritt immer noch das gleiche Problem auf. Das gespeicherte Bild enthält nicht die Linien- oder Punktebenen, sondern nur das vorinstallierte Raster.
EastWest
Ich denke, das Signal wird zwischen dem Abschluss des Auftrags und dem Zeichnen des Bildes auf den Bildschirm ausgegeben. Es wird ein Parameter painterausgegeben, mit dem Sie noch zusätzliche Dinge zeichnen können, die auf dem endgültigen Bild landen (und von denen Sie wahrscheinlich auch das endgültige Bild nehmen könnten, damit dieser Ansatz funktioniert).
Matthias Kuhn
1
Das sieht nach der Lösung aus. Danke für deine Hilfe. Ein kurzer Hinweis, wenn jemand anderes dies findet - damit der QTimer ordnungsgemäß funktioniert, lassen Sie die Klammer nach render_image weg, oder Python gibt eine Warnung aus. Sollte lesenQTimer.singleShot(10, render_image)
EastWest
Ups natürlich. Im obigen Code behoben
Matthias Kuhn