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