栅格驱动程序实现教程

整体方法

一般来说,通过将特定于格式的驱动程序实现为 GDALDataset ,和作为 GDALRasterBand . 还有,一个 GDALDriver 实例是为该格式创建的,并在 GDALDriverManager ,以确保系统知道格式。

本教程将从实现一个简单的只读驱动程序(基于JDEM驱动程序)开始,然后继续使用RawRasterBand助手类,实现可创建和可更新的格式,以及一些深奥的问题。

强烈建议 栅格数据模型 在尝试实现GDAL驱动程序之前,请先检查并理解。

实现数据集

We will start showing minimal implementation of a read-only driver for the Japanese DEM format (jdemdataset.cpp). First we declare a format specific dataset class, JDEMDataset in this case.

class JDEMDataset : public GDALPamDataset
{
    friend class JDEMRasterBand;
    FILE        *fp;
    GByte       abyHeader[1012];

public:
                ~JDEMDataset();
    static GDALDataset *Open( GDALOpenInfo * );
    static int          Identify( GDALOpenInfo * );
    CPLErr      GetGeoTransform( double * padfTransform );
    const char *GetProjectionRef();
};

通常,我们通过重写GDALDataset基类上的各种虚拟方法来为驱动程序提供功能。但是,Open()方法是特殊的。这不是基类上的虚方法,我们需要一个独立的函数来执行这个操作,所以我们声明它是静态的。在JDEMDataset类中作为方法实现它很方便,因为我们拥有修改数据库对象内容的特权访问权。

open方法本身可能如下所示:

GDALDataset *JDEMDataset::Open( GDALOpenInfo *poOpenInfo )
{
    // Confirm that the header is compatible with a JDEM dataset.
    if( !Identify(poOpenInfo) )
        return NULL;

    // Confirm the requested access is supported.
    if( poOpenInfo->eAccess == GA_Update )
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                "The JDEM driver does not support update access to existing "
                "datasets.");
        return NULL;
    }

    // Check that the file pointer from GDALOpenInfo* is available
    if( poOpenInfo->fpL == NULL )
    {
        return NULL;
    }

    // Create a corresponding GDALDataset.
    JDEMDataset *poDS = new JDEMDataset();

    // Borrow the file pointer from GDALOpenInfo*.
    poDS->fp = poOpenInfo->fpL;
    poOpenInfo->fpL = NULL;

    // Read the header.
    VSIFReadL(poDS->abyHeader, 1, 1012, poDS->fp);
    poDS->nRasterXSize =
        JDEMGetField(reinterpret_cast<char *>(poDS->abyHeader) + 23, 3);
    poDS->nRasterYSize =
        JDEMGetField(reinterpret_cast<char *>(poDS->abyHeader) + 26, 3);
    if( poDS->nRasterXSize <= 0 || poDS->nRasterYSize <= 0 )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                "Invalid dimensions : %d x %d",
                poDS->nRasterXSize, poDS->nRasterYSize);
        delete poDS;
        return NULL;
    }

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

    // Initialize any PAM information.
    poDS->SetDescription(poOpenInfo->pszFilename);
    poDS->TryLoadXML();

    // Initialize default overviews.
    poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
    return poDS;
}

任何数据库打开函数的第一步都是验证所传递的文件实际上是此驱动程序的类型。重要的是要意识到每个驱动程序的Open函数依次调用,直到一个成功为止。如果传递的文件不是其格式,则驱动程序必须悄悄返回NULL。只有当文件的格式确实受支持,但由于某种原因不受支持或已损坏时,它们才会产生错误。要打开的文件的信息包含在GDALOpenInfo对象中。GDALOpenInfo包含以下公共数据成员:

char        *pszFilename;
char**      papszOpenOptions;
GDALAccess  eAccess;  // GA_ReadOnly or GA_Update
int         nOpenFlags;
int         bStatOK;
int         bIsDirectory;
VSILFILE   *fpL;
int         nHeaderBytes;
GByte       *pabyHeader;

驱动程序可以检查这些文件以确定是否支持该文件。如果 pszFilename 引用文件系统中的对象 bStatOK 标志将设置为TRUE。同样,如果文件被成功打开,则会读入第一个千字节左右的内容,并放入pabyHeader,并在 nHeaderBytes .

在这个典型的测试示例中,将验证文件是否已成功打开,是否至少有足够的头信息来执行测试,以及头的各个部分是否与此格式的预期一致。在这种情况下,JDEM格式没有幻数,因此我们检查各种日期字段以确保它们具有合理的世纪值。如果测试失败,我们会悄悄地返回NULL,表示该文件不是我们支持的格式。

标识实际上被委托给Identify()静态函数:

/************************************************************************/
/*                              Identify()                              */
/************************************************************************/
int JDEMDataset::Identify( GDALOpenInfo * poOpenInfo )
{
    // Confirm that the header has what appears to be dates in the
    // expected locations.  Sadly this is a relatively weak test.
    if( poOpenInfo->nHeaderBytes < 50 )
        return FALSE;

    // Check if century values seem reasonable.
    const char *psHeader = reinterpret_cast<char *>(poOpenInfo->pabyHeader);
    if( (!EQUALN(psHeader + 11, "19", 2) &&
        !EQUALN(psHeader + 11, "20", 2)) ||
        (!EQUALN(psHeader + 15, "19", 2) &&
        !EQUALN(psHeader + 15, "20", 2)) ||
        (!EQUALN(psHeader + 19, "19", 2) &&
        !EQUALN(psHeader + 19, "20", 2)) )
    {
        return FALSE;
    }
    return TRUE;
}

重要的是要使“这是我的格式”测试尽可能严格。在这种特殊的情况下,测试是弱的,一个在几个位置碰巧有19s或20s的文件可能会被错误地识别为JDEM格式,导致它不能被正确地处理。一旦我们确信文件是我们的格式,我们就可以做任何其他必要的测试来验证文件是否可用,特别是我们可以提供所需的访问级别。由于JDEM驱动程序不提供更新支持,在这种情况下会出错。

if( poOpenInfo->eAccess == GA_Update )
{
    CPLError(CE_Failure, CPLE_NotSupported,
             "The JDEM driver does not support update access to existing "
             "datasets.");
    return NULL;
}

接下来我们需要创建一个数据库类的实例,在这个实例中我们将设置各种感兴趣的信息。

// Check that the file pointer from GDALOpenInfo* is available.
if( poOpenInfo->fpL == NULL )
{
    return NULL;
}
JDEMDataset *poDS = new JDEMDataset();

// Borrow the file pointer from GDALOpenInfo*.
poDS->fp = poOpenInfo->fpL;
poOpenInfo->fpL = NULL;

此时,我们“借用”了GDALOpenInfo持有的文件句柄 . 此文件指针使用VSI 访问磁盘上文件的GDAL API。这个虚拟化的POSIX风格API允许一些特殊的功能,比如支持大文件、内存文件和压缩文件。

接下来,从标题中提取X和Y大小。这个 nRasterXSizenRasterYSize 是从GDALDataset基类继承的数据字段,必须由Open()方法设置。

VSIFReadL(poDS->abyHeader, 1, 1012, poDS->fp);
poDS->nRasterXSize =
    JDEMGetField(reinterpret_cast<char *>(poDS->abyHeader) + 23, 3);
poDS->nRasterYSize =
    JDEMGetField(reinterpret_cast<char *>(poDS->abyHeader) + 26, 3);
if  (poDS->nRasterXSize <= 0 || poDS->nRasterYSize <= 0 )
{
    CPLError(CE_Failure, CPLE_AppDefined,
            "Invalid dimensions : %d x %d",
            poDS->nRasterXSize, poDS->nRasterYSize);
    delete poDS;
    return NULL;
}

必须使用SetBand()方法创建并附加与此数据集相关的所有标注栏。我们稍后将研究JDEMRasterBand()类。

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

最后,我们为dataset对象指定一个名称,并调用GDALPamDataset TryLoadXML()方法,该方法可以从.aux.xml文件(如果可用)初始化辅助信息。有关这些服务的更多详细信息,请查看GDALPamDataset和相关类。

    // Initialize any PAM information.
    poDS->SetDescription( poOpenInfo->pszFilename );
    poDS->TryLoadXML();
    return poDS;
}

实现RasterBand

与来自GDALDataset的自定义JDEMDataset类类似,我们还需要声明并实现一个从 GDALRasterBand 用于访问JDEM文件的频带。对于JDEMRasterBand,声明如下:

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

构造函数可以有任何签名,并且只能从Open()方法调用。其他虚拟方法,如 GDALRasterBand::IReadBlock() 必须与gdal_priv.h中的方法签名完全匹配。

构造函数实现如下所示:

JDEMRasterBand::JDEMRasterBand( JDEMDataset *poDSIn, int nBandIn )
{
    poDS = poDSIn;
    nBand = nBandIn;
    eDataType = GDT_Float32;
    nBlockXSize = poDS->GetRasterXSize();
    nBlockYSize = 1;
}

以下数据成员继承自GDALRasterBand,通常应在band构造函数中设置。

poDS: Pointer to the parent GDALDataset.
nBand: The band number within the dataset.
eDataType: The data type of pixels in this band.
nBlockXSize: The width of one block in this band.
nBlockYSize: The height of one block in this band.

所有可能的GDALDataType值都用gdal.h声明,包括GDT_Byte、GDT_UInt16、GDT_Int16和GDT_Float32。块大小用于建立一个自然的或有效的块大小来访问数据。对于平铺数据集,这将是平铺的大小,而对于大多数其他数据集,这将是一个扫描线,如本例所示。

接下来我们将看到实际读取图像数据的代码的实现,IReadBlock()。

CPLErr JDEMRasterBand::IReadBlock( int nBlockXOff, int nBlockYOff,
                                void * pImage )
{
    JDEMDataset *poGDS = static_cast<JDEMDataset *>(poDS);
    int nRecordSize = nBlockXSize * 5 + 9 + 2;
    VSIFSeekL(poGDS->fp, 1011 + nRecordSize*nBlockYOff, SEEK_SET);
    char *pszRecord = static_cast<char *>(CPLMalloc(nRecordSize));
    VSIFReadL(pszRecord, 1, nRecordSize, poGDS->fp);
    if( !EQUALN(reinterpret_cast<char *>(poGDS->abyHeader), pszRecord, 6) )
    {
        CPLFree(pszRecord);
        CPLError(CE_Failure, CPLE_AppDefined,
                "JDEM Scanline corrupt.  Perhaps file was not transferred "
                "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 "
                "currently support partial datasets.");
        return CE_Failure;
    }
    for( int i = 0; i < nBlockXSize; i++ )
        ((float *) pImage)[i] = JDEMGetField(pszRecord + 9 + 5 * i, 5) * 0.1;
    return CE_None;
}

需要注意的关键事项有:

  • 通常将gdalStartBand::poDS成员强制转换为所属数据集的派生类型。如果RasterBand类将需要对所属数据集对象的特权访问,请确保将其声明为friend(为简洁起见,上面省略了)。

  • 如果发生错误,请使用CPLError()报告,并返回CE_Failure。否则不返回CE_None。

  • pImage缓冲区应该填充一个数据块。块是在栅格带的nBlockXSize和nBlockYSize中声明的大小。pImage中的数据类型应与栅格标注栏对象中eDataType中声明的类型匹配。

  • nBlockXOff和nBlockYOff是块偏移量,因此对于128x128平铺数据集,值1和1表示应该加载从(128128)到(255255)的块。

司机

虽然JDEMDataset和JDEMRasterBand现在已经可以用来读取图像数据,但是GDAL系统如何知道新的驱动程序还不清楚。这是通过 GDALDriverManager . 为了注册我们的格式,我们实现了一个注册函数。声明位于gcore/gdal_frmts.h:void CPL_DLL GDALRegister_JDEM(void);

驱动程序文件中的定义是:

void GDALRegister_JDEM()
{
    if( !GDAL_CHECK_VERSION("JDEM") )
        return;

    if( GDALGetDriverByName("JDEM") != NULL )
        return;

    GDALDriver *poDriver = new GDALDriver();
    poDriver->SetDescription("JDEM");
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
                            "Japanese DEM (.mem)");
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
                            "frmt_various.html#JDEM");
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "mem");
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    poDriver->pfnOpen = JDEMDataset::Open;
    poDriver->pfnIdentify = JDEMDataset::Identify;
    GetGDALDriverManager()->RegisterDriver(poDriver);
}

注意GDALu CHECKu VERSION宏的用法。这是GDAL树中不依赖于外部库的驱动程序的可选宏,但是如果您将驱动程序编译为插件(也就是说,树外驱动程序),这将非常有用。由于GDAL C++ + ABI可以并且将在GDAL版本之间进行更改(例如从GDAL 1到x 1)。因此,可能需要重新编译驱动程序,以与您希望使其工作的GDAL版本的头文件重新编译。GDALu CHECKu VERSION宏将检查编译驱动程序的GDAL版本和运行驱动程序的版本是否兼容。

第一次调用时,registration函数将创建GDALDriver对象的实例,并将其注册到GDALDriverManager。在向GDALDriverManager注册之前,可以在驱动程序中设置以下字段。

  • 描述是格式的简称。这是此格式的唯一名称,通常用于在脚本和命令行程序中标识驱动程序。通常长度为3-5个字符,与格式类的前缀匹配。(强制性)

  • GDAL_DCAP_RASTER:设置为YES表示此驱动程序处理栅格数据。(强制性)

  • GDAL_DMD_LONGNAME:文件格式的较长描述性名称,但仍不超过50-60个字符。(强制性)

  • GDAL_DMD_help topic:要为此驱动程序显示的帮助主题的名称(如果有)。在这种情况下,JDEM格式包含在gdal/html中保存的各种格式的web页面中。(可选)

  • GDAL_DMD_扩展名:用于此类型文件的扩展名。如果有多个选择主扩展名,或者根本没有。(可选)

  • GDAL_DMD_mime type:此文件格式的标准mime类型,如“image/png”。(可选)

  • GDAL_DMD_CREATIONOPTIONLIST:在描述创建选项的机制方面有不断发展的工作。有关此示例,请参见geotiff驱动程序。(可选)

  • GDAL_DMD_CREATIONDATATYPES:创建新数据集时此创建支持的空间分隔数据类型的列表。如果存在Create()方法,将支持这些方法。如果CreateCopy()方法存在,这将是一个可以无损导出的类型列表,但它可能包含比最终写入的类型更弱的数据类型。例如,具有CreateCopy()方法且始终写入Float32的格式也可能列出Byte、Int16和UInt16,因为它们可以无损地转换为Float32。一个示例值可能是“Byte Int16 UInt16”。(必需-如果支持创建)

  • GDAL_DCAP_VIRTUALIO:设置为“是”表示此驱动程序可以处理使用VSI*L GDAL API打开的文件。否则不应定义此元数据项。(可选)

  • pfnOpen:调用此函数尝试打开此格式的文件。(可选)

  • pfnIdentify:调用此函数尝试标识此格式的文件。如果驱动程序将文件识别为其格式,则应返回1;如果驱动程序将文件识别为非其格式,则应返回0;如果驱动程序仅通过检查头字节无法得出确定的结论,则应返回-1。(可选)

  • pfnCreate:用于调用以创建此格式的新可更新数据集的函数。(可选)

  • pfnCreateCopy:调用此函数创建从另一个源复制但不需要可更新的此格式的新数据集。(可选)

  • pfnDelete:用于调用以删除此格式的数据集的函数。(可选)

  • pfnUnloadDriver:仅当驱动程序被破坏时调用的函数。可用于清除驱动程序级别的数据。很少使用。(可选)

将驱动程序添加到GDAL树

注意,GDALRegister_JDEM()方法必须由更高级别的程序调用,才能访问JDEM驱动程序。编写新驱动程序时的常规做法是:

  • 在gdal/frmts下添加一个驱动程序目录,目录名与短名称相同。

  • 在该目录中添加一个GNUmakefile和makefile.vc,该目录以其他类似目录(即jdem目录)中的目录为模型。

  • 使用数据集添加模块,并实现rasterband。通常这称为<short_name>dataset.cpp,所有特定于GDAL的代码都在一个文件中,尽管这不是必需的。

  • 将注册入口点声明(即GDALRegister_JDEM())添加到gdal/gcore/gdal_frmts.h。

  • 向frmts/gdalallregister.cpp添加对注册函数的调用,该函数受适当的ifdef保护。

  • 将格式短名称添加到GDALmake.opt.in(和GDALmake.opt)中的GDAL_FORMATS宏。

  • 将特定于格式的项添加到frmts/makefile.vc中的EXTRAFLAGS宏。

一旦全部完成,就应该可以重建GDAL,并在所有实用程序中使用新格式。这个 gdalinfo 实用程序可用于测试打开和报告格式是否有效,以及 gdal_translate 实用程序可用于测试图像读取。

添加地理参照

现在我们将把这个例子向前推进一步,添加地理参考支持。我们将以下两个虚拟方法重写添加到JDEMDataset,以确保与GDALDataset基类上方法的签名完全匹配。

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

实施 GDALDataset::GetGeoTransform() 只需将常用的geotransform矩阵复制到提供的缓冲区中。请注意 GDALDataset::GetGeoTransform() 可能被称为很多,所以在其中进行大量计算通常是不明智的。在许多情况下,Open()将收集geotransform,而此方法将只复制它。还要注意,geotransform返回基于左上角像素左上角的定位点,而不是某些包中使用的像素中心方法。

CPLErr JDEMDataset::GetGeoTransform( double * padfTransform )
{
    const char *psHeader = reinterpret_cast<char *>(abyHeader);
    const double dfLLLat = JDEMGetAngle(psHeader + 29);
    const double dfLLLong = JDEMGetAngle(psHeader + 36);
    const double dfURLat = JDEMGetAngle(psHeader + 43);
    const double dfURLong = JDEMGetAngle(psHeader + 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;
}

这个 GDALDataset::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驱动程序特性的解释。可以根据需要查看jdemdataset.cpp的完整源代码。

概览

GDAL允许文件格式通过 GDALRasterBand::GetOverview() 以及相关方法。然而,实现这一点非常复杂,目前已经超出了本文的范围。可以查看GeoTIFF驱动程序(gdal/frmts/gtiff/GeoTIFF.cpp)和相关源,以获取实现概览报告和创建支持的文件格式示例。

格式还可以通过重写 GDALRasterBand::HasArbitraryOverviews() 方法,返回TRUE。在这种情况下,栅格带对象将覆盖 GDALRasterBand::RasterIO() 方法本身,通过重采样实现对图像的有效访问。这也涉及到,正确实现RasterIO()方法有很多要求。这方面的一个例子可以在OGDI和ECW格式中找到。

但是,到目前为止,实现overview的最常见方法是在GDAL中对存储在TIFF文件中的外部overview使用默认支持,该文件与数据集同名,但附加了扩展名.ovr。为了能够读取和创建这种类型的概述,GDALDataset必须初始化 oOvManager 对象本身。这通常是在Open()方法(在PAM之后 GDALDataset::TryLoadXML()

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

这将启用默认实现来读取和创建格式的概述。建议对所有简单的基于文件系统的格式启用此功能,除非要绑定到自定义的概述机制。

文件创建

创建文件有两种方法。第一种方法称为 GDALDriver::CreateCopy() 方法,并涉及实现一个函数,该函数可以以输出格式写入文件,从源GDALDataset中提取所需的所有图像和其他信息。第二种方法,动态创建方法,涉及实现一个创建方法来创建文件的外壳,然后应用程序通过调用set方法来写入各种信息。

第一种方法的好处是,在创建输出文件时,所有信息都是可用的。当使用外部库实现文件格式时,这一点尤其重要,外部库需要诸如颜色映射之类的信息,以及在创建文件时的地理参考信息。此方法的另一个优点是CreateCopy()方法可以读取某些类型的信息,如min/max、scaling、description和gcp,而这些信息没有等效的set方法。

第二种方法的好处是,应用程序可以创建一个空的新文件,并在结果可用时将其写入。不必事先提供所需数据的完整图像。

对于非常重要的格式,这两种方法都可以实现,否则请执行更简单的操作,或者提供所需的功能。

CreateCopy

直接传递GDALDriver::CreateCopy()方法调用,因此应该参考该方法以获取参数的详细信息。然而,需要记住的是:

  • 如果 bStrict flag为FALSE当驱动程序不能准确地表示源数据集、动态转换数据类型、删除地理参照等时,驱动程序应该尝试做一些合理的事情。

  • 正确执行进度报告在一定程度上涉及到。总是需要检查progress函数的返回结果是否被取消,并且应该以合理的间隔报告进度。JPEGCreateCopy()方法演示了对progress函数的良好处理。

  • 特殊创建选项应记录在联机帮助中。如果选项采用“NAME=VALUE”格式,则可以使用 CPLFetchNameValue() 如JPEGCreateCopy()的质量和渐进标志处理中所示。

  • 返回的GDALDataset句柄可以处于只读或更新模式。如果可行,在更新模式下返回,否则在只读模式下就可以了。

这里是JPEG的CreateCopy函数(在GDALDriver对象中分配给pfnCreateCopy)的完整实现。静态GDALDataset*

JPEGCreateCopy( const char * pszFilename, GDALDataset *poSrcDS,
                int bStrict, char ** papszOptions,
                GDALProgressFunc pfnProgress, void * pProgressData )
{
    const int nBands = poSrcDS->GetRasterCount();
    const int nXSize = poSrcDS->GetRasterXSize();
    const int nYSize = poSrcDS->GetRasterYSize();
    // 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.", 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.",
                GDALGetDataTypeName(
                    poSrcDS->GetRasterBand(1)->GetRasterDataType()));
        return NULL;
    }

    // What options has the user selected?
    int nQuality = 75;
    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;
        }
    }

    bool bProgressive = false;
    if( CSLFetchNameValue(papszOptions, "PROGRESSIVE") != NULL )
    {
        bProgressive = true;
    }

    // Create the dataset.
    VSILFILE *fpImage = VSIFOpenL(pszFilename, "wb");
    if( fpImage == NULL )
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                "Unable to create jpeg file %s.",
                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 = static_cast<GByte *>(CPLMalloc(nBands * nXSize));
    for( int iLine = 0; iLine < nYSize; iLine++ )
    {
        for( int iBand = 0; iBand < nBands; iBand++ )
        {
            GDALRasterBand * poBand = poSrcDS->GetRasterBand(iBand + 1);
            const CPLErr eErr =
                poBand->RasterIO(GF_Read, 0, iLine, nXSize, 1,
                                pabyScanline + iBand, nXSize, 1, GDT_Byte,
                                nBands, nBands * nXSize);
            // TODO: Handle error.
        }
        JSAMPLE *ppSamples = pabyScanline;
        jpeg_write_scanlines(&sCInfo, &ppSamples, 1);
    }
    CPLFree(pabyScanline);
    jpeg_finish_compress(&sCInfo);
    jpeg_destroy_compress(&sCInfo);
    VSIFCloseL(fpImage);
    return static_cast<GDALDataset *>(GDALOpen(pszFilename, GA_ReadOnly));
}

动态创作

在动态创建的情况下,没有源数据集。相反,将提供所需文件的大小、频带数和像素数据类型,但稍后将通过对生成的GDALDataset的其他方法调用提供其他信息(例如地理参考和图像数据)。

下面的示例实现了标记为raw raster creation的PCI.aux。它遵循一种常见的方法:使用非GDAL调用创建一个空白但有效的文件,然后在最后调用GDALOpen(,GA_Update)返回一个可写的文件句柄。这样可以避免在Open()函数中重复各种设置操作。

GDALDataset *PAuxDataset::Create( const char * pszFilename,
                                int nXSize, int nYSize, int nBands,
                                GDALDataType eType,
                                char ** /* papszParamList */ )
{
    // 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 labeled dataset with an illegal "
            "data type (%s).",
            GDALGetDataTypeName(eType));
        return NULL;
    }

    // Try to create the file.
    FILE *fp = VSIFOpen(pszFilename, "w");
    if( fp == NULL )
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                "Attempt to create file `%s' failed.",
                pszFilename);
        return NULL;
    }

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

    // Create the aux filename.
    char *pszAuxFilename = static_cast<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.",
                pszAuxFilename);
        return NULL;
    }

    // We need to write out the original filename but without any
    // path components in the AuxiliaryTarget line.  Do so now.
    int 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 int nPixelOffset = GDALGetDataTypeSize(eType)/8;
        const int nLineOffset = nXSize * nPixelOffset;
        const char *pszTypeName = NULL;
        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 static_cast<GDALDataset *>(GDALOpen(pszFilename, GA_Update));
}

支持动态创建,甚至只是就地更新访问的文件格式还需要在栅格带类上实现IWriteBlock()方法。它的语义类似于IReadBlock()。此外,出于各种深奥的原因,在栅格带析构函数中实现FlushCache()方法是非常重要的。这是为了确保在调用析构函数之前清除带区的任何写缓存块。

RawDataset/RawRasterBand帮助程序类

许多文件格式将实际图像数据存储为常规的、二进制的、面向扫描线的格式。不是为每个格式重新实现访问语义,而是提供 RawDatasetRawRasterBand 在gcore/中声明的类,可用于实现高效和方便的访问。

在这些情况下,可能不需要特定于格式的band类,或者如果需要,可以从RawRasterBand派生该类。数据集类应该从RawDataset派生。

然后,数据集的Open()方法实例化栅格带,将所有布局信息传递给构造函数。例如,PNM驱动程序使用以下调用创建其栅格带。

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));
}

RawRasterBand接受以下参数。

  • poDS:这个波段将是GDALDataset数据集的子类。此数据集必须是从RawRasterDataset派生的类。

  • nBand:数据集上的波段,1为基础。

  • fpRaw:包含栅格数据的文件*句柄。

  • nImgOffset:第一条扫描线到栅格数据第一个像素的字节偏移量。

  • nPixelOffset:扫描线中从一个像素开始到下一个像素开始的字节偏移量。

  • nLineOffset:从一个扫描行开始到下一个扫描行开始的字节偏移量。

  • eDataType:磁盘上数据类型的GDALDataType代码。

  • bNativeOrder:如果数据与运行GDAL的机器不在同一个endianness中,则为FALSE。数据将自动进行字节交换。

使用原始服务的简单文件格式通常都放在gdal/frmts/Raw目录中的一个文件中。这里有很多格式实现的例子。

元数据和其他外来扩展

GDAL数据模型中还有许多其他项,其中的虚拟方法存在于GDALDataset和GDALRasterBand中。它们包括:

  • 元数据:关于数据集或标注栏的名称/值文本值。GDALMajorObject(GdalMasterBand和GDALDataset的基类)内置了对保存元数据的支持,因此对于读取访问,只需要在Open()期间通过调用SetMetadataItem()来设置它。SAR-CEOS(frmts/ceos2/SAR-ceosdaset.cpp)和GeoTIFF驱动程序是实现可读元数据的驱动程序示例。

  • 颜色表:GDT_字节栅格带可以具有与其关联的颜色表。frmts/png/pngdataset.cpp驱动程序包含一个支持颜色表的格式示例。

  • 颜色解释:PNG驱动程序包含一个驱动程序示例,该驱动程序返回一个指示,指示一个波段是否应被视为红色、绿色、蓝色、Alpha或灰度波段。

  • GCPs:gdaldataset可以有一组与之相关联的地面控制点(与GetGeotransform()返回的显式仿射变换相反),它们将栅格与地理参考坐标相关联。MFF2(gdal/frmts/raw/hkvdataset.cpp)格式是支持gcp的格式的一个简单示例。

  • NoDataValue:具有已知“nodata”值的频带可以实现GetNoDataValue()方法。请参见PAux(frmts/raw/pauxdataset.cpp)以获取此示例。

  • 类别名称:具有每个类名称的分类图像可以使用GetCategoryNames()方法返回它们,尽管当前没有实现此功能的格式。