版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/this_is_id/article/details/86524600
官方示例
首先看一下官方文档中的矩形工具源码:
class RectangleMapTool(QgsMapToolEmitPoint):
def __init__(self, canvas):
self.canvas = canvas
QgsMapToolEmitPoint.__init__(self, self.canvas)
self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry)
self.rubberBand.setColor(QColor(255, 0, 0, 100))
self.rubberBand.setWidth(1)
self.reset()
def reset(self):
self.startPoint = self.endPoint = None
self.isEmittingPoint = False
self.rubberBand.reset(True)
def canvasPressEvent(self, e):
self.startPoint = self.toMapCoordinates(e.pos())
self.endPoint = self.startPoint
self.isEmittingPoint = True
self.showRect(self.startPoint, self.endPoint)
def canvasReleaseEvent(self, e):
self.isEmittingPoint = False
r = self.rectangle()
if r is not None:
print("Rectangle:", r.xMinimum(), r.yMinimum(), r.xMaximum(), r.yMaximum())
def canvasMoveEvent(self, e):
if not self.isEmittingPoint:
return
self.endPoint = self.toMapCoordinates(e.pos())
self.showRect(self.startPoint, self.endPoint)
def showRect(self, startPoint, endPoint):
self.rubberBand.reset(QgsWkbTypes.PolygonGeometry)
if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y():
return
point1 = QgsPointXY(startPoint.x(), startPoint.y())
point2 = QgsPointXY(startPoint.x(), endPoint.y())
point3 = QgsPointXY(endPoint.x(), endPoint.y())
point4 = QgsPointXY(endPoint.x(), startPoint.y())
self.rubberBand.addPoint(point1, False)
self.rubberBand.addPoint(point2, False)
self.rubberBand.addPoint(point3, False)
self.rubberBand.addPoint(point4, True) # true to update canvas
self.rubberBand.show()
def rectangle(self):
if self.startPoint is None or self.endPoint is None:
return None
elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y() == self.endPoint.y():
return None
return QgsRectangle(self.startPoint, self.endPoint)
def deactivate(self):
super(RectangleMapTool, self).deactivate()
self.deactivated.emit()
self.reset()
文档中的有些语法还是QGIS2的写法,比如QGis.Polygon在QGIS3中应该用QgsWkbTypes.PolygonGeometry、QgsPoint在QGIS3中对应QgsPointXY,这里我已经做了更改。
源码解析
- 首先继承QgsMapToolEmitPoint类
- 重写canvasPressEvent、canvasReleaseEvent、canvasMoveEvent
- 使用QgsRubberBand渲染绘制的图形(支持PolygonGeometry、PointGeometry、LineGeometry)
具体思路是当鼠标按压画布时,触发canvasPressEvent,得到第一个点self.startPoint,鼠标拖动后触发canvasMoveEvent,得到第二个点,使用这两个点绘制一个矩形,鼠标释放时触发canvasReleaseEvent,结束绘制。
注:每次绘制前都要清除之前的图形,否则会出现拖影,使用rubberBand的reset方法:
self.rubberBand.reset(QgsWkbTypes.PolygonGeometry)
实现多边形绘制
QGIS自带的矢量编辑工具中已经有多边形绘制工具,如下:
接下来我们添加一个自动垂直绘制的功能,源码如下:
class PolygonMapTool(QgsMapToolEmitPoint):
def __init__(self, canvas):
self.canvas = canvas
QgsMapToolEmitPoint.__init__(self, self.canvas)
self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry)
self.rubberBand.setColor(QColor(255, 0, 0, 100))
self.rubberBand.setWidth(1)
self.reset()
def reset(self):
self.is_start = False # 开始绘图
self.is_vertical = False # 垂直画线
self.cursor_point = None
self.points = []
self.rubberBand.reset(True)
def canvasPressEvent(self, event):
if event.button() == Qt.LeftButton:
self.points.append(self.cursor_point)
self.is_start = True
elif event.button() == Qt.RightButton:
# 右键结束绘制
if self.is_start:
self.is_start = False
self.cursor_point = None
self.show_polygon()
self.points = []
else:
pass
else:
pass
def canvasMoveEvent(self, event):
self.cursor_point = event.mapPoint()
if not self.is_start:
return
self.show_polygon()
def show_polygon(self):
self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) # 防止拖影
first_point = self.points[0]
last_point = self.points[-1]
self.rubberBand.addPoint(first_point, False)
for point in self.points[1:-1]:
self.rubberBand.addPoint(point, False)
if self.cursor_point:
self.rubberBand.addPoint(QgsPointXY(last_point.x(), last_point.y()), False)
else:
self.rubberBand.addPoint(QgsPointXY(last_point.x(), last_point.y()), True)
self.rubberBand.show()
return
# 光标所在点
if self.is_vertical and len(self.points) >= 2:
countdown_second_point = self.points[-2]
cursor_point_x = self.cursor_point.x()
cursor_point_y = self.cursor_point.y()
diff_x = int(math.fabs(last_point.x() - countdown_second_point.x()))
diff_y = int(math.fabs(last_point.y() - countdown_second_point.y()))
if diff_x > diff_y:
# 最后一条线的x,y差值比较
cursor_point_x = equation(countdown_second_point.x(), countdown_second_point.y(), last_point.x(),
last_point.y(), self.cursor_point.y(), solve_type='x')
else:
cursor_point_y = equation(countdown_second_point.x(), countdown_second_point.y(), last_point.x(),
last_point.y(), self.cursor_point.x(), solve_type='y')
_cursor_point = QgsPointXY(cursor_point_x, cursor_point_y)
self.cursor_point = _cursor_point
self.rubberBand.addPoint(self.cursor_point, True)
self.rubberBand.show()
def deactivate(self):
super(PolygonMapTool, self).deactivate()
self.deactivated.emit()
self.reset()
其中equation算法是自己实现的,原理就是直角三角形直角边平方和等于斜边平方(),自行理解,使用到了sympy库(第一次使用,好强大。。。),源码如下:
def equation(x1, y1, x2, y2, f, solve_type='x'):
'''
已知两点坐标和第三个点x或y,求对应的y或x
:param x1:第一个点x坐标
:param y1:第一个点y坐标
:param x2:第二个点x坐标
:param y2:第二个点y坐标
:param f:x或y的坐标值
:param solve_type:'x'或'y',默认'x'
:return:
'''
if solve_type == 'x':
x = symbols('x')
y = f
s = solve((x - x1) ** 2 + (y - y1) ** 2 - (x2 - x1) ** 2 - (y2 - y1) ** 2 - (x - x2) ** 2 - (y - y2) ** 2, x)
return s[0]
else:
y = symbols('y')
x = f
s = solve((x - x1) ** 2 + (y - y1) ** 2 - (x2 - x1) ** 2 - (y2 - y1) ** 2 - (x - x2) ** 2 - (y - y2) ** 2, y)
return s[0]