画像を保存する前に、キャンバスのレンダリングが完了するのを待ちます


11

マップコンポーザーを使用して、複数のレイヤーのレンダリングを保存するスクリプトを作成しようとしています。私が直面している問題は、qgisがすべてのレイヤーのレンダリングを完了する前にスクリプトが保存されることです。

いくつかの他の回答(に基づいて123)、私が使用しようとしてきたiface.mapCanvas.mapCanvasRefreshed.connect()し、関数内で保存した画像を入れて、私はまだ同じ問題に直面しています-画像はすべてのレイヤーが含まれていません。

私が使用しているコードと、メインウィンドウとレンダリングがどのように見えるかの画像を以下に示します。

コンソールウィンドウを開いて3 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を使用しています


また、いくつかのWebページ12も参照しました。私はポストするこれらを追加しようとしましたが、私の担当者は十分に高くない
EastWest社

最後の2行目でmapCanv.mapCanvasRefreshed.connect(custFunc)mapCanv.renderComplete.connect(custFunc)?に置き換えてみてください。
ジョセフ

@Joseph残念ながら、違いは見られませんでした。私はまだ上記と同じ結果を得る
EastWest社

レイヤーに追加した機能をコミットしてみませんか?(つまりlayerP .commitChanges())。画像を保存するだけで、試してみる価値があるので、なぜそれが役立つはずなのかわかりませんが。そうでなければ、他の人がアドバイスできることを願っています:)
ジョセフ

@ジョセフ試しましたcommitChanges()が、運が悪いのは残念です。提案をありがとう。
イースト

回答:


5

ここに浮上するさまざまな問題があります

画面上でのレンダリングと画像へのレンダリング

mapCanvasRefreshedキャンバスが画面にレンダリングされている間、信号は繰り返し放出されます。オンスクリーンディスプレイの場合、これはより迅速なフィードバックを提供し、ユーザーが進行中の何かを確認したり、ナビゲーションを支援したりするのに役立ちます。

ファイルへの保存などのオフスクリーンレンダリングの場合、これは信頼できません(レンダリングが十分に高速だった場合にのみ完全なイメージが得られるため)。

できること:画像をレンダリングするのにマップキャンバスは必要ありません。QgsMapSettingsマップキャンバスからコピーするだけです。これらの設定は、レンダラーに送信されるパラメーターであり、すべてのデータプロバイダーからラスターイメージに変換する対象とその正確性を定義します。

レイヤーレジストリとマップキャンバス

レジストリに追加されたレイヤーは、すぐにキャンバスに表示されるのではなく、イベントループの次の実行時にのみ表示されます。したがって、次の2つのことのいずれかを実行する方が良いです。

  • タイマーで画像のレンダリングを開始します。 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信号が機能しないという考えはありますか?
ジョセフ

すべての情報をありがとう!残念ながら、提案されたコードをスクリプトに挿入して、マップコンポーザーセクションを完全に置き換えようとしましたが、まだ同じ問題が発生しています。保存された画像には、ラインまたはポイントレイヤーは含まれず、事前に読み込まれたラスターのみが含まれます。
イースト

ジョブが完了し、画像が画面に描画される間に信号が発せられると思います。painterそれと共に出力されるパラメーターがあり、最終的な画像に最終的に追加されるものを描画することができます(おそらく、このアプローチを機能させるために最終的な画像を取得することもできます)。
マティアスクーン

1
これは解決策のように見えます。ご助力いただきありがとうございます。他の誰かがこれを見つけた場合の簡単な注意-QTimerを適切に動作させるには、render_imageの後に括弧を省くか、Pythonが警告をスローします。読む必要がありますQTimer.singleShot(10, render_image)
イースト

おっと、もちろん。上記のコードで修正
マティアス・クーン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.