【QGIS二次开发】地图编辑-09
系列目录:
【QGIS二次开发】地图显示与交互-01_qgis二次开发加载地图案例-CSDN博客
【QGIS二次开发】地图显示与交互-02_setlayerlabeling-CSDN博客
【QGIS二次开发】地图符号与色表-03-CSDN博客
【QGIS二次开发】地图编辑-04-CSDN博客
【QGIS二次开发】地图编辑-05-CSDN博客
【QGIS二次开发】地图编辑-06-CSDN博客
【QGIS二次开发】地图编辑-07-CSDN博客
【QGIS二次开发】地图编辑-08-CSDN博客
4.19 属性结构编辑
对树状图的矢量数据,右键点击“查看属性表结构”;
图 79 右键菜单-查看属性表结构
查看结构
图 80 属性表结构
相关代码分析:
当viewProperty函数被调用时,它首先确认当前是否有图层被选中,并判断该图层是否为矢量图层。如果条件满足,它接着创建一个QgsSourceFieldsProperties的实例,这个实例用于显示和管理选中矢量图层的属性字段。通过loadRows()方法加载属性字段信息,并通过show()方法显示这些信息。
代码部分如下:
void MenuProvider::viewProperty()
{ QgsMapLayer* currentLayer = mView->currentLayer(); if (currentLayer) { // 判断图层类型 QgsVectorLayer* layer = qobject_cast<QgsVectorLayer*>(currentLayer); if (layer) { //显示属性结构字段 QgsSourceFieldsProperties* pWidget = new QgsSourceFieldsProperties(layer, nullptr); pWidget->loadRows(); pWidget->show(); } }
}
4.20 属性编辑
点击“查看属性表“
图 81 右键菜单-查看属性表
查看属性表;
图 82 属性表
点击左上角的编辑,然后就可以编辑矢量表数据
图 83 属性表编辑
相关代码思路分析:
使用QgsAttributeTableDialog来显示矢量的属性表;
代码部分如下:
void MenuProvider::viewPropertySheet()
{ QgsMapLayer* currentLayer = mView->currentLayer(); if (currentLayer) { QgsVectorLayer* vectorLayer = qobject_cast<QgsVectorLayer*>(currentLayer); if (vectorLayer) { // 创建图层数据缓存 QgsVectorLayerCache* layerCache = new QgsVectorLayerCache(vectorLayer, vectorLayer->featureCount()); // 使用原始图层创建属性表对话框 QgsAttributeTableDialog* attrDialog = new QgsAttributeTableDialog(vectorLayer); attrDialog->setAttribute(Qt::WA_DeleteOnClose); attrDialog->show(); } }
}
4.21 表格文件转矢量
点击按钮,跳出相关窗口
图 84 表格文件转矢量
选择csv文件后,再选择输出路径。选择csv文件的坐标的坐标系,再选择目标坐标系,完成后点击确定;
图 85 选择投影
完成后点文件显示在图框中;
图 86 转换结果
相关代码思路分析:
首先,通过使用Qt的界面组件创建对话框,包括文本框、文件选择按钮、坐标系选择按钮和确定按钮,构建了用户交互界面。文件选择逻辑使用QFileDialog实现,允许用户选择输入和输出文件的路径。坐标系选择逻辑通过QgsProjectionSelectionDialog实现,允许用户选择原始文件和目标坐标系,并保存坐标系代码。在用户点击“确定”按钮时,对输入进行验证,确保文件路径和坐标系信息已提供,否则显示警告消息。接着,基于用户选择的坐标系代码创建QgsCoordinateReferenceSystem对象,并设置一个坐标转换对象QgsCoordinateTransform。如果坐标转换设置不正确,显示错误消息。
随后,使用输入文件路径创建QgsVectorLayer表示原始Excel数据,并遍历该图层中的所有要素,使用坐标转换对象转换它们的几何形状到目标坐标系。然后,创建一个新的内存矢量图层,将原始图层的字段复制到新图层,再将转换后的要素添加到新图层中。最后,将转换后的图层保存为Shapefile格式,若保存过程中出现错误则显示错误消息,并将新图层添加到当前QGIS项目中,刷新地图画布以显示新图层。
代码部分如下:
void DataViewer::on_actionExcelToVector_triggered()
{ QString sourceCrsCode, targetCrsCode; QDialog dialog; QVBoxLayout layout(&dialog); QLabel labelIn("输入文件:"); QLineEdit lineEditIn; QPushButton fileButtonIn("选择文件"); QLabel labelOut("输出文件:"); QLineEdit lineEditOut; QPushButton fileButtonOut("选择文件"); // 原文件坐标系选择 QPushButton sourceCrsButton("选择原文件坐标系"); QLabel sourceCrsLabel("原文件坐标系:未选择"); // 目标坐标系选择 QPushButton targetCrsButton("选择目标坐标系"); QLabel targetCrsLabel("目标坐标系:未选择"); QPushButton okButton("确定"); layout.addWidget(&labelIn); layout.addWidget(&lineEditIn); layout.addWidget(&fileButtonIn); layout.addWidget(&labelOut); layout.addWidget(&lineEditOut); layout.addWidget(&fileButtonOut); layout.addWidget(&sourceCrsButton); layout.addWidget(&sourceCrsLabel); layout.addWidget(&targetCrsButton); layout.addWidget(&targetCrsLabel); layout.addWidget(&okButton); // 连接文件选择按钮的点击事件到文件选择功能 QObject::connect(&fileButtonIn, &QPushButton::clicked, [&lineEditIn]() { QString fileName = QFileDialog::getOpenFileName(nullptr, "选择输入文件", QDir::currentPath(), "csv (*.csv)"); if (!fileName.isEmpty()) { lineEditIn.setText(fileName); } }); QObject::connect(&fileButtonOut, &QPushButton::clicked, [&lineEditOut]() { QString fileName = QFileDialog::getSaveFileName(nullptr, "选择输出文件", QDir::currentPath(), "shp (*.shp)"); if (!fileName.isEmpty()) { lineEditOut.setText(fileName); } }); // 原文件坐标系选择逻辑 QObject::connect(&sourceCrsButton, &QPushButton::clicked, [&]() { QgsProjectionSelectionDialog crsDialog; if (crsDialog.exec()) { QgsCoordinateReferenceSystem crs = crsDialog.crs(); sourceCrsCode = crs.authid(); sourceCrsLabel.setText("原文件坐标系:" + sourceCrsCode); } }); // 目标坐标系选择逻辑 QObject::connect(&targetCrsButton, &QPushButton::clicked, [&]() { QgsProjectionSelectionDialog crsDialog; if (crsDialog.exec()) { QgsCoordinateReferenceSystem crs = crsDialog.crs(); targetCrsCode = crs.authid(); targetCrsLabel.setText("目标坐标系:" + targetCrsCode); } }); //点击确定之后接受数据 QObject::connect(&okButton, &QPushButton::clicked, &dialog, &QDialog::accept); //dialog.exec(); if (dialog.exec() != QDialog::Accepted) { return; } QString originFileName = lineEditIn.text(); QString finalFileName = lineEditOut.text(); // 检查输入文件名和坐标系是否已提供 if (originFileName.isEmpty() || finalFileName.isEmpty() || sourceCrsCode.isEmpty() || targetCrsCode.isEmpty()) { QMessageBox::warning(nullptr, "警告", "请确保所有输入均已正确填写。"); return; } // 创建原始坐标系和目标坐标系对象 QgsCoordinateReferenceSystem sourceCrs(sourceCrsCode); QgsCoordinateReferenceSystem targetCrs(targetCrsCode); // 创建坐标转换对象 QgsCoordinateTransform transform(sourceCrs, targetCrs, QgsProject::instance()->transformContext()); if (!transform.isValid()) { QMessageBox::critical(nullptr, "错误", "坐标转换设置不正确。"); return; } // 创建矢量图层,使用原始坐标系 QString uri = "file:///" + originFileName + "?delimiter=,&crs=" + sourceCrsCode + "&xField=x&yField=y"; QgsVectorLayer* layer = new QgsVectorLayer(uri, "Layer Name", "delimitedtext"); if (!layer->isValid()) { QMessageBox::critical(nullptr, "错误", "创建矢量图层失败。"); return; } // 转换图层中的要素到目标坐标系 QList<QgsFeature> transformedFeatures; QgsFeature feature; QgsFeatureIterator featureIt = layer->getFeatures(); while (featureIt.nextFeature(feature)) { QgsGeometry geom = feature.geometry(); geom.transform(transform); feature.setGeometry(geom); transformedFeatures.append(feature); } // 创建新的内存图层以保存转换后的要素 QString memoryLayerUri = "Point?crs=" + targetCrs.authid(); QgsVectorLayer* transformedLayer = new QgsVectorLayer(memoryLayerUri, "转换后图层", "memory"); // 确保新图层有与原图层相同的字段 transformedLayer->dataProvider()->addAttributes(layer->dataProvider()->fields().toList()); transformedLayer->updateFields(); // 开始编辑图层 transformedLayer->startEditing(); // 将转换后的要素添加到新图层 for (auto& transformedFeature : transformedFeatures) { transformedLayer->dataProvider()->addFeature(transformedFeature); } // 提交更改 transformedLayer->commitChanges(); // 保存为 Shapefile QgsVectorFileWriter writer(finalFileName, "UTF-8", transformedLayer->fields(), transformedLayer->wkbType(), transformedLayer->crs(), "ESRI Shapefile"); // 检查保存过程中是否有错误发生 if (writer.hasError() != QgsVectorFileWriter::NoError) { QMessageBox::critical(nullptr, "错误", "保存 Shapefile 时出错: " + writer.errorMessage()); return; } // 遍历转换后的图层中的每个要素并写入 Shapefile QgsFeatureIterator transformedFeatureIt = transformedLayer->getFeatures(); QgsFeature transformedFeature; while (transformedFeatureIt.nextFeature(transformedFeature)) { writer.addFeature(transformedFeature); } // 添加转换后的图层到项目 QgsProject::instance()->addMapLayer(transformedLayer); m_mapCanvas->refresh(); }
4.22 撤销与重做
用户需要撤销或是重做时,点击工具栏中的撤销或重做来对缓存中的内容进行撤销或重做,但是对于已经保存在内存中的内容用户已经不能进行撤销。
例如,当我对面要素进行删除面边界上的点时,我点击撤销,刚才进行的删除点的操作全部撤销,实现效果如下:
图 87 删除点的要素
图 88 撤销效果
实现的代码如下,通过在 DataViewer中的添加两个成员函数on_actionUndo_triggered和on_actionRedo_triggered。这两个函数分别处理撤销和重做操作。首先,函数检查当前图层是否为矢量图层,然后获取该图层的撤销堆栈undoStack。通过调用undo和redo函数,这两个函数分别执行当前图层上的撤销和重做操作。最后,刷新地图视图和所有图层,以确保最新的更改在地图中反映出来。这些功能使用户能够在编辑矢量图层时方便地撤销和重做操作。
void DataViewer::on_actionUndo_triggered()
{ if (currentLayer) { QgsVectorLayer* vectorLayer = qobject_cast<QgsVectorLayer*>(currentLayer); if (vectorLayer) { // 使用撤销堆栈来撤销或重做操作 QUndoStack* undoStack = vectorLayer->undoStack(); undoStack->undo(); // 撤销操作 m_mapCanvas->refresh(); m_mapCanvas->refreshAllLayers(); } }
} void DataViewer::on_actionRedo_triggered()
{ if (currentLayer) { QgsVectorLayer* vectorLayer = qobject_cast<QgsVectorLayer*>(currentLayer); if (vectorLayer) { // 使用撤销堆栈来撤销或重做操作 QUndoStack* undoStack = vectorLayer->undoStack(); undoStack->redo(); // 重做操作 m_mapCanvas->refresh(); m_mapCanvas->refreshAllLayers(); } }
}