简介:这是一套基于QGIS源码编译生成的完整工程文件,面向需要二次开发或定制GIS功能的开发者。项目通过CMake与Visual Studio 2019编译,并集成Qt 5.15.2环境,亲测可正常打开运行。工程内含有常用的GIS模块,用户可根据实际需求自行增加、删除或修改对应模块,灵活性较高。资源包共8183个文件,约473MB,涵盖svg矢量图标、头文件、Python脚本、DLL动态库、PNG图像以及QGIS特有的qgm工程文件等,结构完整,能支撑从界面到核心算法的二次开发。目前已有1210人浏览学习。下载后无需再手动配置第三方依赖和解决编译报错,直接打开工程文件即可启动,特别适合希望快速进入QGIS源码层进行功能扩展或学习编译流程的开发者,能极大节省环境搭建与排错时间。
1. 拿到编译好的QGIS工程文件:它不只是安装包,而是一个能改的起点
先纠正一个预期:编译好的QGIS工程文件,重点不是“安装”,而是“工程”。它通常是一套带着 CMakeLists.txt 或 Qt Creator 工程文件的源码目录,外加已经跑通配置的构建目录。你能在 Qt Creator 里打开它、改某个功能模块、重新编译出带自己定制逻辑的 QGIS,而这在装好的官方安装包里几乎是不可行的。它适合四类人:需要把 QGIS 嵌进自己产品里的集成工程师、要裁剪体积或功能的交付团队、想在离线环境把依赖一次配齐的部署人员,以及准备做二次开发的插件作者。先说清楚,这个方向值不值得投入:如果只是画地图,安装包更快;但当你需要“自家的 QGIS”,这份工程文件才是真正的地下室。
2. 拆开编译好的QGIS工程文件:先从模块归属和目录结构建立认知
很多人在 Qt Creator 里一打开工程就急着点构建,结果几分钟后被满屏的 include 错误拍回桌面。我一般建议先花 20 分钟把目录结构走一遍,搞清楚哪些功能模块在哪个子工程里,后面编译和裁剪都会顺很多。
2.1 目录地图:src/core、src/gui、src/app 各管什么
这份编译好的 QGIS 工程文件,目录布局通常沿用官方源码组织方式。核心模块分三层,顺序依赖很清楚:
| 目录 | 对应库 | 职责 | 常见代码入口 |
|---|---|---|---|
src/core | qgis_core | 不依赖界面的底线能力 | QgsProject、QgsVectorLayer、QgsExpression、QgsCoordinateReferenceSystem |
src/gui | qgis_gui | 依赖 Qt Widgets 的绘图与交互 | QgsMapCanvas、QgsMapTool、图层树、属性表控件 |
src/app | qgis_app | 主程序,菜单、工具栏、命令行分发 | main.cpp、QgisApp、QgisAppPluginManager |
注意依赖方向永远是从上往下:src/gui依赖src/core,src/app又依赖前两者。你如果只想把 QGIS 的地图引擎嵌进自己的桌面应用,完全可以只编译qgis_core和qgis_gui,手动把它们当成两套动态库链接进来。很多“把 QGIS 当 SDK 用”的工程,项目文件里根本没有src/app这个子目录。
除了这三层,还有两个你迟早会碰到的目录:python/放的是 Python 绑定相关代码,plugins/是各类 C++ 插件(比如处理 GDB 的ogr提供者插件、几何图形插件 GeometryShapes)。这份工程文件的“基本功能模块”,在我经手的项目里基本指:矢量编辑、属性表、表达式引擎、地图画布、工程保存与恢复、OGR/GDAL 数据打开能力、坐标系转换。这些正好对应src/core和src/gui的大部分编译目标。
2.2 功能模块放在哪:应用内置和插件式加载是两回事
我在指导新人时发现一个普遍的误解:以为功能模块都以插件形式加载。实际上 QGIS 功能模块分两种组织形态,各有千秋:
一种是“编译进主程序”的模块,比如矢量编辑、字段计算器、表达式公式设置界面。这类模块写在src/app或直接作为qgis_gui的类存在,编译后就在 EXE 里,你在分发时不用额外带文件。代价是任何改动都要重新编译主程序,编译时间通常以小时计。
另一种是“插件式模块”,比如数据源提供者、 GeometryShapes 这类几何工具插件。它们被编译成独立的 DLL 或.so文件,运行时从插件目录动态加载。好处是修改一个插件只需要编译对应小工程,分发时也灵活。坑在于运行时的插件搜索路径非常敏感,编译出来加载不到基本模块,一半原因是插件目录路径写错,另一半是依赖库没打完。
判断一个功能模块属于哪种方式有个笨办法:在 Qt Creator 里打开函数定义,如果代码在src/app或src/gui里直接能看到,那就是内置;如果只看到接口声明,具体实现在plugins/子目录里,那就是插件式。比如 FileGDB 的读取能力,对外的接口在src/core/providers/ogr/,而真正调用 ESRI 库的逻辑在src/providers/ogr里,这幅编译好的工程文件会把这些边界画得很清楚。
2.3 在 Qt Creator 里拉起工程的最小配置清单
拿到工程文件后不要急着 Configure,先确认三件事:Qt 版本是否匹配、CMake 工具链是否是工程锁定的那套、第三方依赖目录是否还在原路径。
我用一份典型的 Windows 工程配置命令来说明参数意义,这段脚本假设你已经装好 Qt 和依赖库:
cmake -S . -B build-qtcreator ^ -DCMAKE_PREFIX_PATH=C:/Qt/5.15.2/msvc2019_64;C:/deps ^ -DCMAKE_BUILD_TYPE=Release ^ -DCMAKE_CXX_COMPILER=cl ^ -DQGIS_CUSTOM_QT_INCLUDE=C:/Qt/5.15.2/msvc2019_64/include ^ -DWITH_APP=ON ^ -DWITH_CORE=ON ^ -DWITH_GUI=ON cmake --build build-qtcreator --config Release --parallel 8说明一下参数:CMAKE_PREFIX_PATH是告诉 CMake 去哪里找 Qt 和第三方库,路径里包含 Qt 的 CMake 配置目录和依赖库根目录,这是整个配置过程最容易出错的点;CMAKE_BUILD_TYPE=Release决定优化级别,调试阶段可以换 Debug,但注意 Debug 和 Release 的依赖库混用会导致链接失败;WITH_APP/CORE/GUI是功能模块开关,想简化工程就关掉 APP,保留 CORE 和 GUI 嵌入自己程序。
如果工程文件是 Qt Creator 的.pro工程,你需要打开.pro文件确认QT += core gui widgets和CONFIG += c++17这些基本信息,然后指定构建目录与 Qt Kit。同一个源码目录,在区分 Debug 和 Release 时,我的习惯是建build-debug、build-release两个目录,避免 CMake 缓存互相污染。
3. 把依赖环境理顺:Windows 和 Linux 编译前的两套处理路径
很多编译好的 QGIS 工程文件拿回来第一眼看上去没问题,但一编译就报cannot find -lxxx。这基本不是工程文件的问题,而是依赖库的搜索路径没有交代清楚。编译 QGIS 不是编译一个普通的小工具,它的依赖分三层:Qt 基础、地理数据底层库、以及可选功能模块的第三方库。
3.1 依赖分组:没有 GDAL/PROJ 这套底层,core 模块根本编译不过
我按依赖的必要程度把它们分成三组,这套分组也适用于判断工程文件是否完整:
第一组是必要依赖,包括 Qt(Core、Gui、Widgets、Xml、Sql)、GDAL/OGR 数据访问库、PROJ 坐标系库。没有 GDAL,QgsVectorLayer连 Shapefile 都打不开;没有 PROJ,坐标系变换模块直接报错。第二组是常用功能依赖,包括 GEOS(空间几何运算)、SQLite(空间索引和属性数据库)、Expat(XML 解析)。第三组是可选功能依赖,比如 PostgreSQL 驱动、MDAL 洪水模型库、NetCDF,这些没装也能编过,只是对应功能模块会被禁用。
在 Linux 上检查依赖最直接的办法是用发行版包管理一起装上,比如在 Ubuntu 里常用的安装命令是:
sudo apt install qtbase5-dev qt5-qmake qttools5-dev-tools \ libqt5svg5-dev libqt5sql5-mysql libqt5xmlpatterns5-dev \ libgdal-dev libproj-dev libgeos-dev libsqlite3-dev命令里的每组包名按依赖分组对应:qtbase5-dev提供 Qt 核心库,libgdal-dev和libproj-dev提供地理数据底层能力,libgeos-dev提供空间运算几何内核。注意版本匹配问题:GDAL 和 PROJ 各有自己的主版本号,QGIS 某版本对它们的 API 版本有硬性要求,如果系统源里的版本太新或太旧,编译时会出现函数签名对不上的情况,我的排查顺序是先看工程根目录CMakeLists.txt里的最低版本提示,而不是盲目升级依赖库。
3.2 Windows 下编译:MSVC 编译器和 Qt 目录是命门
Windows 上最常见的翻车点是“编译器不匹配”。这份工程文件如果是在 MSVC 环境下生成的,你强行用 MinGW 编译器打开,链接阶段会报大量无法解析的外部符号,因为 Qt 库本身是按 MSVC ABI 编译的,两种编译器的二进制互不兼容。
我常用的 Windows 编译姿势是先设置一个环境变量脚本,把 Qt 和依赖库路径固定住,再跑 CMake:
set PATH=C:/Qt/5.15.2/msvc2019_64/bin;C:/deps/bin;%PATH% set QTDIR=C:/Qt/5.15.2/msvc2019_64 cmake -S . -B build-msvc ^ -DCMAKE_PREFIX_PATH=C:/Qt/5.15.2/msvc2019_64;C:/deps ^ -DCMAKE_GENERATOR="Visual Studio 16 2019" -A x64 ^ -DWITH_BINDINGS=OFF cmake --build build-msvc --config Release --parallel %NUMBER_OF_PROCESSORS%参数解释:-DWITH_BINDINGS=OFF表示不编译 Python 绑定,如果目标只是跑通桌面程序和 C++ 开发,关掉它能省掉一长串 SIP/PyQt 依赖;生成器选 Visual Studio 与工程文件头里声明的 Qt 版本对应,-A x64指定 64 位架构。编译结束后,运行 GOST 目录下的qgis.exe,如果提示找不到 Qt DLL,原因就是 PATH 环境变量没有带上 Qt 的 bin 目录。
3.3 Linux 下编译:用系统 Qt 还是手动装 Qt 的取舍
Linux 编译 QGIS 工程文件通常会遇到两个分支:用发行版自带 Qt,或手动安装 Qt 离线包。用系统 Qt 的好处是依赖完整,apt install一条龙把 GDAL、PROJ、GEOS 全装完,生成 Makefile 简单;缺点是 Qt 版本被发行版锁定,如果 QGIS 要求 Qt 5.15 而系统源里只有 5.14,CMake 配置阶段就会失败。
我的处理方法是:先用快速命令探测系统 Qt 版本,低于要求的版本就走手动安装。
qmake -v cmake -S . -B build-linux \ -DCMAKE_PREFIX_PATH=/opt/Qt/5.15.2/gcc_64 \ -DCMAKE_BUILD_TYPE=Release \ -DGDAL_CONFIG=/usr/bin/gdal-config \ -DPROJ_INCLUDE_DIR=/usr/include \ -DPROJ_LIBRARY=/usr/lib/x86_64-linux-gnu/libproj.so cmake --build build-linux --parallel 4解释一下:qmake -v用来快速判断 Qt 版本是否够用;GDAL_CONFIG是 QGIS 工程查询 GDAL 安装位置的依赖工具,如果这个路径不对,CMake 会提示找不到 GDAL;PROJ_INCLUDE_DIR和PROJ_LIBRARY是手动指定头文件和静态库路径的方式,适合自动检测失败时写死。这里把 Release 加上后,Linux 的编译通常需要 40 分钟到 1 小时,取决于机器核数,建议第一次编译就用-j4限制并发数,别把机器拖到没响应。
4. 增删功能模块:把一份标准 QGIS 改成你自己的定制版
工程文件的价值不在“能编过”,而在“能改”。基本功能模块齐全之后,紧接着要考虑的是怎么按业务需求裁剪或增加。我在这里给出三套具体操作,按“加模块—减模块—加自定义逻辑”的顺序展开。
4.1 基本功能模块清单:先确认你手里有哪些能力
一份标准的 QGIS 功能模块清单,在我接触到的工程文件里通常包含这些能力:工程文件的创建、打开、保存与恢复;矢量图层(Shapefile、GeoJSON、GPKG)的加载与编辑;属性表的字段查看、字段值和表达式批量赋值;栅格图层显示与基础配色;地图坐标参考系的分配和转换;空间书签、比例尺、图例的出图能力;内置表达式引擎的基础函数库。
这些模块的共同点是它们都挂在src/core或src/gui的主干上,属于“基本功能”。如果你是嵌入式集成场景,比如只做一个属性录入工具,连出图和书签都可以去掉;如果是测绘内业软件,表达式引擎和属性表必须保留。建议你先在源码树里用查找功能搜索QgsExpression类在多少个文件被引用,搜索数量越多,说明表达式功能被绑得越紧,想单独砍掉的代价越大。
4.2 增加一个功能模块:以自定义表达式函数为例
这一节的实际操作我选择在表达式引擎里注册一个自定义函数,这也是 QGIS 扩展里最入门但最常用的功能。它对应“矢量 表达式 公式 设置”这个检索词,很多业务场景比如批量给字段赋值、按时间规整属性值,都会用到这类自定义函数。
以 C++ 方式在工程文件里加一个函数,我最常写的模板是这样:
#include "qgsexpression.h" #include "qgsexpressionfunction.h" #include <QDateTime> static QVariant f_now_iso(const QgsExpressionFunction::Context& ctx, const QVariantList& values) { Q_UNUSED(ctx); Q_UNUSED(values); return QDateTime::currentDateTime().toString(Qt::ISODate); } void registerCustomFunctions() { QgsExpressionFunction* fn = new QgsExpressionFunction( QStringLiteral("now_iso"), // 函数名,表达式中写 now_iso() 0, // 参数个数,0 表示无参 f_now_iso, QStringLiteral("自定义函数"), QStringLiteral("返回当前时刻的 ISO 8601 时间字符串") ); QgsExpression::registerFunction(fn); }这段代码里最关键的是QgsExpression::registerFunction(fn),它把函数注册进全局表达式引擎,注册之后你在字段计算器里写now_iso()就能得到当前时刻的字符串。需要注意的点:函数名不能和内置函数重名,否则注册会失败且不报错;参数个数声明为 0,调用时括号不能省略;如果用 Python 去调用这个 C++ 注册的函数,需要清空表达式引擎缓存或重启程序才能生效。
4.3 裁剪功能模块:关闭 Python 绑定和分发包体积
很多交付场景不需要 Python 控制台,也不需要内置浏览器。这时候我一般会在 CMake 配置阶段直接关掉相关模块,而不是去删源码文件。用开关裁剪的好处是,万一以后要恢复功能,重新打开开关编译即可,不必回滚代码。
cmake -S . -B build-mini ^ -DCMAKE_BUILD_TYPE=Release ^ -DWITH_BINDINGS=OFF ^ -DWITH_ANALYSIS=OFF ^ -DWITH_SERVER=OFF ^ -DWITH_GRASS=OFF ^ -DWITH_CUSTOM_WIDGETS=OFF ^ -DWITH_QT5SERIALPORT=OFF cmake --build build-mini --config Release --parallel 4每个开关对应一个明确的功能模块:WITH_BINDINGS控制 Python 绑定,关掉后不再生成 PyQGIS 模块;WITH_SERVER控制 QGIS Server 的网页地图服务能力,桌面端通常用不到;WITH_GRASS是 GRASS GIS 集成接口,属于较大且独立的重量级模块;WITH_ANALYSIS是空间分析算法库。这些开关关掉之后,编译产物里的库文件会少几个,需要分发的目录也干净很多。但注意不要关掉WITH_CORE或WITH_GUI,那是主程序的基础,关掉后整个界面都拉不起来。
5. 避坑:编译好的QGIS工程文件最常见的5个翻车现场
这一章全部来自我自己的编译血泪经验,按“现象—原因—解决”的方式写。遇到问题先对照现象,不要急着重装依赖,多半是路径或 ABI 的问题。
5.1 现象:CMake 配置时报错找不到 Qt5,明明 Qt 已经装了
我最早编译 QGIS 时遇到过这个玄学问题,Qt 明明装在C:/Qt/5.15.2,CMake 始终坚持找不到。原因基本只有一个:CMAKE_PREFIX_PATH没有指到 Qt 的编译器特定目录,比如 Qt 安装在 MSVC 环境,就必须写C:/Qt/5.15.2/msvc2019_64,而不是C:/Qt/5.15.2。解决方法是把qt5-config.cmake所在目录写进前缀路径,也可以用-DQt5_DIR=...直接指到库的lib/cmake/Qt5子目录,我在只装了一套 Qt 时都用后者,逻辑更明确。
5.2 现象:编译进行到一半,链接时报无法解析的外部符号
这是一个非常具体的链接失败场景,报错内容会指向某个类的方法,比如QgsVectorLayer::addFeature。原因通常是工程文件里的头文件路径和库文件路径不一致,或者混用了 Debug 和 Release 的 Qt 库。我遇到最多次的是 Qt 自带的Qt5Cored.lib和Qt5Core.lib混用。解决方法是检查 CMake 缓存里的CMAKE_IMPLICIT_LINK_DIRECTORIES和CMAKE_BUILD_TYPE是否一致,Debug 工程必须让 Qt 依赖也走d后缀库,不要手动往 PATH 里同时塞两个版本的 Qt 目录。
5.3 现象:编译成功,但运行时报“无法加载地图数据源,缺少 GDAL provider”
这种报错往往是图源数据加载能力缺失,不是 QGIS 主体的问题。原因在于 GDAL 本身是插件式驱动,即使主程序编译成功,如果gdalplugins目录没放到程序运行时搜索的路径下,或者 GDAL 从源码编译时没开启特定驱动,Shapefile 能打开但 GDB 打不开,就是驱动的缺失。解决方法是先确认工程文件编译后产出的“插件目录”,把C:/deps/bin/gdalplugins(或源码里src/providers/ogr编译出的资源目录)放进可执行文件旁的plugins下,同时通过环境变量GDAL_DRIVER_PATH指向该目录。
5.4 现象:编译好的 QGIS 打开 GDB(File Geodatabase)失败,但官方安装包能打开
这个问题在热词“qgis打开gdb”里很突出。很多企业手里有 ESRI File Geodatabase 数据,官方安装包里默认带了 FileGDB 驱动,而自己编译的工程文件默认不包含该驱动,或仅包含 OpenFileGDB 只读驱动。原因在于 GDAL 的 FileGDB 读写驱动需要 ESRI SDK 或 GDAL 以特定选项编译。解决方法是检查gdalinfo --formats | grep -i gdb,如果只有OpenFileGDB说明只能读,要写回必须重新编译 GDAL 并启用 FileGDB 驱动,同时将相关 DLL 一并打包进最终分发的插件目录。
5.5 现象:表达式计算器中字段和值下拉列表为空,自定义函数注册后不生效
有时表达式对话框能打开,但“字段和值”面板里看不到当前图层字段,写好的表达式批量赋值也总返回空。原因通常是该矢量图层没有正确加载到有效数据源,或者表达式引擎的全局变量缓存未刷新。解决方法是先在图层属性里确认要素数量是否显示正常,然后调用QgsExpression::cleanRegisteredFunctions()并重新注册;如果字段列表为空,八成是图层数据源 URL 未正确设置,需要重新设置QgsVectorLayer的数据源字符串,比如file:///D:/data/roads.shp?delimiter=,&xField=longitude&yField=latitude。
6. 验证编译好的QGIS工程文件:一个最小回归清单和部署习惯
先说结论:验证一个编译好的 QGIS 工程文件,重点不是看它能不能打开窗口,而是看核心模块是否真的可用。我每次交付前都固定跑一遍下面这套清单,时间成本控制在 15 分钟以内。
我习惯用命令行模式先做无界面冒烟测试,确认 core 模块能起来:
./build/bin/qgis_process --help ./build/bin/qgis_process listqgis_process是 QGIS 3.x 里提供命令行工具的模块,--help能输出信息说明 main 模块正常加载,list能列出注册的各类算法模块。如果提示找不到库,优先检查 LD_LIBRARY_PATH 或 Windows 的 PATH 是否包含了构建目录下的库目录。
接着用一段最小 PyQGIS 脚本验证数据模块和表达式模块:
from qgis.core import * QgsApplication.setPrefixPath("build/bin", True) QgsApplication.initQgis() layer = QgsVectorLayer("D:/data/roads.shp", "roads", "ogr") print("valid:", layer.isValid()) print("count:", layer.featureCount()) expr = QgsExpression("length(geometry(@layer))") print("expr ok:", expr.hasEvalError()) QgsApplication.exitQgis()脚本里的setPrefixPath是指向 QGIS 安装目录,如果编译出的库在build/bin,就写这个相对路径;QgsVectorLayer的第三个参数ogr指定驱动名,测试时能同时验证 GDAL 驱动是否找齐;QgsExpression构造时可以顺手用hasEvalError()判断表达式引擎有没有初始化错误。
最后再说一个我自己的习惯:我通常在打包前重新用 Release 配置完整编译一次,然后清空build-debug目录,避免把调试符号和发布库混在一起。清空构建目录听起来有点冒险,但这正是避免“编译期正常、运行为零”混乱的后悔药。前面提到的表达式注册函数、裁剪开关、插件路径,我全部会整理成一个 README 放在工程目录里,下次维护时不用翻旧笔记。
希望这份从编译到验证的路径能帮到你。
本文还有配套的精品资源,点击获取