尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

基于QGIS二次开发:使用C++与Qt构建轻量级GIS桌面工具

基于QGIS二次开发:使用C++与Qt构建轻量级GIS桌面工具 简介本资源是一份面向GIS开发初学者与C桌面应用开发者的技术实践项目聚焦于基于QGIS SDK与Qt框架在Visual Studio环境下开展轻量级GIS桌面工具的二次开发核心解决矢量数据Shapefile、GeoJSON等加载与基础地图展示问题。压缩包共10个文件含3个.cpp源文件实现主窗口、图层树菜单及QGIS接口调用、2个.h头文件定义类结构与信号槽、1个README.md说明文档、1个.txt功能说明、1个.docx附赠资源指南、1张界面截图PNG及.gitignore配置文件整体仅59KB结构精简、开箱即用。已有102人学习下载适合希望快速理解QGIS嵌入式集成流程、掌握Qt GUI与QGIS API协同开发范式的开发者。读者可直接编译运行获得可加载多种矢量格式、具备基础图层管理能力的最小可行GIS应用原型并通过代码注释与文档厘清QGIS初始化、图层注册、坐标系处理等关键环节的实现逻辑。1. 项目概述为什么选择QGISQTVS这套组合拳最近在做一个轻量级的GIS桌面工具核心需求很简单能加载常见的矢量数据比如Shapefile、GeoJSON进行一些基础的浏览、查询和简单的空间分析。市面上成熟的GIS软件很多但要么太重像ArcGIS要么功能太单一要么就是定制化程度不够。所以我决定基于QGIS的二次开发能力自己动手搭一个。为什么是QGIS因为它本身就是一个开源GIS的集大成者功能强大且架构清晰其核心库QGIS C API提供了几乎所有我们需要的GIS功能模块从数据读写、坐标转换到地图渲染、空间分析一应俱全。直接调用这些久经考验的库远比从零造轮子要高效和稳定得多。那为什么选择QT作为界面框架并且把开发环境放在Visual Studio里呢这背后有几个很实际的考量。首先QGIS本身就是用QT写的这意味着它的API与QT框架是天作之合集成起来几乎没有隔阂事件循环、信号槽机制都能无缝对接开发体验非常顺畅。其次QT的跨平台特性虽然在这个项目里不是首要目标我们主要面向Windows但其成熟的UI组件库Qt Widgets和强大的布局管理器能让我们快速构建出专业、稳定的桌面应用界面。最后选择Visual Studio主要是看中了它在Windows平台下无与伦比的C开发体验——强大的IntelliSense代码提示、高效的调试器、以及对大型项目良好的管理能力。特别是当项目涉及到复杂的C模板、第三方库链接时VS的工程管理能力能省去很多麻烦。简单说这套组合就是“用成熟的GIS内核QGIS 成熟的UI框架QT 成熟的IDEVS”目标是快速、稳定地实现一个功能专一的GIS工具。2. 核心需求解析与开发环境搭建2.1 轻量级GIS工具的核心功能定义在动手之前我们必须明确“轻量级”到底指什么。对于这个项目我把它定义为以下几个核心功能点这也是评估我们二次开发是否成功的标尺多格式矢量数据加载与显示这是基础中的基础。工具必须能无缝加载如Shapefile (.shp)、GeoJSON、GML等常见矢量格式。加载后需要能根据要素的属性比如一个表示行政区划的Shapefile中的“名称”字段进行符号化渲染并以正确的空间参考显示在地图画布上。基础地图浏览操作用户需要能像使用常规GIS软件一样进行地图的缩放、平移、全图显示等操作。这背后涉及到视图控制器、坐标变换等一系列逻辑。要素查询与属性查看用户点击地图上的某个要素比如一条道路工具应能高亮显示该要素并弹出一个面板展示其所有属性信息如道路名称、长度、类型等。轻量空间分析例如对选中的要素创建缓冲区、计算要素的基本几何信息长度、面积或者进行简单的空间查询如“找出所有与这个湖泊相交的林地”。这些功能听起来简单但每一个背后都涉及到GIS领域专业的概念和QGIS API的调用。我们的目标不是复刻QGIS Desktop而是利用其强大的后端为我们特定的应用场景提供一个简洁、高效的前端界面。2.2 开发环境搭建VSQTQGIS库的集成这是整个项目的第一步也是最容易踩坑的一步。配置不对后面寸步难行。我的环境基于Windows 10/11使用Visual Studio 2019/2022。第一步安装并配置QT我选择的是QT 5.15.2 LTS版本这是一个长期支持版稳定性和兼容性都很好。不建议一上来就用最新版可能会遇到一些意想不到的库依赖问题。安装时务必勾选与你VS版本匹配的MSVC编译器组件例如“MSVC 2019 64-bit”。安装完成后需要将QT的bin目录例如C:\Qt\5.15.2\msvc2019_64\bin添加到系统的PATH环境变量中这样后续CMake或VS才能找到qmake、moc等工具。第二步获取QGIS开发库这是关键。我们不能直接安装QGIS桌面版来开发需要的是它的开发库头文件和链接库。有两种主要方式使用OSGeo4W安装这是最推荐的方式。OSGeo4W是Windows下GIS开源软件的包管理器。运行OSGeo4W安装程序选择“Advanced Install”然后在包选择界面搜索“qgis”。你需要的不是“qgis”这个桌面应用包而是“qgis-dev”或“qgis-ltr-dev”对应长期支持版。安装这个包它会自动为你安装QGIS所有的依赖库如GEOS, PROJ, GDAL等以及C头文件和lib文件。从源码编译这种方式最灵活可以自定义模块但过程极其耗时且复杂对新手不友好除非有特殊需求否则不推荐。安装后关键是要记住几个路径头文件路径通常是C:\OSGeo4W\apps\qgis-dev\include库文件路径通常是C:\OSGeo4W\apps\qgis-dev\libDLL路径通常是C:\OSGeo4W\bin。这个路径下的所有dll在运行时都是必需的。第三步在Visual Studio中创建并配置项目我强烈建议使用CMake来管理VS项目而不是传统的.vcxproj。因为QGIS本身使用CMake构建用CMake能更自然地处理它的依赖。在VS中安装“使用C的桌面开发”和“使用C的Linux开发”工作负载后者包含CMake支持。创建一个新的“CMake项目”。在项目根目录的CMakeLists.txt文件中进行关键配置。下面是一个极简但可用的示例框架cmake_minimum_required(VERSION 3.10) project(MyLightweightGIS LANGUAGES CXX) # 设置C标准 set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 查找必需的包 find_package(Qt5 COMPONENTS Core Widgets Gui REQUIRED) find_package(QGIS REQUIRED) # 这行是关键需要QGIS的CMake配置支持 # 如果你的OSGeo4W安装路径没有被自动找到可能需要手动提示 # set(CMAKE_PREFIX_PATH C:/OSGeo4W/apps/qgis-dev ${CMAKE_PREFIX_PATH}) # 添加可执行文件 add_executable(MyLightweightGIS main.cpp mainwindow.cpp ...) # 链接库 target_link_libraries(MyLightweightGIS Qt5::Core Qt5::Widgets Qt5::Gui qgis_core # QGIS核心库 qgis_gui # QGIS GUI库包含地图画布等 ) # 包含目录 target_include_directories(MyLightweightGIS PRIVATE ${Qt5Core_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} ${QGIS_INCLUDE_DIR} ) # 自动处理QT的MOC、UIC、RCC set_target_properties(MyLightweightGIS PROPERTIES AUTOMOC ON AUTOUIC ON AUTORCC ON )注意find_package(QGIS REQUIRED)能否成功取决于你的OSGeo4W安装是否提供了QGISConfig.cmake文件。通常位于C:\OSGeo4W\apps\qgis-dev\cmake。如果没有你需要手动设置QGIS_INCLUDE_DIR和QGIS_LIBRARIES变量这会麻烦很多。这也是推荐用OSGeo4W安装开发包的原因。配置完成后在VS里选择“CMake: 调试”或“CMake: 发布”配置然后生成项目。如果一切顺利项目应该能成功编译虽然还没写功能代码。3. 核心模块实现从地图画布到数据加载3.1 创建主窗口与地图画布一切从界面开始。我们使用QT Designer来设计主窗口的.ui文件或者直接用代码创建。核心是引入QGIS的地图画布控件QgsMapCanvas。首先在主窗口的头文件中包含必要的头文件并声明成员变量// mainwindow.h #include QMainWindow #include qgsmapcanvas.h // QGIS地图画布 #include qgsvectorlayer.h // 矢量图层 class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent nullptr); ~MainWindow(); private: void setupUI(); // 初始化界面 void loadVectorLayer(const QString path); // 加载矢量图层的函数 QgsMapCanvas *mMapCanvas; // 核心地图画布 // 可以添加其他控件如图层列表、工具栏等 };在源文件中我们初始化地图画布并将其设置为中心控件// mainwindow.cpp #include “mainwindow.h” #include QVBoxLayout #include QFileDialog #include qgsapplication.h // 关键QGIS应用环境 #include qgsproviderregistry.h MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , mMapCanvas(new QgsMapCanvas(this)) { // 1. 设置QGIS环境必须在创建任何QGIS对象之前调用 // 这里需要提供QGIS数据目录的路径通常是OSGeo4W下的 apps/qgis-dev/resources QString qgisDataPath “C:/OSGeo4W/apps/qgis-dev/resources”; QgsApplication::setPrefixPath(“C:/OSGeo4W/apps/qgis-dev”, true); QgsApplication::initQgis(); // 初始化QGIS库这是最重要的调用 setupUI(); } MainWindow::~MainWindow() { QgsApplication::exitQgis(); // 清理QGIS环境 } void MainWindow::setupUI() { // 设置画布的基本属性如背景色、启用反走样等 mMapCanvas-setCanvasColor(Qt::white); mMapCanvas-enableAntiAliasing(true); // 创建一个中心窗口部件并用布局管理画布 QWidget *centralWidget new QWidget(this); QVBoxLayout *layout new QVBoxLayout(centralWidget); layout-addWidget(mMapCanvas); setCentralWidget(centralWidget); // 创建菜单栏和工具栏 QMenu *fileMenu menuBar()-addMenu(“文件(F)”); QAction *openAction fileMenu-addAction(“打开矢量数据(O)…”); connect(openAction, QAction::triggered, this, [this]() { QString filePath QFileDialog::getOpenFileName(this, “选择矢量文件”, “”, “Shapefiles (*.shp);;GeoJSON (*.geojson *.json);;All Files (*.*)”); if (!filePath.isEmpty()) { loadVectorLayer(filePath); } }); // 可以添加缩放、平移等工具按钮 // ... }这里有几个关键点QgsApplication::initQgis()这是整个QGIS二次开发的灵魂调用。它初始化了QGIS内部的所有管理器如投影、数据提供者、符号库等。必须在创建任何QGIS对象如QgsMapCanvas,QgsVectorLayer之前调用且一个进程只需调用一次。通常放在main.cpp或主窗口构造函数最开始的地方。QgsApplication::setPrefixPath()告诉QGIS它的“家”在哪里即安装路径这样它才能找到投影数据库、符号库等资源文件。QgsApplication::exitQgis()在程序退出前调用进行清理工作。3.2 实现矢量数据加载与渲染当用户通过菜单选择了一个文件后loadVectorLayer函数被调用。这是将GIS数据变为可视化地图的关键一步。void MainWindow::loadVectorLayer(const QString path) { // 1. 创建矢量图层对象 // 参数文件路径图层名可自动从文件名获取数据提供者名如 “ogr” 用于读写矢量文件 QgsVectorLayer *layer new QgsVectorLayer(path, QFileInfo(path).baseName(), “ogr”); // 2. 检查图层是否创建成功 if (!layer || !layer-isValid()) { QMessageBox::critical(this, “错误”, QString(“无法加载图层\n%1”).arg(layer ? layer-lastError() : “Unknown error”)); delete layer; return; } // 3. 将图层添加到地图画布的图层集中 // QgsMapCanvas 通过 QgsMapLayerStore 或直接设置图层列表来管理图层 // 这里我们用一个简单的列表先获取现有图层添加新图层再重新设置 QListQgsMapLayer* layers mMapCanvas-layers(); layers.append(layer); mMapCanvas-setLayers(layers); // 4. 设置画布的范围为新图层的全图范围 mMapCanvas-setExtent(layer-extent()); mMapCanvas-refresh(); // 刷新画布触发重绘 // 5. 可选简单的符号化渲染 // 例如根据图层几何类型设置默认符号 if (layer-geometryType() QgsWkbTypes::PointGeometry) { QgsMarkerSymbol *symbol QgsMarkerSymbol::createSimple({ {“color”, “red”}, {“size”, “3”} }); layer-setRenderer(new QgsSingleSymbolRenderer(symbol)); } else if (layer-geometryType() QgsWkbTypes::LineGeometry) { QgsLineSymbol *symbol QgsLineSymbol::createSimple({ {“color”, “blue”}, {“width”, “1”} }); layer-setRenderer(new QgsSingleSymbolRenderer(symbol)); } else if (layer-geometryType() QgsWkbTypes::PolygonGeometry) { QgsFillSymbol *symbol QgsFillSymbol::createSimple({ {“color”, “green”}, {“style”, “solid”}, {“outline_color”, “black”} }); layer-setRenderer(new QgsSingleSymbolRenderer(symbol)); } mMapCanvas-refresh(); // 再次刷新以应用符号 }代码解析与避坑指南数据提供者 ‘ogr’QGIS通过“提供者”Provider来读写不同格式的数据。‘ogr’ 是GDAL/OGR库的提供者它能处理绝大多数矢量格式Shapefile, GeoJSON, GML, KML等。如果你的数据是PostGIS数据库提供者名就是‘postgres’。图层有效性检查layer-isValid()至关重要。加载失败的原因很多文件路径错误、格式不支持、编码问题、缺少依赖库如读取FileGDB需要FileGDB驱动等。lastError()能提供一些线索。坐标系CRS问题这是GIS开发中最常见的坑之一。数据源有自己的坐标系如WGS84, Web Mercator画布也有一个坐标系。setExtent能自动处理不同坐标系下的范围转换前提是图层的坐标系已正确定义即.prj文件存在或数据源内部有定义。如果图层坐标系未知画布范围可能会显示异常。你可以通过layer-crs().isValid()来检查并考虑使用QgsCoordinateTransform进行手动转换。内存管理QgsVectorLayer是QObject的子类并且设置了父对象this因此通常不需要手动delete。但如果在非UI线程中创建需要特别注意线程安全和内存释放。3.3 实现基础地图交互工具一个没有交互的地图画布是没有灵魂的。我们需要实现缩放、平移、点选等基本工具。QGIS提供了QgsMapTool系列类我们可以继承它们或直接使用。首先在主窗口中声明工具和动作// mainwindow.h private slots: void onZoomIn(); // 放大 void onZoomOut(); // 缩小 void onPan(); // 平移 void onIdentify(); // 点选识别 private: QgsMapCanvas *mMapCanvas; QgsMapToolZoom *mZoomInTool; QgsMapToolZoom *mZoomOutTool; QgsMapToolPan *mPanTool; QgsMapToolIdentify *mIdentifyTool; QAction *mZoomInAction; QAction *mZoomOutAction; QAction *mPanAction; QAction *mIdentifyAction;在setupUI()中创建工具栏并关联这些工具void MainWindow::setupUI() { // ... 之前创建画布和布局的代码 ... // 创建工具对象 mZoomInTool new QgsMapToolZoom(mMapCanvas, false); // false 表示放大 mZoomOutTool new QgsMapToolZoom(mMapCanvas, true); // true 表示缩小 mPanTool new QgsMapToolPan(mMapCanvas); mIdentifyTool new QgsMapToolIdentify(mMapCanvas); // 创建工具栏和动作 QToolBar *toolBar addToolBar(“地图工具”); mZoomInAction toolBar-addAction(QIcon(“:/icons/zoom_in.png”), “放大”); mZoomOutAction toolBar-addAction(QIcon(“:/icons/zoom_out.png”), “缩小”); mPanAction toolBar-addAction(QIcon(“:/icons/pan.png”), “平移”); mIdentifyAction toolBar-addAction(QIcon(“:/icons/identify.png”), “识别”); // 将动作与工具关联 mZoomInAction-setCheckable(true); mZoomOutAction-setCheckable(true); mPanAction-setCheckable(true); mIdentifyAction-setCheckable(true); // 使用QActionGroup保证同一时间只有一个工具被激活 QActionGroup *mapToolGroup new QActionGroup(this); mapToolGroup-addAction(mZoomInAction); mapToolGroup-addAction(mZoomOutAction); mapToolGroup-addAction(mPanAction); mapToolGroup-addAction(mIdentifyAction); // 连接信号槽当动作被触发时设置对应的地图工具 connect(mZoomInAction, QAction::triggered, this, MainWindow::onZoomIn); connect(mZoomOutAction, QAction::triggered, this, MainWindow::onZoomOut); connect(mPanAction, QAction::triggered, this, MainWindow::onPan); connect(mIdentifyAction, QAction::triggered, this, MainWindow::onIdentify); // 默认激活平移工具 mPanAction-setChecked(true); mMapCanvas-setMapTool(mPanTool); } void MainWindow::onZoomIn() { mMapCanvas-setMapTool(mZoomInTool); } void MainWindow::onZoomOut() { mMapCanvas-setMapTool(mZoomOutTool); } void MainWindow::onPan() { mMapCanvas-setMapTool(mPanTool); } void MainWindow::onIdentify() { mMapCanvas-setMapTool(mIdentifyTool); // 可以连接IdentifyTool的信号例如当识别到要素时 connect(mIdentifyTool, QgsMapToolIdentify::identifyResultChanged, this, [this](QgsMapToolIdentify::IdentifyResult result) { if (!result.mResults.empty()) { QgsFeature feat result.mResults.first().mFeature; // 弹窗或更新状态栏显示要素属性 QString message QString(“识别到要素ID: %1, 属性: ...”).arg(feat.id()); statusBar()-showMessage(message, 3000); } }); }交互工具的核心要点工具模式QgsMapCanvas::setMapTool()用于切换当前激活的交互工具。一次只能有一个活动工具。可检查动作工具栏按钮使用setCheckable(true)并通过QActionGroup管理可以实现类似“单选按钮”的效果视觉上更友好。识别工具QgsMapToolIdentify功能强大它封装了鼠标点击后计算点击位置、遍历图层、进行空间查询点与要素相交判断的完整逻辑。其identifyResultChanged信号携带了查询结果我们可以从中提取被点击的要素QgsFeature及其属性。4. 进阶功能实现与性能优化4.1 实现属性查询与表格展示仅仅在地图上高亮要素还不够用户需要查看详细的属性信息。我们可以创建一个独立的属性表对话框。创建属性表对话框使用QT Designer创建一个包含QTableWidget的对话框命名为AttributeTableDialog。在识别工具中调用修改上面的onIdentify连接函数使其弹出属性对话框。void MainWindow::onIdentify() { mMapCanvas-setMapTool(mIdentifyTool); connect(mIdentifyTool, QgsMapToolIdentify::identifyResultChanged, this, [this](QgsMapToolIdentify::IdentifyResult result) { if (!result.mResults.empty()) { QgsFeature feat result.mResults.first().mFeature; QgsVectorLayer *layer result.mResults.first().mLayer; // 获取来源图层 // 创建并显示属性对话框 AttributeTableDialog *dlg new AttributeTableDialog(this); dlg-setAttribute(Qt::WA_DeleteOnClose); // 关闭时自动删除 dlg-populateTable(feat, layer); // 传入要素和图层 dlg-show(); } }, Qt::UniqueConnection); // 使用UniqueConnection防止重复连接 }实现populateTable方法// attributetabledialog.cpp void AttributeTableDialog::populateTable(const QgsFeature feature, QgsVectorLayer *layer) { if (!layer || !feature.isValid()) return; QgsFields fields layer-fields(); // 获取图层所有字段定义 int fieldCount fields.count(); ui-tableWidget-setRowCount(1); // 一行显示一个要素 ui-tableWidget-setColumnCount(fieldCount); for (int i 0; i fieldCount; i) { QgsField field fields.at(i); // 设置表头 QTableWidgetItem *headerItem new QTableWidgetItem(field.name()); ui-tableWidget-setHorizontalHeaderItem(i, headerItem); // 获取该字段在要素中的值 QVariant value feature.attribute(i); QString displayValue; // 处理一些特殊类型的值如日期、几何对象 if (value.type() QVariant::DateTime) { displayValue value.toDateTime().toString(“yyyy-MM-dd hh:mm:ss”); } else if (value.userType() QMetaType::type(“QgsGeometry”)) { displayValue “Geometry”; // 几何对象通常不直接显示在属性表 } else { displayValue value.toString(); } QTableWidgetItem *item new QTableWidgetItem(displayValue); item-setFlags(item-flags() ^ Qt::ItemIsEditable); // 设置为只读 ui-tableWidget-setItem(0, i, item); } ui-tableWidget-resizeColumnsToContents(); // 调整列宽 }4.2 实现缓冲区分析功能缓冲区分析是常见的空间分析功能。我们可以在工具栏添加一个按钮让用户先选择一个要素然后为其创建缓冲区。添加选择工具和缓冲区动作// mainwindow.h private: QgsMapToolSelect *mSelectTool; QAction *mSelectAction; QAction *mBufferAction; private slots: void onSelect(); void onCreateBuffer();// mainwindow.cpp 在setupUI中 mSelectTool new QgsMapToolSelect(mMapCanvas); mSelectAction toolBar-addAction(QIcon(“:/icons/select.png”), “选择”); mSelectAction-setCheckable(true); mapToolGroup-addAction(mSelectAction); connect(mSelectAction, QAction::triggered, this, MainWindow::onSelect); mBufferAction toolBar-addAction(QIcon(“:/icons/buffer.png”), “创建缓冲区”); mBufferAction-setEnabled(false); // 初始不可用直到有要素被选中 connect(mBufferAction, QAction::triggered, this, MainWindow::onCreateBuffer); // 连接选择变化信号 connect(mSelectTool, QgsMapToolSelect::selectionChanged, this, [this](QgsVectorLayer *layer) { // 当图层选中要素发生变化时检查是否有选中的要素 bool hasSelection layer layer-selectedFeatureCount() 0; mBufferAction-setEnabled(hasSelection); });实现缓冲区创建逻辑void MainWindow::onCreateBuffer() { // 获取当前活动图层这里简化处理假设只对第一个图层操作 QListQgsMapLayer* layers mMapCanvas-layers(); if (layers.isEmpty()) return; QgsVectorLayer *srcLayer qobject_castQgsVectorLayer*(layers.first()); if (!srcLayer || srcLayer-selectedFeatureCount() 0) return; // 1. 获取用户输入的缓冲区距离这里用简单对话框示例 bool ok; double distance QInputDialog::getDouble(this, “缓冲区分析”, “请输入缓冲区距离地图单位:”, 100.0, 0, 10000, 2, ok); if (!ok) return; // 2. 获取选中的要素 QgsFeatureIds selectedIds srcLayer-selectedFeatureIds(); QgsFeatureIterator it srcLayer-getFeatures(QgsFeatureRequest().setFilterFids(selectedIds)); // 3. 创建用于存放缓冲区结果的内存图层 QgsVectorLayer *bufferLayer new QgsVectorLayer( QString(“Polygon?crs%1”).arg(srcLayer-crs().authid()), // 定义几何类型和坐标系 QString(“Buffer_of_%1”).arg(srcLayer-name()), “memory”); if (!bufferLayer-isValid()) { delete bufferLayer; return; } // 添加一个字段记录原要素ID可选 QgsFields fields; fields.append(QgsField(“src_fid”, QVariant::Int)); bufferLayer-dataProvider()-addAttributes(fields.toList()); bufferLayer-updateFields(); bufferLayer-startEditing(); QgsFeature bufferFeature; QgsFeature srcFeature; while (it.nextFeature(srcFeature)) { QgsGeometry geometry srcFeature.geometry(); if (geometry.isNull()) continue; // 4. 执行缓冲区分析 // 注意distance单位与图层的CRS单位一致如米、度 // 对于地理坐标系单位是度做缓冲区分析需要小心可能需要先投影 QgsGeometry bufferGeometry geometry.buffer(distance, 20); // 20是线段分段数影响平滑度 if (!bufferGeometry.isNull()) { bufferFeature.setGeometry(bufferGeometry); QgsAttributes attrs; attrs srcFeature.id(); // 保存原ID bufferFeature.setAttributes(attrs); bufferLayer-addFeature(bufferFeature); } } bufferLayer-commitChanges(); // 5. 符号化缓冲区图层例如用半透明的红色填充 QgsFillSymbol *symbol QgsFillSymbol::createSimple({ {“color”, “rgba(255, 0, 0, 100)”}, // 红色100是透明度 {“style”, “solid”}, {“outline_color”, “red”}, {“outline_width”, “0.5”} }); bufferLayer-setRenderer(new QgsSingleSymbolRenderer(symbol)); // 6. 将缓冲区图层添加到地图 QListQgsMapLayer* currentLayers mMapCanvas-layers(); currentLayers.prepend(bufferLayer); // 放在最上面显示 mMapCanvas-setLayers(currentLayers); mMapCanvas-refresh(); // 7. 将新图层添加到图层管理器如果有的话 // ... }缓冲区分析注意事项单位问题geometry.buffer(distance)中的distance单位与图层的坐标参考系CRS单位一致。如果图层是地理坐标系如WGS84单位是度那么“100”就代表100度这通常是个巨大的、不正确的距离。对于实际应用如果数据是地理坐标系通常需要先投影到一个投影坐标系单位是米再进行缓冲区分析或者使用地理库的球面缓冲区算法。QGIS的buffer方法在底层会处理但对于精确测量主动投影到合适的CRS是更稳妥的做法。性能对大量要素或复杂几何做缓冲区计算可能耗时。可以考虑在后台线程中进行避免界面卡死。内存图层“memory”提供者创建的是临时图层数据存在内存中程序关闭即丢失。如需保存可以调用QgsVectorFileWriter::writeAsVectorFormat()导出为文件。4.3 性能优化与数据管理当加载大型Shapefile或GeoJSON时可能会遇到性能瓶颈。这里有几个优化方向视图范围渲染确保你的QgsMapCanvas启用了“视图范围渲染”通常默认是开启的。这意味着它只渲染当前画布视口范围内的要素这是最有效的优化。简化几何对于比例尺很小的视图即看得范围很大复杂的几何图形比如有成千上万个节点的海岸线的细节是看不见的。可以在图层渲染器上设置“几何简化”根据视图比例尺动态简化几何大幅提升绘制速度。矢量瓦片或栅格化对于超大型的静态背景数据如全国道路网可以考虑预先将其制作为矢量瓦片Vector Tiles或栅格瓦片Raster Tiles在QGIS中作为瓦片图层加载性能极佳。空间索引QGIS在加载矢量数据时如果数据源支持如Shapefile的.qix文件PostGIS的GiST索引会自动利用空间索引来加速空间查询如识别、选择。确保你的数据有空间索引。多线程数据加载使用QgsVectorLayer的异步加载模式或者将数据加载操作放在QgsApplication::taskManager()的任务中防止界面冻结。5. 项目部署与常见问题排查5.1 打包与分发处理DLL依赖开发完成后如何将程序分发给没有开发环境的用户最大的挑战是处理DLL依赖。你的程序依赖QT、QGIS以及GDAL/PROJ/GEOS等一堆运行时库。推荐方案使用windeployqt和脚本收集QT依赖在VS的Release模式下编译项目后找到生成的.exe文件。打开QT自带的命令行如“Qt 5.15.2 (MSVC 2019 64-bit)”使用windeployqt工具cd /d D:\MyProject\build\Release windeployqt MyLightweightGIS.exe这个命令会自动将程序所需的QT DLL、插件等复制到exe所在目录。收集QGIS及其依赖这是手动步骤。你需要从OSGeo4W的安装目录C:\OSGeo4W\bin复制大量的DLL。一个比较取巧的方法是在开发机上运行你的程序使用“Dependency Walker”或VS自带的“Profile”工具监控程序启动时加载了哪些DLL然后从OSGeo4W的bin目录下找到并复制它们。关键DLL包括qgis_core.dll,qgis_gui.dll,qgis_app.dll可能不需要以及GDAL、PROJ、GEOS、SQLite3、Qt5Core、Qt5Gui、Qt5Widgets等系列DLL。注意windeployqt已经处理了QT的这里主要处理GIS相关的。复制QGIS资源将C:\OSGeo4W\apps\qgis-dev\resources整个文件夹复制到你的程序目录下可以放在一个子目录如resources下。然后在你的代码中通过QgsApplication::setPrefixPath()或设置环境变量QGIS_PREFIX_PATH指向这个resources目录的父目录。测试将整个文件夹包含exe、所有DLL、resources、平台插件platforms等拷贝到一个干净的、没有安装QT和QGIS的Windows虚拟机中进行测试确保能正常运行。5.2 常见问题与解决方案速查表以下是我在开发过程中遇到的一些典型问题及解决方法问题现象可能原因解决方案程序崩溃错误码0xc000007b通常是32位/64位DLL混合导致。比如用了32位的QT DLL和64位的QGIS DLL。检查所有DLL的位数是否一致。确保你的项目配置、QT版本、QGIS库都是同为32位或64位。启动时提示“无法定位程序输入点于动态链接库”运行时加载的某个DLL版本不对缺少需要的函数。使用Dependency Walker检查exe的依赖树确认所有DLL都来自匹配的版本特别是QT和VC运行时库。确保从OSGeo4W复制的DLL是同一套。QgsApplication::initQgis()崩溃或失败1.setPrefixPath路径错误。2. 缺少必要的资源文件如srs.db,proj.db。3. 环境变量冲突。1. 确认setPrefixPath指向的目录包含resources子目录。2. 确保resources文件夹完整。3. 清理可能干扰的环境变量如GDAL_DATA,PROJ_LIB或者在你的程序中显式设置它们。地图画布一片空白但图层已加载1. 画布范围 (extent) 设置不正确。2. 图层坐标系 (CRS) 未知或与画布不匹配。3. 渲染器设置问题要素颜色与背景色相同。1. 尝试mMapCanvas-zoomToFullExtent()。2. 检查layer-crs().isValid()并尝试mMapCanvas-setDestinationCrs(layer-crs())。3. 调试时给图层设置一个醒目的符号如红色边框。加载某些Shapefile时崩溃或报错1. 文件损坏或编码问题。2. 缺少.dbf,.shx等必要组件。3. 字段名或内容包含特殊字符。1. 用QGIS Desktop打开测试看是否有错误提示。2. 确保同目录下存在.shp,.shx,.dbf,.prj可选但推荐文件。3. 尝试用OGR工具ogrinfo检查数据。缓冲区分析结果位置偏移巨大或形状奇怪坐标系问题。对地理坐标系单位度的数据直接做缓冲区。务必进行坐标转换。在分析前将几何图形转换到一个合适的投影坐标系单位是米。使用QgsCoordinateTransform。程序运行时内存持续增长内存泄漏。可能是QGIS对象未正确释放或循环引用。1. 使用QgsMapLayer的setParent()或使用QgsProject.instance()来管理图层生命周期。2. 确保在删除图层前将其从画布 (setLayers) 和任何缓存中移除。3. 使用VS的诊断工具分析内存。5.3 调试技巧与心得启用QGIS日志在initQgis()之前调用QgsApplication::setMaxMessageLevel(QgsMessageLog::INFO)或QgsApplication::enableDebugOutput(true)可以将QGIS内部的警告、错误信息输出到控制台或日志文件对于排查加载失败、渲染问题非常有帮助。善用QGIS Desktop进行对照当你的二次开发程序行为异常时首先用QGIS Desktop打开同一份数据看看正常情况是怎样的。Desktop是功能完整的参考实现。从简单开始逐步叠加不要一开始就想着实现所有功能。先确保能成功初始化QGIS环境、创建一个空白画布并显示出来。然后加载一个最简单的、已知正确的Shapefile。每一步都稳定后再增加新功能。注意线程所有对QGIS对象QgsMapCanvas,QgsVectorLayer等的操作都必须在主线程GUI线程中进行。如果在后台线程进行数据加载或处理需要将更新UI的操作通过信号槽QueuedConnection抛回主线程。资源清理虽然QT的对象树能自动管理很多内存但QGIS的一些全局资源如提供者注册、投影数据库连接需要exitQgis()来清理。确保程序退出路径上能调用到它。这个基于QGIS的二次开发框架就像给你提供了一套功能齐全的“GIS乐高”。你不需要关心地图如何投影、要素如何渲染、Shapefile如何解析这些底层细节而是可以专注于用QT搭建界面并调用QGIS API来实现具体的业务逻辑。虽然初始的环境搭建和依赖处理有点繁琐但一旦跑通后续的功能扩展会非常高效。对于需要嵌入GIS功能的专业桌面应用或者开发定制化的轻量级GIS工具这是一条非常值得探索的路径。本文还有配套的精品资源点击获取
返回列表