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 1237 : CPLStringList GDALReadENVIHeader(VSILFILE *fpHdr)
27 :
28 : {
29 1237 : CPLStringList aosHeaders;
30 :
31 1237 : constexpr int MAX_LINE_SIZE = 10000;
32 :
33 : // Skip first line with "ENVI"
34 1237 : CPLReadLine2L(fpHdr, MAX_LINE_SIZE, nullptr);
35 :
36 : // Start forming sets of name/value pairs.
37 2474 : CPLString osWorkingLine;
38 2474 : std::string osValue;
39 : while (true)
40 : {
41 13641 : const char *pszNewLine = CPLReadLine2L(fpHdr, MAX_LINE_SIZE, nullptr);
42 13641 : if (pszNewLine == nullptr)
43 1237 : 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 12406 : while (*pszNewLine == ' ')
49 2 : ++pszNewLine;
50 12404 : if (strchr(pszNewLine, '=') == nullptr)
51 0 : continue;
52 :
53 12404 : osWorkingLine = pszNewLine;
54 :
55 : // Collect additional lines if we have open curly bracket.
56 14833 : if (osWorkingLine.find("{") != std::string::npos &&
57 2429 : osWorkingLine.find("}") == std::string::npos)
58 : {
59 161 : do
60 : {
61 1370 : pszNewLine = CPLReadLine2L(fpHdr, MAX_LINE_SIZE, nullptr);
62 1370 : if (pszNewLine)
63 : {
64 1370 : osWorkingLine += pszNewLine;
65 : }
66 1370 : 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 1370 : } while (pszNewLine != nullptr &&
73 1370 : strchr(pszNewLine, '}') == nullptr);
74 : }
75 :
76 : // Try to break input into name and value portions. Trim whitespace.
77 12404 : size_t iEqual = osWorkingLine.find("=");
78 :
79 12404 : if (iEqual != std::string::npos && iEqual > 0)
80 : {
81 12404 : osValue = osWorkingLine.substr(iEqual + 1);
82 12404 : const auto found = osValue.find_first_not_of(" \t");
83 12404 : if (found != std::string::npos)
84 12404 : osValue = osValue.substr(found);
85 : else
86 0 : osValue.clear();
87 :
88 12404 : iEqual--;
89 42036 : while (iEqual > 0 && (osWorkingLine[iEqual] == ' ' ||
90 12404 : osWorkingLine[iEqual] == '\t'))
91 : {
92 17228 : iEqual--;
93 : }
94 12404 : osWorkingLine.resize(iEqual + 1);
95 12404 : osWorkingLine.replaceAll(' ', '_');
96 12404 : aosHeaders.SetNameValue(osWorkingLine.c_str(), osValue.c_str());
97 : }
98 12404 : }
99 :
100 1237 : 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 569 : CPLStringList GDALENVISplitList(const char *pszCleanInput)
111 :
112 : {
113 569 : CPLStringList aosList;
114 :
115 569 : if (!pszCleanInput || pszCleanInput[0] != '{')
116 : {
117 258 : return aosList;
118 : }
119 :
120 311 : char *pszInput = CPLStrdup(pszCleanInput);
121 :
122 311 : int iChar = 1;
123 2038 : while (pszInput[iChar] != '}' && pszInput[iChar] != '\0')
124 : {
125 : // Find start of token.
126 1727 : int iFStart = iChar;
127 3262 : while (pszInput[iFStart] == ' ')
128 1535 : iFStart++;
129 :
130 1727 : int iFEnd = iFStart;
131 11739 : while (pszInput[iFEnd] != ',' && pszInput[iFEnd] != '}' &&
132 10012 : pszInput[iFEnd] != '\0')
133 10012 : iFEnd++;
134 :
135 1727 : if (pszInput[iFEnd] == '\0')
136 0 : break;
137 :
138 1727 : iChar = iFEnd + 1;
139 1727 : iFEnd = iFEnd - 1;
140 :
141 1769 : while (iFEnd > iFStart && pszInput[iFEnd] == ' ')
142 42 : iFEnd--;
143 :
144 1727 : pszInput[iFEnd + 1] = '\0';
145 1727 : aosList.AddString(pszInput + iFStart);
146 : }
147 :
148 311 : CPLFree(pszInput);
149 :
150 311 : return aosList;
151 : }
152 :
153 : /************************************************************************/
154 : /* GDALApplyENVIHeaders() */
155 : /************************************************************************/
156 :
157 258 : void GDALApplyENVIHeaders(GDALDataset *poDS, const CPLStringList &aosHeaders,
158 : CSLConstList papszOptions)
159 : {
160 258 : const int nBands = poDS->GetRasterCount();
161 258 : auto poPamDS = dynamic_cast<GDALPamDataset *>(poDS);
162 258 : 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 258 : const char *pszBandNames = aosHeaders["band_names"];
167 258 : const char *pszWaveLength = aosHeaders["wavelength"];
168 258 : 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 24 : "CENTRAL_WAVELENGTH_UM", pszVal, "IMAGERY");
281 : }
282 : }
283 : }
284 :
285 243 : if (nFWHMCount > i && pszWLUnits)
286 : {
287 24 : if (const char *pszVal = ConvertWaveLength(CPLAtof(aosFWHM[i])))
288 : {
289 24 : poDS->GetRasterBand(i + 1)->SetMetadataItem(
290 24 : "FWHM_UM", pszVal, "IMAGERY");
291 : }
292 : }
293 : }
294 : }
295 :
296 258 : if (CPLTestBool(
297 : CSLFetchNameValueDef(papszOptions, "APPLY_DEFAULT_BANDS", "YES")))
298 : {
299 : // Apply "default bands" if we have it to set RGB color interpretation.
300 252 : const char *pszDefaultBands = aosHeaders["default_bands"];
301 252 : if (pszDefaultBands)
302 : {
303 : const CPLStringList aosDefaultBands(
304 26 : GDALENVISplitList(pszDefaultBands));
305 13 : if (aosDefaultBands.size() == 3)
306 : {
307 7 : const int nRBand = atoi(aosDefaultBands[0]);
308 7 : const int nGBand = atoi(aosDefaultBands[1]);
309 7 : const int nBBand = atoi(aosDefaultBands[2]);
310 7 : if (nRBand >= 1 && nRBand <= nBands && nGBand >= 1 &&
311 7 : nGBand <= nBands && nBBand >= 1 && nBBand <= nBands &&
312 7 : nRBand != nGBand && nRBand != nBBand && nGBand != nBBand)
313 : {
314 7 : poDS->GetRasterBand(nRBand)->SetColorInterpretation(
315 7 : GCI_RedBand);
316 7 : poDS->GetRasterBand(nGBand)->SetColorInterpretation(
317 7 : GCI_GreenBand);
318 7 : poDS->GetRasterBand(nBBand)->SetColorInterpretation(
319 7 : GCI_BlueBand);
320 : }
321 : }
322 6 : else if (aosDefaultBands.size() == 1)
323 : {
324 6 : const int nGrayBand = atoi(aosDefaultBands[0]);
325 6 : if (nGrayBand >= 1 && nGrayBand <= nBands)
326 : {
327 6 : poDS->GetRasterBand(nGrayBand)->SetColorInterpretation(
328 6 : GCI_GrayIndex);
329 : }
330 : }
331 : }
332 : }
333 :
334 : // Apply data offset values
335 258 : if (const char *pszDataOffsetValues = aosHeaders["data_offset_values"])
336 : {
337 6 : const CPLStringList aosValues(GDALENVISplitList(pszDataOffsetValues));
338 3 : if (aosValues.size() == nBands)
339 : {
340 12 : for (int i = 0; i < nBands; ++i)
341 9 : poDS->GetRasterBand(i + 1)->SetOffset(CPLAtof(aosValues[i]));
342 : }
343 : }
344 :
345 : // Apply data gain values
346 258 : if (const char *pszDataGainValues = aosHeaders["data_gain_values"])
347 : {
348 6 : const CPLStringList aosValues(GDALENVISplitList(pszDataGainValues));
349 3 : if (aosValues.size() == nBands)
350 : {
351 12 : for (int i = 0; i < nBands; ++i)
352 : {
353 9 : poDS->GetRasterBand(i + 1)->SetScale(CPLAtof(aosValues[i]));
354 : }
355 : }
356 : }
357 :
358 : // Apply class names if we have them.
359 258 : if (const char *pszClassNames = aosHeaders["class_names"])
360 : {
361 6 : poDS->GetRasterBand(1)->SetCategoryNames(
362 6 : GDALENVISplitList(pszClassNames).List());
363 : }
364 :
365 258 : if (const char *pszBBL = aosHeaders["bbl"])
366 : {
367 12 : const CPLStringList aosValues(GDALENVISplitList(pszBBL));
368 6 : if (aosValues.size() == nBands)
369 : {
370 12 : for (int i = 0; i < nBands; ++i)
371 : {
372 12 : poDS->GetRasterBand(i + 1)->SetMetadataItem(
373 : "good_band",
374 6 : strcmp(aosValues[i], "1") == 0 ? "true" : "false");
375 : }
376 : }
377 : }
378 :
379 258 : if (CPLTestBool(
380 : CSLFetchNameValueDef(papszOptions, "APPLY_CLASS_LOOKUP", "YES")))
381 : {
382 : // Apply colormap if we have one.
383 252 : const char *pszClassLookup = aosHeaders["class_lookup"];
384 252 : if (pszClassLookup != nullptr)
385 : {
386 : const CPLStringList aosClassColors(
387 6 : GDALENVISplitList(pszClassLookup));
388 3 : const int nColorValueCount = aosClassColors.size();
389 6 : GDALColorTable oCT;
390 :
391 9 : for (int i = 0; i * 3 + 2 < nColorValueCount; i++)
392 : {
393 6 : const GDALColorEntry sEntry = {
394 6 : static_cast<short>(std::clamp(
395 6 : atoi(aosClassColors[i * 3 + 0]), 0, 255)), // Red
396 6 : static_cast<short>(std::clamp(
397 6 : atoi(aosClassColors[i * 3 + 1]), 0, 255)), // Green
398 6 : static_cast<short>(std::clamp(
399 6 : atoi(aosClassColors[i * 3 + 2]), 0, 255)), // Blue
400 12 : 255};
401 6 : oCT.SetColorEntry(i, &sEntry);
402 : }
403 :
404 3 : poDS->GetRasterBand(1)->SetColorTable(&oCT);
405 3 : poDS->GetRasterBand(1)->SetColorInterpretation(GCI_PaletteIndex);
406 : }
407 : }
408 :
409 258 : if (CPLTestBool(CSLFetchNameValueDef(papszOptions,
410 : "APPLY_DATA_IGNORE_VALUE", "YES")))
411 : {
412 : // Set the nodata value if it is present.
413 252 : const char *pszDataIgnoreValue = aosHeaders["data_ignore_value"];
414 252 : if (pszDataIgnoreValue != nullptr)
415 : {
416 4 : for (int i = 0; i < nBands; i++)
417 : {
418 : auto poBand =
419 2 : dynamic_cast<RawRasterBand *>(poDS->GetRasterBand(i + 1));
420 2 : if (poBand)
421 2 : poBand->SetNoDataValue(CPLAtof(pszDataIgnoreValue));
422 : }
423 : }
424 : }
425 :
426 258 : if (poPamDS)
427 258 : poPamDS->SetPamFlags(nPAMFlagsBackup);
428 258 : }
|