RFC 11:快速格式识别

作者:弗兰克·温特丹

联系方式:warmerdam@pobox.com

状态:通过(和实施)

总结

这个RFC旨在增加应用程序快速识别文件系统中哪些文件是GDAL支持的文件格式的能力,而不必打开它们中的任何一个。它主要用于允许基于文件类型的GUI文件浏览器。

这是通过扩展GDALOpenInfo结构来保存更多的目录上下文,并通过在GDALDriver上添加一个Identify()方法来实现的,驱动程序可以实现该方法来快速标识文件的给定格式,而无需执行更昂贵的Open()操作。

GDALOpenInfo

许多驱动程序的Open()(或Identify())方法需要探测与目标文件相关联的文件,以便打开或识别具有特定格式的文件。例如,为了打开ESRIBIL文件(EHDR驱动程序),有必要探测与目标文件具有相同基名但扩展名为.hdr的驱动程序。目前,这通常是通过VSIFStatL()调用或类似的方法来完成的,这可能相当昂贵。

为了减少对操作系统文件系统机器的此类搜索的需要,GDALOpenInfo结构将被扩展以保存一个可选的文件列表。这是文件系统中与目标文件处于同一级别的所有文件(包括目标文件)的列表。文件名将 not 包含任何路径组件,本质上都是父目录上CPLReadDir()的输出。如果目标对象没有文件系统语义,则文件列表应为空。

将以下内容添加到GDALOpenInfo:

       GDALOpenInfo( const char * pszFile, GDALAccess eAccessIn, char **papszSiblings );
char **papszSiblingFiles;

新的构造函数允许传入文件列表来填充papszSiblingFiles成员(参数将被复制)。现有的默认构造函数将使用CPLGetDirname()获取传递的pszFile的目录,并使用CPLReadDir()读取相应的文件列表。新的构造函数主要是为了高效地实现后面的GDALIdentifyDriver()函数,避免重新读取要测试的每个文件的文件列表。

识别()

GDALDriver类将使用以下函数进行扩展:

int      (*pfnIdentify)( GDALOpenInfo * );

当由驱动程序实现时,如果驱动程序确定通过GDALOpenInfo传入的文件的格式似乎与实现驱动程序的格式相同,则函数将返回TRUE(非零)。要调用此应用程序,应调用新函数:

GDALDriverH *GDALIdentifyDriver( const char *pszDatasource, const char **papszDirFiles );

在内部GDALIdentifyDriver()将执行以下操作

  1. 将基于pszDatasource和papszdir文件初始化GDALOpenInfo结构。

  2. 它将迭代所有驱动程序,与GDALOpen()类似。对于每个驱动程序,如果可用,它将使用pfnIdentify函数,否则它将使用pfnOpen()方法来确定驱动程序是否支持该文件。

  3. 它将返回驱动程序句柄,以便第一个驱动程序作出肯定的响应,如果没有人接受它,则返回NULL。

驱动程序更改

理论上,不需要修改任何驱动程序,因为GDALIdentifyDriver()将回退到使用pfnOpen函数进行测试。但实际上,除非至少更新一些驱动程序(希望那些打开的驱动程序可能非常昂贵),否则无法实现优化。接下来正在进行的工作的一部分是实现GDAL驱动程序的标识功能。

一般来说,应该很容易从开放函数中的初始测试逻辑创建一个标识函数。例如,GeoTIFF驱动程序可能会这样更改:

int GTiffDataset::Identify( GDALOpenInfo * poOpenInfo )

{
/* -------------------------------------------------------------------- */
/*      We have a special hook for handling opening a specific          */
/*      directory of a TIFF file.                                       */
/* -------------------------------------------------------------------- */
    if( EQUALN(poOpenInfo->pszFilename,"GTIFF_DIR:",10) )
        return TRUE;

/* -------------------------------------------------------------------- */
/*  First we check to see if the file has the expected header   */
/*  bytes.                              */
/* -------------------------------------------------------------------- */
    if( poOpenInfo->nHeaderBytes < 2 )
        return FALSE;

    if( (poOpenInfo->pabyHeader[0] != 'I' || poOpenInfo->pabyHeader[1] != 'I')
        && (poOpenInfo->pabyHeader[0] != 'M' || poOpenInfo->pabyHeader[1] != 'M'))
        return FALSE;

    // We can't support BigTIFF files for now.
    if( poOpenInfo->pabyHeader[2] == 43 && poOpenInfo->pabyHeader[3] == 0 )
        return FALSE;


    if( (poOpenInfo->pabyHeader[2] != 0x2A || poOpenInfo->pabyHeader[3] != 0)
        && (poOpenInfo->pabyHeader[3] != 0x2A || poOpenInfo->pabyHeader[2] != 0) )
        return FALSE;

    return TRUE;
}

然后可以修改open以使用identify函数来避免重复测试逻辑。

GDALDataset *GTiffDataset::Open( GDALOpenInfo * poOpenInfo )

{
    TIFF    *hTIFF;

    if( !Identify( poOpenInfo ) )
        return NULL;

/* -------------------------------------------------------------------- */
/*      We have a special hook for handling opening a specific          */
/*      directory of a TIFF file.                                       */
/* -------------------------------------------------------------------- */
    if( EQUALN(poOpenInfo->pszFilename,"GTIFF_DIR:",10) )
        return OpenDir( poOpenInfo->pszFilename );

    GTiffOneTimeInit();
...

需要头文件(如EHdr驱动程序)的驱动程序可能实现如下Identify():

int EHdrDataset::Identify( GDALOpenInfo * poOpenInfo )

{
    int     i, bSelectedHDR;
    const char  *pszHDRFilename;

/* -------------------------------------------------------------------- */
/*  We assume the user is pointing to the binary (ie. .bil) file.   */
/* -------------------------------------------------------------------- */
    if( poOpenInfo->nHeaderBytes < 2 )
        return FALSE;

/* -------------------------------------------------------------------- */
/*      Now we need to tear apart the filename to form a .HDR           */
/*      filename.                                                       */
/* -------------------------------------------------------------------- */
    CPLString osBasename = CPLGetBasename( poOpenInfo->pszFilename );
    pszHDRFilename = CPLFormCIFilename( "", osBasename, "hdr" );

    if( CSLFindString( poOpenInfo->papszSiblingFiles, pszHDRFilename) )
        return TRUE;
    else
        return FALSE;
}

在初始实现期间,将更新各种驱动程序,包括以下内容。此外,还将进行一些性能和文件系统活动日志记录,以确定当前价格昂贵的驱动程序。

  • HFA

  • GTiff

  • JPEG

  • PNG

  • GIF

  • HDF4型

  • DTED

  • 美国地质勘探局数字高程模型

  • 影像数据库

  • 2千日元

  • ECW

  • EHdr

  • RST

CPLReadDir()

目前,在cpl_vsi_mem.cpp中实现的VSIMemFilesystemHandler提供了对内存中对象的“类文件系统”访问,但它没有实现目录读取服务。为了正确地填充目录列表,需要添加这个。

为此,还需要重新实现cpl ReadDir()函数,以使用VSIFilesystemHandler::ReadDir(),而不是在cpl dir.cpp中直接实现。VSIFilesystemHandler::ReadDir()的win32和unix/posix实现已经存在。这将基本上完成文件系统访问服务的虚拟化。

CPLReadDir()也将被重命名为VSIReadDir(),但使用旧名称下的存根可向后兼容。

兼容性

没有预期的向后兼容性问题。不过,转发兼容性将受到影响,因为在trunk中更新的带有标识功能的驱动程序将无法移植回1.4版本并使用它们。未修改的驱动程序和外部维护的驱动程序不应受到此开发的影响。

SWIG含义

GDALIdentifyDriver()和VSIReadDir()函数需要通过SWIG公开。

回归检验

Identify()函数的测试脚本将添加到autotest/gcore目录中。它将包括在a/vsimem内存集合中测试标识。

实施计划

新功能将由Frank wartemdam在 大旅行箱 对于GDAL/OGR 1.5.0版本。

性能试验

引入标识而不实际打开的快速测试将一个目录中包含70个TIFF文件(在NFS共享上)的所有文件的标识时间从2秒更改为0.5秒。因此,节省实际打开文件的开销对于某些格式来说非常重要,包括像GeoTIFF这样的非常常见的格式。