QGIS3.28的二次开发九:添加矢量要素

对矢量要素的编辑是 GIS 软件很重要的功能点之一,也是最难实现的功能点之一。编辑矢量要素涉及到很多方面的考虑,包括且不限于矢量要素的几何类型,拓扑关系,构成要素的节点的增删改,编辑会话 (session) 的启动、回溯和提交,要素属性的增删改等。本文不会也不可能涉及到属性编辑的方方面面,仅仅实现了一个添加面要素的地图工具,作抛砖引玉的作用。

我们预计实现如下需求:

  • 参照 QGIS 和 ArcGIS,用一个按钮控制编辑会话的开始和结束,即控制图层处于编辑状态与否。按下表示处于编辑状态,弹起处于非编辑状态;
  • 编辑状态下,激活“绘制多边形”按钮,点击激活添加多边形地图工具,弹起取消激活;
  • 添加多边形地图工具激活时,用户可以在画布上点击绘制多边形:左键添加节点,右键结束当前多边形绘制。

运行效果

程序刚运行起来的效果如下,此时的绘制多边形按钮是点不了的,只有点击开始编辑之后,绘制多边形按钮才可见,再点击绘制多边形按钮,即可开始绘制。
初始界面
绘制效果如下
绘制多边形

代码解释

其实QGIS 提供了一个 QgsMapToolCapture类,可以实现上述上面绘制多边形的功能(这是 QGIS 软件自己所使用的地图工具),但不幸的是,这个工具的实例化需要引入 QgsAdvancedDigitizingDockWidget 类。

QgsMapToolCapture::QgsMapToolCapture(QgsMapCanvas* canvas,QgsAdvancedDigitizingDockWidget* cadDockWidget,CaptureMode mode 
)	

从 QGIS 开发文档进入 qgsadvanceddigitizingdockwidget.h 源代码,可以发现一行 include

#include "ui_qgsadvanceddigitizingdockwidgetbase.h"

这个类是一个停靠窗口 QDockWidget,在编辑要素的过程中,会弹出这个界面显示一些信息。因此如果我们强行 include 这个类,编译器会提示找不到 ui_qgsadvanceddigitizingdockwidgetbase.h 导致编译错误。原因是,这个类是一个组件,它是自带 UI 的,QGIS 的源代码中提供了这个组件的 .ui 文件(如同我们自己用 Qt Designer 创建的 .ui 文件一样)。所以,要使用QgsMapToolCapture,我们必须从 GitHub 上下载 QgsAdvancedDigitizingDockWidget 的 .ui 文件 qgsadvanceddigitizingdockwidget.ui,用 uic 编译成 ui_qgsadvanceddigitizingdockwidgetbase.h,放到我们的源代码中,才可以通过编译。

然而事情并没有那么简单。如果用 Qt Designer 打开下载回来的 qgsadvanceddigitizingdockwidget.ui,会发现缺失一大堆资源文件,你用 uic 编译也会报同样的错(虽然也可以编译)。因为 QGIS 做的 UI 有不少共享的资源文件,如图标等,都存在 Qt Designer 的资源描述文件(.qrc)文件中。如果要完整编译你还得去把 QGIS 所有的资源文件下载回来。做这么多麻烦事的目的仅仅是让 QgsMapToolCapture 通过编译,有一点本末倒置的感觉。因此,作者决定放弃使用 QgsMapToolCapture 转而手动实现我们所需要的地图工具。

QgsMapToolCapture 的继承链为
QgsMapToolCapture 的继承链
逐级往上翻看源代码,发现 QgsAdvancedDigitizingDockWidget 是在 QgsMapToolAdvancedDigitizing 这一级引入的。因此我们可以直接继承 QgsMapToolEdit,QgsMapToolEdit又继承自QgsMapTool
QgsMapToolEdit继承关系
QgsMapToolEdit 相对于基本的 QgsMapTool,额外实现了如下重要功能:

  • currentVectorLayer(): 获取当前正在编辑的图层(即工具所属 QMapCanvas 的当前激活图层
  • createRubberBand(): 可以直接从工具创建 QgsRubberBand,创建后自动附着于工具所属的 QMapCanvas上

这让我们可以比较方便的操作工具所属的图层和画布。我们创建一个 QgsMapToolEdit 的派生类AddPolygonTool,作为我们绘制多边形的地图工具。

AddPolygonTool.h

#pragma once
#include <qgsmaptooledit.h>	// 用于编辑矢量几何图形的地图工具的基类
#include <qgsmapcanvas.h>	// 画布
#include <qgsrubberband.h>	// 用于在绘制折线或多边形时跟踪鼠标,记录绘制图形过程中的临时要素
#include <qgsmapmouseevent.h>	// QGIS中的鼠标事件class AddPolygonTool :public QgsMapToolEdit
{
public:AddPolygonTool(QgsMapCanvas* pMapCanvas);// 清除当前的 RubberBandvoid clearRubberBand();protected:// 重写 QgsMapTool 的鼠标移动事件virtual void canvasMoveEvent(QgsMapMouseEvent *e);// 重写 QgsMapTool 的鼠标点击事件virtual void canvasPressEvent(QgsMapMouseEvent *e);private:// 当前正在工作的 RubberBandQgsRubberBand* mpRubberBand = nullptr;// 记录是否正在绘制中,构造函数中初始化为 falsebool mIsDrawing;
};

接下来重写鼠标点击事件,大体思路是:如当前无工作中的 RubberBand,则创建并存入 mpRubberBand 并点下第一个点。之后用户连续点击鼠标左键往 mpRubberBand 加入点,直到点击鼠标右键。点击鼠标右键表示停止绘制,如此时有效点数小于 3,不足以构成多边形,则丢弃,否则将 mpRubberBand 输出为新的 QgsFeature,加入受编辑的 QgsVectorLayer 之中。

// 重写QgsMapTool的鼠标点击事件
void AddPolygonTool::canvasPressEvent(QgsMapMouseEvent * e)
{// 如果当前“橡皮筋”没有被创建if (!mpRubberBand){// 使用QGIS设置中的颜色/线宽创建一个“橡皮筋”,方法来自QgsMapToolEditmpRubberBand = createRubberBand(QgsWkbTypes::GeometryType::PolygonGeometry);}// 左键按下if (e->button() == Qt::MouseButton::LeftButton){mIsDrawing = true;	// 开始绘制mpRubberBand->addPoint(e->mapPoint());	// 向“橡皮筋”和更新画布添加一个顶点}// 右键按下else if (e->button() == Qt::MouseButton::RightButton){// 停止绘制mIsDrawing = false;// 如果“橡皮筋”中的顶点数大于3if (mpRubberBand->numberOfVertices() >= 3){// 创建一个QgsFeature来给当前矢量图层添加特征QgsFeature f;// QgsGeometry QgsRubberBand::asGeometry() const 返回“橡皮筋”当前对应的几何对象f.setGeometry(mpRubberBand->asGeometry());// QgsMapToolEdit的currentVectorLayer()返回值类型为QgsVectorLayer // DefMainWindow.cpp中必须设置了当前图层才能用这个方法currentVectorLayer()->addFeature(f);// QgsMapCanvas * QgsMapTool::canvas() const,返回一个指向画布的指针// void QgsMapCanvas::refresh() 重新绘制画布地图	canvas()->refresh();}// 绘制完毕清楚“橡皮筋”clearRubberBand();}
}

上述代码中,如果当前 RubberBand 内顶点数不小于 3,则符合多边形生成的条件。此时创建一个新的要素 (QgsFeature),将当前 RubberBand 绘制好的几何图形 (QgsGeometry 类型,通过 asGeometry() 方法获取) 赋予新建立的要素,并将此要素通过调用 QgsVectorLayer 的 addFeature() 方法,加入到图层之中。当前图层通过QgsMapToolEdit 的 currentVectorLayer() 获取。最后刷新画布。

最后,无论新要素是否生成,删除当前 RubberBand,准备下一个多边形的绘制。

删除当前RubberBand的代码如下

void AddPolygonTool::clearRubberBand()
{// 若当前 RubberBand 为空则直接退出if (!mpRubberBand){return;}// 清除其内存并将指针置空delete mpRubberBand;mpRubberBand = nullptr;
}

然后,为了实现绘制的过程中,RubberBand 的最后一个点“跟着鼠标走”的效果,我们重写工具的鼠标移动事件,这样就可以实现绘制时的“动态”效果。:

// 重写QgsMapTool的鼠标移动事件
void AddPolygonTool::canvasMoveEvent(QgsMapMouseEvent * e)
{// 如果mpRubberBand未被创建,或者当前未绘画if (!mpRubberBand || !mIsDrawing){return;}// “橡皮筋”最后一个点“跟着鼠标走”的效果,实现动态绘制// e->mapPoint()是鼠标的最后位置mpRubberBand->movePoint(e->mapPoint());
}

以上代码我们完成了自定义地图工具 AddPolygonTool 的编写。

接下来我们回到主程序窗体。为方便起见,这里我们创建一个“内存图层”用于编辑。内存图层是指不来源于任何外部数据。直接创建于内存之中的图层。在 QGIS 中通过 New Scratch Layer (草稿图层) 创建的图层就是内存图层。
内存图层的创建非常简单,在 URL 中通过正确的语法描述几何数据类型、坐标系、字段信息即可。具体可参考 QgsVectorLayer 的开发文档,写得十分详细。

主窗体代码头文件DefMainWindow.h内容如下

#pragma once
#include <qmainwindow.h>
#include "mainWindow.h"
#include "AddPolygonTool.h"
#include <qgsvectorlayer.h>class DefMainWindow :public QMainWindow
{
public:DefMainWindow(QWidget * parent = nullptr);private:Ui::MainWindow ui;QgsMapCanvas mCanvas;                        // 画布QgsVectorLayer* mpStratchLayer = nullptr;    // 内存图层AddPolygonTool* mpToolAddPolygon = nullptr;  // “添加多边形”地图工具void onStartEditingButtonToggled(bool isChecked);	// 开始编辑按钮的槽函数void onDrawPolygonButtonToggled(bool isChecked);	// 绘制多边形按钮的槽函数
};

接下来写主窗体的构造函数:

#include "DefMainWindow.h"DefMainWindow::DefMainWindow(QWidget *parent) :QMainWindow(parent),mCanvas(this)
{ui.setupUi(this);ui.verticalLayout->addWidget(&mCanvas);// 在内存中创建一个多边形图层,使用坐标系EPSG : 4326 (WGS 84), "memory" 表示内存图层mpStratchLayer = new QgsVectorLayer("polygon?crs=epsg:4326", u8"临时面图层", "memory");mCanvas.setLayers(QList<QgsMapLayer*>() << mpStratchLayer);// 设置当前图层,因为AddPolygonTool.cpp中添加的特征是添加到当前图层,因此必须设置mCanvas.setCurrentLayer(mpStratchLayer);// 将画布缩放到 WGS 84 坐标系的边界范围,否则画布的初始范围与坐标系范围不符,会导致绘制出现问题mCanvas.setExtent(QgsCoordinateReferenceSystem("EPSG:4326").bounds());// 创建一个画多边形的自定义地图工具AddPolygonToolmpToolAddPolygon = new AddPolygonTool(&mCanvas);// 绑定“开始编辑”按钮和“绘制多边形”按钮点击事件QObject::connect(ui.btnStartEditing, &QPushButton::toggled, this, &DefMainWindow::onStartEditingButtonToggled);QObject::connect(ui.btnDrawPolygon, &QPushButton::toggled, this, &DefMainWindow::onDrawPolygonButtonToggled);
}// 点击开始编辑按钮
void DefMainWindow::onStartEditingButtonToggled(bool checked)
{// 如果按钮被按下if (checked){// 使图层可编辑mpStratchLayer->startEditing();// 使“绘制多边形”按钮可以点击ui.btnDrawPolygon->setEnabled(true);}// 如果按钮被释放else{// 向底层数据提供程序提交自上次调用startEditing()以来所做的任何缓冲更改// 即保留当前已经编辑完毕的数据mpStratchLayer->commitChanges();// 设置“绘制多边形”按钮为释放状态ui.btnDrawPolygon->setChecked(false);// 设置“绘制多边形”按钮不可点击ui.btnDrawPolygon->setEnabled(false);}
}// 点击绘制多边形按钮
void DefMainWindow::onDrawPolygonButtonToggled(bool checked)
{// 如果按钮被按下if (checked){// 设置当前在画布上使用的地图工具mCanvas.setMapTool(mpToolAddPolygon);}// 如果按钮被释放else{// 清除当前的“橡皮筋”mpToolAddPolygon->clearRubberBand();// 取消设置当前地图工具或最后一个非缩放工具mCanvas.unsetMapTool(mpToolAddPolygon);}
}

运行程序,先点击“开始编辑”,再点击“绘制多边形”,然后在画布上点击就能绘制多边形了。点击右键结束当前多边形的绘制,绘制完成的多边形会自动变成临时图层的要素。注意我们并没有实现图层保存的功能,因此你退出程序之后图层就从内存里释放了。

参考文章 mriiiron’s blog

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/91997.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MYSQL 作业三

创建一个student表格&#xff1a; create table student( id int(10) not null unique primary key, name varchar(20) not null, sex varchar(4), birth year, department varchar(20), address varchar(50) ); 创建一个score表格 create table score( id int(10) n…

IPv4分组

4.3.1 IPv4分组 IP协议定义数据传送的基本单元——IP分组及其确切的数据格式 1. IPv4分组的格式 IPv4分组由首部和数据部分&#xff08;TCP、UDP段&#xff09;组成&#xff0c;其中首部分为固定部分&#xff08;20字节&#xff09;和可选字段&#xff08;长度可变&#xff0…

使用MAT分析OOM问题

OOM和内存泄漏在我们的工作中&#xff0c;算是相对比较容易出现的问题&#xff0c;一旦出现了这个问题&#xff0c;我们就需要对堆进行分析。 一般情况下&#xff0c;我们生产应用都会设置这样的JVM参数&#xff0c;以便在出现OOM时&#xff0c;可以dump出堆内存文件&#xff…

Monge矩阵

Monge矩阵 对一个m*n的实数矩阵A&#xff0c;如果对所有i&#xff0c;j&#xff0c;k和l&#xff0c;1≤ i<k ≤ m和1≤ j<l ≤ n&#xff0c;有 A[i,j]A[k,l] ≤ A[i,l]A[k,j] 那么&#xff0c;此矩阵A为Monge矩阵。 换句话说&#xff0c;每当我们从矩阵中挑…

jQuery EasyUI datagrid 无记录时,增加“暂无数据“提示

1、在onLoadSuccess中添加如下代码&#xff1a; if (data.total 0) {var body $(this).data().datagrid.dc.body2;body.find(table tbody).append(<tr><td width" body.width() " style"height: 35px; text-align: center;"><h5>暂…

C++的IO流

目录 C语言的输入与输出 流是什么 CIO流 C标准IO流 C文件IO流 stringstream的简单介绍 在C语言中&#xff0c;如果想要将一个整形变量的数据转化为字符串格式&#xff0c;如何去做&#xff1f; 将数值类型数据格式化为字符串 字符串拼接 序列化和反序列化结构数据 注…

管理类联考——逻辑——综合推理——汇总篇——要点

一、真话假话题 #mermaid-svg-gmlWWCoVLQr21gdi {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-gmlWWCoVLQr21gdi .error-icon{fill:#552222;}#mermaid-svg-gmlWWCoVLQr21gdi .error-text{fill:#552222;stroke:#552…

00 - 环境配置

查看所有文章链接&#xff1a;&#xff08;更新中&#xff09;GIT常用场景- 目录 文章目录 1. 环境说明2. 安装配置2.1 配置user信息2.2 config的三个作用域 3. 建git仓库3.1 把已有的项目代码纳入git管理3.2 新建的项目直接用git管理3.3 配置local的user和email3.4 优先级&…

图像像素梯度

梯度 在高数中&#xff0c;梯度是一个向量&#xff0c;是有方向有大小。假设一二元函数f(x,y)&#xff0c;在某点的梯度有&#xff1a; 结果为&#xff1a; 即方向导数。梯度的方向是函数变化最快的方向&#xff0c;沿着梯度的方向容易找到最大值。 图像梯度 在一幅模糊图…

《电路》基础知识入门学习笔记

文章目录&#xff1a; 一&#xff1a;电路模型和电路规律 1.电路概述 2.电路模型 3.基本电路物理量&#xff1a;电流、电压、电功率和能量 4.电流和电压的参考方向 5.电路元件—电阻 6. 电路元件—电压源和电流源 7.受控电源 8.基尔霍夫&#xff08;后面都要用这个方法…

“之江数据安全治理论坛”暨《浙江省汽车数据处理活动规定(专家建议稿)》研讨会顺利召开

研讨会主题 8月10日&#xff0c;“之江数据安全治理论坛”暨《浙江省汽车数据处理活动规定&#xff08;专家建议稿&#xff09;》研讨会在浙江大学计算机创新技术研究院举办。 本次研讨会的主题聚焦于“智能网联汽车的数据安全与数据合规”&#xff0c;邀请行业主管部门和数据…

使用 Docker 部署 canal 服务实现MySQL和ES实时同步

文章目录 0. 环境介绍0. 前置步骤1. 安装Kibana和Elasticsearch2. 安装Canal和Canal Adapter2.1 修改数据库配置2.1.1 修改配置2.1.2 验证mysql binlog配置2.1.3 查看日志文件2.1.4 用JDBC代码插入数据库 2.2 安装Canal Server2.3 安装Canal Adapter修改两处配置文件配置文件取…

浏览器 - 事件循环机制详解

目录 1&#xff0c;浏览器进程模型进程线程浏览器的进程和线程1&#xff0c;浏览器进程2&#xff0c;网络进程3&#xff0c;渲染进程 2&#xff0c;渲染主线程事件循环异步同步 JS 为什么会阻塞渲染任务优先级 3&#xff0c;常见面试题1&#xff0c;如何理解 js 的异步2&#x…

如何使用CSS实现一个响应式网格布局?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 使用CSS实现响应式网格布局⭐ 设置基本的HTML结构⭐ 创建基本的CSS样式⭐ 添加媒体查询以实现响应式效果⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端…

【wiki】电竞助手掉落提醒 EsportsHelper「Webhook」「钉钉」「饭碗警告」「企业微信」「Discord」

介绍 本项目链接 Github电竞助手链接 github上项目电竞助手(EsportsHelper)的掉落提醒配置教程,当有掉宝的时候会发送你信息提示. 至于这个脚本是怎么使用的简单说一下,就是通过自动观看英雄联盟直播 从而获取奖励(仅限直营服),有兴趣的可以去github上看readme,非常详细,支持…

ARM--day2(cpsr、spsr、数据搬移指令、移位操作指令、位运算操作指令、算数运算指令、比较指令、跳转指令)

.text .global _gcd _gcd:mov r0,#9mov r1,#15b loop loop:cmp r0,r1beq stopsubhi r0,r1bhi loopsubcc r1,r0bcc loopstop:b stop.end用for循环实现1~100之间和5050 .text .global _gcd _gcd:mov r0,#0x0mov r1,#0x1mov r2,#0x64b loop loop:cmp r1,r2bhi stopadd r0,r0,r1ad…

epoll数据结构

目录 1.大量的fd 集合。选择什么数据结构&#xff1f;2、Epoll 数据结构Epitem 的定义Eventpoll 的定义 1.大量的fd 集合。选择什么数据结构&#xff1f; 查找频率很高的数据结构 1.红黑树 2.哈希&#xff08;扩容缩容&#xff09; 3. b/btree &#xff08;降低树的高度&#…

【学会动态规划】环形子数组的最大和(20)

目录 动态规划怎么学&#xff1f; 1. 题目解析 2. 算法原理 1. 状态表示 2. 状态转移方程 3. 初始化 4. 填表顺序 5. 返回值 3. 代码编写 写在最后&#xff1a; 动态规划怎么学&#xff1f; 学习一个算法没有捷径&#xff0c;更何况是学习动态规划&#xff0c; 跟我…

UI设计师个人工作总结范文精选

UI设计师个人工作总结范文(一) 在忙忙碌碌中&#xff0c;2019年又将过去了&#xff0c;在这一年当中&#xff0c;设计部无论是在运作模式、设计产值、还是人员结构&#xff0c;各方面的变化都比较大。 设计部的运作模式是从7月底开始进行调整的&#xff0c;以独立承包制的运营方…

pgsql checkpoint机制(1)

检查点触发时机 检查点间隔时间由checkpoint_timeout设置pg_xlog中wall段文件总大小超过参数max_WAL_size的值postgresql服务器在smart或fast模式下关闭手动checkpoint 为什么需要检查点&#xff1f; 定期保持修改过的数据块作为实例恢复时起始位置&#xff08;问题&#xf…