Coverage Report

Created: 2025-11-16 06:25

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