GDALOpen 代码分析

先来一句话,看了这么多GDAL的源代码,并不喜欢其C风格的烙印太重,还是更喜欢boost风格的简洁的现代C++风格。不过为了更好地应用GDAL,更深的定制它,还是需要将源代码看到底。因为GDAL毕竟是一个很好的图像处理的解决方案。复用它,可以省掉很多人年的工作。

GDALOpen函数代码:注释值得一看

/************************************************************************/
/*                              GDALOpen()                              */
/************************************************************************/

/**
 * rief Open a raster file as a GDALDataset.
 *
 * This function will try to open the passed file, or virtual dataset
 * name by invoking the Open method of each registered GDALDriver in turn. 
 * The first successful open will result in a returned dataset.  If all
 * drivers fail then NULL is returned and an error is issued.
 *
 * Several recommandations :
 * <ul>
 * <li>If you open a dataset object with GA_Update access, it is not recommanded
 * to open a new dataset on the same underlying file.</li>
 * <li>The returned dataset should only be accessed by one thread at a time. If you
 * want to use it from different threads, you must add all necessary code (mutexes, etc.)
 * to avoid concurrent use of the object. (Some drivers, such as GeoTIFF, maintain internal
 * state variables that are updated each time a new block is read, thus preventing concurrent
 * use.) </li>
 * </ul>
 *
 * sa GDALOpenShared()
 *
 * @param pszFilename the name of the file to access.  In the case of
 * exotic drivers this may not refer to a physical file, but instead contain
 * information for the driver on how to access a dataset.  It should be in UTF8
 * encoding.
 *
 * @param eAccess the desired access, either GA_Update or GA_ReadOnly.  Many
 * drivers support only read only access.
 *
 * @return A GDALDatasetH handle or NULL on failure.  For C++ applications
 * this handle can be cast to a GDALDataset *. 
 */

GDALDatasetH CPL_STDCALL 
GDALOpen( const char * pszFilename, GDALAccess eAccess )

{
    return GDALOpenInternal(pszFilename, eAccess, NULL);
}

注释提示了几个地方:

1. 会依次调用每个已经注册的driver的open函数,第一个成功的会返回Dataset。这个依次应该是按照注册顺序,先注册的driver先被调用。

2. Dataset对象不是线程安全的,使用者自己注意维护多线程环境下的安全性。

3. 返回NULL代表打开失败

GDALDatasetH实际上是个void* , 又是C的玩法。逃过了编译器类型检查。

/** Opaque type used for the C bindings of the C++ GDALDataset class */
typedef void *GDALDatasetH;


实际上就是CDALDataset* 指针。

真正实现代码在下面的函数里面:

/* The drivers listed in papszAllowedDrivers can be in any order */
/* Only the order of registration will be taken into account */
GDALDatasetH GDALOpenInternal( const char * pszFilename, GDALAccess eAccess,
                               const char* const * papszAllowedDrivers)
{
    VALIDATE_POINTER1( pszFilename, "GDALOpen", NULL );

    int         iDriver;
    GDALDriverManager *poDM = GetGDALDriverManager();
    GDALOpenInfo oOpenInfo( pszFilename, eAccess );
    CPLLocaleC  oLocaleForcer;

    CPLErrorReset();
    CPLAssert( NULL != poDM );

    for( iDriver = 0; iDriver < poDM->GetDriverCount(); iDriver++ )
    {
        GDALDriver      *poDriver = poDM->GetDriver( iDriver );
        GDALDataset     *poDS;

        if (papszAllowedDrivers != NULL &&
            CSLFindString((char**)papszAllowedDrivers, GDALGetDriverShortName(poDriver)) == -1)
            continue;

        if ( poDriver->pfnOpen == NULL )
            continue;

        poDS = poDriver->pfnOpen( &oOpenInfo );
        if( poDS != NULL )
        {
            if( strlen(poDS->GetDescription()) == 0 )
                poDS->SetDescription( oOpenInfo.pszFilename );

            if( poDS->poDriver == NULL )
                poDS->poDriver = poDriver;


            if( CPLGetPID() != GDALGetResponsiblePIDForCurrentThread() )
                CPLDebug( "GDAL", "GDALOpen(%s, this=%p) succeeds as %s (pid=%d, responsiblePID=%d).",
                          pszFilename, poDS, poDriver->GetDescription(),
                          (int)CPLGetPID(), (int)GDALGetResponsiblePIDForCurrentThread() );
            else
                CPLDebug( "GDAL", "GDALOpen(%s, this=%p) succeeds as %s.",
                          pszFilename, poDS, poDriver->GetDescription() );

            return (GDALDatasetH) poDS;
        }

        if( CPLGetLastErrorNo() != 0 )
            return NULL;
    }

    if( oOpenInfo.bStatOK )
        CPLError( CE_Failure, CPLE_OpenFailed,
                  "`%s' not recognised as a supported file format.
",
                  pszFilename );
    else
        CPLError( CE_Failure, CPLE_OpenFailed,
                  "`%s' does not exist in the file system,
"
                  "and is not recognised as a supported dataset name.
",
                  pszFilename );

    return NULL;
}


这段就和注释说的一样,遍历driver,依次尝试打开文件。在我的GeoTiff driver中,open方法会调用geotiff.cpp文件的Open方法:

/************************************************************************/
/*                                Open()                                */
/************************************************************************/

GDALDataset *GTiffDataset::Open( GDALOpenInfo * poOpenInfo )

{
    TIFF	*hTIFF;
    int          bAllowRGBAInterface = TRUE;
    const char  *pszFilename = poOpenInfo->pszFilename;

/* -------------------------------------------------------------------- */
/*      Check if it looks like a TIFF file.                             */
/* -------------------------------------------------------------------- */
    if (!Identify(poOpenInfo))
        return NULL;

    if( EQUALN(pszFilename,"GTIFF_RAW:", strlen("GTIFF_RAW:")) )
    {
        bAllowRGBAInterface = FALSE;
        pszFilename +=  strlen("GTIFF_RAW:");
    }

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

    if (!GTiffOneTimeInit())
        return NULL;

/* -------------------------------------------------------------------- */
/*      Try opening the dataset.                                        */
/* -------------------------------------------------------------------- */
    if( poOpenInfo->eAccess == GA_ReadOnly )
	hTIFF = VSI_TIFFOpen( pszFilename, "r" );
    else
        hTIFF = VSI_TIFFOpen( pszFilename, "r+" );
    
    if( hTIFF == NULL )
        return( NULL );

/* -------------------------------------------------------------------- */
/*      Create a corresponding GDALDataset.                             */
/* -------------------------------------------------------------------- */
    GTiffDataset 	*poDS;

    poDS = new GTiffDataset();
    poDS->SetDescription( pszFilename );
    poDS->osFilename = pszFilename;
    poDS->poActiveDS = poDS;

    if( poDS->OpenOffset( hTIFF, &(poDS->poActiveDS),
                          TIFFCurrentDirOffset(hTIFF), TRUE,
                          poOpenInfo->eAccess, 
                          bAllowRGBAInterface, TRUE,
                          poOpenInfo->papszSiblingFiles) != CE_None )
    {
        delete poDS;
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Initialize any PAM information.                                 */
/* -------------------------------------------------------------------- */
    poDS->TryLoadXML();
    poDS->ApplyPamInfo();

    int i;
    for(i=1;i<=poDS->nBands;i++)
    {
        GTiffRasterBand* poBand = (GTiffRasterBand*) poDS->GetRasterBand(i);

        /* Load scale, offset and unittype from PAM if available */
        if (!poBand->bHaveOffsetScale)
        {
            poBand->dfScale = poBand->GDALPamRasterBand::GetScale(&poBand->bHaveOffsetScale);
            poBand->dfOffset = poBand->GDALPamRasterBand::GetOffset();
        }
        if (poBand->osUnitType.size() == 0)
        {
            const char* pszUnitType = poBand->GDALPamRasterBand::GetUnitType();
            if (pszUnitType)
                poBand->osUnitType = pszUnitType;
        }
    }

    poDS->bMetadataChanged = FALSE;
    poDS->bGeoTIFFInfoChanged = FALSE;

/* -------------------------------------------------------------------- */
/*      Check for external overviews.                                   */
/* -------------------------------------------------------------------- */
    poDS->oOvManager.Initialize( poDS, pszFilename );
    
    return poDS;
}


这里主要看poDs->OpenOffset函数,它负责读取tiff文件的基本信息,以便后面快速读取。因为这么大的文件,显然不可能一次读进内存来。

/************************************************************************/
/*                             OpenOffset()                             */
/*                                                                      */
/*      Initialize the GTiffDataset based on a passed in file           */
/*      handle, and directory offset to utilize.  This is called for    */
/*      full res, and overview pages.                                   */
/************************************************************************/

CPLErr GTiffDataset::OpenOffset( TIFF *hTIFFIn, 
                                 GTiffDataset **ppoActiveDSRef,
                                 toff_t nDirOffsetIn, 
				 int bBaseIn, GDALAccess eAccess,
                                 int bAllowRGBAInterface,
                                 int bReadGeoTransform,
                                 char** papszSiblingFiles )


该函数的定义在geotiff.cpp文件中,非常长,所以这里就不列代码了。

如果要完整的将GeoTiff整个加载过程分析透,需要更多的篇幅。我以后会不断的修改已经有的文章,使得其更准确,并增加新的文章,描述更多的细节。

欢迎讨论。

原文地址:https://www.cnblogs.com/keanuyaoo/p/3260400.html