Estoy intentando escribir un script que guardará una representación de varias capas usando el compositor de mapas. El problema con el que me encuentro es que el script se guarda antes de que qgis haya terminado de procesar todas las capas.
Basado en varias otras respuestas ( 1 , 2 , 3 ), he intentado usar iface.mapCanvas.mapCanvasRefreshed.connect()
y poner la imagen guardada dentro de una función, pero todavía encuentro el mismo problema: las imágenes no incluyen todas las capas.
El código que estoy usando, así como las imágenes de cómo se ven la ventana principal y las representaciones se enumeran a continuación.
Me di cuenta de que si tengo la ventana de la consola abierta y descomento las tres print layerList
líneas, el programa esperará a que finalice el renderizado antes de guardar las imágenes. No estoy seguro de si esto se debe al mayor tiempo de procesamiento o si está cambiando la forma en que se ejecuta el programa.
¿Cómo implemento esto correctamente para que todas las capas estén incluidas 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 ráster aleatorio en el que se muestra):
Como información adicional, estoy usando QGIS 2.18.7 en Windows 7
mapCanv.mapCanvasRefreshed.connect(custFunc)
conmapCanv.renderComplete.connect(custFunc)
?layerP .commitChanges()
) Aunque no veo por qué debería ayudar, ya que solo está guardando la imagen, pero vale la pena intentarlo, supongo. De lo contrario, espero que otros puedan aconsejar :)commitChanges()
, pero no tuve suerte, desafortunadamente. Gracias por la sugerencia.Respuestas:
Aquí surgen diferentes problemas
Renderizado en pantalla vs renderizado en una imagen
La señal
mapCanvasRefreshed
se emite repetidamente mientras el lienzo se representa en la pantalla. Para la visualización en pantalla, esto proporciona una respuesta más rápida que puede ser agradable para que un usuario vea algo que sucede o ayude en la navegación.Para el renderizado fuera de pantalla como guardar en un archivo, esto no es confiable (ya que solo tendrá una imagen completa si el renderizado fue lo suficientemente rápido).
Qué se puede hacer: no necesitamos el lienzo del mapa para representar su imagen. Podemos copiarlo
QgsMapSettings
desde el lienzo del mapa. Estas configuraciones son los parámetros que se envían al procesador y definen exactamente qué y exactamente cómo se deben convertir las cosas de todos los proveedores de datos a una imagen ráster.Registro de capa vs lienzo de mapa
Las capas agregadas al registro no terminan en el lienzo inmediatamente, sino solo en la próxima ejecución del bucle de eventos. Por lo tanto, es mejor hacer una de las siguientes dos cosas
Comience la representación de la imagen en un temporizador.
QTimer.singleShot(10, render_image)
EjecutarEsto funciona, pero es una llamada peligrosa de usar (a veces conduce a accidentes extraños) y, por lo tanto, debe evitarse.QApplication.processEvents()
después de agregar la capa.Muéstrame el código
El siguiente código hace esto (ligeramente ajustado desde QFieldSync , eche un vistazo allí si está interesado en una mayor personalización)
fuente
renderComplete
señal no funciona?painter
emite un parámetro con el que aún puede dibujar cosas adicionales que terminarán en la imagen final (y de las cuales probablemente también podría tomar la imagen final para que este enfoque funcione).QTimer.singleShot(10, render_image)