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

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

 

f:id:Chiakikun:20191128012124p:plain

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

インストール用のzipファイルはこちらに置いておきました。

コード

screenShot_sample_dialog.py

class ScreenShotSampleDialog(QtWidgets.QDialog, FORM_CLASS):

    def __init__(self, iface, parent=None):

~中略~

    def exportMap(self):
        self.canvas.saveAsImage( "d:/{}.png".format( self.ids.pop() ) )

        if self.ids:
            self.setNextFeatureExtent()
        else:
            self.canvas.mapCanvasRefreshed.disconnect( self.exportMap )


    def setNextFeatureExtent(self): # この関数が終わるとself.exportMapが呼ばれます(リフレッシュが終わるので)
        self.canvas.zoomToFeatureIds( self.layer, [self.ids[-1]] ) # -1 しているのは、exportMapでpopしているから
        self.canvas.zoomScale(250000) # 縮尺を1:250000に設定します 


    def pushExec(self):
        self.layer = self.iface.activeLayer()
        if type(self.layer) is not qgis.core.QgsVectorLayer:
            return
        self.ids = self.layer.allFeatureIds()

        self.canvas.mapCanvasRefreshed.connect( self.exportMap )

        self.setNextFeatureExtent() # setNextFeatureExtent -> exportMap -> ...のループに突入

        self.close()

7行~18行目 setNextFeatureExtentでズーム→リフレッシュイベントが発生→exportMap→setNextFeatureExtent...のループでフューチャーのスクリーンショットを撮っています。普通のforループですと、ズームしたあとに地物の再描画が終わる前にスクリーンショットが撮られてしまうことがあります。

 


フューチャー毎にスクリーンショット

使い方

f:id:Chiakikun:20191203001817p:plain

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

撮影するベクタレイヤをアクティブにしてアイコンを押下すると上のようなインターフェースが表示されるので、画像保存先のテキストボックス右側にあるボタンを押下して、保存先フォルダを選択します。

縮尺を設定したら『実行』ボタンを押下します。

 

f:id:Chiakikun:20191203001840p:plain

どのフューチャーを中心に撮っているかがわかるように、そのフューチャーが赤くなっているスクリーンショットが保存先に出力されます。

インストール用のzipファイルはこちらに置いておきました。

コード

screenShot_Sample2_dialog.py

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

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


    def exportMap(self):
    
    ~中略~

    def setNextFeatureExtent(self):
        # スクリーンショット撮るフューチャー
        rule = self.root_rule.children()[0].clone()
        rule.setFilterExpression('$id = ' + str(self.ids[-1]))
        self.root_rule.insertChild(1,rule) # この時点でルールは「赤(対象idは前のid)、赤(対象idはids[-1])、そのまま」
        self.root_rule.removeChildAt(0)    # この時点でルールは「赤(対象idはids[-1])、そのまま」
        self.layer.setRenderer(self.renderer)
        self.layer.triggerRepaint()

        self.canvas.zoomToFeatureIds( self.layer, [self.ids[-1]] )
        self.canvas.zoomScale(int(self.scaleList.currentText()))


    def pushExec(self):
    
    ~中略~

        # スタイル設定 画像にするフューチャーを赤くしたいので... 「https://gis.stackovernet.com/ja/q/54438」を参考にしました
        self.prerenderer = self.layer.renderer().clone()
        symbol = qgis.core.QgsSymbol.defaultSymbol(self.layer.geometryType())
        self.renderer = qgis.core.QgsRuleBasedRenderer(symbol)
        style_rules = (
            ('Target', '$id = 0', '#ff0000'), # $idが0なら赤
            ('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() # ラベル、フィルタ、色以外はデフォルトを使いたいので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()

        # スクリーンショット開始
        self.canvas.mapCanvasRefreshed.connect( self.exportMap )
        self.setNextFeatureExtent() # setNextFeatureExtent -> exportMap -> ... のループ

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

 

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


フューチャー毎にスクリーンショット

使い方

f:id:Chiakikun:20200229153014p:plain

①ラスタレイヤをいくつか開いた状態でプラグインを実行します。

 

f:id:Chiakikun:20200229153035p:plain

②ダイアログが表示されるので赤丸部分のボタンを押下します。

 

f:id:Chiakikun:20200229153051p:plain

③上のようなダイアログが表示されるので、スクリーンショットを撮りたいレイヤを選択します。OpenStreatMapもラスタではありますが、サンプルプラグインでは表示されないようにしています。選択したら『OK』ボタンを押下します。

 

f:id:Chiakikun:20200229153111p:plain

④ラスタレイヤの選択ダイアログで選択し終えたら、『実行』ボタンを押下します。

プラグインではDドライブ直下に画像が保存されるようになっています。

 

インストール用のzipファイルはこちらに置いておきました。

コード

screenshot_sample3_dialog.py

class ScreenShotSample3Dialog(QtWidgets.QDialog, FORM_CLASS):

~中略~

    def exportMap(self):
        self.iface.setActiveLayer(self.layer)
        self.canvas.saveAsImage('d:/' + self.iface.activeLayer().name() + '.png')

        if len(self.selectedLayers) > self.count:
            self.setNextFeatureExtent()
        else:
            self.canvas.mapCanvasRefreshed.disconnect( self.exportMap )
            self.close()


    def setNextFeatureExtent(self):
        self.layer = qgis.core.QgsProject.instance().mapLayersByName(self.selectedLayers[self.count])[0]
        self.iface.setActiveLayer(self.layer)
        self.iface.zoomToActiveLayer() 	

        self.count += 1

~中略~
  

ほとんど最小限のサンプルと変わらないですね。フューチャー毎の部分がレイヤ毎になっただけです。



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

PyQGIS マウスイベントサンプル

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


MapTool版

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

使い方

f:id:Chiakikun:20200222105739p:plain

ZIPファイルからインストールすると、メニューにプラグインが追加されるので、実行します。実行するとマウスカーソルがマップウィンドウ上で「+」に変わります。

 

f:id:Chiakikun:20200222111544p:plain

②「Pythonコンソール」を表示した状態でマウスをマップウィンドウ上で動かしたりマウスボタンをクリックすると、その位置が表示されます。

コード

maptool_sample.py

class MapToolSample:

    def __init__(self, iface):

~中略~

        # キャンバスウィンドウ上でのマウスイベントの設定
        self.mouseEventSample = MouseEventSample(self.iface, self.canvas)

~中略~

    def execSample(self):
        if self.action.isChecked():
            self.previousMapTool = self.canvas.mapTool()
            self.canvas.setMapTool(self.mouseEventSample)
        else:
            self.canvas.unsetMapTool(self.mouseEventSample)
            self.canvas.setMapTool(self.previousMapTool)
            self.action.setChecked(False)

        
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())))

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

12行~19行目 このプラグインのアイコンが押されたときに呼ばれます。アイコンがONになった時、現在有効なマップツールを退避して、MouseEventSampleをマップツールに登録しています。アイコンがOFFになった時、MouseEventSampleをマップツールから削除して、退避しておいたマップツールを登録しています。

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

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


MapToolEmitPoint版

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

使い方 

f:id:Chiakikun:20200223112027p:plain

zipファイルをインストールするとメニューにプラグインが追加されるので実行します。するとダイアログが表示されます。

『実行』ボタンを押下すると、マップウィンドウ上でマウスカーソルが「+」になります。

 

f:id:Chiakikun:20200223112517p:plain

②ボタンが「実行中」の間は、マウスを動かしたりボタンをクリックすると、その場所の座標がPythonコンソールに表示されます。

コード

maptoolemitpoint_sample_dialog.py

class MapToolEmitPointSampleDialog(QtWidgets.QDialog, FORM_CLASS):

    def __init__(self, iface, parent=None):

~中略~

        # キャンバスウィンドウ上でのマウスイベントの設定
        self.mouseEventSample = qgis.gui.QgsMapToolEmitPoint(self.canvas)

~中略~

    def pushExec(self, checked):
        if checked == True:
            self.mouseEventSample.canvasClicked.connect(self.mouseClick)
            self.mouseEventSample.canvasMoveEvent = self.canvasMoveEvent        
            self.canvas.setMapTool(self.mouseEventSample)

~中略~

    def canvasMoveEvent(self, event):
        print('マウス移動:' + str(self.canvas.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)))

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

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

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

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


Rubber Band サンプル

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

gis.stackexchange.com

使い方 

f:id:Chiakikun:20200223155848p:plain

①使い方はMaptoolEmitPoint版のサンプルとほとんど変わりません。zipファイルをインストールするとメニューにプラグインが追加され、プラグインを実行するとダイアログが表示されます。

 マウスの左クリックで描画開始、描画中ならノード追加をします。マウスの右クリックで描画終了で仮想レイヤにレコードが追加されます。

コード

rubberbandSample_dialog.py

class RubberBandSampleDialog(QtWidgets.QDialog, FORM_CLASS):
    def __init__(self, iface, parent=None):

~中略~

        # 仮想レイヤ作成
        self.vlyr = qgis.core.QgsVectorLayer("Polygon?&crs=epsg:4326&field=name:string&field=size:double", "サンプルレイヤ", "memory")
        # キャンバスウィンドウ上でのマウスイベントの設定
        self.mouseEventSample = qgis.gui.QgsMapToolEmitPoint(self.canvas)

~中略~

    def pushExec(self, checked):
        if checked == True:
            self.mouseEventSample.canvasClicked.connect(self.mouseClick)
            self.mouseEventSample.canvasMoveEvent = self.canvasMoveEvent        
            self.canvas.setMapTool(self.mouseEventSample)

            # このサンプル以外のアイコンを押した場合に、凹んでいる実行ボタンを元に戻すため
            self.canvas.mapToolSet.connect(self.unsetTool)
            self.pushButton_Exec.setText('実行中')

            self.myRubberBand = None
        else:
            self.unsetTool()


    def canvasMoveEvent(self, event):
        if self.myRubberBand == None: # 一度も左ボタンをクリックしてない時にこのメソッドが呼ばれた場合
            return

        # 地物の描画中に、マウスカーソルを追って形状が変わるように
        point = self.canvas.getCoordinateTransform().toMapCoordinates(event.pos())
        self.myRubberBand.movePoint(point)


    def mouseClick(self, currentPos, clickedButton ):
        # 地物の最初の一点目
        if clickedButton == QtCore.Qt.LeftButton and self.myRubberBand == None:
            self.myRubberBand = qgis.gui.QgsRubberBand( self.canvas, qgis.core.QgsWkbTypes.PolygonGeometry )
            self.myRubberBand.setColor( QColor(78, 97, 114, 190) )
            self.myRubberBand.addPoint( qgis.core.QgsPointXY(currentPos) )

        # 地物の二点目以降
        if clickedButton == QtCore.Qt.LeftButton and self.myRubberBand.numberOfVertices() > 0:
            self.myRubberBand.addPoint( qgis.core.QgsPointXY(currentPos) )

        # __init__で作成した仮想レイヤのレコード作成
        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()))
            record = qgis.core.QgsFeature(qf) 

            # ラバーバンドで作成した地物をセットする
            geomP = self.myRubberBand.asGeometry() 
            record.setGeometry(geomP) 

            # 属性をセットする
            record.setAttributes([str(datetime.now()), geomP.area()])

            # 作成したレコードをレイヤに追加
            self.vlyr.dataProvider().addFeatures([record])
            self.vlyr.updateExtents() # これが無いと『レイヤの領域にズーム』した時に、レイヤの最初のオブジェクト部分しかズームされない

            # キャンバスにオブジェクトを表示する      
            qgis.core.QgsProject.instance().addMapLayers([self.vlyr])
            #self.canvas.setExtent(geomP.boundingBox()) #パンしたい場合は#外して
            self.canvas.refreshAllLayers()

            self.canvas.scene().removeItem(self.myRubberBand) # ラバーバンドで描いた図形はもう必要ないので消す
            self.myRubberBand = None

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


ラスタの値取得

MapTool版を少しいじって、マウスカーソル下のラスタのピクセル値を表示するサンプルを作成しました。

このプラグインは下のサイトを参考にしました。

gis.stackexchange.com

使い方

f:id:Chiakikun:20200223185047p:plain

①使い方はMapTool版のサンプルとほとんど変わりません。zipファイルをインストールすると、プラグインがメニューに追加されます。

プラグインを実行するとマウスカーソルが「+」に変わるので、アクティブにしたラスタレイヤの上にマウスカーソルを移動させると、カーソル下のピクセル値を表示します。

コード

getrastervalue_sample.py

    def __init__(self, iface):

~中略~

        # キャンバスウィンドウ上でのマウスイベントの設定
        self.mouseEventSample = MouseEventSample(self.iface, self.canvas)

~中略~

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

        self.canvas = canvas
        self.iface = iface

        # canvasMoveEventで設定した秒数(このプログラムでは700ms)経過したら呼ばれるメソッドを設定
        self.timerMapTips = QTimer( canvas )
        self.timerMapTips.timeout.connect( self.showMapTip )


    def canvasMoveEvent(self, event):
        QToolTip.hideText()
        self.timerMapTips.start( 700 )


    def showMapTip( self ):
        self.timerMapTips.stop()

        rLayer = self.iface.activeLayer()
        if type(rLayer) is qgis.core.QgsRasterLayer:
            # マウスカーソル下のピクセル値を取得する
            mappos = self.toMapCoordinates(self.canvas.mouseLastXY())
            ident = rLayer.dataProvider().identify( mappos, qgis.core.QgsRaster.IdentifyFormatValue ) # 参考: https://qgis.org/api/qgsraster_8h_source.html#l00057

            # ツールチップに表示する文字列を設定する
            if ident.isValid():
                values = ident.results().values()
                text = ", ".join(['{0:g}'.format(r) for r in values if r is not None] ) # 複数取得することは無いだろうけど一応
            else:
                text = "Non valid value"

            QToolTip.showText( self.canvas.mapToGlobal( self.canvas.mouseLastXY() ), text, self.canvas )

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



最後までご覧いただきありがとうございます。