最近在一台 Ubuntu 20.04 的服务器上重新部署 GIS 数据处理环境,核心任务就是把 GDAL 3.6.2 从源码编译安装起来。这个库做遥感与 GIS 的人基本都绕不开,但官方渠道的预编译包不一定能覆盖所有场景,尤其是内网离线环境、定制功能裁剪和版本锁定的需求。这篇就把我这次完整的编译实操过程、依赖链分析、configure 参数取舍,以及中间踩过的坑全部记录下来,给同样需要手动编译 GDAL 的同行做个参考。
1. 为什么要从源码编译 GDAL
1.1 系统包管理器和二进制的局限
很多人会问,apt 安装或者 conda 安装不是更快吗?确实快,但现实中问题不少。Ubuntu 20.04 自带的 GDAL 版本停留在 3.0.4,距离 3.6.2 差了整整三个大版本,部分新栅格驱动、COG 优化和矢量格式支持都没有。直接 apt 装,你只能得到一个很老的库,还得被迫接受系统预编译时的那一套依赖组合。
Python 生态里的 pip wheel 和 conda 包确实方便,pip install gdal==3.6.2 一条命令就能装完。但这类预编译包的痛点在于动态库版本被绑定得比较死,比如很多 wheel 依赖的 PROJ 版本是固定的,你项目里如果同时用了需要更高版本 PROJ 的其他库,就很容易出现动态库冲突。还有一个更现实的问题:很多生产服务器是内网隔离的,根本连不上公共软件源,这时候手里就只有一份源码包,不编译也得编译。
1.2 源码编译带来的实际收益
从源码编译最核心的优势就是可控。你可以指定 GDAL 版本,指定 PROJ、GEOS、OpenJPEG 这些关键依赖的版本,按需开启或关闭驱动,把整个库放进一个独立的前缀目录,用动态库搜索路径来管理,完全不影响系统自带的 GDAL。
举个例子,GDAL 编译时会尝试自动探测系统里的各种可选依赖,比如 PostgreSQL、MySQL、NetCDF、HDF5 等。如果通过 apt 安装的 GDAL,这些驱动一般都会默认开启或关闭,你没有选择权。而源码编译时可以明确用 --without-pg 这类参数把不需要的驱动关掉,既减少编译时间,也避免运行时加载一堆用不到的动态库。对像我这样需要在一个干净环境里搭建长期服务的场景来说,这种干净可控的状态非常重要。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 编译前的依赖准备与关键取舍
2.1 一套最常用的依赖清单
GDAL 3.6.x 的依赖体系主要分成三块:基础工具链、必选依赖、可选功能库。基础工具链就是 gcc、g++、make 这些,Linux 下通常已经装好,没有的话用 apt 一次装齐即可。必选依赖里有几个需要注意:PROJ(投影库,GDAL 3.x 强依赖,版本不能太老)、libtiff、libjpeg、libpng、libcurl、sqlite3、expat、libxml2。可选依赖就比较多了,比如 OpenJPEG 用于 JPEG2000、NetCDF/HDF5 用于科学数据格式、PostgreSQL/MySQL 用于数据库驱动。
我这次编译前的 apt 依赖安装命令是:
bash复制apt update
apt install -y build-essential pkg-config
apt install -y libproj-dev proj-bin
apt install -y libgeos-dev
apt install -y libtiff-dev libjpeg-dev libpng-dev
apt install -y libcurl4-openssl-dev
apt install -y libsqlite3-dev sqlite3
apt install -y libexpat-dev libxml2-dev
apt install -y python3-dev python3-setuptools
这里单独说一下 libproj-dev 的问题。Ubuntu 20.04 上通过 apt 装的 libproj-dev 是 7.2.1,理论上满足 GDAL 3.6 的最低要求(PROJ 6.0+),但实际用起来你会发现某些坐标转换功能行为不太对,而且后续如果系统升级或混用依赖库,很容易出现 PROJ 版本错乱的问题。我这次没有直接用系统自带的 PROJ,而是先编译了 PROJ 9.2.1 再编译 GDAL,一次到位。
2.2 提前解决 PROJ 的版本问题
PROJ 是 GDAL 最关键的依赖,没有之一。GDAL 3.x 在坐标转换、栅格投影、矢量重投影等操作上都深度依赖 PROJ,并且需要 PROJ 的 proj.db 数据库。如果系统自带的 PROJ 版本过旧,编译 GDAL 时会出现经典的 configure: error: PROJ 6.0 or later is required,但即便版本刚好卡在 6.x,运行某些功能时也可能报数据库缺失或能力不足的错误。
编译 PROJ 9.2.1 的流程比较简单,它从 8.x 开始默认使用 CMake 构建,需要在之前 apt 安装的依赖基础上再确保 libsqlite3-dev、libtiff-dev 已装好。主要步骤是获取源码、解压、用 CMake 配置到独立前缀目录、编译安装。命令大致如下:
bash复制wget https://download.osgeo.org/proj/proj-9.2.1.tar.gz
tar -zxvf proj-9.2.1.tar.gz
cd proj-9.2.1
mkdir build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=/opt/proj-9.2.1 -DBUILD_SHARED_LIBS=ON
make -j$(nproc)
make install
编译完 PROJ 之后,需要把它的动态库路径告诉系统,否则后续 GDAL configure 探测 PROJ 时可能找不到。在 /etc/ld.so.conf.d/proj-9.2.1.conf 里写入 /opt/proj-9.2.1/lib,然后执行 ldconfig,再验证一下 projinfo --version 是否能正常输出。这一步做扎实了,后面 GDAL 的 configure 会省心很多。
2.3 configure 阶段必看的参数说明
GDAL 的 configure 脚本是编译安装的核心决策点。它本质上是一个巨大的依赖探测脚本,会逐个检查系统中有没有对应的库、版本是否达标,然后生成最终的 Makefile。所以 configure 参数选得对不对,直接关系到最终库的功能边界和能否成功编译。
我这次实际使用的 configure 命令长这样:
bash复制./configure \
--prefix=/opt/gdal-3.6.2 \
--with-proj=/opt/proj-9.2.1 \
--with-geos \
--with-curl \
--with-sqlite3 \
--with-expat \
--with-xml2 \
--with-python \
--with-openjpeg=/usr \
--with-libtiff=/usr \
--with-jpeg=/usr \
--with-png=/usr \
--without-pg \
--without-mysql
这里的每个参数都有明确目的。--prefix 指定安装目录,不直接塞进 /usr/local,方便多版本共存和后续卸载。--with-proj 指向刚才编译好的 PROJ 9.2.1,--with-geos 开启几何操作支持,没有 GEOS 的话很多矢量空间分析功能会缺失。--with-curl 用来支持 HTTP/HTTPS 数据源,现代 GDAL 访问在线瓦片、WMS、对象存储都需要它。--with-sqlite3 是 GeoPackage 格式的硬依赖,这个必须开。
有几个参数我特意选择了关闭。数据库驱动如果项目里用不到 PostgreSQL,那就是纯纯的编译负担,而且还需要额外安装 libpq-dev,所以用 --without-pg 关掉。MySQL 同理,--without-mysql 能减少一次依赖检索。这种按需裁剪的思路,能让编译时间缩短不少,运行时的动态库依赖也更简洁。
3. 从下载源码到编译安装的完整流程
3.1 获取 GDAL 3.6.2 源码
源码获取方式有两种,一种是从 GitHub Releases 页面下载,另一种是从官方镜像站下载。个人经验是下载官方发布的 tar.gz 归档比 git clone 更靠谱,因为官方 Release 包已经完成了子模块整合和文档生成,开箱即可编译,而 git clone 的是开发分支,可能需要额外处理子模块版本问题。
下载和解压的命令如下:
bash复制wget https://download.osgeo.org/gdal/3.6.2/gdal-3.6.2.tar.gz
tar -zxvf gdal-3.6.2.tar.gz
cd gdal-3.6.2
解压后建议先看一眼 NEWS.md 或 VERSION 文件,确认版本号正确。GDAL 的源码目录结构很清晰:frmts/ 是栅格驱动,ogr/ 是矢量驱动,alg/ 是算法实现,apps/ 是命令行工具,swig/ 是各种语言绑定。了解这些目录有助于后续排查问题,比如遇到某个格式驱动编译失败,可以直接到对应目录下看编译日志。
3.2 先编译 PROJ 9.2.1 作为前置
前面提到我选择了先编译 PROJ 9.2.1。这里把完整流程再说细一点。PROJ 9.x 对系统的要求比 7.x 高一些,编译前最好确认 sqlite3 和 libtiff 的开发包都在。CMake 配置时,除了指定安装前缀,还可以加上 -DCMAKE_BUILD_TYPE=Release 来使用优化编译选项。
我遇到的第一个坑出现在 cmake 阶段:提示 TIFF 库找不到。但其实系统里已经通过 apt 装了 libtiff-dev。后来发现是 CMake 在 64 位系统上默认搜索的路径不包含 /usr/lib/x86_64-linux-gnu,需要显式指定。解决方法是设置环境变量:
bash复制export CMAKE_INCLUDE_PATH=/usr/include/x86_64-linux-gnu
export CMAKE_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu
设置完重新 cmake,PROJ 9.2.1 就顺利编译通过。编译耗时在十几分钟左右,取决于机器性能。安装完成后用 projinfo EPSG:4326 简单验证一下,能正常输出坐标系信息就说明 PROJ 工作正常。
3.3 正式配置与编译 GDAL
进入 GDAL 源码目录后,执行前面准备的 configure 命令。configure 脚本的输出信息量非常大,重点看最后几行的 Summary,它会明确列出哪些功能被开启、哪些被禁用。比如 Python bindings: yes、GRASS support: no、PostgreSQL support: no 等。如果某项你想用的功能显示 no,说明对应的依赖探测失败,需要回去装依赖再重新 configure。
configure 成功后会生成 Makefile,然后执行编译:
bash复制make -j$(nproc)
这里想提醒一个很多人栽过的坑。GDAL 的源码量非常大,make -j$(nproc) 在高核数机器上虽然理论上更快,但内存占用也会成倍上升。我在这台 32 核的机器上第一次直接用 make -j32,编译到一半直接 OOM 被系统 kill 了。后来改用 make -j12,虽然慢一点但稳定跑完。如果你的服务器内存小于 8GB,建议用 -j4 或 -j2,不要盲目追求并行度。
整个编译过程我这边大概花了十五分钟。期间屏幕上会滚过大量的 CC、CXX、LD 信息,看到某些 frmts 或 ogr 驱动的编译日志很正常,不用担心。
3.4 安装收尾、动态库与 Python 绑定
编译完成后执行 make install,GDAL 的主程序、库文件、头文件、数据文件和 Python 绑定会安装到 /opt/gdal-3.6.2 目录下。安装完成后有两个收尾操作不能省略,否则后面用的时候会一脸懵。
第一是动态库路径配置。GDAL 编译出来的 libgdal 默认装在 /opt/gdal-3.6.2/lib,系统默认的动态库搜索路径里不包含它,所以直接运行 gdalinfo 会报 error while loading shared libraries: libgdal.so.32: cannot open shared object file。解决办法是新建一个 ldconfig 配置文件:
bash复制echo '/opt/gdal-3.6.2/lib' > /etc/ld.so.conf.d/gdal-3.6.2.conf
ldconfig
第二是环境变量配置。如果希望所有用户都能直接用 gdal-bin,可以把 /opt/gdal-3.6.2/bin 加进 PATH,把 /opt/gdal-3.6.2/lib 的 pkgconfig 路径加进 PKG_CONFIG_PATH,同时按需设置 PROJ_LIB 指向 PROJ 的数据库目录。我习惯在 /etc/profile.d/gdal.sh 里统一写入:
bash复制export PATH=/opt/gdal-3.6.2/bin:$PATH
export LD_LIBRARY_PATH=/opt/gdal-3.6.2/lib:$LD_LIBRARY_PATH
export PKG_CONFIG_PATH=/opt/gdal-3.6.2/lib/pkgconfig:$PKG_CONFIG_PATH
export PROJ_LIB=/opt/proj-9.2.1/share/proj
Python 绑定这块,如果 configure 阶段检测到了 python3-dev 并开启了 --with-python,那么 make install 时通常已经顺带处理了。如果没有自动安装成功,也可以手动到 swig/python 目录下编译:
bash复制cd swig/python
python setup.py build
python setup.py install
手动编译的好处是能看到更详细的错误日志,排查起来更直接。
3.5 验证安装结果
安装完成后一定要做完整验证,不能只看到文件存在就认为成功。我习惯按下面几个层次检查。
先看命令行工具版本:
bash复制gdalinfo --version
正常会输出 GDAL 3.6.2, released 2023/02/10 之类的信息。再看 gdal-config 的版本和动态库依赖:
bash复制gdal-config --version
ldd /opt/gdal-3.6.2/bin/gdalinfo | grep 'not found'
ldd 的检查很关键,如果输出里有 not found,说明某个动态库链接有问题,必须解决。再验证 Python 绑定:
bash复制python3 -c "from osgeo import gdal; print(gdal.__version__)"
最后做一个最朴实的功能验证:打开一个真实的栅格文件读取元数据,或者用 ogrinfo 读取一个 Shapefile 或者 GeoJSON。如果这些都能正常跑通,说明 GDAL 的核心功能、PROJ 数据库、矢量驱动和栅格驱动都工作正常。光依赖的库版本不匹配,即使上述命令能输出,运行时也可能报某个驱动加载失败,所以最好用真实文件走一遍。
4. 常见问题与排查技巧实录
4.1 高频报错快查表
编辑安装过程中最容易遇到的几类问题,我做了个速查表方便快速定位:
| 报错现象 | 根本原因 | 解决思路 |
|---|---|---|
| configure 提示 PROJ not found 或版本过旧 | 系统没有 libproj-dev,或 PROJ 安装路径不在搜索范围 | 编译安装 PROJ,设置 PKG_CONFIG_PATH 指向 proj.pc 所在目录 |
| 编译时 ld 报找不到某库文件 | 对应依赖的 dev 包未安装或路径未识别 | apt 安装缺失 dev 包,或用 -I、-L 手动指定 |
make -j 过程中 OOM killed |
并行编译任务数过高、内存不足 | 降低并行数,比如 make -j4 |
| 运行 gdalinfo 报 shared library not found | 安装目录不在系统动态库搜索路径 | 添加 ld.so.conf.d 配置并执行 ldconfig |
| 运行时提示 Cannot find proj.db | PROJ_LIB 环境变量未指向 proj.db 所在目录 | 找到 proj.db 路径后 export PROJ_LIB |
| Python import osgeo 失败 | configure 阶段 Python 绑定未开启或 python3-dev 缺失 | 安装 python3-dev,重新 configure 检查 Python bindings 选项 |
| 某些驱动在 gdalinfo --formats 中缺失 | configure 时对应依赖未探测到,编译被裁剪 | 回到依赖安装和 configure 环节,确定所需驱动并补齐依赖 |
| 编译过程报语法错误但依赖已安装 | 多个 PROJ/依赖版本混用,头文件指向混乱 | 统一依赖版本,清理系统里的旧 GDAL/PROJ 残留,必要时独立前缀目录重新编译 |
这些坑里,最典型的是头文件与库文件版本混用。检查时可以用 pkg-config --modversion proj 看 PROJ 版本,用 which gdal-config 看 GDAL 配置工具来自哪里,加上 ldd 追踪运行时实际加载了哪个动态库,基本能定位到问题根源。
4.2 内网离线编译场景的特别建议
如果你的编译环境是完全的内网隔离,拿不到公共源,那第 2 节前列的那些 apt 安装命令就不好用了,需要在有网的环境里预先下载好所有依赖包和源码包。这种情况下最省事的方案是准备齐全所有 tar.gz 源码包,在离线机器上按依赖顺序从底往上编译,顺序大致是:sqlite3/curl/tiff/jpeg/png 等基础库、PROJ、GEOS、OpenJPEG,最后是 GDAL 本身。
离线编译还有一个细节值得留意:源码包之间也存在版本依赖关系。比如 PROJ 9.2.1 要求 SQLite3 版本不能太老,如果你的离线源里只有旧版 sqlite3,PROJ 的数据库构建阶段就可能失败。所以离线准备阶段一定要把几个关键依赖的版本往上提一点,宁新勿旧。另外,pre-built 的 Python wheel 在离线环境也可以单独下载,之后用 pip install --no-index --find-links=./packages gdal==3.6.2 安装,但注意 wheel 与系统库的兼容性问题,如果能源码编译还是尽量走完整编译流程。
4.3 多版本共存时如何优雅切换
如果你手里同时存在系统的 GDAL、conda 的 GDAL、以及自己编译的 /opt/gdal-3.6.2,Path 和动态库搜索路径的优先级就变得特别重要。环境变量顺序稍有不对,你以为你用的是自己编译的版本,实际上程序加载的可能是系统里另一个版本。这个问题我在实际项目里踩过好几次,非常隐蔽。
我的做法是用一个独立的脚本来管理环境,平时不全局设置 LD_LIBRARY_PATH,只在需要时执行 source /opt/gdal-3.6.2/env.sh 切换。脚本内容就是把 PATH、LD_LIBRARY_PATH、PKG_CONFIG_PATH、PROJ_LIB 全部指向 /opt/gdal-3.6.2 和 /opt/proj-9.2.1。这样全局环境干净,切换到编译版的 GDAL 时也不会和系统的 GDAL 打架。
提示:检查当前 shell 实际用的是哪个 GDAL,可以依次执行
which gdalinfo、gdal-config --version、ldd $(which gdalinfo) | grep gdal,三者要对应上才是同一个版本链。
这套流程我前前后后在多台机器上重复走了好几遍,最深的体会就是:源码编译 GDAL 的难点几乎全在依赖管理上,GDAL 本身的编译反而不太容易出错。版本冲突、库搜索路径、系统残留这几个问题虽然烦人,但只要把独立前缀目录的规范坚持下来,基本就杜绝了后续的大多数麻烦。如果你也准备长期维护 GIS 环境,建议从一开始就给 GDAL 和 PROJ 各建一个干净的独立安装目录,再写清楚环境切换脚本,这会成为你整个数据基础设施里最省心的部分。
