PyQGIS スクリーンショットを作成する

下のサイトを参考に(ほとんど全部コピペですが)アクティブレイヤのスクリーンショットを保存するプラグインをPlugin Builderを使って作成してみました。

gis.stackexchange.com


最小限のサンプル

f:id:Chiakikun:20191128011658p:plain

適当に作成したポリゴンのレイヤをアクティブ状態にしてプラグインを実行すると...(背景には国土地理院の航空写真を表示させています。)

 

f:id:Chiakikun:20191128011904p:plain

このような感じで、画像とワールドファイルが作成されます。

 

f:id:Chiakikun:20191128012124p:plain

ポリゴンを中心にスクリーンショットが作成されました。国土地理院の航空写真も拡大されています。

 

screenShot_sample_dialog.py

import os

from qgis.PyQt import uic
from qgis.PyQt import QtWidgets

import qgis.core

# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
FORM_CLASS, _ = uic.loadUiType(os.path.join(
    os.path.dirname(__file__), 'test_plugin_dialog_base.ui'))


class TestPluginDialog(QtWidgets.QDialog, FORM_CLASS):
    def __init__(self, parent=None):
        """Constructor."""
~中略~

    def exportMap(self):
        qgis.utils.iface.mapCanvas().saveAsImage( "d:/{}.png".format( self.ids.pop() ) )
        if self.ids:
            self.setNextFeatureExtent()
        else: # We're done
            qgis.utils.iface.mapCanvas().mapCanvasRefreshed.disconnect( self.exportMap )

    def setNextFeatureExtent(self):
        qgis.utils.iface.mapCanvas().zoomToFeatureIds( self.layer, [self.ids[-1]] )

    def clickExecButton(self):
        self.layer = qgis.utils.iface.activeLayer()
        self.ids = self.layer.allFeatureIds()

        qgis.utils.iface.mapCanvas().mapCanvasRefreshed.connect( self.exportMap )
        self.setNextFeatureExtent() # Let's start

    def clickCloseButton(self):
        self.close()

18行~23行目 画像を作成しています。次のフューチャーが存在するならsetNextFeatureExtentを呼び出し、存在しないなら、exportMapを登録から外します。

25行目 フューチャーにズームアップしています。インデックス-1しているのは、呼び出し元でポップしているからです。

32行目 マップがリフレッシュされた後に実行するメソッドexportMapを登録しています。


サンプル2

f:id:Chiakikun:20191203001817p:plain

少しインターフェースを変えて、画像の保存先と縮尺を設定できるようにしてみました。

 

f:id:Chiakikun:20191203001840p:plain

どのフューチャーを中心に撮っているかがわかるように、そのフューチャーを赤くしてみました。

screenShot_Sample2_dialog.py

import os

from qgis.PyQt import uic
from qgis.PyQt import QtWidgets
from qgis.PyQt.QtWidgets import QFileDialog, QMessageBox
from qgis.PyQt.QtGui import QColor
import qgis.core


# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
FORM_CLASS, _ = uic.loadUiType(os.path.join(
    os.path.dirname(__file__), 'screenShot_Sample2_dialog_base.ui'))


class ScreenShotSample2Dialog(QtWidgets.QDialog, FORM_CLASS):
    def __init__(self, parent=None):
        """Constructor."""
~中略~
        self.isrun = False

        # 縮尺リスト
        self.scaleList.addItem('2500')
        self.scaleList.addItem('10000')
        self.scaleList.addItem('50000')
        self.scaleList.addItem('100000')
        self.scaleList.addItem('250000')


    def closeEvent(self, e):
        if self.isrun:
            self.layer.setRenderer(self.prerenderer)
            self.layer.triggerRepaint()
        self.isrun = False


    def pushSelectDir(self):
        path = QFileDialog.getExistingDirectory(None, "", "")
        self.saveDirPath.setText(path)


    def pushClose(self):
        self.close()


    def exportMap(self):
        qgis.utils.iface.mapCanvas().saveAsImage(self.saveDirPath.text() + "\{}.png".format(self.ids.pop()) )

        if self.ids:
            self.setNextFeatureExtent()
        else: # We're done
            qgis.utils.iface.mapCanvas().mapCanvasRefreshed.disconnect( self.exportMap )
            self.close()

    def setNextFeatureExtent(self):
        rule = self.root_rule.children()[0].clone()
        rule.setFilterExpression('$id = ' + str(self.ids[-1]))
        self.root_rule.insertChild(1,rule)
        self.root_rule.removeChildAt(0)
        self.layer.setRenderer(self.renderer)
        self.layer.triggerRepaint()

        qgis.utils.iface.mapCanvas().zoomToFeatureIds( self.layer, [self.ids[-1]] )
        qgis.utils.iface.mapCanvas().zoomScale(int(self.scaleList.currentText()))


    def pushExec(self):
        if self.isrun:
            return

        if not (os.path.exists(self.saveDirPath.text()) and (os.path.isdir(self.saveDirPath.text()))):
            QtWidgets.QMessageBox.information(None, "エラー", "指定したフォルダが見つかりませんでした。;"+ self.saveDirPath.text() , QMessageBox.Ok)
            self.done(-1)
            return

        self.isrun = True

        self.layer = qgis.utils.iface.activeLayer()
        self.ids = self.layer.allFeatureIds()

        self.prerenderer = self.layer.renderer().clone() # 後で元に戻したいので現在のスタイル設定を退避しておく

        # 画像にするフューチャーを赤くしたいので... 「https://gis.stackovernet.com/ja/q/54438」を参考にしました
        symbol = qgis.core.QgsSymbol.defaultSymbol(self.layer.geometryType())
        self.renderer = qgis.core.QgsRuleBasedRenderer(symbol)
        style_rules = (
            ('Target', '$id = 0', '#ff0000'),
            ('Other', 'ELSE', self.prerenderer.symbol().color().name()),        
        )
        self.root_rule = self.renderer.rootRule()
        for label, expression, color_name in style_rules:
            rule = self.root_rule.children()[0].clone()
            rule.setLabel(label)
            rule.setFilterExpression(expression)
            rule.symbol().setColor(QColor(color_name))
            self.root_rule.appendChild(rule)
        self.root_rule.removeChildAt(0)
        self.layer.setRenderer(self.renderer)
        self.layer.triggerRepaint()

        # スクリーンショット開始
        qgis.utils.iface.mapCanvas().mapCanvasRefreshed.connect( self.exportMap )
        self.setNextFeatureExtent() # Let's start

55行目~58行目 シンボロジ『ルールに基づいた』設定の先頭ルールをコピーして、これから撮影しようとしているフューチャーは赤くなるように設定しています。コピー元は設定後に削除しています。

63行目 撮影するフューチャーを中心に、ダイアログで設定した縮尺にしています。

78行目 レイヤの全フューチャーのidを取得しています。

83行目~98行目 シンボロジ『ルールに基づいた』で、指定したidは赤く、それ以外はそのままの色になるように設定しています。



ご覧いただき、ありがとうございました。