LCOV - code coverage report
Current view: top level - gcore - enviutils.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 192 202 95.0 %
Date: 2025-12-01 18:11:08 Functions: 4 4 100.0 %

          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 : }

Generated by: LCOV version 1.14