QGISでジオリファレンス


ジオリファレンス

 QGISでジオリファレンスをしてみたので、その手順を紹介します。マップのベクタデータは国土数値情報ダウンロードサービスから、画像は白地図専門店様で無料提供されている画像を使用しました。

f:id:Chiakikun:20191211001633p:plain

マップ

f:id:Chiakikun:20191211001653p:plain

画像

ジオリファレンサを起動する

f:id:Chiakikun:20191211002230p:plain

メニューの『ラスタ』→『ジオリファレンサ』を選択します。

画像を開く

f:id:Chiakikun:20191211002310p:plain

 ジオリファレンサウィンドウが表示されます。『ラスタを開く』アイコンを押下するとファイル選択ダイアログが表示されるので、画像ファイルを選択します。すると...

 

f:id:Chiakikun:20191211002350p:plain

ジオリファレンサウィンドウに選択した画像が表示されます。

 

ポイントを追加する

f:id:Chiakikun:20191211002520p:plain

位置合わせをするためにマップと画像にポイントを置いていきます。『ポイントの追加』アイコンを押下します。

画像に点を置く

f:id:Chiakikun:20191211002553p:plain

マップの適当な箇所をクリックすると...

地図座標の入力ダイアログ

f:id:Chiakikun:20191211002654p:plain

地図座標の入力ダイアログが表示されるので、『キャンバスから』ボタンを押下します。

マップに点を置く

f:id:Chiakikun:20191211003013p:plain

マップが前面に表示されるので、画像のクリックした箇所に対応するマップの位置をクリックします。すると...

 

座標が入力される

f:id:Chiakikun:20191211003056p:plain

地図座標の入力ダイアログに座標が入力された状態で前面に表示されます。これで良ければ『OK』ボタンを押下します。

ジオリファレンサウィンドウとマップにポイントが表示される

f:id:Chiakikun:20191211003144p:plain

マップと画像のクリックした場所にポイントが置かれます。この作業を他の箇所で何回か行います。

保存先、変換方法の設定

f:id:Chiakikun:20191211003236p:plain

まだ何も設定していない状態で『ジオリファレンスの開始』アイコンを押下すると、下のダイアログが表示されます。

f:id:Chiakikun:20191211003527p:plain

ここで位置合わせ後の画像の保存先や変換方法を設定します。

 

マップに画像が表示される

f:id:Chiakikun:20191211003851p:plain

もう一度『ジオリファレンスの開始』アイコンを押下すると、マップに画像が表示されます。画像レイヤを少し透明にすると、下のベクタが表示されるので、ずれが確認できると思います。

微調整したいとき

f:id:Chiakikun:20191211003932p:plain


まだ少しずれている箇所があるので...

f:id:Chiakikun:20191211004027p:plain

ポイントを追加した後、『ジオリファレンスの開始』アイコンを押下すると...

 

f:id:Chiakikun:20191211004217p:plain

少し位置が合いました



結果

f:id:Chiakikun:20191211004351p:plain

最終的に8個ほどポイントを置き、上記のような感じになりました。位置合わせした画像はTiffで保存されています。



お疲れさまでした。

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は赤く、それ以外はそのままの色になるように設定しています。



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

PyQGIS マウスイベント

QGISのマップ上でマウスのクリック、移動イベントを受け取るプラグインのサンプルを幾つかメモしておきます。尚、プログラムの大半はPlugin Builderを使って作成しました。


MapTool版

f:id:Chiakikun:20191125234501p:plain

プラグインのアイコンをクリックすると、マウスカーソルが十字になり、マップをクリックしたり移動した時に、その位置の座標をPythonコンソールに表示するサンプルです。

maptool_sample.py

from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction

# Initialize Qt resources from file resources.py
from .resources import *

import os.path

import qgis.core;

class MapToolSample:
    """QGIS Plugin Implementation."""
 ~中略~

    def initGui(self):
        icon = QIcon(':/plugins/maptool_sample/icon.png')
        self.action = QAction(icon, self.tr(u'MapTool版サンプル'), self.iface.mainWindow())
        self.action.triggered.connect(self.execSample)
        self.action.setEnabled(True)
        self.action.setCheckable(True)
        self.action.setEnabled(True)
        self.iface.addToolBarIcon(self.action)
        self.iface.addPluginToMenu(self.menu, self.action)
        
        # このサンプル実行中かな?
        self.isrun = False

        # 追加 キャンバス上のマウスイベント設定
        self.mouseEventSample = MouseEventSample(self.iface, self.iface.mapCanvas())
        self.iface.mapCanvas().mapToolSet.connect(self.unsetTool)


    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
~中略~

    def unsetTool(self, tool):
        try:
        # 実行中にこのサンプル以外のアイコンを押した場合
            if not isinstance(tool, MouseEventSample) and self.isrun:
                self.isrun = False
                self.action.setChecked(False)
        except Exception:
            pass


    def execSample(self):
        if self.isrun:
            self.iface.mapCanvas().unsetMapTool(self.mouseEventSample)
            self.iface.mapCanvas().setMapTool(self.previousMapTool)
            self.isrun = False
        else:
            self.previousMapTool = qgis.utils.iface.mapCanvas().mapTool()
            self.iface.mapCanvas().setMapTool(self.mouseEventSample)
            self.isrun = True

        
class MouseEventSample(qgis.gui.QgsMapTool):
    def __init__(self, iface, canvas):
        qgis.gui.QgsMapTool.__init__(self, canvas)

    def canvasMoveEvent(self, event):
        print('マウス移動:' + str(self.toMapCoordinates(event.pos())))

    def canvasPressEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton:
            print('左クリック!' + str(self.toMapCoordinates(event.pos())))

        if event.button() == QtCore.Qt.RightButton:
            print('右クリック!' + str(self.toMapCoordinates(event.pos())))

19行目 アイコンを押したしたときに呼ばれるメソッドを指定します。

21行目 Trueを指定すると、アイコンを押したしたとき、押して凹んでいる状態になります。

30行目 QgsMapToolを継承したクラスのインスタンスを生成しています。

31行目 他のアイコンが押されたときに呼び出すメソッドを指定します。

38行目 他のアイコンが押されたとき、押して凹んでいる状態のアイコンを元に戻します。

49行~52行目 このプラグイン実行前のMapToolに処理を戻します。

53行~56行目 現在実行中のMapToolを退避して、このプラグインをMapToolに登録します。

59行目~ マウス移動、ボタンクリックをしたときにPythonコンソールに座標を表示するクラスを定義しています。

こちらにソースを置いておきました。


MapToolEmitPoint版

f:id:Chiakikun:20191126002209p:plain

 

f:id:Chiakikun:20191126002638p:plain

アイコンをクリックすると、ダイアログが表示されます。ダイアログの『実行』ボタンを押下すると、マウスカーソルが十字になり、マップをクリックしたり移動した時に、マップ上の座標をPythonコンソールに表示するサンプルです。

maptoolemitpoint_sample_dialog.py

import os

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

import qgis.core
from qgis.PyQt import QtCore

# 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__), 'maptoolemitpoint_sample_dialog_base.ui'))


class MapToolEmitPointSampleDialog(QtWidgets.QDialog, FORM_CLASS):
    def __init__(self, parent=None):
        """Constructor."""
~中略~
self.mouseEventSample = qgis.gui.QgsMapToolEmitPoint(qgis.utils.iface.mapCanvas()) def showEvent(self, e): self.isrun = False def pushExec(self): self.mouseEventSample.canvasClicked.connect(self.mouseClick) self.mouseEventSample.canvasMoveEvent = self.canvasMoveEvent qgis.utils.iface.mapCanvas().setMapTool(self.mouseEventSample) self.isrun = True def pushClose(self): if self.isrun: self.mouseEventSample.canvasClicked.disconnect(self.mouseClick) qgis.utils.iface.mapCanvas().unsetMapTool(self.mouseEventSample) self.close() def canvasMoveEvent(self, event): print('マウス移動:' + str(qgis.utils.iface.mapCanvas().getCoordinateTransform().toMapCoordinates(event.pos()))) def mouseClick(self, currentPos, clickedButton ): if clickedButton == QtCore.Qt.LeftButton: print('左クリック!' + str(qgis.core.QgsPointXY(currentPos))) if clickedButton == QtCore.Qt.RightButton: print('右クリック!' + str(qgis.core.QgsPointXY(currentPos)))

18行目 QgsMapToolEmitPointのインスタンスを生成しています。

21行目 ダイアログが表示されたときに実行されるメソッドです。

23行~27行目 ダイアログの『実行』ボタンが押下されたとき、マップをクリックしたときに実行されるメソッドと、マウスを移動させたときに実行されるメソッドをMapToolに登録します。

29行~34行目 ダイアログの『閉じる』ボタンが押下されたとき、(MapToolが実行中なら)MapToolの登録を解除してダイアログを閉じます。

36行目~ マウス移動、ボタンクリックをしたときにPythonコンソールに座標を表示するメソッドを定義しています。

こちらにソースを置いておきました。


ラバーバンドサンプル

最後に、MapToolEmitPointとQgsRubberBandを使って、マップに図面を描画するサンプルを作成してみました。このプラグインは下のサイトを参考にしました。

gis.stackexchange.com

rubberbandSample_dialog.py

MapToolEmitPointのサンプルのようにダイアログが表示されるので、『実行』ボタンを押下するとマウスカーソルが十字になり、ラバーバンドでポリゴン描画状態になります。

右クリックすると、描画したポリゴンが一時レイヤに登録されます。

import qgis.core
from qgis.PyQt.QtGui import QColor
from qgis.PyQt import QtCore, QtGui

import os

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

from datetime import datetime

# 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__), 'maptoolemitpoint_sample_dialog_base.ui'))


class RubberBandSampleDialog(QtWidgets.QDialog, FORM_CLASS):
    def __init__(self, parent=None):
        """Constructor."""
~中略~
        # Create new virtual layer 
        self.vlyr = qgis.core.QgsVectorLayer("Polygon?&crs=epsg:4326&field=name:string&field=size:double", "サンプルレイヤ", "memory")

        self.mouseEventSample = qgis.gui.QgsMapToolEmitPoint( qgis.utils.iface.mapCanvas() )


    def showEvent(self, e):
        self.isrun = False
        self.myRubberBand = None

    def closeEvent(self, e):
        if self.isrun:
            self.isrun = False
            self.mouseEventSample.canvasClicked.disconnect(self.mouseClick)

        qgis.utils.iface.mapCanvas().unsetMapTool(self.mouseEventSample)

        if not self.myRubberBand == None:
            qgis.utils.iface.mapCanvas().scene().removeItem(self.myRubberBand)
            self.myRubberBand = None

    def pushExec(self):
        self.mouseEventSample.canvasClicked.connect(self.mouseClick)
        self.mouseEventSample.canvasMoveEvent = self.canvasMoveEvent        
        qgis.utils.iface.mapCanvas().setMapTool(self.mouseEventSample)
        self.isrun = True

    def pushClose(self):
        self.close()

    def canvasMoveEvent(self, event):
        # 一度も左ボタンをクリックしてない
        if self.myRubberBand == None:
            return

        pos = event.pos()
        point = qgis.utils.iface.mapCanvas().getCoordinateTransform().toMapCoordinates(pos)
        point_count = self.myRubberBand.numberOfVertices()
        if point_count == 0:
            return
        self.myRubberBand.movePoint(point)


    def mouseClick(self, currentPos, clickedButton ):
        if clickedButton == QtCore.Qt.LeftButton and self.myRubberBand == None: #len(self.coordinates) == 0: 
            # create the polygon rubber band associated to the current canvas             
            self.myRubberBand = qgis.gui.QgsRubberBand( qgis.utils.iface.mapCanvas(), qgis.core.QgsWkbTypes.PolygonGeometry )
            # set rubber band style
            color = QColor(78, 97, 114)
            color.setAlpha(190)
            self.myRubberBand.setColor(color)
            #Draw rubberband
            self.myRubberBand.addPoint( qgis.core.QgsPointXY(currentPos) )

        if clickedButton == QtCore.Qt.LeftButton and self.myRubberBand.numberOfVertices() > 0:
            self.myRubberBand.addPoint( qgis.core.QgsPointXY(currentPos) )

        if clickedButton == QtCore.Qt.RightButton:
            # フューチャー作成
            qf = qgis.core.QgsFields()
            for field in self.vlyr.fields():
                qf.append(qgis.core.QgsField(str(field.name()), typeName=field.typeName()))
            poly = qgis.core.QgsFeature(qf) 

            # easier solution using simply the geometry of self.myRubberBand
            geomP = self.myRubberBand.asGeometry() 
            poly.setGeometry(geomP) 

            # add feature
            # set attributes
            poly.setAttributes([str(datetime.now()), geomP.area()])
            self.vlyr.dataProvider().addFeatures([poly])
            self.vlyr.updateExtents() # これが無いと『レイヤの領域にズーム』した時に、レイヤの最初のオブジェクト部分しかズームされない

            # キャンバスにオブジェクトを追加して、パンする。setExtentが無いと、画面に表示されない(少しでも画面動かせば表示されるので)      
            qgis.core.QgsProject.instance().addMapLayers([self.vlyr])
            qgis.utils.iface.mapCanvas().setExtent(geomP.boundingBox())
            qgis.utils.iface.mapCanvas().refresh()

            self.close()

こちらにソースを置いておきました。