等待画布完成渲染,然后再保存图像


11

我正在尝试编写一个脚本,该脚本将使用地图编辑器保存多个图层的渲染。我遇到的问题是脚本在qgis完成渲染所有图层之前保存。

根据其他几个答案(123),我试图使用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主窗口中的外观(正在显示随机栅格地图): 在此处输入图片说明

保存了什么: 在此处输入图片说明

作为进一步的信息,我在Windows 7上使用QGIS 2.18.7


我还引用了几个网页12。我尝试将其添加为帖子,但我的代表不够高
EastWest

在倒数第二行中,尝试将其替换mapCanv.mapCanvasRefreshed.connect(custFunc)mapCanv.renderComplete.connect(custFunc)
约瑟夫

@Joseph不幸的是,这似乎没有什么不同。我仍然得到与上述相同的结果
EastWest

也许尝试提交已添加到图层的功能?(即layerP .commitChanges())。尽管我不明白为什么它应该有所帮助,因为您只是保存图像,但是我想值得一试。否则希望其他人可以建议:)
约瑟夫

@Joseph我尝试过commitChanges(),但不幸的是没有运气。谢谢你的建议。
EastWest

Answers:


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信号不起作用的任何想法吗?
约瑟夫

感谢您所提供的所有信息!不幸的是,我尝试将建议的代码插入脚本中,完全替换了map composer部分,但仍然遇到相同的问题。保存的图像不包含线或点图层,仅包含预加载的栅格。
EastWest

我认为在作业完成和图像绘制到屏幕之间发出信号。painter随它发出一个参数,您仍然可以在该参数上绘制其他内容,这些内容最终会出现在最终图像上(并且您可能还可以从中获取最终图像以使此方法起作用)。
马提亚斯·库恩

1
这看起来像解决方案。感谢你的帮助。一个简短的注释,如果有人最终找到了它-为了使QTimer正常工作,请在render_image之后省略括号,否则python会发出警告。应该阅读QTimer.singleShot(10, render_image)
EastWest

糟糕,当然。固定在上面的代码中
的Matthias库恩
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.