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

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