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

 

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

PostGIS ST_Intersectsで地物を抽出する

はじめに

PostGISを使って、北海道に重なる基準地域メッシュ(第3次地域区画)のオブジェクト抽出を行ったので、その時の作業メモです。

f:id:Chiakikun:20210506122146p:plain

メッシュ(PostGISに「jpmesh_3」で登録、フィールド「code」にはメッシュ番号が入っています)

f:id:Chiakikun:20210518231252p:plain

拡大したところ。

f:id:Chiakikun:20210506122455p:plain

北海道(PostGISに「北海道」で登録)


コード

INSERT INTO
    temp1(geom, メッシュコード)
SELECT
    jpmesh_3.geom,
    jpmesh_3.code
FROM
    jpmesh_3, 北海道
WHERE
    ST_Intersects(
        jpmesh_3.geom,北海道.geom
    );

上記のコードで、北海道に重なるメッシュのオブジェクトを抽出して、予め作成しておいたtemp1というテーブルに投入しています。

 


実行速度を計測

ネタのかさ増しのため、INDEX作成前と後で実行速度がどのくらい変わるのか計測してみました。

INDEX作成前 Time: 6547.685 ms (00:06.548)

INDEX作成後 Time: 4210.643 ms (00:04.211)

ついでに北海道のオブジェクトのノードを半分程間引いて計測してみました。

間引いた後の北海道はこんな感じです

f:id:Chiakikun:20210506124536p:plain

f:id:Chiakikun:20210506124651p:plain

f:id:Chiakikun:20210506124811p:plain

赤く見えているところが、間引き前の北海道です。

INDEX作成前 Time: 4933.373 ms (00:04.933)

INDEX作成後 Time: 3123.348 ms (00:03.123)


QGISで作業する場合

f:id:Chiakikun:20210506125051p:plain

QGISで同じことをする場合は「場所による選択」を実行します。

 

f:id:Chiakikun:20210506125116p:plain

実行時間は121.43秒でした。PostGISにテーブルを登録する手間を考えると、QGISでの作業は実行時間こそかかりましたが、簡単操作でできるので、少ないレコード数のレイヤを抽出するならQGISで...ということになってしまいました。


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

PyQGIS オブジェクトを移動、拡大縮小、回転させるサンプル

ここで紹介しているサンプルはオブジェクトの形状を変更するものなので、実行する際はバックアップを必ず取ってください。


はじめに

通常、マルチオブジェクトを移動したり回転させようとするとパーツ全体が影響をうけますが、このサンプルでは1つのパーツだけを移動、拡大縮小、回転させることができます。


使い方

ここからzipファイルをダウンロードしてインストールします。インストールできたら、処理内容に合わせてmultiobjectedit_sample.pyの以下の部分を編集してください。

        self.maptool = MultiObjectEditClass(self.iface, self.canvas, 'move') # 移動の場合

3番目の引数が、移動させたいなら'move'、拡大縮小させたいなら'scale'、回転させたいなら'rotate'に設定してください。

            # self.layer.startEditing() # バックアップは取りましたか?バックアップできたなら、#を外してください
            self.changeGeometry(self.layer.selectedFeatures()[0])
            # self.layer.commitChanges() # ここも

レイヤをバックアップしたら、#を外してください。このままだと編集内容が反映されません。

 

f:id:Chiakikun:20200621144611p:plain

レイヤをアクティブにして、プラグインを実行します。オブジェクトをクリックすると...

 

f:id:Chiakikun:20200621144802p:plain

オブジェクトが編集状態になり、移動、拡大縮小、回転ができるようになります。上の画像では、マルチポリゴンの一部を回転させています。

 

f:id:Chiakikun:20200621144820p:plain

回転できました。オブジェクトから少し離れたところで右クリックすると、編集状態を解除できます。

 

f:id:Chiakikun:20200621144836p:plain

ライン(シングルオブジェクト)を拡大しています。

 

f:id:Chiakikun:20200621144852p:plain

マルチポリゴンの一部を移動させています。

注意 このサンプルではドーナツのポリゴンには対応していません。


コード

class MultiObjectEditSample(QgsMapTool):

    def selectedFeature(self, feature):        
        self.maptool.setFeature(feature)


    def start(self):
        self.maptool = MultiObjectEditClass(self.iface, self.canvas, 'move') # 移動の場合
        self.maptool.setLayer(self.iface.activeLayer())
        self.maptool.featureIdentified.connect(self.selectedFeature)

~略~

class MultiObjectEditClass(QgsMapToolIdentifyFeature):
    setFeature = pyqtSignal(QgsGeometry)

    def __init__(self, iface, canvas, edittype): # edittype = 'scale', 'move', 'rotate'
        self.iface = iface
        self.canvas = canvas
        self.layer = self.iface.activeLayer()
        self.objType = self.layer.geometryType()
        self.edittype = edittype

        QgsMapToolIdentifyFeature.__init__(self, self.canvas)
        self.myRubberBand = None


    def canvasPressEvent(self, event): # featureIdentifiedより先に呼ばれる
        self.srcpos = event.pos()

        if self.myRubberBand == None: return

        if event.button() == QtCore.Qt.LeftButton:
            # self.layer.startEditing() # バックアップは取りましたか?バックアップできたなら、#を外してください
            self.changeGeometry(self.layer.selectedFeatures()[0])
            # self.layer.commitChanges() # ここも

        self.myRubberBand.reset()
        self.myRubberBand = None
        self.layer.removeSelection() 


    def setFeature(self, feat):
        if self.myRubberBand != None: return

        self.layer.select(feat.id())

        curpos = QgsGeometry().fromPointXY(self.toMapCoordinates(self.srcpos))

        mpol = self.tolist(feat.geometry())

        # 選択したマルチオブジェクトで、選択位置に一番近いオブジェクト取得
        dist = [pol.distance(curpos) for pol in mpol]
        self.nearidx = dist.index(min(dist))
        self.nearobj = mpol[self.nearidx]

        # ラバーバンドにnearobjを追加する
        self.myRubberBand = QgsRubberBand( self.canvas, self.objType )
        self.myRubberBand.setColor(QColor(255, 0, 0, 255))
        self.myRubberBand.addGeometry(self.nearobj, self.layer)


    def canvasMoveEvent(self, e):
        if self.myRubberBand == None: return

        self.myRubberBand.reset(self.objType)

        dstpos = e.pos()

        if self.edittype == 'move':
            self.moveObject(self.srcpos, dstpos)
        elif self.edittype == 'scale':
            self.scaleObject(self.srcpos, dstpos)
        else:
            self.rotateObject(self.srcpos, dstpos)


    def changeGeometry(self, feat):

        if self.objType == QgsWkbTypes.GeometryType.PolygonGeometry:
            if feat.geometry().isMultipart():
                mpol = feat.geometry().asMultiPolygon()
                mpol[self.nearidx] = self.myRubberBand.asGeometry().asPolygon()
                self.layer.changeGeometry (feat.id(), QgsGeometry().fromMultiPolygonXY(mpol))
            else:
                pol = self.myRubberBand.asGeometry().asPolygon()
                self.layer.changeGeometry (feat.id(), QgsGeometry.fromPolygonXY(pol))

        elif self.objType == QgsWkbTypes.GeometryType.LineGeometry:
            if feat.geometry().isMultipart():
                mline = feat.geometry().asMultiPolyline()
                mline[self.nearidx] = self.myRubberBand.asGeometry().asPolyline()
                self.layer.changeGeometry (feat.id(), QgsGeometry().fromMultiPolylineXY(mline))
            else:
                line = self.myRubberBand.asGeometry().asPolyline()
                self.layer.changeGeometry (feat.id(), QgsGeometry.fromPolylineXY(line))

        elif self.objType == QgsWkbTypes.GeometryType.PointGeometry:
            if feat.geometry().isMultipart():
                mpnt = feat.geometry().asMultiPoint()
                mpnt[self.nearidx] = self.myRubberBand.asGeometry().asMultiPoint()[0]
                self.layer.changeGeometry (feat.id(), QgsGeometry().fromMultiPointXY(mpnt))
            else:
                pnt = self.myRubberBand.asGeometry().asMultiPoint()[0]
                self.layer.changeGeometry (feat.id(), QgsGeometry.fromPointXY(pnt))


    def tolist(self, obj):

        if self.objType == QgsWkbTypes.GeometryType.PolygonGeometry:
            if obj.isMultipart():
                return [QgsGeometry().fromPolygonXY(pol) for pol in obj.asMultiPolygon()]
            else:
                return [QgsGeometry().fromPolygonXY(obj.asPolygon())]

        elif self.objType == QgsWkbTypes.GeometryType.LineGeometry:
            if obj.isMultipart():
                return [QgsGeometry().fromPolylineXY(pline) for pline in obj.asMultiPolyline()]
            else:
                return [QgsGeometry().fromPolylineXY(obj.asPolyline())]

        elif self.objType == QgsWkbTypes.GeometryType.PointGeometry:
            if obj.isMultipart():
                return [QgsGeometry().fromPointXY(pnt) for pnt in obj.asMultiPoint()]
            else:
                return [QgsGeometry().fromPointXY(obj.asPoint())]


    def moveObject(self, srcpos, dstpos):
        temp = QgsGeometry(self.nearobj)

        s = self.toMapCoordinates(srcpos)
        d = self.toMapCoordinates(dstpos)

        self.myRubberBand.addGeometry(self.trans(temp, s, d), self.layer)


    def scaleObject(self, srcpos, dstpos):
        original = QgsPointXY(0, 0)
        centroid = self.nearobj.centroid().asPoint()
        temp = QgsGeometry(self.nearobj)

        s = QgsGeometry().fromPointXY(QgsPointXY(srcpos))
        d = QgsGeometry().fromPointXY(QgsPointXY(dstpos))

        # 一度中心に持ってこないとオブジェクトが変な方向に行ってしまうので
        temp = self.trans(temp, centroid, original)
        temp = self.scale(temp, s, d)
        temp = self.trans(temp, original, centroid)

        self.myRubberBand.addGeometry(temp, self.layer)


    def rotateObject(self, srcpos, dstpos):
        original = QgsPointXY(0, 0)
        centroid = self.nearobj.centroid().asPoint()
        temp = QgsGeometry(self.nearobj)

        s = QgsPointXY(self.toMapCoordinates(srcpos))
        d = QgsPointXY(self.toMapCoordinates(dstpos))

        # 一度中心に持ってこないとオブジェクトが変な方向に行ってしまうので
        temp = self.trans(temp, centroid, original)
        temp = self.rotate(temp, s, d)
        temp = self.trans(temp, original, centroid)

        self.myRubberBand.addGeometry(temp, self.layer)


    def trans(self, obj, srcpos, dstpos):
        x = dstpos.x() - srcpos.x()
        y = dstpos.y() - srcpos.y()

        obj.transform(QTransform(1, 0, 0, 1, x, y))
        return obj


    def scale(self, obj, srcpos, dstpos):
        cent = QgsGeometry().fromPointXY(QgsPointXY(0, 0))
        srclen = cent.distance(srcpos)
        dstlen = cent.distance(dstpos)
        ratio = (dstlen / srclen) * 1.5 # 「1.5」は適当

        obj.transform(QTransform(ratio, 0, 0, ratio, 0, 0))
        return obj


    def rotate(self, obj, srcpos, dstpos):
        centroid = self.nearobj.centroid().asPoint()
        theta = self.radian(centroid, dstpos)
        sin = math.sin(theta)
        cos = math.cos(theta)

        obj.transform(QTransform(cos, sin, -sin, cos, 0, 0))
        return obj


    def radian(self, a, b):
        return math.atan2(b.y() - a.y(), b.x() - a.x())


    def deactivate(self):
        try: # 左クリックで既にNoneしていた場合エラーになるので
            self.myRubberBand.reset()
        except:
            pass
        self.myRubberBand = None
        self.layer.removeSelection() 

8行目 編集処理内容を設定します。
9行目 編集するレイヤを設定します。
4行目 地物を選択した時に、編集を開始します。
50~55行目 選択したマルチオブジェクトをバラして、マウスカーソルに一番近いパーツを編集対象とします。
70~75行目 マウスカーソルを動かして、移動、拡大縮小、回転をさせています。
33行目 左ボタンで編集内容を確定します。
コード全文


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