รอให้ Canvas วาดเสร็จก่อนบันทึกภาพ


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)?
โจเซฟ

@Joseph น่าเสียดายที่มันไม่ได้สร้างความแตกต่าง ฉันยังคงได้รับผลลัพธ์เช่นเดียวกับข้างต้น
EastWest

อาจลองใช้คุณสมบัติที่คุณเพิ่มในเลเยอร์ (เช่นlayerP .commitChanges()) แม้ว่าฉันจะไม่เห็นว่าทำไมมันควรช่วยเพราะคุณเพียงบันทึกภาพ แต่ควรลองเดาดู มิฉะนั้นหวังว่าผู้อื่นจะสามารถให้คำแนะนำได้ :)
โจเซฟ

@ โจcommitChanges()เซฟฉันพยายามแต่โชคไม่ดีโชคไม่ดี ขอบคุณสำหรับคำแนะนำ
EastWest

คำตอบ:


5

มีปัญหาที่แตกต่างกันที่นี่

แสดงผลบนหน้าจอเทียบกับการแสดงผลภาพ

สัญญาณmapCanvasRefreshedจะถูกปล่อยออกมาซ้ำ ๆ ในขณะที่ผ้าใบกำลังถูกเรนเดอร์ไปที่หน้าจอ สำหรับการแสดงผลบนหน้าจอนี้จะให้ข้อเสนอแนะที่รวดเร็วซึ่งอาจเป็นเรื่องดีสำหรับผู้ใช้ที่จะเห็นสิ่งที่เกิดขึ้นหรือช่วยในการนำทาง

สำหรับการเรนเดอร์หน้าจอเช่นบันทึกไปยังไฟล์สิ่งนี้ไม่น่าเชื่อถือ (เนื่องจากคุณจะมีภาพที่สมบูรณ์หากการเรนเดอร์เร็วพอ)

สิ่งที่สามารถทำได้: เราไม่ต้องการผืนผ้าใบแผนที่เพื่อแสดงภาพของคุณ เราสามารถคัดลอกQgsMapSettingsจากผืนผ้าใบแผนที่ การตั้งค่าเหล่านี้เป็นพารามิเตอร์ที่ส่งไปยังโหมดแสดงภาพและกำหนดสิ่งที่แน่นอนและสิ่งที่ควรแปลงจากผู้ให้บริการข้อมูลทั้งหมดไปเป็นภาพแรสเตอร์

Layer Registry เทียบกับผืนผ้าใบแผนที่

เลเยอร์ที่เพิ่มลงในรีจิสตรีจะไม่จบลงบนผืนผ้าใบในทันที แต่เฉพาะในการเรียกใช้ลูปเหตุการณ์ถัดไป ดังนั้นคุณควรทำสิ่งใดสิ่งหนึ่งต่อไปนี้

  • เริ่มการแสดงภาพในเวลา 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ปล่อยออกมาซึ่งคุณยังสามารถวาดสิ่งเพิ่มเติมที่จะสิ้นสุดในภาพสุดท้าย (และคุณอาจใช้ภาพสุดท้ายเพื่อให้วิธีการนี้ทำงานได้)
Matthias Kuhn

1
ดูเหมือนว่าวิธีการแก้ปัญหา ขอบคุณสำหรับความช่วยเหลือของคุณ บันทึกย่อฉบับย่อหนึ่งรายการหากใครก็ตามพบว่าสิ่งนี้จบลง - เพื่อให้ QTimer ทำงานได้อย่างถูกต้องออกจากวงเล็บหลังจาก render_image มิฉะนั้น Python จะส่งสัญญาณเตือน ควรอ่านQTimer.singleShot(10, render_image)
EastWest

อ๊ะแน่นอน แก้ไขในโค้ดด้านบน
Matthias Kuhn
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.