GDAL驱动实现向导

翻译:柴树杉(chaishushan@gmail.com)
原文:http://www.gdal.org/gdal_drivertut.html

通常,可以重新继承GDALDataset和GDALRasterBand来实现对特定格式数据的支持。 同样,还需要为这种格式创建一个GDALDriver的实例,让后通过GDALDriverManager将 驱动注册给GDAL系统。

该教程将为JDEM格式数据实现一个简单的只读驱动开始,进而使用RawRasterBand帮助类, 实现一个可创建和更改的格式以及比较高级的一些问题。

强烈建议在实现GDAL驱动之前前面所描述的GDAL数据模型的内容。

目录

  1. 子类化Dataset
  2. 子类化RasterBand
  3. 驱动
  4. 将驱动添加到GDAL中
  5. 添加参照系
  6. Overview
  7. 创建文件
  8. RawDataset/RawRasterBand Helper Classes
  9. 元数据以及其他扩展

子类化Dataset

将将演示一个小日本DEM驱动的最基本的实现。首先,从GDALDataset继承一个 子类JDEMDataset,JDEMDataset对应小日本格式DEM数据。

class JDEMDataset : public GDALDataset
{
    FILE        *fp;
    GByte       abyHeader[1012];

  public:
                ~JDEMDataset();
    
    static GDALDataset *Open( GDALOpenInfo * );
};

我们可以通过重载基类GDALDataset中的一些虚函数来为驱动重新实现某些特殊的 功能。然而,Open()相对比较特殊,它不是基类 GDALDataset的虚函数。我们 需要一个独立的函数来实现这个功能,因此我们把他声明为static的。将Open()声明 为 JDEMDataset的static函数比较方便,因为这样可以用JDEMDataset的私有方法去 修改内容。

Open()函数具体实现如下:

GDALDataset *JDEMDataset::Open( GDALOpenInfo * poOpenInfo )

{
// -------------------------------------------------------------------- 
//      Before trying JDEMOpen() we first verify that there is at       
//      least one "\n#keyword" type signature in the first chunk of     
//      the file.                                                       
// -------------------------------------------------------------------- 
    if( poOpenInfo->fp == NULL || poOpenInfo->nHeaderBytes < 50 )
        return NULL;

    // check if century values seem reasonable 
    if( (!EQUALN((char *)poOpenInfo->pabyHeader+11,"19",2)
          && !EQUALN((char *)poOpenInfo->pabyHeader+11,"20",2))
        || (!EQUALN((char *)poOpenInfo->pabyHeader+15,"19",2)
             && !EQUALN((char *)poOpenInfo->pabyHeader+15,"20",2))
        || (!EQUALN((char *)poOpenInfo->pabyHeader+19,"19",2)
             && !EQUALN((char *)poOpenInfo->pabyHeader+19,"20",2)) )
    {
        return NULL;
    }
    
// -------------------------------------------------------------------- 
//      Create a corresponding GDALDataset.                             
// -------------------------------------------------------------------- 
    JDEMDataset 	*poDS;

    poDS = new JDEMDataset();

    poDS->fp = poOpenInfo->fp;
    poOpenInfo->fp = NULL;
    
// -------------------------------------------------------------------- 
//      Read the header.                                                
// -------------------------------------------------------------------- 
    VSIFSeek( poDS->fp, 0, SEEK_SET );
    VSIFRead( poDS->abyHeader, 1, 1012, poDS->fp );

    poDS->nRasterXSize = JDEMGetField( (char *) poDS->abyHeader + 23, 3 );
    poDS->nRasterYSize = JDEMGetField( (char *) poDS->abyHeader + 26, 3 );

// -------------------------------------------------------------------- 
//      Create band information objects.                                
// -------------------------------------------------------------------- 
    poDS->nBands = 1;
    poDS->SetBand( 1, new JDEMRasterBand( poDS, 1 ));

    return( poDS );
}

任何数据集打开文件之前,都要判断驱动是否支持该类型。我们应该知道, 在打开文件的时候每个驱动的open函数将被依次调用,直到有一个成功为止。 如果传递的文件不是驱动所支持的类型,则它们必须返回NULL。如果格式是它们所 支持的类型,但是数据已经被破坏,那么它们应该产生一个错误。

文件的信息在文件打开之后被传递给GDALOpenInfo对象。GDALOpenInfo对象的公有成员如下:

    char        *pszFilename;

    GDALAccess  eAccess; // GA_ReadOnly or GA_Update

    GBool       bStatOK;
    VSIStatBuf  sStat;
    
    FILE        *fp;

    int         nHeaderBytes;
    GByte       *pabyHeader;

驱动可以检查这些值来判断是否支持这个格式的文件。如果pszFilename指向 一个文件系统对象,那么bStatOK将被设置,并且sStat结构将包含关于对象的 通用信息。如果对象是一个规则的可读文件,那么fp指针将非空,并且可以使 用fp来读取文件(请使用cpl_vsi.h中标准输入输出文件)。最后,如果文件可以 被打开,那么开头的近1K字节的数据将被读到pabyHeader中,nHeaderBytes保存 实际读取的字节数。

在这个例子中,假设文件已经被打开并且可以测试文件头部的一些信息。在这里, JDEM并没有魔术数字,因此我们只是检测不同的数据域。如果文件不是当前驱动所 支持的格式,那么将返回NULL。

    if( poOpenInfo->fp == NULL || poOpenInfo->nHeaderBytes < 50 )
        return NULL;

    // check if century values seem reasonable
    if( (!EQUALN((char *)poOpenInfo->pabyHeader+11,"19",2)
          && !EQUALN((char *)poOpenInfo->pabyHeader+11,"20",2))
        || (!EQUALN((char *)poOpenInfo->pabyHeader+15,"19",2)
             && !EQUALN((char *)poOpenInfo->pabyHeader+15,"20",2))
        || (!EQUALN((char *)poOpenInfo->pabyHeader+19,"19",2)
             && !EQUALN((char *)poOpenInfo->pabyHeader+19,"20",2)) )
    {
        return NULL;
    }

在真正的测试中,测试代码越严格越好。这里的测试相对比较弱。如果一个文件的 在相应的位置具有相同的内容,那么它们很可能被错误地当作JDEM格式处理,最终 导致不可预期的结果。

如果文件是我们支持的类型,我们需要创建一个database的实例,并在database中 记录必要的信息。

    JDEMDataset         *poDS;

    poDS = new JDEMDataset();

    poDS->fp = poOpenInfo->fp;
    poOpenInfo->fp = NULL;

通常在这个时刻我们将打开文件(这里已经打开了),并且保存文件的指针。 然而,如果只是只读的话,仅仅从GDALOpenInfo中获取文件的指针也是可以的。 在这里我们将GDALOpenInfo的文件指针设置为NULL,以防止文件可能被关闭两次 (因为JDEMDataset的析构函数中已经关闭了文件)。同样,我们假设文件的当前读写 状态是不确定的,因此我们需要用VSIFSeek()重新定位文件的当前地址。下面的两行 代码完成了重新定位和读文件头的工作。

    VSIFSeek( poDS->fp, 0, SEEK_SET );
    VSIFRead( poDS->abyHeader, 1, 1012, poDS->fp );

接着,从abyHeader中获取x和y的大小。变量nRasterXSize和nRasterYSize是 从基类GDALDataset中继承的,并且必须在Open()中被设置。

    poDS->nRasterXSize = JDEMGetField( (char *) poDS->abyHeader + 23, 3 );
    poDS->nRasterYSize = JDEMGetField( (char *) poDS->abyHeader + 26, 3 );

最后,通过SetBand()将所有的波段与当前的GDALDataset对象绑定。在下一节, 我们将讨论JDEMRasterBand类的具体细节。

    poDS->SetBand( 1, new JDEMRasterBand( poDS, 1 ));

    return( poDS );

子类化RasterBand

和常规的从JDEMDataset继承的子类一样,我们需要为JDEMRasterBand定义一个 接受每个波段数据的一致的入口。JDEMRasterBand类的定义如下:

class JDEMRasterBand : public GDALRasterBand
{
  public:
                JDEMRasterBand( JDEMDataset *, int );
    virtual CPLErr IReadBlock( int, int, void * );
};

构造函数可以任意定义,但是只能从Open()函数中调用。其他的虚函数必须 和gdal_priv.h中定义的一致,例如IReadBlock()。构造函数实现代码如下:

JDEMRasterBand::JDEMRasterBand( JDEMDataset *poDS, int nBand )

{
    this->poDS = poDS;
    this->nBand = nBand;
    
    eDataType = GDT_Float32;

    nBlockXSize = poDS->GetRasterXSize();
    nBlockYSize = 1;
}

下面成员变量从GDALRasterBand继承,并且通常在构造函数中设置。

所有的GDALDataType类型在gdal.h文件中定义,包括GDT_Byte、GDT_UInt16、 GDT_Int16和 GDT_Float32。块的尺寸(block size)记录数据的实际或有效的大小。 对于约束数据集这将是一个约束尺寸,而对于其他大多数数据而言这将是一个扫描线。

接下来,我们将实现真正读取影象数据的代码——IReadBlock()。

CPLErr JDEMRasterBand::IReadBlock( int nBlockXOff, int nBlockYOff,
                                  void * pImage )

{
    JDEMDataset *poGDS = (JDEMDataset *) poDS;
    char        *pszRecord;
    int         nRecordSize = nBlockXSize*5 + 9 + 2;
    int         i;

    VSIFSeek( poGDS->fp, 1011 + nRecordSize*nBlockYOff, SEEK_SET );

    pszRecord = (char *) CPLMalloc(nRecordSize);
    VSIFRead( pszRecord, 1, nRecordSize, poGDS->fp );

    if( !EQUALN((char *) poGDS->abyHeader,pszRecord,6) )
    {
        CPLFree( pszRecord );

        CPLError( CE_Failure, CPLE_AppDefined, 
                  "JDEM Scanline corrupt.  Perhaps file was not transferred\n"
                  "in binary mode?" );
        return CE_Failure;
    }
    
    if( JDEMGetField( pszRecord + 6, 3 ) != nBlockYOff + 1 )
    {
        CPLFree( pszRecord );

        CPLError( CE_Failure, CPLE_AppDefined, 
                  "JDEM scanline out of order, JDEM driver does not\n"
                  "currently support partial datasets." );
        return CE_Failure;
    }

    for( i = 0; i < nBlockXSize; i++ )
        ((float *) pImage)[i] = JDEMGetField( pszRecord + 9 + 5 * i, 5) * 0.1;

    return CE_None;
}

需要注意的地方:

驱动

虽然JDEMDataset和JDEMRasterBand已经可以读数据,但是GDAL仍然不知道关于 JDEMDataset驱动的任何信息。这可以通过GDALDriverManager来实现。为了注册我们 自己实现的驱动,我们需要重新实现一个注册函数:

CPL_C_START
void    GDALRegister_JDEM(void);
CPL_C_END

...

void GDALRegister_JDEM()

{
    GDALDriver  *poDriver;

    if( GDALGetDriverByName( "JDEM" ) == NULL )
    {
        poDriver = new GDALDriver();
        
        poDriver->SetDescription( "JDEM" );
        poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, 
                                   "Japanese DEM (.mem)" );
        poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, 
                                   "frmt_various.html#JDEM" );
        poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "mem" );

        poDriver->pfnOpen = JDEMDataset::Open;

        GetGDALDriverManager()->RegisterDriver( poDriver );
    }
}

当第一次被调用的时候,注册函数将new一个GDALDriver对象,并且通过GDALDriverManager来注册。在用GDALDriverManager注册驱动之前,需要设置以下的成员变量:

将驱动添加到GDAL中

GDALRegister_JDEM()函数必须被更高层次的函数调用以生成关于JDEM的驱动。通常实现一个驱动的时候需要做以下事情:

  1. 在gdal/frmts下创建一个驱动目录,目录的名字和驱动的短名字相同。

  2. 在驱动的目录中添加GNUmakefile和makefile.vc两个文件,具体的格式可以 参考其他驱动目录。(例如jdem目录)

  3. 为dataset和rasterband添加实现模块。通常情况下是调用一个 <short_name>dataset.cpp文件(这里为jdemdataset.cpp)。该文件一般放置 关于GDAL的特殊代码,当然这不是必须的。

  4. 在gdal/gcore/gdal_frmts.h文件中添加注册入口点声明(这里为GDALRegister_JDEM())。

  5. 在frmts/gdalallregister.c文件中添加一个注册函数的调用, 最好是在ifdef之间(可以参考已有的代码)。

  6. 在GDALmake.opt.in(和GDALmake.opt)文件中的GDAL_FORMATS宏中添加格式短名称。

  7. 在frmts/makefile.vc的EXTRAFLGS宏中添加格式特别项。

一旦所有的这些操作都完成,我们将重新构件GDAL,并且所有的应用程序都将识别 新的格式。gdalinfo程序可以用来测打开数据和显示信息。gdal_translate可以用 来测试影象的读操作。

添加参照系

现在我们继续晚上这个驱动,并添加参照系的支持。我们将在JDEMDataset中重新实现 两个虚函数,注意要和GDALRasterDataset中函数一致。

    CPLErr      GetGeoTransform( double * padfTransform );
    const char *GetProjectionRef();

重新实现的GetGeoTransform()函数只是复制地理转换矩阵到缓冲。GetGeoTransform() 函数可能经常使用,因此一般最好保持该函数短小。在许多时候,在Open()函数 将收集地理转换,并且用这个函数复制。需要注意的是,转换矩阵对应像素左上角 为原点,而不是中心。

CPLErr JDEMDataset::GetGeoTransform( double * padfTransform )

{
    double      dfLLLat, dfLLLong, dfURLat, dfURLong;

    dfLLLat = JDEMGetAngle( (char *) abyHeader + 29 );
    dfLLLong = JDEMGetAngle( (char *) abyHeader + 36 );
    dfURLat = JDEMGetAngle( (char *) abyHeader + 43 );
    dfURLong = JDEMGetAngle( (char *) abyHeader + 50 );
    
    padfTransform[0] = dfLLLong;
    padfTransform[3] = dfURLat;
    padfTransform[1] = (dfURLong - dfLLLong) / GetRasterXSize();
    padfTransform[2] = 0.0;
        
    padfTransform[4] = 0.0;
    padfTransform[5] = -1 * (dfURLat - dfLLLat) / GetRasterYSize();


    return CE_None;
}

GetProjectionRef() 方法将返回一个字符串,其中包括OGC WKT格式的坐标系统 的定义。在这个例子中,坐标系统适合于这种格式的所有数据。但是在比较复杂的 数据格式中,我们可以需要使用 OGRSpatialReference得到针对与特定情形的更具体 的坐标系统。

const char *JDEMDataset::GetProjectionRef()

{
    return( "GEOGCS[\"Tokyo\",DATUM[\"Tokyo\",SPHEROID[\"Bessel 1841\","
        "6377397.155,299.1528128,AUTHORITY[\"EPSG\",7004]],TOWGS84[-148,"
        "507,685,0,0,0,0],AUTHORITY[\"EPSG\",6301]],PRIMEM[\"Greenwich\","
        "0,AUTHORITY[\"EPSG\",8901]],UNIT[\"DMSH\",0.0174532925199433,"
        "AUTHORITY[\"EPSG\",9108]],AXIS[\"Lat\",NORTH],AXIS[\"Long\",EAST],"
        "AUTHORITY[\"EPSG\",4301]]" );
}

到这里已经能够完成了JDEM驱动的部分特征代码,我们重新回顾一下前面的代码。

Overview

GDAL allows file formats to make pre-built overviews available to applications via the GDALRasterBand::GetOverview() and related methods. However, implementing this is pretty involved, and goes beyond the scope of this document for now. The GeoTIFF driver (gdal/frmts/gtiff/geotiff.cpp) and related source can be reviewed for an example of a file format implementing overview reporting and creation support.

Formats can also report that they have arbitrary overviews, by overriding the HasArbitraryOverviews() method on the GDALRasterBand, returning TRUE. In this case the raster band object is expected to override the RasterIO() method itself, to implement efficient access to imagery with resampling. This is also involved, and there are a lot of requirements for correct implementation of the RasterIO() method. An example of this can be found in the OGDI and ECW formats.

However, by far the most common approach to implementing overviews is to use the default support in GDAL for external overviews stored in TIFF files with the same name as the dataset, but the extension .ovr appended. In order to enable reading and creation of this style of overviews it is necessary for the GDALDataset to initialize the oOvManager object within itself. This is typically accomplished with a call like the following near the end of the Open() method.

    poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename );

This will enable default implementations for reading and creating overviews for the format. It is advised that this be enabled for all simple file system based formats unless there is a custom overview mechanism to be tied into.

创建文件

有两种方法创建文件。第一种是调用CreateCopy()函数,包含以输出格式写影象 的函数和从源影象中获取信息的函数。第二种是调用Create动态创建文件, 包含Create函数和用来设置各种信息的函数。

第一种方法的优点是在创建文件的所有的信息都被输出。对于通过在文件创建时需要例如颜 色图,参照系外在库实现文件格式是特别重要的。关于这种方法其他的优点是CreateCopy方法 读取各种没有相应的设置方法的信息,例如min/max、scaling、description和GCPs。

第二种方法的优点在于可以创建一个空的新文件,并且在需要的时候把结果写入其中。 对于一个影象来说,动态创建不需要提前获取所有信息。

对于比较重要的格式而言,两种方法最好都支持。

CreateCopy

GDALDriver::CreateCopy()方法可以直接调用,因此只需要知道调用的参数既可。不过以下的细节需要注意:

JPEG格式对应的CreateCopy的代码如下:

static GDALDataset *
JPEGCreateCopy( const char * pszFilename, GDALDataset *poSrcDS, 
                int bStrict, char ** papszOptions, 
                GDALProgressFunc pfnProgress, void * pProgressData )

{
    int  nBands = poSrcDS->GetRasterCount();
    int  nXSize = poSrcDS->GetRasterXSize();
    int  nYSize = poSrcDS->GetRasterYSize();
    int  nQuality = 75;
    int  bProgressive = FALSE;

// -------------------------------------------------------------------- 
//      Some some rudimentary checks                                    
// -------------------------------------------------------------------- 
    if( nBands != 1 && nBands != 3 )
    {
        CPLError( CE_Failure, CPLE_NotSupported, 
                  "JPEG driver doesn't support %d bands.  Must be 1 (grey) "
                  "or 3 (RGB) bands.\n", nBands );

        return NULL;
    }

    if( poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte && bStrict )
    {
        CPLError( CE_Failure, CPLE_NotSupported, 
                  "JPEG driver doesn't support data type %s. "
                  "Only eight bit byte bands supported.\n", 
                  GDALGetDataTypeName( 
                      poSrcDS->GetRasterBand(1)->GetRasterDataType()) );

        return NULL;
    }

// -------------------------------------------------------------------- 
//      What options has the user selected?                             
// -------------------------------------------------------------------- 
    if( CSLFetchNameValue(papszOptions,"QUALITY") != NULL )
    {
        nQuality = atoi(CSLFetchNameValue(papszOptions,"QUALITY"));
        if( nQuality < 10 || nQuality > 100 )
        {
            CPLError( CE_Failure, CPLE_IllegalArg,
                      "QUALITY=%s is not a legal value in the range 10-100.",
                      CSLFetchNameValue(papszOptions,"QUALITY") );
            return NULL;
        }
    }

    if( CSLFetchNameValue(papszOptions,"PROGRESSIVE") != NULL )
    {
        bProgressive = TRUE;
    }

// -------------------------------------------------------------------- 
//      Create the dataset.                                             
// -------------------------------------------------------------------- 
    FILE	*fpImage;

    fpImage = VSIFOpen( pszFilename, "wb" );
    if( fpImage == NULL )
    {
        CPLError( CE_Failure, CPLE_OpenFailed, 
                  "Unable to create jpeg file %s.\n", 
                  pszFilename );
        return NULL;
    }

// -------------------------------------------------------------------- 
//      Initialize JPG access to the file.                              
// -------------------------------------------------------------------- 
    struct jpeg_compress_struct sCInfo;
    struct jpeg_error_mgr sJErr;
    
    sCInfo.err = jpeg_std_error( &sJErr );
    jpeg_create_compress( &sCInfo );
    
    jpeg_stdio_dest( &sCInfo, fpImage );
    
    sCInfo.image_width = nXSize;
    sCInfo.image_height = nYSize;
    sCInfo.input_components = nBands;

    if( nBands == 1 )
    {
        sCInfo.in_color_space = JCS_GRAYSCALE;
    }
    else
    {
        sCInfo.in_color_space = JCS_RGB;
    }

    jpeg_set_defaults( &sCInfo );
    
    jpeg_set_quality( &sCInfo, nQuality, TRUE );

    if( bProgressive )
        jpeg_simple_progression( &sCInfo );

    jpeg_start_compress( &sCInfo, TRUE );

// -------------------------------------------------------------------- 
//      Loop over image, copying image data.                            
// -------------------------------------------------------------------- 
    GByte 	*pabyScanline;
    CPLErr      eErr;

    pabyScanline = (GByte *) CPLMalloc( nBands * nXSize );

    for( int iLine = 0; iLine < nYSize; iLine++ )
    {
        JSAMPLE      *ppSamples;

        for( int iBand = 0; iBand < nBands; iBand++ )
        {
            GDALRasterBand * poBand = poSrcDS->GetRasterBand( iBand+1 );
            eErr = poBand->RasterIO( GF_Read, 0, iLine, nXSize, 1, 
                                     pabyScanline + iBand, nXSize, 1, GDT_Byte,
                                     nBands, nBands * nXSize );
        }

        ppSamples = pabyScanline;
        jpeg_write_scanlines( &sCInfo, &ppSamples, 1 );
    }

    CPLFree( pabyScanline );

    jpeg_finish_compress( &sCInfo );
    jpeg_destroy_compress( &sCInfo );

    VSIFClose( fpImage );

    return (GDALDataset *) GDALOpen( pszFilename, GA_ReadOnly );
}

动态创建

在动态创建的例子中,没有源数据。但是提供了文件的大小、波段数和像素的数据类型。 对于其他的一些信息(例如地理参照系等),将在以后通过特定的函数设置。

接下来的自立简单实现了PCI.aux标记的原始矢量创建。它采用的自己的方法创建了一个 空白,并且在最后调用GDALOpen。这避免在Open函数中出现两套不同的启动过程。

GDALDataset *PAuxDataset::Create( const char * pszFilename,
                                  int nXSize, int nYSize, int nBands,
                                  GDALDataType eType,
                                  char ** // papszParmList  )

{
    char	*pszAuxFilename;

// -------------------------------------------------------------------- 
//      Verify input options.                                           
// -------------------------------------------------------------------- 
    if( eType != GDT_Byte && eType != GDT_Float32 && eType != GDT_UInt16
        && eType != GDT_Int16 )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
              "Attempt to create PCI .Aux labelled dataset with an illegal\n"
              "data type (%s).\n",
              GDALGetDataTypeName(eType) );

        return NULL;
    }

// -------------------------------------------------------------------- 
//      Try to create the file.                                         
// -------------------------------------------------------------------- 
    FILE	*fp;

    fp = VSIFOpen( pszFilename, "w" );

    if( fp == NULL )
    {
        CPLError( CE_Failure, CPLE_OpenFailed,
                  "Attempt to create file `%s' failed.\n",
                  pszFilename );
        return NULL;
    }

// -------------------------------------------------------------------- 
//      Just write out a couple of bytes to establish the binary        
//      file, and then close it.                                        
// -------------------------------------------------------------------- 
    VSIFWrite( (void *) "\0\0", 2, 1, fp );
    VSIFClose( fp );

// -------------------------------------------------------------------- 
//      Create the aux filename.                                        
// -------------------------------------------------------------------- 
    pszAuxFilename = (char *) CPLMalloc(strlen(pszFilename)+5);
    strcpy( pszAuxFilename, pszFilename );;

    for( int i = strlen(pszAuxFilename)-1; i > 0; i-- )
    {
        if( pszAuxFilename[i] == '.' )
        {
            pszAuxFilename[i] = '\0';
            break;
        }
    }

    strcat( pszAuxFilename, ".aux" );

// -------------------------------------------------------------------- 
//      Open the file.                                                  
// -------------------------------------------------------------------- 
    fp = VSIFOpen( pszAuxFilename, "wt" );
    if( fp == NULL )
    {
        CPLError( CE_Failure, CPLE_OpenFailed,
                  "Attempt to create file `%s' failed.\n",
                  pszAuxFilename );
        return NULL;
    }
    
// -------------------------------------------------------------------- 
//      We need to write out the original filename but without any      
//      path components in the AuxilaryTarget line.  Do so now.         
// -------------------------------------------------------------------- 
    int		iStart;

    iStart = strlen(pszFilename)-1;
    while( iStart > 0 && pszFilename[iStart-1] != '/'
           && pszFilename[iStart-1] != '\\' )
        iStart--;

    VSIFPrintf( fp, "AuxilaryTarget: %s\n", pszFilename + iStart );

// -------------------------------------------------------------------- 
//      Write out the raw definition for the dataset as a whole.        
// -------------------------------------------------------------------- 
    VSIFPrintf( fp, "RawDefinition: %d %d %d\n",
                nXSize, nYSize, nBands );

// -------------------------------------------------------------------- 
//      Write out a definition for each band.  We always write band     
//      sequential files for now as these are pretty efficiently        
//      handled by GDAL.                                                
// -------------------------------------------------------------------- 
    int		nImgOffset = 0;
    
    for( int iBand = 0; iBand < nBands; iBand++ )
    {
        const char * pszTypeName;
        int	     nPixelOffset;
        int	     nLineOffset;

        nPixelOffset = GDALGetDataTypeSize(eType)/8;
        nLineOffset = nXSize * nPixelOffset;

        if( eType == GDT_Float32 )
            pszTypeName = "32R";
        else if( eType == GDT_Int16 )
            pszTypeName = "16S";
        else if( eType == GDT_UInt16 )
            pszTypeName = "16U";
        else
            pszTypeName = "8U";

        VSIFPrintf( fp, "ChanDefinition-%d: %s %d %d %d %s\n",
                    iBand+1, pszTypeName,
                    nImgOffset, nPixelOffset, nLineOffset,
#ifdef CPL_LSB
                    "Swapped"
#else
                    "Unswapped"
#endif
                    );

        nImgOffset += nYSize * nLineOffset;
    }

// -------------------------------------------------------------------- 
//      Cleanup                                                         
// -------------------------------------------------------------------- 
    VSIFClose( fp );

    return (GDALDataset *) GDALOpen( pszFilename, GA_Update );
}

文件格式支持动态创建或支持更新都需要实现GDALRasterBand中的IWriteBlock()方法。 它类似于IReadBlock()。并且,因为许多理由,在GDALRasterBand的析构中实现 FlushCache()方法是比较危险的。因此,必须确保在析构方法调用之前,带的任何 写缓冲区块都被清除。

RawDataset/RawRasterBand Helper Classes

Many file formats have the actual imagery data stored in a regular, binary, scanline oriented format. Rather than re-implement the access semantics for this for each formats, there are provided RawDataset and RawRasterBand classes declared in gdal/frmts/raw that can be utilized to implement efficient and convenient access.

In these cases the format specific band class may not be required, or if required it can be derived from RawRasterBand. The dataset class should be derived from RawDataset.

The Open() method for the dataset then instantiates raster bands passing all the layout information to the constructor. For instance, the PNM driver uses the following calls to create it's raster bands.

    if( poOpenInfo->pabyHeader[1] == '5' )
    {
        poDS->SetBand( 
            1, new RawRasterBand( poDS, 1, poDS->fpImage,
                                  iIn, 1, nWidth, GDT_Byte, TRUE ));
    }
    else 
    {
        poDS->SetBand( 
            1, new RawRasterBand( poDS, 1, poDS->fpImage,
                                  iIn, 3, nWidth*3, GDT_Byte, TRUE ));
        poDS->SetBand( 
            2, new RawRasterBand( poDS, 2, poDS->fpImage,
                                  iIn+1, 3, nWidth*3, GDT_Byte, TRUE ));
        poDS->SetBand( 
            3, new RawRasterBand( poDS, 3, poDS->fpImage,
                                  iIn+2, 3, nWidth*3, GDT_Byte, TRUE ));
    }

The RawRasterBand takes the following arguments.

Simple file formats utilizing the Raw services are normally placed all within one file in the gdal/frmts/raw directory. There are numerous examples there of format implementation.

元数据以及其他扩展

在GDAL数据模型中有很多其他项,在GDALDataset和GDALRasterBand中存在对应的虚函数。 它们包括:


Generated at Thu Aug 14 14:14:47 2008 by  doxygen 1.5.4