Coverage Report

Created: 2026-08-14 09:29

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
54.5k
{
29
54.5k
    CPLStringList aosHeaders;
30
31
54.5k
    constexpr int MAX_LINE_SIZE = 10000;
32
33
    // Skip first line with "ENVI"
34
54.5k
    CPLReadLine2L(fpHdr, MAX_LINE_SIZE, nullptr);
35
36
    // Start forming sets of name/value pairs.
37
54.5k
    CPLString osWorkingLine;
38
54.5k
    std::string osValue;
39
3.04M
    while (true)
40
3.04M
    {
41
3.04M
        const char *pszNewLine = CPLReadLine2L(fpHdr, MAX_LINE_SIZE, nullptr);
42
3.04M
        if (pszNewLine == nullptr)
43
54.5k
            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
3.26M
        while (*pszNewLine == ' ')
49
280k
            ++pszNewLine;
50
2.98M
        if (strchr(pszNewLine, '=') == nullptr)
51
2.29M
            continue;
52
53
690k
        osWorkingLine = pszNewLine;
54
55
        // Collect additional lines if we have open curly bracket.
56
690k
        if (osWorkingLine.find("{") != std::string::npos &&
57
84.7k
            osWorkingLine.find("}") == std::string::npos)
58
51.9k
        {
59
51.9k
            do
60
2.54M
            {
61
2.54M
                pszNewLine = CPLReadLine2L(fpHdr, MAX_LINE_SIZE, nullptr);
62
2.54M
                if (pszNewLine)
63
2.52M
                {
64
2.52M
                    osWorkingLine += pszNewLine;
65
2.52M
                }
66
2.54M
                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
2.54M
            } while (pszNewLine != nullptr &&
73
2.52M
                     strchr(pszNewLine, '}') == nullptr);
74
51.9k
        }
75
76
        // Try to break input into name and value portions. Trim whitespace.
77
690k
        size_t iEqual = osWorkingLine.find("=");
78
79
690k
        if (iEqual != std::string::npos && iEqual > 0)
80
655k
        {
81
655k
            osValue = osWorkingLine.substr(iEqual + 1);
82
655k
            const auto found = osValue.find_first_not_of(" \t");
83
655k
            if (found != std::string::npos)
84
626k
                osValue = osValue.substr(found);
85
29.4k
            else
86
29.4k
                osValue.clear();
87
88
655k
            iEqual--;
89
974k
            while (iEqual > 0 && (osWorkingLine[iEqual] == ' ' ||
90
610k
                                  osWorkingLine[iEqual] == '\t'))
91
318k
            {
92
318k
                iEqual--;
93
318k
            }
94
655k
            osWorkingLine.resize(iEqual + 1);
95
655k
            osWorkingLine.replaceAll(' ', '_');
96
655k
            aosHeaders.SetNameValue(osWorkingLine.c_str(), osValue.c_str());
97
655k
        }
98
690k
    }
99
100
54.5k
    return aosHeaders;
101
54.5k
}
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
36.3k
{
113
36.3k
    CPLStringList aosList;
114
115
36.3k
    if (!pszCleanInput || pszCleanInput[0] != '{')
116
4.93k
    {
117
4.93k
        return aosList;
118
4.93k
    }
119
120
31.4k
    char *pszInput = CPLStrdup(pszCleanInput);
121
122
31.4k
    int iChar = 1;
123
10.6M
    while (pszInput[iChar] != '}' && pszInput[iChar] != '\0')
124
10.6M
    {
125
        // Find start of token.
126
10.6M
        int iFStart = iChar;
127
10.7M
        while (pszInput[iFStart] == ' ')
128
64.8k
            iFStart++;
129
130
10.6M
        int iFEnd = iFStart;
131
61.2M
        while (pszInput[iFEnd] != ',' && pszInput[iFEnd] != '}' &&
132
50.6M
               pszInput[iFEnd] != '\0')
133
50.6M
            iFEnd++;
134
135
10.6M
        if (pszInput[iFEnd] == '\0')
136
17.8k
            break;
137
138
10.6M
        iChar = iFEnd + 1;
139
10.6M
        iFEnd = iFEnd - 1;
140
141
10.6M
        while (iFEnd > iFStart && pszInput[iFEnd] == ' ')
142
18.1k
            iFEnd--;
143
144
10.6M
        pszInput[iFEnd + 1] = '\0';
145
10.6M
        aosList.AddString(pszInput + iFStart);
146
10.6M
    }
147
148
31.4k
    CPLFree(pszInput);
149
150
31.4k
    return aosList;
151
36.3k
}
152
153
/************************************************************************/
154
/*                        GDALApplyENVIHeaders()                        */
155
/************************************************************************/
156
157
void GDALApplyENVIHeaders(GDALDataset *poDS, const CPLStringList &aosHeaders,
158
                          CSLConstList papszOptions)
159
45.3k
{
160
45.3k
    const int nBands = poDS->GetRasterCount();
161
45.3k
    auto poPamDS = dynamic_cast<GDALPamDataset *>(poDS);
162
45.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
45.3k
    const char *pszBandNames = aosHeaders["band_names"];
167
45.3k
    const char *pszWaveLength = aosHeaders["wavelength"];
168
45.3k
    if (pszBandNames || pszWaveLength)
169
748
    {
170
748
        const bool bSetDatasetLevelMetadata = CPLTestBool(CSLFetchNameValueDef(
171
748
            papszOptions, "SET_DATASET_LEVEL_METADATA", "YES"));
172
748
        const bool bSetBandName = CPLTestBool(
173
748
            CSLFetchNameValueDef(papszOptions, "SET_BAND_NAME", "YES"));
174
748
        const CPLStringList aosBandNames(GDALENVISplitList(pszBandNames));
175
748
        const CPLStringList aosWL(GDALENVISplitList(pszWaveLength));
176
748
        const char *pszFWHM = aosHeaders["fwhm"];
177
748
        const CPLStringList aosFWHM(GDALENVISplitList(pszFWHM ? pszFWHM : ""));
178
179
748
        const char *pszWLUnits = nullptr;
180
748
        const int nWLCount = aosWL.size();
181
748
        const int nFWHMCount = aosFWHM.size();
182
748
        if (nWLCount)
183
106
        {
184
            // If WL information is present, process wavelength units.
185
106
            pszWLUnits = aosHeaders["wavelength_units"];
186
106
            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
106
            if (pszWLUnits && bSetDatasetLevelMetadata)
193
0
            {
194
                // Set wavelength units to dataset metadata.
195
0
                poDS->SetMetadataItem("wavelength_units", pszWLUnits);
196
0
            }
197
106
        }
198
199
6.87k
        for (int i = 0; i < nBands; i++)
200
6.12k
        {
201
            // First set up the wavelength names and units if available.
202
6.12k
            std::string osWavelength;
203
6.12k
            if (nWLCount > i)
204
1.32k
            {
205
1.32k
                osWavelength = aosWL[i];
206
1.32k
                if (pszWLUnits)
207
0
                {
208
0
                    osWavelength += " ";
209
0
                    osWavelength += pszWLUnits;
210
0
                }
211
1.32k
            }
212
213
6.12k
            if (bSetBandName)
214
6.12k
            {
215
                // Build the final name for this band.
216
6.12k
                std::string osBandName;
217
6.12k
                if (aosBandNames && CSLCount(aosBandNames) > i)
218
2.63k
                {
219
2.63k
                    osBandName = aosBandNames[i];
220
2.63k
                    if (!osWavelength.empty())
221
458
                    {
222
458
                        osBandName += " (";
223
458
                        osBandName += osWavelength;
224
458
                        osBandName += ")";
225
458
                    }
226
2.63k
                }
227
3.48k
                else
228
3.48k
                {
229
                    // WL but no band names.
230
3.48k
                    osBandName = std::move(osWavelength);
231
3.48k
                }
232
233
                // Description is for internal GDAL usage.
234
6.12k
                poDS->GetRasterBand(i + 1)->SetDescription(osBandName.c_str());
235
236
                // Metadata field named Band_1, etc. Needed for ArcGIS integration.
237
6.12k
                const std::string osBandId = CPLSPrintf("Band_%i", i + 1);
238
6.12k
                if (bSetDatasetLevelMetadata)
239
6.12k
                    poDS->SetMetadataItem(osBandId.c_str(), osBandName.c_str());
240
6.12k
            }
241
242
6.12k
            const auto ConvertWaveLength =
243
6.12k
                [pszWLUnits](double dfVal) -> const char *
244
6.12k
            {
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
6.12k
            if (nWLCount > i)
267
1.32k
            {
268
1.32k
                poDS->GetRasterBand(i + 1)->SetMetadataItem("wavelength",
269
1.32k
                                                            aosWL[i]);
270
271
1.32k
                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
                            GDALMD_CENTRAL_WAVELENGTH_UM, pszVal,
281
0
                            GDAL_MDD_IMAGERY);
282
0
                    }
283
0
                }
284
1.32k
            }
285
286
6.12k
            if (nFWHMCount > i && pszWLUnits)
287
0
            {
288
0
                if (const char *pszVal = ConvertWaveLength(CPLAtof(aosFWHM[i])))
289
0
                {
290
0
                    poDS->GetRasterBand(i + 1)->SetMetadataItem(
291
0
                        GDALMD_FWHM_UM, pszVal, GDAL_MDD_IMAGERY);
292
0
                }
293
0
            }
294
6.12k
        }
295
748
    }
296
297
45.3k
    if (CPLTestBool(
298
45.3k
            CSLFetchNameValueDef(papszOptions, "APPLY_DEFAULT_BANDS", "YES")))
299
45.3k
    {
300
        // Apply "default bands" if we have it to set RGB color interpretation.
301
45.3k
        const char *pszDefaultBands = aosHeaders["default_bands"];
302
45.3k
        if (pszDefaultBands)
303
25
        {
304
25
            const CPLStringList aosDefaultBands(
305
25
                GDALENVISplitList(pszDefaultBands));
306
25
            if (aosDefaultBands.size() == 3)
307
0
            {
308
0
                const int nRBand = atoi(aosDefaultBands[0]);
309
0
                const int nGBand = atoi(aosDefaultBands[1]);
310
0
                const int nBBand = atoi(aosDefaultBands[2]);
311
0
                if (nRBand >= 1 && nRBand <= nBands && nGBand >= 1 &&
312
0
                    nGBand <= nBands && nBBand >= 1 && nBBand <= nBands &&
313
0
                    nRBand != nGBand && nRBand != nBBand && nGBand != nBBand)
314
0
                {
315
0
                    poDS->GetRasterBand(nRBand)->SetColorInterpretation(
316
0
                        GCI_RedBand);
317
0
                    poDS->GetRasterBand(nGBand)->SetColorInterpretation(
318
0
                        GCI_GreenBand);
319
0
                    poDS->GetRasterBand(nBBand)->SetColorInterpretation(
320
0
                        GCI_BlueBand);
321
0
                }
322
0
            }
323
25
            else if (aosDefaultBands.size() == 1)
324
0
            {
325
0
                const int nGrayBand = atoi(aosDefaultBands[0]);
326
0
                if (nGrayBand >= 1 && nGrayBand <= nBands)
327
0
                {
328
0
                    poDS->GetRasterBand(nGrayBand)->SetColorInterpretation(
329
0
                        GCI_GrayIndex);
330
0
                }
331
0
            }
332
25
        }
333
45.3k
    }
334
335
    // Apply data offset values
336
45.3k
    if (const char *pszDataOffsetValues = aosHeaders["data_offset_values"])
337
74
    {
338
74
        const CPLStringList aosValues(GDALENVISplitList(pszDataOffsetValues));
339
74
        if (aosValues.size() == nBands)
340
31
        {
341
138
            for (int i = 0; i < nBands; ++i)
342
107
                poDS->GetRasterBand(i + 1)->SetOffset(CPLAtof(aosValues[i]));
343
31
        }
344
74
    }
345
346
    // Apply data gain values
347
45.3k
    if (const char *pszDataGainValues = aosHeaders["data_gain_values"])
348
36
    {
349
36
        const CPLStringList aosValues(GDALENVISplitList(pszDataGainValues));
350
36
        if (aosValues.size() == nBands)
351
12
        {
352
24
            for (int i = 0; i < nBands; ++i)
353
12
            {
354
12
                poDS->GetRasterBand(i + 1)->SetScale(CPLAtof(aosValues[i]));
355
12
            }
356
12
        }
357
36
    }
358
359
    // Apply class names if we have them.
360
45.3k
    if (const char *pszClassNames = aosHeaders["class_names"])
361
101
    {
362
101
        poDS->GetRasterBand(1)->SetCategoryNames(
363
101
            GDALENVISplitList(pszClassNames).List());
364
101
    }
365
366
45.3k
    if (const char *pszBBL = aosHeaders["bbl"])
367
0
    {
368
0
        const CPLStringList aosValues(GDALENVISplitList(pszBBL));
369
0
        if (aosValues.size() == nBands)
370
0
        {
371
0
            for (int i = 0; i < nBands; ++i)
372
0
            {
373
0
                poDS->GetRasterBand(i + 1)->SetMetadataItem(
374
0
                    "good_band",
375
0
                    strcmp(aosValues[i], "1") == 0 ? "true" : "false");
376
0
            }
377
0
        }
378
0
    }
379
380
45.3k
    if (CPLTestBool(
381
45.3k
            CSLFetchNameValueDef(papszOptions, "APPLY_CLASS_LOOKUP", "YES")))
382
45.3k
    {
383
        // Apply colormap if we have one.
384
45.3k
        const char *pszClassLookup = aosHeaders["class_lookup"];
385
45.3k
        if (pszClassLookup != nullptr)
386
474
        {
387
474
            const CPLStringList aosClassColors(
388
474
                GDALENVISplitList(pszClassLookup));
389
474
            const int nColorValueCount = aosClassColors.size();
390
474
            GDALColorTable oCT;
391
392
3.27M
            for (int i = 0; i * 3 + 2 < nColorValueCount; i++)
393
3.27M
            {
394
3.27M
                const GDALColorEntry sEntry = {
395
3.27M
                    static_cast<short>(std::clamp(
396
3.27M
                        atoi(aosClassColors[i * 3 + 0]), 0, 255)),  // Red
397
3.27M
                    static_cast<short>(std::clamp(
398
3.27M
                        atoi(aosClassColors[i * 3 + 1]), 0, 255)),  // Green
399
3.27M
                    static_cast<short>(std::clamp(
400
3.27M
                        atoi(aosClassColors[i * 3 + 2]), 0, 255)),  // Blue
401
3.27M
                    255};
402
3.27M
                oCT.SetColorEntry(i, &sEntry);
403
3.27M
            }
404
405
474
            poDS->GetRasterBand(1)->SetColorTable(&oCT);
406
474
            poDS->GetRasterBand(1)->SetColorInterpretation(GCI_PaletteIndex);
407
474
        }
408
45.3k
    }
409
410
45.3k
    if (CPLTestBool(CSLFetchNameValueDef(papszOptions,
411
45.3k
                                         "APPLY_DATA_IGNORE_VALUE", "YES")))
412
45.3k
    {
413
        // Set the nodata value if it is present.
414
45.3k
        const char *pszDataIgnoreValue = aosHeaders["data_ignore_value"];
415
45.3k
        if (pszDataIgnoreValue != nullptr)
416
1.49k
        {
417
16.7k
            for (int i = 0; i < nBands; i++)
418
15.2k
            {
419
15.2k
                auto poBand =
420
15.2k
                    dynamic_cast<RawRasterBand *>(poDS->GetRasterBand(i + 1));
421
15.2k
                if (poBand)
422
15.2k
                    poBand->SetNoDataValue(CPLAtof(pszDataIgnoreValue));
423
15.2k
            }
424
1.49k
        }
425
45.3k
    }
426
427
45.3k
    if (poPamDS)
428
45.3k
        poPamDS->SetPamFlags(nPAMFlagsBackup);
429
45.3k
}