1 /******************************************************************************
2  *
3  * Project:  Erdas EIR Raw Driver
4  * Purpose:  Implementation of EIRDataset
5  * Author:   Adam Milling, amilling@alumni.uwaterloo.ca
6  *
7  ******************************************************************************
8  * Copyright (c) 1999, Frank Warmerdam <warmerdam@pobox.com>
9  * Copyright (c) 2009-2010, Even Rouault <even dot rouault at spatialys.com>
10  *
11  * Permission is hereby granted, free of charge, to any person obtaining a
12  * copy of this software and associated documentation files (the "Software"),
13  * to deal in the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15  * and/or sell copies of the Software, and to permit persons to whom the
16  * Software is furnished to do so, subject to the following conditions:
17  *
18  * The above copyright notice and this permission notice shall be included
19  * in all copies or substantial portions of the Software.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27  * DEALINGS IN THE SOFTWARE.
28  ****************************************************************************/
29 
30 #include "cpl_string.h"
31 #include "gdal_frmts.h"
32 #include "ogr_spatialref.h"
33 #include "rawdataset.h"
34 
35 CPL_CVSID("$Id: eirdataset.cpp 8ca42e1b9c2e54b75d35e49885df9789a2643aa4 2020-05-17 21:43:40 +0200 Even Rouault $")
36 
37 /************************************************************************/
38 /* ==================================================================== */
39 /*              EIRDataset                                              */
40 /* ==================================================================== */
41 /************************************************************************/
42 
43 class EIRDataset final: public RawDataset
44 {
45     friend class RawRasterBand;
46 
47     VSILFILE  *fpImage; // image data file
48     bool   bGotTransform;
49     double adfGeoTransform[6];
50     bool   bHDRDirty;
51     char **papszHDR;
52     char **papszExtraFiles;
53 
54     void        ResetKeyValue( const char *pszKey, const char *pszValue );
55 #ifdef unused
56     const char *GetKeyValue( const char *pszKey, const char *pszDefault = "" );
57 #endif
58 
59     CPL_DISALLOW_COPY_ASSIGN(EIRDataset)
60 
61   public:
62     EIRDataset();
63     ~EIRDataset() override;
64 
65     CPLErr GetGeoTransform( double * padfTransform ) override;
66 
67     char **GetFileList() override;
68 
69     static int          Identify( GDALOpenInfo * );
70     static GDALDataset *Open( GDALOpenInfo * );
71 };
72 
73 /************************************************************************/
74 /* ==================================================================== */
75 /*                            EIRDataset                                */
76 /* ==================================================================== */
77 /************************************************************************/
78 
79 /************************************************************************/
80 /*                            EIRDataset()                             */
81 /************************************************************************/
82 
EIRDataset()83 EIRDataset::EIRDataset() :
84     fpImage(nullptr),
85     bGotTransform(false),
86     bHDRDirty(false),
87     papszHDR(nullptr),
88     papszExtraFiles(nullptr)
89 {
90     memset( adfGeoTransform, 0, sizeof(adfGeoTransform) );
91 }
92 
93 /************************************************************************/
94 /*                            ~EIRDataset()                            */
95 /************************************************************************/
96 
~EIRDataset()97 EIRDataset::~EIRDataset()
98 
99 {
100     FlushCache();
101 
102     if( nBands > 0 && GetAccess() == GA_Update )
103     {
104         RawRasterBand *poBand
105             = reinterpret_cast<RawRasterBand *>( GetRasterBand( 1 ) );
106 
107         int bNoDataSet = FALSE;
108         const double dfNoData = poBand->GetNoDataValue(&bNoDataSet);
109         if( bNoDataSet )
110         {
111             ResetKeyValue( "NODATA",
112                            CPLString().Printf( "%.8g", dfNoData ) );
113         }
114     }
115 
116     if( fpImage != nullptr )
117         CPL_IGNORE_RET_VAL(VSIFCloseL( fpImage ));
118 
119     CSLDestroy( papszHDR );
120     CSLDestroy( papszExtraFiles );
121 }
122 
123 #ifdef unused
124 /************************************************************************/
125 /*                            GetKeyValue()                             */
126 /************************************************************************/
127 
GetKeyValue(const char * pszKey,const char * pszDefault)128 const char *EIRDataset::GetKeyValue( const char *pszKey,
129                                      const char *pszDefault )
130 
131 {
132     for( int i = 0; papszHDR[i] != nullptr; i++ )
133     {
134         if( EQUALN(pszKey,papszHDR[i],strlen(pszKey))
135             && isspace((unsigned char)papszHDR[i][strlen(pszKey)]) )
136         {
137             const char *pszValue = papszHDR[i] + strlen(pszKey);
138             while( isspace(static_cast<unsigned char>(*pszValue)) )
139                 pszValue++;
140 
141             return pszValue;
142         }
143     }
144 
145     return pszDefault;
146 }
147 #endif
148 
149 /************************************************************************/
150 /*                           ResetKeyValue()                            */
151 /*                                                                      */
152 /*      Replace or add the keyword with the indicated value in the      */
153 /*      papszHDR list.                                                  */
154 /************************************************************************/
155 
ResetKeyValue(const char * pszKey,const char * pszValue)156 void EIRDataset::ResetKeyValue( const char *pszKey, const char *pszValue )
157 
158 {
159     if( strlen(pszValue) > 65 )
160     {
161         CPLAssert( strlen(pszValue) <= 65 );
162         return;
163     }
164 
165     char szNewLine[82] = { '\0' };
166     snprintf( szNewLine, sizeof(szNewLine), "%-15s%s", pszKey, pszValue );
167 
168     for( int i = CSLCount(papszHDR)-1; i >= 0; i-- )
169     {
170         if( EQUALN(papszHDR[i],szNewLine,strlen(pszKey)+1 ) )
171         {
172             if( strcmp(papszHDR[i],szNewLine) != 0 )
173             {
174                 CPLFree( papszHDR[i] );
175                 papszHDR[i] = CPLStrdup( szNewLine );
176                 bHDRDirty = true;
177             }
178             return;
179         }
180     }
181 
182     bHDRDirty = true;
183     papszHDR = CSLAddString( papszHDR, szNewLine );
184 }
185 
186 /************************************************************************/
187 /*                          GetGeoTransform()                           */
188 /************************************************************************/
189 
GetGeoTransform(double * padfTransform)190 CPLErr EIRDataset::GetGeoTransform( double * padfTransform )
191 
192 {
193     if( bGotTransform )
194     {
195         memcpy( padfTransform, adfGeoTransform, sizeof(double) * 6 );
196         return CE_None;
197     }
198 
199     return GDALPamDataset::GetGeoTransform( padfTransform );
200 }
201 
202 /************************************************************************/
203 /*                            GetFileList()                             */
204 /************************************************************************/
205 
GetFileList()206 char **EIRDataset::GetFileList()
207 
208 {
209     // Main data file, etc.
210     char **papszFileList = GDALPamDataset::GetFileList();
211 
212     // Header file.
213     papszFileList = CSLInsertStrings( papszFileList, -1,
214                                       papszExtraFiles );
215 
216     return papszFileList;
217 }
218 
219 /************************************************************************/
220 /*                              Identify()                              */
221 /************************************************************************/
222 
Identify(GDALOpenInfo * poOpenInfo)223 int EIRDataset::Identify( GDALOpenInfo * poOpenInfo )
224 
225 {
226     if( poOpenInfo->nHeaderBytes < 100 )
227         return FALSE;
228 
229     if( strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
230                "IMAGINE_RAW_FILE" ) == nullptr )
231         return FALSE;
232 
233     return TRUE;
234 }
235 
236 /************************************************************************/
237 /*                                Open()                                */
238 /************************************************************************/
239 
Open(GDALOpenInfo * poOpenInfo)240 GDALDataset *EIRDataset::Open( GDALOpenInfo * poOpenInfo )
241 
242 {
243     if( !Identify( poOpenInfo ) || poOpenInfo->fpL == nullptr )
244         return nullptr;
245 
246     /* header example and description
247 
248     IMAGINE_RAW_FILE // must be on first line, by itself
249     WIDTH 581        // number of columns in the image
250     HEIGHT 695       // number of rows in the image
251     NUM_LAYERS 3     // number of spectral bands in the image; default 1
252     PIXEL_FILES raw8_3n_ui_sanjack.bl // raster file
253                                       // default: same name with no extension
254     FORMAT BIL       // BIL BIP BSQ; default BIL
255     DATATYPE U8      // U1 U2 U4 U8 U16 U32 S16 S32 F32 F64; default U8
256     BYTE_ORDER       // LSB MSB; required for U16 U32 S16 S32 F32 F64
257     DATA_OFFSET      // start of image data in raster file; default 0 bytes
258     END_RAW_FILE     // end RAW file - stop reading
259 
260     For a true color image with three bands (R, G, B) stored using 8 bits
261     for each pixel in each band, DATA_TYPE equals U8 and NUM_LAYERS equals
262     3 for a total of 24 bits per pixel.
263 
264     Note that the current version of ERDAS Raw Raster Reader/Writer does
265     not support the LAYER_SKIP_BYTES, RECORD_SKIP_BYTES, TILE_WIDTH and
266     TILE_HEIGHT directives. Since the reader does not read the PIXEL_FILES
267     directive, the reader always assumes that the raw binary file is the
268     dataset, and the name of this file is the name of the header without the
269     extension. Currently, the reader does not support multiple raw binary
270     files in one dataset or a single file with both the header and the raw
271     binary data at the same time.
272     */
273 
274     int nRows = -1;
275     int nCols = -1;
276     int nBands = 1;
277     int nSkipBytes = 0;
278     int nLineCount = 0;
279     GDALDataType eDataType = GDT_Byte;
280     int nBits = 8;
281     char chByteOrder = 'M';
282     char szLayout[10] = "BIL";
283     char **papszHDR = nullptr;
284 
285     // default raster file: same name with no extension
286     const CPLString osPath = CPLGetPath( poOpenInfo->pszFilename );
287     const CPLString osName = CPLGetBasename( poOpenInfo->pszFilename );
288     CPLString osRasterFilename = CPLFormCIFilename( osPath, osName, "" );
289 
290     // parse the header file
291     const char *pszLine = nullptr;
292     VSIRewindL(poOpenInfo->fpL);
293     while( (pszLine = CPLReadLineL( poOpenInfo->fpL )) != nullptr )
294     {
295         nLineCount++;
296 
297         if ( (nLineCount == 1) && !EQUAL(pszLine, "IMAGINE_RAW_FILE") ) {
298             return nullptr;
299         }
300 
301         if ( (nLineCount > 50) || EQUAL(pszLine, "END_RAW_FILE") ) {
302             break;
303         }
304 
305         if( strlen(pszLine) > 1000 )
306             break;
307 
308         papszHDR = CSLAddString( papszHDR, pszLine );
309 
310         char **papszTokens
311             = CSLTokenizeStringComplex( pszLine, " \t", TRUE, FALSE );
312         if( CSLCount( papszTokens ) < 2 )
313         {
314             CSLDestroy( papszTokens );
315             continue;
316         }
317 
318         if( EQUAL(papszTokens[0], "WIDTH") )
319         {
320             nCols = atoi(papszTokens[1]);
321         }
322         else if( EQUAL(papszTokens[0], "HEIGHT") )
323         {
324             nRows = atoi(papszTokens[1]);
325         }
326         else if( EQUAL(papszTokens[0], "NUM_LAYERS") )
327         {
328             nBands = atoi(papszTokens[1]);
329         }
330         else if( EQUAL(papszTokens[0], "PIXEL_FILES") )
331         {
332             osRasterFilename = CPLFormCIFilename( osPath, papszTokens[1], "" );
333         }
334         else if( EQUAL(papszTokens[0], "FORMAT") )
335         {
336             snprintf( szLayout, sizeof(szLayout), "%s", papszTokens[1] );
337         }
338         else if( EQUAL(papszTokens[0], "DATATYPE")
339                  || EQUAL(papszTokens[0], "DATA_TYPE") )
340         {
341             if ( EQUAL(papszTokens[1], "U1")
342                  || EQUAL(papszTokens[1], "U2")
343                  || EQUAL(papszTokens[1], "U4")
344                  || EQUAL(papszTokens[1], "U8") ) {
345                 nBits = 8;
346                 eDataType = GDT_Byte;
347             }
348             else if( EQUAL(papszTokens[1], "U16") ) {
349                 nBits = 16;
350                 eDataType = GDT_UInt16;
351             }
352             else if( EQUAL(papszTokens[1], "U32") ) {
353                 nBits = 32;
354                 eDataType = GDT_UInt32;
355             }
356             else if( EQUAL(papszTokens[1], "S16") ) {
357                 nBits = 16;
358                 eDataType = GDT_Int16;
359             }
360             else if( EQUAL(papszTokens[1], "S32") ) {
361                 nBits = 32;
362                 eDataType = GDT_Int32;
363             }
364             else if( EQUAL(papszTokens[1], "F32") ) {
365                 nBits = 32;
366                 eDataType = GDT_Float32;
367             }
368             else if( EQUAL(papszTokens[1], "F64") ) {
369                 nBits = 64;
370                 eDataType = GDT_Float64;
371             }
372             else {
373                 CPLError(
374                     CE_Failure, CPLE_NotSupported,
375                     "EIR driver does not support DATATYPE %s.",
376                     papszTokens[1] );
377                 CSLDestroy( papszTokens );
378                 CSLDestroy( papszHDR );
379                 return nullptr;
380             }
381         }
382         else if( EQUAL(papszTokens[0], "BYTE_ORDER") )
383         {
384             // M for MSB, L for LSB
385             chByteOrder = static_cast<char>( toupper(papszTokens[1][0]) );
386         }
387         else if( EQUAL(papszTokens[0],"DATA_OFFSET") )
388         {
389             nSkipBytes = atoi(papszTokens[1]); // TBD: is this mapping right?
390         }
391 
392         CSLDestroy( papszTokens );
393     }
394     CPL_IGNORE_RET_VAL(nBits);
395 
396 /* -------------------------------------------------------------------- */
397 /*      Did we get the required keywords?  If not we return with        */
398 /*      this never having been considered to be a match. This isn't     */
399 /*      an error!                                                       */
400 /* -------------------------------------------------------------------- */
401     if( nRows == -1 || nCols == -1 )
402     {
403         CSLDestroy( papszHDR );
404         return nullptr;
405     }
406 
407     if (!GDALCheckDatasetDimensions(nCols, nRows) ||
408         !GDALCheckBandCount(nBands, FALSE))
409     {
410         CSLDestroy( papszHDR );
411         return nullptr;
412     }
413 
414 /* -------------------------------------------------------------------- */
415 /*      Confirm the requested access is supported.                      */
416 /* -------------------------------------------------------------------- */
417     if( poOpenInfo->eAccess == GA_Update )
418     {
419         CSLDestroy( papszHDR );
420         CPLError( CE_Failure, CPLE_NotSupported,
421                   "The EIR driver does not support update access to existing"
422                   " datasets." );
423         return nullptr;
424     }
425 /* -------------------------------------------------------------------- */
426 /*      Create a corresponding GDALDataset.                             */
427 /* -------------------------------------------------------------------- */
428     EIRDataset *poDS = new EIRDataset();
429 
430 /* -------------------------------------------------------------------- */
431 /*      Capture some information from the file that is of interest.     */
432 /* -------------------------------------------------------------------- */
433     poDS->nRasterXSize = nCols;
434     poDS->nRasterYSize = nRows;
435     poDS->papszHDR = papszHDR;
436 
437 /* -------------------------------------------------------------------- */
438 /*      Open target binary file.                                        */
439 /* -------------------------------------------------------------------- */
440     poDS->fpImage = VSIFOpenL( osRasterFilename.c_str(), "rb" );
441     if( poDS->fpImage == nullptr )
442     {
443         CPLError( CE_Failure, CPLE_OpenFailed,
444                   "Failed to open %s: %s",
445                   osRasterFilename.c_str(), VSIStrerror( errno ) );
446         delete poDS;
447         return nullptr;
448     }
449     poDS->papszExtraFiles =
450             CSLAddString( poDS->papszExtraFiles,
451                           osRasterFilename );
452 
453     poDS->eAccess = poOpenInfo->eAccess;
454 
455 /* -------------------------------------------------------------------- */
456 /*      Compute the line offset.                                        */
457 /* -------------------------------------------------------------------- */
458     const int nItemSize = GDALGetDataTypeSizeBytes(eDataType);
459     int nPixelOffset = 0;
460     int nLineOffset = 0;
461     vsi_l_offset nBandOffset = 0;
462 
463     if( EQUAL(szLayout, "BIP") )
464     {
465         nPixelOffset = nItemSize * nBands;
466         if( nPixelOffset > INT_MAX / nCols )
467         {
468             delete poDS;
469             return nullptr;
470         }
471         nLineOffset = nPixelOffset * nCols;
472         nBandOffset = static_cast<vsi_l_offset>(nItemSize);
473     }
474     else if( EQUAL(szLayout, "BSQ") )
475     {
476         nPixelOffset = nItemSize;
477         if( nPixelOffset > INT_MAX / nCols )
478         {
479             delete poDS;
480             return nullptr;
481         }
482         nLineOffset = nPixelOffset * nCols;
483         nBandOffset = static_cast<vsi_l_offset>(nLineOffset) * nRows;
484     }
485     else /* assume BIL */
486     {
487         nPixelOffset = nItemSize;
488         if( nItemSize > INT_MAX / nBands || nItemSize * nBands > INT_MAX / nCols )
489         {
490             delete poDS;
491             return nullptr;
492         }
493         nLineOffset = nItemSize * nBands * nCols;
494         nBandOffset = static_cast<vsi_l_offset>(nItemSize) * nCols;
495     }
496 
497     poDS->SetDescription( poOpenInfo->pszFilename );
498     poDS->PamInitialize();
499 
500 /* -------------------------------------------------------------------- */
501 /*      Create band information objects.                                */
502 /* -------------------------------------------------------------------- */
503     poDS->nBands = nBands;
504     for( int i = 0; i < poDS->nBands; i++ )
505     {
506         RawRasterBand *poBand
507             = new RawRasterBand( poDS, i+1, poDS->fpImage,
508                                 nSkipBytes + nBandOffset * i,
509                                 nPixelOffset, nLineOffset, eDataType,
510 #ifdef CPL_LSB
511                                 chByteOrder == 'I' || chByteOrder == 'L',
512 #else
513                                 chByteOrder == 'M',
514 #endif
515                                 RawRasterBand::OwnFP::NO );
516 
517         poDS->SetBand( i+1, poBand );
518     }
519 
520 /* -------------------------------------------------------------------- */
521 /*      look for a worldfile                                            */
522 /* -------------------------------------------------------------------- */
523 
524     if( !poDS->bGotTransform )
525         poDS->bGotTransform = CPL_TO_BOOL(
526             GDALReadWorldFile( poOpenInfo->pszFilename, nullptr,
527                                poDS->adfGeoTransform ) );
528 
529     if( !poDS->bGotTransform )
530         poDS->bGotTransform = CPL_TO_BOOL(
531             GDALReadWorldFile( poOpenInfo->pszFilename, "wld",
532                                poDS->adfGeoTransform ) );
533 
534 /* -------------------------------------------------------------------- */
535 /*      Initialize any PAM information.                                 */
536 /* -------------------------------------------------------------------- */
537     poDS->TryLoadXML();
538 
539 /* -------------------------------------------------------------------- */
540 /*      Check for overviews.                                            */
541 /* -------------------------------------------------------------------- */
542     poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename );
543 
544     return poDS;
545 }
546 
547 /************************************************************************/
548 /*                         GDALRegister_EIR()                           */
549 /************************************************************************/
550 
GDALRegister_EIR()551 void GDALRegister_EIR()
552 
553 {
554     if( GDALGetDriverByName( "EIR" ) != nullptr )
555         return;
556 
557     GDALDriver *poDriver = new GDALDriver();
558 
559     poDriver->SetDescription( "EIR" );
560     poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
561     poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, "Erdas Imagine Raw" );
562     poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "drivers/raster/eir.html" );
563     poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
564 
565     poDriver->pfnOpen = EIRDataset::Open;
566     poDriver->pfnIdentify = EIRDataset::Identify;
567 
568     GetGDALDriverManager()->RegisterDriver( poDriver );
569 }
570