Coverage Report

Created: 2025-12-03 08:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/gcore/enviutils.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  Read ENVI .hdr file
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2002, Frank Warmerdam
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "cpl_string.h"
14
#include "cpl_vsi.h"
15
16
#include "gdal_cpp_functions.h"
17
#include "gdal_colortable.h"
18
#include "gdal_dataset.h"
19
#include "gdal_rasterband.h"
20
#include "rawdataset.h"
21
22
/************************************************************************/
23
/*                       GDALReadENVIHeader()                           */
24
/************************************************************************/
25
26
CPLStringList GDALReadENVIHeader(VSILFILE *fpHdr)
27
28
41.4k
{
29
41.4k
    CPLStringList aosHeaders;
30
31
41.4k
    constexpr int MAX_LINE_SIZE = 10000;
32
33
    // Skip first line with "ENVI"
34
41.4k
    CPLReadLine2L(fpHdr, MAX_LINE_SIZE, nullptr);
35
36
    // Start forming sets of name/value pairs.
37
41.4k
    CPLString osWorkingLine;
38
41.4k
    std::string osValue;
39
2.21M
    while (true)
40
2.21M
    {
41
2.21M
        const char *pszNewLine = CPLReadLine2L(fpHdr, MAX_LINE_SIZE, nullptr);
42
2.21M
        if (pszNewLine == nullptr)
43
41.4k
            break;
44
45
        // Skip leading spaces. This may happen for example with
46
        // AVIRIS datasets (https://aviris.jpl.nasa.gov/dataportal/) whose
47
        // wavelength metadata starts with a leading space.
48
2.83M
        while (*pszNewLine == ' ')
49
656k
            ++pszNewLine;
50
2.17M
        if (strchr(pszNewLine, '=') == nullptr)
51
1.64M
            continue;
52
53
533k
        osWorkingLine = pszNewLine;
54
55
        // Collect additional lines if we have open curly bracket.
56
533k
        if (osWorkingLine.find("{") != std::string::npos &&
57
72.3k
            osWorkingLine.find("}") == std::string::npos)
58
42.3k
        {
59
42.3k
            do
60
1.73M
            {
61
1.73M
                pszNewLine = CPLReadLine2L(fpHdr, MAX_LINE_SIZE, nullptr);
62
1.73M
                if (pszNewLine)
63
1.72M
                {
64
1.72M
                    osWorkingLine += pszNewLine;
65
1.72M
                }
66
1.73M
                if (osWorkingLine.size() > 10 * 1024 * 1024)
67
0
                {
68
0
                    CPLError(CE_Failure, CPLE_AppDefined,
69
0
                             "Concatenated line exceeds 10 MB");
70
0
                    return aosHeaders;
71
0
                }
72
1.73M
            } while (pszNewLine != nullptr &&
73
1.72M
                     strchr(pszNewLine, '}') == nullptr);
74
42.3k
        }
75
76
        // Try to break input into name and value portions. Trim whitespace.
77
533k
        size_t iEqual = osWorkingLine.find("=");
78
79
533k
        if (iEqual != std::string::npos && iEqual > 0)
80
511k
        {
81
511k
            osValue = osWorkingLine.substr(iEqual + 1);
82
511k
            const auto found = osValue.find_first_not_of(" \t");
83
511k
            if (found != std::string::npos)
84
490k
                osValue = osValue.substr(found);
85
21.2k
            else
86
21.2k
                osValue.clear();
87
88
511k
            iEqual--;
89
765k
            while (iEqual > 0 && (osWorkingLine[iEqual] == ' ' ||
90
481k
                                  osWorkingLine[iEqual] == '\t'))
91
254k
            {
92
254k
                iEqual--;
93
254k
            }
94
511k
            osWorkingLine.resize(iEqual + 1);
95
511k
            osWorkingLine.replaceAll(' ', '_');
96
511k
            aosHeaders.SetNameValue(osWorkingLine.c_str(), osValue.c_str());
97
511k
        }
98
533k
    }
99
100
41.4k
    return aosHeaders;
101
41.4k
}
102
103
/************************************************************************/
104
/*                       GDALENVISplitList()                            */
105
/*                                                                      */
106
/*      Split an ENVI value list into component fields, and strip       */
107
/*      white space.                                                    */
108
/************************************************************************/
109
110
CPLStringList GDALENVISplitList(const char *pszCleanInput)
111
112
28.2k
{
113
28.2k
    CPLStringList aosList;
114
115
28.2k
    if (!pszCleanInput || pszCleanInput[0] != '{')
116
4.40k
    {
117
4.40k
        return aosList;
118
4.40k
    }
119
120
23.8k
    char *pszInput = CPLStrdup(pszCleanInput);
121
122
23.8k
    int iChar = 1;
123
8.46M
    while (pszInput[iChar] != '}' && pszInput[iChar] != '\0')
124
8.44M
    {
125
        // Find start of token.
126
8.44M
        int iFStart = iChar;
127
8.49M
        while (pszInput[iFStart] == ' ')
128
44.9k
            iFStart++;
129
130
8.44M
        int iFEnd = iFStart;
131
32.9M
        while (pszInput[iFEnd] != ',' && pszInput[iFEnd] != '}' &&
132
24.5M
               pszInput[iFEnd] != '\0')
133
24.5M
            iFEnd++;
134
135
8.44M
        if (pszInput[iFEnd] == '\0')
136
13.5k
            break;
137
138
8.43M
        iChar = iFEnd + 1;
139
8.43M
        iFEnd = iFEnd - 1;
140
141
8.45M
        while (iFEnd > iFStart && pszInput[iFEnd] == ' ')
142
16.0k
            iFEnd--;
143
144
8.43M
        pszInput[iFEnd + 1] = '\0';
145
8.43M
        aosList.AddString(pszInput + iFStart);
146
8.43M
    }
147
148
23.8k
    CPLFree(pszInput);
149
150
23.8k
    return aosList;
151
28.2k
}
152
153
/************************************************************************/
154
/*                       GDALApplyENVIHeaders()                         */
155
/************************************************************************/
156
157
void GDALApplyENVIHeaders(GDALDataset *poDS, const CPLStringList &aosHeaders,
158
                          CSLConstList papszOptions)
159
35.3k
{
160
35.3k
    const int nBands = poDS->GetRasterCount();
161
35.3k
    auto poPamDS = dynamic_cast<GDALPamDataset *>(poDS);
162
35.3k
    const int nPAMFlagsBackup = poPamDS ? poPamDS->GetPamFlags() : -1;
163
164
    // Apply band names if we have them.
165
    // Use wavelength for more descriptive information if possible.
166
35.3k
    const char *pszBandNames = aosHeaders["band_names"];
167
35.3k
    const char *pszWaveLength = aosHeaders["wavelength"];
168
35.3k
    if (pszBandNames || pszWaveLength)
169
647
    {
170
647
        const bool bSetDatasetLevelMetadata = CPLTestBool(CSLFetchNameValueDef(
171
647
            papszOptions, "SET_DATASET_LEVEL_METADATA", "YES"));
172
647
        const bool bSetBandName = CPLTestBool(
173
647
            CSLFetchNameValueDef(papszOptions, "SET_BAND_NAME", "YES"));
174
647
        const CPLStringList aosBandNames(GDALENVISplitList(pszBandNames));
175
647
        const CPLStringList aosWL(GDALENVISplitList(pszWaveLength));
176
647
        const char *pszFWHM = aosHeaders["fwhm"];
177
647
        const CPLStringList aosFWHM(GDALENVISplitList(pszFWHM ? pszFWHM : ""));
178
179
647
        const char *pszWLUnits = nullptr;
180
647
        const int nWLCount = aosWL.size();
181
647
        const int nFWHMCount = aosFWHM.size();
182
647
        if (nWLCount)
183
101
        {
184
            // If WL information is present, process wavelength units.
185
101
            pszWLUnits = aosHeaders["wavelength_units"];
186
101
            if (pszWLUnits)
187
0
            {
188
                // Don't show unknown or index units.
189
0
                if (EQUAL(pszWLUnits, "Unknown") || EQUAL(pszWLUnits, "Index"))
190
0
                    pszWLUnits = nullptr;
191
0
            }
192
101
            if (pszWLUnits && bSetDatasetLevelMetadata)
193
0
            {
194
                // Set wavelength units to dataset metadata.
195
0
                poDS->SetMetadataItem("wavelength_units", pszWLUnits);
196
0
            }
197
101
        }
198
199
5.29k
        for (int i = 0; i < nBands; i++)
200
4.65k
        {
201
            // First set up the wavelength names and units if available.
202
4.65k
            std::string osWavelength;
203
4.65k
            if (nWLCount > i)
204
1.00k
            {
205
1.00k
                osWavelength = aosWL[i];
206
1.00k
                if (pszWLUnits)
207
0
                {
208
0
                    osWavelength += " ";
209
0
                    osWavelength += pszWLUnits;
210
0
                }
211
1.00k
            }
212
213
4.65k
            if (bSetBandName)
214
4.65k
            {
215
                // Build the final name for this band.
216
4.65k
                std::string osBandName;
217
4.65k
                if (aosBandNames && CSLCount(aosBandNames) > i)
218
2.36k
                {
219
2.36k
                    osBandName = aosBandNames[i];
220
2.36k
                    if (!osWavelength.empty())
221
472
                    {
222
472
                        osBandName += " (";
223
472
                        osBandName += osWavelength;
224
472
                        osBandName += ")";
225
472
                    }
226
2.36k
                }
227
2.28k
                else
228
2.28k
                {
229
                    // WL but no band names.
230
2.28k
                    osBandName = std::move(osWavelength);
231
2.28k
                }
232
233
                // Description is for internal GDAL usage.
234
4.65k
                poDS->GetRasterBand(i + 1)->SetDescription(osBandName.c_str());
235
236
                // Metadata field named Band_1, etc. Needed for ArcGIS integration.
237
4.65k
                const std::string osBandId = CPLSPrintf("Band_%i", i + 1);
238
4.65k
                if (bSetDatasetLevelMetadata)
239
4.65k
                    poDS->SetMetadataItem(osBandId.c_str(), osBandName.c_str());
240
4.65k
            }
241
242
4.65k
            const auto ConvertWaveLength =
243
4.65k
                [pszWLUnits](double dfVal) -> const char *
244
4.65k
            {
245
0
                if (EQUAL(pszWLUnits, "Micrometers") || EQUAL(pszWLUnits, "um"))
246
0
                {
247
0
                    return CPLSPrintf("%.3f", dfVal);
248
0
                }
249
0
                else if (EQUAL(pszWLUnits, "Nanometers") ||
250
0
                         EQUAL(pszWLUnits, "nm"))
251
0
                {
252
0
                    return CPLSPrintf("%.3f", dfVal / 1000);
253
0
                }
254
0
                else if (EQUAL(pszWLUnits, "Millimeters") ||
255
0
                         EQUAL(pszWLUnits, "mm"))
256
0
                {
257
0
                    return CPLSPrintf("%.3f", dfVal * 1000);
258
0
                }
259
0
                else
260
0
                {
261
0
                    return nullptr;
262
0
                }
263
0
            };
264
265
            // Set wavelength metadata to band.
266
4.65k
            if (nWLCount > i)
267
1.00k
            {
268
1.00k
                poDS->GetRasterBand(i + 1)->SetMetadataItem("wavelength",
269
1.00k
                                                            aosWL[i]);
270
271
1.00k
                if (pszWLUnits)
272
0
                {
273
0
                    poDS->GetRasterBand(i + 1)->SetMetadataItem(
274
0
                        "wavelength_units", pszWLUnits);
275
276
0
                    if (const char *pszVal =
277
0
                            ConvertWaveLength(CPLAtof(aosWL[i])))
278
0
                    {
279
0
                        poDS->GetRasterBand(i + 1)->SetMetadataItem(
280
0
                            "CENTRAL_WAVELENGTH_UM", pszVal, "IMAGERY");
281
0
                    }
282
0
                }
283
1.00k
            }
284
285
4.65k
            if (nFWHMCount > i && pszWLUnits)
286
0
            {
287
0
                if (const char *pszVal = ConvertWaveLength(CPLAtof(aosFWHM[i])))
288
0
                {
289
0
                    poDS->GetRasterBand(i + 1)->SetMetadataItem(
290
0
                        "FWHM_UM", pszVal, "IMAGERY");
291
0
                }
292
0
            }
293
4.65k
        }
294
647
    }
295
296
35.3k
    if (CPLTestBool(
297
35.3k
            CSLFetchNameValueDef(papszOptions, "APPLY_DEFAULT_BANDS", "YES")))
298
35.3k
    {
299
        // Apply "default bands" if we have it to set RGB color interpretation.
300
35.3k
        const char *pszDefaultBands = aosHeaders["default_bands"];
301
35.3k
        if (pszDefaultBands)
302
50
        {
303
50
            const CPLStringList aosDefaultBands(
304
50
                GDALENVISplitList(pszDefaultBands));
305
50
            if (aosDefaultBands.size() == 3)
306
0
            {
307
0
                const int nRBand = atoi(aosDefaultBands[0]);
308
0
                const int nGBand = atoi(aosDefaultBands[1]);
309
0
                const int nBBand = atoi(aosDefaultBands[2]);
310
0
                if (nRBand >= 1 && nRBand <= nBands && nGBand >= 1 &&
311
0
                    nGBand <= nBands && nBBand >= 1 && nBBand <= nBands &&
312
0
                    nRBand != nGBand && nRBand != nBBand && nGBand != nBBand)
313
0
                {
314
0
                    poDS->GetRasterBand(nRBand)->SetColorInterpretation(
315
0
                        GCI_RedBand);
316
0
                    poDS->GetRasterBand(nGBand)->SetColorInterpretation(
317
0
                        GCI_GreenBand);
318
0
                    poDS->GetRasterBand(nBBand)->SetColorInterpretation(
319
0
                        GCI_BlueBand);
320
0
                }
321
0
            }
322
50
            else if (aosDefaultBands.size() == 1)
323
0
            {
324
0
                const int nGrayBand = atoi(aosDefaultBands[0]);
325
0
                if (nGrayBand >= 1 && nGrayBand <= nBands)
326
0
                {
327
0
                    poDS->GetRasterBand(nGrayBand)->SetColorInterpretation(
328
0
                        GCI_GrayIndex);
329
0
                }
330
0
            }
331
50
        }
332
35.3k
    }
333
334
    // Apply data offset values
335
35.3k
    if (const char *pszDataOffsetValues = aosHeaders["data_offset_values"])
336
54
    {
337
54
        const CPLStringList aosValues(GDALENVISplitList(pszDataOffsetValues));
338
54
        if (aosValues.size() == nBands)
339
25
        {
340
110
            for (int i = 0; i < nBands; ++i)
341
85
                poDS->GetRasterBand(i + 1)->SetOffset(CPLAtof(aosValues[i]));
342
25
        }
343
54
    }
344
345
    // Apply data gain values
346
35.3k
    if (const char *pszDataGainValues = aosHeaders["data_gain_values"])
347
36
    {
348
36
        const CPLStringList aosValues(GDALENVISplitList(pszDataGainValues));
349
36
        if (aosValues.size() == nBands)
350
18
        {
351
36
            for (int i = 0; i < nBands; ++i)
352
18
            {
353
18
                poDS->GetRasterBand(i + 1)->SetScale(CPLAtof(aosValues[i]));
354
18
            }
355
18
        }
356
36
    }
357
358
    // Apply class names if we have them.
359
35.3k
    if (const char *pszClassNames = aosHeaders["class_names"])
360
71
    {
361
71
        poDS->GetRasterBand(1)->SetCategoryNames(
362
71
            GDALENVISplitList(pszClassNames).List());
363
71
    }
364
365
35.3k
    if (const char *pszBBL = aosHeaders["bbl"])
366
0
    {
367
0
        const CPLStringList aosValues(GDALENVISplitList(pszBBL));
368
0
        if (aosValues.size() == nBands)
369
0
        {
370
0
            for (int i = 0; i < nBands; ++i)
371
0
            {
372
0
                poDS->GetRasterBand(i + 1)->SetMetadataItem(
373
0
                    "good_band",
374
0
                    strcmp(aosValues[i], "1") == 0 ? "true" : "false");
375
0
            }
376
0
        }
377
0
    }
378
379
35.3k
    if (CPLTestBool(
380
35.3k
            CSLFetchNameValueDef(papszOptions, "APPLY_CLASS_LOOKUP", "YES")))
381
35.3k
    {
382
        // Apply colormap if we have one.
383
35.3k
        const char *pszClassLookup = aosHeaders["class_lookup"];
384
35.3k
        if (pszClassLookup != nullptr)
385
530
        {
386
530
            const CPLStringList aosClassColors(
387
530
                GDALENVISplitList(pszClassLookup));
388
530
            const int nColorValueCount = aosClassColors.size();
389
530
            GDALColorTable oCT;
390
391
2.60M
            for (int i = 0; i * 3 + 2 < nColorValueCount; i++)
392
2.60M
            {
393
2.60M
                const GDALColorEntry sEntry = {
394
2.60M
                    static_cast<short>(std::clamp(
395
2.60M
                        atoi(aosClassColors[i * 3 + 0]), 0, 255)),  // Red
396
2.60M
                    static_cast<short>(std::clamp(
397
2.60M
                        atoi(aosClassColors[i * 3 + 1]), 0, 255)),  // Green
398
2.60M
                    static_cast<short>(std::clamp(
399
2.60M
                        atoi(aosClassColors[i * 3 + 2]), 0, 255)),  // Blue
400
2.60M
                    255};
401
2.60M
                oCT.SetColorEntry(i, &sEntry);
402
2.60M
            }
403
404
530
            poDS->GetRasterBand(1)->SetColorTable(&oCT);
405
530
            poDS->GetRasterBand(1)->SetColorInterpretation(GCI_PaletteIndex);
406
530
        }
407
35.3k
    }
408
409
35.3k
    if (CPLTestBool(CSLFetchNameValueDef(papszOptions,
410
35.3k
                                         "APPLY_DATA_IGNORE_VALUE", "YES")))
411
35.3k
    {
412
        // Set the nodata value if it is present.
413
35.3k
        const char *pszDataIgnoreValue = aosHeaders["data_ignore_value"];
414
35.3k
        if (pszDataIgnoreValue != nullptr)
415
1.36k
        {
416
15.9k
            for (int i = 0; i < nBands; i++)
417
14.6k
            {
418
14.6k
                auto poBand =
419
14.6k
                    dynamic_cast<RawRasterBand *>(poDS->GetRasterBand(i + 1));
420
14.6k
                if (poBand)
421
14.6k
                    poBand->SetNoDataValue(CPLAtof(pszDataIgnoreValue));
422
14.6k
            }
423
1.36k
        }
424
35.3k
    }
425
426
35.3k
    if (poPamDS)
427
35.3k
        poPamDS->SetPamFlags(nPAMFlagsBackup);
428
35.3k
}