矢量驱动程序实现教程
整体方法
一般来说,通过实现特定于格式的驱动程序并实例化 GDALDriver
以及 GDALDataset
和 OGRLayer
. GDALDriver实例在 GDALDriverManager
在运行时。
在遵循本教程实现OGR驱动程序之前,请查看 矢量数据模型 仔细记录。
本教程将基于实现一个简单的ascii点格式。
实施GDALDriver
特定于格式的驱动程序类是作为GDALDriver的实例实现的。通常会创建一个驱动程序实例,并在GDALDriverManager中注册。驱动程序的实例化通常由一个全局C可调用注册函数处理,类似于将以下内容放在驱动程序类所在的同一文件中。
void RegisterOGRSPF()
{
if( GDALGetDriverByName("SPF") != NULL )
return;
GDALDriver *poDriver = new GDALDriver();
poDriver->SetDescription("SPF");
poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Long name for SPF driver");
poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "spf");
poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drv_spf.html");
poDriver->pfnOpen = OGRSPFDriverOpen;
poDriver->pfnIdentify = OGRSPFDriverIdentify;
poDriver->pfnCreate = OGRSPFDriverCreate;
poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
GetGDALDriverManager()->RegisterDriver(poDriver);
}
这个 GDALDriver::SetDescription()
设置驱动程序的名称。在创建数据源时,此名称在命令行中指定,因此通常最好保持简短,并且没有任何特殊字符或空格。
指定SetMetadataItem(GDAL_DCAP_VECTOR,“YES”)表示驱动程序将处理矢量数据。
指定SetMetadataItem(GDAL_DCAP_VIRTUALIO,“YES”)表示驱动程序可以处理使用VSI*L GDAL API打开的文件。否则不应定义此元数据项。
对于具有读取或读取和更新访问(Open()方法)和创建支持(Create()方法)的格式,驱动程序声明通常如下所示。
static GDALDataset* OGRSPFDriverOpen(GDALOpenInfo* poOpenInfo);
static int OGRSPFDriverIdentify(GDALOpenInfo* poOpenInfo);
static GDALDataset* OGRSPFDriverCreate(const char* pszName, int nXSize, int nYSize,
int nBands, GDALDataType eDT, char** papszOptions);
Open()方法由调用 GDALOpenEx()
. 如果传递的文件名不是驱动程序支持的格式,则应悄悄返回NULL。如果是目标格式,则应返回数据集的新GDALDataset对象。
Open()方法通常被委托给实际格式的GDALDataset类上的Open()方法。
static GDALDataset *OGRSPFDriverOpen( GDALOpenInfo* poOpenInfo )
{
if( !OGRSPFDriverIdentify(poOpenInfo) )
return NULL;
OGRSPFDataSource *poDS = new OGRSPFDataSource();
if( !poDS->Open(poOpenInfo->pszFilename, poOpenInfo->eAccess == GA_Update) )
{
delete poDS;
return NULL;
}
return poDS;
}
Identify()方法的实现方式如下:
static int OGRSPFDriverIdentify( GDALOpenInfo* poOpenInfo )
{
// Does this appear to be an .spf file?
return EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "spf");
}
Create()方法的示例留给创建和更新部分。
基本只读数据源
我们将开始实现一个最小的只读数据源。不尝试优化操作,而是使用从GDALDataset继承的许多方法的默认实现。
数据源的主要职责是管理层列表。在SPF格式的情况下,数据源是表示一个层的单个文件,因此最多只有一个层。数据源的“名称”通常应该是传递给Open()方法的名称。
下面的Open()方法没有重写基类方法,但是我们有它来实现驱动程序类委托的Open操作。
对于这个简单的例子,我们提供一个存根 GDALDataset::TestCapability()
对所有扩展功能返回FALSE。TestCapability()方法是纯虚拟的,因此它确实需要实现。
class OGRSPFDataSource : public GDALDataset
{
OGRSPFLayer **papoLayers;
int nLayers;
public:
OGRSPFDataSource();
~OGRSPFDataSource();
int Open( const char *pszFilename, int bUpdate );
int GetLayerCount() { return nLayers; }
OGRLayer *GetLayer( int );
int TestCapability( const char * ) { return FALSE; }
};
构造函数是默认状态的简单初始值设定项。Open()将实际将其附加到文件。析构函数负责层的有序清理。
OGRSPFDataSource::OGRSPFDataSource()
{
papoLayers = NULL;
nLayers = 0;
}
OGRSPFDataSource::~OGRSPFDataSource()
{
for( int i = 0; i < nLayers; i++ )
delete papoLayers[i];
CPLFree(papoLayers);
}
Open()方法是数据源中最重要的方法,不过在这个特定的实例中,如果它认为文件是所需的格式,那么它会将大部分工作传递给OGRSPFLayer构造函数。
请注意,Open()方法应该尽可能有效地确定文件不是标识的格式,因为在到达正确的驱动程序之前,可能会使用错误格式的文件调用许多驱动程序。在这个特定的Open()中,我们只是测试文件扩展名,但这通常是识别文件格式的一种糟糕方法。如果有的话,最好检查“magic header values”或类似的内容。
在SPF格式的情况下,不支持就地更新,因此如果bUpdate为FALSE,我们总是失败。
int OGRSPFDataSource::Open( const char *pszFilename, int bUpdate )
{
if( bUpdate )
{
CPLError(CE_Failure, CPLE_OpenFailed,
"Update access not supported by the SPF driver.");
return FALSE;
}
// Create a corresponding layer.
nLayers = 1;
papoLayers = static_cast<OGRSPFLayer **>(CPLMalloc(sizeof(void *)));
papoLayers[0] = new OGRSPFLayer(pszFilename);
pszName = CPLStrdup(pszFilename);
return TRUE;
}
还需要实现GetLayer()方法。由于层列表是在Open()中创建的,所以这只是一个查找,并进行了一些安全测试。
OGRLayer *OGRSPFDataSource::GetLayer( int iLayer )
{
if( iLayer < 0 || iLayer >= nLayers )
return NULL;
return papoLayers[iLayer];
}
只读层
OGRSPFLayer为.spf文件实现层语义。它提供对具有特定属性列集的一致坐标系中的一组要素对象的访问。我们的类定义如下:
class OGRSPFLayer : public OGRLayer
{
OGRFeatureDefn *poFeatureDefn;
FILE *fp;
int nNextFID;
public:
OGRSPFLayer( const char *pszFilename );
~OGRSPFLayer();
void ResetReading();
OGRFeature * GetNextFeature();
OGRFeatureDefn * GetLayerDefn() { return poFeatureDefn; }
int TestCapability( const char * ) { return FALSE; }
};
层构造函数负责初始化。最重要的初始化是设置 OGRFeatureDefn
对于层。这将定义字段及其类型、几何图形类型和图层的坐标系的列表。在SPF格式中,字段集是固定的-一个字符串字段,我们没有坐标系信息可设置。
请特别注意OGRFeatureDefn的引用计数。由于该层的OGRFeature也将引用该定义,因此我们还必须代表层本身建立一个引用。
OGRSPFLayer::OGRSPFLayer( const char *pszFilename )
{
nNextFID = 0;
poFeatureDefn = new OGRFeatureDefn(CPLGetBasename(pszFilename));
SetDescription(poFeatureDefn->GetName());
poFeatureDefn->Reference();
poFeatureDefn->SetGeomType(wkbPoint);
OGRFieldDefn oFieldTemplate("Name", OFTString);
poFeatureDefn->AddFieldDefn(&oFieldTemplate);
fp = VSIFOpenL(pszFilename, "r");
if( fp == NULL )
return;
}
注意析构函数使用 OGRFeatureDefn::Release()
在OGRFeatureDefn上。如果引用计数降至零,这将破坏功能定义,但如果应用程序仍保留来自此层的功能,则该功能将保留对功能定义的引用,并且不会在此处破坏(这很好!)。
OGRSPFLayer::~OGRSPFLayer()
{
poFeatureDefn->Release();
if( fp != NULL )
VSIFCloseL(fp);
}
这个 OGRLayer::GetNextFeature()
方法通常是OGRLayer实现的工作马。它负责根据当前安装的空间和属性筛选器读取下一个功能。
while()循环存在于循环中,直到找到满意的特征。代码的第一部分用于解析SPF文本文件的一行,并为该行建立x、y和名称。
OGRFeature *OGRSPFLayer::GetNextFeature()
{
// Loop till we find a feature matching our requirements.
while( true )
{
const char *pszLine = CPLReadLineL(fp);
// Are we at end of file (out of features)?
if( pszLine == NULL )
return NULL;
const double dfX = atof(pszLine);
pszLine = strstr(pszLine,"|");
if( pszLine == NULL )
continue; // we should issue an error!
else
pszLine++;
const double dfY = atof(pszLine);
pszLine = strstr(pszLine,"|");
const char *pszName = NULL;
if( pszLine == NULL )
continue; // we should issue an error!
else
pszName = pszLine + 1;
下一节将x、y和名称转换为一个要素。还要注意,我们分配了一个线性递增的特征id。在我们的例子中,第一个特征的起始值是0,尽管有些驱动程序的起始值是1。
OGRFeature *poFeature = new OGRFeature(poFeatureDefn);
poFeature->SetGeometryDirectly(new OGRPoint(dfX, dfY));
poFeature->SetField(0, pszName);
poFeature->SetFID(nNextFID++);
接下来,我们检查该特性是否与当前属性或空间过滤器(如果有)匹配。OGRLayer基类上的方法支持在OGRLayer成员字段中维护筛选器 OGRLayer::m_poFilterGeom
(空间滤波器)和 OGRLayer::m_poAttrQuery
(属性过滤器)所以如果这些值不为空,我们可以在这里使用它们。下面的测试本质上是“stock”,在所有格式中都是这样做的。一些格式还使用空间索引提前进行一些空间过滤。
如果这个功能符合我们的标准,我们就返回它。否则,我们销毁它,并返回循环的顶部以获取另一个来尝试。
if( (m_poFilterGeom == NULL ||
FilterGeometry(poFeature->GetGeometryRef())) &&
(m_poAttrQuery == NULL ||
m_poAttrQuery->Evaluate(poFeature)) )
return poFeature;
delete poFeature;
}
}
在从层读取功能集的过程中,或在应用程序可以调用的任何其他时间 OGRLayer::ResetReading()
其目的是在功能集的开头重新开始读取。我们通过寻找文件的开头并重置特性id计数器来实现这一点。
void OGRSPFLayer::ResetReading()
{
VSIFSeekL(fp, 0, SEEK_SET);
nNextFID = 0;
}
在此实现中,我们不为GetFeature()方法提供自定义实现。这意味着试图通过某个特性的特性id读取该特性将导致多次调用GetNextFeature(),直到找到所需的特性为止。然而,在一个连续的文本格式,如spf,我们也没有什么可以做的了。
在那里!我们已经完成了一个简单的只读功能文件格式驱动程序。