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