Coverage Report

Created: 2025-06-09 07:42

/src/gdal/frmts/raw/ehdrdataset.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  ESRI .hdr Driver
4
 * Purpose:  Implementation of EHdrDataset
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 1999, Frank Warmerdam <warmerdam@pobox.com>
9
 * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "cpl_port.h"
15
#include "ehdrdataset.h"
16
#include "rawdataset.h"
17
18
#include <algorithm>
19
#include <cctype>
20
#include <cerrno>
21
#include <climits>
22
#include <cmath>
23
#include <cstddef>
24
#include <cstdio>
25
#include <cstdlib>
26
#include <cstring>
27
#if HAVE_FCNTL_H
28
#include <fcntl.h>
29
#endif
30
31
#include <limits>
32
33
#include "cpl_conv.h"
34
#include "cpl_error.h"
35
#include "cpl_progress.h"
36
#include "cpl_string.h"
37
#include "cpl_vsi.h"
38
#include "gdal.h"
39
#include "gdal_frmts.h"
40
#include "gdal_pam.h"
41
#include "gdal_priv.h"
42
#include "ogr_core.h"
43
#include "ogr_spatialref.h"
44
45
constexpr int HAS_MIN_FLAG = 0x1;
46
constexpr int HAS_MAX_FLAG = 0x2;
47
constexpr int HAS_MEAN_FLAG = 0x4;
48
constexpr int HAS_STDDEV_FLAG = 0x8;
49
constexpr int HAS_ALL_FLAGS =
50
    HAS_MIN_FLAG | HAS_MAX_FLAG | HAS_MEAN_FLAG | HAS_STDDEV_FLAG;
51
52
/************************************************************************/
53
/*                           EHdrRasterBand()                           */
54
/************************************************************************/
55
56
EHdrRasterBand::EHdrRasterBand(GDALDataset *poDSIn, int nBandIn,
57
                               VSILFILE *fpRawIn, vsi_l_offset nImgOffsetIn,
58
                               int nPixelOffsetIn, int nLineOffsetIn,
59
                               GDALDataType eDataTypeIn,
60
                               RawRasterBand::ByteOrder eByteOrderIn,
61
                               int nBitsIn)
62
0
    : RawRasterBand(poDSIn, nBandIn, fpRawIn, nImgOffsetIn, nPixelOffsetIn,
63
0
                    nLineOffsetIn, eDataTypeIn, eByteOrderIn,
64
0
                    RawRasterBand::OwnFP::NO),
65
0
      nBits(nBitsIn), nStartBit(0), nPixelOffsetBits(0), nLineOffsetBits(0),
66
0
      bNoDataSet(FALSE), dfNoData(0.0), dfMin(0.0), dfMax(0.0), dfMean(0.0),
67
0
      dfStdDev(0.0), minmaxmeanstddev(0)
68
0
{
69
0
    m_bValid = RawRasterBand::IsValid();
70
71
0
    EHdrDataset *poEDS = reinterpret_cast<EHdrDataset *>(poDS);
72
73
0
    if (nBits < 8)
74
0
    {
75
0
        const int nSkipBytes = atoi(poEDS->GetKeyValue("SKIPBYTES"));
76
0
        if (nSkipBytes < 0 || nSkipBytes > std::numeric_limits<int>::max() / 8)
77
0
        {
78
0
            m_bValid = false;
79
0
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid SKIPBYTES: %d",
80
0
                     nSkipBytes);
81
0
            nStartBit = 0;
82
0
        }
83
0
        else
84
0
        {
85
0
            nStartBit = static_cast<vsi_l_offset>(nSkipBytes) * 8;
86
0
        }
87
0
        if (nBand >= 2)
88
0
        {
89
0
            GIntBig nBandRowBytes =
90
0
                CPLAtoGIntBig(poEDS->GetKeyValue("BANDROWBYTES"));
91
0
            if (nBandRowBytes < 0)
92
0
            {
93
0
                m_bValid = false;
94
0
                CPLError(CE_Failure, CPLE_AppDefined,
95
0
                         "Invalid BANDROWBYTES: " CPL_FRMT_GIB, nBandRowBytes);
96
0
                nBandRowBytes = 0;
97
0
            }
98
0
            vsi_l_offset nRowBytes = 0;
99
0
            if (nBandRowBytes == 0)
100
0
                nRowBytes =
101
0
                    (static_cast<vsi_l_offset>(nBits) * poDS->GetRasterXSize() +
102
0
                     7) /
103
0
                    8;
104
0
            else
105
0
                nRowBytes = static_cast<vsi_l_offset>(nBandRowBytes);
106
107
0
            nStartBit += nRowBytes * (nBand - 1) * 8;
108
0
        }
109
110
0
        nPixelOffsetBits = nBits;
111
0
        GIntBig nTotalRowBytes =
112
0
            CPLAtoGIntBig(poEDS->GetKeyValue("TOTALROWBYTES"));
113
0
        if (nTotalRowBytes < 0 ||
114
0
            nTotalRowBytes > GINTBIG_MAX / 8 / poDS->GetRasterYSize())
115
0
        {
116
0
            m_bValid = false;
117
0
            CPLError(CE_Failure, CPLE_AppDefined,
118
0
                     "Invalid TOTALROWBYTES: " CPL_FRMT_GIB, nTotalRowBytes);
119
0
            nTotalRowBytes = 0;
120
0
        }
121
0
        if (nTotalRowBytes > 0)
122
0
            nLineOffsetBits = static_cast<vsi_l_offset>(nTotalRowBytes * 8);
123
0
        else
124
0
            nLineOffsetBits = static_cast<vsi_l_offset>(nPixelOffsetBits) *
125
0
                              poDS->GetRasterXSize();
126
127
0
        nBlockXSize = poDS->GetRasterXSize();
128
0
        nBlockYSize = 1;
129
130
0
        SetMetadataItem("NBITS", CPLString().Printf("%d", nBits),
131
0
                        "IMAGE_STRUCTURE");
132
0
    }
133
0
}
134
135
/************************************************************************/
136
/*                             IReadBlock()                             */
137
/************************************************************************/
138
139
CPLErr EHdrRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
140
141
0
{
142
0
    if (nBits >= 8)
143
0
        return RawRasterBand::IReadBlock(nBlockXOff, nBlockYOff, pImage);
144
145
    // Establish desired position.
146
0
    const vsi_l_offset nLineStart =
147
0
        (nStartBit + nLineOffsetBits * nBlockYOff) / 8;
148
0
    int iBitOffset =
149
0
        static_cast<int>((nStartBit + nLineOffsetBits * nBlockYOff) % 8);
150
0
    const vsi_l_offset nLineEnd =
151
0
        (nStartBit + nLineOffsetBits * nBlockYOff +
152
0
         static_cast<vsi_l_offset>(nPixelOffsetBits) * nBlockXSize - 1) /
153
0
        8;
154
0
    const vsi_l_offset nLineBytesBig = nLineEnd - nLineStart + 1;
155
0
    if (nLineBytesBig >
156
0
        static_cast<vsi_l_offset>(std::numeric_limits<int>::max()))
157
0
        return CE_Failure;
158
0
    const unsigned int nLineBytes = static_cast<unsigned int>(nLineBytesBig);
159
160
    // Read data into buffer.
161
0
    GByte *pabyBuffer = static_cast<GByte *>(VSI_MALLOC_VERBOSE(nLineBytes));
162
0
    if (pabyBuffer == nullptr)
163
0
        return CE_Failure;
164
165
0
    if (VSIFSeekL(GetFPL(), nLineStart, SEEK_SET) != 0 ||
166
0
        VSIFReadL(pabyBuffer, 1, nLineBytes, GetFPL()) != nLineBytes)
167
0
    {
168
0
        CPLError(CE_Failure, CPLE_FileIO,
169
0
                 "Failed to read %u bytes at offset %lu.\n%s", nLineBytes,
170
0
                 static_cast<unsigned long>(nLineStart), VSIStrerror(errno));
171
0
        CPLFree(pabyBuffer);
172
0
        return CE_Failure;
173
0
    }
174
175
    // Copy data, promoting to 8bit.
176
0
    for (int iX = 0, iPixel = 0; iX < nBlockXSize; iX++)
177
0
    {
178
0
        int nOutWord = 0;
179
180
0
        for (int iBit = 0; iBit < nBits; iBit++)
181
0
        {
182
0
            if (pabyBuffer[iBitOffset >> 3] & (0x80 >> (iBitOffset & 7)))
183
0
                nOutWord |= (1 << (nBits - 1 - iBit));
184
0
            iBitOffset++;
185
0
        }
186
187
0
        iBitOffset = iBitOffset + nPixelOffsetBits - nBits;
188
189
0
        reinterpret_cast<GByte *>(pImage)[iPixel++] =
190
0
            static_cast<GByte>(nOutWord);
191
0
    }
192
193
0
    CPLFree(pabyBuffer);
194
195
0
    return CE_None;
196
0
}
197
198
/************************************************************************/
199
/*                            IWriteBlock()                             */
200
/************************************************************************/
201
202
CPLErr EHdrRasterBand::IWriteBlock(int nBlockXOff, int nBlockYOff, void *pImage)
203
204
0
{
205
0
    if (nBits >= 8)
206
0
        return RawRasterBand::IWriteBlock(nBlockXOff, nBlockYOff, pImage);
207
208
    // Establish desired position.
209
0
    const vsi_l_offset nLineStart =
210
0
        (nStartBit + nLineOffsetBits * nBlockYOff) / 8;
211
0
    int iBitOffset =
212
0
        static_cast<int>((nStartBit + nLineOffsetBits * nBlockYOff) % 8);
213
0
    const vsi_l_offset nLineEnd =
214
0
        (nStartBit + nLineOffsetBits * nBlockYOff +
215
0
         static_cast<vsi_l_offset>(nPixelOffsetBits) * nBlockXSize - 1) /
216
0
        8;
217
0
    const vsi_l_offset nLineBytesBig = nLineEnd - nLineStart + 1;
218
0
    if (nLineBytesBig >
219
0
        static_cast<vsi_l_offset>(std::numeric_limits<int>::max()))
220
0
        return CE_Failure;
221
0
    const unsigned int nLineBytes = static_cast<unsigned int>(nLineBytesBig);
222
223
    // Read data into buffer.
224
0
    GByte *pabyBuffer = static_cast<GByte *>(VSI_CALLOC_VERBOSE(nLineBytes, 1));
225
0
    if (pabyBuffer == nullptr)
226
0
        return CE_Failure;
227
228
0
    if (VSIFSeekL(GetFPL(), nLineStart, SEEK_SET) != 0)
229
0
    {
230
0
        CPLError(CE_Failure, CPLE_FileIO,
231
0
                 "Failed to read %u bytes at offset %lu.\n%s", nLineBytes,
232
0
                 static_cast<unsigned long>(nLineStart), VSIStrerror(errno));
233
0
        CPLFree(pabyBuffer);
234
0
        return CE_Failure;
235
0
    }
236
237
0
    CPL_IGNORE_RET_VAL(VSIFReadL(pabyBuffer, nLineBytes, 1, GetFPL()));
238
239
    // Copy data, promoting to 8bit.
240
0
    for (int iX = 0, iPixel = 0; iX < nBlockXSize; iX++)
241
0
    {
242
0
        const int nOutWord = reinterpret_cast<GByte *>(pImage)[iPixel++];
243
244
0
        for (int iBit = 0; iBit < nBits; iBit++)
245
0
        {
246
0
            if (nOutWord & (1 << (nBits - 1 - iBit)))
247
0
                pabyBuffer[iBitOffset >> 3] |= (0x80 >> (iBitOffset & 7));
248
0
            else
249
0
                pabyBuffer[iBitOffset >> 3] &= ~((0x80 >> (iBitOffset & 7)));
250
251
0
            iBitOffset++;
252
0
        }
253
254
0
        iBitOffset = iBitOffset + nPixelOffsetBits - nBits;
255
0
    }
256
257
    // Write the data back out.
258
0
    if (VSIFSeekL(GetFPL(), nLineStart, SEEK_SET) != 0 ||
259
0
        VSIFWriteL(pabyBuffer, 1, nLineBytes, GetFPL()) != nLineBytes)
260
0
    {
261
0
        CPLError(CE_Failure, CPLE_FileIO,
262
0
                 "Failed to write %u bytes at offset %lu.\n%s", nLineBytes,
263
0
                 static_cast<unsigned long>(nLineStart), VSIStrerror(errno));
264
0
        return CE_Failure;
265
0
    }
266
267
0
    CPLFree(pabyBuffer);
268
269
0
    return CE_None;
270
0
}
271
272
/************************************************************************/
273
/*                             IRasterIO()                              */
274
/************************************************************************/
275
276
CPLErr EHdrRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
277
                                 int nXSize, int nYSize, void *pData,
278
                                 int nBufXSize, int nBufYSize,
279
                                 GDALDataType eBufType, GSpacing nPixelSpace,
280
                                 GSpacing nLineSpace,
281
                                 GDALRasterIOExtraArg *psExtraArg)
282
283
0
{
284
    // Defer to RawRasterBand
285
0
    if (nBits >= 8)
286
0
        return RawRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
287
0
                                        pData, nBufXSize, nBufYSize, eBufType,
288
0
                                        nPixelSpace, nLineSpace, psExtraArg);
289
290
    // Force use of IReadBlock() and IWriteBlock()
291
0
    return GDALRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
292
0
                                     pData, nBufXSize, nBufYSize, eBufType,
293
0
                                     nPixelSpace, nLineSpace, psExtraArg);
294
0
}
295
296
/************************************************************************/
297
/*                              OSR_GDS()                               */
298
/************************************************************************/
299
300
static const char *OSR_GDS(char *pszResult, int nResultLen, char **papszNV,
301
                           const char *pszField, const char *pszDefaultValue)
302
303
0
{
304
0
    if (papszNV == nullptr || papszNV[0] == nullptr)
305
0
        return pszDefaultValue;
306
307
0
    int iLine = 0;  // Used after for.
308
0
    for (; papszNV[iLine] != nullptr &&
309
0
           !EQUALN(papszNV[iLine], pszField, strlen(pszField));
310
0
         iLine++)
311
0
    {
312
0
    }
313
314
0
    if (papszNV[iLine] == nullptr)
315
0
        return pszDefaultValue;
316
317
0
    char **papszTokens = CSLTokenizeString(papszNV[iLine]);
318
319
0
    if (CSLCount(papszTokens) > 1)
320
0
        strncpy(pszResult, papszTokens[1], nResultLen - 1);
321
0
    else
322
0
        strncpy(pszResult, pszDefaultValue, nResultLen - 1);
323
0
    pszResult[nResultLen - 1] = '\0';
324
325
0
    CSLDestroy(papszTokens);
326
0
    return pszResult;
327
0
}
328
329
/************************************************************************/
330
/* ==================================================================== */
331
/*                            EHdrDataset                               */
332
/* ==================================================================== */
333
/************************************************************************/
334
335
/************************************************************************/
336
/*                            EHdrDataset()                             */
337
/************************************************************************/
338
339
EHdrDataset::EHdrDataset()
340
0
    : fpImage(nullptr), osHeaderExt("hdr"), bGotTransform(false),
341
0
      bHDRDirty(false), papszHDR(nullptr), bCLRDirty(false)
342
0
{
343
0
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
344
0
    adfGeoTransform[0] = 0.0;
345
0
    adfGeoTransform[1] = 1.0;
346
0
    adfGeoTransform[2] = 0.0;
347
0
    adfGeoTransform[3] = 0.0;
348
0
    adfGeoTransform[4] = 0.0;
349
0
    adfGeoTransform[5] = 1.0;
350
0
}
351
352
/************************************************************************/
353
/*                            ~EHdrDataset()                            */
354
/************************************************************************/
355
356
EHdrDataset::~EHdrDataset()
357
358
0
{
359
0
    EHdrDataset::Close();
360
0
}
361
362
/************************************************************************/
363
/*                              Close()                                 */
364
/************************************************************************/
365
366
CPLErr EHdrDataset::Close()
367
0
{
368
0
    CPLErr eErr = CE_None;
369
0
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
370
0
    {
371
0
        if (EHdrDataset::FlushCache(true) != CE_None)
372
0
            eErr = CE_Failure;
373
374
0
        if (nBands > 0 && GetAccess() == GA_Update)
375
0
        {
376
0
            int bNoDataSet;
377
0
            RawRasterBand *poBand =
378
0
                reinterpret_cast<RawRasterBand *>(GetRasterBand(1));
379
380
0
            const double dfNoData = poBand->GetNoDataValue(&bNoDataSet);
381
0
            if (bNoDataSet)
382
0
            {
383
0
                ResetKeyValue("NODATA", CPLString().Printf("%.8g", dfNoData));
384
0
            }
385
386
0
            if (bCLRDirty)
387
0
                RewriteCLR(poBand);
388
389
0
            if (bHDRDirty)
390
0
            {
391
0
                if (RewriteHDR() != CE_None)
392
0
                    eErr = CE_Failure;
393
0
            }
394
0
        }
395
396
0
        if (fpImage)
397
0
        {
398
0
            if (VSIFCloseL(fpImage) != 0)
399
0
            {
400
0
                CPLError(CE_Failure, CPLE_FileIO, "I/O error");
401
0
                eErr = CE_Failure;
402
0
            }
403
0
        }
404
405
0
        CSLDestroy(papszHDR);
406
0
        if (GDALPamDataset::Close() != CE_None)
407
0
            eErr = CE_Failure;
408
0
    }
409
0
    return eErr;
410
0
}
411
412
/************************************************************************/
413
/*                            GetKeyValue()                             */
414
/************************************************************************/
415
416
const char *EHdrDataset::GetKeyValue(const char *pszKey, const char *pszDefault)
417
418
0
{
419
0
    for (int i = 0; papszHDR[i] != nullptr; i++)
420
0
    {
421
0
        if (EQUALN(pszKey, papszHDR[i], strlen(pszKey)) &&
422
0
            isspace(static_cast<unsigned char>(papszHDR[i][strlen(pszKey)])))
423
0
        {
424
0
            const char *pszValue = papszHDR[i] + strlen(pszKey);
425
0
            while (isspace(static_cast<unsigned char>(*pszValue)))
426
0
                pszValue++;
427
428
0
            return pszValue;
429
0
        }
430
0
    }
431
432
0
    return pszDefault;
433
0
}
434
435
/************************************************************************/
436
/*                           ResetKeyValue()                            */
437
/*                                                                      */
438
/*      Replace or add the keyword with the indicated value in the      */
439
/*      papszHDR list.                                                  */
440
/************************************************************************/
441
442
void EHdrDataset::ResetKeyValue(const char *pszKey, const char *pszValue)
443
444
0
{
445
0
    if (strlen(pszValue) > 65)
446
0
    {
447
0
        CPLAssert(strlen(pszValue) <= 65);
448
0
        return;
449
0
    }
450
451
0
    char szNewLine[82] = {'\0'};
452
0
    snprintf(szNewLine, sizeof(szNewLine), "%-15s%s", pszKey, pszValue);
453
454
0
    for (int i = CSLCount(papszHDR) - 1; i >= 0; i--)
455
0
    {
456
0
        if (EQUALN(papszHDR[i], szNewLine, strlen(pszKey) + 1))
457
0
        {
458
0
            if (strcmp(papszHDR[i], szNewLine) != 0)
459
0
            {
460
0
                CPLFree(papszHDR[i]);
461
0
                papszHDR[i] = CPLStrdup(szNewLine);
462
0
                bHDRDirty = true;
463
0
            }
464
0
            return;
465
0
        }
466
0
    }
467
468
0
    bHDRDirty = true;
469
0
    papszHDR = CSLAddString(papszHDR, szNewLine);
470
0
}
471
472
/************************************************************************/
473
/*                           RewriteCLR()                               */
474
/************************************************************************/
475
476
void EHdrDataset::RewriteCLR(GDALRasterBand *poBand) const
477
478
0
{
479
0
    CPLString osCLRFilename = CPLResetExtensionSafe(GetDescription(), "clr");
480
0
    GDALColorTable *poTable = poBand->GetColorTable();
481
0
    GDALRasterAttributeTable *poRAT = poBand->GetDefaultRAT();
482
0
    if (poTable || poRAT)
483
0
    {
484
0
        VSILFILE *fp = VSIFOpenL(osCLRFilename, "wt");
485
0
        if (fp != nullptr)
486
0
        {
487
            // Write RAT in priority if both are defined
488
0
            if (poRAT)
489
0
            {
490
0
                for (int iEntry = 0; iEntry < poRAT->GetRowCount(); iEntry++)
491
0
                {
492
0
                    CPLString oLine;
493
0
                    oLine.Printf("%3d %3d %3d %3d\n",
494
0
                                 poRAT->GetValueAsInt(iEntry, 0),
495
0
                                 poRAT->GetValueAsInt(iEntry, 1),
496
0
                                 poRAT->GetValueAsInt(iEntry, 2),
497
0
                                 poRAT->GetValueAsInt(iEntry, 3));
498
0
                    if (VSIFWriteL(reinterpret_cast<void *>(
499
0
                                       const_cast<char *>(oLine.c_str())),
500
0
                                   strlen(oLine), 1, fp) != 1)
501
0
                    {
502
0
                        CPLError(CE_Failure, CPLE_FileIO,
503
0
                                 "Error while write color table");
504
0
                        CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
505
0
                        return;
506
0
                    }
507
0
                }
508
0
            }
509
0
            else
510
0
            {
511
0
                for (int iColor = 0; iColor < poTable->GetColorEntryCount();
512
0
                     iColor++)
513
0
                {
514
0
                    GDALColorEntry sEntry;
515
0
                    poTable->GetColorEntryAsRGB(iColor, &sEntry);
516
517
                    // I wish we had a way to mark transparency.
518
0
                    CPLString oLine;
519
0
                    oLine.Printf("%3d %3d %3d %3d\n", iColor, sEntry.c1,
520
0
                                 sEntry.c2, sEntry.c3);
521
0
                    if (VSIFWriteL(reinterpret_cast<void *>(
522
0
                                       const_cast<char *>(oLine.c_str())),
523
0
                                   strlen(oLine), 1, fp) != 1)
524
0
                    {
525
0
                        CPLError(CE_Failure, CPLE_FileIO,
526
0
                                 "Error while write color table");
527
0
                        CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
528
0
                        return;
529
0
                    }
530
0
                }
531
0
            }
532
0
            if (VSIFCloseL(fp) != 0)
533
0
            {
534
0
                CPLError(CE_Failure, CPLE_FileIO,
535
0
                         "Error while write color table");
536
0
            }
537
0
        }
538
0
        else
539
0
        {
540
0
            CPLError(CE_Failure, CPLE_OpenFailed,
541
0
                     "Unable to create color file %s.", osCLRFilename.c_str());
542
0
        }
543
0
    }
544
0
    else
545
0
    {
546
0
        VSIUnlink(osCLRFilename);
547
0
    }
548
0
}
549
550
/************************************************************************/
551
/*                           SetSpatialRef()                            */
552
/************************************************************************/
553
554
CPLErr EHdrDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
555
556
0
{
557
    // Reset coordinate system on the dataset.
558
0
    m_oSRS.Clear();
559
0
    if (poSRS == nullptr)
560
0
        return CE_None;
561
562
0
    m_oSRS = *poSRS;
563
    // Convert to ESRI WKT.
564
0
    char *pszESRI_SRS = nullptr;
565
0
    const char *const apszOptions[] = {"FORMAT=WKT1_ESRI", nullptr};
566
0
    m_oSRS.exportToWkt(&pszESRI_SRS, apszOptions);
567
568
0
    if (pszESRI_SRS)
569
0
    {
570
        // Write to .prj file.
571
0
        CPLString osPrjFilename =
572
0
            CPLResetExtensionSafe(GetDescription(), "prj");
573
0
        VSILFILE *fp = VSIFOpenL(osPrjFilename.c_str(), "wt");
574
0
        if (fp != nullptr)
575
0
        {
576
0
            size_t nCount = VSIFWriteL(pszESRI_SRS, strlen(pszESRI_SRS), 1, fp);
577
0
            nCount += VSIFWriteL("\n", 1, 1, fp);
578
0
            if (VSIFCloseL(fp) != 0 || nCount != 2)
579
0
            {
580
0
                CPLFree(pszESRI_SRS);
581
0
                return CE_Failure;
582
0
            }
583
0
        }
584
585
0
        CPLFree(pszESRI_SRS);
586
0
    }
587
588
0
    return CE_None;
589
0
}
590
591
/************************************************************************/
592
/*                          GetGeoTransform()                           */
593
/************************************************************************/
594
595
CPLErr EHdrDataset::GetGeoTransform(double *padfTransform)
596
597
0
{
598
0
    if (bGotTransform)
599
0
    {
600
0
        memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
601
0
        return CE_None;
602
0
    }
603
604
0
    return GDALPamDataset::GetGeoTransform(padfTransform);
605
0
}
606
607
/************************************************************************/
608
/*                          SetGeoTransform()                           */
609
/************************************************************************/
610
611
CPLErr EHdrDataset::SetGeoTransform(double *padfGeoTransform)
612
613
0
{
614
    // We only support non-rotated images with info in the .HDR file.
615
0
    if (padfGeoTransform[2] != 0.0 || padfGeoTransform[4] != 0.0)
616
0
    {
617
0
        return GDALPamDataset::SetGeoTransform(padfGeoTransform);
618
0
    }
619
620
    // Record new geotransform.
621
0
    bGotTransform = true;
622
0
    memcpy(adfGeoTransform, padfGeoTransform, sizeof(double) * 6);
623
624
    // Strip out all old geotransform keywords from HDR records.
625
0
    for (int i = CSLCount(papszHDR) - 1; i >= 0; i--)
626
0
    {
627
0
        if (STARTS_WITH_CI(papszHDR[i], "ul") ||
628
0
            STARTS_WITH_CI(papszHDR[i] + 1, "ll") ||
629
0
            STARTS_WITH_CI(papszHDR[i], "cell") ||
630
0
            STARTS_WITH_CI(papszHDR[i] + 1, "dim"))
631
0
        {
632
0
            papszHDR = CSLRemoveStrings(papszHDR, i, 1, nullptr);
633
0
        }
634
0
    }
635
636
    // Set the transformation information.
637
0
    CPLString oValue;
638
639
0
    oValue.Printf("%.15g", adfGeoTransform[0] + adfGeoTransform[1] * 0.5);
640
0
    ResetKeyValue("ULXMAP", oValue);
641
642
0
    oValue.Printf("%.15g", adfGeoTransform[3] + adfGeoTransform[5] * 0.5);
643
0
    ResetKeyValue("ULYMAP", oValue);
644
645
0
    oValue.Printf("%.15g", adfGeoTransform[1]);
646
0
    ResetKeyValue("XDIM", oValue);
647
648
0
    oValue.Printf("%.15g", fabs(adfGeoTransform[5]));
649
0
    ResetKeyValue("YDIM", oValue);
650
651
0
    return CE_None;
652
0
}
653
654
/************************************************************************/
655
/*                             RewriteHDR()                             */
656
/************************************************************************/
657
658
CPLErr EHdrDataset::RewriteHDR()
659
660
0
{
661
0
    const CPLString osPath = CPLGetPathSafe(GetDescription());
662
0
    const CPLString osName = CPLGetBasenameSafe(GetDescription());
663
0
    CPLString osHDRFilename =
664
0
        CPLFormCIFilenameSafe(osPath, osName, osHeaderExt);
665
666
    // Write .hdr file.
667
0
    VSILFILE *fp = VSIFOpenL(osHDRFilename, "wt");
668
669
0
    if (fp == nullptr)
670
0
    {
671
0
        CPLError(CE_Failure, CPLE_OpenFailed, "Failed to rewrite .hdr file %s.",
672
0
                 osHDRFilename.c_str());
673
0
        return CE_Failure;
674
0
    }
675
676
0
    for (int i = 0; papszHDR[i] != nullptr; i++)
677
0
    {
678
0
        size_t nCount = VSIFWriteL(papszHDR[i], strlen(papszHDR[i]), 1, fp);
679
0
        nCount += VSIFWriteL("\n", 1, 1, fp);
680
0
        if (nCount != 2)
681
0
        {
682
0
            CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
683
0
            return CE_Failure;
684
0
        }
685
0
    }
686
687
0
    bHDRDirty = false;
688
689
0
    if (VSIFCloseL(fp) != 0)
690
0
        return CE_Failure;
691
692
0
    return CE_None;
693
0
}
694
695
/************************************************************************/
696
/*                             RewriteSTX()                             */
697
/************************************************************************/
698
699
CPLErr EHdrDataset::RewriteSTX() const
700
0
{
701
0
    const CPLString osPath = CPLGetPathSafe(GetDescription());
702
0
    const CPLString osName = CPLGetBasenameSafe(GetDescription());
703
0
    const CPLString osSTXFilename =
704
0
        CPLFormCIFilenameSafe(osPath, osName, "stx");
705
706
0
    VSILFILE *fp = VSIFOpenL(osSTXFilename, "wt");
707
0
    if (fp == nullptr)
708
0
    {
709
0
        CPLDebug("EHDR", "Failed to rewrite .stx file %s.",
710
0
                 osSTXFilename.c_str());
711
0
        return CE_Failure;
712
0
    }
713
714
0
    bool bOK = true;
715
0
    for (int i = 0; bOK && i < nBands; ++i)
716
0
    {
717
0
        EHdrRasterBand *poBand =
718
0
            reinterpret_cast<EHdrRasterBand *>(papoBands[i]);
719
0
        bOK &= VSIFPrintfL(fp, "%d %.10f %.10f ", i + 1, poBand->dfMin,
720
0
                           poBand->dfMax) >= 0;
721
0
        if (poBand->minmaxmeanstddev & HAS_MEAN_FLAG)
722
0
            bOK &= VSIFPrintfL(fp, "%.10f ", poBand->dfMean) >= 0;
723
0
        else
724
0
            bOK &= VSIFPrintfL(fp, "# ") >= 0;
725
726
0
        if (poBand->minmaxmeanstddev & HAS_STDDEV_FLAG)
727
0
            bOK &= VSIFPrintfL(fp, "%.10f\n", poBand->dfStdDev) >= 0;
728
0
        else
729
0
            bOK &= VSIFPrintfL(fp, "#\n") >= 0;
730
0
    }
731
732
0
    if (VSIFCloseL(fp) != 0)
733
0
        bOK = false;
734
735
0
    return bOK ? CE_None : CE_Failure;
736
0
}
737
738
/************************************************************************/
739
/*                              ReadSTX()                               */
740
/************************************************************************/
741
742
CPLErr EHdrDataset::ReadSTX() const
743
0
{
744
0
    const CPLString osPath = CPLGetPathSafe(GetDescription());
745
0
    const CPLString osName = CPLGetBasenameSafe(GetDescription());
746
0
    const CPLString osSTXFilename =
747
0
        CPLFormCIFilenameSafe(osPath, osName, "stx");
748
749
0
    VSILFILE *fp = VSIFOpenL(osSTXFilename, "rt");
750
0
    if (fp == nullptr)
751
0
        return CE_None;
752
753
0
    const char *pszLine = nullptr;
754
0
    while ((pszLine = CPLReadLineL(fp)) != nullptr)
755
0
    {
756
0
        char **papszTokens =
757
0
            CSLTokenizeStringComplex(pszLine, " \t", TRUE, FALSE);
758
0
        const int nTokens = CSLCount(papszTokens);
759
0
        if (nTokens >= 5)
760
0
        {
761
0
            const int i = atoi(papszTokens[0]);
762
0
            if (i > 0 && i <= nBands)
763
0
            {
764
0
                EHdrRasterBand *poBand =
765
0
                    reinterpret_cast<EHdrRasterBand *>(papoBands[i - 1]);
766
0
                poBand->dfMin = CPLAtof(papszTokens[1]);
767
0
                poBand->dfMax = CPLAtof(papszTokens[2]);
768
769
0
                int bNoDataSet = FALSE;
770
0
                const double dfNoData = poBand->GetNoDataValue(&bNoDataSet);
771
0
                if (bNoDataSet && dfNoData == poBand->dfMin)
772
0
                {
773
                    // Triggered by
774
                    // /vsicurl/http://eros.usgs.gov/archive/nslrsda/GeoTowns/HongKong/srtm/n22e113.zip/n22e113.bil
775
0
                    CPLDebug(
776
0
                        "EHDr",
777
0
                        "Ignoring .stx file where min == nodata. "
778
0
                        "The nodata value should not be taken into account "
779
0
                        "in minimum value computation.");
780
0
                    CSLDestroy(papszTokens);
781
0
                    papszTokens = nullptr;
782
0
                    break;
783
0
                }
784
785
0
                poBand->minmaxmeanstddev = HAS_MIN_FLAG | HAS_MAX_FLAG;
786
                // Reads optional mean and stddev.
787
0
                if (!EQUAL(papszTokens[3], "#"))
788
0
                {
789
0
                    poBand->dfMean = CPLAtof(papszTokens[3]);
790
0
                    poBand->minmaxmeanstddev |= HAS_MEAN_FLAG;
791
0
                }
792
0
                if (!EQUAL(papszTokens[4], "#"))
793
0
                {
794
0
                    poBand->dfStdDev = CPLAtof(papszTokens[4]);
795
0
                    poBand->minmaxmeanstddev |= HAS_STDDEV_FLAG;
796
0
                }
797
798
0
                if (nTokens >= 6 && !EQUAL(papszTokens[5], "#"))
799
0
                    poBand->SetMetadataItem("STRETCHMIN", papszTokens[5],
800
0
                                            "RENDERING_HINTS");
801
802
0
                if (nTokens >= 7 && !EQUAL(papszTokens[6], "#"))
803
0
                    poBand->SetMetadataItem("STRETCHMAX", papszTokens[6],
804
0
                                            "RENDERING_HINTS");
805
0
            }
806
0
        }
807
808
0
        CSLDestroy(papszTokens);
809
0
    }
810
811
0
    CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
812
813
0
    return CE_None;
814
0
}
815
816
/************************************************************************/
817
/*                      GetImageRepFilename()                           */
818
/************************************************************************/
819
820
// Check for IMAGE.REP (Spatiocarte Defense 1.0) or name_of_image.rep
821
// if it is a GIS-GeoSPOT image.
822
// For the specification of SPDF (in French), see
823
//   http://eden.ign.fr/download/pub/doc/emabgi/spdf10.pdf/download
824
825
CPLString EHdrDataset::GetImageRepFilename(const char *pszFilename)
826
0
{
827
828
0
    const CPLString osPath = CPLGetPathSafe(pszFilename);
829
0
    const CPLString osName = CPLGetBasenameSafe(pszFilename);
830
0
    CPLString osREPFilename = CPLFormCIFilenameSafe(osPath, osName, "rep");
831
832
0
    VSIStatBufL sStatBuf;
833
0
    if (VSIStatExL(osREPFilename.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG) == 0)
834
0
        return osREPFilename;
835
836
0
    if (EQUAL(CPLGetFilename(pszFilename), "imspatio.bil") ||
837
0
        EQUAL(CPLGetFilename(pszFilename), "haspatio.bil"))
838
0
    {
839
0
        CPLString osImageRepFilename(
840
0
            CPLFormCIFilenameSafe(osPath, "image", "rep"));
841
0
        if (VSIStatExL(osImageRepFilename, &sStatBuf, VSI_STAT_EXISTS_FLAG) ==
842
0
            0)
843
0
            return osImageRepFilename;
844
845
        // Try in the upper directories if not found in the BIL image directory.
846
0
        CPLString dirName(CPLGetDirnameSafe(osPath));
847
0
        if (CPLIsFilenameRelative(osPath.c_str()))
848
0
        {
849
0
            char *cwd = CPLGetCurrentDir();
850
0
            if (cwd)
851
0
            {
852
0
                dirName = CPLFormFilenameSafe(cwd, dirName.c_str(), nullptr);
853
0
                CPLFree(cwd);
854
0
            }
855
0
        }
856
0
        while (dirName[0] != 0 && EQUAL(dirName, ".") == FALSE &&
857
0
               EQUAL(dirName, "/") == FALSE)
858
0
        {
859
0
            osImageRepFilename =
860
0
                CPLFormCIFilenameSafe(dirName.c_str(), "image", "rep");
861
0
            if (VSIStatExL(osImageRepFilename.c_str(), &sStatBuf,
862
0
                           VSI_STAT_EXISTS_FLAG) == 0)
863
0
                return osImageRepFilename;
864
865
            // Don't try to recurse above the 'image' subdirectory.
866
0
            if (EQUAL(dirName, "image"))
867
0
            {
868
0
                break;
869
0
            }
870
0
            dirName = CPLString(CPLGetDirnameSafe(dirName));
871
0
        }
872
0
    }
873
0
    return CPLString();
874
0
}
875
876
/************************************************************************/
877
/*                            GetFileList()                             */
878
/************************************************************************/
879
880
char **EHdrDataset::GetFileList()
881
882
0
{
883
0
    const CPLString osPath = CPLGetPathSafe(GetDescription());
884
0
    const CPLString osName = CPLGetBasenameSafe(GetDescription());
885
886
    // Main data file, etc.
887
0
    char **papszFileList = GDALPamDataset::GetFileList();
888
889
    // Header file.
890
0
    CPLString osFilename = CPLFormCIFilenameSafe(osPath, osName, osHeaderExt);
891
0
    papszFileList = CSLAddString(papszFileList, osFilename);
892
893
    // Statistics file
894
0
    osFilename = CPLFormCIFilenameSafe(osPath, osName, "stx");
895
0
    VSIStatBufL sStatBuf;
896
0
    if (VSIStatExL(osFilename, &sStatBuf, VSI_STAT_EXISTS_FLAG) == 0)
897
0
        papszFileList = CSLAddString(papszFileList, osFilename);
898
899
    // color table file.
900
0
    osFilename = CPLFormCIFilenameSafe(osPath, osName, "clr");
901
0
    if (VSIStatExL(osFilename, &sStatBuf, VSI_STAT_EXISTS_FLAG) == 0)
902
0
        papszFileList = CSLAddString(papszFileList, osFilename);
903
904
    // projections file.
905
0
    osFilename = CPLFormCIFilenameSafe(osPath, osName, "prj");
906
0
    if (VSIStatExL(osFilename, &sStatBuf, VSI_STAT_EXISTS_FLAG) == 0)
907
0
        papszFileList = CSLAddString(papszFileList, osFilename);
908
909
0
    const CPLString imageRepFilename = GetImageRepFilename(GetDescription());
910
0
    if (!imageRepFilename.empty())
911
0
        papszFileList = CSLAddString(papszFileList, imageRepFilename.c_str());
912
913
0
    return papszFileList;
914
0
}
915
916
/************************************************************************/
917
/*                                Open()                                */
918
/************************************************************************/
919
920
GDALDataset *EHdrDataset::Open(GDALOpenInfo *poOpenInfo)
921
922
475k
{
923
475k
    return Open(poOpenInfo, true);
924
475k
}
925
926
GDALDataset *EHdrDataset::Open(GDALOpenInfo *poOpenInfo, bool bFileSizeCheck)
927
928
475k
{
929
    // Assume the caller is pointing to the binary (i.e. .bil) file.
930
475k
    if (poOpenInfo->nHeaderBytes < 2 || poOpenInfo->fpL == nullptr)
931
473k
        return nullptr;
932
933
    // Tear apart the filename to form a .HDR filename.
934
1.61k
    const CPLString osPath = CPLGetPathSafe(poOpenInfo->pszFilename);
935
1.61k
    const CPLString osName = CPLGetBasenameSafe(poOpenInfo->pszFilename);
936
937
1.61k
    const char *pszHeaderExt = "hdr";
938
1.61k
    if (poOpenInfo->IsExtensionEqualToCI("SRC") && osName.size() == 7 &&
939
1.61k
        (osName[0] == 'e' || osName[0] == 'E' || osName[0] == 'w' ||
940
0
         osName[0] == 'W') &&
941
1.61k
        (osName[4] == 'n' || osName[4] == 'N' || osName[4] == 's' ||
942
0
         osName[4] == 'S'))
943
0
    {
944
        // It is a GTOPO30 or SRTM30 source file, whose header extension is .sch
945
        // see http://dds.cr.usgs.gov/srtm/version1/SRTM30/GTOPO30_Documentation
946
0
        pszHeaderExt = "sch";
947
0
    }
948
949
1.61k
    char **papszSiblingFiles = poOpenInfo->GetSiblingFiles();
950
1.61k
    CPLString osHDRFilename;
951
1.61k
    if (papszSiblingFiles)
952
1.59k
    {
953
1.59k
        const int iFile = CSLFindString(
954
1.59k
            papszSiblingFiles,
955
1.59k
            CPLFormFilenameSafe(nullptr, osName, pszHeaderExt).c_str());
956
1.59k
        if (iFile < 0)  // Return if there is no corresponding .hdr file.
957
1.59k
            return nullptr;
958
959
0
        osHDRFilename =
960
0
            CPLFormFilenameSafe(osPath, papszSiblingFiles[iFile], nullptr);
961
0
    }
962
25
    else
963
25
    {
964
25
        osHDRFilename = CPLFormCIFilenameSafe(osPath, osName, pszHeaderExt);
965
25
    }
966
967
25
    const bool bSelectedHDR = EQUAL(osHDRFilename, poOpenInfo->pszFilename);
968
969
    // Do we have a .hdr file?
970
25
    VSILFILE *fp = VSIFOpenL(osHDRFilename, "r");
971
25
    if (fp == nullptr)
972
25
    {
973
25
        return nullptr;
974
25
    }
975
976
    // Is this file an ESRI header file?  Read a few lines of text
977
    // searching for something starting with nrows or ncols.
978
0
    int nRows = -1;
979
0
    int nCols = -1;
980
0
    int l_nBands = 1;
981
0
    int nSkipBytes = 0;
982
0
    double dfULXMap = 0.5;
983
0
    double dfULYMap = 0.5;
984
0
    double dfYLLCorner = -123.456;
985
0
    int bCenter = TRUE;
986
0
    double dfXDim = 1.0;
987
0
    double dfYDim = 1.0;
988
0
    double dfNoData = 0.0;
989
0
    int nLineCount = 0;
990
0
    int bNoDataSet = FALSE;
991
0
    GDALDataType eDataType = GDT_Byte;
992
0
    int nBits = -1;
993
0
    char chByteOrder = 'M';
994
0
    char chPixelType = 'N';  // Not defined.
995
0
    char szLayout[10] = "BIL";
996
0
    char **papszHDR = nullptr;
997
0
    int bHasInternalProjection = FALSE;
998
0
    int bHasMin = FALSE;
999
0
    int bHasMax = FALSE;
1000
0
    double dfMin = 0;
1001
0
    double dfMax = 0;
1002
1003
0
    const char *pszLine = nullptr;
1004
0
    while ((pszLine = CPLReadLineL(fp)) != nullptr)
1005
0
    {
1006
0
        nLineCount++;
1007
1008
0
        if (nLineCount > 50 || strlen(pszLine) > 1000)
1009
0
            break;
1010
1011
0
        papszHDR = CSLAddString(papszHDR, pszLine);
1012
1013
0
        char **papszTokens =
1014
0
            CSLTokenizeStringComplex(pszLine, " \t", TRUE, FALSE);
1015
0
        if (CSLCount(papszTokens) < 2)
1016
0
        {
1017
0
            CSLDestroy(papszTokens);
1018
0
            continue;
1019
0
        }
1020
1021
0
        if (EQUAL(papszTokens[0], "ncols"))
1022
0
        {
1023
0
            nCols = atoi(papszTokens[1]);
1024
0
        }
1025
0
        else if (EQUAL(papszTokens[0], "nrows"))
1026
0
        {
1027
0
            nRows = atoi(papszTokens[1]);
1028
0
        }
1029
0
        else if (EQUAL(papszTokens[0], "skipbytes"))
1030
0
        {
1031
0
            nSkipBytes = atoi(papszTokens[1]);
1032
0
        }
1033
0
        else if (EQUAL(papszTokens[0], "ulxmap") ||
1034
0
                 EQUAL(papszTokens[0], "xllcorner") ||
1035
0
                 EQUAL(papszTokens[0], "xllcenter"))
1036
0
        {
1037
0
            dfULXMap = CPLAtofM(papszTokens[1]);
1038
0
            if (EQUAL(papszTokens[0], "xllcorner"))
1039
0
                bCenter = FALSE;
1040
0
        }
1041
0
        else if (EQUAL(papszTokens[0], "ulymap"))
1042
0
        {
1043
0
            dfULYMap = CPLAtofM(papszTokens[1]);
1044
0
        }
1045
0
        else if (EQUAL(papszTokens[0], "yllcorner") ||
1046
0
                 EQUAL(papszTokens[0], "yllcenter"))
1047
0
        {
1048
0
            dfYLLCorner = CPLAtofM(papszTokens[1]);
1049
0
            if (EQUAL(papszTokens[0], "yllcorner"))
1050
0
                bCenter = FALSE;
1051
0
        }
1052
0
        else if (EQUAL(papszTokens[0], "xdim"))
1053
0
        {
1054
0
            dfXDim = CPLAtofM(papszTokens[1]);
1055
0
        }
1056
0
        else if (EQUAL(papszTokens[0], "ydim"))
1057
0
        {
1058
0
            dfYDim = CPLAtofM(papszTokens[1]);
1059
0
        }
1060
0
        else if (EQUAL(papszTokens[0], "cellsize"))
1061
0
        {
1062
0
            dfXDim = CPLAtofM(papszTokens[1]);
1063
0
            dfYDim = dfXDim;
1064
0
        }
1065
0
        else if (EQUAL(papszTokens[0], "nbands"))
1066
0
        {
1067
0
            l_nBands = atoi(papszTokens[1]);
1068
0
        }
1069
0
        else if (EQUAL(papszTokens[0], "layout"))
1070
0
        {
1071
0
            snprintf(szLayout, sizeof(szLayout), "%s", papszTokens[1]);
1072
0
        }
1073
0
        else if (EQUAL(papszTokens[0], "NODATA_value") ||
1074
0
                 EQUAL(papszTokens[0], "NODATA"))
1075
0
        {
1076
0
            dfNoData = CPLAtofM(papszTokens[1]);
1077
0
            bNoDataSet = TRUE;
1078
0
        }
1079
0
        else if (EQUAL(papszTokens[0], "NBITS"))
1080
0
        {
1081
0
            nBits = atoi(papszTokens[1]);
1082
0
        }
1083
0
        else if (EQUAL(papszTokens[0], "PIXELTYPE"))
1084
0
        {
1085
0
            chPixelType = static_cast<char>(
1086
0
                toupper(static_cast<unsigned char>(papszTokens[1][0])));
1087
0
        }
1088
0
        else if (EQUAL(papszTokens[0], "byteorder"))
1089
0
        {
1090
0
            chByteOrder = static_cast<char>(
1091
0
                toupper(static_cast<unsigned char>(papszTokens[1][0])));
1092
0
        }
1093
1094
        // http://www.worldclim.org/futdown.htm have the projection extensions
1095
0
        else if (EQUAL(papszTokens[0], "Projection"))
1096
0
        {
1097
0
            bHasInternalProjection = TRUE;
1098
0
        }
1099
0
        else if (EQUAL(papszTokens[0], "MinValue") ||
1100
0
                 EQUAL(papszTokens[0], "MIN_VALUE"))
1101
0
        {
1102
0
            dfMin = CPLAtofM(papszTokens[1]);
1103
0
            bHasMin = TRUE;
1104
0
        }
1105
0
        else if (EQUAL(papszTokens[0], "MaxValue") ||
1106
0
                 EQUAL(papszTokens[0], "MAX_VALUE"))
1107
0
        {
1108
0
            dfMax = CPLAtofM(papszTokens[1]);
1109
0
            bHasMax = TRUE;
1110
0
        }
1111
1112
0
        CSLDestroy(papszTokens);
1113
0
    }
1114
1115
0
    CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
1116
1117
    // Did we get the required keywords?  If not, return with this never having
1118
    // been considered to be a match. This isn't an error!
1119
0
    if (nRows == -1 || nCols == -1)
1120
0
    {
1121
0
        CSLDestroy(papszHDR);
1122
0
        return nullptr;
1123
0
    }
1124
1125
0
    if (!GDALCheckDatasetDimensions(nCols, nRows) ||
1126
0
        !GDALCheckBandCount(l_nBands, FALSE))
1127
0
    {
1128
0
        CSLDestroy(papszHDR);
1129
0
        return nullptr;
1130
0
    }
1131
1132
    // Has the caller selected the .hdr file to open?
1133
0
    if (bSelectedHDR)
1134
0
    {
1135
0
        CPLError(CE_Failure, CPLE_AppDefined,
1136
0
                 "The selected file is an ESRI BIL header file, but to "
1137
0
                 "open ESRI BIL datasets, the data file should be selected "
1138
0
                 "instead of the .hdr file.  Please try again selecting "
1139
0
                 "the data file (often with the extension .bil) corresponding "
1140
0
                 "to the header file: %s",
1141
0
                 poOpenInfo->pszFilename);
1142
0
        CSLDestroy(papszHDR);
1143
0
        return nullptr;
1144
0
    }
1145
1146
    // If we aren't sure of the file type, check the data file size.  If it is 4
1147
    // bytes or more per pixel then we assume it is floating point data.
1148
0
    if (nBits == -1 && chPixelType == 'N')
1149
0
    {
1150
0
        VSIStatBufL sStatBuf;
1151
0
        if (VSIStatL(poOpenInfo->pszFilename, &sStatBuf) == 0)
1152
0
        {
1153
0
            const size_t nBytes = static_cast<size_t>(sStatBuf.st_size / nCols /
1154
0
                                                      nRows / l_nBands);
1155
0
            if (nBytes > 0 && nBytes != 3)
1156
0
                nBits = static_cast<int>(nBytes * 8);
1157
1158
0
            if (nBytes == 4)
1159
0
                chPixelType = 'F';
1160
0
        }
1161
0
    }
1162
1163
    // If the extension is FLT it is likely a floating point file.
1164
0
    if (chPixelType == 'N')
1165
0
    {
1166
0
        if (poOpenInfo->IsExtensionEqualToCI("FLT"))
1167
0
            chPixelType = 'F';
1168
0
    }
1169
1170
    // If we have a negative nodata value, assume that the
1171
    // pixel type is signed. This is necessary for datasets from
1172
    // http://www.worldclim.org/futdown.htm
1173
1174
0
    if (bNoDataSet && dfNoData < 0 && chPixelType == 'N')
1175
0
    {
1176
0
        chPixelType = 'S';
1177
0
    }
1178
1179
0
    auto poDS = std::make_unique<EHdrDataset>();
1180
1181
0
    poDS->osHeaderExt = pszHeaderExt;
1182
1183
0
    poDS->nRasterXSize = nCols;
1184
0
    poDS->nRasterYSize = nRows;
1185
0
    poDS->papszHDR = papszHDR;
1186
0
    std::swap(poDS->fpImage, poOpenInfo->fpL);
1187
0
    poDS->eAccess = poOpenInfo->eAccess;
1188
1189
    // Figure out the data type.
1190
0
    if (nBits == 16)
1191
0
    {
1192
0
        if (chPixelType == 'S')
1193
0
            eDataType = GDT_Int16;
1194
0
        else
1195
0
            eDataType = GDT_UInt16;  // Default
1196
0
    }
1197
0
    else if (nBits == 32)
1198
0
    {
1199
0
        if (chPixelType == 'S')
1200
0
            eDataType = GDT_Int32;
1201
0
        else if (chPixelType == 'F')
1202
0
            eDataType = GDT_Float32;
1203
0
        else
1204
0
            eDataType = GDT_UInt32;  // Default
1205
0
    }
1206
0
    else if (nBits >= 1 && nBits <= 8)
1207
0
    {
1208
0
        if (chPixelType == 'S')
1209
0
            eDataType = GDT_Int8;
1210
0
        else
1211
0
            eDataType = GDT_Byte;
1212
0
        nBits = 8;
1213
0
    }
1214
0
    else if (nBits == -1)
1215
0
    {
1216
0
        if (chPixelType == 'F')
1217
0
        {
1218
0
            eDataType = GDT_Float32;
1219
0
            nBits = 32;
1220
0
        }
1221
0
        else
1222
0
        {
1223
0
            eDataType = GDT_Byte;
1224
0
            nBits = 8;
1225
0
        }
1226
0
    }
1227
0
    else
1228
0
    {
1229
0
        CPLError(CE_Failure, CPLE_NotSupported,
1230
0
                 "EHdr driver does not support %d NBITS value.", nBits);
1231
0
        return nullptr;
1232
0
    }
1233
1234
    // Compute the line offset.
1235
0
    const int nItemSize = GDALGetDataTypeSizeBytes(eDataType);
1236
0
    CPLAssert(nItemSize != 0);
1237
0
    CPLAssert(l_nBands != 0);
1238
1239
0
    int nPixelOffset = 0;
1240
0
    int nLineOffset = 0;
1241
0
    vsi_l_offset nBandOffset = 0;
1242
1243
0
    if (EQUAL(szLayout, "BIP"))
1244
0
    {
1245
0
        if (nCols > std::numeric_limits<int>::max() / (nItemSize * l_nBands))
1246
0
        {
1247
0
            CPLError(CE_Failure, CPLE_AppDefined, "Int overflow occurred.");
1248
0
            return nullptr;
1249
0
        }
1250
0
        nPixelOffset = nItemSize * l_nBands;
1251
0
        nLineOffset = nPixelOffset * nCols;
1252
0
        nBandOffset = static_cast<vsi_l_offset>(nItemSize);
1253
0
    }
1254
0
    else if (EQUAL(szLayout, "BSQ"))
1255
0
    {
1256
0
        if (nCols > std::numeric_limits<int>::max() / nItemSize)
1257
0
        {
1258
0
            CPLError(CE_Failure, CPLE_AppDefined, "Int overflow occurred.");
1259
0
            return nullptr;
1260
0
        }
1261
0
        nPixelOffset = nItemSize;
1262
0
        nLineOffset = nPixelOffset * nCols;
1263
0
        nBandOffset = static_cast<vsi_l_offset>(nLineOffset) * nRows;
1264
0
    }
1265
0
    else
1266
0
    {
1267
        // Assume BIL.
1268
0
        if (nCols > std::numeric_limits<int>::max() / (nItemSize * l_nBands))
1269
0
        {
1270
0
            CPLError(CE_Failure, CPLE_AppDefined, "Int overflow occurred.");
1271
0
            return nullptr;
1272
0
        }
1273
0
        nPixelOffset = nItemSize;
1274
0
        nLineOffset = nItemSize * l_nBands * nCols;
1275
0
        nBandOffset = static_cast<vsi_l_offset>(nItemSize) * nCols;
1276
0
    }
1277
1278
0
    if (nBits >= 8 && bFileSizeCheck &&
1279
0
        !RAWDatasetCheckMemoryUsage(
1280
0
            poDS->nRasterXSize, poDS->nRasterYSize, l_nBands, nItemSize,
1281
0
            nPixelOffset, nLineOffset, nSkipBytes, nBandOffset, poDS->fpImage))
1282
0
    {
1283
0
        return nullptr;
1284
0
    }
1285
1286
0
    poDS->SetDescription(poOpenInfo->pszFilename);
1287
0
    poDS->PamInitialize();
1288
1289
    // Create band information objects.
1290
0
    for (int i = 0; i < l_nBands; i++)
1291
0
    {
1292
0
        auto poBand = std::make_unique<EHdrRasterBand>(
1293
0
            poDS.get(), i + 1, poDS->fpImage, nSkipBytes + nBandOffset * i,
1294
0
            nPixelOffset, nLineOffset, eDataType,
1295
0
            chByteOrder == 'I' || chByteOrder == 'L'
1296
0
                ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
1297
0
                : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN,
1298
0
            nBits);
1299
0
        if (!poBand->IsValid())
1300
0
            return nullptr;
1301
1302
0
        poBand->bNoDataSet = bNoDataSet;
1303
0
        poBand->dfNoData = dfNoData;
1304
1305
0
        if (bHasMin && bHasMax)
1306
0
        {
1307
0
            poBand->dfMin = dfMin;
1308
0
            poBand->dfMax = dfMax;
1309
0
            poBand->minmaxmeanstddev = HAS_MIN_FLAG | HAS_MAX_FLAG;
1310
0
        }
1311
1312
0
        poDS->SetBand(i + 1, std::move(poBand));
1313
0
    }
1314
1315
    // If we didn't get bounds in the .hdr, look for a worldfile.
1316
0
    if (dfYLLCorner != -123.456)
1317
0
    {
1318
0
        if (bCenter)
1319
0
            dfULYMap = dfYLLCorner + (nRows - 1) * dfYDim;
1320
0
        else
1321
0
            dfULYMap = dfYLLCorner + nRows * dfYDim;
1322
0
    }
1323
1324
0
    if (dfULXMap != 0.5 || dfULYMap != 0.5 || dfXDim != 1.0 || dfYDim != 1.0)
1325
0
    {
1326
0
        poDS->bGotTransform = true;
1327
1328
0
        if (bCenter)
1329
0
        {
1330
0
            poDS->adfGeoTransform[0] = dfULXMap - dfXDim * 0.5;
1331
0
            poDS->adfGeoTransform[1] = dfXDim;
1332
0
            poDS->adfGeoTransform[2] = 0.0;
1333
0
            poDS->adfGeoTransform[3] = dfULYMap + dfYDim * 0.5;
1334
0
            poDS->adfGeoTransform[4] = 0.0;
1335
0
            poDS->adfGeoTransform[5] = -dfYDim;
1336
0
        }
1337
0
        else
1338
0
        {
1339
0
            poDS->adfGeoTransform[0] = dfULXMap;
1340
0
            poDS->adfGeoTransform[1] = dfXDim;
1341
0
            poDS->adfGeoTransform[2] = 0.0;
1342
0
            poDS->adfGeoTransform[3] = dfULYMap;
1343
0
            poDS->adfGeoTransform[4] = 0.0;
1344
0
            poDS->adfGeoTransform[5] = -dfYDim;
1345
0
        }
1346
0
    }
1347
1348
0
    if (!poDS->bGotTransform)
1349
0
        poDS->bGotTransform = CPL_TO_BOOL(GDALReadWorldFile(
1350
0
            poOpenInfo->pszFilename, nullptr, poDS->adfGeoTransform));
1351
1352
0
    if (!poDS->bGotTransform)
1353
0
        poDS->bGotTransform = CPL_TO_BOOL(GDALReadWorldFile(
1354
0
            poOpenInfo->pszFilename, "wld", poDS->adfGeoTransform));
1355
1356
    // Check for a .prj file.
1357
0
    std::string osPrjFilename = CPLFormCIFilenameSafe(osPath, osName, "prj");
1358
1359
0
    fp = VSIFOpenL(osPrjFilename.c_str(), "r");
1360
1361
    // .hdr files from http://www.worldclim.org/futdown.htm have the projection
1362
    // info in the .hdr file itself.
1363
0
    if (fp == nullptr && bHasInternalProjection)
1364
0
    {
1365
0
        osPrjFilename = std::move(osHDRFilename);
1366
0
        fp = VSIFOpenL(osPrjFilename.c_str(), "r");
1367
0
    }
1368
1369
0
    if (fp != nullptr)
1370
0
    {
1371
0
        CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
1372
0
        fp = nullptr;
1373
1374
0
        char **papszLines = CSLLoad(osPrjFilename.c_str());
1375
1376
0
        if (poDS->m_oSRS.importFromESRI(papszLines) == OGRERR_NONE)
1377
0
        {
1378
            // If geographic values are in seconds, we must transform.
1379
            // Is there a code for minutes too?
1380
0
            char szResult[80] = {'\0'};
1381
0
            if (poDS->m_oSRS.IsGeographic() &&
1382
0
                EQUAL(OSR_GDS(szResult, sizeof(szResult), papszLines, "Units",
1383
0
                              ""),
1384
0
                      "DS"))
1385
0
            {
1386
0
                poDS->adfGeoTransform[0] /= 3600.0;
1387
0
                poDS->adfGeoTransform[1] /= 3600.0;
1388
0
                poDS->adfGeoTransform[2] /= 3600.0;
1389
0
                poDS->adfGeoTransform[3] /= 3600.0;
1390
0
                poDS->adfGeoTransform[4] /= 3600.0;
1391
0
                poDS->adfGeoTransform[5] /= 3600.0;
1392
0
            }
1393
0
        }
1394
0
        else
1395
0
        {
1396
0
            poDS->m_oSRS.Clear();
1397
0
        }
1398
1399
0
        CSLDestroy(papszLines);
1400
0
    }
1401
0
    else
1402
0
    {
1403
        // Check for IMAGE.REP (Spatiocarte Defense 1.0) or name_of_image.rep
1404
        // if it is a GIS-GeoSPOT image
1405
        // For the specification of SPDF (in French), see
1406
        //   http://eden.ign.fr/download/pub/doc/emabgi/spdf10.pdf/download
1407
0
        const CPLString szImageRepFilename =
1408
0
            GetImageRepFilename(poOpenInfo->pszFilename);
1409
0
        if (!szImageRepFilename.empty())
1410
0
        {
1411
0
            fp = VSIFOpenL(szImageRepFilename.c_str(), "r");
1412
0
        }
1413
0
        if (fp != nullptr)
1414
0
        {
1415
0
            bool bUTM = false;
1416
0
            bool bWGS84 = false;
1417
0
            int bNorth = FALSE;
1418
0
            bool bSouth = false;
1419
0
            int utmZone = 0;
1420
1421
0
            while ((pszLine = CPLReadLineL(fp)) != nullptr)
1422
0
            {
1423
0
                if (STARTS_WITH(pszLine, "PROJ_ID") && strstr(pszLine, "UTM"))
1424
0
                {
1425
0
                    bUTM = true;
1426
0
                }
1427
0
                else if (STARTS_WITH(pszLine, "PROJ_ZONE"))
1428
0
                {
1429
0
                    const char *c = strchr(pszLine, '"');
1430
0
                    if (c)
1431
0
                    {
1432
0
                        c++;
1433
0
                        if (*c >= '0' && *c <= '9')
1434
0
                        {
1435
0
                            utmZone = atoi(c);
1436
0
                            if (utmZone >= 1 && utmZone <= 60)
1437
0
                            {
1438
0
                                if (strstr(pszLine, "Nord") ||
1439
0
                                    strstr(pszLine, "NORD"))
1440
0
                                {
1441
0
                                    bNorth = TRUE;
1442
0
                                }
1443
0
                                else if (strstr(pszLine, "Sud") ||
1444
0
                                         strstr(pszLine, "SUD"))
1445
0
                                {
1446
0
                                    bSouth = true;
1447
0
                                }
1448
0
                            }
1449
0
                        }
1450
0
                    }
1451
0
                }
1452
0
                else if (STARTS_WITH(pszLine, "PROJ_CODE") &&
1453
0
                         strstr(pszLine, "FR-MINDEF"))
1454
0
                {
1455
0
                    const char *c = strchr(pszLine, 'A');
1456
0
                    if (c)
1457
0
                    {
1458
0
                        c++;
1459
0
                        if (*c >= '0' && *c <= '9')
1460
0
                        {
1461
0
                            utmZone = atoi(c);
1462
0
                            if (utmZone >= 1 && utmZone <= 60)
1463
0
                            {
1464
0
                                if (c[1] == 'N' ||
1465
0
                                    (c[1] != '\0' && c[2] == 'N'))
1466
0
                                {
1467
0
                                    bNorth = TRUE;
1468
0
                                }
1469
0
                                else if (c[1] == 'S' ||
1470
0
                                         (c[1] != '\0' && c[2] == 'S'))
1471
0
                                {
1472
0
                                    bSouth = true;
1473
0
                                }
1474
0
                            }
1475
0
                        }
1476
0
                    }
1477
0
                }
1478
0
                else if (STARTS_WITH(pszLine, "HORIZ_DATUM") &&
1479
0
                         (strstr(pszLine, "WGS 84") ||
1480
0
                          strstr(pszLine, "WGS84")))
1481
0
                {
1482
0
                    bWGS84 = true;
1483
0
                }
1484
0
                else if (STARTS_WITH(pszLine, "MAP_NUMBER"))
1485
0
                {
1486
0
                    const char *c = strchr(pszLine, '"');
1487
0
                    if (c)
1488
0
                    {
1489
0
                        char *pszMapNumber = CPLStrdup(c + 1);
1490
0
                        char *c2 = strchr(pszMapNumber, '"');
1491
0
                        if (c2)
1492
0
                            *c2 = 0;
1493
0
                        poDS->SetMetadataItem("SPDF_MAP_NUMBER", pszMapNumber);
1494
0
                        CPLFree(pszMapNumber);
1495
0
                    }
1496
0
                }
1497
0
                else if (STARTS_WITH(pszLine, "PRODUCTION_DATE"))
1498
0
                {
1499
0
                    const char *c = pszLine + strlen("PRODUCTION_DATE");
1500
0
                    while (*c == ' ')
1501
0
                        c++;
1502
0
                    if (*c)
1503
0
                    {
1504
0
                        poDS->SetMetadataItem("SPDF_PRODUCTION_DATE", c);
1505
0
                    }
1506
0
                }
1507
0
            }
1508
1509
0
            CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
1510
1511
0
            if (utmZone >= 1 && utmZone <= 60 && bUTM && bWGS84 &&
1512
0
                (bNorth || bSouth))
1513
0
            {
1514
0
                char projCSStr[64] = {'\0'};
1515
0
                snprintf(projCSStr, sizeof(projCSStr), "WGS 84 / UTM zone %d%c",
1516
0
                         utmZone, (bNorth) ? 'N' : 'S');
1517
1518
0
                poDS->m_oSRS.SetProjCS(projCSStr);
1519
0
                poDS->m_oSRS.SetWellKnownGeogCS("WGS84");
1520
0
                poDS->m_oSRS.SetUTM(utmZone, bNorth);
1521
0
                poDS->m_oSRS.SetAuthority("PROJCS", "EPSG",
1522
0
                                          (bNorth ? 32600 : 32700) + utmZone);
1523
0
                poDS->m_oSRS.AutoIdentifyEPSG();
1524
0
            }
1525
0
            else
1526
0
            {
1527
0
                CPLError(CE_Warning, CPLE_NotSupported,
1528
0
                         "Cannot retrieve projection from IMAGE.REP");
1529
0
            }
1530
0
        }
1531
0
    }
1532
1533
    // Check for a color table.
1534
0
    const std::string osCLRFilename =
1535
0
        CPLFormCIFilenameSafe(osPath, osName, "clr");
1536
1537
    // Only read the .clr for byte, int16 or uint16 bands.
1538
0
    if (nItemSize <= 2)
1539
0
        fp = VSIFOpenL(osCLRFilename.c_str(), "r");
1540
0
    else
1541
0
        fp = nullptr;
1542
1543
0
    if (fp != nullptr)
1544
0
    {
1545
0
        std::shared_ptr<GDALRasterAttributeTable> poRat(
1546
0
            new GDALDefaultRasterAttributeTable());
1547
0
        poRat->CreateColumn("Value", GFT_Integer, GFU_Generic);
1548
0
        poRat->CreateColumn("Red", GFT_Integer, GFU_Red);
1549
0
        poRat->CreateColumn("Green", GFT_Integer, GFU_Green);
1550
0
        poRat->CreateColumn("Blue", GFT_Integer, GFU_Blue);
1551
1552
0
        poDS->m_poColorTable.reset(new GDALColorTable());
1553
1554
0
        bool bHasFoundNonCTValues = false;
1555
0
        int nRatRow = 0;
1556
1557
0
        while (true)
1558
0
        {
1559
0
            pszLine = CPLReadLineL(fp);
1560
0
            if (!pszLine)
1561
0
                break;
1562
1563
0
            if (*pszLine == '#' || *pszLine == '!')
1564
0
                continue;
1565
1566
0
            char **papszValues =
1567
0
                CSLTokenizeString2(pszLine, "\t ", CSLT_HONOURSTRINGS);
1568
1569
0
            if (CSLCount(papszValues) >= 4)
1570
0
            {
1571
0
                const int nIndex = atoi(papszValues[0]);
1572
0
                poRat->SetValue(nRatRow, 0, nIndex);
1573
0
                poRat->SetValue(nRatRow, 1, atoi(papszValues[1]));
1574
0
                poRat->SetValue(nRatRow, 2, atoi(papszValues[2]));
1575
0
                poRat->SetValue(nRatRow, 3, atoi(papszValues[3]));
1576
0
                nRatRow++;
1577
1578
0
                if (nIndex >= 0 && nIndex < 65536)
1579
0
                {
1580
0
                    const GDALColorEntry oEntry = {
1581
0
                        static_cast<short>(atoi(papszValues[1])),  // Red
1582
0
                        static_cast<short>(atoi(papszValues[2])),  // Green
1583
0
                        static_cast<short>(atoi(papszValues[3])),  // Blue
1584
0
                        255};
1585
1586
0
                    poDS->m_poColorTable->SetColorEntry(nIndex, &oEntry);
1587
0
                }
1588
0
                else
1589
0
                {
1590
                    // Negative values are valid. At least we can find use of
1591
                    // them here:
1592
                    //   http://www.ngdc.noaa.gov/mgg/topo/elev/esri/clr/
1593
                    // But, there's no way of representing them with GDAL color
1594
                    // table model.
1595
0
                    if (!bHasFoundNonCTValues)
1596
0
                        CPLDebug("EHdr", "Ignoring color index : %d", nIndex);
1597
0
                    bHasFoundNonCTValues = true;
1598
0
                }
1599
0
            }
1600
1601
0
            CSLDestroy(papszValues);
1602
0
        }
1603
1604
0
        CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
1605
1606
0
        if (bHasFoundNonCTValues)
1607
0
        {
1608
0
            poDS->m_poRAT.swap(poRat);
1609
0
        }
1610
1611
0
        for (int i = 1; i <= poDS->nBands; i++)
1612
0
        {
1613
0
            EHdrRasterBand *poBand =
1614
0
                cpl::down_cast<EHdrRasterBand *>(poDS->GetRasterBand(i));
1615
0
            poBand->m_poColorTable = poDS->m_poColorTable;
1616
0
            poBand->m_poRAT = poDS->m_poRAT;
1617
0
            poBand->SetColorInterpretation(GCI_PaletteIndex);
1618
0
        }
1619
1620
0
        poDS->bCLRDirty = false;
1621
0
    }
1622
1623
    // Read statistics (.STX).
1624
0
    poDS->ReadSTX();
1625
1626
    // Initialize any PAM information.
1627
0
    poDS->TryLoadXML();
1628
1629
    // Check for overviews.
1630
0
    poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
1631
1632
0
    return poDS.release();
1633
0
}
1634
1635
/************************************************************************/
1636
/*                               Create()                               */
1637
/************************************************************************/
1638
1639
GDALDataset *EHdrDataset::Create(const char *pszFilename, int nXSize,
1640
                                 int nYSize, int nBandsIn, GDALDataType eType,
1641
                                 char **papszParamList)
1642
1643
0
{
1644
    // Verify input options.
1645
0
    if (nBandsIn <= 0)
1646
0
    {
1647
0
        CPLError(CE_Failure, CPLE_NotSupported,
1648
0
                 "EHdr driver does not support %d bands.", nBandsIn);
1649
0
        return nullptr;
1650
0
    }
1651
1652
0
    if (eType != GDT_Byte && eType != GDT_Int8 && eType != GDT_Float32 &&
1653
0
        eType != GDT_UInt16 && eType != GDT_Int16 && eType != GDT_Int32 &&
1654
0
        eType != GDT_UInt32)
1655
0
    {
1656
0
        CPLError(CE_Failure, CPLE_AppDefined,
1657
0
                 "Attempt to create ESRI .hdr labelled dataset with an illegal"
1658
0
                 "data type (%s).",
1659
0
                 GDALGetDataTypeName(eType));
1660
1661
0
        return nullptr;
1662
0
    }
1663
1664
    // Try to create the file.
1665
0
    VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
1666
1667
0
    if (fp == nullptr)
1668
0
    {
1669
0
        CPLError(CE_Failure, CPLE_OpenFailed,
1670
0
                 "Attempt to create file `%s' failed.", pszFilename);
1671
0
        return nullptr;
1672
0
    }
1673
1674
    // Just write out a couple of bytes to establish the binary
1675
    // file, and then close it.
1676
0
    bool bOK = VSIFWriteL(reinterpret_cast<void *>(const_cast<char *>("\0\0")),
1677
0
                          2, 1, fp) == 1;
1678
0
    if (VSIFCloseL(fp) != 0)
1679
0
        bOK = false;
1680
0
    fp = nullptr;
1681
0
    if (!bOK)
1682
0
        return nullptr;
1683
1684
    // Create the hdr filename.
1685
0
    char *const pszHdrFilename =
1686
0
        CPLStrdup(CPLResetExtensionSafe(pszFilename, "hdr").c_str());
1687
1688
    // Open the file.
1689
0
    fp = VSIFOpenL(pszHdrFilename, "wt");
1690
0
    if (fp == nullptr)
1691
0
    {
1692
0
        CPLError(CE_Failure, CPLE_OpenFailed,
1693
0
                 "Attempt to create file `%s' failed.", pszHdrFilename);
1694
0
        CPLFree(pszHdrFilename);
1695
0
        return nullptr;
1696
0
    }
1697
1698
    // Decide how many bits the file should have.
1699
0
    int nBits = GDALGetDataTypeSize(eType);
1700
1701
0
    if (CSLFetchNameValue(papszParamList, "NBITS") != nullptr)
1702
0
        nBits = atoi(CSLFetchNameValue(papszParamList, "NBITS"));
1703
1704
0
    const int nRowBytes = (nBits * nXSize + 7) / 8;
1705
1706
    // Check for signed byte.
1707
0
    const char *pszPixelType = CSLFetchNameValue(papszParamList, "PIXELTYPE");
1708
0
    if (pszPixelType == nullptr)
1709
0
        pszPixelType = "";
1710
1711
    // Write out the raw definition for the dataset as a whole.
1712
0
    bOK &= VSIFPrintfL(fp, "BYTEORDER      I\n") >= 0;
1713
0
    bOK &= VSIFPrintfL(fp, "LAYOUT         BIL\n") >= 0;
1714
0
    bOK &= VSIFPrintfL(fp, "NROWS          %d\n", nYSize) >= 0;
1715
0
    bOK &= VSIFPrintfL(fp, "NCOLS          %d\n", nXSize) >= 0;
1716
0
    bOK &= VSIFPrintfL(fp, "NBANDS         %d\n", nBandsIn) >= 0;
1717
0
    bOK &= VSIFPrintfL(fp, "NBITS          %d\n", nBits) >= 0;
1718
0
    bOK &= VSIFPrintfL(fp, "BANDROWBYTES   %d\n", nRowBytes) >= 0;
1719
0
    bOK &= VSIFPrintfL(fp, "TOTALROWBYTES  %d\n", nRowBytes * nBandsIn) >= 0;
1720
1721
0
    if (eType == GDT_Float32)
1722
0
        bOK &= VSIFPrintfL(fp, "PIXELTYPE      FLOAT\n") >= 0;
1723
0
    else if (eType == GDT_Int8 || eType == GDT_Int16 || eType == GDT_Int32)
1724
0
        bOK &= VSIFPrintfL(fp, "PIXELTYPE      SIGNEDINT\n") >= 0;
1725
0
    else if (eType == GDT_Byte && EQUAL(pszPixelType, "SIGNEDBYTE"))
1726
0
        bOK &= VSIFPrintfL(fp, "PIXELTYPE      SIGNEDINT\n") >= 0;
1727
0
    else
1728
0
        bOK &= VSIFPrintfL(fp, "PIXELTYPE      UNSIGNEDINT\n") >= 0;
1729
1730
0
    if (VSIFCloseL(fp) != 0)
1731
0
        bOK = false;
1732
1733
0
    CPLFree(pszHdrFilename);
1734
1735
0
    if (!bOK)
1736
0
        return nullptr;
1737
1738
0
    GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
1739
0
    return Open(&oOpenInfo, false);
1740
0
}
1741
1742
/************************************************************************/
1743
/*                             CreateCopy()                             */
1744
/************************************************************************/
1745
1746
GDALDataset *EHdrDataset::CreateCopy(const char *pszFilename,
1747
                                     GDALDataset *poSrcDS, int bStrict,
1748
                                     char **papszOptions,
1749
                                     GDALProgressFunc pfnProgress,
1750
                                     void *pProgressData)
1751
1752
0
{
1753
0
    const int nBands = poSrcDS->GetRasterCount();
1754
0
    if (nBands == 0)
1755
0
    {
1756
0
        CPLError(CE_Failure, CPLE_NotSupported,
1757
0
                 "EHdr driver does not support source dataset without any "
1758
0
                 "bands.");
1759
0
        return nullptr;
1760
0
    }
1761
1762
0
    char **papszAdjustedOptions = CSLDuplicate(papszOptions);
1763
1764
    // Ensure we pass on NBITS and PIXELTYPE structure information.
1765
0
    auto poSrcBand = poSrcDS->GetRasterBand(1);
1766
0
    if (poSrcBand->GetMetadataItem("NBITS", "IMAGE_STRUCTURE") != nullptr &&
1767
0
        CSLFetchNameValue(papszOptions, "NBITS") == nullptr)
1768
0
    {
1769
0
        papszAdjustedOptions = CSLSetNameValue(
1770
0
            papszAdjustedOptions, "NBITS",
1771
0
            poSrcBand->GetMetadataItem("NBITS", "IMAGE_STRUCTURE"));
1772
0
    }
1773
1774
0
    if (poSrcBand->GetRasterDataType() == GDT_Byte &&
1775
0
        CSLFetchNameValue(papszOptions, "PIXELTYPE") == nullptr)
1776
0
    {
1777
0
        poSrcBand->EnablePixelTypeSignedByteWarning(false);
1778
0
        const char *pszPixelType =
1779
0
            poSrcBand->GetMetadataItem("PIXELTYPE", "IMAGE_STRUCTURE");
1780
0
        poSrcBand->EnablePixelTypeSignedByteWarning(true);
1781
0
        if (pszPixelType != nullptr)
1782
0
        {
1783
0
            papszAdjustedOptions = CSLSetNameValue(papszAdjustedOptions,
1784
0
                                                   "PIXELTYPE", pszPixelType);
1785
0
        }
1786
0
    }
1787
1788
    // Proceed with normal copying using the default createcopy  operators.
1789
0
    GDALDriver *poDriver =
1790
0
        reinterpret_cast<GDALDriver *>(GDALGetDriverByName("EHdr"));
1791
1792
0
    GDALDataset *poOutDS = poDriver->DefaultCreateCopy(
1793
0
        pszFilename, poSrcDS, bStrict, papszAdjustedOptions, pfnProgress,
1794
0
        pProgressData);
1795
0
    CSLDestroy(papszAdjustedOptions);
1796
1797
0
    if (poOutDS != nullptr)
1798
0
        poOutDS->FlushCache(false);
1799
1800
0
    return poOutDS;
1801
0
}
1802
1803
/************************************************************************/
1804
/*                        GetNoDataValue()                              */
1805
/************************************************************************/
1806
1807
double EHdrRasterBand::GetNoDataValue(int *pbSuccess)
1808
0
{
1809
0
    if (pbSuccess)
1810
0
        *pbSuccess = bNoDataSet;
1811
1812
0
    if (bNoDataSet)
1813
0
        return dfNoData;
1814
1815
0
    return RawRasterBand::GetNoDataValue(pbSuccess);
1816
0
}
1817
1818
/************************************************************************/
1819
/*                           GetMinimum()                               */
1820
/************************************************************************/
1821
1822
double EHdrRasterBand::GetMinimum(int *pbSuccess)
1823
0
{
1824
0
    if (pbSuccess != nullptr)
1825
0
        *pbSuccess = (minmaxmeanstddev & HAS_MIN_FLAG) != 0;
1826
1827
0
    if (minmaxmeanstddev & HAS_MIN_FLAG)
1828
0
        return dfMin;
1829
1830
0
    return RawRasterBand::GetMinimum(pbSuccess);
1831
0
}
1832
1833
/************************************************************************/
1834
/*                           GetMaximum()                               */
1835
/************************************************************************/
1836
1837
double EHdrRasterBand::GetMaximum(int *pbSuccess)
1838
0
{
1839
0
    if (pbSuccess != nullptr)
1840
0
        *pbSuccess = (minmaxmeanstddev & HAS_MAX_FLAG) != 0;
1841
1842
0
    if (minmaxmeanstddev & HAS_MAX_FLAG)
1843
0
        return dfMax;
1844
1845
0
    return RawRasterBand::GetMaximum(pbSuccess);
1846
0
}
1847
1848
/************************************************************************/
1849
/*                           GetStatistics()                            */
1850
/************************************************************************/
1851
1852
CPLErr EHdrRasterBand::GetStatistics(int bApproxOK, int bForce, double *pdfMin,
1853
                                     double *pdfMax, double *pdfMean,
1854
                                     double *pdfStdDev)
1855
0
{
1856
0
    if (!(GetMetadataItem("STATISTICS_APPROXIMATE") && !bApproxOK))
1857
0
    {
1858
0
        if ((minmaxmeanstddev & HAS_ALL_FLAGS) == HAS_ALL_FLAGS)
1859
0
        {
1860
0
            if (pdfMin)
1861
0
                *pdfMin = dfMin;
1862
0
            if (pdfMax)
1863
0
                *pdfMax = dfMax;
1864
0
            if (pdfMean)
1865
0
                *pdfMean = dfMean;
1866
0
            if (pdfStdDev)
1867
0
                *pdfStdDev = dfStdDev;
1868
0
            return CE_None;
1869
0
        }
1870
0
    }
1871
1872
0
    const CPLErr eErr = RawRasterBand::GetStatistics(
1873
0
        bApproxOK, bForce, &dfMin, &dfMax, &dfMean, &dfStdDev);
1874
0
    if (eErr != CE_None)
1875
0
        return eErr;
1876
1877
0
    EHdrDataset *poEDS = reinterpret_cast<EHdrDataset *>(poDS);
1878
1879
0
    minmaxmeanstddev = HAS_ALL_FLAGS;
1880
1881
0
    if (!bApproxOK && poEDS->RewriteSTX() != CE_None)
1882
0
        RawRasterBand::SetStatistics(dfMin, dfMax, dfMean, dfStdDev);
1883
1884
0
    if (pdfMin)
1885
0
        *pdfMin = dfMin;
1886
0
    if (pdfMax)
1887
0
        *pdfMax = dfMax;
1888
0
    if (pdfMean)
1889
0
        *pdfMean = dfMean;
1890
0
    if (pdfStdDev)
1891
0
        *pdfStdDev = dfStdDev;
1892
1893
0
    return CE_None;
1894
0
}
1895
1896
/************************************************************************/
1897
/*                           SetStatistics()                            */
1898
/************************************************************************/
1899
1900
CPLErr EHdrRasterBand::SetStatistics(double dfMinIn, double dfMaxIn,
1901
                                     double dfMeanIn, double dfStdDevIn)
1902
0
{
1903
    // Avoid churn if nothing is changing.
1904
0
    if (dfMin == dfMinIn && dfMax == dfMaxIn && dfMean == dfMeanIn &&
1905
0
        dfStdDev == dfStdDevIn)
1906
0
        return CE_None;
1907
1908
0
    dfMin = dfMinIn;
1909
0
    dfMax = dfMaxIn;
1910
0
    dfMean = dfMeanIn;
1911
0
    dfStdDev = dfStdDevIn;
1912
1913
    // marks stats valid
1914
0
    minmaxmeanstddev = HAS_ALL_FLAGS;
1915
1916
0
    EHdrDataset *poEDS = reinterpret_cast<EHdrDataset *>(poDS);
1917
1918
0
    if (GetMetadataItem("STATISTICS_APPROXIMATE") == nullptr)
1919
0
    {
1920
0
        if (GetMetadataItem("STATISTICS_MINIMUM"))
1921
0
        {
1922
0
            SetMetadataItem("STATISTICS_MINIMUM", nullptr);
1923
0
            SetMetadataItem("STATISTICS_MAXIMUM", nullptr);
1924
0
            SetMetadataItem("STATISTICS_MEAN", nullptr);
1925
0
            SetMetadataItem("STATISTICS_STDDEV", nullptr);
1926
0
        }
1927
0
        return poEDS->RewriteSTX();
1928
0
    }
1929
1930
0
    return RawRasterBand::SetStatistics(dfMinIn, dfMaxIn, dfMeanIn, dfStdDevIn);
1931
0
}
1932
1933
/************************************************************************/
1934
/*                           GetColorTable()                            */
1935
/************************************************************************/
1936
1937
GDALColorTable *EHdrRasterBand::GetColorTable()
1938
0
{
1939
0
    return m_poColorTable.get();
1940
0
}
1941
1942
/************************************************************************/
1943
/*                           SetColorTable()                            */
1944
/************************************************************************/
1945
1946
CPLErr EHdrRasterBand::SetColorTable(GDALColorTable *poNewCT)
1947
0
{
1948
0
    if (poNewCT == nullptr)
1949
0
        m_poColorTable.reset();
1950
0
    else
1951
0
        m_poColorTable.reset(poNewCT->Clone());
1952
1953
0
    reinterpret_cast<EHdrDataset *>(poDS)->bCLRDirty = true;
1954
1955
0
    return CE_None;
1956
0
}
1957
1958
/************************************************************************/
1959
/*                           GetDefaultRAT()                            */
1960
/************************************************************************/
1961
1962
GDALRasterAttributeTable *EHdrRasterBand::GetDefaultRAT()
1963
0
{
1964
0
    return m_poRAT.get();
1965
0
}
1966
1967
/************************************************************************/
1968
/*                            SetDefaultRAT()                           */
1969
/************************************************************************/
1970
1971
CPLErr EHdrRasterBand::SetDefaultRAT(const GDALRasterAttributeTable *poRAT)
1972
0
{
1973
0
    if (poRAT)
1974
0
    {
1975
0
        if (!(poRAT->GetColumnCount() == 4 &&
1976
0
              poRAT->GetTypeOfCol(0) == GFT_Integer &&
1977
0
              poRAT->GetTypeOfCol(1) == GFT_Integer &&
1978
0
              poRAT->GetTypeOfCol(2) == GFT_Integer &&
1979
0
              poRAT->GetTypeOfCol(3) == GFT_Integer &&
1980
0
              poRAT->GetUsageOfCol(0) == GFU_Generic &&
1981
0
              poRAT->GetUsageOfCol(1) == GFU_Red &&
1982
0
              poRAT->GetUsageOfCol(2) == GFU_Green &&
1983
0
              poRAT->GetUsageOfCol(3) == GFU_Blue))
1984
0
        {
1985
0
            CPLError(CE_Warning, CPLE_NotSupported,
1986
0
                     "Unsupported type of RAT: "
1987
0
                     "only value,R,G,B ones are supported");
1988
0
            return CE_Failure;
1989
0
        }
1990
0
    }
1991
1992
0
    if (poRAT == nullptr)
1993
0
        m_poRAT.reset();
1994
0
    else
1995
0
        m_poRAT.reset(poRAT->Clone());
1996
1997
0
    reinterpret_cast<EHdrDataset *>(poDS)->bCLRDirty = true;
1998
1999
0
    return CE_None;
2000
0
}
2001
2002
/************************************************************************/
2003
/*                         GDALRegister_EHdr()                          */
2004
/************************************************************************/
2005
2006
void GDALRegister_EHdr()
2007
2008
2
{
2009
2
    if (GDALGetDriverByName("EHdr") != nullptr)
2010
0
        return;
2011
2012
2
    GDALDriver *poDriver = new GDALDriver();
2013
2014
2
    poDriver->SetDescription("EHdr");
2015
2
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
2016
2
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "ESRI .hdr Labelled");
2017
2
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/ehdr.html");
2018
2
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "bil");
2019
2
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES,
2020
2
                              "Byte Int8 Int16 UInt16 Int32 UInt32 Float32");
2021
2022
2
    poDriver->SetMetadataItem(
2023
2
        GDAL_DMD_CREATIONOPTIONLIST,
2024
2
        "<CreationOptionList>"
2025
2
        "   <Option name='NBITS' type='int' description='Special pixel bits "
2026
2
        "(1-7)'/>"
2027
2
        "   <Option name='PIXELTYPE' type='string' description='By setting "
2028
2
        "this to SIGNEDBYTE, a new Byte file can be forced to be written as "
2029
2
        "signed byte'/>"
2030
2
        "</CreationOptionList>");
2031
2032
2
    poDriver->SetMetadataItem(GDAL_DCAP_UPDATE, "YES");
2033
2
    poDriver->SetMetadataItem(GDAL_DMD_UPDATE_ITEMS, "GeoTransform SRS NoData "
2034
2
                                                     "RasterValues");
2035
2036
2
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
2037
2
    poDriver->pfnOpen = EHdrDataset::Open;
2038
2
    poDriver->pfnCreate = EHdrDataset::Create;
2039
2
    poDriver->pfnCreateCopy = EHdrDataset::CreateCopy;
2040
2041
2
    GetGDALDriverManager()->RegisterDriver(poDriver);
2042
2
}