Aguarde a tela concluir a renderização antes de salvar a imagem

11

Estou tentando escrever um script que salvará uma renderização de várias camadas usando o compositor de mapas. O problema que encontro é que o script salva antes que o qgis termine de renderizar todas as camadas.

Com base em várias outras respostas ( 1 , 2 , 3 ), tentei usar iface.mapCanvas.mapCanvasRefreshed.connect()e colocar a imagem salva dentro de uma função, mas ainda encontro o mesmo problema - as imagens não incluem todas as camadas.

O código que estou usando, bem como as imagens da aparência da janela principal e das renderizações, estão listadas abaixo.

Percebi que, se a janela do console estiver aberta e descomente as três print layerListlinhas, o programa aguardará a conclusão da renderização antes de salvar as imagens. Não tenho certeza se isso se deve ao aumento do tempo de processamento ou se está mudando a maneira como o programa é executado.

Como implemento isso corretamente para que todas as camadas sejam incluídas na imagem?

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

Como é a aparência na janela principal do QGIS (existe um mapa raster aleatório no qual está sendo exibido): insira a descrição da imagem aqui

O que é salvo: insira a descrição da imagem aqui

Como informações adicionais, estou usando o QGIS 2.18.7 no Windows 7

Leste Oeste
fonte
Também referenciei várias páginas da web, 1 e 2 . Eu tentei adicionar estes a ser post, mas meu representante não é alta o suficiente
EastWest
Na sua segunda última linha, tente substituir mapCanv.mapCanvasRefreshed.connect(custFunc)por mapCanv.renderComplete.connect(custFunc)?
31417 Joseph
@ Joseph Infelizmente, não parecia fazer a diferença. Continuo com o mesmo resultado acima
EastWest
Talvez tente confirmar os recursos que você adicionou à camada? (ie layerP .commitChanges()) Embora eu não entenda por que isso deve ajudar, pois você está salvando apenas a imagem, mas vale a pena tentar, eu acho. Caso contrário, espero que outros possam aconselhar :)
Joseph
@ Joseph Eu tentei commitChanges(), mas sem sorte, infelizmente. Obrigado pela sugestão.
EastWest 29/06

Respostas:

5

Existem diferentes problemas surgindo aqui

Renderização na tela versus renderização em uma imagem

O sinal mapCanvasRefreshedé emitido repetidamente enquanto a tela está sendo renderizada para a tela. Para exibição na tela, isso fornece um feedback mais rápido, o que pode ser bom para um usuário ver algo acontecendo ou ajudar na navegação.

Para renderizações fora da tela, como salvar em um arquivo, isso não é confiável (pois você só terá uma imagem completa se a renderização for rápida o suficiente).

O que pode ser feito: não precisamos da tela do mapa para renderizar sua imagem. Podemos apenas copiar o QgsMapSettingsda tela do mapa. Essas configurações são os parâmetros enviados ao renderizador e definem o que exatamente e como exatamente as coisas devem ser convertidas de todos os provedores de dados em uma imagem rasterizada.

Registro de camada vs tela de mapa

As camadas adicionadas ao registro não acabam na tela imediatamente, mas apenas na próxima execução do loop de eventos. Portanto, é melhor fazer uma das duas coisas a seguir

  • Inicie a renderização da imagem em um cronômetro. QTimer.singleShot(10, render_image)

  • Execute QApplication.processEvents()após adicionar a camada. Isso funciona, mas é uma chamada perigosa de usar (às vezes leva a falhas estranhas) e, portanto, deve ser evitada.

Mostre-me o código

O código a seguir faz isso (ligeiramente ajustado no QFieldSync , consulte se você estiver interessado em mais personalização)

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
fonte
1
Alguma idéia sobre o renderCompletesinal não está funcionando?
317 Joseph
Obrigado por todas as informações! Infelizmente, tentei inserir o código sugerido no meu script, substituindo completamente a seção de compositores de mapas e ainda estou encontrando o mesmo problema. A imagem salva não possui as camadas de linha ou ponto incluídas, apenas a varredura pré-carregada.
EastWest
Acho que o sinal é emitido entre o trabalho e a imagem é atraída para a tela. Existe um parâmetro painteremitido com ele, no qual você ainda pode desenhar coisas adicionais que acabarão na imagem final (e a partir da qual você provavelmente também poderá obter a imagem final para fazer essa abordagem funcionar).
Matthias Kuhn
1
Parece a solução. Obrigado por toda sua ajuda. Uma observação rápida se alguém acabar descobrindo isso - para que o QTimer funcione corretamente, deixe de fora os parênteses após render_image, ou então o python lança um aviso. Deve lerQTimer.singleShot(10, render_image)
EastWest
Opa, é claro. Corrigido no código acima
Matthias Kuhn