GDAL矢量數據集相關接口的資源控制問題
1. 引言
筆者在《使用GDAL讀寫矢量文件》這篇文章中總結了通過GDAL讀寫矢量的具體實現。不過這篇文章中并沒有談到涉及到矢量數據集相關接口的資源控制問題。具體來說,GDAL/OGR誕生的年代連C++語言本身都不是很完善(c++11之前),因此提供的C++接口往往存在申請的資源需要釋放的問題,因此在這里將其總結一下。
2. 詳論
2.1 數據集類GDALDataset
矢量數據集GDALDataset對象需要通過GDALOpenEx來讀取或者更新。在不需要這個對象之后,使用GDALClose進行關閉。例如:
GDALDataset *poDS = (GDALDataset*)GDALOpenEx(filePath, GDAL_OF_VECTOR, NULL, NULL, NULL);
//...
GDALClose(poDS);
poDS = nullptr;
另一方面,通過驅動類GDALDriver創建矢量數據集,不需要之后仍然使用GDALClose進行關閉。例如:
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就會隨之釋放。
OGRLayer* poLayer = poDS->GetLayer(0);
//獲取后無需顯式釋放OGRLayer
OGRLayer* poLayer = dataset->CreateLayer("houseType", NULL, wkbPolygon, NULL);
//創建后無需顯式釋放OGRLayer
2.3 要素類OGRFeature
要素類OGRFeature一般從圖層類OGRLayer對象中獲取或者創建,不過無論是獲取還是創建都需要進行顯式釋放。例如讀取矢量數據集時遍歷獲取要素:
OGRFeature *poFeature;
while ((poFeature = poLayer->GetNextFeature()) != NULL)
{
OGRGeometry *pGeo = poFeature->GetGeometryRef();
//...
OGRFeature::DestroyFeature(poFeature);
}
這里的OGRFeature::DestroyFeature(poFeature);就是GDAL提供的用于銷毀要素對象的方法。另一方面,如果是寫出數據集創建要素,比如筆者這里創建一個經緯度網格的矢量:
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提供了一個工廠類來創建和銷毀,這是一種非常經典的設計模式:
OGRGeometry* poGeom = OGRGeometryFactory::createGeometry(wkbPoint);
// 使用完 poGeom 后釋放它
OGRGeometryFactory::destroyGeometry(poGeom);
也就是OGRGeometryFactory::createGeometry和OGRGeometryFactory::destroyGeometry需要成對出現。不過筆者認為如果不是為了多態表達,直接使用值對象更加方便,如第2.3節中的示例所示。
另外,OGRGeometry對象是需要放置到OGRFeature對象中的,因此OGRFeature提供了兩個接口:
OGRErr SetGeometryDirectly(OGRGeometry*):淺拷貝OGRGeometry對象,OGRFeature對象直接托管OGRGeometry對象的所有權。OGRErr SetGeometry(const OGRGeometry*):深拷貝OGRGeometry對象,OGRFeature對象托管OGRGeometry拷貝對象的所有權。
另外,幾何類之間的相互引用也是如此,如第2.3節中的示例所示,多邊形增加環也有兩個接口:
addRingDirectly()淺拷貝OGRLinearRing對象,OGRPolygon對象直接托管OGRLinearRing對象的所有權。addRing()深拷貝OGRLinearRing對象,OGRPolygon對象托管OGRLinearRing拷貝對象的所有權。
也就是一般而言,GDAL通常使用Directly后綴的函數接口來表達對原幾何對象的托管。
3. 其他
可以看到,GDAL的資源控制方面還是有點混亂的,有的要顯式釋放,有的又可以托管,有的干脆提供了兩個接口。據說新的GDAL版本引入了很多新的C++特性,估計資源控制的邏輯要清晰一點。另外,我們也可以主動使用一些新的C++特性來避免資源控制需要主動釋放的問題。例如使用智能指針,配合自定義刪除器來銷毀OGRFeature對象,如下例所示:
// 獲取第一個圖層
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字符串
}
}

浙公網安備 33010602011771號