15 votos

Esperar a que el lienzo termine de renderizarse antes de guardar la imagen

Estoy intentando escribir un script que guarde un renderizado de varias capas utilizando el compositor de mapas. El problema que estoy encontrando es que el script guarda antes de que qgis haya terminado de renderizar todas las capas.

Basándose en otras respuestas ( 1 , 2 , 3 ), he intentado utilizar iface.mapCanvas.mapCanvasRefreshed.connect() y puse el guardado de imágenes dentro de una función, pero sigo encontrando el mismo problema: las imágenes no incluyen todas las capas.

El código que estoy utilizando, así como las imágenes de lo que la ventana principal y las representaciones se ven a continuación.

Me he dado cuenta de que si tengo la ventana de la consola abierta y descomento los tres print layerList líneas, que el programa esperará a que termine el renderizado antes de guardar las imágenes. No estoy seguro de si esto se debe al aumento del tiempo de procesamiento, o si está cambiando la forma en que el programa se ejecuta.

¿Cómo puedo implementar esto correctamente para que todas las capas se incluyan en la imagen?

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

Cómo se ve en la ventana principal de QGIS (hay un mapa rasterizado aleatorio sobre el que se está mostrando): enter image description here

Lo que se salva: enter image description here

Como información adicional, estoy usando QGIS 2.18.7 en Windows 7

6voto

Michael Puntos 11

Aquí surgen diferentes cuestiones

Renderización en pantalla vs. renderización en una imagen

La señal mapCanvasRefreshed se emite repetidamente mientras el lienzo se renderiza en la pantalla. En el caso de la visualización en pantalla, esto da una respuesta más rápida que puede ser agradable para que un usuario vea algo que está sucediendo o ayude en la navegación.

Para el renderizado fuera de la pantalla, como guardar en un archivo, esto no es fiable (ya que sólo tendrá una imagen completa si el renderizado fue lo suficientemente rápido).

Lo que se puede hacer: no necesitamos el lienzo del mapa para renderizar su imagen. Podemos simplemente copiar el QgsMapSettings del lienzo del mapa. Estos ajustes son los parámetros que se envían al renderizador y definen qué y cómo exactamente se deben convertir las cosas de todos los proveedores de datos a una imagen rasterizada.

Registro de capas frente al lienzo del mapa

Las capas añadidas al registro no terminan en el lienzo inmediatamente, sino sólo en la siguiente ejecución del bucle de eventos. Por lo tanto, es mejor hacer una de las dos cosas siguientes

  • Inicie la renderización de la imagen en un temporizador. QTimer.singleShot(10, render_image)

  • Ejecutar QApplication.processEvents() después de añadir la capa. Esto funciona, pero es una llamada peligrosa (a veces conduce a extrañas caídas) y por lo tanto debe evitarse.

Muéstrame el código

El siguiente código hace esto (ligeramente ajustado desde QFieldSync (si estás interesado en una mayor personalización, échale un vistazo)

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)

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X