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()函數可以根據影象的名字將其分類。 不過目前還沒有哪個驅動用到這個特性。