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字符串
    }
}
相关推荐
一个有理想的摸鱼选手18 小时前
CesiumLite-开箱即用的轻量化三维地图包(持续更新中...)
gis·cesium
小艳加油14 天前
生态环评GIS/遥感制图:土地利用+植被覆盖+土壤侵蚀+水系提取,ArcGIS+ENVI实战
gis·遥感解译·环境影响评价
疯狂学习GIS19 天前
ArcGIS工具操作报错999999的通用处理方式
arcgis·gis·rs·学术工作效率·遥感数据
小艳加油23 天前
GIS应用攻略:全面涵盖GIS基础、ArcGIS操作、数据制备、空间分析、地图制作、三维建模与多领域实战应用,助力科研与行业实践!
arcgis·gis·空间分析·数据制备
阿怼丶1 个月前
🚀 如何在内网中运行 Cesium?基于 NestJS 构建离线地形与影像服务
前端·gis
疯狂学习GIS1 个月前
数据分析必备:GPS轨迹、软件签到、手机信令数据获取方式合集
gis·学术工作效率·gis数据
Dorian_Ov01 个月前
ArcGIS/geosceneAPI学习01
gis
山人在山上1 个月前
达梦 空间数据库扩展记录
gis·达梦数据库
WangYan20221 个月前
深度解析地质灾害风险普查:RS与GIS技术在泥石流、滑坡灾害中的应用,ArcGIS数据管理、空间数据转换、专题地图制作、DEM分析及实战案例分析
gis·遥感·滑坡·泥石流