矢量驱动程序实现教程

整体方法

一般来说,通过实现特定于格式的驱动程序并实例化 GDALDriver 以及 GDALDatasetOGRLayer . 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,我们也没有什么可以做的了。

在那里!我们已经完成了一个简单的只读功能文件格式驱动程序。