GDAL 3.x 概述

GDAL 3.0 是一次重大版本升级,于 2019 年发布,带来了多项底层架构变化。本章将介绍 GDAL 3.x 系列的核心变化、各版本亮点、新头文件体系、统一命令行工具以及编译系统变化,帮助读者从整体上把握 GDAL 3.x 的演进方向。

GDAL 3.0 核心变化

GDAL 3.0 相比 2.x 系列是一次跨越式的升级,主要体现在坐标参考系统处理方式的根本性改变。以下介绍三个最重要的变化。

PROJ 6+ 集成

GDAL 3.0 最重要的变化之一是将底层坐标转换库从 PROJ 4 升级到 PROJ 6+。PROJ 6 引入了全新的坐标转换管道机制,基于 PROJ pipeline 的坐标转换方式,取代了旧版本中基于简单参数的转换方法。

这一变化带来的主要影响包括:

  • 坐标转换精度显著提高,特别是涉及到基准面转换(datum transformation)的场景

  • 支持更多坐标参考系统定义方式

  • 需要 proj.db 数据库文件,运行时必须能访问到该文件

  • 不再支持 PROJ 4 中的部分过时接口

如果运行时找不到 proj.db,程序会报错。可以通过以下方式设置搜索路径:

#include "ogr_spatialref.h"

// 方式一:在代码中设置 PROJ 数据库搜索路径
const char *const apszSearchPaths[] = { "/path/to/proj" };
OSRSetPROJSearchPaths(apszSearchPaths);

// 方式二:设置环境变量 PROJ_LIB 指向 proj.db 所在目录

也可以通过设置环境变量 PROJ_LIB 来指定路径:

# Linux / macOS
export PROJ_LIB=/usr/local/share/proj

# Windows (PowerShell)
$env:PROJ_LIB = "C:\OSGeo4W64\share\proj"

WKT2 支持

GDAL 3.0 开始全面支持 WKT2(Well Known Text 2)格式,即 ISO 19162 标准。相比传统的 WKT1(基于 OGC 01-009),WKT2 提供了更丰富的表达能力。

WKT2 与 WKT1 的主要区别:

  • WKT2 使用 BOUNDCRS 来表达坐标参考系统之间的绑定关系,而非 WKT1 中隐含的 TOWGS84 节点

  • WKT2 支持 ENSEMBLE 概念来描述大地基准面

  • WKT2 中的坐标系轴顺序严格按照定义,不再像 WKT1 那样默认将经纬度翻转为经度-纬度顺序

#include "ogr_spatialref.h"

OGRSpatialReference oSRS;
oSRS.importFromEPSG(4326);

// 导出为 WKT2_2019 格式
char *pszWKT2 = nullptr;
oSRS.exportToWkt(&pszWKT2, OGRSpatialReference::WKT2_2019);
// 输出类似:
// GEOGCRS["WGS 84",
//   DATUM["World Geodetic System 1984",
//     ELLIPSOID["WGS 84",6378137,298.257223563,
//       LENGTHUNIT["metre",1]]],
//   PRIMEM["Greenwich",0,
//     ANGLEUNIT["degree",0.0174532925199433]],
//   CS[ellipsoidal,2],
//     AXIS["geodetic latitude (Lat)",north,
//       ORDER[1],
//       ANGLEUNIT["degree",0.0174532925199433]],
//     AXIS["geodetic longitude (Lon)",east,
//       ORDER[2],
//       ANGLEUNIT["degree",0.0174532925199433]],
//   ID["EPSG",4326]]
CPLFree(pszWKT2);

// 也可以导出为传统 WKT1 格式
char *pszWKT1 = nullptr;
oSRS.exportToWkt(&pszWKT1, OGRSpatialReference::WKT1_GDAL);
CPLFree(pszWKT1);

AxisMappingStrategy 引入

由于 WKT2 严格遵循轴顺序定义,EPSG:4326 在 WKT2 下纬度在前、经度在后,这与 GDAL 2.x 中默认的经度在前、纬度在后不同。为了处理这种差异,GDAL 3.0 引入了 AxisMappingStrategy 机制。

AxisMappingStrategy 的常见设置:

#include "ogr_spatialref.h"

OGRSpatialReference oSRS;
oSRS.importFromEPSG(4326);

// OAMS_TRADITIONAL_GIS_ORDER:强制使用传统 GIS 顺序(经度, 纬度)
// 这是 GDAL 2.x 的默认行为
oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);

// OAMS_AUTHORITY_COMPLIENT:严格按照坐标参考系统定义的轴顺序
// 对于 EPSG:4326,即为(纬度, 经度)
oSRS.SetAxisMappingStrategy(OAMS_AUTHORITY_COMPLIENT);

在使用坐标转换时,必须正确设置 AxisMappingStrategy,否则坐标转换结果可能出错:

#include "ogr_spatialref.h"

OGRSpatialReference oSrcSRS, oDstSRS;
oSrcSRS.importFromEPSG(4326);
oSrcSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);

oDstSRS.importFromEPSG(32652);
oDstSRS.SetAxisMappingStrategy(OAMS_TRADORITY_COMPLIENT);

OGRCoordinateTransformation *poCT =
    OGRCreateCoordinateTransformation(&oSrcSRS, &oDstSRS);

double x = 129.0;  // 经度
double y = 36.0;   // 纬度
poCT->Transform(1, &x, &y);

// 结果为 UTM 52N 坐标
CPLDebug("Transform", "Result: x=%.2f, y=%.2f", x, y);

OGRCoordinateTransformation::DestroyCT(poCT);

Note

在 GDAL 3.0 中,如果通过 Python 绑定使用 OGRSpatialReference,默认情况下 AxisMappingStrategyOAMS_AUTHORITY_COMPLIENT。而在 C/C++ API 中,需要显式设置。建议始终显式设置该策略,以避免跨版本兼容问题。

GDAL 3.1~3.13 版本亮点

GDAL 3.0 发布后,后续的 3.x 版本持续引入了大量改进和新功能。以下按版本简要列举各版本的重要变化。

3.1(2020年1月)

  • 新增 GDALDataset 栅格数据集的多线程读取支持

  • 新增 --if 选项用于 gdal_translate 等工具,显式指定输入驱动

  • COG(Cloud Optimized GeoTIFF)驱动改进

3.2(2020年11月)

  • 新增多维(Multidimensional)数据 API 的 Python 绑定

  • STACIT 虚拟驱动:基于 STAC 目录访问影像

  • FlatGeoBuf 驱动性能优化

3.3(2021年5月)

  • 新增 RFC 84:Raster dataset thread safety improvements

  • 改进 GeoTIFF 读写性能

  • 新增 ESRIC 缓存切片驱动

3.4(2021年11月)

  • 新增 Zarr 格式驱动(多维数组数据)

  • 改进 HDF5 驱动对多维数据的支持

  • 新增 gdal raster pipeline 概念原型

3.5(2022年5月)

  • 新增 Rasterio 数组协议支持

  • 改进 STAC 集成

  • 新增 OGR SQLite WAL 模式支持

3.6(2022年11月)

  • 改进多线程瓦片读写

  • 新增 RFC 100: Float16 支持(IEEE 754 half-precision)

  • QOI 图像格式支持

3.7(2023年5月)

  • 新增 gdal vector pipelinegdal raster pipeline 管道语法

  • 改进 CMake 编译系统模块化

  • 新增 GRIB2 产品的更多模板支持

3.8(2023年11月)

  • 新增 RFC 104: Unified CLI tool (gdal 命令)

  • OpenFileGDB 驱动完全取代 FileGDB SDK 驱动

  • 改进 Parquet 驱动性能

3.9(2024年5月)

  • gdal 统一 CLI 进一步完善

  • 新增 RFC 109: C/C++ Header File Reorganization

  • 改进 Zarr 和 netCDF 多维数据驱动

3.10(2024年11月)

  • 新头文件体系初步可用(gdal_raster_cpp.h 等)

  • 改进 MVT(Mapbox Vector Tile)驱动

  • 性能优化:大规模栅格数据读取

3.11(2025年2月)

  • 新头文件体系成为默认推荐方式

  • 改进 COG 驱动的 HTTP 范围请求效率

  • SQLite 3.47+ 集成优化

3.12(2025年5月)

  • 废弃旧头文件 gdal_priv.h 的使用警告

  • 改进 FlatGeoBuf 和 PMTiles 驱动

  • 增强 gdal CLI 的子命令功能

3.13(2025年11月)

  • RFC 109 头文件重组基本完成

  • 改进 Parquet 和 GeoParquet 驱动

  • 增强多维数据 API 性能

新头文件体系(RFC 109)

GDAL 3.9 引入了 RFC 109(C/C++ Header File Reorganization),对头文件进行了重新组织,将原先庞大而集中的头文件拆分为多个细粒度的头文件。这一变化的目的是:

  • 减少编译时不必要的依赖引入

  • 缩小编译时间

  • 使 API 结构更加清晰

gdal_priv.h 的变化

在 GDAL 2.x 和早期 3.x 中,几乎所有 C++ 开发都使用 gdal_priv.h 作为主要头文件:

// 旧方式:引入整个 gdal_priv.h
#include "gdal_priv.h"

// gdal_priv.h 包含了 GDALDataset、GDALRasterBand、GDALDriver 等所有核心类
// 也包含了 cpl_conv.h、cpl_error.h 等基础设施头文件

在新体系中,gdal_priv.h 被拆分为多个功能明确的头文件。gdal_priv.h 仍然存在以保持向后兼容,但推荐使用新的细粒度头文件。

gdal_raster_cpp.h

gdal_raster_cpp.h 是新的栅格操作主头文件,替代 gdal_priv.h 用于栅格数据读写场景。它聚合了栅格操作所需的核心声明:

// 新方式:使用 gdal_raster_cpp.h
#include "gdal_raster_cpp.h"

// 等效于引入了以下内容:
// - GDALDataset(数据集)
// - GDALRasterBand(波段)
// - GDALDriver(驱动)
// - GDALMajorObject(基类)
// 以及其他栅格操作相关的类型和函数

gdal_vector_cpp.h

gdal_vector_cpp.h 是矢量操作的主头文件,用于 OGR 矢量数据的读写:

// 矢量操作专用头文件
#include "gdal_vector_cpp.h"

// 包含了 OGRLayer、OGRFeature、OGRGeometry 等核心矢量类
// 以及 OGRDataSource 等相关定义

gdal_multidim_cpp.h

gdal_multidim_cpp.h 用于多维数组(Multidimensional Array)操作,这是 GDAL 3.x 中引入的全新数据模型:

// 多维数据操作头文件
#include "gdal_multidim_cpp.h"

// 包含了 GDALMDArray(多维数组)、GDALDimension(维度)
// GDALAttribute(属性)等多维数据相关类

// 示例:打开多维数据集
GDALAllRegister();
auto poDataset = std::unique_ptr<GDALDataset>(
    GDALDataset::Open("data.nc", GDAL_OF_MULTIDIM_RASTER));
if (!poDataset) {
    CPLError(CE_Failure, CPLE_OpenFailed, "Cannot open dataset");
    return;
}

auto poRootGroup = poDataset->GetRootGroup();
auto poVar = poRootGroup->OpenMDArray("temperature");
if (poVar) {
    auto poDim = poVar->GetDimensions();
    for (const auto &poD : poDim) {
        CPLDebug("Dim", "Name: %s, Size: %d",
                 poD->GetName().c_str(),
                 (int)poD->GetSize());
    }
}

细粒度头文件

除了上述三个聚合头文件,RFC 109 还提供了更细粒度的头文件,适用于只需要引入特定类的场景:

// 只需要数据集相关定义时
#include "gdal_dataset.h"

// 只需要驱动相关定义时
#include "gdal_driver.h"

// 只需要波段相关定义时
#include "gdal_rasterband.h"

// 只需要主对象基类时
#include "gdal_majorobject.h"

// 只需要颜色表相关定义时
#include "gdal_colortable.h"

// 只需要栅格属性表时
#include "gdal_rat.h"  // Raster Attribute Table

以下是一个完整的细粒度头文件使用示例:

// 使用细粒度头文件的完整读取示例
#include "gdal_dataset.h"   // GDALDataset
#include "gdal_rasterband.h" // GDALRasterBand
#include "gdal_driver.h"    // GDALDriver
#include "cpl_conv.h"       // CPLMalloc 等内存工具

void ReadRasterData(const char *pszFilename)
{
    GDALAllRegister();

    // 打开数据集
    GDALDataset *poDS = static_cast<GDALDataset *>(
        GDALOpen(pszFilename, GA_ReadOnly));
    if (poDS == nullptr) {
        CPLError(CE_Failure, CPLE_OpenFailed,
                 "Cannot open %s", pszFilename);
        return;
    }

    // 获取第一个波段
    GDALRasterBand *poBand = poDS->GetRasterBand(1);
    int nXSize = poBand->GetXSize();
    int nYSize = poBand->GetYSize();

    // 读取整行数据
    float *pafScanline = static_cast<float *>(
        CPLMalloc(sizeof(float) * nXSize));

    for (int row = 0; row < nYSize; ++row) {
        CPLErr err = poBand->RasterIO(
            GF_Read, 0, row, nXSize, 1,
            pafScanline, nXSize, 1, GDT_Float32,
            0, 0);
        if (err != CE_None) {
            CPLError(CE_Failure, CPLE_FileIO,
                     "Read error at row %d", row);
            break;
        }
        // 处理 pafScanline 数据...
    }

    CPLFree(pafScanline);
    GDALClose(poDS);
}

头文件选择建议

场景

推荐头文件

通用栅格读写

gdal_raster_cpp.h

矢量数据操作

gdal_vector_cpp.h

多维数组操作

gdal_multidim_cpp.h

只需打开数据集读取元数据

gdal_dataset.h

编写新的 GDAL 驱动

gdal_raster_cpp.h + gdal_driver.h

向后兼容旧代码

gdal_priv.h (不推荐新项目使用)

Note

新头文件体系在 GDAL 3.9 中引入,3.10 开始可选使用,3.11 起成为推荐方式。旧的 gdal_priv.h 在当前版本中仍然可用,但未来版本可能会发出废弃警告。建议新项目直接使用新头文件体系。

新 gdal 统一 CLI(RFC 104)

GDAL 3.8 引入了 RFC 104,即 gdal 统一命令行工具。在此之前,GDAL 提供了多个独立的命令行工具(如 gdal_translategdalwarpogr2ogr 等),新版本中将它们统一到一个 gdal 命令下,通过子命令的方式调用。

旧方式与新方式对比

# ===== 旧方式(仍然可用) =====

# 栅格格式转换
gdal_translate -of GTiff input.tif output.tif

# 栅格重投影和裁剪
gdalwarp -t_srs EPSG:4326 -te 126 35 130 38 input.tif output.tif

# 矢量格式转换
ogr2ogr -f GeoJSON output.json input.shp

# 查看数据信息
gdalinfo input.tif

# ===== 新方式 =====

# 栅格格式转换
gdal raster convert -of GTiff input.tif output.tif

# 栅格重投影和裁剪
gdal raster pipeline read input.tif ! reproject -t_srs EPSG:4326 ! write output.tif

# 矢量格式转换
gdal vector convert -f GeoJSON output.json input.shp

# 查看数据信息
gdal info input.tif

管道语法

gdal 统一 CLI 的一大特色是支持管道(pipeline)语法,可以将多个操作串联起来:

# 栅格处理管道:读取 -> 裁剪 -> 重投影 -> 重采样 -> 写入
gdal raster pipeline \
    read input.tif \
    ! crop -bbox 126 35 130 38 \
    ! reproject -t_srs EPSG:3857 \
    ! write -of COG output.tif

# 矢量处理管道:读取 -> 过滤 -> 投影转换 -> 写入
gdal vector pipeline \
    read input.shp \
    ! filter -where "population > 100000" \
    ! reproject -t_srs EPSG:4326 \
    ! write -f GeoJSON output.json

# 管道也支持从标准输入读取
cat input.tif | gdal raster pipeline read /vsistdin/ ! info

子命令列表

gdal 统一 CLI 提供了以下主要子命令:

  • gdal info — 查看数据集信息(替代 gdalinfoogrinfo

  • gdal raster — 栅格操作子命令组

  • gdal vector — 矢量操作子命令组

  • gdal mdim — 多维数据操作子命令组

其中 gdal rastergdal vector 各自包含以下常用子命令:

# 栅格子命令
gdal raster convert   # 格式转换(替代 gdal_translate)
gdal raster pipeline  # 管道处理
gdal raster overview  # 金字塔管理
gdal raster info      # 栅格信息

# 矢量子命令
gdal vector convert   # 格式转换(替代 ogr2ogr)
gdal vector pipeline  # 管道处理
gdal vector info      # 矢量信息

# 查看所有可用子命令
gdal --help
gdal raster --help
gdal vector --help

Note

gdal 统一 CLI 在 GDAL 3.8 中引入,目前仍处于积极开发中。旧的独立命令行工具在可预见的未来仍然可用,但新功能可能优先在 gdal 统一 CLI 中实现。建议新项目和脚本使用 gdal 统一 CLI。

CMake 编译系统变化

GDAL 3.x 全面转向 CMake 编译系统,取代了之前基于 GNU Autotools(configure/make)和 Windows nmake 的编译方式。

基本编译流程

# 克隆源码
git clone https://github.com/OSGeo/gdal.git
cd gdal
git checkout v3.13.0  # 切换到指定版本

# 创建构建目录(推荐 out-of-source 构建)
mkdir build
cd build

# 配置(默认 Release 模式)
cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local

# 编译
cmake --build . -j$(nproc)

# 安装
cmake --build . --target install

常用 CMake 配置选项

# 基础配置示例
cmake .. \
    -DCMAKE_BUILD_TYPE=Release \
    -DCMAKE_INSTALL_PREFIX=/usr/local \
    -DGDAL_BUILD_OPTIONAL_DRIVERS=ON \
    -DGDAL_USE_GEOS=ON \
    -DGDAL_USE_PROJ=ON \
    -DGDAL_USE_TIFF=ON \
    -DGDAL_USE_GEOTIFF=ON \
    -DGDAL_USE_JPEG=ON \
    -DGDAL_USE_PNG=ON \
    -DBUILD_TESTING=OFF

以下是一些常用的 CMake 选项:

选项

说明

CMAKE_BUILD_TYPE

构建类型:Release、Debug、RelWithDebInfo 等

CMAKE_INSTALL_PREFIX

安装路径前缀

BUILD_SHARED_LIBS

是否构建动态库(默认 ON)

BUILD_TESTING

是否构建测试(默认 ON)

GDAL_BUILD_OPTIONAL_DRIVERS

是否构建可选驱动(默认 ON)

GDAL_USE_<LIB>

是否启用特定依赖库(如 GDAL_USE_GEOS、GDAL_USE_PROJ)

GDAL_BUILD_APPS

是否构建命令行工具(默认 ON)

GDAL_BUILD_PYTHON_BINDINGS

是否构建 Python 绑定(默认 ON)

Windows 下使用 vcpkg 编译

推荐使用 vcpkg 管理依赖库,在 Windows 下的编译流程:

# 假设 vcpkg 已安装在 C:/vcpkg
cd gdal
mkdir build
cd build

cmake .. \
    -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake \
    -DCMAKE_INSTALL_PREFIX=C:/GDAL_INSTALL \
    -DCMAKE_BUILD_TYPE=Release \
    -DBUILD_SHARED_LIBS=OFF

cmake --build . --config Release
cmake --build . --config Release --target install

CMake 与旧编译系统的对比

特性

旧系统(nmake/configure)

CMake

跨平台

需要分别维护 nmake.opt 和 configure

统一的 CMakeLists.txt

依赖管理

手动配置路径

自动检测 + find_package

IDE 集成

需手动生成工程文件

直接生成 VS / Xcode 工程

构建速度

单线程编译

支持并行编译(-j 参数)

模块化

较差

较好,可按需启用/禁用驱动