已编译GDAL 1.11实践:老项目兼容、动态链接与坐标系锁定

📅 发布时间:2026/10/15 22:04:40
已编译GDAL 1.11实践:老项目兼容、动态链接与坐标系锁定
简介已编译的GDAL gdal111 是一份预编译好的地理空间数据处理库面向需要快速集成GDAL/OGR功能的C开发者及GIS数据处理人员省去源码编译与依赖配置。压缩包共241个文件、约3.46MB包含72个头文件、89个HTML接口文档、28个CSV坐标参数、22个EXE命令行工具以及dll/lib动态与静态链接库并按lib、include、bin、data、html五个目录清晰组织十分方便按需取用。其中lib和include提供二次开发所需的库与API声明可直接在Visual Studio等环境链接bin下的工具支持栅格/矢量格式转换、裁剪、重投影可处理Shapefile、GeoJSON、TIFF等常见格式data内含投影与基准面转换参数html为离线参考手册。整套包解压后配置系统路径即可调用适合用于搭建地理信息实验环境、验证算法、课程设计或项目集成。该包已有297人学习下载体积轻巧、组件完整值得GIS开发者收藏备查。1. 已编译的 GDAL 1.11省掉的是编译时间锁死的是坐标系选型拿到一个已经编译好的 GDAL 1.11 包多数人的第一反应不是放松而是怀疑这东西到底帮我编了哪些驱动里面嵌了哪个版本的 PROJ链接上去会不会和系统里的新版 GDAL 打架这几个问题问得越细越说明你之前被源码编译坑过。GDAL 的源码编译本身不难难的是它背后一串依赖——PROJ、GEOS、SQLite、TIFF、CURL任何一个版本错位configure 就要反复重来三个小时起步。已编译的 GDAL 1.11 解决的就是这个问题别人已经把最痛苦的依赖缝合阶段做完了你拿到的是可以直接放进工程的库和头文件。但代价也很实在——/usr/lib里若是存在新版 GDAL你需要花点心思管理动态库路径否则运行期会拉到错的版本后面我会把这一整套链接排查过程完整写出来。2. 老项目为什么不敢换 GDAL 3.xAPI 债与坐标系差异2.1 1.11 与新版 C API 的断层GDAL 1.11 在技术演进上处在一个特殊节点OGR 已经合并进主库但 C 封装还没经过 2.0 那轮“智能指针清洗”。那个年代写代码的风格是裸指针加手动释放读矢量要素要用OGRFeature::DestroyFeature打开数据源用OGROpen而不是 2.0 之后的GDALOpenEx。如果你手头有一个 2016 年前后编译的 GIS 模块它大概率长这样OGRRegisterAll(); OGRDataSource *poDS OGROpen(xxx.shp, FALSE, NULL); OGRLayer *poLayer poDS-GetLayer(0); OGRFeature *poFeature poLayer-GetNextFeature();这段代码放到 GDAL 3.x 下编译不是改一个头文件就能过的事OGRDataSource在 2.0 后变成了GDALDataset*GetLayer返回的容器类型也不再是裸指针老代码里大量delete poDS的调用会直接编译报错。也就是说升级新版 GDAL 等于把整套 GIS 业务代码做一次 API 迁移工作量通常按周计。而对 1.11 来说这些老代码就是它的原生环境。所以“已编译的 GDAL 1.11”真正的价值不在功能而在兼容——它让一个扔在角落多年的老工程还能正常编译、正常跑起来。这个库适合的场景就很明确了你维护着一个基于 1.x/2.x API 写成的老旧系统暂时没有预算做 API 迁移但需要把它部署到新的环境里。2.2 已编译包里通常藏着哪些东西拿到一个编译好的 GDAL 1.11先别急着看代码打开目录结构确认四样东西第一是include/里面必须有gdal.h、gdal_priv.h、ogr_api.h、ogr_srs_api.h和cpl_conv.h。这五个是 C/C 二次开发的基础缺了任何一个你连最小程序都编不过。第二是lib/通常能看到libgdal.so、libgdal.so.1以及带完整小版本号的真实文件注意.so多半是软链接拷贝到其他机器时如果没带-L参数会把软链接复制过去运行时就等着按头找库吧。第三是bin/里面应该有gdalinfo、ogr2ogr、gdaltransform这几个命令行工具这是快速验证库是否可用的最直接手段。第四是share/gdal/或者share/gdal11/里面放着pcs.csv、gcs.csv、proj.db1.11 时代还没有 proj.db主要是各类 csv 和 esri 的 WKT 定义。GDAL_DATA环境变量必须指向这个目录否则很多投影坐标系不会被正确初始化。这类预编译包在生成时常见的 configure 组合是--without-python --disable-debug --with-pic --with-proj --with-geos。--with-pic主要是为了让静态库也能被链接进动态库--without-python则意味着from osgeo import gdal这类 Python 调用就别指望了这个包是纯 C/C 通道。2.3 两个决策参数编译时间与坐标系版本锁要不要直接用这个已编译包我认为就看两个数字。第一个是编译时间。GDAL 1.11 全量驱动开启时在双核 4G 内存的机器上完整编译一次四十分钟到一个小时很正常。如果算上前期依赖库的编译——PROJ 4.9、GEOS 3.5、TIFF 4.0——总时长轻松超过半天。而且这里有个隐形风险你在源码编译时一旦把系统自带的 PROJ 更新掉系统中其他依赖 PROJ 的软件可能直接启动失败。已编译包绕开了这个改动系统库的过程风险更低。第二个是坐标系版本锁。GDAL 1.11 时代捆绑的 PROJ 版本通常比较老对 EPSG 数据库的识别也不如 PROJ 6 之后的新版全面。如果项目里有大量 2000 国家大地坐标系或者地方独立坐标系的转换需求老 PROJ 会遇到部分 EPSG 代码识别不了的情况。而这个问题恰恰也是已编译包的核心价值——它把坐标系行为固定在一个已知版本上不会因为系统有人升级了libproj.so而突然改变转换结果。在测绘质检这类项目中坐标系数值稳定性比版本号新更重要。如果目标项目涉及这类“必须稳定复现某个历史转换结果”的场景1.11 反而是正确的选择。3. 拆开这个资源包三步完成开箱验证3.1 目录结构与头文件版本核对先把压缩包解压到固定位置我习惯放在/opt/gdal111下路径里不带空格和中文避免后续编译时各类工具链抽风。mkdir -p /opt/gdal111 tar -xzf gdal111.tar.gz -C /opt/gdal111 cd /opt/gdal111 ls -la解压后第一眼确认目录层级有的包解压出来直接就是 include、lib 平铺有的会多套一层以版本号命名的目录。多套一层的情况建议把真正的根目录用软链接指出来这样可以避免后面每写一个 Makefile 都改-I路径。然后核对头文件版本号这是很多人会跳过的步骤。打开include/gdal_version.h看两个关键宏grep -E GDAL_VERSION_MAJOR|GDAL_VERSION_MINOR|GDAL_VERSION_REV|GDAL_RELEASE_DATE /opt/gdal111/include/gdal_version.h重点确认GDAL_VERSION_MINOR是 11说明这是 1.11.x 系列再看GDAL_RELEASE_DATE是不是你预期的时间节点。如果头文件版本和库文件版本对不上后面链接时会出现很隐蔽的内存布局错误——不是编译报错而是运行到某个驱动注册时直接段错误。这类错误极难排查因为GDALAllRegister()执行的是各驱动的动态注册版本错位的库会把导出符号表错乱地合并进来。3.2 四条环境变量一次配好验证库能不能用先把环境变量设置成最小闭环。后面你可以选择写进~/.bashrc但在验证阶段我建议直接在终端里执行避免污染全局配置export GDAL_HOME/opt/gdal111 export PATH$GDAL_HOME/bin:$PATH export LD_LIBRARY_PATH$GDAL_HOME/lib:$LD_LIBRARY_PATH export GDAL_DATA$GDAL_HOME/share/gdal逐条说下为什么。PATH指向bin是为了让gdalinfo等命令直接可用LD_LIBRARY_PATH指向lib是关键中的关键动态链接器在运行期找libgdal.so.1时靠的就是这个变量注意它和编译期的-L是两码事——编译期只决定链接时能不能找到符号运行期才决定程序启动时加载哪个库GDAL_DATA若漏掉gdalinfo常见表现是能启动但一查投影信息就报告ERROR 4: Unable to open EPSG support file gcs.csv。多数预编译包默认把数据目录编译到了/usr/local/share/gdal但你的包解压在/opt下所以必须用环境变量指回来。3.3 用一段最小代码确认库真的能干活环境变量设置后先用命令行工具试一把gdalinfo --version正常输出会显示类似GDAL 1.11.x, released ...。再检查它依赖的共享库是否能被动态链接器全部找到ldd /opt/gdal111/bin/gdalinfo grep -E not found|libgdal|libproj|libgeos (ldd /opt/gdal111/bin/gdalinfo)若gdalinfo能输出版本说明基本依赖没问题。接下来写个五行的 C 文件验证库在自定义链接场景下也能工作#include gdal_priv.h #include cstdio int main() { GDALAllRegister(); const char* v GDALVersionInfo(RELEASE_NAME); printf(GDAL version: %s\n, v); return 0; }编译命令g -o /tmp/gdaltest /tmp/gdaltest.cpp \ -I/opt/gdal111/include \ -L/opt/gdal111/lib -lgdal /tmp/gdaltest注意-L和-l的组合-L只告诉链接器“上哪找 libgdal.so”而-lgdal才是真正指定链接库的名字。这里如果只写-l gdal而不带路径系统会优先找/usr/lib下的版本如果在/usr/lib下恰好有一个 GDAL 2.x这个程序就会用完全错误的头文件/库组合完成链接运行阶段再崩溃。这个五行的最小程序实际上把“头文件版本、库版本、动态链接路径”这三件事一次性验证完了。4. 链接与动态库命名玄学已编译 GDAL 的四个翻车点4.1 动态库命名机制链接期找 .so运行期找 .so.1Linux 动态库的命名规则在 GDAL 上体现得淋漓尽致。你在lib/下会看到三个文件ls -l /opt/gdal111/lib/libgdal*典型输出是libgdal.so - libgdal.so.1、libgdal.so.1 - libgdal.so.1.11.x真实文件是那个带全版本号的。编译时-lgdal找的是libgdal.so这是一个软链接程序运行时动态链接器读取可执行文件里的DT_NEEDED条目找的是libgdal.so.1也就是带 SONAME 的那个。这个机制带来的问题很直接系统里若装过 GDAL 3.x它的 SONAME 是libgdal.so.20和 1.11 的libgdal.so.1不会发生文件名冲突——但正因为不冲突两个版本可以同时存在于系统中而链接器到底把哪个.so交给你的程序完全取决于-L的搜索顺序。看实际生效的是哪个库用readelf最直观readelf -d /tmp/gdaltest | grep -E NEEDED|RPATH|RUNPATH这个命令能看到可执行文件记录的是libgdal.so.1以及是否存在 RPATH 或 RUNPATH。如果这里显示依赖了一个你已经不存在的库文件名那基本确定是链接时选到了错误目录下的软链接。4.2 符号冲突1.11 自带的老 PROJ 与系统 PROJ 之间的搏斗已编译 GDAL 1.11 最容易被忽略的坑是它内部捆绑的 PROJ。很多预编译包在 configure 时把--with-proj指向了一个静态依赖意味着 GDAL 的.so里已经包含了老 PROJ 的符号。当你链接这个库时程序里同时存在两份 PROJ——一份是老符号另一份可能是系统里 PROJ 6/7 提供的libproj.so.15。两者都导出pj_init_plus、pj_free、pj_transform这些同名函数。后果是什么动态链接器遵循“先到先得”原则如果一个程序先加载了 GDAL 1.11其中内含老 PROJ随后又调用系统 PROJ 的接口函数指针会指向老版本实现。这种冲突不会在编译期暴露只有在坐标转换结果异常时才会发现——比如结果差了几十米或者某些投影参数突然失效。用nm -D可以快速检查包里是否带老 PROJ 符号nm -D /opt/gdal111/lib/libgdal.so.1 | grep -E pj_init|pj_transform|pj_free有输出说明这个包把 PROJ 静态编了进来。后续使用这类库时有一条原则必须遵守不要把系统的libproj.so目录放在LD_LIBRARY_PATH前面否则一旦 GDAL 先被加载PROJ 符号表会被老版本主导牵连到其它基于新 PROJ 的程序。4.3 避坑清单现象、原因、解法这里写四条我实际遇到过的翻车记录每一条都能对上具体的报错表现。第一条现象编译通过运行gdaltest时报error while loading shared libraries: libgdal.so.1: cannot open shared object file。原因编译期的-L虽然正确但运行期动态链接器没找到libgdal.so.1——LD_LIBRARY_PATH没设或设错路径。解决确认/opt/gdal111/lib是否在LD_LIBRARY_PATH中用ldd /tmp/gdaltest核对哪个库解析失败。这类问题的最隐蔽变体是你把/opt/gdal111/lib放在了LD_LIBRARY_PATH的末尾而/usr/lib/x86_64-linux-gnu在前面也有一个libgdal.so.1程序链接到的并不是包里的库。第二条现象程序在GDALAllRegister()处段错误gdb 回溯栈显示崩在GDALRegister_OGR或某个驱动构造函数。原因头文件来自 1.11但链接的却是系统里的 GDAL 2.x——-L顺序不对导致。解决编译时把-L/opt/gdal111/lib放在所有系统库目录之前并在编译命令后追加-Wl,-rpath,/opt/gdal111/lib让运行期带上绝对路径彻底绕开路径搜索顺序不确定性。第三条现象坐标转换结果和同事用别的新版工具算出来的结果差异明显换算成距离能差几十米。原因这个包捆绑了老 PROJ而新版系统 PROJ 的 EPSG 数据库更新过七参数或椭球定义有差异。解决这不是 bug是版本锁定的副作用。先确认你的项目是否需要与新版 PROJ 结果保持一致需要的话转换逻辑就得绕开 GDAL 的默认转换改用显式的PROJ.4字符串或者在后处理中补偿。第四条现象程序里读写 SHP 属性时中文属性显示乱码。原因GDAL 1.11 的 DBF 读取默认按本地代码页解释现代 Linux 环境普遍是 UTF-8而数据文件的编码是 GBK 或 GB2312。解决在打开文件前调CPLSetConfigOption(SHAPE_ENCODING, utf-8)或在代码里按数据的实际编码把配置项写死。这个坑还牵扯到另一个细节预编译包在编译时如果没有启用 ICU 支持字符串编码转换能力是有限的。5. 工程实战把 1.11 接进 C 代码做坐标转换与矢量读取5.1 老式 API 与新式 API 的边界坐标系定义方式GDAL 1.11 的坐标系定义最稳妥的方式是SetWellKnownGeogCS(WGS84)加importFromEPSG。但要注意1.11 的importFromEPSG对部分 EPSG 代码的支持依赖GDAL_DATA下的gcs.csv和pcs.csv如果数据文件不全函数返回错误码而不会像新版 PROJ 那样自动联机拉取定义。所以实际开发中我一般会先确认坐标系的 EPSG 定义是否可加载打印出来看一眼再继续转换。这个习惯帮我挡掉过大量“结果偏移但却找不到原因”的问题。5.2 经纬度到投影坐标一段不会翻车的转换代码下面的代码实现 WGS84 经纬度到 UTM 50N 投影坐标的转换是 1.11 里最典型的用法#include gdal_priv.h #include ogr_spatialref.h #include cstdio int main() { GDALAllRegister(); OGRSpatialReference oLatLong; oLatLong.SetWellKnownGeogCS(WGS84); oLatLong.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); OGRSpatialReference oProj; oProj.importFromEPSG(32650); oProj.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); OGRCoordinateTransformation *poCT OGRCreateCoordinateTransformation(oLatLong, oProj); if (poCT NULL) { printf(Failed to create transformation.\n); return 1; } double x 116.4; // 经度 double y 39.9; // 纬度 int bOK poCT-Transform(1, x, y); if (bOK) { printf(UTM: %f %f\n, x, y); } else { printf(Transform failed.\n); } OGRCoordinateTransformation::DestroyCT(poCT); return 0; }这段代码里有两处容易改错的地方。第一1.11 的Transform签名是Transform(int nCount, double* x, double* y, double* z)它默认处理的是数组指针——这个签名一直沿用到 GDAL 2.x 才改成直接接收三个 double 引用。如果把新版代码的调用方式直接搬进 1.11因为参数类型不匹配编译时会报函数重载找不到这还算好的至少不会静默出错。第二SetAxisMappingStrategy是控制坐标系轴顺序的选项1.11 里如果你不设置部分版本会默认按经纬度顺序处理而新版 GDAL 3.x 默认按 EPSG 定义的轴顺序。不写这一行在部分老库版本里转换结果会纬度经度对调。编译命令沿用之前的最小验证方式g -o /tmp/ct_test /tmp/ct_test.cpp \ -I/opt/gdal111/include \ -L/opt/gdal111/lib -lgdal \ -Wl,-rpath,/opt/gdal111/lib运行后如果程序静默退出但不输出坐标大概率是poCT NULL——建转换失败原因多半是GDAL_DATA没指对或 EPSG 数据缺失。这时候去/opt/gdal111/share/gdal下检查pcs.csv里有没有32650条目即可。5.3 矢量数据读取与 OGRFeature 释放时机再看一段矢量读取代码。1.11 里打开 SHP 数据源用的是OGROpen而不是 2.0 之后的GDALOpenEx#include ogr_api.h #include ogr_feature.h #include cstdio int main() { OGRRegisterAll(); CPLSetConfigOption(SHAPE_ENCODING, UTF-8); OGRDataSource *poDS OGROpen(/tmp/test.shp, FALSE, NULL); if (poDS NULL) { printf(Open failed.\n); return 1; } OGRLayer *poLayer poDS-GetLayer(0); OGRFeature *poFeature; while ((poFeature poLayer-GetNextFeature()) ! NULL) { OGRFeatureDefn *poFDefn poLayer-GetLayerDefn(); for (int i 0; i poFDefn-GetFieldCount(); i) { OGRFieldDefn *poFieldDefn poFDefn-GetFieldDefn(i); printf(%s: %s\n, poFieldDefn-GetNameRef(), poFeature-GetFieldAsString(i)); } OGRFeature::DestroyFeature(poFeature); } OGRDataSource::DestroyDataSource(poDS); OGRCleanupAll(); return 0; }这里有两个比 API 名更重要的事。第一OGRFeature::DestroyFeature必须在每次循环迭代结束时调用否则 feature 对象持有的字段缓冲区会累积跑几千条要素后内存占用猛涨。第二SHAPE_ENCODING必须在打开数据源之前设置因为代码页解析发生在数据源打开阶段如果放在OGROpen之后这个选项完全不生效中文继续乱码。这是 1.11 年代特别容易搞混的执行序坑。OGRCleanupAll()在程序退出前调用是推荐的它会释放驱动管理器持有的全局资源。如果你的程序不退出、持续运行就要避免反复调用OGROpen和OGRCleanupAll的交替操作——在某些 1.11 小版本里这组操作连续执行会导致二次释放崩溃。常见做法是程序启动时初始化一次结束时统一清理。6. 离线分发三步走做出一个不污染系统的运行环境6.1 用 ldd 把整个依赖树抓出来把已编译 GDAL 部署到离线环境最怕的不是 GDAL 本身而是它依赖的那些小库。在目标机器上lib/libgdal.so.1可能还需要libproj.so、libgeos_c.so、libsqlite3.so等。先在自己机器上做一次完整盘点mkdir -p /opt/gdal111/pkg cd /opt/gdal111/pkg ldd /opt/gdal111/bin/gdalinfo | awk // {print $3} | sort -u把列出的文件全部拷贝到目标机器的同一相对路径下用cp -L而不是普通cp因为很多.so是指向真实文件的软链接普通拷贝会把软链接关系也搬过去目标机器上若没有原始文件就断链了。拷完后的目录结构是一个完全自洽的lib/文件夹。6.2 做一个“免配置”的启动脚本离线环境通常不允许你登录后设置一堆export所以把环境变量封装进脚本是最稳妥的#!/bin/bash HERE$(cd $(dirname $0) pwd) export LD_LIBRARY_PATH$HERE/lib:${LD_LIBRARY_PATH} export GDAL_DATA$HERE/share/gdal export GDAL_DRIVER_PATH$HERE/lib/gdalplugins exec $这段脚本的关键是$HERE它根据脚本自身位置反推目录不强依赖固定的绝对路径。在目标机器上只要解压包、给脚本加执行权限chmod x runenv然后通过./runenv gdalinfo --version调用工具即可。6.3 两层验证与最后的教训验证分发结果我一般做两层。第一层是命令行冒烟测试用./runenv gdalinfo --version确认库能加载第二层是真实数据验证拿一份带投影的 GeoTIFF用./runenv gdalinfo读取投影信息看输出的坐标系 WKT 是否和开发环境一致。两次都通过这个分发包才算合格。那次配合某地理信息单位做离线部署我把所有依赖拷进lib/后自认为没问题结果目标机器一跑就报找不到libgdal.so.1。查了一圈才发现原因是我拷贝时用了cp -r把软链接原封不动搬了过去目标机器上软链接指向的绝对路径是开发机的/opt/gdal111自然全部断链。从那以后我每次处理已编译 GDAL 的包都会强制检查一遍文件类型确认.so和.so.1都是实物文件而不是指向开发机路径的软链接再进到离线环境里做二次验证。这个习惯帮我省掉了至少三回远程排障的麻烦希望也能帮到你。本文还有配套的精品资源点击获取