Coverage Report

Created: 2026-09-26 08:22

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
55.0k
{
29
55.0k
    CPLStringList aosHeaders;
30
31
55.0k
    constexpr int MAX_LINE_SIZE = 10000;
32
33
    // Skip first line with "ENVI"
34
55.0k
    CPLReadLine2L(fpHdr, MAX_LINE_SIZE, nullptr);
35
36
    // Start forming sets of name/value pairs.
37
55.0k
    CPLString osWorkingLine;
38
55.0k
    std::string osValue;
39
3.15M
    while (true)
40
3.15M
    {
41
3.15M
        const char *pszNewLine = CPLReadLine2L(fpHdr, MAX_LINE_SIZE, nullptr);
42
3.15M
        if (pszNewLine == nullptr)
43
55.0k
            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.38M
        while (*pszNewLine == ' ')
49
289k
            ++pszNewLine;
50
3.09M
        if (strchr(pszNewLine, '=') == nullptr)
51
2.40M
            continue;
52
53
692k
        osWorkingLine = pszNewLine;
54
55
        // Collect additional lines if we have open curly bracket.
56
692k
        if (osWorkingLine.find("{") != std::string::npos &&
57
82.7k
            osWorkingLine.find("}") == std::string::npos)
58
52.4k
        {
59
52.4k
            do
60
2.42M
            {
61
2.42M
                pszNewLine = CPLReadLine2L(fpHdr, MAX_LINE_SIZE, nullptr);
62
2.42M
                if (pszNewLine)
63
2.40M
                {
64
2.40M
                    osWorkingLine += pszNewLine;
65
2.40M
                }
66
2.42M
                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.42M
            } while (pszNewLine != nullptr &&
73
2.40M
                     strchr(pszNewLine, '}') == nullptr);
74
52.4k
        }
75
76
        // Try to break input into name and value portions. Trim whitespace.
77
692k
        size_t iEqual = osWorkingLine.find("=");
78
79
692k
        if (iEqual != std::string::npos && iEqual > 0)
80
657k
        {
81
657k
            osValue = osWorkingLine.substr(iEqual + 1);
82
657k
            const auto found = osValue.find_first_not_of(" \t");
83
657k
            if (found != std::string::npos)
84
629k
                osValue = osValue.substr(found);
85
28.8k
            else
86
28.8k
                osValue.clear();
87
88
657k
            iEqual--;
89
975k
            while (iEqual > 0 && (osWorkingLine[iEqual] == ' ' ||
90
610k
                                  osWorkingLine[iEqual] == '\t'))
91
318k
            {
92
318k
                iEqual--;
93
318k
            }
94
657k
            osWorkingLine.resize(iEqual + 1);
95
657k
            osWorkingLine.replaceAll(' ', '_');
96
657k
            aosHeaders.SetNameValue(osWorkingLine.c_str(), osValue.c_str());
97
657k
        }
98
692k
    }
99
100
55.0k
    return aosHeaders;
101
55.0k
}
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.6k
{
113
36.6k
    CPLStringList aosList;
114
115
36.6k
    if (!pszCleanInput || pszCleanInput[0] != '{')
116
5.11k
    {
117
5.11k
        return aosList;
118
5.11k
    }
119
120
31.5k
    char *pszInput = CPLStrdup(pszCleanInput);
121
122
31.5k
    int iChar = 1;
123
12.6M
    while (pszInput[iChar] != '}' && pszInput[iChar] != '\0')
124
12.6M
    {
125
        // Find start of token.
126
12.6M
        int iFStart = iChar;
127
12.6M
        while (pszInput[iFStart] == ' ')
128
62.6k
            iFStart++;
129
130
12.6M
        int iFEnd = iFStart;
131
58.6M
        while (pszInput[iFEnd] != ',' && pszInput[iFEnd] != '}' &&
132
46.0M
               pszInput[iFEnd] != '\0')
133
45.9M
            iFEnd++;
134
135
12.6M
        if (pszInput[iFEnd] == '\0')
136
17.5k
            break;
137
138
12.6M
        iChar = iFEnd + 1;
139
12.6M
        iFEnd = iFEnd - 1;
140
141
12.6M
        while (iFEnd > iFStart && pszInput[iFEnd] == ' ')
142
17.4k
            iFEnd--;
143
144
12.6M
        pszInput[iFEnd + 1] = '\0';
145
12.6M
        aosList.AddString(pszInput + iFStart);
146
12.6M
    }
147
148
31.5k
    CPLFree(pszInput);
149
150
31.5k
    return aosList;
151
36.6k
}
152
153
/************************************************************************/
154
/*                        GDALApplyENVIHeaders()                        */
155
/************************************************************************/
156
157
void GDALApplyENVIHeaders(GDALDataset *poDS, const CPLStringList &aosHeaders,
158
                          CSLConstList papszOptions)
159
45.7k
{
160
45.7k
    const int nBands = poDS->GetRasterCount();
161
45.7k
    auto poPamDS = dynamic_cast<GDALPamDataset *>(poDS);
162
45.7k
    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.7k
    const char *pszBandNames = aosHeaders["band_names"];
167
45.7k
    const char *pszWaveLength = aosHeaders["wavelength"];
168
45.7k
    if (pszBandNames || pszWaveLength)
169
656
    {
170
656
        const bool bSetDatasetLevelMetadata = CPLTestBool(CSLFetchNameValueDef(
171
656
            papszOptions, "SET_DATASET_LEVEL_METADATA", "YES"));
172
656
        const bool bSetBandName = CPLTestBool(
173
656
            CSLFetchNameValueDef(papszOptions, "SET_BAND_NAME", "YES"));
174
656
        const CPLStringList aosBandNames(GDALENVISplitList(pszBandNames));
175
656
        const CPLStringList aosWL(GDALENVISplitList(pszWaveLength));
176
656
        const char *pszFWHM = aosHeaders["fwhm"];
177
656
        const CPLStringList aosFWHM(GDALENVISplitList(pszFWHM ? pszFWHM : ""));
178
179
656
        const char *pszWLUnits = nullptr;
180
656
        const int nWLCount = aosWL.size();
181
656
        const int nFWHMCount = aosFWHM.size();
182
656
        if (nWLCount)
183
75
        {
184
            // If WL information is present, process wavelength units.
185
75
            pszWLUnits = aosHeaders["wavelength_units"];
186
75
            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
75
            if (pszWLUnits && bSetDatasetLevelMetadata)
193
0
            {
194
                // Set wavelength units to dataset metadata.
195
0
                poDS->SetMetadataItem("wavelength_units", pszWLUnits);
196
0
            }
197
75
        }
198
199
6.12k
        for (int i = 0; i < nBands; i++)
200
5.46k
        {
201
            // First set up the wavelength names and units if available.
202
5.46k
            std::string osWavelength;
203
5.46k
            if (nWLCount > i)
204
1.50k
            {
205
1.50k
                osWavelength = aosWL[i];
206
1.50k
                if (pszWLUnits)
207
0
                {
208
0
                    osWavelength += " ";
209
0
                    osWavelength += pszWLUnits;
210
0
                }
211
1.50k
            }
212
213
5.46k
            if (bSetBandName)
214
5.46k
            {
215
                // Build the final name for this band.
216
5.46k
                std::string osBandName;
217
5.46k
                if (aosBandNames && CSLCount(aosBandNames) > i)
218
2.71k
                {
219
2.71k
                    osBandName = aosBandNames[i];
220
2.71k
                    if (!osWavelength.empty())
221
540
                    {
222
540
                        osBandName += " (";
223
540
                        osBandName += osWavelength;
224
540
                        osBandName += ")";
225
540
                    }
226
2.71k
                }
227
2.75k
                else
228
2.75k
                {
229
                    // WL but no band names.
230
2.75k
                    osBandName = std::move(osWavelength);
231
2.75k
                }
232
233
                // Description is for internal GDAL usage.
234
5.46k
                poDS->GetRasterBand(i + 1)->SetDescription(osBandName.c_str());
235
236
                // Metadata field named Band_1, etc. Needed for ArcGIS integration.
237
5.46k
                const std::string osBandId = CPLSPrintf("Band_%i", i + 1);
238
5.46k
                if (bSetDatasetLevelMetadata)
239
5.46k
                    poDS->SetMetadataItem(osBandId.c_str(), osBandName.c_str());
240
5.46k
            }
241
242
5.46k
            const auto ConvertWaveLength =
243
5.46k
                [pszWLUnits](double dfVal) -> const char *
244
5.46k
            {
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
5.46k
            if (nWLCount > i)
267
1.50k
            {
268
1.50k
                poDS->GetRasterBand(i + 1)->SetMetadataItem("wavelength",
269
1.50k
                                                            aosWL[i]);
270
271
1.50k
                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.50k
            }
285
286
5.46k
            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
5.46k
        }
295
656
    }
296
297
45.7k
    if (CPLTestBool(
298
45.7k
            CSLFetchNameValueDef(papszOptions, "APPLY_DEFAULT_BANDS", "YES")))
299
45.7k
    {
300
        // Apply "default bands" if we have it to set RGB color interpretation.
301
45.7k
        const char *pszDefaultBands = aosHeaders["default_bands"];
302
45.7k
        if (pszDefaultBands)
303
24
        {
304
24
            const CPLStringList aosDefaultBands(
305
24
                GDALENVISplitList(pszDefaultBands));
306
24
            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
24
            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
24
        }
333
45.7k
    }
334
335
    // Apply data offset values
336
45.7k
    if (const char *pszDataOffsetValues = aosHeaders["data_offset_values"])
337
44
    {
338
44
        const CPLStringList aosValues(GDALENVISplitList(pszDataOffsetValues));
339
44
        if (aosValues.size() == nBands)
340
24
        {
341
110
            for (int i = 0; i < nBands; ++i)
342
86
                poDS->GetRasterBand(i + 1)->SetOffset(CPLAtof(aosValues[i]));
343
24
        }
344
44
    }
345
346
    // Apply data gain values
347
45.7k
    if (const char *pszDataGainValues = aosHeaders["data_gain_values"])
348
49
    {
349
49
        const CPLStringList aosValues(GDALENVISplitList(pszDataGainValues));
350
49
        if (aosValues.size() == nBands)
351
27
        {
352
54
            for (int i = 0; i < nBands; ++i)
353
27
            {
354
27
                poDS->GetRasterBand(i + 1)->SetScale(CPLAtof(aosValues[i]));
355
27
            }
356
27
        }
357
49
    }
358
359
    // Apply class names if we have them.
360
45.7k
    if (const char *pszClassNames = aosHeaders["class_names"])
361
154
    {
362
154
        poDS->GetRasterBand(1)->SetCategoryNames(
363
154
            GDALENVISplitList(pszClassNames).List());
364
154
    }
365
366
45.7k
    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.7k
    if (CPLTestBool(
381
45.7k
            CSLFetchNameValueDef(papszOptions, "APPLY_CLASS_LOOKUP", "YES")))
382
45.7k
    {
383
        // Apply colormap if we have one.
384
45.7k
        const char *pszClassLookup = aosHeaders["class_lookup"];
385
45.7k
        if (pszClassLookup != nullptr)
386
396
        {
387
396
            const CPLStringList aosClassColors(
388
396
                GDALENVISplitList(pszClassLookup));
389
396
            const int nColorValueCount = aosClassColors.size();
390
396
            GDALColorTable oCT;
391
392
3.90M
            for (int i = 0; i * 3 + 2 < nColorValueCount; i++)
393
3.90M
            {
394
3.90M
                const GDALColorEntry sEntry = {
395
3.90M
                    static_cast<short>(std::clamp(
396
3.90M
                        atoi(aosClassColors[i * 3 + 0]), 0, 255)),  // Red
397
3.90M
                    static_cast<short>(std::clamp(
398
3.90M
                        atoi(aosClassColors[i * 3 + 1]), 0, 255)),  // Green
399
3.90M
                    static_cast<short>(std::clamp(
400
3.90M
                        atoi(aosClassColors[i * 3 + 2]), 0, 255)),  // Blue
401
3.90M
                    255};
402
3.90M
                oCT.SetColorEntry(i, &sEntry);
403
3.90M
            }
404
405
396
            poDS->GetRasterBand(1)->SetColorTable(&oCT);
406
396
            poDS->GetRasterBand(1)->SetColorInterpretation(GCI_PaletteIndex);
407
396
        }
408
45.7k
    }
409
410
45.7k
    if (CPLTestBool(CSLFetchNameValueDef(papszOptions,
411
45.7k
                                         "APPLY_DATA_IGNORE_VALUE", "YES")))
412
45.7k
    {
413
        // Set the nodata value if it is present.
414
45.7k
        const char *pszDataIgnoreValue = aosHeaders["data_ignore_value"];
415
45.7k
        if (pszDataIgnoreValue != nullptr)
416
1.49k
        {
417
15.5k
            for (int i = 0; i < nBands; i++)
418
14.0k
            {
419
14.0k
                auto poBand =
420
14.0k
                    dynamic_cast<RawRasterBand *>(poDS->GetRasterBand(i + 1));
421
14.0k
                if (poBand)
422
14.0k
                    poBand->SetNoDataValue(CPLAtof(pszDataIgnoreValue));
423
14.0k
            }
424
1.49k
        }
425
45.7k
    }
426
427
45.7k
    if (poPamDS)
428
45.7k
        poPamDS->SetPamFlags(nPAMFlagsBackup);
429
45.7k
}