【QGIS二次开发】地图显示与交互-02

发布于:2025-03-05 ⋅ 阅读:(8) ⋅ 点赞:(0)

再续前言:【QGIS二次开发】地图显示与交互-01-CSDN博客


2.4 显示矢量图层注记

任务要求:从矢量图层的属性结构中选择一个属性作为注记,显示在地图视图中。

        完成的逻辑是在QgsLayerTreeViewMenuProvider的实例类中添加一个openLabelsDialog方法,实现矢量图层注记功能。

        代码逻辑如下:

  1. 获取当前选中的图层。如果没有选中的图层,或者选中的图层不是矢量图层,就直接返回。
  2. 创建一个对话框,并设置其布局为垂直布局。
  3. 在对话框中添加一个标签,显示"标注值:"。创建一个标签设置对象,并设置标签可见。
  4. 创建一个字段名下拉菜单,并将图层的所有字段名添加到下拉菜单中。设置下拉菜单为可编辑。
  5. 创建一个按钮,点击该按钮会打开一个表达式对话框。在表达式对话框中调用QgsExpressionBuilderDialog对象,用户可以输入一个表达式,该表达式将被设置为标签字段。
  6. 调用QgsTextFormatWidget对象,创建一个文本格式设置窗口,用于设置标签的文本格式。
  7. 创建一个包含“应用”、“确定”和“取消”按钮的按钮框。点击“确定”或“取消”按钮会关闭对话框。
  8. 连接“应用”按钮的clicked信号到一个槽函数。在这个槽函数中,获取用户选择的字段名或表达式,并将其设置为标签字段。获取文本格式设置窗口的文本格式,并将其设置到标签设置的文本格式中。然后,创建一个标签提供者,并将其设置到图层中。启用图层的标签,并触发图层的重绘。
  9. 显示对话框,并等待用户的操作。如果用户点击了“确定”按钮,就执行和“应用”按钮相同的操作。

实现的效果是,在图层管理器中图层的右键菜单中添加图层标注菜单选项,点击图层标注菜单选项打开对话框,对话框内包含了一个包含了图层所有字段名的可编辑下拉框,一个打开表达式对话框按钮,一个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();  
}  


网站公告

今日签到

点亮在社区的每一天
去签到