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

news2024/11/22 23:32:46

对矢量要素的编辑是 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);

	// 清除当前的 RubberBand
	void clearRubberBand();

protected:

	// 重写 QgsMapTool 的鼠标移动事件
	virtual void canvasMoveEvent(QgsMapMouseEvent *e);

	// 重写 QgsMapTool 的鼠标点击事件
	virtual void canvasPressEvent(QgsMapMouseEvent *e);

private:

	// 当前正在工作的 RubberBand
	QgsRubberBand* mpRubberBand = nullptr;

	// 记录是否正在绘制中,构造函数中初始化为 false
	bool mIsDrawing;
};

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

// 重写QgsMapTool的鼠标点击事件
void AddPolygonTool::canvasPressEvent(QgsMapMouseEvent * e)
{
	// 如果当前“橡皮筋”没有被创建
	if (!mpRubberBand)
	{
		// 使用QGIS设置中的颜色/线宽创建一个“橡皮筋”,方法来自QgsMapToolEdit
		mpRubberBand = 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;
		// 如果“橡皮筋”中的顶点数大于3
		if (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());
	// 创建一个画多边形的自定义地图工具AddPolygonTool
	mpToolAddPolygon = 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.coloradmin.cn/o/880826.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Tuxera NTFS Mac2023最新免费版Mac读写工具

有时候我们在使用苹果笔记本的时候&#xff0c;会遇到一些问题&#xff0c;比如怎么打开移动硬盘&#xff0c;或者为什么苹果电脑读不出U盘。这些问题可能让我们感到困惑和沮丧&#xff0c;但其实都有解决办法。本文就来为大家介绍一下苹果笔记本怎么打开移动硬盘和苹果电脑读不…

MRO数字化帮助企业实现明显降本增效

MRO&#xff08;Maintenance, Repair and Operations&#xff09;数字化是指将传统的维修、保养和运营管理过程数字化&#xff0c;以提高效率、降低成本、提高质量和可靠性。MRO数字化是企业数字化转型的重要抓手之一&#xff0c;因为它可以帮助企业实现明显的降本增效。 MRO数…

CSS变形与动画(二):perspctive透视效果 与 preserve-3d 3d效果(奥运五环例子)

文章目录 perspective 3d透视效果preserve-3d 3d嵌套效果例子 奥运五环 backface-visibility 背面效果 perspective 3d透视效果 perspective 指定了观察者与 z0 平面的距离&#xff0c;使具有三维位置变换的元素产生透视效果。z>0 的三维元素比正常大&#xff0c;而 z<0 …

WMS系统库存锁定:提升物流管理效率

一、库存锁定的概念 库存锁定是WMS系统中的一个重要功能&#xff0c;它在物流运作过程中发挥着关键的作用。库存锁定是指在物流流程中&#xff0c;将特定的库存数量或者位置与订单或任务相关联&#xff0c;以确保这些库存不会在未经授权的情况下被其他操作所改变或消耗。库存锁…

「2024」预备研究生mem-论证推理强化:引入前提(上)

一、引入前提&#xff08;上&#xff09; 二、课后练习

DP——背包问题

DP——背包问题 01背包问题分数背包问题多重背包问题完全背包问题 当我们谈论背包问题时&#xff0c;可以想象成一个小朋友要去旅行&#xff0c;但是他只能带一个容量有限的背包。他有一些物品可以选择放入背包&#xff0c;每个物品都有自己的重量和价值。小朋友的目标是在不超…

安卓逆向 - 某麦网 x-mini-wua x-sgext x-sign x-umt x-utdid

本文仅供学习交流&#xff0c;只提供关键思路不会给出完整代码&#xff0c;严禁用于非法用途&#xff0c;若有侵权请联系我删除&#xff01; 目标app: 5aSn6bqm572ROC41LjQ 目标接口&#xff1a;aHR0cHM6Ly9hY3MubS50YW9iYW8uY29tL2d3L210b3AuZGFtYWkud2lyZWxlc3MuZGlzY292ZX…

【历史上的今天】8 月 15 日:苹果推出初代 iMac;谷歌收购摩托罗拉移动;Fuchsia 首次发布

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 8 月 15 日&#xff0c;在 1878 年的今天&#xff0c;我国第一套邮票发行。中国是一个文明古国&#xff0c;在邮政通信方面&#xff0c;有着悠久的历史。早在三…

C语言实例_和校验算法

一、算法介绍 和校验&#xff08;Checksum&#xff09;是一种简单的纠错算法&#xff0c;用于检测或验证数据传输或存储过程中的错误。它通过对数据进行计算并生成校验和&#xff0c;然后将校验和附加到数据中&#xff0c;在接收端再次计算校验和并进行比较&#xff0c;以确定…

10个经典战略分析模型,助力洞察市场明确优势

在企业的经营管理过程中&#xff0c;要时刻清晰内外部环境和自身的优劣势&#xff0c;做好企业略规划&#xff0c;进行企业内外部资源的分析&#xff0c;对经营环境&#xff0c;企业核心竞争力有足够的判断&#xff0c;才能明确企业的发展方向。本文为大家分享10个常用的战略分…

Leetcode每日一题:833. 字符串中的查找与替换(2023.8.15 C++)

目录 833. 字符串中的查找与替换 题目描述&#xff1a; 实现代码与思路&#xff1a; 哈希表 模拟 原理思路&#xff1a; 833. 字符串中的查找与替换 题目描述&#xff1a; 你会得到一个字符串 s (索引从 0 开始)&#xff0c;你必须对它执行 k 个替换操作。替换操作以三个…

ide internal errors【bug】

ide internal errors【bug】 前言版权ide internal errors错误产生相关资源解决1解决2 设置虚拟内存最后 前言 2023-8-15 12:36:59 以下内容源自《【bug】》 仅供学习交流使用 版权 禁止其他平台发布时删除以下此话 本文首次发布于CSDN平台 作者是CSDN日星月云 博客主页是h…

数据分析案例丨商品零售购物篮分析(上)

购物篮分析关联规则挖掘应用 将单个客户一次购买商品的总和(以收银台结账为准)称为一个购物篮。那么购物篮分析就是针对商品的相关性进行分析。因为最初这种关联分析主要是在超市应用广泛&#xff0c;所以也称为“购物篮分析”。 购物篮分析是商业领域最前沿、最具挑战性的问…

使用wxPython和PyMuPDF提取PDF页面指定页数的内容的应用程序

在本篇博客中&#xff0c;我们将探讨如何使用wxPython和PyMuPDF库创建一个简单的Bokeh应用程序&#xff0c;用于选择PDF文件并提取指定页面的内容&#xff0c;并将提取的内容显示在文本框中。 C:\pythoncode\new\pdfgetcontent.py 准备工作 首先&#xff0c;确保你已经安装了…

高忆管理:什么是一码通?有什么好处?

在经过券商开户后&#xff0c;除了其间的财物账户、沪深股账户外&#xff0c;还有一个一码通账户&#xff0c;什么是一码通&#xff1f;它有什么好处&#xff1f;关于这些&#xff0c;高忆管理为大家预备了以下参阅内容。 什么是一码通&#xff1f; 一码通账户&#xff0c;一般…

公司电脑三维图纸加密、机械图挡加密软件

机械图纸加密软件的问世&#xff0c;让很多的网络公司都大受其带来的工作中的便利。在安装了机械图纸加密软件后&#xff0c;不仅可以很好的管理员工在工作时的上网娱乐&#xff0c;在对整个公司员工的工作效率上也有着明显的提高&#xff0c;那么对于机械图纸加密软件的具体特…

【数据结构】堆的实现,堆排序以及TOP-K问题

目录 1.堆的概念及结构 2.堆的实现 2.1初始化堆 2.2销毁堆 2.3取堆顶元素 2.4返回堆的大小 2.5判断是否为空 2.6打印堆 2.7插入元素 2.8堆的向上调整 2.9弹出元素 2.10堆的向下调整 3. 建堆时间复杂度 4. 堆的应用 4.1 堆排序 4.2 TOP-K问题 1.堆的概念及结构 …

springboot多模块打包方式

明确子父模块结构 父目录是带modules 大致结构如下&#xff1a; <modules><module>ruoyi-admin</module><module>ruoyi-framework</module><module>ruoyi-system</module><module>ruoyi-quartz</module><module>…

线程记录(2)

1.线程状态 NEW : 分配内存地址&#xff0c;创建线程 RUNNABLE&#xff1a;&#xff08;就绪/运行&#xff09;调用start()之后&#xff08;/没有调度CPU调度&#xff09; BLOCKED&#xff1a;还未拿到锁&#xff0c;等待、被阻塞&#xff08;拿到synchronized失败状态&…

erp系统是什么,erp系统有哪些品牌

阅读本文&#xff0c;您可以了解&#xff1a;1、erp系统是什么&#xff1b;2、如何选择erp系统&#xff1b;3、erp系统有哪些品牌 一、erp系统是什么 ERP系统是企业资源规划&#xff08;Enterprise Resource Planning&#xff09;系统的缩写。它是一种集成的、全面的企业管理…