GDAL驱动实现向导
翻译:柴树杉(chaishushan@gmail.com)
原文:http://www.gdal.org/gdal_drivertut.html
通常,可以重新继承GDALDataset和GDALRasterBand来实现对特定格式数据的支持。 同样,还需要为这种格式创建一个GDALDriver的实例,让后通过GDALDriverManager将 驱动注册给GDAL系统。
该教程将为JDEM格式数据实现一个简单的只读驱动开始,进而使用RawRasterBand帮助类, 实现一个可创建和更改的格式以及比较高级的一些问题。
强烈建议在实现GDAL驱动之前前面所描述的GDAL数据模型的内容。
目录
- 子类化Dataset
- 子类化RasterBand
- 驱动
- 将驱动添加到GDAL中
- 添加参照系
- Overview
- 创建文件
- RawDataset/RawRasterBand Helper Classes
- 元数据以及其他扩展
子类化Dataset
将将演示一个小日本DEM驱动的最基本的实现。首先,从GDALDataset继承一个 子类JDEMDataset,JDEMDataset对应小日本格式DEM数据。
1 class JDEMDataset : public GDALDataset 2 { 3 FILE *fp; 4 GByte abyHeader[1012]; 5 6 public: 7 ~JDEMDataset(); 8 9 static GDALDataset *Open( GDALOpenInfo * ); 10 };
我们可以通过重载基类GDALDataset中的一些虚函数来为驱动重新实现某些特殊的 功能。然而,Open()相对比较特殊,它不是基类 GDALDataset的虚函数。我们 需要一个独立的函数来实现这个功能,因此我们把他声明为static的。将Open()声明 为 JDEMDataset的static函数比较方便,因为这样可以用JDEMDataset的私有方法去 修改内容。
Open()函数具体实现如下:
1 GDALDataset *JDEMDataset::Open( GDALOpenInfo * poOpenInfo ) 2 3 { 4 // -------------------------------------------------------------------- 5 // Before trying JDEMOpen() we first verify that there is at 6 // least one "\n#keyword" type signature in the first chunk of 7 // the file. 8 // -------------------------------------------------------------------- 9 if( poOpenInfo->fp == NULL || poOpenInfo->nHeaderBytes < 50 ) 10 return NULL; 11 12 // check if century values seem reasonable 13 if( (!EQUALN((char *)poOpenInfo->pabyHeader+11,"19",2) 14 && !EQUALN((char *)poOpenInfo->pabyHeader+11,"20",2)) 15 || (!EQUALN((char *)poOpenInfo->pabyHeader+15,"19",2) 16 && !EQUALN((char *)poOpenInfo->pabyHeader+15,"20",2)) 17 || (!EQUALN((char *)poOpenInfo->pabyHeader+19,"19",2) 18 && !EQUALN((char *)poOpenInfo->pabyHeader+19,"20",2)) ) 19 { 20 return NULL; 21 } 22 23 // -------------------------------------------------------------------- 24 // Create a corresponding GDALDataset. 25 // -------------------------------------------------------------------- 26 JDEMDataset *poDS; 27 28 poDS = new JDEMDataset(); 29 30 poDS->fp = poOpenInfo->fp; 31 poOpenInfo->fp = NULL; 32 33 // -------------------------------------------------------------------- 34 // Read the header. 35 // -------------------------------------------------------------------- 36 VSIFSeek( poDS->fp, 0, SEEK_SET ); 37 VSIFRead( poDS->abyHeader, 1, 1012, poDS->fp ); 38 39 poDS->nRasterXSize = JDEMGetField( (char *) poDS->abyHeader + 23, 3 ); 40 poDS->nRasterYSize = JDEMGetField( (char *) poDS->abyHeader + 26, 3 ); 41 42 // -------------------------------------------------------------------- 43 // Create band information objects. 44 // -------------------------------------------------------------------- 45 poDS->nBands = 1; 46 poDS->SetBand( 1, new JDEMRasterBand( poDS, 1 )); 47 48 return( poDS ); 49 }
任何数据集打开文件之前,都要判断驱动是否支持该类型。我们应该知道, 在打开文件的时候每个驱动的open函数将被依次调用,直到有一个成功为止。 如果传递的文件不是驱动所支持的类型,则它们必须返回NULL。如果格式是它们所 支持的类型,但是数据已经被破坏,那么它们应该产生一个错误。
文件的信息在文件打开之后被传递给GDALOpenInfo对象。GDALOpenInfo对象的公有成员如下:
1 char *pszFilename; 2 3 GDALAccess eAccess; // GA_ReadOnly or GA_Update 4 5 GBool bStatOK; 6 VSIStatBuf sStat; 7 8 FILE *fp; 9 10 int nHeaderBytes; 11 GByte *pabyHeader;
驱动可以检查这些值来判断是否支持这个格式的文件。如果pszFilename指向 一个文件系统对象,那么bStatOK将被设置,并且sStat结构将包含关于对象的 通用信息。如果对象是一个规则的可读文件,那么fp指针将非空,并且可以使 用fp来读取文件(请使用cpl_vsi.h中标准输入输出文件)。最后,如果文件可以 被打开,那么开头的近1K字节的数据将被读到pabyHeader中,nHeaderBytes保存 实际读取的字节数。
在这个例子中,假设文件已经被打开并且可以测试文件头部的一些信息。在这里, JDEM并没有魔术数字,因此我们只是检测不同的数据域。如果文件不是当前驱动所 支持的格式,那么将返回NULL。
1 if( poOpenInfo->fp == NULL || poOpenInfo->nHeaderBytes < 50 ) 2 return NULL; 3 4 // check if century values seem reasonable 5 if( (!EQUALN((char *)poOpenInfo->pabyHeader+11,"19",2) 6 && !EQUALN((char *)poOpenInfo->pabyHeader+11,"20",2)) 7 || (!EQUALN((char *)poOpenInfo->pabyHeader+15,"19",2) 8 && !EQUALN((char *)poOpenInfo->pabyHeader+15,"20",2)) 9 || (!EQUALN((char *)poOpenInfo->pabyHeader+19,"19",2) 10 && !EQUALN((char *)poOpenInfo->pabyHeader+19,"20",2)) ) 11 { 12 return NULL; 13 }
在真正的测试中,测试代码越严格越好。这里的测试相对比较弱。如果一个文件的 在相应的位置具有相同的内容,那么它们很可能被错误地当作JDEM格式处理,最终 导致不可预期的结果。
如果文件是我们支持的类型,我们需要创建一个database的实例,并在database中 记录必要的信息。
1 JDEMDataset *poDS; 2 3 poDS = new JDEMDataset(); 4 5 poDS->fp = poOpenInfo->fp; 6 poOpenInfo->fp = NULL;
通常在这个时刻我们将打开文件(这里已经打开了),并且保存文件的指针。 然而,如果只是只读的话,仅仅从GDALOpenInfo中获取文件的指针也是可以的。 在这里我们将GDALOpenInfo的文件指针设置为NULL,以防止文件可能被关闭两次 (因为JDEMDataset的析构函数中已经关闭了文件)。同样,我们假设文件的当前读写 状态是不确定的,因此我们需要用VSIFSeek()重新定位文件的当前地址。下面的两行 代码完成了重新定位和读文件头的工作。
1 VSIFSeek( poDS->fp, 0, SEEK_SET ); 2 VSIFRead( poDS->abyHeader, 1, 1012, poDS->fp );
接着,从abyHeader中获取x和y的大小。变量nRasterXSize和nRasterYSize是 从基类GDALDataset中继承的,并且必须在Open()中被设置。
1 poDS->nRasterXSize = JDEMGetField( (char *) poDS->abyHeader + 23, 3 ); 2 poDS->nRasterYSize = JDEMGetField( (char *) poDS->abyHeader + 26, 3 );
最后,通过SetBand()将所有的波段与当前的GDALDataset对象绑定。在下一节, 我们将讨论JDEMRasterBand类的具体细节。
1 poDS->SetBand( 1, new JDEMRasterBand( poDS, 1 )); 2 3 return( poDS );
子类化RasterBand
和常规的从JDEMDataset继承的子类一样,我们需要为JDEMRasterBand定义一个 接受每个波段数据的一致的入口。JDEMRasterBand类的定义如下:
1 class JDEMRasterBand : public GDALRasterBand 2 { 3 public: 4 JDEMRasterBand( JDEMDataset *, int ); 5 virtual CPLErr IReadBlock( int, int, void * ); 6 };
构造函数可以任意定义,但是只能从Open()函数中调用。其他的虚函数必须 和gdal_priv.h中定义的一致,例如IReadBlock()。构造函数实现代码如下:
1 JDEMRasterBand::JDEMRasterBand( JDEMDataset *poDS, int nBand ) 2 3 { 4 this->poDS = poDS; 5 this->nBand = nBand; 6 7 eDataType = GDT_Float32; 8 9 nBlockXSize = poDS->GetRasterXSize(); 10 nBlockYSize = 1; 11 }
下面成员变量从GDALRasterBand继承,并且通常在构造函数中设置。
- poDS: 基类GDALDataset指针。
- nBand: 对应dataset中的波段编号。
- eDataType: 像素在该波段中的数据类型。
- nBlockXSize: 该波段的宽度。
- nBlockYSize: 该波段的高度。
所有的GDALDataType类型在gdal.h文件中定义,包括GDT_Byte、GDT_UInt16、 GDT_Int16和 GDT_Float32。块的尺寸(block size)记录数据的实际或有效的大小。 对于约束数据集这将是一个约束尺寸,而对于其他大多数数据而言这将是一个扫描线。
接下来,我们将实现真正读取影象数据的代码——IReadBlock()。
1 CPLErr JDEMRasterBand::IReadBlock( int nBlockXOff, int nBlockYOff, 2 void * pImage ) 3 4 { 5 JDEMDataset *poGDS = (JDEMDataset *) poDS; 6 char *pszRecord; 7 int nRecordSize = nBlockXSize*5 + 9 + 2; 8 int i; 9 10 VSIFSeek( poGDS->fp, 1011 + nRecordSize*nBlockYOff, SEEK_SET ); 11 12 pszRecord = (char *) CPLMalloc(nRecordSize); 13 VSIFRead( pszRecord, 1, nRecordSize, poGDS->fp ); 14 15 if( !EQUALN((char *) poGDS->abyHeader,pszRecord,6) ) 16 { 17 CPLFree( pszRecord ); 18 19 CPLError( CE_Failure, CPLE_AppDefined, 20 "JDEM Scanline corrupt. Perhaps file was not transferred\n" 21 "in binary mode?" ); 22 return CE_Failure; 23 } 24 25 if( JDEMGetField( pszRecord + 6, 3 ) != nBlockYOff + 1 ) 26 { 27 CPLFree( pszRecord ); 28 29 CPLError( CE_Failure, CPLE_AppDefined, 30 "JDEM scanline out of order, JDEM driver does not\n" 31 "currently support partial datasets." ); 32 return CE_Failure; 33 } 34 35 for( i = 0; i < nBlockXSize; i++ ) 36 ((float *) pImage)[i] = JDEMGetField( pszRecord + 9 + 5 * i, 5) * 0.1; 37 38 return CE_None; 39 }
需要注意的地方:
- 把
GDALRasterBand::poDS
成员传递给子类是常用的做法。如果你的RasterBand需要 操作dataset的私有成员,确保将它声明为JDEMRasterBand类的友元。 - 如果发生错误,用CPLError()报告错误信息,并且返回Failure错误标志。 否则返回None。
- pImage缓冲必须用一个块的数据来填充。块的大小在JDEMRasterBand的 nBlockXSize/nBlockYSize中定义。pImage缓冲的数据类型为JDEMRasterBand中 的eDataType对应的类型。
- nBlockXOff/nBlockYOff是块的开始地址。因此,对于一个声明为128*128大小的块, 则开始地址为1/1的块对应的数据为(128,128)到(256,256)。
驱动
虽然JDEMDataset和JDEMRasterBand已经可以读数据,但是GDAL仍然不知道关于 JDEMDataset驱动的任何信息。这可以通过GDALDriverManager来实现。为了注册我们 自己实现的驱动,我们需要重新实现一个注册函数:
1 CPL_C_START 2 void GDALRegister_JDEM(void); 3 CPL_C_END 4 5 ... 6 7 void GDALRegister_JDEM() 8 9 { 10 GDALDriver *poDriver; 11 12 if( GDALGetDriverByName( "JDEM" ) == NULL ) 13 { 14 poDriver = new GDALDriver(); 15 16 poDriver->SetDescription( "JDEM" ); 17 poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, 18 "Japanese DEM (.mem)" ); 19 poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, 20 "frmt_various.html#JDEM" ); 21 poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "mem" ); 22 23 poDriver->pfnOpen = JDEMDataset::Open; 24 25 GetGDALDriverManager()->RegisterDriver( poDriver ); 26 } 27 }
当第一次被调用的时候,注册函数将new一个GDALDriver对象,并且通过GDALDriverManager来注册。在用GDALDriverManager注册驱动之前,需要设置以下的成员变量:
- 驱动对应格式的名称,不能和其他驱动名字冲突。通常在3-5个字符长度, 并且匹配驱动类名字的前缀。(手工设置)
- GDAL_DMD_LONGNAME: 文件格式的详细描述,长度一般在50-60个字符。(手工设置)
- GDAL_DMD_HELPTOPIC: 如果存在的话,是关于这个驱动帮助主题的名称。 在这个例子中,JDEM格式包含在frmt_various.html::JDEM位置描述。(可选)
- GDAL_DMD_EXTENSION: 该类型文件的扩展名。如果扩展名多于一个, 则最好选择最通用的那个,或者直接为空。(可选)
- GDAL_DMD_MIMETYPE: 该格式数据的标准用途类型,例如“image/png”。(可选)
- GDAL_DMD_CREATIONOPTIONLIST: 用于描述创建时的选项。可以参考geotiff驱动 的实现代码。(可选)
- GDAL_DMD_CREATIONDATATYPES: 支持创建数据集的全部类型列表。如果存 在Create()方法这些类型都将被支持。如果存在CreateCopy()方法,该类型列表将 是那些支持无损输出的类型。例如,一个格式使用了CreateCopy()方法,如果可以 写为Float32类型,那么Byte、Int16和UInt16都应该被支持(因为它们都可以用Float32表示)。 一个同样的例子是“Byte Int16 UInt16”。(如果支持创建则需要)
- pfnOpen: 打开这种格式文件的函数。(可选)
- pfnCreate: 创建这个格式的updatable模式的数据集的函数。(可选)
- pfnCreateCopy: 创建从其它数据源拷贝而来的这种格式的新数据集, 但是不需要更新的函数。(可选)
- pfnDelete: 删除这种格式数据集函数。(可选)
- pfnUnloadDriver: 这个函数只有在驱动被销毁的时候才被调用。 在驱动层被用来清除数据。很少用到。(可选)
将驱动添加到GDAL中
GDALRegister_JDEM()函数必须被更高层次的函数调用以生成关于JDEM的驱动。通常实现一个驱动的时候需要做以下事情:
- 在gdal/frmts下创建一个驱动目录,目录的名字和驱动的短名字相同。
- 在驱动的目录中添加GNUmakefile和makefile.vc两个文件,具体的格式可以 参考其他驱动目录。(例如jdem目录)
- 为dataset和rasterband添加实现模块。通常情况下是调用一个 <short_name>dataset.cpp文件(这里为jdemdataset.cpp)。该文件一般放置 关于GDAL的特殊代码,当然这不是必须的。
- 在gdal/gcore/gdal_frmts.h文件中添加注册入口点声明(这里为GDALRegister_JDEM())。
- 在frmts/gdalallregister.c文件中添加一个注册函数的调用, 最好是在ifdef之间(可以参考已有的代码)。
- 在GDALmake.opt.in(和GDALmake.opt)文件中的GDAL_FORMATS宏中添加格式短名称。
- 在frmts/makefile.vc的EXTRAFLGS宏中添加格式特别项。
一旦所有的这些操作都完成,我们将重新构件GDAL,并且所有的应用程序都将识别 新的格式。gdalinfo程序可以用来测打开数据和显示信息。gdal_translate可以用 来测试影象的读操作。
添加参照系
现在我们继续晚上这个驱动,并添加参照系的支持。我们将在JDEMDataset中重新实现 两个虚函数,注意要和GDALRasterDataset中函数一致。
1 CPLErr GetGeoTransform( double * padfTransform ); 2 const char *GetProjectionRef();
重新实现的GetGeoTransform()函数只是复制地理转换矩阵到缓冲。GetGeoTransform() 函数可能经常使用,因此一般最好保持该函数短小。在许多时候,在Open()函数 将收集地理转换,并且用这个函数复制。需要注意的是,转换矩阵对应像素左上角 为原点,而不是中心。
1 CPLErr JDEMDataset::GetGeoTransform( double * padfTransform ) 2 { 3 double dfLLLat, dfLLLong, dfURLat, dfURLong; 4 5 dfLLLat = JDEMGetAngle( (char *) abyHeader + 29 ); 6 dfLLLong = JDEMGetAngle( (char *) abyHeader + 36 ); 7 dfURLat = JDEMGetAngle( (char *) abyHeader + 43 ); 8 dfURLong = JDEMGetAngle( (char *) abyHeader + 50 ); 9 10 padfTransform[0] = dfLLLong; 11 padfTransform[3] = dfURLat; 12 padfTransform[1] = (dfURLong - dfLLLong) / GetRasterXSize(); 13 padfTransform[2] = 0.0; 14 15 padfTransform[4] = 0.0; 16 padfTransform[5] = -1 * (dfURLat - dfLLLat) / GetRasterYSize(); 17 18 return CE_None; 19 }
GetProjectionRef() 方法将返回一个字符串,其中包括OGC WKT格式的坐标系统 的定义。在这个例子中,坐标系统适合于这种格式的所有数据。但是在比较复杂的 数据格式中,我们可以需要使用 OGRSpatialReference得到针对与特定情形的更具体 的坐标系统。
1 const char *JDEMDataset::GetProjectionRef() 2 { 3 return( "GEOGCS[\"Tokyo\",DATUM[\"Tokyo\",SPHEROID[\"Bessel 1841\"," 4 "6377397.155,299.1528128,AUTHORITY[\"EPSG\",7004]],TOWGS84[-148," 5 "507,685,0,0,0,0],AUTHORITY[\"EPSG\",6301]],PRIMEM[\"Greenwich\"," 6 "0,AUTHORITY[\"EPSG\",8901]],UNIT[\"DMSH\",0.0174532925199433," 7 "AUTHORITY[\"EPSG\",9108]],AXIS[\"Lat\",NORTH],AXIS[\"Long\",EAST]," 8 "AUTHORITY[\"EPSG\",4301]]" ); 9 }
到这里已经能够完成了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.
1 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()
方法可以直接调用,因此只需要知道调用的参数既可。不过以下的细节需要注意:
- 如果为FALSE,那么驱动器将对数据自行做一些合适的处理。 特别是对于那些并不完全等同格式。
- 可以实现CreateCopy进度提示。回调函数的返回值需要被检测以确定是 否需要继续进行,并且过程最好是在合理的时间片被调用(这个例子并没有示范)。
- 特殊的创建选项必须在帮助中说明。如果存在"NAME=VALUE"的格式,表明 JPEGCreateCopy()函数将通过CPLFetchNameValue()函数设置QUALITY和PROGRESSIVE标志的。
- 返回的GDALDataset句柄是只读或者更新模式。在实用情况下返回更新模式, 否则返回只读模式即可。
JPEG格式对应的CreateCopy的代码如下:
1 static GDALDataset * 2 JPEGCreateCopy( const char * pszFilename, GDALDataset *poSrcDS, 3 int bStrict, char ** papszOptions, 4 GDALProgressFunc pfnProgress, void * pProgressData ) 5 { 6 int nBands = poSrcDS->GetRasterCount(); 7 int nXSize = poSrcDS->GetRasterXSize(); 8 int nYSize = poSrcDS->GetRasterYSize(); 9 int nQuality = 75; 10 int bProgressive = FALSE; 11 12 // -------------------------------------------------------------------- 13 // Some some rudimentary checks 14 // -------------------------------------------------------------------- 15 if( nBands != 1 && nBands != 3 ) 16 { 17 CPLError( CE_Failure, CPLE_NotSupported, 18 "JPEG driver doesn't support %d bands. Must be 1 (grey) " 19 "or 3 (RGB) bands.\n", nBands ); 20 21 return NULL; 22 } 23 24 if( poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte && bStrict ) 25 { 26 CPLError( CE_Failure, CPLE_NotSupported, 27 "JPEG driver doesn't support data type %s. " 28 "Only eight bit byte bands supported.\n", 29 GDALGetDataTypeName( 30 poSrcDS->GetRasterBand(1)->GetRasterDataType()) ); 31 32 return NULL; 33 } 34 35 // -------------------------------------------------------------------- 36 // What options has the user selected? 37 // -------------------------------------------------------------------- 38 if( CSLFetchNameValue(papszOptions,"QUALITY") != NULL ) 39 { 40 nQuality = atoi(CSLFetchNameValue(papszOptions,"QUALITY")); 41 if( nQuality < 10 || nQuality > 100 ) 42 { 43 CPLError( CE_Failure, CPLE_IllegalArg, 44 "QUALITY=%s is not a legal value in the range 10-100.", 45 CSLFetchNameValue(papszOptions,"QUALITY") ); 46 return NULL; 47 } 48 } 49 50 if( CSLFetchNameValue(papszOptions,"PROGRESSIVE") != NULL ) 51 { 52 bProgressive = TRUE; 53 } 54 55 // -------------------------------------------------------------------- 56 // Create the dataset. 57 // -------------------------------------------------------------------- 58 FILE *fpImage; 59 60 fpImage = VSIFOpen( pszFilename, "wb" ); 61 if( fpImage == NULL ) 62 { 63 CPLError( CE_Failure, CPLE_OpenFailed, 64 "Unable to create jpeg file %s.\n", 65 pszFilename ); 66 return NULL; 67 } 68 69 // -------------------------------------------------------------------- 70 // Initialize JPG access to the file. 71 // -------------------------------------------------------------------- 72 struct jpeg_compress_struct sCInfo; 73 struct jpeg_error_mgr sJErr; 74 75 sCInfo.err = jpeg_std_error( &sJErr ); 76 jpeg_create_compress( &sCInfo ); 77 78 jpeg_stdio_dest( &sCInfo, fpImage ); 79 80 sCInfo.image_width = nXSize; 81 sCInfo.image_height = nYSize; 82 sCInfo.input_components = nBands; 83 84 if( nBands == 1 ) 85 { 86 sCInfo.in_color_space = JCS_GRAYSCALE; 87 } 88 else 89 { 90 sCInfo.in_color_space = JCS_RGB; 91 } 92 93 jpeg_set_defaults( &sCInfo ); 94 95 jpeg_set_quality( &sCInfo, nQuality, TRUE ); 96 97 if( bProgressive ) 98 jpeg_simple_progression( &sCInfo ); 99 100 jpeg_start_compress( &sCInfo, TRUE ); 101 102 // -------------------------------------------------------------------- 103 // Loop over image, copying image data. 104 // -------------------------------------------------------------------- 105 GByte *pabyScanline; 106 CPLErr eErr; 107 108 pabyScanline = (GByte *) CPLMalloc( nBands * nXSize ); 109 110 for( int iLine = 0; iLine < nYSize; iLine++ ) 111 { 112 JSAMPLE *ppSamples; 113 114 for( int iBand = 0; iBand < nBands; iBand++ ) 115 { 116 GDALRasterBand * poBand = poSrcDS->GetRasterBand( iBand+1 ); 117 eErr = poBand->RasterIO( GF_Read, 0, iLine, nXSize, 1, 118 pabyScanline + iBand, nXSize, 1, GDT_Byte, 119 nBands, nBands * nXSize ); 120 } 121 122 ppSamples = pabyScanline; 123 jpeg_write_scanlines( &sCInfo, &ppSamples, 1 ); 124 } 125 126 CPLFree( pabyScanline ); 127 128 jpeg_finish_compress( &sCInfo ); 129 jpeg_destroy_compress( &sCInfo ); 130 131 VSIFClose( fpImage ); 132 133 return (GDALDataset *) GDALOpen( pszFilename, GA_ReadOnly ); 134 }
动态创建
在动态创建的例子中,没有源数据。但是提供了文件的大小、波段数和像素的数据类型。 对于其他的一些信息(例如地理参照系等),将在以后通过特定的函数设置。
接下来的自立简单实现了PCI.aux标记的原始矢量创建。它采用的自己的方法创建了一个 空白,并且在最后调用GDALOpen。这避免在Open函数中出现两套不同的启动过程。
1 GDALDataset *PAuxDataset::Create( const char * pszFilename, 2 int nXSize, int nYSize, int nBands, 3 GDALDataType eType, 4 char ** // papszParmList ) 5 { 6 char *pszAuxFilename; 7 8 // -------------------------------------------------------------------- 9 // Verify input options. 10 // -------------------------------------------------------------------- 11 if( eType != GDT_Byte && eType != GDT_Float32 && eType != GDT_UInt16 12 && eType != GDT_Int16 ) 13 { 14 CPLError( CE_Failure, CPLE_AppDefined, 15 "Attempt to create PCI .Aux labelled dataset with an illegal\n" 16 "data type (%s).\n", 17 GDALGetDataTypeName(eType) ); 18 19 return NULL; 20 } 21 22 // -------------------------------------------------------------------- 23 // Try to create the file. 24 // -------------------------------------------------------------------- 25 FILE *fp; 26 27 fp = VSIFOpen( pszFilename, "w" ); 28 29 if( fp == NULL ) 30 { 31 CPLError( CE_Failure, CPLE_OpenFailed, 32 "Attempt to create file `%s' failed.\n", 33 pszFilename ); 34 return NULL; 35 } 36 37 // -------------------------------------------------------------------- 38 // Just write out a couple of bytes to establish the binary 39 // file, and then close it. 40 // -------------------------------------------------------------------- 41 VSIFWrite( (void *) "\0\0", 2, 1, fp ); 42 VSIFClose( fp ); 43 44 // -------------------------------------------------------------------- 45 // Create the aux filename. 46 // -------------------------------------------------------------------- 47 pszAuxFilename = (char *) CPLMalloc(strlen(pszFilename)+5); 48 strcpy( pszAuxFilename, pszFilename );; 49 50 for( int i = strlen(pszAuxFilename)-1; i > 0; i-- ) 51 { 52 if( pszAuxFilename[i] == '.' ) 53 { 54 pszAuxFilename[i] = '\0'; 55 break; 56 } 57 } 58 59 strcat( pszAuxFilename, ".aux" ); 60 61 // -------------------------------------------------------------------- 62 // Open the file. 63 // -------------------------------------------------------------------- 64 fp = VSIFOpen( pszAuxFilename, "wt" ); 65 if( fp == NULL ) 66 { 67 CPLError( CE_Failure, CPLE_OpenFailed, 68 "Attempt to create file `%s' failed.\n", 69 pszAuxFilename ); 70 return NULL; 71 } 72 73 // -------------------------------------------------------------------- 74 // We need to write out the original filename but without any 75 // path components in the AuxilaryTarget line. Do so now. 76 // -------------------------------------------------------------------- 77 int iStart; 78 79 iStart = strlen(pszFilename)-1; 80 while( iStart > 0 && pszFilename[iStart-1] != '/' 81 && pszFilename[iStart-1] != '\\' ) 82 iStart--; 83 84 VSIFPrintf( fp, "AuxilaryTarget: %s\n", pszFilename + iStart ); 85 86 // -------------------------------------------------------------------- 87 // Write out the raw definition for the dataset as a whole. 88 // -------------------------------------------------------------------- 89 VSIFPrintf( fp, "RawDefinition: %d %d %d\n", 90 nXSize, nYSize, nBands ); 91 92 // -------------------------------------------------------------------- 93 // Write out a definition for each band. We always write band 94 // sequential files for now as these are pretty efficiently 95 // handled by GDAL. 96 // -------------------------------------------------------------------- 97 int nImgOffset = 0; 98 99 for( int iBand = 0; iBand < nBands; iBand++ ) 100 { 101 const char * pszTypeName; 102 int nPixelOffset; 103 int nLineOffset; 104 105 nPixelOffset = GDALGetDataTypeSize(eType)/8; 106 nLineOffset = nXSize * nPixelOffset; 107 108 if( eType == GDT_Float32 ) 109 pszTypeName = "32R"; 110 else if( eType == GDT_Int16 ) 111 pszTypeName = "16S"; 112 else if( eType == GDT_UInt16 ) 113 pszTypeName = "16U"; 114 else 115 pszTypeName = "8U"; 116 117 VSIFPrintf( fp, "ChanDefinition-%d: %s %d %d %d %s\n", 118 iBand+1, pszTypeName, 119 nImgOffset, nPixelOffset, nLineOffset, 120 #ifdef CPL_LSB 121 "Swapped" 122 #else 123 "Unswapped" 124 #endif 125 ); 126 127 nImgOffset += nYSize * nLineOffset; 128 } 129 130 // -------------------------------------------------------------------- 131 // Cleanup 132 // -------------------------------------------------------------------- 133 VSIFClose( fp ); 134 135 return (GDALDataset *) GDALOpen( pszFilename, GA_Update ); 136 }
文件格式支持动态创建或支持更新都需要实现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.
1 if( poOpenInfo->pabyHeader[1] == '5' ) 2 { 3 poDS->SetBand( 4 1, new RawRasterBand( poDS, 1, poDS->fpImage, 5 iIn, 1, nWidth, GDT_Byte, TRUE )); 6 } 7 else 8 { 9 poDS->SetBand( 10 1, new RawRasterBand( poDS, 1, poDS->fpImage, 11 iIn, 3, nWidth*3, GDT_Byte, TRUE )); 12 poDS->SetBand( 13 2, new RawRasterBand( poDS, 2, poDS->fpImage, 14 iIn+1, 3, nWidth*3, GDT_Byte, TRUE )); 15 poDS->SetBand( 16 3, new RawRasterBand( poDS, 3, poDS->fpImage, 17 iIn+2, 3, nWidth*3, GDT_Byte, TRUE )); 18 }
The RawRasterBand takes the following arguments.
- poDS: The GDALDataset this band will be a child of. This dataset must be of a class derived from RawRasterDataset.
- nBand: The band it is on that dataset, 1 based.
- fpRaw: The FILE * handle to the file containing the raster data.
- nImgOffset: The byte offset to the first pixel of raster data for the first scanline.
- nPixelOffset: The byte offset from the start of one pixel to the start of the next within the scanline.
- nLineOffset: The byte offset from the start of one scanline to the start of the next.
- eDataType: The GDALDataType code for the type of the data on disk.
- bNativeOrder: FALSE if the data is not in the same endianness as the machine GDAL is running on. The data will be automatically byte swapped.
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中存在对应的虚函数。 它们包括:
- Metadata: 关于数据集或者波段的Name/value。GDALMajorObject(包括其子类) 支持元数据,可以在Open()函数中调用 SetMetadataItem()设置。 SAR_CEOS(frmts/ceos2/sar_ceosdataset.cpp)和GeoTIFF驱动是实现可读元数据的例子。
- ColorTables: GDT_Byte类型的波段可以含有与它们相关联的颜色表。 frmts/png/pngdataset.cpp对应的驱动是一个致支持颜色表的例子。
- ColorInterpretation: PNG驱动包括一个驱动返回一个波段被表示为 红,绿,蓝,透明度或者灰度的描述。
- GCPs:GDALDataset有一系列大地控制点与他们相关联关联矢量到地理 参照系(通过GetGeoTransform来映射转换)。MFF2格式(gdal/frmts.raw.hkvdataset.cpp) 是一个简单的支持GCP的例子。
- NoDataValue: GetNoDataValue()可以判断波段是否是"nodata"。 参考frmts/raw/pauxdataset.cpp驱动。
- Category Names: GetCategoryNames()函数可以根据影象的名字将其分类。 不过目前还没有哪个驱动用到这个特性。