QGIS自定义地图工具

版权声明:本文为博主原创文章,未经博主允许不得转载。 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算法是自己实现的,原理就是直角三角形直角边平方和等于斜边平方(a^{2}+b^{2}=c^{2}),自行理解,使用到了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]

截图

矩形

多边形

猜你喜欢

转载自blog.csdn.net/this_is_id/article/details/86524600