ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

VS+QGIS+Qt实现地图上画点:从环境配置到坐标转换实战

VS+QGIS+Qt实现地图上画点:从环境配置到坐标转换实战 简介面向C与GIS开发者的入门实战资源利用Visual Studio、QGIS和Qt在Windows 10环境下实现地图加载与标记点绘制适合需要掌握桌面GIS应用开发基础的学生或工程师也可作为课程设计的参考。资源压缩包大小仅为3KB总共包含3个文件其中2个是C源代码文件1个是头文件代码量精简完整覆盖从使用QGIS获取网络地图、新建图层到将经纬度坐标转换为墨卡托投影坐标再通过Qt图形接口在画布上绘制点的关键流程。目前已有3618人学习核心价值在于提供了可直接阅读和复用的源码能够清楚展示各工具之间的调用关系与数据处理链路并帮助读者梳理VS、QGIS与Qt整合开发时可能遇到的环境配置、坐标转换和图形绘制问题为后续扩展线、面等地图交互功能奠定基础。1. 为什么“vsqgisqt实现地图上画点”会成为一个真需求先说结论在QGIS桌面版里画一个点右键、新建图层、打开编辑、点一下十秒钟搞定。但当你要把“画点”这个能力嵌进自己的Qt程序时你会发现难点根本不在“画点”本身而在于 Visual Studio、QGIS、Qt 这三个软件之间的环境协同。项目标题“vsqgisqt实现地图上画点”指的就是用 Visual Studio 作为 IDE、Qt 作为界面框架、QGIS 作为地图引擎在自研的桌面程序中使用 QGIS 的 C API 在地图画布上绘制和保存点要素它不是教你在 QGIS 里手工画点而是教你把 QGIS 的地图能力移植到自己的应用里。这套技术方案适合谁适合做 GIS 桌面工具、用 Qt 做行业软件但又不想从零写地图渲染引擎的开发者。传统做法是自己用 QPainter 画底图、处理坐标变换、管理图层工程量巨大且后期维护困难。而 QGIS 提供了完整的开源 C 库使用它可以省掉最底层的地图渲染和坐标系处理工作。下面我会从环境搭建开始逐步拆解 API 使用、坐标转换和交互画点的完整路线并把每一步的坑提前指出来。2. 开发环境搭建VS 与 Qt 与 QGIS 的版本匹配是第一道分水岭很多人在这一步就翻车了不是因为不会写代码而是因为三个软件的版本搭配出了问题。QGIS 是纯 64 位的 C 应用其编译基于特定版本的 Qt 和 MSVC 工具链如果用 VS2022 默认的 v143 编译器去链接基于 v142 编译的 QGIS 库链接器会报一堆 LNK2038 或 LNK2001 错误。因此在开始写代码之前先把三者的版本关系锁定好。2.1 版本选型VS2022 Qt 5.15.2 QGIS 3.28 是我最稳妥的一套组合我先给出一套经过大量项目验证的组合方案并解释为什么是它而不是别的版本组件推荐版本选择理由Visual Studio2022 社区版免费支持 CMake 和 Qt VS Tools兼容 Qt 5.15 全系Qt5.15.2MSVC2019_64 编译套件QGIS 3.28 官方 Windows 包使用 Qt 5.15.2 编译匹配度最高Qt VS Tools 插件2.x 最新直接从 VS 菜单里调用 qmake 和 Qt DesignerQGIS3.28.14 LTR 或更高3.x 时代 API 稳定官方安装包带完整的 include 和 lib 目录注意三点Qt 下载页里 5.15.2 提供了 msvc2019_64 和 msvc2015_64 两套编译套件这里要选择 msvc2019_64它与 VS2022 的 v142 工具集兼容。QGIS 安装时记得勾选“Desktop”完整组件不建议使用 OSGeo4W 网络安装的精简版否则缺少 QGIS 的头文件和导入库。VS 安装时选择“使用 C 的桌面开发”工作负载并把 Windows 10 SDK 勾上。为什么不用 Qt 6因为 QGIS 3.28 的官方 Windows 二进制包仍然是基于 Qt 5.15 构建的你用 Qt 6 去链接 QGIS 3.28 的库OFF 与 ON 的编译宏匹配不上最终导致 QGisCore.lib 的符号解析失败。除非自己去编译 QGIS 源码极其耗时否则不要冒险。2.2 在 Visual Studio 里配置 Qt VS Tools三步完成少一步都无法调用 qmake安装 Qt VS Tools 后第一步是配置 Qt 版本路径第二步是把项目属性中的“Qt Settings”指向该版本第三步是用 VS 自带的“扩展”菜单打开 .ui 文件。打开 VS2022在菜单栏点击“扩展” → “Qt VS Tools” → “Qt Versions”点击“Add”按钮名称填Qt5.15.2_msvc2019_64路径填C:\Qt\Qt5.15.2\5.15.2\msvc2019_64。这个路径必须精确到包含bin、lib、include三个子目录的层级。配置完成后新建一个“Qt Widgets Application”项目右键项目 → “Qt Settings”确认 Qt 版本下拉框已选中刚才添加的版本同时勾选“Qt Modules”中的Core、Gui、Widgets。一个常见的困惑是QGIS 的第二行代码QgsApplication::setPrefixPath(C:/Program Files/QGIS 3.28.14/apps/qgis, true);这里的路径在 QGIS 安装后是固定的但如果你的 QGIS 安装在 D 盘需要同步修改。这行代码没有返回值漏掉它的后果是调用initQgis()后系统提示找不到 QGIS 的各类qgis_*.dll动态库。2.3 环境变量 PATH 的配置不把 QGIS 的 bin 目录加进去程序跑不起来在 VS 里按 F5 启动调试时程序会去寻找依赖的 DLL。QGIS 的运行时依赖远不止qgis_core.dll和qgis_gui.dll它还需要 Qt 的qwindows.dllplatform 插件、zlib.dll、gdal.dll等一系列文件。建议使用“路径加数组”的思路把以下目录一次性增加到系统环境变量 PATH 中C:\Qt\Qt5.15.2\5.15.2\msvc2019_64\bin C:\Program Files\QGIS 3.28.14\apps\qgis\bin C:\Program Files\QGIS 3.28.14\apps\Qt5\bin配置后不要忘记重启 VS否则环境变量不生效。验证是否配置成功的方法是在 VS 的“工具” → “命令行” → “开发者命令提示符”中执行where qgis_core.dll如果能弹出 DLL 文件路径说明路径没问题。提示如果你的 Qt 是 6.x 且 QGIS 编译用的是 Qt5则第 1 行路径会引入错误的 DLL导致运行时出现 “Qt platform plugin windows 加载失败”。此时把 Qt6 的 bin 从 PATH 中临时移除即可验证。这个问题的典型报错是qt.qpa.plugin: could not load the Qt platform plugin windows in even though it was found。3. QGIS C API 核心类拆解从 QgsApplication 到 QgsVectorLayer 的完整链路在 VS 项目里使用 QGIS 的 C API本质上是把 QGIS 的地图渲染核心作为一个库来调用。这里有个重要事实需要说明QGIS 官方主要支持 Python 插件开发C 二次开发的资料相对少但 C API 在 3.x 时代已经比较稳定。要掌握画点这个功能只需要理解这个对象链QgsApplication负责初始化运行时、QgsMapCanvas负责显示地图、QgsVectorLayer承载点数据、QgsFeature描述单个点要素。3.1 用 QgsApplication 启动 GIS 运行时所有 QGIS 类的前提任何使用 QGIS C API 的程序第一步必须是创建QgsApplication实例它是全局功能的容器包括坐标参考系系统、数据提供者注册表和插件管理器。#include QApplication #include qgsapplication.h #include qgsmapcanvas.h #include qgsvectorlayer.h #include qgsfeature.h #include qgsgeometry.h #include qgspointxy.h #include qgscoordinatereferencesystem.h int main(int argc, char *argv[]) { // 第2个参数传入 true 表示启用 GUI 相关功能 QgsApplication app(argc, argv, true); QgsApplication::setPrefixPath(C:/Program Files/QGIS 3.28.14/apps/qgis, true); QgsApplication::initQgis(); // 创建地图画布 QgsMapCanvas *canvas new QgsMapCanvas(); canvas-setCanvasColor(Qt::white); canvas-resize(800, 600); canvas-show(); return app.exec(); }这段代码中的参数true会让 QGIS 加载 GUI 模块包括qgis_gui.dll中的QgsMapCanvas相关类。如果你只处理数据而不需要界面可以传false但画点必须为真。setPrefixPath中的true表示路径是相对标志这个写法依赖 QGIS 安装目录的固定结构不要随意拼接。3.2 创建内存图层并用 dataProvider 写入点要素核心的数据写入逻辑QGIS 里所有地图数据都被抽象为图层QgsVectorLayer。画点之前先创建一个内存的点图层然后把点写入到图层的数据源中最后把图层挂到画布上。// 创建内存点图层crsEPSG:4326 表示使用 WGS 84 经纬度坐标系 QgsVectorLayer *layer new QgsVectorLayer( QStringLiteral(Point?crsEPSG:4326fieldname:string(50)fieldtype:int), QStringLiteral(my_points), QStringLiteral(memory) ); // 构造一个点要素 QgsFeature feature; feature.setAttributes({QVariant(站点A), QVariant(1)}); QgsGeometry geom QgsGeometry::fromPointXY(QgsPointXY(116.397, 39.908)); feature.setGeometry(geom); // 数据提供器负责真正把要素写入数据源 if (layer-dataProvider()) { QListQgsFeature features; features.append(feature); layer-dataProvider()-addFeatures(features); } layer-updateExtents(); QgsProject::instance()-addMapLayer(layer); // 把图层添加到画布并缩放到该点 QListQgsMapLayer * layers; layers.append(layer); canvas-setLayers(layers); canvas-zoomToFullExtent();这里有几个必须理解的参数第一个构造函数参数是一段描述图层结构的字符串Point定义了几何类型crsEPSG:4326指定坐标参考系fieldname:string(50)定义了一个名为 name 的字段。如果不声明字段后期使用feature.setAttribute()时会报索引越界。特征对象的setGeometry接收QgsGeometry它是几何封装类内部可以持有点、线、面三种类型这里用fromPointXY构造一个二维点。特别说明一下updateExtents()的作用它强制图层重新计算范围如果不调用画布不知道图层的边界zoomToFullExtent()可能缩放到空白区域。这块是新手最容易忽略的地方。3.3 在 Qt Widgets 界面里提升地图画布用 Qt Designer 还是手写布局画布创建完成后需要嵌入到一个主窗口中。常见的实现方式有两种成本一种是在 Qt Designer 中绘制一个QWidget控件然后在“提升窗口部件”对话框里输入类名QgsMapCanvas头文件填qgsmapcanvas.h这样 UI 文件在生成时就会自动创建QgsMapCanvas对象并设置布局。另一种是全部用手写代码方式创建窗口适用于不愿意碰 .ui 文件的场景。我推荐使用 Qt Designer 的提升方式因为地图画布通常占满整个客户区它在设计器里可视化调整边距和排列更方便。“qt designer界面设计”这一步熟悉之后你会意识到提升控件是 Qt 插件开发中的一个高频操作除了QgsMapCanvasQgsMapToolIdentify等工具类也可以按键提升。但有一个易出错的地方很多教程让你在 .ui 文件属性里直接改 canvas 类型却忘了在类声明中重新定义Ui::MainWindow生成的对象。正确做法是在自定义MainWindow类中创建一个QgsMapCanvas *mMapCanvas随后在构造函数里ui-setupUi(this)之后再从提升后的控件中取出真正的 canvas 实例或者直接在.ui的成员变量里用QgsMapCanvas *mMapCanvas引用它。两者的核心目的是保证后续canvas-setLayers()调用的是同一个画布对象。4. 实现交互画点与坐标转换从固定坐标到鼠标点击的动态过程完成静态画点后更实际的需求是让用户在地图上用鼠标点击画点。这个阶段的核心工作是两件事注册点击交互工具QgsMapTool子类以及在经纬度与 Web Mercator 平面坐标之间做转换。4.1 继承 QgsMapTool 实现点击画点canvasReleaseEvent 是核心入口QGIS 的地图工具机制模拟了设计模式中的命令模式用户触发鼠标事件后事件由其激活的QgsMapTool子类处理。下面的代码演示如何创建一个点击画点的工具类#include qgsmaptool.h #include qgsmapmouseevent.h class PointClickTool : public QgsMapTool { public: PointClickTool(QgsMapCanvas *canvas) : QgsMapTool(canvas) {} // 鼠标释放时被调用event 中已包含画布坐标 void canvasReleaseEvent(QgsMapMouseEvent *event) override { // mapPoint() 返回当前画布对应的地图坐标可随画布 CRS 变化 QgsPointXY mapPoint event-mapPoint(); QgsPointXY wgsPoint mCanvas-mapSettings().layerToMapCoordinates( event-mapPoint(), nullptr); // 将坐标转换为添加图层时指定的 EPSG:4326 QgsCoordinateReferenceSystem canvasCrs mCanvas-mapSettings().destinationCrs(); QgsCoordinateReferenceSystem wgsCrs QgsCoordinateReferenceSystem::fromEpsgId(4326); QgsCoordinateTransform transform(canvasCrs, wgsCrs, QgsProject::instance()); QgsPointXY converted transform.transform(mapPoint); // 添加点要素到当前活动图层 addPointToLayer(converted); } };关键在于理解event-mapPoint()返回的坐标是在画布 CRS 体系下的。如果画布加载的是 Web 墨卡托EPSG:3857底图而点图层是 EPSG:4326直接使用这个坐标写入图层点会偏移到不可思议的位置。这里的QgsCoordinateTransform是 QGIS 3.x 中执行坐标变换的标准方式构造函数需要源 CRS、目标 CRS 以及一个QgsProject作用域对象用于传递转换参数。4.2 在程序中绑定工具setMapTool 的时机选择工具类写好后需要激活它。激活动作一般放在主窗口的构造函数中或者某个按钮的点击槽函数里// 在 MainWindow::MainWindow 中 mPointTool new PointClickTool(mMapCanvas); mMapCanvas-setMapTool(mPointTool);注意setMapTool必须在画布设置了图层之后调用否则画布尚未绑定有效 CRS事件处理会因坐标系为空而崩溃。结合这个时机建议在完成canvas-setLayers()与canvas-zoomToFullExtent()之后紧跟setMapTool调用流程。如果你在多个工具之间切换测距、选点、画点可以用mMapCanvas-unsetMapTool(mPointTool)先释放再设置下一个工具。4.3 坐标转换的常见场景底图是 3857数据是 4326谁该转换在项目 “vsqgisqt实现地图上画点” 中95% 的场景是加载在线底图例如使用 XYZ 图层加载 OpenStreetMap 或国内公共地图服务。这时画布 CRS 会被底图自动设置为 EPSG:3857而业务数据更常用 EPSG:4326 经纬度存储。所以转换方向并不是固定的。如果用户点击的点要写入 4326 图层采用上例中的canvasCrs - wgsCrs。反过来如果要从数据库中读取 4326 坐标并显示在 3857 画布上需要做wgsCrs - canvasCrs转换。明确准则是所有传给QgsFeature的几何坐标其 CRS 必须与图层构造函数中声明的crs参数一致而画布在显示时会自动执行图层 CRS 到画布 CRS 的转换不需要你手动干预。这也解释了为什么很多人画出的点“偏到海里”初始化图层时写了crsEPSG:4326但坐标从event-mapPoint()拿来时还是 3857 值跳过QgsCoordinateTransform直接写入最终点和底图错位几百公里。这块是“地图上画点”需求里最常见的踩坑点。5. 避坑与常见问题排查环境、链接、坐标系三个重灾区在这里我把真实项目中遇到过的高频问题统一列出每一条都按“现象 → 原因 → 解决”的格式呈现这些是网上教程里很少一次性写全的。5.1 报错 cannot find -lpublic链接阶段最常见的意外现象在 VS 输出窗口看到LNK1104 cannot open file public.lib或cannot find -lpublic且发生在链接 QGIS 库之后。原因这不是缺少某个库而是 Qt 的.pro文件或 CMake 配置中LIBS 写入了不存在的库名。常见的触发因素是代码中包含了#include public或 Qt VS Tools 自动生成的链接指令里多了public这个文本。另一个高发场景是你在.pro文件中手误写成LIBS -lqgis_core -lpublic而实际的导入库名是qgis_core.lib。解决打开 QGIS 安装目录下的lib文件夹确认导入库全名一般为qgis_core.lib、qgis_gui.lib在 VS 项目属性“链接器” → “输入” → “附加依赖项”里删掉不存在的public.lib。如果是在 .pro 文件中使用 Qt Creator 构建写法应为LIBS -LC:/Program Files/QGIS 3.28.14/apps/qgis/lib -lqgis_core -lqgis_gui不要拼额外的库名。5.2 程序启动报 qt.qpa.plugin could not find the Qt platform plugin ...现象双击 exe 后弹出错误对话框内容包含could not find the Qt platform plugin windows in 。原因程序运行依赖的qwindows.dll位于 Qt 安装目录的plugins/platforms子目录中。这个路径不在 PATH 环境变量内Windows 无法自动加载它们。注意这个报错在 Linux 设备上表现为 “linuxfb” 字样但 Windows 上同样高频发生。解决有两种处理方式。第一种是开发阶段在 PATH 中加入C:\Qt\Qt5.15.2\5.15.2\msvc2019_64\plugins但注意 platform 插件的搜索逻辑是通过qt_platform_plugin路径简单加入 plugins 目录不一定生效。更可靠的方式是在 main 函数顶部加上QCoreApplication::addLibraryPath(C:/Qt/Qt5.15.2/5.15.2/msvc2019_64/plugins);这样 Qt 运行库会从该额外路径下寻找 platforms 子目录。部署阶段应该把整个plugins目录复制到 exe 所在目录然后在代码里使用相对路径定位它。重要提示如果你的程序把 Qt 的 bin 目录排在 PATH 最前面而 QGIS 自己带了一套 Qt 5.15 运行时两者混用时极易出现版本 A 的库加载版本 B 的插件报错不同但背景相同。开发时保持 PATH 里只放 Qt 或只放 QGIS 自带 Qt能避免很多莫名问题。5.3 Debug 模式崩溃、Release 正常运行时库不一致现象程序使用 Debug 配置编译时运行到QgsApplication::initQgis()崩溃或者 QGIS 面板加载后点击地图直接闪退。换成 Release 配置又一切正常。原因QGIS 官方二进制包是按 Release 模式编译的其链接了 Qt 的 release 运行时库。你的项目如果使用 Debug 模式链接了 QGIS 的 release 库内存管理等机制会发生冲突。最直接的表现是堆损坏导致崩溃即所谓的 “Debug vs Release 混串”。解决项目属性中把“配置类型”切换为 Release或者在 Qt VS Tools 设置中让 Debug 和 Release 都使用同一个 Qt 套件。绝不要尝试去下载一套 “qgis debug 库”因为 QGIS 官方不发布带调试符号的 Windows 二进制。如果必须 Debug 模式调试业务代码那就用调试器附加进程的方式去调试 release 版本这样 QGIS 部分仍是 release 链路而你的业务代码可以通过断点观察。5.4 画出的点不在鼠标点击位置坐标系错乱的三种表象现象点击上海市中心点画到了海里点击北京二环点落到天津方向偏移几十到几百公里不等还有更隐蔽的情况在某些缩放级别下位置正确但缩放后偏移量放大。原因归根到底是数据 CRS 与图层 CRS 不一致。第一类是底图 3857、图层 4326未做转换第二类是图层在dataProvider()-addFeatures()之前没有正确设置 CRS第三类是QgsCoordinateTransform构造时传入的QgsProject作用域对象与当前工程不匹配比如传入nullptr导致某些临时地理变换无法获得参数。解决把每次特征写入前都做一次标准的三步校验检查layer-crs().authid()输出的字符串是否符合预期检查canvas-mapSettings().destinationCrs().authid()在addFeature之前打印feature.geometry().asPoint()以确认坐标值已经符合图层 CRS。这个排查逻辑是 GIS 数据开发的基本功你会发现 90% 的“点位置不对”问题都不是代码逻辑问题而是对 CRS 这条主线理解不透。6. 进阶把点保存到磁盘使用 GeoJSON 与数据库的思路实现了点击画点下一个常规诉求是把这些点持久化保存下来方便下次打开程序继续使用。QGIS 的数据源机制非常成熟常见做法是利用QgsVectorFileWriter将内存图层导出为 GeoJSON 或 Shapefile或是直接使用 PostGIS 数据库图层。#include qgsvectorfilewriter.h QString geoJsonPath D:/data/my_points.geojson; QString errorMsg; QString errorCause; // 第二个参数 UTF-8 可避免中文属性乱码 if (QgsVectorFileWriter::writeAsVectorFormatV3( layer, geoJsonPath, QgsCoordinateReferenceSystem::fromEpsgId(4326), GeoJSON, QgsVectorFileWriter::utf8, errorMsg, errorCause) ! QgsVectorFileWriter::NoError) { qDebug() 保存失败: errorMsg errorCause; }writeAsVectorFormatV3是 QGIS 3.22 之后引入的新接口旧代码中常见的writeAsVectorFormatV2已废弃但仍在源码中可用。如果你用的是 QGIS 3.28就用 V3 版本。注意这里第二个参数我传入了QgsCoordinateReferenceSystem::fromEpsgId(4326)表示无论内存图层的 CRS 是什么写入文件时都转换到 WGS 84 经纬度这样做是为了让导出的 GeoJSON 在 QGIS、Web 地图和绝大多数 GIS 软件中都能通用。保存后可以用 QGIS 桌面版直接打开 GeoJSON 验证画点效果。代码清单可以精简为三步创建点、写入要素、触发保存。如果要支持多用户协作或长期增量管理建议把图层改为 PostGIS 连接例如在构造QgsVectorLayer时传入服务连接字符串。这样每次addFeatures的数据会直接进入 PostgreSQL 表不再有保存文件的问题但这需要你具备数据库运维基础。在这套系统的调试方面要养成的另一个习惯是给QgsMessageLog::logMessage()加上打印输出它会把日志写到 QGIS 的本地目录中。当你怀疑某一步执行但没有报错时使用它比用qDebug()更可靠因为qDebug()在某些 Qt 插件模式下可能被吞掉。这算是解决“程序不崩溃但行为不对”的后悔药。做这个方向久了一个深刻的感受是这套技术栈里真正值钱的部分不是那几行画点的代码而是对 QGIS 运行时、Qt 插件路径与 CRS 转换机制的整体理解。VS 与 QGIS、Qt 的版本组合千变万化但只要你站稳了“数据 CRS 与图层 CRS 必须一致”和“运行时 DLL 必须匹配编译时工具链”这两条主线遇到任何奇怪的报错都不至于慌。希望帮到你。本文还有配套的精品资源点击获取
返回列表