1 引言
最近终于将使用的GDAL 2.X升级到成了3.X版本,总结一下遇到的各种问题。
2 详论
2.1 数据路径
GDAL 3.X以后深度依赖PROJ库,以前只是可选构建项,现在已经是必须构建项了。最直接的体现是如果涉及到空间参考相关的内容时,除了要配置GDAL_DATA环境变量,还必须配置PROJ_DATA环境变量。GDAL_DATA和PROJ_DATA分别是GDAL和PROJ库的数据,里面存储了一些空间参考相关的参数,因此一般在使用GDAL之前,需要配置一下相关的路径:
cpp
string gdalDir = shareDataDir + string("/gdal");
CPLSetConfigOption("GDAL_DATA", gdalDir.c_str());
std::string projDir = shareDataDir + string("/proj");
CPLSetConfigOption("PROJ_DATA", projDir.c_str());
这些数据一般在构建的时候会安装到指定的目录的share
文件夹内,如下图所示:

注意以下几点:
- 经过笔者的测试,一般设置PROJ_DATA目录就可以了。但是有的资料显示还必须继续设置GDAL_DATA目录。因为GDAL_DATA目录中可能保存的不仅仅是空间参考信息,可能保存了很多GIS相关的数据。
- 另外,很多资料推荐直接设置操作系统的环境变量。这样做不是不行,前提是与操作系统的其他GDAL环境不冲突。如果你安装过QGIS或者PostGIS等环境就知道,这些程序也会在操作系统中设置GDAL_DATA、PROJ_DATA环境变量,那么就只能像笔者这样在程序中配置。
- 在PROJ 9.1版本以前,PROJ数据的环境变量名为PROJ_LIB;在后续版本中优先使用PROJ_DATA,PROJ_LIB还会使用一段时间,但是最好替换成PROJ_DATA,避免后续的版本废弃。
2.2 坐标顺序
GDAL升级到3.X后另外一个问题就是坐标顺序的问题。例如如果要进行空间参考坐标转换:
cpp
// CGCS2000
gcs.importFromEPSG(4326);
// Tm投影
pcs.importFromEPSG(3857);
OGRCoordinateTransformation* lonLat2XY =
OGRCreateCoordinateTransformation(&gcs, &pcs);
OGRCoordinateTransformation* xy2LonLat =
OGRCreateCoordinateTransformation(&pcs, &gcs);
if (!lonLat2XY || !xy2LonLat) {
return 1;
}
double x = 113.6;
double y = 38.8;
printf("经纬度坐标:%.9lf\t%.9lf\n", x, y);
if (!lonLat2XY->Transform(1, &x, &y)) {
return 1;
}
printf("平面坐标:%.9lf\t%.9lf\n", x, y);
if (!xy2LonLat->Transform(1, &x, &y)) {
return 1;
}
printf("再次转换回的经纬度坐标:%.9lf\t%.9lf\n", x, y);
OGRCoordinateTransformation::DestroyCT(lonLat2XY);
lonLat2XY = nullptr;
OGRCoordinateTransformation::DestroyCT(xy2LonLat);
xy2LonLat = nullptr;
这段代码在3.X的结果就不正确。原因是GDAL 3.X更换了坐标轴的顺序,认为y在前,x在后才是更加专业的坐标表达。不过这样就破坏了向后兼容性。解决方案是给空间参考设置轴策略为传统顺序[[1]](#[1]):
cpp
// CGCS2000
gcs.importFromEPSG(4326);
// Tm投影
pcs.importFromEPSG(3857);
gcs.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
pcs.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
//...
想这样一个一个坐标参考修改很麻烦,另一个更加合适的办法是设置全局的坐标轴策略为传统顺序[[2]](#[2]):
cpp
// 设置全局坐标顺序为传统GIS顺序(经度,纬度)
CPLSetConfigOption("OGR_CT_FORCE_TRADITIONAL_GIS_ORDER", "YES");
3 总结
最好的办法就是在程序的最开始阶段执行一个初始化函数:
cpp
void Init(const char *shareDataDir) {
GDALAllRegister(); //注册所有的格式
CPLSetConfigOption("SHAPE_ENCODING", ""); //解决中文乱码问题
string gdalDir = shareDataDir + string("/gdal");
CPLSetConfigOption("GDAL_DATA", gdalDir.c_str());
std::string projDir = shareDataDir + string("/proj");
CPLSetConfigOption("PROJ_DATA", projDir.c_str());
// 设置全局坐标顺序为传统GIS顺序(经度,纬度)
CPLSetConfigOption("OGR_CT_FORCE_TRADITIONAL_GIS_ORDER", "YES");
}