QGIS二次开发技术文档

QGIS二次开发技术文档

目录

  1. 工程概述
  2. 开发环境搭建与项目配置
  3. QGIS初始化与核心架构
  4. 地图画布(QgsMapCanvas)(#4-地图画布qgsmapcanvas)
  5. 打开矢量图层
  6. 打开栅格图层
  7. 坐标测量与显示
  8. 测量面积与距离
  9. 跳转到指定坐标
  10. [波段组合(Band Combination)](#波段组合(Band Combination))
  11. 图层树与项目管理
  12. 状态栏定制
  13. Ribbon风格界面集成
  14. 常见问题与注意事项
  15. 总结与最佳实践

1. 工程概述

1.1 工程背景

本工程QtGisDemo是一个基于 QGIS(Quantum GIS)C++ API 进行二次开发的桌面GIS应用程序。工程包含两个主要部分:

1.一个轻量级的QGIS二次开发演示项目,演示了包括打开栅格/矢量数据、坐标显示、比例尺控制、放大镜、图层树等核心功能,采用了Office风格的Ribbon界面(QtitanRibbon库)。

2.一个更复杂的卫星遥感数据处理平台,包含辐射定标(Radiometric Calibration)、几何校正(Geometric Correction)、MTF(Modulation Transfer Function)计算、影像配准(Registration)、信噪比计算(SNR)等专业的遥感数据处理功能。

1.2 技术栈

技术 用途
C++ 17 主开发语言
Qt 5.12+ GUI框架
QGIS 3.x C++ API(qgis_core.dll, qgis_gui.dll) GIS核心功能库
GDAL 3.0.4 栅格/矢量数据读写
QtitanRibbon 5.x Office风格Ribbon界面
Qwt 数据可视化组件
Visual Studio 2017/2019 开发与编译环境
qmake 构建系统

1.3 核心依赖库

运行时所需的DLL文件清单(位于x64/Release目录下):

  • QGIS核心库qgis_core.dllqgis_gui.dllqgis_native.dll

  • GDAL/OGR库gdal304.dll

  • PROJ库proj_7_2.dllproj_8_2.dll

  • GEOS库geos.dllgeos_c.dll

  • 其他spatialite.dllsqlite3.dlllibxml2.dllzlib.dll


2. 开发环境搭建与项目配置

2.1 QGIS SDK编译与引用

QGIS二次开发的第一步是获取QGIS SDK。开发者需要自行编译QGIS源码或使用OSGeo4W安装的SDK。本工程采用了独立编译的QGIS核心库 ,直接链接到 qgis_core.libqgis_gui.lib

关键配置要点:

  1. 包含目录(Include Directories) :指向QGIS SDK头文件目录,如 qgis_core/includeqgis_gui/include
  2. 库目录(Library Directories) :指向QGIS编译后的 .lib 文件目录
  3. 预处理器定义 :需定义 GUI_EXPORT=CORE_EXPORT= 用于导出类的引用
  4. QGIS插件目录:运行时需设置QGIS插件搜索路径

2.3 运行时环境设置

运行编译后的程序需要保证:

  • 所有依赖DLL位于可执行文件目录或系统PATH中
  • GDAL的数据文件路径(GDAL_DATA)需要正确设置
  • PROJ的proj.db路径需要正确
  • QGIS的插件、样式、SVG数据文件路径

3. QGIS初始化与核心架构

3.1 QGIS Application初始化

main.cpp 中,QGIS二次开发的初始化流程是固定的:

cpp 复制代码
#include "qgsapplication.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QString appDir = QCoreApplication::applicationDirPath();
    
    QCoreApplication::setOrganizationName("QtQgis");
    QCoreApplication::setApplicationName("QtQgis Application");
    
    // ★★ 核心初始化步骤 ★★
    QgsApplication::init(appDir);      // 初始化QGIS应用路径
    QgsApplication::initQgis();        // 初始化QGIS核心服务(数据提供者、坐标参考系统等)
    QgsApplication::setMaxThreads(8);  // 设置最大线程数(并行渲染)
    
    QtGis w;
    w.showMaximized();
    return a.exec();
}

注意事项

  • QgsApplication::initQgis() 必须在创建任何 QgsMapCanvas 之前调用
  • 它初始化了GDAL、OGR、PROJ等底层库,并注册了所有数据提供者
  • setMaxThreads 控制并行渲染的线程数量,需根据CPU核心数设置
  • QGIS的初始化必须在 QApplication 对象创建之后进行

3.2 项目对象(QgsProject)

QgsProject 是QGIS的项目级单例,管理所有图层、坐标参考系统(CRS)、项目设置等:

cpp 复制代码
// 设置项目CRS
QgsProject::instance()->setCrs(QgsCoordinateReferenceSystem("EPSG:4326"));
// 标记项目为已修改
QgsProject::instance()->setDirty(true);
// 获取图层树根节点
QgsProject::instance()->layerTreeRoot();

3.3 数据提供者机制

QGIS采用插件式数据提供者 架构,通过 QgsProviderRegistry 单例管理所有数据驱动:

  • GDAL :栅格数据提供者("gdal"),支持GeoTIFF、IMG、NetCDF等
  • OGR :矢量数据提供者("ogr"),支持Shapefile、GeoJSON、KML等
  • 内存提供者 :临时数据层("memory"

4. 地图画布(QgsMapCanvas)

QgsMapCanvas 是QGIS应用的核心可视组件,负责地图的渲染、交互、缩放、平移等操作。

4.1 创建与配置

cpp 复制代码
// 创建地图画布
mMapCanvas = new QgsMapCanvas();

// 基本配置
mMapCanvas->setPreviewJobsEnabled(true);        // 启用预览任务(渲染优化)
mMapCanvas->setCanvasColor(QColor(255, 255, 255)); // 画布背景色(白色)
mMapCanvas->setSelectionColor(QColor(0, 255, 255)); // 选择要素高亮色(青色)
mMapCanvas->setProject(QgsProject::instance()); // 关联项目对象
mMapCanvas->setParallelRenderingEnabled(true);   // 启用并行渲染
mMapCanvas->setCachingEnabled(true);             // 启用缓存(提升平移缩放性能)
mMapCanvas->setMapUpdateInterval(250);           // 更新间隔(毫秒)

4.2 与TabWidget集成

工程将画布放置于 QTabWidget 中,支持多视图管理:

cpp 复制代码
mCenterTabWidget = new QTabWidget(this);
mCenterTabWidget->addTab(mMapCanvas, "Map View");
// 移除Tab右侧的图标按钮
mCenterTabWidget->tabBar()->setTabButton(
    mCenterTabWidget->indexOf(mMapCanvas), 
    QTabBar::RightSide, nullptr);
setCentralWidget(mCenterTabWidget);

4.3 画布冻结与刷新机制

为了提高批量操作的性能,工程实现了画布冻结(freeze)机制:

cpp 复制代码
// 冻结所有画布,防止频繁重绘
void QtGis::freezeCanvases(bool frozen) {
    const auto canvases = mapCanvases();
    for (QgsMapCanvas* canvas : canvases) {
        canvas->freeze(frozen);
    }
}

// 刷新所有画布
void QtGis::refreshMapCanvas(bool redrawAllLayers) {
    const auto canvases = mapCanvases();
    for (QgsMapCanvas* canvas : canvases) {
        canvas->stopRendering();
        if (redrawAllLayers)
            canvas->refreshAllLayers();
        else
            canvas->refresh();
    }
}

RAII冻结机制QgsCanvasRefreshBlocker):

cpp 复制代码
class QgsCanvasRefreshBlocker {
    // 构造时冻结,析构时解冻
    // 支持嵌套使用,只有最外层的析构才触发解冻刷新
};

这种设计模式在大批量数据加载时非常有用------避免每添加一个图层就触发一次重绘。


5. 打开矢量图层

5.1 功能入口与UI

栅格和矢量图层的打开分别通过Ribbon工具栏的"Add Raster Layer"和"Add Vector Layer"按钮触发。用户点击后,弹出文件选择对话框。

5.2 打开矢量图层的核心代码

矢量图层的打开通过OGR数据提供者实现,代码位于 QLayerCommand.cpp

cpp 复制代码
bool QtGis::addVectorLayer(const QStringList& layerFiles) {
    // 冻结画布,防止逐个添加图层时频繁重绘
    QgsCanvasRefreshBlocker refreshBlocker;
    QList<QgsMapLayer*> layersToAdd;

    for (QString src : layerFiles) {
        src = src.trimmed();
        QFileInfo fi(src);
        QString baseName = fi.completeBaseName();
        
        // 创建矢量图层
        QgsVectorLayer::LayerOptions options{ 
            QgsProject::instance()->transformContext() 
        };
        options.loadDefaultStyle = false;  // 不加载默认样式
        
        QgsVectorLayer* layer = new QgsVectorLayer(
            src,           // 文件路径
            baseName,      // 图层名称(使用文件名)
            QStringLiteral("ogr"),  // 数据提供者:OGR
            options        // 图层选项
        );

        if (layer != nullptr && layer->isValid()) {
            layersToAdd << layer;
        } else {
            delete layer;
            // 弹出错误提示
            QMessageBox::warning(this, tr("Invalid Data Source"), 
                tr("%1 is not a valid or recognized data source.").arg(src));
        }
    }

    if (layersToAdd.size() > 0) {
        // 批量添加到项目
        QgsProject::instance()->addMapLayers(layersToAdd);
        markDirty();  // 标记项目已修改
    }
    return true;
}

5.3 文件过滤设置

cpp 复制代码
void QtGis::slot_addVectorLayer() {
    QStringList layerFiles;
    QString e;
    QString title = tr("Open Vector Layer");
    // 支持多种矢量格式
    QString vectorFileFilter = "OGR Layer(*.shp *.geojson *.gmt);; KML(*.kml)";
    QgsGuiUtils::openFilesRememberingFilter(
        QStringLiteral("lastRasterFileFilter"), 
        vectorFileFilter, layerFiles, e, title);
    if (layerFiles.isEmpty())
        return;
    addVectorLayer(layerFiles);
}

5.4 技术要点

  1. QgsVectorLayer 构造参数

    • 第一个参数(source):可以是文件路径、数据库连接字符串、WMS/WFS URL等
    • 第二个参数(name):图层的显示名称
    • 第三个参数(providerType):数据提供者类型,矢量通常为 "ogr"
    • 第四参数(options):LayerOptions 包含变换上下文、样式加载选项等
  2. LayerOptions 说明

    • transformContext:坐标变换上下文,来自项目对象
    • loadDefaultStyle:是否加载QML样式文件,设为 false 可避免加载不存在的样式文件导致的警告
  3. 支持的矢量数据源

    • Shapefile (.shp)
    • GeoJSON (.geojson)
    • GMT格式 (.gmt)
    • KML (.kml)
    • 通过GDAL/OGR还可支持GeoPackage、DWG/DXF、PostGIS、SpatiaLite等
  4. 注意事项

    • 必须通过 isValid() 验证图层是否成功创建
    • 返回 nullptr 或无效图层时需 delete 释放内存
    • 使用 QgsCanvasRefreshBlocker 防止多次重绘,提升性能

6. 打开栅格图层

6.1 实现原理

栅格图层的打开通过GDAL数据提供者实现,代码同样位于 QLayerCommand.cpp

cpp 复制代码
bool QtGis::addRaterLayer(const QStringList& layerFiles) {
    if (layerFiles.empty()) return false;

    QgsCanvasRefreshBlocker refreshBlocker;
    bool returnValue = true;
    QList<QgsMapLayer*> myList;

    for (auto myIterator = layerFiles.begin(); 
         myIterator != layerFiles.end(); ++myIterator) {
        QString errMsg;
        bool ok = false;

        // 检查是否为有效的栅格文件名
        if (QgsRasterLayer::isValidRasterFileName(*myIterator, errMsg)) {
            QFileInfo myFileInfo(*myIterator);
            QString layerName = myFileInfo.completeBaseName();

            // 从URI中解码更精确的图层名
            const QVariantMap uriDetails = QgsProviderRegistry::instance()
                ->decodeUri(QStringLiteral("gdal"), *myIterator);
            if (!uriDetails[QStringLiteral("layerName")].toString().isEmpty()) {
                layerName = uriDetails[QStringLiteral("layerName")].toString();
            }

            // 创建栅格图层
            QgsRasterLayer* layer = new QgsRasterLayer(
                *myIterator, layerName, QStringLiteral("gdal"));
            if (layer && layer->isValid()) {
                myList << layer;
                ok = true;
            }
        } else {
            ok = false;
            QString msg = tr("%1 is not a supported raster data source").arg(*myIterator);
            if (!errMsg.isEmpty())
                msg += '\n' + errMsg;
            QMessageBox::warning(this, "Unsupported Data Source", msg);
        }
        if (!ok) returnValue = false;
    }
    QgsProject::instance()->addMapLayers(myList);
    markDirty();
    return returnValue;
}

6.2 文件过滤设置

cpp 复制代码
void QtGis::slot_addRaterLayer() {
    QStringList layerFiles;
    QString e;
    QString title = tr("Open Raster Layer");
    // 支持多种栅格格式
    QString rasterFileFilter = 
        "All supported(*.tif *.img *.hgt *.grd);;"
        "GeoTIFF(*.tif *.tiff *.TIF *.TIFF);;"
        "Erdas Imagine Images(*.img *.IMG);;"
        "NetCDF(*.grd *.GRD);;"
        "STRMHGT File(*.hgt *.HGT)";
    QgsGuiUtils::openFilesRememberingFilter(
        QStringLiteral("lastRasterFileFilter"), 
        rasterFileFilter, layerFiles, e, title);
    if (layerFiles.isEmpty())
        return;
    addRaterLayer(layerFiles);
}

6.3 技术要点

  1. QgsRasterLayer::isValidRasterFileName():这是QGIS提供的静态验证方法,在执行文件打开前先检查文件是否为QGIS可识别的栅格格式,避免无效操作。

  2. QgsProviderRegistry::decodeUri():通过GDAL提供者解码文件URI,可以从某些复合格式(如NetCDF的多波段文件)中提取更精确的图层名称。

  3. 栅格图层名称的处理

    • 默认使用文件的 completeBaseName()(不带扩展名)
    • 对于HDF4、NetCDF等复合格式,decodeUri 可以提取内部子数据集名称
  4. 支持的栅格数据源

    • GeoTIFF (.tif, .tiff)
    • Erdas Imagine (.img)
    • NetCDF (.grd)
    • SRTM HGT (.hgt)
    • 通过GDAL还可支持JPEG2000、ENVI、HDF4/5、GRIB等几乎所有的遥感栅格格式
  5. 与矢量图层的区别

    • 栅格使用 QgsRasterLayer 类,矢量使用 QgsVectorLayer
    • 栅格的数据提供者为 "gdal",矢量为 "ogr"
    • 栅格需要先通过 isValidRasterFileName 检查,矢量则直接构建并检查有效性

7. 坐标测量与显示

7.1 功能概述

坐标显示是GIS应用中最重要的交互功能之一。工程通过 QgsStatusBarCoordinatesWidget 实现了以下功能:

  1. 鼠标追踪坐标显示:实时显示鼠标在地图画布上的地理坐标
  2. 视图范围显示:显示当前地图视图的经纬度范围
  3. 坐标有效性验证:用户可手动输入坐标并验证格式
  4. 跳转到坐标:输入坐标后按回车,地图中心跳转到指定位置

7.2 核心实现

QgsStatusBarCoordinatesWidget 通过信号-槽机制与画布交互:

cpp 复制代码
void QgsStatusBarCoordinatesWidget::setMapCanvas(QgsMapCanvas *mapCanvas) {
    // 断开旧的连接
    if (mMapCanvas) {
        disconnect(mMapCanvas, &QgsMapCanvas::xyCoordinates, this, 
                   &QgsStatusBarCoordinatesWidget::showMouseCoordinates);
        disconnect(mMapCanvas, &QgsMapCanvas::extentsChanged, this, 
                   &QgsStatusBarCoordinatesWidget::showExtent);
    }
    mMapCanvas = mapCanvas;
    
    // 连接信号:鼠标移动时更新坐标
    connect(mMapCanvas, &QgsMapCanvas::xyCoordinates, this, 
            &QgsStatusBarCoordinatesWidget::showMouseCoordinates);
    // 连接信号:视图范围变化时更新
    connect(mMapCanvas, &QgsMapCanvas::extentsChanged, this, 
            &QgsStatusBarCoordinatesWidget::showExtent);
}

7.3 鼠标坐标实时显示

cpp 复制代码
void QgsStatusBarCoordinatesWidget::showMouseCoordinates(const QgsPointXY &p) {
    if (!mMapCanvas || mToggleExtentsViewButton->isChecked())
        return;

    // 使用项目设置格式化坐标(可配置经纬度显示格式)
    mLineEdit->setText(QgsCoordinateUtils::formatCoordinateForProject(
        QgsProject::instance(), p, 
        mMapCanvas->mapSettings().destinationCrs(),
        mMousePrecisionDecimalPlaces));
    
    ensureCoordinatesVisible();  // 自动调整文本框宽度
}

关键API说明

  • QgsMapCanvas::xyCoordinates 信号 :鼠标在画布上移动时连续发射,参数为鼠标位置的地理坐标 QgsPointXY
  • QgsCoordinateUtils::formatCoordinateForProject():根据项目设置格式化坐标字符串,支持多种显示格式(DD、DMS、DMM等)
  • mMapCanvas->mapSettings().destinationCrs():获取画布当前的坐标系

7.4 视图范围显示

cpp 复制代码
void QgsStatusBarCoordinatesWidget::extentsViewToggled(bool flag) {
    if (flag) {
        // 切换到视图范围显示模式
        mToggleExtentsViewButton->setIcon(
            QgsApplication::getThemeIcon(QStringLiteral("extents.svg")));
        mLineEdit->setReadOnly(true);
        showExtent();
    } else {
        // 切换到鼠标坐标模式
        mToggleExtentsViewButton->setIcon(
            QgsApplication::getThemeIcon(QStringLiteral("tracking.svg")));
        mLineEdit->setReadOnly(false);
        mLabel->setText(tr("Coordinate"));
    }
}

void QgsStatusBarCoordinatesWidget::showExtent() {
    if (!mToggleExtentsViewButton->isChecked()) return;
    QgsRectangle myExtents = mMapCanvas->extent();
    mLabel->setText(tr("Extents"));
    mLineEdit->setText(myExtents.toString(true));  // 格式化显示
}

7.5 坐标输入验证

坐标输入框使用正则表达式进行验证,允许带符号的小数坐标:

cpp 复制代码
QRegExp coordValidator("[+-]?\\d+\\.?\\d*\\s*,\\s*[+-]?\\d+\\.?\\d*");
mCoordsEditValidator = new QRegExpValidator(coordValidator, this);
mLineEdit->setToolTip(tr("Current map coordinate (longitude,latitude or east,north)"));

支持的输入格式:

  • 119.5, 32.8(经度在前,纬度在后,逗号分隔)
  • 119.5 32.8(空格分隔)

7.6 注意事项

  1. 坐标精度控制 :通过 setMouseCoordinatesPrecision() 可设置小数位数,默认为自动
  2. 文本自适应宽度ensureCoordinatesVisible() 方法根据文本长度动态调整输入框宽度
  3. 坐标系感知:坐标值始终以画布当前的目标坐标系显示,默认为 WGS84(EPSG:4326)

8. 测量面积与距离

8.1 使用QGIS原生的测量工具

QGIS提供了 QgsMeasureTool 及其子类,可直接用于距离和面积的测量:

cpp 复制代码
// 距离测量工具
#include "qgsmeasuretool.h"

// 创建距离测量工具
QgsMeasureTool* distanceTool = new QgsMeasureTool(
    mMapCanvas, 
    QgsMeasureTool::MeasureLine  // 距离测量模式
);
mMapCanvas->setMapTool(distanceTool);

// 面积测量工具
QgsMeasureTool* areaTool = new QgsMeasureTool(
    mMapCanvas,
    QgsMeasureTool::MeasureArea  // 面积测量模式
);
mMapCanvas->setMapTool(areaTool);

8.2 自定义测量实现方案

如果需要自定义测量交互,可以使用 QgsMapTool 基类派生新的测量工具:

基本架构

cpp 复制代码
class CustomMeasureTool : public QgsMapTool {
    Q_OBJECT
public:
    CustomMeasureTool(QgsMapCanvas* canvas) 
        : QgsMapTool(canvas) {}
    
    // 鼠标点击事件 - 添加测量点
    virtual void canvasPressEvent(QgsMapMouseEvent* e) override;
    // 鼠标移动事件 - 实时更新测量线
    virtual void canvasMoveEvent(QgsMapMouseEvent* e) override;
    // 右键取消/完成测量
    
private:
    QList<QgsPointXY> mPoints;  // 测量点列表
    double calculateDistance();   // 计算距离
    double calculateArea();      // 计算面积(多边形)
};

距离计算 使用 QgsDistanceArea 类:

cpp 复制代码
QgsDistanceArea da;
da.setSourceCrs(mMapCanvas->mapSettings().destinationCrs(), 
                QgsProject::instance()->transformContext());
da.setEllipsoid(QgsProject::instance()->ellipsoid());

// 计算两点间的椭球距离
double distance = da.measureLine(p1, p2);  // 返回米

// 计算多边形面积
double area = da.measurePolygon(points);   // 返回平方米

8.4 关键技术参数

  • QgsMeasureTool 配置:支持度量单位配置(米、千米、英尺、英里等)
  • 椭球体设置:面积和距离的精确计算依赖于椭球体参数的设置
  • 结果显示:可以在状态栏或浮动标签中实时显示测量结果
  • 标注 :可以使用 QgsAnnotation 在画布上标注测量数值

8.5 注意事项

  1. 测量工具是模态的,激活后接管鼠标事件,需要显式切换回默认的平移/缩放工具
  2. 高精度测量(特别是面积)需要正确设置椭球体和坐标系
  3. 对于大范围测量,应考虑地图投影变形的影响
  4. 距离测量支持测地线(大圆路径)和欧氏距离两种模式

9. 跳转到指定坐标

9.1 功能描述

跳转到指定坐标功能允许用户在坐标输入框中输入经纬度坐标,按回车后地图画布自动将视图中心移动到该坐标位置。

9.2 核心实现

cpp 复制代码
void QgsStatusBarCoordinatesWidget::validateCoordinates() {
    if (!mMapCanvas) return;
    
    // (省略彩蛋处理代码...)
    
    bool xOk = false, yOk = false;
    double x = 0., y = 0.;
    QString coordText = mLineEdit->text();
    coordText.replace(QRegExp(" {2,}"), QStringLiteral(" "));

    // 尝试逗号分隔解析
    QStringList parts = coordText.split(',');
    if (parts.size() == 2) {
        x = parts.at(0).toDouble(&xOk);
        y = parts.at(1).toDouble(&yOk);
    }
    
    // 尝试空格分隔解析
    if (!xOk || !yOk) {
        parts = coordText.split(' ');
        if (parts.size() == 2) {
            x = parts.at(0).toDouble(&xOk);
            y = parts.at(1).toDouble(&yOk);
        }
    }

    if (!xOk || !yOk) return;

    // ★★ 核心功能:设置画布中心点并刷新 ★★
    mMapCanvas->setCenter(QgsPointXY(x, y));
    mMapCanvas->refresh();
}

9.3 技术要点

  1. QgsMapCanvas::setCenter():将画布视图中心移动到指定的地理坐标点。坐标值使用画布当前的目标坐标系。

  2. 坐标输入解析

    • 支持 "经度,纬度"(逗号分隔,如:119.5,32.8
    • 支持 "经度 纬度"(空格分隔,如:119.5 32.8
    • 自动去除多余空格
    • 支持带符号和带小数点的数值
  3. 坐标系一致性:输入的坐标应与画布的目标坐标系一致。如果画布使用WGS84(EPSG:4326),则需要输入十进制度数。如果使用投影坐标系,则需要输入投影坐标值。

9.4 扩展应用:跳转到指定XY

如果需要支持从其他UI控件(如独立的输入对话框)跳转到坐标,可以封装为独立方法:

cpp 复制代码
void QtGis::goToCoordinate(double x, double y) {
    if (!mMapCanvas) return;
    mMapCanvas->setCenter(QgsPointXY(x, y));
    mMapCanvas->refresh();
    // 可选:设置缩放级别
    // mMapCanvas->zoomScale(10000);  // 1:10000
}

9.5 注意事项

  1. 坐标顺序 :在GIS领域,坐标通常以 (X, Y) 表示,在国际标准中对应 (经度, 纬度)。但部分用户习惯 (纬度, 经度) 格式,需要在UI中明确提示。

  2. 有效性检查:对于WGS84坐标,经度范围为 -180° 到 180°,纬度范围为 -90° 到 90°,需要添加范围验证。

  3. 边界处理:当输入的坐标超出当前图层范围时,画布会显示空白区域。建议判断坐标是否在合理范围内。


10. 波段组合(Band Combination)

10.1 功能概述

波段组合是遥感图像处理中的核心功能,用于将多波段栅格数据的任意三个波段分别映射到RGB三个颜色通道。根据 TODO.txt 的功能规划,波段组合功能位于 "Home > Bands > Band Combinations" 和 "Red Green Blue"。

10.2 实现原理

在QGIS中,多波段栅格图层的显示是通过 QgsRasterRenderer 及其子类管理的。默认情况下,多波段数据的前三个波段分别映射到RGB通道(RGB合成)。要改变波段组合,需要修改渲染器设置。

核心流程

cpp 复制代码
#include "qgsrasterlayer.h"
#include "qgsmultibandcolorrenderer.h"
#include "qgsrasterrenderer.h"

void setBandCombination(QgsRasterLayer* layer, 
                         int redBand, int greenBand, int blueBand) {
    if (!layer || !layer->isValid()) return;
    
    // 获取当前渲染器
    QgsRasterRenderer* renderer = layer->renderer();
    
    // 创建多波段彩色渲染器
    QgsMultiBandColorRenderer* rgbRenderer = 
        new QgsMultiBandColorRenderer(
            layer->dataProvider(),        // 数据提供者
            redBand,                      // 红色波段索引
            greenBand,                    // 绿色波段索引
            blueBand,                     // 蓝色波段索引
            QgsContrastEnhancement::StretchToMinimumMaximum  // 对比度增强
        );
    
    // 设置渲染器到图层
    layer->setRenderer(rgbRenderer);
    
    // 触发图层重绘
    layer->triggerRepaint();
}

10.3 获取波段信息

在设置波段组合前,通常需要获取栅格数据的波段信息供用户选择:

cpp 复制代码
void getBandInfo(QgsRasterLayer* layer) {
    if (!layer || !layer->isValid()) return;
    
    QgsRasterDataProvider* provider = layer->dataProvider();
    int bandCount = provider->bandCount();  // 波段总数
    
    for (int band = 1; band <= bandCount; band++) {
        // 获取波段名称
        QString bandName = provider->generateBandName(band);
        
        // 获取波段数据类型
        Qgis::DataType dataType = provider->dataType(band);
        
        // 获取波段统计信息(最小值、最大值、均值、标准差)
        QgsRasterBandStats stats = provider->bandStatistics(band);
        double minVal = stats.minimumValue;
        double maxVal = stats.maximumValue;
        double meanVal = stats.mean;
        
        // TODO: 显示到UI组件供用户选择
    }
}

10.4 Raster Band Stats计算

cpp 复制代码
// 获取波段统计信息(需要先设置感兴趣的统计项)
QgsRasterBandStats stats = provider->bandStatistics(
    band,                    // 波段序号
    QgsRasterBandStats::Min | QgsRasterBandStats::Max | 
    QgsRasterBandStats::Mean | QgsRasterBandStats::StdDev,
    QgsRectangle(),          // 感兴趣区域(空表示全图)
    0                        // 采样大小(0表示全分辨率)
);

10.5 对比度增强

QgsContrastEnhancement 提供了多种对比度增强方式,用于优化显示效果:

  • StretchToMinimumMaximum:拉伸到最小最大值
  • StretchAndClipToMinimumMaximum:拉伸并裁剪
  • ClipToMinimumMaximum:裁剪到最小最大值
  • NoEnhancement:不做增强
cpp 复制代码
// 设置每个波段的对比度增强
QgsContrastEnhancement* redEnhancement = new QgsContrastEnhancement(
    Qgis::Float64  // 数据类型
);
redEnhancement->setMinimumValue(minRed);
redEnhancement->setMaximumValue(maxRed);
redEnhancement->setContrastEnhancementAlgorithm(
    QgsContrastEnhancement::StretchToMinimumMaximum);

rgbRenderer->setRedContrastEnhancement(redEnhancement);

10.6 单波段伪彩色渲染

除了RGB组合,单波段数据还可以使用伪彩色渲染(如NDVI显示):

cpp 复制代码
#include "qgssinglebandpseudocolorrenderer.h"

void setPseudoColorRenderer(QgsRasterLayer* layer, int band) {
    // 创建伪彩色着色器
    QgsColorRampShader* shader = new QgsColorRampShader();
    shader->setColorRampType(QgsColorRampShader::Interpolated);
    
    // 添加颜色节点
    shader->setColorRampItemList({
        QgsColorRampShader::ColorRampItem(0, QColor(255, 0, 0), "Low"),
        QgsColorRampShader::ColorRampItem(128, QColor(255, 255, 0), "Medium"),
        QgsColorRampShader::ColorRampItem(255, QColor(0, 255, 0), "High")
    });
    
    QgsRasterShader* rasterShader = new QgsRasterShader();
    rasterShader->setRasterShaderFunction(shader);
    
    QgsSingleBandPseudoColorRenderer* renderer = 
        new QgsSingleBandPseudoColorRenderer(
            layer->dataProvider(), band, rasterShader);
    
    layer->setRenderer(renderer);
    layer->triggerRepaint();
}

10.7 注意事项

  1. 波段编号从1开始:不同于C++数组的索引从0开始,QGIS的波段编号从1开始
  2. 渲染器所有权 :将渲染器设置到图层后,图层会接管渲染器的所有权,不要再手动删除
  3. 触发重绘 :修改渲染器后必须调用 triggerRepaint()refresh() 才能看到效果
  4. 数据类型:不同卫星传感器的波段数量和数据格式不同,需要动态获取

11. 图层树与项目管理

11.1 功能概述

图层树(Layer Tree)是QGIS应用中最核心的UI组件之一,负责管理所有已加载的图层,包括图层的增删、排序、可见性切换、分组管理、右键菜单等操作。在本工程中,图层树通过 QgsLayerTreeView 结合 QgsLayerTreeModel 实现,并通过 QgsLayerTreeMapCanvasBridge 与地图画布联动。

11.2 核心实现

11.2.1 图层树视图创建
cpp 复制代码
void QtGis::createLayerTreeView()
{
    // 新建图层树控件
    mLayerTreeView = new QgsLayerTreeView(this);
    mLayerTreeView->setObjectName(QStringLiteral("theLayerTreeView"));

    // 创建QgsLayerTreeModel实例,绑定项目图层根节点
    QgsLayerTreeModel* model = new QgsLayerTreeModel(
        QgsProject::instance()->layerTreeRoot(), this);
    
    // 设置模型标志位
    model->setFlag(QgsLayerTreeModel::AllowNodeChangeVisibility);
    model->setFlag(QgsLayerTreeModel::AllowNodeReorder);
    model->setFlag(QgsLayerTreeModel::AllowNodeRename);
    model->setFlag(QgsLayerTreeModel::ShowLegendAsTree);
    model->setFlag(QgsLayerTreeModel::UseEmbeddedWidgets);
    model->setFlag(QgsLayerTreeModel::UseTextFormatting);
    model->setAutoCollapseLegendNodes(10);

    // 视图绑定模型
    mLayerTreeView->setModel(model);

    // 创建画布桥接------连接图层树与地图画布
    mLayerTreeCanvasBridge = new QgsLayerTreeMapCanvasBridge(
        QgsProject::instance()->layerTreeRoot(), mMapCanvas, this);
}
11.2.2 图层树工具栏
cpp 复制代码
// 添加群组
QAction* addGroup = new QAction(tr("Add Group"), this);
addGroup->setIcon(createIcon(Add_Group));
connect(addGroup, &QAction::triggered, 
    mLayerTreeView->defaultActions(), &QgsLayerTreeViewDefaultActions::addGroup);

// 展开全部
QAction* actionExpandAll = new QAction(tr("Expand All"), this);
actionExpandAll->setIcon(createIcon(Expand_All));
connect(actionExpandAll, &QAction::triggered, 
    mLayerTreeView, &QgsLayerTreeView::expandAllNodes);

// 折叠全部
QAction* actionCollapseAll = new QAction(tr("Collapse All"), this);
actionCollapseAll->setIcon(createIcon(Collapse_All));
connect(actionCollapseAll, &QAction::triggered, 
    mLayerTreeView, &QgsLayerTreeView::collapseAllNodes);
11.2.3 右键菜单提供者

图层树的右键菜单通过 QgsLayerTreeViewMenuProvider 接口实现自定义上下文菜单:

cpp 复制代码
QMenu* QtGisLayerTreeViewMenuProvider::createContextMenu()
{
    QMenu* menu = new QMenu;
    QModelIndex index = mView->currentIndex();
    if (!index.isValid()) return nullptr;
    
    QgsLayerTreeNode* node = mView->index2node(index);
    
    if (QgsLayerTree::isGroup(node)) {
        // 群组节点菜单
        menu->addAction(mView->defaultActions()->actionRemoveGroupOrLayer(menu));
        menu->addAction(mView->defaultActions()->actionRenameGroupOrLayer(menu));
    }
    else if (QgsLayerTree::isLayer(node)) {
        QgsMapLayer* layer = QgsLayerTree::toLayer(node)->layer();
        if (layer) {
            menu->addAction(mView->defaultActions()->actionZoomToLayer(mMapCanvas, menu));
            menu->addAction(mView->defaultActions()->actionRemoveGroupOrLayer(menu));
            menu->addAction(mView->defaultActions()->actionRenameGroupOrLayer(menu));
        }
        // 矢量图层额外显示要素计数
        QgsVectorLayer* vLayer = qobject_cast<QgsVectorLayer*>(layer);
        if (vLayer) {
            menu->addAction(mView->defaultActions()->actionShowFeatureCount(menu));
        }
    }
    return menu;
}

11.3 图层自动选择与插入点管理

cpp 复制代码
// 新增图层时自动选中该图层
void QtGis::autoSelectAddedLayer(QList<QgsMapLayer*> layers)
{
    if (!layers.isEmpty()) {
        QgsLayerTreeLayer* nodeLayer = 
            QgsProject::instance()->layerTreeRoot()->findLayer(layers[0]->id());
        if (!nodeLayer) return;
        auto index = mLayerTreeView->layerTreeModel()->node2index(nodeLayer);
        mLayerTreeView->setCurrentIndex(index);
    }
}

// 更新新图层的插入位置
void QtGis::updateNewLayerInsertionPoint()
{
    QgsLayerTreeGroup* insertGroup = mLayerTreeView->layerTreeModel()->rootGroup();
    QModelIndex current = mLayerTreeView->currentIndex();
    int index = 0;
    QgsLayerTreeRegistryBridge::InsertionPoint insertionPoint(insertGroup, index);

    if (current.isValid()) {
        index = current.row();
        QgsLayerTreeNode* currentNode = mLayerTreeView->currentNode();
        if (currentNode) {
            if (QgsLayerTree::isGroup(currentNode)) {
                // 插入到群组中
                insertGroup = QgsLayerTreeUtils::firstGroupWithoutCustomProperty(
                    QgsLayerTree::toGroup(currentNode), QStringLiteral("embedded"));
                insertionPoint = QgsLayerTreeRegistryBridge::InsertionPoint(insertGroup, 0);
            } else {
                // 插入到当前节点前
                QgsLayerTreeNode* parentNode = currentNode->parent();
                if (QgsLayerTree::isGroup(parentNode)) {
                    QgsLayerTreeGroup* parentGroup = QgsLayerTree::toGroup(parentNode);
                    insertGroup = QgsLayerTreeUtils::firstGroupWithoutCustomProperty(
                        parentGroup, QStringLiteral("embedded"));
                    if (parentGroup != insertGroup) index = 0;
                }
                insertionPoint = QgsLayerTreeRegistryBridge::InsertionPoint(insertGroup, index);
            }
        }
    }
    QgsProject::instance()->layerTreeRegistryBridge()->setLayerInsertionPoint(insertionPoint);
}

11.4 技术要点

  1. QgsLayerTreeModel 标志位 :通过 setFlag() 控制模型行为,AllowNodeChangeVisibility 启用复选框切换可见性,AllowNodeReorder 允许拖拽排序,AllowNodeRename 允许重命名。

  2. QgsLayerTreeMapCanvasBridge:是连接图层管理和地图显示的核心桥梁,自动将图层树中的添加/删除/顺序变化同步到画布。

  3. QgsLayerTreeViewDefaultActions :提供常见操作的预置Action,包括缩放至图层(actionZoomToLayer)、移除图层(actionRemoveGroupOrLayer)、重命名(actionRenameGroupOrLayer)、显示要素计数(actionShowFeatureCount)等。

  4. QgsDockWidget 停靠布局 :图层树停靠在左侧(Qt::LeftDockWidgetArea),属性面板停靠在右侧(Qt::RightDockWidgetArea)。


12. QGIS初始化与运行时环境

12.1 工程入口与初始化流程

cpp 复制代码
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QString appDir = QCoreApplication::applicationDirPath();
    
    QCoreApplication::setOrganizationName("QtQgis");
    QCoreApplication::setApplicationName("QtQgis Application");

    // ★★ 核心:初始化QGIS环境 ★★
    QgsApplication::init(appDir);       // 设置QGIS路径
    QgsApplication::initQgis();         // 初始化QGIS核心服务
    QgsApplication::setMaxThreads(8);   // 设置最大渲染线程数

    QtGis w;
    w.showMaximized();
    return a.exec();
}

12.2 QgsApplication::initQgis() 的作用

initQgis() 是QGIS二次开发中必不可少的初始化调用,它完成以下工作:

初始化项 说明
GDAL/OGR 驱动注册 注册所有支持的栅格/矢量格式驱动
PROJ 库初始化 初始化坐标参考系统支持
QGIS 插件路径设置 设置插件搜索路径
SVG 渲染库初始化 初始化SVG符号渲染支持
数据库连接 初始化SpatiaLite等内置数据库

12.3 运行时依赖

从工程的 Release 目录可以观察到运行时需要的依赖库:

核心GIS库

  • gdal304.dll --- GDAL栅格数据抽象库
  • proj_7_2.dll / proj_8_2.dll --- PROJ坐标投影库
  • geos.dll / geos_c.dll --- GEOS几何引擎
  • spatialindex-64.dll --- 空间索引库
  • spatialite.dll / sqlite3.dll --- SpatiaLite空间数据库
  • libxml2.dll --- XML解析库

QGIS框架库

  • qgis_core.dll --- QGIS核心库(数据访问、几何运算、工程管理等)
  • qgis_gui.dll --- QGIS GUI库(地图控件、渲染器等)
  • qgis_native.dll --- 原生对话框支持

Qt框架库

  • Qt5Core.dll / Qt5Gui.dll / Qt5Widgets.dll --- Qt基础库
  • Qt5Concurrent.dll --- 并发处理
  • Qt5Svg.dll --- SVG渲染
  • Qt5Xml.dll --- XML处理
  • Qt5Network.dll --- 网络请求
  • Qt5Sql.dll --- 数据库访问
  • Qt5WebKit.dll / Qt5WebKitWidgets.dll --- Web引擎

12.4 注意事项

  1. 初始化顺序 :必须在 QApplication 创建之后调用 initQgis(),因为QGIS底层依赖Qt的事件循环和资源管理。

  2. 多线程渲染 :通过 QgsApplication::setMaxThreads(8) 设置最大并行渲染线程数,需根据目标硬件配置调整。

  3. 路径设置init() 的参数应为QGIS安装目录(包含 qgis_core.dll 等文件的目录),SDK路径配置错误会导致初始化失败。

  4. QSetting配置 :在初始化前设置 OrganizationNameApplicationName,确保QGIS配置(QgsSettings)能正确读写注册表或配置文件。


13. 状态栏与辅助信息显示

13.1 功能概述

工程自定义了丰富的状态栏组件,包括坐标显示、比例尺、缩放倍数、旋转角度、坐标参考系信息等,为用户提供全面的地图状态反馈。

13.2 状态栏架构

cpp 复制代码
void QtGis::createStatusBar()
{
    mStatusBar = new QgsStatusBar();
    mStatusBar->setFont(statusBarFont);
    statusBar()->addPermanentWidget(mStatusBar, 10);

    // 进度条
    mProgressBar = new QProgressBar(mStatusBar);
    mProgressBar->setMaximumWidth(100);
    mProgressBar->setMaximumHeight(18);
    mProgressBar->hide();

    // 任务管理器
    mTaskManagerWidget = new QgsTaskManagerStatusBarWidget(
        QgsApplication::taskManager(), mStatusBar);

    // 坐标显示
    mCoordsEdit = new QgsStatusBarCoordinatesWidget(mStatusBar);
    mCoordsEdit->setMapCanvas(mMapCanvas);

    // 比例尺
    mScaleWidget = new QgsStatusBarScaleWidget(mMapCanvas, mStatusBar);

    // 放大镜(缩放倍数)
    mMagnifierWidget = new QgsStatusBarMagnifierWidget(mStatusBar);

    // 旋转角度
    mRotationLabel = new QLabel(QString(), mStatusBar);
    mRotationEdit = new QgsDoubleSpinBox(mStatusBar);

    // 投影状态
    mOnTheFlyProjectionStatusButton = new QToolButton(mStatusBar);
}

13.3 坐标显示组件

坐标显示组件是状态栏最重要的组件之一,实时响应鼠标移动并显示地理坐标:

信号连接

cpp 复制代码
// 鼠标移动时更新坐标显示
connect(mMapCanvas, &QgsMapCanvas::xyCoordinates, 
    this, &QgsStatusBarCoordinatesWidget::showMouseCoordinates);

// 视图范围变化时更新范围显示
connect(mMapCanvas, &QgsMapCanvas::extentsChanged, 
    this, &QgsStatusBarCoordinatesWidget::showExtent);

坐标格式化

cpp 复制代码
void QgsStatusBarCoordinatesWidget::showMouseCoordinates(const QgsPointXY &p)
{
    if (!mMapCanvas || mToggleExtentsViewButton->isChecked()) return;

    // 使用QGIS内置工具格式化坐标
    mLineEdit->setText(
        QgsCoordinateUtils::formatCoordinateForProject(
            QgsProject::instance(), 
            p, 
            mMapCanvas->mapSettings().destinationCrs(),
            mMousePrecisionDecimalPlaces
        )
    );
    ensureCoordinatesVisible();
}

13.4 比例尺组件

cpp 复制代码
// 用户修改比例尺后触发缩放
connect(mScale, &QgsScaleComboBox::scaleChanged, 
    this, &QgsStatusBarScaleWidget::userScale);

void QgsStatusBarScaleWidget::userScale() const
{
    mMapCanvas->zoomScale(mScale->scale());
}

13.5 放大镜组件

放大镜组件允许用户在不改变比例尺的情况下放大显示内容(锁定比例尺后通过放大镜缩放):

cpp 复制代码
// 画布放大倍数变化 -> 更新控件显示
connect(mMapCanvas, &QgsMapCanvas::magnificationChanged, 
    mMagnifierWidget, &QgsStatusBarMagnifierWidget::updateMagnification);

// 控件值变化 -> 设置画布放大倍数
connect(mMagnifierWidget, &QgsStatusBarMagnifierWidget::magnificationChanged, 
    mMapCanvas, [=](double factor) { 
        mMapCanvas->setMagnificationFactor(factor); 
    });

// 缩放锁定信号传递
connect(mMagnifierWidget, &QgsStatusBarMagnifierWidget::scaleLockChanged, 
    mMapCanvas, &QgsMapCanvas::setScaleLocked);
connect(mMagnifierWidget, &QgsStatusBarMagnifierWidget::scaleLockChanged, 
    mScaleWidget, &QgsStatusBarScaleWidget::setLocked);

13.6 旋转角度控制

cpp 复制代码
mRotationEdit = new QgsDoubleSpinBox(mStatusBar);
mRotationEdit->setClearValue(0.0);
mRotationEdit->setKeyboardTracking(false);
mRotationEdit->setDecimals(1);
mRotationEdit->setRange(-360.0, 360.0);
mRotationEdit->setWrapping(true);
mRotationEdit->setSingleStep(5.0);
mRotationEdit->setSuffix(QStringLiteral("°"));

connect(mRotationEdit, 
    static_cast<void (QgsDoubleSpinBox::*)(double)>(&QgsDoubleSpinBox::valueChanged), 
    this, &QtGis::userRotation);

13.7 技术要点

  1. QgsStatusBar :QGIS自定义的状态栏容器,支持弹性布局,通过 addPermanentWidget() 添加右侧固定位置的组件。

  2. QgsCoordinateUtils::formatCoordinateForProject():根据项目设置自动格式化坐标显示(单位、精度、显示格式)。

  3. QgsDoubleSpinBox :QGIS扩展的DoubleSpinBox,支持 setClearValue() 设置清空值模式、setKeyboardTracking(false) 减少实时信号触发。

  4. 放大镜与比例尺锁定:当用户锁定比例尺后,缩放操作会通过放大镜实现(改变渲染DPI),而不是改变比例尺,这在制图输出时特别有用。


14. Ribbon界面与UI框架

14.1 框架选型

工程使用 QtitanRibbon 组件库(QtitanRibbon5.dll / QtitanBase1.dll)实现Office风格的Ribbon界面,继承自 RibbonMainWindow(封装在 DemoRibbonWindow 中)。

14.3 当前已实现的RibbonBar代码

cpp 复制代码
void QtGis::createRibbonBar()
{
    // 创建Layer页面
    RibbonPage* pageFile = ribbonBar()->addPage(tr("&Layer"));
    RibbonGroup* layerGroup = pageFile->addGroup(QStringLiteral("Layer"));
    
    // 添加栅格/矢量图层按钮
    layerGroup->addAction(mAddRasterLayer, Qt::ToolButtonTextUnderIcon);
    layerGroup->addAction(mAddVectorLayer, Qt::ToolButtonTextUnderIcon);

    ribbonBar()->setFrameThemeEnabled();
}

14.4 快速访问栏

cpp 复制代码
void QtGis::createQuickAccessBar()
{
    RibbonQuickAccessBar* quickAccessBar = ribbonBar()->quickAccessBar();
    
    QAction* openBtn = quickAccessBar->addAction(
        DemoRibbonWindow::createIcon(Image_Open), "Open Project");
    connect(openBtn, SIGNAL(triggered()), this, SLOT(slot_fileOpen()));

    QAction* saveBtn = quickAccessBar->addAction(
        DemoRibbonWindow::createIcon(Image_Save), "Save Project");
    connect(saveBtn, SIGNAL(triggered()), this, SLOT(slot_fileSave()));
}
cpp 复制代码
void QtGis::createMenuFile()
{
    QAction* actionFile = ribbonBar()->addSystemButton(iconLogo, tr("&Project"));
    ribbonBar()->systemButton()->setToolButtonStyle(Qt::ToolButtonTextOnly);
    
    RibbonSystemMenu* systemMenu = qobject_cast<RibbonSystemMenu*>(actionFile->menu());
    systemMenu->addAction(mFileNew);
    systemMenu->addAction(mFileOpen);
    systemMenu->addAction(mFileSave);
    systemMenu->addAction(mFileSaveAs);
    systemMenu->addAction(mFileClose);
    systemMenu->addAction(mFileExit);

    // 最近文件列表
    RibbonPageSystemRecentFileList* pageRecentFile = 
        systemMenu->addPageRecentFile("Recent Maps");
    pageRecentFile->setSize(9);
    
    connect(pageRecentFile, SIGNAL(openRecentFile(const QString&)), 
        this, SLOT(openRecentFile(const QString&)));
}

14.6 技术要点

  1. QtitanRibbon:商业Ribbon组件库,提供与Office一致的UI体验。支持多种风格主题(Office 2007/2010/2013/2016、Adobe Photoshop等)。

  2. 图标管理 :通过 DemoRibbonWindow::createIcon(ImagesId) 统一管理,支持16x16和32x32双尺寸图标。

  3. 多风格支持DemoRibbonWindow 实现了完整的风格切换机制,用户可通过 Options > Style 菜单切换界面风格。


15. 综合注意事项与最佳实践

15.1 内存管理

  1. 图层面生命周期 :通过 QgsProject::instance()->addMapLayers() 添加的图层由QGIS项目对象管理,无需手动删除。直接 new QgsRasterLayer() 创建的图层若未添加到项目,需要手动 delete

  2. 渲染器所有权layer->setRenderer(renderer) 后,图层接管渲染器的所有权,不要再手动删除,否则会导致悬空指针崩溃。

  3. Canvas Refresh Blocker :使用 QgsCanvasRefreshBlocker 的RAII机制批量添加多个图层时冻结画布刷新,避免频繁重绘:

cpp 复制代码
{
    QgsCanvasRefreshBlocker refreshBlocker;
    // 批量添加图层
    for (auto& file : files) {
        QgsRasterLayer* layer = new QgsRasterLayer(...);
        layersToAdd << layer;
    }
    QgsProject::instance()->addMapLayers(layersToAdd);
} // refreshBlocker析构时自动解冻并刷新画布

15.2 线程安全

  1. 渲染线程:QGIS的图层渲染在独立线程中进行,不要在渲染线程中直接操作UI组件。

  2. 信号槽跨线程 :使用Qt的跨线程信号槽机制(Qt::QueuedConnection)确保数据变更能安全通知UI更新。

  3. canvasRefreshStarted / canvasRefreshFinished:通过连接这两个信号可以在状态栏显示渲染进度。

15.3 坐标参考系统

  1. 项目CRS设置QgsProject::instance()->setCrs(QgsCoordinateReferenceSystem("EPSG:4326")) 设置项目的默认CRS,所有图层的坐标会在显示时实时投影到此CRS。

  2. 动态投影:QGIS支持动态投影(On-The-Fly投影),即使图层的源CRS不同,也能在同一画布中正确叠加显示。

  3. CRS一致性检查:添加图层时建议检查图层的CRS与项目CRS是否匹配,避免坐标偏差。

15.4 文件格式支持

栅格格式(通过GDAL驱动):

格式 扩展名 说明
GeoTIFF .tif, .tiff 最常用的地理栅格格式
Erdas Imagine .img 遥感常用格式
NetCDF .grd, .nc 科学数据格式
SRTM HGT .hgt DEM高程数据
JPEG2000 .jp2 压缩栅格格式

矢量格式(通过OGR驱动):

格式 扩展名 说明
Shapefile .shp 最通用的矢量格式
GeoJSON .geojson WebGIS标准格式
GMT .gmt 通用制图工具格式
KML .kml Google Earth格式
GeoPackage .gpkg 新一代开放标准格式

15.5 异常处理最佳实践

  1. 图层有效性检查 :在操作任何图层前检查 layer->isValid(),无效图层可能是文件损坏、格式不支持或路径错误导致。

  2. 中文路径支持:GDAL/OGR在Windows下对中文路径的支持有限,建议在UI中提示用户使用英文路径。

  3. 大文件处理 :对于大型遥感影像(>1GB),使用 setPreviewJobsEnabled(true)setParallelRenderingEnabled(true) 启用预览作业和并行渲染以提升交互体验。

  4. Ribbon界面注意事项 :QtitanRibbon与QGIS的样式存在潜在冲突,使用 ribbonBar()->setFrameThemeEnabled() 控制框架主题的启用。

16. 总结

本工程是一个典型的基于QGIS SDK的C++二次开发项目,集成了QtitanRibbon商业UI组件,构建了具有Office风格的卫星遥感图像处理平台。通过对该工程的源码分析,我们可以系统性地掌握QGIS二次开发的核心技术要点。

核心技术栈

技术层 组件 用途
框架层 Qt 5 + QGIS Core/GUI GIS基础框架
数据层 GDAL/OGR + PROJ + GEOS 空间数据读写与投影
界面层 QtitanRibbon Office风格Ribbon界面
业务层 算法库 卫星遥感数据专用处理

结语

QGIS SDK为GIS二次开发提供了完善的基础设施,从数据访问(GDAL/OGR集成)、空间分析(GEOS/Proj)、地图渲染到UI组件(地图画布、图层树、状态栏),形成了一站式的开发框架。开发者可以在此基础上专注于业务逻辑的实现,大幅降低GIS软件开发的技术门槛与周期。


相关推荐
菜鸟‍17 小时前
【论文学习】MICCAI 2025 || ARSeg:面向不完整文本提示的鲁棒医学图像指代表达分割
图像处理·学习
搞科研的小刘选手20 小时前
【华中师范大学、华南师范大学联合主办】第五届图像处理、目标检测与跟踪国际学术会议(IPODT 2026)
图像处理·目标检测·跟踪·学术会议·会议推荐
初学者,亦行者20 小时前
利用Pyecharts绘制堆叠柱状图
python·信息可视化·数据分析
nVisual1 天前
机柜PDU安装位置与空间建模方案
大数据·网络·数据库·信息可视化·数据中心基础设施管理
GrepowTattu1 天前
新品发布|Tattu TA-BC无人机电池检测器上线,多通道充电状态尽在掌握
无人机
初学者,亦行者1 天前
利用pyecharts自动化绘制漏斗图保姆级教程
python·信息可视化·数据分析
初阳7851 天前
【Qt】SQLite——数据库介绍
数据库·qt·sqlite
xuanshang_yutou1 天前
无人机调试起飞遇到的问题及注意事项
无人机
延凡科技2 天前
多场景落地复盘:端边云架构无人机智能巡检系统设计与实践
大数据·数据结构·人工智能·科技·架构·无人机·能源