GDAL矢量数据集相关接口的资源控制问题

1. 引言

笔者在《使用GDAL读写矢量文件》这篇文章中总结了通过GDAL读写矢量的具体实现。不过这篇文章中并没有谈到涉及到矢量数据集相关接口的资源控制问题。具体来说,GDAL/OGR诞生的年代连C++语言本身都不是很完善(c++11之前),因此提供的C++接口往往存在申请的资源需要释放的问题,因此在这里将其总结一下。

2. 详论

2.1 数据集类GDALDataset

矢量数据集GDALDataset对象需要通过GDALOpenEx来读取或者更新。在不需要这个对象之后,使用GDALClose进行关闭。例如:

cpp 复制代码
GDALDataset *poDS = (GDALDataset*)GDALOpenEx(filePath, GDAL_OF_VECTOR, NULL, NULL, NULL);

//...

GDALClose(poDS);
poDS = nullptr;

另一方面,通过驱动类GDALDriver创建矢量数据集,不需要之后仍然使用GDALClose进行关闭。例如:

cpp 复制代码
GDALDriver* driver = GetGDALDriverManager()->GetDriverByName("ESRI Shapefile");
if (!driver)
{
    printf("Get Driver ESRI Shapefile Error!\n");
    return false;
}
	
GDALDataset* dataset = driver->Create(filePath, 0, 0, 0, GDT_Unknown, NULL);

GDALClose(dataset);
dataset = nullptr;

理论上来说,GDALDataset对象在打开或者创建之后,使用delete进行释放也是可以的。但是一般而言,最好使用GDAL本身提供出来的释放接口。因为这个接口的内部实现可能并不只是delete那么简单,可能有其他的资源释放操作。不仅仅是GDAL,其他类库也是同理。

2.2 图层类OGRLayer

GDALDataset既可以是矢量数据集,也可以是栅格数据集。但是只有矢量数据集才能获取或创建图层类OGRLayer。但是无论是获取还是创建OGRLayer,再无需使用之后,都不用再进行主动释放了,OGRLayer对象会被GDALDataset对象托管,在GDALClose释放数据集对象之后,图层类OGRLayer就会随之释放。

cpp 复制代码
OGRLayer* poLayer = poDS->GetLayer(0);
//获取后无需显式释放OGRLayer

OGRLayer* poLayer = dataset->CreateLayer("houseType", NULL, wkbPolygon, NULL);
//创建后无需显式释放OGRLayer

2.3 要素类OGRFeature

要素类OGRFeature一般从图层类OGRLayer对象中获取或者创建,不过无论是获取还是创建都需要进行显式释放。例如读取矢量数据集时遍历获取要素:

cpp 复制代码
OGRFeature *poFeature;
while ((poFeature = poLayer->GetNextFeature()) != NULL)
{
    OGRGeometry *pGeo = poFeature->GetGeometryRef();
    //...
    OGRFeature::DestroyFeature(poFeature);
}

这里的OGRFeature::DestroyFeature(poFeature);就是GDAL提供的用于销毁要素对象的方法。另一方面,如果是写出数据集创建要素,比如笔者这里创建一个经纬度网格的矢量:

cpp 复制代码
for (int yi = -90; yi < 90; ++yi) {
    for (int xi = -180; xi < 180; ++xi) {    
      OGRFeature poFeature(poLayer->GetLayerDefn());

      OGRLinearRing ogrRing;
      ogrRing.addPoint(xi, yi);
      ogrRing.addPoint(xi + 1, yi);
      ogrRing.addPoint(xi + 1, yi + 1);
      ogrRing.addPoint(xi, yi + 1);
      ogrRing.closeRings();

      OGRPolygon polygon;
      polygon.addRing(&ogrRing);
      poFeature.SetGeometry(&polygon);

      if (poLayer->CreateFeature(&poFeature) != OGRERR_NONE) {
        printf("Failed to create feature in shapefile.\n");
        return false;
      }
    }
  }

OGRFeature使用的是值对象,在超出作用域之后会自动销毁。经过验证笔者这样写并没有问题,可以推断OGRLayer对于OGRFeature对象的管理应该是采用的深拷贝方式,并且会托管这个拷贝后的OGRFeature对象。

2.4 几何类OGRGeometry

几何类OGRGeometry使用了C++类的继承和多态特性,本身其是一个基类,但是继承出了如OGRLinearRing、OGRPolygon等子类来表达点线面多种要素几何类型。因此GDAL提供了一个工厂类来创建和销毁,这是一种非常经典的设计模式:

cpp 复制代码
OGRGeometry* poGeom = OGRGeometryFactory::createGeometry(wkbPoint);
// 使用完 poGeom 后释放它
OGRGeometryFactory::destroyGeometry(poGeom);

也就是OGRGeometryFactory::createGeometry和OGRGeometryFactory::destroyGeometry需要成对出现。不过笔者认为如果不是为了多态表达,直接使用值对象更加方便,如第2.3节中的示例所示。

另外,OGRGeometry对象是需要放置到OGRFeature对象中的,因此OGRFeature提供了两个接口:

  1. OGRErr SetGeometryDirectly(OGRGeometry*):浅拷贝OGRGeometry对象,OGRFeature对象直接托管OGRGeometry对象的所有权。
  2. OGRErr SetGeometry(const OGRGeometry*):深拷贝OGRGeometry对象,OGRFeature对象托管OGRGeometry拷贝对象的所有权。

另外,几何类之间的相互引用也是如此,如第2.3节中的示例所示,多边形增加环也有两个接口:

  1. addRingDirectly() 浅拷贝OGRLinearRing对象,OGRPolygon对象直接托管OGRLinearRing对象的所有权。
  2. addRing()深拷贝OGRLinearRing对象,OGRPolygon对象托管OGRLinearRing拷贝对象的所有权。

也就是一般而言,GDAL通常使用Directly后缀的函数接口来表达对原几何对象的托管。

3. 其他

可以看到,GDAL的资源控制方面还是有点混乱的,有的要显式释放,有的又可以托管,有的干脆提供了两个接口。据说新的GDAL版本引入了很多新的C++特性,估计资源控制的逻辑要清晰一点。另外,我们也可以主动使用一些新的C++特性来避免资源控制需要主动释放的问题。例如使用智能指针,配合自定义删除器来销毁OGRFeature对象,如下例所示:

cpp 复制代码
// 获取第一个图层
OGRLayer* poLayer = poDS->GetLayer(0);
if (poLayer == nullptr) {
    std::cerr << "Failed to get the layer." << std::endl;
    GDALClose(poDS);
    return -1;
}

// 自定义删除器用于销毁 OGRFeature
auto featureDeleter = [](OGRFeature* poFeature) {
    OGRFeature::DestroyFeature(poFeature);
};

// 遍历图层中的要素
poLayer->ResetReading();
std::unique_ptr<OGRFeature, decltype(featureDeleter)> poFeature(nullptr, featureDeleter);

while ((poFeature.reset(poLayer->GetNextFeature())), poFeature) {
    // 获取几何体
    OGRGeometry* poGeometry = poFeature->GetGeometryRef();
    if (poGeometry != nullptr) {
        // 输出几何体的WKT表示
        char* pszWKT = nullptr;
        poGeometry->exportToWkt(&pszWKT);
        std::cout << "Geometry: " << pszWKT << std::endl;
        CPLFree(pszWKT);  // 释放WKT字符串
    }
}
相关推荐
ct9782 天前
Cesium高级特效与着色器开发全指南
前端·gis·cesium·着色器
葱明撅腚4 天前
shapely空间数据分析
python·pandas·gis·shapely
极海拾贝5 天前
秒加在线底图!天地图、高德地图、星图地球、吉林一号底图一次配齐,收藏这篇就够了!
arcgis·gis·geoscene
ct9785 天前
Cesium 矩阵系统详解
前端·线性代数·矩阵·gis·webgl
两点王爷5 天前
KML文件格式和支持添加的内容
gis
水静川流6 天前
GIS工具、POI数据、DEM数据、NDVI数据等地学大数据
arcgis·gis·poi·dem·地学大数据
GIS遥遥8 天前
2026年地信测绘遥感(3S)专业升学、就业、考证、竞赛专属日历
gis·gis开发·测绘·地图可视化
酬勤-人间道8 天前
XPlote3DGenie 2.1.1.0:实用 3D 数据处理工具,百度网盘可直接安装
c++·3d·gis·编程·计算机软件·岩土
GISBox8 天前
GIS场景透明度贴图全流程指南:模型导入、透明模式设置与3DTiles服务发布
3d·gis·gisbox·透明度贴图·opaque·alphatest·alphablend