实习四 GIS矢量数据编辑
4.1 任务要求
- 利用 QgsRasterLayer 和 QgsMapCavans 显示 GeoTiff 格式的遥感影像或 DEM 数据。
- 利用 QgsVectorLayer 和 QgsMapCavas 显示 shp 格式的图层数据和 qgs 格式的工程数据。
- 在显示矢量图时,查看、编辑矢量图的属性结构、属性值。
- 在显示矢量图时,查看、编辑矢量图的点、线、面的几何数据,
- 在显示矢量图时,编辑矢量图的图形参数。
4.2 完成过程
4.2.1 GIS矢量数据编辑实现技术
在QGIS中,GIS矢量数据编辑等功能的实现主要应用到了<QgsVectorLayer>类和<QgsRasterLayer>类。这里<QgsVectorLayer>是QGIS的核心类之一,用于加载和管理矢量数据。矢量数据以点、线、多边形等几何类型为基础,同时可以存储丰富的属性信息。该类支持加载多种矢量数据格式,如Shapefile、GeoJSON、PostGIS等,并提供对图层字段、几何和属性的全面操作能力。通过QgsVectorLayer,用户可以执行添加、删除、编辑要素的操作,还可以进行几何操作,如缓冲区分析、相交或合并等。为了支持多样化的渲染和显示效果,QgsVectorLayer还结合QgsRenderer和QgsSymbol提供丰富的样式设置能力,能够灵活渲染点、线、面等几何对象,是GIS矢量数据处理和可视化的核心组件。
<QgsRasterLayer>类是QGIS中用于加载和管理栅格数据的关键类,栅格数据通常由像素格网组成,每个像素具有一个或多个数值(如影像亮度值、海拔高度值等)。该类支持多种栅格数据格式,如GeoTIFF、JPEG、PNG,以及基于服务的栅格源(如WMS和WMTS)。通过QgsRasterLayer,用户可以对栅格数据进行基本的可视化设置,如指定颜色渲染方式、调整波段组合和透明度等。它还支持与其他工具结合,执行栅格数据的裁剪、重投影、统计分析等操作,是GIS栅格数据加载、管理和渲染的主要工具。
< QgsVectorLayer >类的继承关系如图4.2.1-1所示。
图4.2.1-1 < QgsVectorLayer >类的继承关系
与之相搭配的,< QgsRasterLayer >类的继承关系如图4.2.1-2所示。
图4.2.1-2 < QgsRasterLayer >类的继承关系
接下来我们就如何实现基于QGIS二次开发实现GIS矢量数据编辑操作给出详细过程。
4.2.2 GIS矢量数据编辑实现过程
这里我们首先打开项目的ui文件,在主界面添加需要使用的QAction控件,分别为“打开工程”、“添加矢量数据”、“添加栅格数据”、“打开属性表”和“打开属性管理器”。为了便于代码的编辑,将添加的按钮的objectName分别命名为“actionOpenProject”、“actionAddVectorData”、“actionAddRasterData”、“actionOpenFields”和“actionOpenAttrTable”。操作如图4.2.2-1所示。
图4.2.2-1 对ui进行操作
将设定的ui进行保存后,即可回到Visual Studio中,对相应的代码进行适当的编辑。
一、打开文件的实现
首先,需要进入头文件中,定义必要的函数,这是由于QT的信号与槽的机制,必须要进行信号与槽的相关绑定,这里我们主要预先声明了一个函数,即“on_actionOpenProject_triggered()”。如图4.2.2-2所示。
图4.2.2-2 声明函数
接下来,需要对声明的函数进行详细的定义。“on_actionOpenProject_triggered()”函数主要用于打开一个 QGIS 工程文件(.qgs),加载其图层到项目中,并创建一个新的 QgsLayerTreeMapCanvasBridge 对象,将工程的图层树与地图画布关联,实现图层的同步渲染与显示。
操作如图4.2.2-3所示。
图4.2.2-3 定义函数
二、矢量和栅格文件读取的实现
接下来,需要进入头文件中,定义必要的函数,这是由于QT的信号与槽的机制,必须要进行信号与槽的相关绑定,这里我们主要预先声明了两个函数,即“on_actionAddVectorData_triggered”和“on_actionAddRasterData_triggered”。如图4.2.2-4所示。
图4.2.2-4 声明函数
接下来,需要对声明的函数进行详细的定义。“on_actionAddVectorData_triggered()”函数用于加载用户选择的矢量数据(如 Shapefile),验证其有效性后添加到 QGIS 工程中,并更新地图画布显示,同时设置当前活动图层。
“on_actionAddRasterData_triggered()”函数用于加载用户选择的栅格数据(如 TIFF、IMG 文件),验证其有效性后将图层添加到 QGIS 工程,并刷新地图画布以显示新加载的图层。
操作如图4.2.2-5所示。
图4.2.2-5 定义函数
三、属性表和属性字段的实现
接下来,需要进入头文件中,定义必要的函数,这是由于QT的信号与槽的机制,必须要进行信号与槽的相关绑定,这里我们主要预先声明了两个函数,即“on_actionOpenAttrTable_triggered()”和“on_actionOpenFields_triggered()”。如图4.2.2-6所示。
图4.2.2-6 声明函数
接下来,需要对声明的函数进行详细的定义。“on_actionOpenAttrTable_triggered”函数用于打开当前矢量图层的属性表,检查图层有效性和要素数量后,创建属性表视图并加载所有要素数据,同时避免重复打开多个属性表窗口。
“on_actionOpenFields_triggered”函数用于打开当前矢量图层的字段结构编辑窗口,支持字段的查看和修改。如果字段窗口已存在,则直接激活,否则创建新的窗口并加载字段数据。
操作如图4.2.2-7所示。
图4.2.2-7 定义函数
进行完如上步骤,编译即可得到相应具备GIS矢量数据编辑功能的系统,如图4.2.2-8所示。
图4.2.2-8 系统界面
4.3 结果展示
完成4.2部分的系统构建后,我们便可以使用系统的GIS数据编辑功能了,点击“打开工程”,便可以选择打开一个QGIS的“.qgs”的工程项目。如图4.3-1所示。
图4.3-1 打开工程
这里我们也可以进行矢量数据的添加,这里可以通过点击菜单栏打开,同样也可以通过“添加栅格图层”点击界面的按钮进行打开,如图4.3-2所示。
图4.3-2 矢量数据的添加
当然,这里也可以显示 GeoTiff 格式的遥感影像或 DEM 数据,可以通过点击菜单栏“添加栅格图层”打开,同样也可以通过点击界面的按钮进行打开,如图4.3-3所示。
图4.3-3 栅格数据添加
同样,这里也可以显示矢量数据的属性结构,可以通过点击菜单栏“打开属性结构”打开,同样也可以通过点击界面的按钮进行打开如,图4.3-4所示。
图4.3-4 矢量数据的属性结构
同样,也可以显示属性字段,可以通过点击菜单栏“打开属性字段”打开,同样也可以通过点击界面的按钮进行打开如,如图4.3-5所示。
图4.3-5 显示属性字段
在打开的属性表中,也可以进行字段的添加,点击左上角的“添加字段”按钮即可完成字段的添加操作,如图4.3.6所示。
图4.3-6 字段属性修改
4.4 关键代码
这里的关键代码是针对GIS矢量数据编辑操作。
具体代码的功能主要是:“on_actionOpenProject_triggered()”函数主要用于打开一个 QGIS 工程文件(.qgs),加载其图层到项目中,并创建一个新的 QgsLayerTreeMapCanvasBridge 对象,将工程的图层树与地图画布关联,实现图层的同步渲染与显示。on_actionAddVectorData_triggered()”函数用于加载用户选择的矢量数据(如 Shapefile),验证其有效性后添加到 QGIS 工程中,并更新地图画布显示,同时设置当前活动图层。
“on_actionAddRasterData_triggered()”函数用于加载用户选择的栅格数据(如 TIFF、IMG 文件),验证其有效性后将图层添加到 QGIS 工程,并刷新地图画布以显示新加载的图层。
“on_actionOpenAttrTable_triggered”函数用于打开当前矢量图层的属性表,检查图层有效性和要素数量后,创建属性表视图并加载所有要素数据,同时避免重复打开多个属性表窗口。
“on_actionOpenFields_triggered”函数用于打开当前矢量图层的字段结构编辑窗口,支持字段的查看和修改。如果字段窗口已存在,则直接激活,否则创建新的窗口并加载字段数据。
源代码如下:
//打开qgs项目
void YLGIS::on_actionOpenProject_triggered()
{
QString filename = QFileDialog::getOpenFileName(this, QStringLiteral("选择工程文件"), "", "QGIS project (*.qgs)");
QFileInfo fi(filename);
if (!fi.exists())
{
return;
}
QgsProject::instance()->read(filename);
m_curMapLayer = QgsProject::instance()->mapLayer(0);
// 删除已有的m_layerTreeCanvasBridge以防止重复实例化
if (m_layerTreeCanvasBridge) {
delete m_layerTreeCanvasBridge;
m_layerTreeCanvasBridge = nullptr;
}
m_layerTreeCanvasBridge = new QgsLayerTreeMapCanvasBridge(QgsProject::instance()->layerTreeRoot(), m_mapCanvas, this);
}
//打开矢量文件
void YLGIS::on_actionAddVectorData_triggered()
{
QStringList layerPathList = QFileDialog::getOpenFileNames(this, QStringLiteral("选择矢量数据"), "", "shapefile (*.shp)");
QList<QgsMapLayer*> layerList;
for (QString layerPath : layerPathList)
{
QFileInfo fi(layerPath);
if (!fi.exists()) { return; }
QString layerBaseName = fi.baseName(); // 图层名称
QgsVectorLayer* vecLayer = new QgsVectorLayer(layerPath, layerBaseName);
if (!vecLayer) { return; }
if (!vecLayer->isValid())
{
QMessageBox::information(0, "", "layer is invalid");
return;
}
layerList << vecLayer;
}
if (layerList.size() < 1)
return;
//选择图层
connect(m_layerTreeView, &QgsLayerTreeView::currentLayerChanged, this, &YLGIS::onCurrentLayerChanged);
m_curMapLayer = layerList[0];
QgsProject::instance()->addMapLayers(layerList);
m_mapCanvas->refresh();
}
//打开栅格文件
void YLGIS::on_actionAddRasterData_triggered()
{
QStringList layerPathList = QFileDialog::getOpenFileNames(this, QStringLiteral("选择栅格数据"), "", "Image (*.img *.tif *.tiff)");
QList<QgsMapLayer*> layerList;
for (QString layerPath : layerPathList)
{
QFileInfo fi(layerPath);
if (!fi.exists()) { return; }
QString layerBaseName = fi.baseName(); // 图层名称
QgsRasterLayer* rasterLayer = new QgsRasterLayer(layerPath, layerBaseName);
if (!rasterLayer) { return; }
if (!rasterLayer->isValid())
{
QMessageBox::information(0, "", "layer is invalid");
return;
}
layerList << rasterLayer;
}
QgsProject::instance()->addMapLayers(layerList);
m_mapCanvas->refresh();
}
//显示属性表
void YLGIS::on_actionOpenAttrTable_triggered()
{
// 检查当前图层是否有效
QgsVectorLayer* layer = (QgsVectorLayer*)m_curMapLayer;
if (layer == nullptr || !layer->isValid() || QgsMapLayerType::VectorLayer != m_curMapLayer->type())
{
qDebug() << QStringLiteral("当前图层无效或不是矢量图层。");
return;
}
// 检查图层是否有要素,若没有则提示
if (layer->featureCount() == 0)
{
qDebug() << QStringLiteral("图层中没有要素数据。");
return;
}
// 创建一个属性表缓存(缓存大小可根据图层要素数量调整)
QgsVectorLayerCache* lc = new QgsVectorLayerCache(layer, 5000);
// 创建属性表模型并加载图层数据
QgsAttributeTableModel* tm = new QgsAttributeTableModel(lc);
//if (!tm->loadLayer()) // 确保加载成功
//{
// qDebug() << "属性表模型加载失败。";
// delete lc;
// delete tm;
// return;
//}
// 创建过滤模型,并设置为显示所有数据
QgsAttributeTableFilterModel* tfm = new QgsAttributeTableFilterModel(m_mapCanvas, tm, tm);
tfm->setFilterMode(QgsAttributeTableFilterModel::ShowAll);
// 检查是否已经存在一个打开的属性表视图,避免重复打开
static QgsAttributeTableView* tv = nullptr;
if (tv) // 如果已存在,则直接显示
{
tv->raise();
tv->activateWindow();
return;
}
// 创建并设置属性表视图
tv = new QgsAttributeTableView();
tv->setModel(tfm);
// 显示属性表视图
tv->show();
tv->raise();
tv->activateWindow(); // 将窗口置于前台
// 在窗口关闭时,重置 tv 指针,确保下次可以重新创建
connect(tv, &QgsAttributeTableView::destroyed, [=]() {
tv = nullptr;
});
}
//打开属性字段
void YLGIS::on_actionOpenFields_triggered()
{
// 检查当前图层是否有效
QgsVectorLayer* layer = (QgsVectorLayer*)m_curMapLayer;
if (layer == nullptr || !layer->isValid() || QgsMapLayerType::VectorLayer != m_curMapLayer->type())
{
qDebug() << QStringLiteral("当前图层无效或不是矢量图层。");
return;
}
// 启用编辑模式(如果未启动)
if (!layer->isEditable())
{
layer->startEditing();
}
// 静态指针,确保只打开一个字段结构窗口
static QgsSourceFieldsProperties* pWidget = nullptr;
if (pWidget) // 如果窗口已存在,则直接激活
{
pWidget->raise();
pWidget->activateWindow();
return;
}
// 创建属性结构窗口并加载字段数据
pWidget = new QgsSourceFieldsProperties(layer, nullptr);
pWidget->loadRows();
// 显示窗口
pWidget->show();
pWidget->raise();
pWidget->activateWindow();
// 连接窗口销毁事件,窗口关闭时重置 pWidget 指针
connect(pWidget, &QgsSourceFieldsProperties::destroyed, [=]() {
pWidget = nullptr;
});
}