再续前言:【QGIS二次开发】地图显示与交互-01-CSDN博客
2.4 显示矢量图层注记
任务要求:从矢量图层的属性结构中选择一个属性作为注记,显示在地图视图中。
完成的逻辑是在QgsLayerTreeViewMenuProvider的实例类中添加一个openLabelsDialog方法,实现矢量图层注记功能。
代码逻辑如下:
- 获取当前选中的图层。如果没有选中的图层,或者选中的图层不是矢量图层,就直接返回。
- 创建一个对话框,并设置其布局为垂直布局。
- 在对话框中添加一个标签,显示"标注值:"。创建一个标签设置对象,并设置标签可见。
- 创建一个字段名下拉菜单,并将图层的所有字段名添加到下拉菜单中。设置下拉菜单为可编辑。
- 创建一个按钮,点击该按钮会打开一个表达式对话框。在表达式对话框中调用QgsExpressionBuilderDialog对象,用户可以输入一个表达式,该表达式将被设置为标签字段。
- 调用QgsTextFormatWidget对象,创建一个文本格式设置窗口,用于设置标签的文本格式。
- 创建一个包含“应用”、“确定”和“取消”按钮的按钮框。点击“确定”或“取消”按钮会关闭对话框。
- 连接“应用”按钮的clicked信号到一个槽函数。在这个槽函数中,获取用户选择的字段名或表达式,并将其设置为标签字段。获取文本格式设置窗口的文本格式,并将其设置到标签设置的文本格式中。然后,创建一个标签提供者,并将其设置到图层中。启用图层的标签,并触发图层的重绘。
- 显示对话框,并等待用户的操作。如果用户点击了“确定”按钮,就执行和“应用”按钮相同的操作。
实现的效果是,在图层管理器中图层的右键菜单中添加图层标注菜单选项,点击图层标注菜单选项打开对话框,对话框内包含了一个包含了图层所有字段名的可编辑下拉框,一个打开表达式对话框按钮,一个QgsTextFormatWidget对象,用来修改标注的字体格式;以及三个按钮,包括ok、cancel和apply。
使用时可以在下拉框中更改标注字段,也可以点击打开表达式对话框按钮打开一个QgsExpressionBuilderDialog对象,用表达式的方式标注。选择完标注的格式后可以在下方调整标注的字体格式,再点击ok或apply按钮即可修改图层标注。
核心代码如下:
void MenuProvider::openLabelsDialog()
{
// 获取当前选中的图层
QModelIndex index = mView->currentIndex();
// 获取图层树模型
QgsLayerTreeModel* layerTreeModel = mView->layerTreeModel();
// 将当前选中的索引转换为一个图层树节点
QgsLayerTreeNode* node = layerTreeModel->index2node(index);
// 将节点转换为一个地图图层
QgsMapLayer* layer = QgsLayerTree::toLayer(node)->layer();
// 如果获取的图层为空,则返回
if (!layer)
return;
// 尝试将图层转换为矢量图层,如果不是矢量图层,则返回
QgsVectorLayer* vLayer = qobject_cast<QgsVectorLayer*>(layer);
if (!vLayer)
return;
// 创建一个对话框
QDialog* dialog = new QDialog();
// 创建一个垂直布局,并将其设置到对话框中
QVBoxLayout* layout = new QVBoxLayout(dialog);
// 创建一个标签
QLabel* label = new QLabel("标注值:");
// 将标签添加到布局中
layout->addWidget(label);
// 创建一个标签设置对象
QgsPalLayerSettings labelSettings;
// 设置标签可见
labelSettings.drawLabels = true;
// 创建一个字段名下拉菜单
QComboBox* fieldNameComboBox = new QComboBox();
// 将图层的所有字段名添加到下拉菜单中
for (const QgsField& field : vLayer->fields())
{
fieldNameComboBox->addItem(field.name());
}
// 设置下拉菜单为可编辑
fieldNameComboBox->setEditable(true);
// 将字段名下拉菜单和文本格式设置窗口添加到布局中
layout->addWidget(fieldNameComboBox);
// 创建一个按钮
QPushButton* expressionButton = new QPushButton("打开表达式对话框");
// 连接按钮的clicked信号到一个槽函数
QObject::connect(expressionButton, &QPushButton::clicked, [=]() mutable {
// 创建一个表达式对话框
QgsExpressionBuilderDialog expressionDialog(vLayer, "", dialog);
// 显示表达式对话框,并等待用户的操作
if (expressionDialog.exec() == QDialog::Accepted)
{
// 如果用户点击了“确定”按钮,就获取用户输入的表达式,并将其设置为标签字段
QString expression = expressionDialog.expressionText();
labelSettings.fieldName = expression;
// 将表达式添加到下拉菜单中
fieldNameComboBox->addItem(expression);
// 将表达式设置为下拉菜单的当前项
fieldNameComboBox->setCurrentText(expression);
// 获取用户选择的字段名或表达式,并将其设置为标签字段
labelSettings.fieldName = fieldNameComboBox->currentText();
// 设置为表达式模式
labelSettings.isExpression = true; // 添加这一行
}
});
// 将按钮添加到布局中
layout->addWidget(expressionButton);
// 创建一个文本格式设置窗口
QgsTextFormatWidget* formatWidget = new QgsTextFormatWidget();
// 设置窗口的文本格式为标签设置的文本格式
formatWidget->setFormat(labelSettings.format());
layout->addWidget(formatWidget);
// 创建一个包含“应用”、“确定”和“取消”按钮的按钮框,并将其添加到布局中
QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Apply | QDialogButtonBox::Ok | QDialogButtonBox::Cancel, dialog);
layout->addWidget(buttonBox);
// 连接按钮框的“确定”和“取消”信号到对话框的“接受”和“拒绝”槽
QObject::connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept);
QObject::connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject);
// 获取应用按钮并连接其clicked信号到一个自定义的槽
QPushButton* applyButton = buttonBox->button(QDialogButtonBox::Apply);
QObject::connect(applyButton, &QPushButton::clicked, [=]() {
// 在这里创建一个新的标签设置对象
QgsPalLayerSettings labelSettings;
// 设置标签可见
labelSettings.drawLabels = true;
// 设置为表达式模式
labelSettings.isExpression = true;
// 获取用户选择的字段名,并将其设置为标签字段
labelSettings.fieldName = fieldNameComboBox->currentText();
// 获取文本格式设置窗口的文本格式,并将其设置到标签设置的文本格式中
labelSettings.setFormat(formatWidget->format());
// 创建一个标签提供者
QgsVectorLayerSimpleLabeling* labeling = new QgsVectorLayerSimpleLabeling(labelSettings);
// 将标签提供者设置到图层中
vLayer->setLabeling(labeling);
// 启用图层的标签
vLayer->setLabelsEnabled(true);
// 触发图层的重绘,以更新图层的显示
vLayer->triggerRepaint();
});
// 显示对话框,并等待用户的操作
if (dialog->exec() == QDialog::Accepted)
{
// 设置为表达式模式
labelSettings.isExpression = true;
// 如果用户点击了“确定”按钮,就获取用户选择的字段名,并将其设置为标签字段
labelSettings.fieldName = fieldNameComboBox->currentText();
// 获取文本格式设置窗口的文本格式,并将其设置到标签设置的文本格式中
labelSettings.setFormat(formatWidget->format());
// 创建一个标签提供者
QgsVectorLayerSimpleLabeling* labeling = new QgsVectorLayerSimpleLabeling(labelSettings);
// 将标签提供者设置到图层中
vLayer->setLabeling(labeling);
// 启用图层的标签
vLayer->setLabelsEnabled(true);
// 触发图层的重绘,以更新图层的显示
vLayer->triggerRepaint();
}
}
实现的效果如下:
图 9 右键-图层标注按钮
图 10 标注设置
图 11 标注效果
2.5 图层动态投影与叠加显示
功能点描述:将两个地理范围一致,但投影参数不同的图层添加到一个工程中,通过动态投影,进行图层叠加显示。
操作及截图
矢量数据转换:在矢量坐标转换界面上输入需要进行转换的图层(根据源文件的坐标系),之后确定输出文件路径,如下图:
图 12 矢量投影转换面板
点击确定按钮后进行坐标转换,程序会获取输入路径和输出路径。首先创建一个QgsVectorLayer对象,用于读取输入路径对应的Shapefile文件。如果输入图层无效,则结束操作,之后定义目标图层的参考坐标系为EPSG:3857,创建QgsCoordinateTransform对象,用于将原始图层的坐标系转换为目标图层的坐标系,遍历输入图层的每个要素,将其几何对象进行坐标转换,并将转换后的要素添加到postTransfeatureList中。创建一个新的临时图层targetLayer,使用createTempLayer函数根据转换后的要素列表创建。该函数根据要素的几何类型和目标坐标系创建一个相应的空白图层,并将要素和属性添加到图层中。使用QgsVectorFileWriter将targetLayer保存为输出路径对应的Shapefile文件。如果保存过程中出现错误,则结束操作,转换后的图层如下:
图 13 投影转换结果
图 14 查看图层投影坐标
核心代码:
QgsVectorLayer* createTempLayer(QgsWkbTypes::GeometryType geomType, QString crs, QgsFields fields, QgsFeatureList features)
{
QgsVectorLayer* newLayerPtr = nullptr;
if (geomType == QgsWkbTypes::GeometryType::PointGeometry)
{
QString layerDef = "Point?crs=" + crs;
newLayerPtr = new QgsVectorLayer(layerDef, QStringLiteral("PointLayer"), QStringLiteral("memory"));
}
else if (geomType == QgsWkbTypes::GeometryType::LineGeometry)
{
QString layerDef = "LineString?crs=" + crs;
newLayerPtr = new QgsVectorLayer(layerDef, QStringLiteral("LineLayer"), QStringLiteral("memory"));
}
else if (geomType == QgsWkbTypes::GeometryType::PolygonGeometry)
{
QString layerDef = "MultiPolygon?crs=" + crs;
newLayerPtr = new QgsVectorLayer(layerDef, QStringLiteral("PolyLayer"), QStringLiteral("memory"));
}
if (newLayerPtr == nullptr && !newLayerPtr->isValid())
{
return newLayerPtr;
}
newLayerPtr->commitChanges();
if (!newLayerPtr->isEditable())
{
newLayerPtr->startEditing();
}
QgsFields sourceFlds = fields;
for (int i = 0; i < sourceFlds.size(); i++)
{
newLayerPtr->editBuffer()->addAttribute(sourceFlds[i]);
}
newLayerPtr->commitChanges();
if (!newLayerPtr->isEditable())
{
newLayerPtr->startEditing();
}
newLayerPtr->editBuffer()->addFeatures(features);
bool flag = newLayerPtr->commitChanges();//保存
newLayerPtr->editingStopped();
return newLayerPtr;
}
void ShpTrans::on_pushButton_clicked()
{
// 获取路径
QString inputPath = ui_shp.InputEdit->text();
QString outputPth = ui_shp.OutputEdit->text();
QgsVectorLayer* layer = new QgsVectorLayer(inputPath);
if (layer == nullptr || !layer->isValid())
{
return;
}
//确定原始图层的参考系和目标图层的参考系
QString targetCrsCode = QStringLiteral("EPSG:3857");
QgsCoordinateReferenceSystem shpCrs = layer->crs();//原始图层参考系
QgsCoordinateReferenceSystem targetCrs = QgsCoordinateReferenceSystem(targetCrsCode);//目标图层参考系
//构造坐标转换对象
QString srsDbName = QgsApplication::srsDatabaseFilePath();
QgsCoordinateTransform* pTransform = new QgsCoordinateTransform(shpCrs, targetCrs, QgsProject::instance());
if (pTransform == nullptr || !pTransform->isValid())
{
return;
}
//对图层中每一个要素进行转换,将转换后的结果放入postRansfeatureLIst中
QgsFeatureList postTransfeatureList;
QgsFeatureIterator iter = layer->getFeatures();
QgsFeature feature;
while ((iter.nextFeature(feature)))
{
QgsGeometry g = feature.geometry();
QgsAttributes f= feature.attributes();
//transform函数就是转换函数,将每一个geometry进行转换
//QgsGeometry是基类,transform函数是个虚函数
//根据具体的类型调用各自Geometry中的transfrom
//具体的转换可以参考QgsGeometry的子类的transform
if (g.transform(*pTransform) == 0)
{
feature.setGeometry(g);
}
else
{
feature.clearGeometry();
}
postTransfeatureList << feature;
}
//创建一个新shp,将转换后的postTransfeatureList存入新的shp中
QString errorMessage;
const QString fileFormat = "ESRI Shapefile";
const QString enc = "System";
QgsFields fields = layer->fields();
QgsVectorLayer* targetLayer = createTempLayer(layer->geometryType(), targetCrsCode, fields, postTransfeatureList);
QString errMsg;
QgsVectorFileWriter::SaveVectorOptions saveOptions;
saveOptions.fileEncoding = targetLayer->dataProvider()->encoding();
saveOptions.driverName = "ESRI Shapefile";
QgsVectorFileWriter::WriterError err = QgsVectorFileWriter::writeAsVectorFormatV2(targetLayer, outputPth, targetLayer->transformContext(), saveOptions, nullptr, nullptr, &errMsg);
if (err != QgsVectorFileWriter::WriterError::NoError)
{
return;
}
close();
QMessageBox::information(nullptr, tr("success"), tr("Shp transform success."));
return;
}
void ShpTrans::on_InputpushButton_clicked()
{
QString filePath = QFileDialog::getOpenFileName(nullptr, "Select Shapefile", "", "Shapefiles (*.shp)");
ui_shp.InputEdit->setText(filePath);
}
void ShpTrans::on_OutputpushButton_clicked()
{
QString saveFilePath = QFileDialog::getSaveFileName(nullptr, "Save Shapefile", "", "Shapefiles (*.shp)");
ui_shp.OutputEdit->setText(saveFilePath);
}
2.6 地图视图交互
地图视图交互功能包含地图的平移、放大、缩小及缩放至图层。
运行程序后,点击菜单栏中的“数据->添加矢量数据”以导入演示所需的矢量图层。
图 15 输入数据示意图
单击右侧边栏中的“漫游工具”按钮,启动地图漫游。此时鼠标箭头变为手的形状,在图框内按住鼠标左键拖动,即可实现地图的平移。
图 16 地图平移操作示意图
单击右侧边栏中的“放大”按钮,启动地图放大功能。此时鼠标箭头变为带有+符号的放大镜,在图框内点击任意位置,即可实现以点击位置为中心的地图放大。
图 17 地图放大操作示意图
单击右侧边栏中的“缩小”按钮,启动地图缩小功能。此时鼠标箭头变为带有-符号的放大镜,在图框内点击任意位置,即可实现以点击位置为中心的地图缩小。
图 18 地图缩小操作示意图
单击右侧边栏中的“缩放至图层”按钮,即可将当前地图显示范围调整至可以完整展示各图层内所有要素。
图 19 缩放至图层操作示意图
代码展示:
代码通过创建不同的QgsMapTool对象实现不同的地图操作工具,并在触发相应的动作时通过setMapTool方法切换当前的地图工具。具体而言,代码中创建了平移工具QgsMapToolPan、放大工具QgsMapToolZoom,使用false参数表示放大和缩小工具QgsMapToolZoom,使用true参数表示缩小)的实例。每个工具通过对应的触发器triggered函数来设置为当前的地图工具,从而实现相应的地图交互操作。此外,通过调用zoomToFullExtent方法,实现缩放至图层功能,将地图缩放至包含所有图层的完整范围。
//平移
m_panTool = new QgsMapToolPan(m_mapCanvas);
void DataViewer::on_actionPan_triggered()
{
m_mapCanvas->setMapTool(m_panTool);
}
//放大
m_zoomInTool = new QgsMapToolZoom(m_mapCanvas, false);
void DataViewer::on_actionZoomIn_triggered()
{
m_mapCanvas->setMapTool(m_zoomInTool);
}
//缩小
m_zoomOutTool = new QgsMapToolZoom(m_mapCanvas, true);
void DataViewer::on_actionZoomOut_triggered()
{
m_mapCanvas->setMapTool(m_zoomOutTool);
}
//缩放至图层
void DataViewer::on_actionFullExtent_triggered()
{
m_mapCanvas->zoomToFullExtent();
}