PyQGIS オブジェクトを移動、拡大縮小、回転させるサンプル
ここで紹介しているサンプルはオブジェクトの形状を変更するものなので、実行する際はバックアップを必ず取ってください。
どんな動きをするの?
レイヤをアクティブにして、プラグインを実行します。オブジェクトをクリックすると...
オブジェクトが編集状態になり、移動、拡大縮小、回転ができるようになります。上の画像では、マルチポリゴンの一部を回転させています。
回転できました。
ライン(シングルオブジェクト)を拡大しています。
マルチポリゴンの一部を移動させています。
注意 このサンプルではドーナツのポリゴンには対応していません。
コード
import qgis from qgis.core import * from qgis.gui import * from PyQt5.Qt import pyqtSignal from qgis.PyQt.QtGui import QColor, QTransform import math class EditGeometrySample(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()
ソースはこちら
使い方
例として、ここからダウンロードできるサンプルプラグインに追加します。
nodialog_skelton.pyと同じフォルダにソースを置いて、インポート部分に次のコードを追記します。
from .editgeometrysample import EditGeometrySample
nodialog_skelton.pyのstartを次のコードに書き換えます。
def start(self): self.maptool = EditGeometrySample(self.iface, self.canvas, 'move') # 移動の場合 self.maptool.setLayer(self.iface.activeLayer()) self.maptool.featureIdentified.connect(self.selectedFeature) self.canvas.setMapTool(self.maptool) self.canvas.mapToolSet.connect(self.unsetTool) # このサンプル実行中に他のアイコンを押した場合
次のメソッドを追加してください。
def selectedFeature(self, feature): self.maptool.setFeature(feature)
このようになればOKです。
~略~ from .editgeometrysample import EditGeometrySample class NodialogSkelton(qgis.gui.QgsMapTool): def selectedFeature(self, feature): self.maptool.setFeature(feature) def start(self): self.maptool = EditGeometrySample(self.iface, self.canvas, 'move') self.maptool.setLayer(self.iface.activeLayer()) self.maptool.featureIdentified.connect(self.selectedFeature) self.canvas.setMapTool(self.maptool) self.canvas.mapToolSet.connect(self.unsetTool) # このサンプル実行中に他のアイコンを押した場合 ~略~
最後までご覧頂き、ありがとうございました
PyQGIS ラスタから値を取得するサンプル
どんな動きをするの?
ラスタレイヤをアクティブにした状態でプラグインを実行すると、マウスカーソル位置の標高をPythonコンソールに表示します。
コード
import qgis from qgis.core import * from qgis.gui import * import pyproj class GetRasterPixelValue: def __init__(self, layer: QgsRasterLayer): self.__grs80 = pyproj.Geod(ellps='GRS80') # ピクセル1つのサイズ self.__unitX = layer.rasterUnitsPerPixelX() self.__unitY = layer.rasterUnitsPerPixelY() # ラスタのサイズ self.__height = layer.height() self.__width = layer.width() # ラスタの原点 self.__provider = layer.dataProvider() self.__extent = self.__provider.extent() self.__orgX = self.__extent.xMinimum() self.__orgY = self.__extent.yMaximum() # ピクセル self.block = self.__provider.block(1, self.__extent, self.__width, self.__height) def getIndex(self, pos: QgsPointXY): pixelIdxX = (pos.x() - self.__orgX) / self.__unitX pixelIdxY = (self.__orgY - pos.y()) / self.__unitY if (pixelIdxX < 0) or (pixelIdxY < 0): return None pixelIdxX = int(pixelIdxX) pixelIdxY = int(pixelIdxY) if (pixelIdxX > self.__width - 1) or (pixelIdxY > self.__height - 1): return None return [pixelIdxX, pixelIdxY] def getGeometry(self, idxX: int, idxY: int): if (idxX < 0) or (idxY < 0): return None if (idxX > self.__width - 1) or (idxY > self.__height - 1): return None pixelPosX = self.__unitX * idxX + self.__orgX + self.__unitX / 2 pixelPosY = self.__orgY - self.__unitY * idxY - self.__unitY / 2 return [pixelPosX, pixelPosY] def getValueFromPos(self, pos: QgsPointXY): ident = self.__provider.identify(pos, QgsRaster.IdentifyFormatValue ) if ident.isValid(): return list(ident.results().values())[0] else: return None def getValueFromIdx(self, idxX: int, idxY: int): if (idxX < 0) or (idxY < 0): return None if (idxX > self.__width - 1) or (idxY > self.__height - 1): return None return self.block.value(idxY, idxX) def getValueInterpolation(self, pos: QgsPointXY): pixidx = self.getIndex(pos) if pixidx == None: return None pixelIdxX = pixidx[0] pixelIdxY = pixidx[1] pixpos = self.getGeometry(pixelIdxX, pixelIdxY) pixelPosX = pixpos[0] pixelPosY = pixpos[1] # 右上 if (pixelPosX < pos.x()) and (pixelPosY < pos.y()): if (pixelIdxX > self.__width - 2) or (pixelIdxY - 1 < 0): return None a = self.__getXYZ__(pixelIdxX+1, pixelIdxY-1) b = self.__getXYZ__(pixelIdxX, pixelIdxY-1) c = self.__getXYZ__(pixelIdxX, pixelIdxY) d = self.__getXYZ__(pixelIdxX+1, pixelIdxY) # 左上 elif (pixelPosX >= pos.x()) and (pixelPosY < pos.y()): if (pixelIdxX - 1 < 0) or (pixelIdxY - 1 < 0): return None a = self.__getXYZ__(pixelIdxX, pixelIdxY-1) b = self.__getXYZ__(pixelIdxX-1, pixelIdxY-1) c = self.__getXYZ__(pixelIdxX-1, pixelIdxY) d = self.__getXYZ__(pixelIdxX, pixelIdxY) # 左下 elif (pixelPosX >= pos.x()) and (pixelPosY >= pos.y()): if (pixelIdxX - 1 < 0) or (pixelIdxY > self.__height - 2): return None a = self.__getXYZ__(pixelIdxX, pixelIdxY) b = self.__getXYZ__(pixelIdxX-1, pixelIdxY) c = self.__getXYZ__(pixelIdxX-1, pixelIdxY+1) d = self.__getXYZ__(pixelIdxX, pixelIdxY+1) # 右下 elif (pixelPosX < pos.x()) and (pixelPosY >= pos.y()): if (pixelIdxX > self.__width - 2) or (pixelIdxY > self.__height - 2): return None a = self.__getXYZ__(pixelIdxX+1, pixelIdxY) b = self.__getXYZ__(pixelIdxX, pixelIdxY) c = self.__getXYZ__(pixelIdxX, pixelIdxY+1) d = self.__getXYZ__(pixelIdxX+1, pixelIdxY+1) dist1 = self.__grs80.inv(b.x(), b.y(), a.x(), a.y())[2] dist2 = self.__grs80.inv(b.x(), b.y(), pos.x(), b.y())[2] delta = (a.z() - b.z()) / dist1 tmp1 = delta * dist2 + b.z() dist1 = self.__grs80.inv(c.x(), c.y(), d.x(), d.y())[2] dist2 = self.__grs80.inv(c.x(), c.y(), pos.x(), c.y())[2] delta = (d.z() - c.z()) / dist1 tmp2 = delta * dist2 + c.z() dist1 = self.__grs80.inv(d.x(), d.y(), a.x(), a.y())[2] dist2 = self.__grs80.inv(d.x(), d.y(), d.x(), pos.y())[2] delta = (tmp1 - tmp2) / dist1 inp = delta * dist2 + tmp2 return inp def __getXYZ__(self, idxX: int, idxY: int): x = self.__unitX * idxX + self.__orgX + self.__unitX / 2 y = self.__orgY - self.__unitY * idxY - self.__unitY / 2 z = self.block.value(idxY, idxX) return QgsPoint(x, y, z)
ソースはこちら
使い方
例として、マウスイベントサンプルに追加します。このmouseeventsample.pyの使い方はこちらを参照してください。
mouseeventsample.pyとgetrasterpixelvalue.pyを同じフォルダに置いて、mouseeventsample.pyのインポート部分に次のコードを追記します。
from .getrasterpixelvalue import GetRasterPixelValue
mouseeventsample.pyの__init__の引数は次のように変更してください。
__init__(self, iface, canvas)
mouseeventsample.pyの__init__に次のコードを加えます。
self.gr = GetRasterPixelValue(iface.activeLayer())
mouseeventsample.pyのcanvasMoveEventを次のコードに変えます。
def canvasMoveEvent(self, event): print(str(self.gr.getValueInterpolation(self.toMapCoordinates(event.pos()))))
nodialog_skelton.pyのstartを次のコードに書き換えます。
def start(self): maptool = MouseEventSample(self.iface, self.canvas)
nodialog_skelton.pyはこのようになればOKです。
~略~ from .mouseeventsample import MouseEventSample class NodialogSkelton(qgis.gui.QgsMapTool): def start(self): maptool = MouseEventSample(self.iface, self.canvas) self.canvas.setMapTool(maptool) self.canvas.mapToolSet.connect(self.unsetTool) # このサンプル実行中に他のアイコンを押した場合 ~略~
mouseeventsample.pyはこのようになればOKです。
~略~ from .getrasterpixelvalue import GetRasterPixelValue class MouseEventSample(QgsMapTool): def __init__(self, iface, canvas): QgsMapTool.__init__(self, canvas) self.gr = GetRasterPixelValue(iface.activeLayer()) def canvasMoveEvent(self, event): print(str(self.gr.getValueInterpolation(self.toMapCoordinates(event.pos())))) ~略~
最後までご覧頂き、ありがとうございました
PyQGIS リレーションのサンプル
どんな動きをするの?
このサンプルでは、国土数値情報ダウンロードサービスからダウンロードできる行政区域と避難所を使います。
プラグインを実行すると、上のようにリレーションが作られます。
プラグインを実行したままで、行政区域の地物を選択すると...
選択した地物と関係ある避難所の地物が選択状態になり、属性テーブルが表示されます。
コード
from qgis.PyQt import QtWidgets from qgis.PyQt.Qt import QSettings import qgis from qgis.core import * from qgis.gui import * class RelationSample: def __init__(self, iface, parentLayer, parentField, childLayer, childField): self.iface = iface self.rel = QgsRelation() self.rel.setReferencingLayer(childLayer.id()) self.rel.setReferencedLayer(parentLayer.id()) self.rel.addFieldPair(childField, parentField) self.rel.setId('適当なID') self.rel.setName('適当な名前') QgsProject.instance().relationManager().addRelation(self.rel) self.parent = parentLayer self.parent.selectionChanged.connect(self.showChildren) def showChildren(self): parent = self.rel.referencedLayer() child = self.rel.referencingLayer() features = parent.selectedFeatures() if len(features) == 0: return child.removeSelection() # クリアしないと、属性テーブルに余計に表示されるから for c in self.rel.getRelatedFeatures(features[0]): child.select(c.id()) selectedlayer = self.iface.activeLayer() # 退避 try: # このプログラム実行中は属性テーブルは選択中のフューチャーしか表示しない self.oldsetting = QSettings().value('/Qgis/attributeTableBehaviour') QSettings().setValue('/Qgis/attributeTableBehavior', 'ShowSelected') self.iface.setActiveLayer(child) self.iface.mainWindow().findChild(QtWidgets.QAction, 'mActionOpenTable' ).trigger() finally: self.iface.setActiveLayer(selectedlayer) # 戻す QSettings().setValue('/Qgis/attributeTableBehavior', self.oldsetting) def __del__(self): QgsProject.instance().relationManager().removeRelation(self.rel) self.parent.selectionChanged.disconnect(self.showChildren)
ソースはこちら
使い方
例として、ここからダウンロードできるサンプルプラグインに追加します。
nodialog_skelton.pyと同じフォルダにソースを置いて、インポート部分に次のコードを追記します。
from .relationsample import RelationSample
nodialog_skelton.pyのstartを次のコードに書き換えます。
def start(self): # 国土数値情報ダウンロードサービスからダウンロードできる行政区域と避難所を使う場合 parent = QgsProject.instance().mapLayersByName('行政区域')[0] child =QgsProject.instance().mapLayersByName('避難所')[0] self.rel = RelationSample(self.iface, parent, 'N03_007', child, 'p20_001')
nodialog_skelton.pyのfinishを次のコードに書き換えます。
def finish(self): self.rel = None
このようになればOKです。
~略~ from .relationsample import RelationSample class NodialogSkelton(qgis.gui.QgsMapTool): def start(self): parent = QgsProject.instance().mapLayersByName('行政区域')[0] child =QgsProject.instance().mapLayersByName('避難所')[0] self.rel = RelationSample(self.iface, parent, 'N03_007', child, 'p20_001') def finish(self): self.rel = None ~略~
最後までご覧頂き、ありがとうございました