QGISのプラグイン作成 Matplotlibを使ってグラフを作成するサンプル


はじめに

QGISプラグインでグラフを作成し、画像として保存します。
グラフにするデータとして、ここではDEMから取得した標高値を使います。


このプラグインの使い方

ここからダウンロードできるzipをインストールして、graph_sample.pyの以下の部分を適当に編集してください。

        self._llcrs = 4326
        self._xycrs = 2451 # 千葉県のDEMでテストしたので9系になってます。
        self._savedir = 'c:\\users\\〇〇\\desktop\\pic\\' # 横断図の保存先
        self._odanlinespan   = 100 # 横断線の間隔
        self._odanlinelength = 200 # 横断線の片側の長さ
        self._odanpointspan  = 10 # 横断線上のサンプリング間隔

f:id:Chiakikun:20201105234335p:plain

DEMのラスタをアクティブにした状態でプラグインを実行すると、10秒ほど経ってマウスカーソルが✚になり、ラバーバンドでラインが描画できるようになります。描画したら右クリックで確定します。

 

f:id:Chiakikun:20201105234357p:plain

しばらく(描画したラインの長さによっては大分)待たされますが、描画したラインを横断するように点が作成されます。各点にはDEMから取得した標高が入っています。

 

f:id:Chiakikun:20201105234441p:plain

各点の標高値を使って、グラフが作成されます。


f:id:Chiakikun:20210505105803p:plain

このような画像になります。


コード

~略~
import matplotlib.pyplot as plt
import numpy as np
from .rubberband import RubberBand
from .getrasterpixelvalue import GetRasterPixelValue
import datetime
import math

class GraphSample(QgsMapTool):

    # lineLLを垂直に横断する点列を作成する
    def addOdansen(self, lineLL, key):

~略~

        # startpnt、endpntが成す線分の、startpntからlength分進んだ位置を中心に、線分を横断する線を作成する。
        def odansen(startpnt, endpnt, length, cnt):

~略~

            for i in range(-self._odanlinelength, self._odanlinelength + self._odanpointspan, self._odanpointspan):
                xypnt = QgsGeometry().fromPointXY(destination(center, rad, i))
                llpnt = transformCRS(xypnt, self._xycrs, self._llcrs)

                elev = self.gr.getValueInterpolation(llpnt.asPoint())
                self.addFeature(self.hol, llpnt, [key, cnt, i, elev])

~略~


    def setFeature(self, geom):
        def getNumber(feature):
            return feature['pointcount']

        key = datetime.datetime.now().strftime('%Y%m%d%H%M%S') # 縦断線と横断線を紐付けるキー
        self.addFeature(self.ver, geom, [key]) # 縦断線レイヤに、描画したラインを追加
        self.addOdansen(geom, key)             # 描画したラインを横断する点列を横断戦レイヤに追加
        
        # グラフ作成
        features = list(self.hol.getFeatures(QgsFeatureRequest().setFilterExpression('"key"='+'\'' + key + '\'')))
        linenumbers = sorted(set([f['linecount'] for f in features]))
        for linenumber in linenumbers:
            linenode = sorted([f for f in features if f['linecount'] == linenumber], key=getNumber) # 'pointcount'の値順に並び変える

            fig, ax = plt.subplots()
            y = np.array([l['elev'] for l in linenode])
            x = np.array([l['pointcount'] for l in linenode])
            ax.set_title(str(linenode[0]['key']) + '_' + str(linenumber))
            ax.plot(x, y, '-')
            plt.savefig(self._savedir + str(linenode[0]['key']) + '_' + str(linenumber) + '.png')


    # 一時レイヤ作成
    def createTemporaryLayer(self, layername, type, fieldsstr):

~略~


    # レイヤにレコード追加
    def addFeature(self, layer, geometry, attrs):

~略~


    def start(self):
        # このプログラム使うときはこの辺を調整してください
        self._llcrs = 4326
        self._xycrs = 2451 # 千葉県のDEMでテストしたので9系になってます。
        self._savedir = 'c:\\users\\me\\desktop\\pic\\' # 横断図の保存先
        self._odanlinespan   = 100 # 横断線の間隔
        self._odanlinelength = 200 # 横断線の片側の長さ
        self._odanpointspan  = 10  # 横断線上のサンプリング間隔

        if self.iface.activeLayer() is not qgis.core.QgsRasterLayer:

~略~

        self.gr = GetRasterPixelValue(self.iface.activeLayer())

        maptool = RubberBand(self.iface, self.canvas, QgsWkbTypes.LineGeometry)
        maptool.getObject.connect(self.setFeature)

        self.ver = self.createTemporaryLayer('縦断線', 'LineString', '&field=key:string')
        self.hol = self.createTemporaryLayer('横断線', 'Point',      '&field=key:string&field=linecount:integer&field=pointcount:integer&field=elev:double')

        self.canvas.setMapTool(maptool)
        self.canvas.mapToolSet.connect(self.unsetTool) # このサンプル実行中に他のアイコンを押した場合

~略~

7行目 描画したラインを横断する点を、点が格納されているレイヤ(self.hol)から取得してきます。

8行目 横断線の本数を取得しています。

9行目 7行目で取得した点の中から、作成するグラフに使う点を取得しています。

 12~16行目 グラフを準備します。縦軸には標高、横軸には描画したラインからの距離を使います。

17行目 作成したグラフを保存しています。

ソース全文


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

QGISのプラグイン作成 マルチポリゴンの各パーツのバウンディングボックスを作成するサンプル

はじめに

QGISで地物のバウンディングボックスを取得するツール「バウンディングボックス」を使うと、下の画像の様になってしまいます。

f:id:Chiakikun:20210505154822p:plain

これは各レコードのマルチポリゴン全体を囲ってしまっている状態なので、各パーツ毎にボックスを作成するようなプラグインを作成してみました。


このサンプルの使い方

インストールzipはここからダウンロードできます。
インストールできたら、バウンディングボックスを作成したいレイヤをアクティブにした状態でプラグインを実行します。
しばらくすると一時レイヤにバウンディングボックスが作成されます。

f:id:Chiakikun:20210505171228p:plain


コード

~略~
    def start(self):
        # バウンディングボックス作成元
        srclayer = self.iface.activeLayer()
        if (srclayer == None) or (type(srclayer) is not QgsVectorLayer):
            QMessageBox.about(None, '警告', 'ベクタレイヤを選択してから実行してください')
            self.action.setChecked(False)
            return
        # バウンディングボックス投入先
        fieldstr = ''
        for field in srclayer.fields():
            fieldstr = fieldstr + '&field=' + str(field.name()) + ':' + str(field.typeName())
        crsstr = srclayer.sourceCrs().authid()
        dstlayer = QgsVectorLayer("Polygon?crs=" + crsstr + fieldstr, "サンプルレイヤ", "memory")

        for f in srclayer.getFeatures():

            # 属性
            qf = QgsFields()
            ## フィールド
            for field in srclayer.fields():
                qf.append(QgsField(str(field.name()), typeName=field.typeName()))
            record = QgsFeature(qf)
            ## 値投入
            for i in range(0, f.fields().count()):
                record[i] = f[i]
            # オブジェクト
            mpol = f.geometry().asMultiPolygon()
            bnds = []
            for i in range(0, len(mpol)):
                bnds.append(QgsGeometry.fromRect(QgsGeometry().fromPolygonXY(mpol[i]).boundingBox()).asPolygon())
            newobj = QgsGeometry.fromMultiPolygonXY(bnds)
            record.setGeometry(newobj)
            # レイヤに追加
            dstlayer.dataProvider().addFeatures([record])
            dstlayer.updateExtents()

        # キャンバスにオブジェクトを表示する      
        QgsProject.instance().addMapLayers([dstlayer])
        self.canvas.refreshAllLayers()
~略~

28行目 マルチオブジェクトをレコードから取り出して...
30~31行目 各バーツのバウンディングボックスを作成して...
32行目 作成したバウンディングボックスをまとめて一つのオブジェクトを作成しています。

コード全文はこちら

 


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

PostGISのレコードを誰かがQGISで編集すると、別の誰かのQGISではどうなるのか確認してみた


はじめに

いつもは一人でPostGISのデータをQGISで編集していましたが、2人で同じレイヤを編集した場合にどうなるのかが気になったので、今回実験してみました。


テスト環境

f:id:Chiakikun:20201107123910p:plain

実験環境は、サーバー1台、作業用PC2台です。

 

f:id:Chiakikun:20201107131727p:plain

Aさん接続直後

 

f:id:Chiakikun:20201107131821p:plain

Bさん接続直後

 


片方のPCでオブジェクトを削除してみる 

f:id:Chiakikun:20201107132833p:plain

AさんのPCで、左のオブジェクトを削除して保存しました。

f:id:Chiakikun:20201107133045p:plain

BさんのPCには一切触れていません。見た目は何も変わりませんでした。ここで、ちょっとマップを動かしてみると...

 

f:id:Chiakikun:20201107133350p:plain

Aさんが削除したオブジェクトが、BさんのPCでも消えました。


いつ削除されたことを知ったのか

f:id:Chiakikun:20201107140243p:plain

サーバーでtcpdumpを実行して、AさんのPCでオブジェクト削除から保存までのデータを見てみました。このログにはBさんのPCのIPアドレスは出てこなかったので、どうやらサーバーからはBさんにオブジェクト削除のことを教えていないようです。

今度はBさんPCのマップをずらしてみたり、テーブルを開いてみました。するとログにBさんPCのIPアドレスが出てきたので、Bさんが何かする度にサーバーに問い合わせているようです。

 


同じレコードを、片方は削除、片方は移動してみる

f:id:Chiakikun:20201107183535p:plain

AさんPCでは削除。

 

f:id:Chiakikun:20201107183735p:plain

BさんPCではオブジェクトを移動します。

 

f:id:Chiakikun:20201107185119p:plain

AさんPCで先に変更を保存し、次にBさんPCで変更を保存すると、BさんPCで移動したはずのオブジェクトが消えてしまいました。先に行ったAさんの変更が反映されてしまったようです。


課題

QGISからPostGISのテーブルをロックすることが可能かどうか調べ、複数のユーザーで編集が安全に行える方法が分かったらここに追記したいです。

 

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