Подождите, пока холст завершит рендеринг, прежде чем сохранять изображение

11

Я пытаюсь написать скрипт, который будет сохранять рендеринг нескольких слоев с помощью компоновщика карт. Проблема, с которой я сталкиваюсь, заключается в том, что скрипт сохраняет до того, как qgis закончит рендеринг всех слоев.

Основываясь на нескольких других ответах ( 1 , 2 , 3 ), я попытался использовать iface.mapCanvas.mapCanvasRefreshed.connect()и поместить сохранение изображения в функцию, но я все еще сталкиваюсь с той же проблемой - изображения не включают все слои.

Код, который я использую, а также изображения того, как выглядят главное окно и визуализация, перечислены ниже.

Я заметил, что если у меня открыто окно консоли и раскомментированы три print layerListстроки, то программа будет ждать окончания рендеринга, прежде чем сохранять изображения. Я не уверен, связано ли это с увеличением времени обработки или с изменением способа выполнения программы.

Как правильно реализовать это, чтобы все слои были включены в изображение?

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

Как это выглядит в главном окне QGIS (там отображается случайная растровая карта): введите описание изображения здесь

Что сохраняется: введите описание изображения здесь

В качестве дополнительной информации я использую QGIS 2.18.7 в Windows 7

Восток-Запад
источник
Я также ссылался на несколько веб-страниц, 1 и 2 . Я пытался добавить их в пост, но мой представитель недостаточно высок
EastWest
Во второй последней строке попробуйте заменить mapCanv.mapCanvasRefreshed.connect(custFunc)на mapCanv.renderComplete.connect(custFunc)?
Джозеф
@ Джозеф К сожалению, это не имело значения. Я все еще получаю тот же результат, что и выше
EastWest
Возможно, попробуйте добавить функции, которые вы добавили в слой? (то есть layerP .commitChanges()). Хотя я не понимаю, почему это должно помочь, поскольку вы только сохраняете изображение, но стоит попробовать. В противном случае, надеюсь, другие могут посоветовать :)
Джозеф
@ Джозеф Я пытался commitChanges(), но, к сожалению, не повезло. Спасибо за предложение.
EastWest

Ответы:

5

Здесь появляются разные проблемы

Рендеринг на экране против рендеринга в изображение

Сигнал mapCanvasRefreshedиспускается многократно, пока холст отображается на экране. Для отображения на экране это дает более быструю обратную связь, которая может быть полезной для пользователя, чтобы увидеть что-то происходящее или помочь в навигации.

Для рендеринга вне экрана, такого как сохранение в файл, это ненадежно (поскольку у вас будет полное изображение, только если рендеринг был достаточно быстрым).

Что можно сделать: нам не нужен холст карты для визуализации вашего изображения. Мы можем просто скопировать QgsMapSettingsс холста карты. Эти параметры являются параметрами, которые отправляются в средство визуализации и определяют, что именно и как именно должно быть преобразовано из всех поставщиков данных в растровое изображение.

Реестр слоев против холста карты

Слои, добавленные в реестр, не попадают на холст сразу, а только при следующем запуске цикла событий. Поэтому вам лучше сделать одну из следующих двух вещей

  • Запустите рендеринг изображения в таймере. QTimer.singleShot(10, render_image)

  • Запустите QApplication.processEvents()после добавления слоя. Это работает, но это опасный вызов (иногда приводит к странным сбоям), и поэтому его следует избегать.

Покажи мне код

В следующем коде это делается (слегка отрегулировано с QFieldSync , посмотрите там, если вы заинтересованы в дополнительной настройке)

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)
Матиас Кун
источник
1
Есть идеи о том, что renderCompleteсигнал не работает?
Джозеф
Спасибо всем за информацию! К сожалению, я попытался вставить предложенный код в мой скрипт, полностью заменив раздел компоновщика карт, и все еще сталкиваюсь с той же проблемой. Сохраненное изображение не содержит линейных или точечных слоев, а только предварительно загруженный растр.
EastWest
Я думаю, что сигнал испускается между заданием завершено и изображение выводится на экран. painterВместе с ним выдается параметр, по которому вы все равно можете рисовать дополнительные вещи, которые в конечном итоге окажутся на конечном изображении (и из которого вы, вероятно, также могли бы взять окончательное изображение, чтобы этот подход работал).
Матиас Кун
1
Это похоже на решение. Спасибо за вашу помощь. Одно быстрое замечание, если кто-то в конце концов обнаружит это - чтобы заставить QTimer работать должным образом, пропустите скобки после render_image, иначе python выдает предупреждение. Должен прочитатьQTimer.singleShot(10, render_image)
EastWest
Ой, конечно. Исправлено в коде выше
Матиас Кун