LCOV - code coverage report
Current view: top level - frmts/raw - envidataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1185 1466 80.8 %
Date: 2024-11-21 22:18:42 Functions: 47 49 95.9 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  ENVI .hdr Driver
       4             :  * Purpose:  Implementation of ENVI .hdr labelled raw raster support.
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  * Maintainer: Chris Padwick (cpadwick at ittvis.com)
       7             :  *
       8             :  ******************************************************************************
       9             :  * Copyright (c) 2002, Frank Warmerdam
      10             :  * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com>
      11             :  *
      12             :  * SPDX-License-Identifier: MIT
      13             :  ****************************************************************************/
      14             : 
      15             : #include "cpl_port.h"
      16             : #include "envidataset.h"
      17             : #include "rawdataset.h"
      18             : 
      19             : #include <climits>
      20             : #include <cmath>
      21             : #include <cstdlib>
      22             : #include <cstring>
      23             : #if HAVE_FCNTL_H
      24             : #include <fcntl.h>
      25             : #endif
      26             : 
      27             : #include <algorithm>
      28             : #include <limits>
      29             : #include <string>
      30             : 
      31             : #include "cpl_conv.h"
      32             : #include "cpl_error.h"
      33             : #include "cpl_string.h"
      34             : #include "cpl_vsi.h"
      35             : #include "gdal.h"
      36             : #include "gdal_frmts.h"
      37             : #include "gdal_priv.h"
      38             : #include "ogr_core.h"
      39             : #include "ogr_spatialref.h"
      40             : #include "ogr_srs_api.h"
      41             : 
      42             : // TODO(schwehr): This really should be defined in port/somewhere.h.
      43             : constexpr double kdfDegToRad = M_PI / 180.0;
      44             : constexpr double kdfRadToDeg = 180.0 / M_PI;
      45             : 
      46             : #include "usgs_esri_zones.h"
      47             : 
      48             : /************************************************************************/
      49             : /*                           ITTVISToUSGSZone()                         */
      50             : /*                                                                      */
      51             : /*      Convert ITTVIS style state plane zones to NOS style state       */
      52             : /*      plane zones.  The ENVI default is to use the new NOS zones,     */
      53             : /*      but the old state plane zones can be used.  Handle this.        */
      54             : /************************************************************************/
      55             : 
      56           0 : static int ITTVISToUSGSZone(int nITTVISZone)
      57             : 
      58             : {
      59             :     // TODO(schwehr): int anUsgsEsriZones[] -> a std::set and std::map.
      60           0 :     const int nPairs = sizeof(anUsgsEsriZones) / (2 * sizeof(int));
      61             : 
      62             :     // Default is to use the zone as-is, as long as it is in the
      63             :     // available list
      64           0 :     for (int i = 0; i < nPairs; i++)
      65             :     {
      66           0 :         if (anUsgsEsriZones[i * 2] == nITTVISZone)
      67           0 :             return anUsgsEsriZones[i * 2];
      68             :     }
      69             : 
      70             :     // If not found in the new style, see if it is present in the
      71             :     // old style list and convert it.  We don't expect to see this
      72             :     // often, but older files allowed it and may still exist.
      73           0 :     for (int i = 0; i < nPairs; i++)
      74             :     {
      75           0 :         if (anUsgsEsriZones[i * 2 + 1] == nITTVISZone)
      76           0 :             return anUsgsEsriZones[i * 2];
      77             :     }
      78             : 
      79           0 :     return nITTVISZone;  // Perhaps it *is* the USGS zone?
      80             : }
      81             : 
      82             : /************************************************************************/
      83             : /*                            ENVIDataset()                             */
      84             : /************************************************************************/
      85             : 
      86         251 : ENVIDataset::ENVIDataset()
      87             :     : fpImage(nullptr), fp(nullptr), pszHDRFilename(nullptr),
      88             :       bFoundMapinfo(false), bHeaderDirty(false), bFillFile(false),
      89         251 :       interleave(BSQ)
      90             : {
      91         251 :     adfGeoTransform[0] = 0.0;
      92         251 :     adfGeoTransform[1] = 1.0;
      93         251 :     adfGeoTransform[2] = 0.0;
      94         251 :     adfGeoTransform[3] = 0.0;
      95         251 :     adfGeoTransform[4] = 0.0;
      96         251 :     adfGeoTransform[5] = 1.0;
      97         251 : }
      98             : 
      99             : /************************************************************************/
     100             : /*                            ~ENVIDataset()                            */
     101             : /************************************************************************/
     102             : 
     103         502 : ENVIDataset::~ENVIDataset()
     104             : 
     105             : {
     106         251 :     ENVIDataset::Close();
     107         502 : }
     108             : 
     109             : /************************************************************************/
     110             : /*                              Close()                                 */
     111             : /************************************************************************/
     112             : 
     113         495 : CPLErr ENVIDataset::Close()
     114             : {
     115         495 :     CPLErr eErr = CE_None;
     116         495 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
     117             :     {
     118         251 :         if (ENVIDataset::FlushCache(true) != CE_None)
     119           0 :             eErr = CE_Failure;
     120             : 
     121         251 :         if (fpImage)
     122             :         {
     123             :             // Make sure the binary file has the expected size
     124         249 :             if (!IsMarkedSuppressOnClose() && bFillFile && nBands > 0)
     125             :             {
     126          92 :                 const int nDataSize = GDALGetDataTypeSizeBytes(
     127             :                     GetRasterBand(1)->GetRasterDataType());
     128          92 :                 const vsi_l_offset nExpectedFileSize =
     129          92 :                     static_cast<vsi_l_offset>(nRasterXSize) * nRasterYSize *
     130          92 :                     nBands * nDataSize;
     131          92 :                 if (VSIFSeekL(fpImage, 0, SEEK_END) != 0)
     132             :                 {
     133           0 :                     eErr = CE_Failure;
     134           0 :                     CPLError(CE_Failure, CPLE_FileIO, "I/O error");
     135             :                 }
     136          92 :                 if (VSIFTellL(fpImage) < nExpectedFileSize)
     137             :                 {
     138          42 :                     GByte byVal = 0;
     139          42 :                     if (VSIFSeekL(fpImage, nExpectedFileSize - 1, SEEK_SET) !=
     140          84 :                             0 ||
     141          42 :                         VSIFWriteL(&byVal, 1, 1, fpImage) == 0)
     142             :                     {
     143           0 :                         eErr = CE_Failure;
     144           0 :                         CPLError(CE_Failure, CPLE_FileIO, "I/O error");
     145             :                     }
     146             :                 }
     147             :             }
     148         249 :             if (VSIFCloseL(fpImage) != 0)
     149             :             {
     150           0 :                 eErr = CE_Failure;
     151           0 :                 CPLError(CE_Failure, CPLE_FileIO, "I/O error");
     152             :             }
     153             :         }
     154         251 :         if (fp)
     155             :         {
     156         251 :             if (VSIFCloseL(fp) != 0)
     157             :             {
     158           0 :                 eErr = CE_Failure;
     159           0 :                 CPLError(CE_Failure, CPLE_FileIO, "I/O error");
     160             :             }
     161             :         }
     162         251 :         if (!m_asGCPs.empty())
     163             :         {
     164           2 :             GDALDeinitGCPs(static_cast<int>(m_asGCPs.size()), m_asGCPs.data());
     165             :         }
     166             : 
     167             :         // Should be called before pszHDRFilename is freed.
     168         251 :         CleanupPostFileClosing();
     169             : 
     170         251 :         CPLFree(pszHDRFilename);
     171             : 
     172         251 :         if (GDALPamDataset::Close() != CE_None)
     173           0 :             eErr = CE_Failure;
     174             :     }
     175         495 :     return eErr;
     176             : }
     177             : 
     178             : /************************************************************************/
     179             : /*                             FlushCache()                             */
     180             : /************************************************************************/
     181             : 
     182         260 : CPLErr ENVIDataset::FlushCache(bool bAtClosing)
     183             : 
     184             : {
     185         260 :     CPLErr eErr = RawDataset::FlushCache(bAtClosing);
     186             : 
     187         260 :     GDALRasterBand *band = GetRasterCount() > 0 ? GetRasterBand(1) : nullptr;
     188             : 
     189         260 :     if (!band || !bHeaderDirty || (bAtClosing && IsMarkedSuppressOnClose()))
     190         178 :         return eErr;
     191             : 
     192             :     // If opening an existing file in Update mode (i.e. "r+") we need to make
     193             :     // sure any existing content is cleared, otherwise the file may contain
     194             :     // trailing content from the previous write.
     195          82 :     if (VSIFTruncateL(fp, 0) != 0)
     196           0 :         return CE_Failure;
     197             : 
     198          82 :     if (VSIFSeekL(fp, 0, SEEK_SET) != 0)
     199           0 :         return CE_Failure;
     200             : 
     201             :     // Rewrite out the header.
     202          82 :     bool bOK = VSIFPrintfL(fp, "ENVI\n") >= 0;
     203          82 :     if ("" != sDescription)
     204          82 :         bOK &= VSIFPrintfL(fp, "description = {\n%s}\n",
     205          82 :                            sDescription.c_str()) >= 0;
     206          82 :     bOK &= VSIFPrintfL(fp, "samples = %d\nlines   = %d\nbands   = %d\n",
     207          82 :                        nRasterXSize, nRasterYSize, nBands) >= 0;
     208             : 
     209          82 :     char **catNames = band->GetCategoryNames();
     210             : 
     211          82 :     bOK &= VSIFPrintfL(fp, "header offset = 0\n") >= 0;
     212          82 :     if (nullptr == catNames)
     213          81 :         bOK &= VSIFPrintfL(fp, "file type = ENVI Standard\n") >= 0;
     214             :     else
     215           1 :         bOK &= VSIFPrintfL(fp, "file type = ENVI Classification\n") >= 0;
     216             : 
     217          82 :     const int iENVIType = GetEnviType(band->GetRasterDataType());
     218          82 :     bOK &= VSIFPrintfL(fp, "data type = %d\n", iENVIType) >= 0;
     219          82 :     const char *pszInterleaving = nullptr;
     220          82 :     switch (interleave)
     221             :     {
     222           4 :         case BIP:
     223           4 :             pszInterleaving = "bip";  // Interleaved by pixel.
     224           4 :             break;
     225           2 :         case BIL:
     226           2 :             pszInterleaving = "bil";  // Interleaved by line.
     227           2 :             break;
     228          76 :         case BSQ:
     229          76 :             pszInterleaving = "bsq";  // Band sequential by default.
     230          76 :             break;
     231           0 :         default:
     232           0 :             pszInterleaving = "bsq";
     233           0 :             break;
     234             :     }
     235          82 :     bOK &= VSIFPrintfL(fp, "interleave = %s\n", pszInterleaving) >= 0;
     236             : 
     237          82 :     const char *pszByteOrder = m_aosHeader["byte_order"];
     238          82 :     if (pszByteOrder)
     239             :     {
     240             :         // Supposed to be required
     241          82 :         bOK &= VSIFPrintfL(fp, "byte order = %s\n", pszByteOrder) >= 0;
     242             :     }
     243             : 
     244             :     // Write class and color information.
     245          82 :     catNames = band->GetCategoryNames();
     246          82 :     if (nullptr != catNames)
     247             :     {
     248           1 :         int nrClasses = 0;
     249           3 :         while (*catNames++)
     250           2 :             ++nrClasses;
     251             : 
     252           1 :         if (nrClasses > 0)
     253             :         {
     254           1 :             bOK &= VSIFPrintfL(fp, "classes = %d\n", nrClasses) >= 0;
     255             : 
     256           1 :             GDALColorTable *colorTable = band->GetColorTable();
     257           1 :             if (nullptr != colorTable)
     258             :             {
     259             :                 const int nrColors =
     260           1 :                     std::min(nrClasses, colorTable->GetColorEntryCount());
     261           1 :                 bOK &= VSIFPrintfL(fp, "class lookup = {\n") >= 0;
     262           3 :                 for (int i = 0; i < nrColors; ++i)
     263             :                 {
     264           2 :                     const GDALColorEntry *color = colorTable->GetColorEntry(i);
     265           4 :                     bOK &= VSIFPrintfL(fp, "%d, %d, %d", color->c1, color->c2,
     266           2 :                                        color->c3) >= 0;
     267           2 :                     if (i < nrColors - 1)
     268             :                     {
     269           1 :                         bOK &= VSIFPrintfL(fp, ", ") >= 0;
     270           1 :                         if (0 == (i + 1) % 5)
     271           0 :                             bOK &= VSIFPrintfL(fp, "\n") >= 0;
     272             :                     }
     273             :                 }
     274           1 :                 bOK &= VSIFPrintfL(fp, "}\n") >= 0;
     275             :             }
     276             : 
     277           1 :             catNames = band->GetCategoryNames();
     278           1 :             if (nullptr != *catNames)
     279             :             {
     280           1 :                 bOK &= VSIFPrintfL(fp, "class names = {\n%s", *catNames) >= 0;
     281           1 :                 catNames++;
     282           1 :                 int i = 0;
     283           2 :                 while (*catNames)
     284             :                 {
     285           1 :                     bOK &= VSIFPrintfL(fp, ",") >= 0;
     286           1 :                     if (0 == (++i) % 5)
     287           0 :                         bOK &= VSIFPrintfL(fp, "\n") >= 0;
     288           1 :                     bOK &= VSIFPrintfL(fp, " %s", *catNames) >= 0;
     289           1 :                     catNames++;
     290             :                 }
     291           1 :                 bOK &= VSIFPrintfL(fp, "}\n") >= 0;
     292             :             }
     293             :         }
     294             :     }
     295             : 
     296             :     // Write the rest of header.
     297             : 
     298             :     // Only one map info type should be set:
     299             :     //     - rpc
     300             :     //     - pseudo/gcp
     301             :     //     - standard
     302          82 :     if (!WriteRpcInfo())  // Are rpcs in the metadata?
     303             :     {
     304          81 :         if (!WritePseudoGcpInfo())  // are gcps in the metadata
     305             :         {
     306          80 :             WriteProjectionInfo();  // standard - affine xform/coord sys str
     307             :         }
     308             :     }
     309             : 
     310          82 :     bOK &= VSIFPrintfL(fp, "band names = {\n") >= 0;
     311         248 :     for (int i = 1; i <= nBands; i++)
     312             :     {
     313         332 :         CPLString sBandDesc = GetRasterBand(i)->GetDescription();
     314             : 
     315         166 :         if (sBandDesc == "")
     316         141 :             sBandDesc = CPLSPrintf("Band %d", i);
     317         166 :         bOK &= VSIFPrintfL(fp, "%s", sBandDesc.c_str()) >= 0;
     318         166 :         if (i != nBands)
     319          84 :             bOK &= VSIFPrintfL(fp, ",\n") >= 0;
     320             :     }
     321          82 :     bOK &= VSIFPrintfL(fp, "}\n") >= 0;
     322             : 
     323          82 :     int bHasNoData = FALSE;
     324          82 :     double dfNoDataValue = band->GetNoDataValue(&bHasNoData);
     325          82 :     if (bHasNoData)
     326             :     {
     327           5 :         bOK &=
     328           5 :             VSIFPrintfL(fp, "data ignore value = %.17g\n", dfNoDataValue) >= 0;
     329             :     }
     330             : 
     331             :     // Write "data offset values", if needed
     332             :     {
     333          82 :         bool bHasOffset = false;
     334         248 :         for (int i = 1; i <= nBands; i++)
     335             :         {
     336         166 :             int bHasValue = FALSE;
     337         166 :             CPL_IGNORE_RET_VAL(GetRasterBand(i)->GetOffset(&bHasValue));
     338         166 :             if (bHasValue)
     339           1 :                 bHasOffset = true;
     340             :         }
     341          82 :         if (bHasOffset)
     342             :         {
     343           1 :             bOK &= VSIFPrintfL(fp, "data offset values = {") >= 0;
     344           4 :             for (int i = 1; i <= nBands; i++)
     345             :             {
     346           3 :                 int bHasValue = FALSE;
     347           3 :                 double dfValue = GetRasterBand(i)->GetOffset(&bHasValue);
     348           3 :                 if (!bHasValue)
     349           2 :                     dfValue = 0;
     350           3 :                 bOK &= VSIFPrintfL(fp, "%.17g", dfValue) >= 0;
     351           3 :                 if (i != nBands)
     352           2 :                     bOK &= VSIFPrintfL(fp, ", ") >= 0;
     353             :             }
     354           1 :             bOK &= VSIFPrintfL(fp, "}\n") >= 0;
     355             :         }
     356             :     }
     357             : 
     358             :     // Write "data gain values", if needed
     359             :     {
     360          82 :         bool bHasScale = false;
     361         248 :         for (int i = 1; i <= nBands; i++)
     362             :         {
     363         166 :             int bHasValue = FALSE;
     364         166 :             CPL_IGNORE_RET_VAL(GetRasterBand(i)->GetScale(&bHasValue));
     365         166 :             if (bHasValue)
     366           1 :                 bHasScale = true;
     367             :         }
     368          82 :         if (bHasScale)
     369             :         {
     370           1 :             bOK &= VSIFPrintfL(fp, "data gain values = {") >= 0;
     371           4 :             for (int i = 1; i <= nBands; i++)
     372             :             {
     373           3 :                 int bHasValue = FALSE;
     374           3 :                 double dfValue = GetRasterBand(i)->GetScale(&bHasValue);
     375           3 :                 if (!bHasValue)
     376           2 :                     dfValue = 1;
     377           3 :                 bOK &= VSIFPrintfL(fp, "%.17g", dfValue) >= 0;
     378           3 :                 if (i != nBands)
     379           2 :                     bOK &= VSIFPrintfL(fp, ", ") >= 0;
     380             :             }
     381           1 :             bOK &= VSIFPrintfL(fp, "}\n") >= 0;
     382             :         }
     383             :     }
     384             : 
     385             :     // Write the metadata that was read into the ENVI domain.
     386          82 :     char **papszENVIMetadata = GetMetadata("ENVI");
     387         164 :     if (CSLFetchNameValue(papszENVIMetadata, "default bands") == nullptr &&
     388          82 :         CSLFetchNameValue(papszENVIMetadata, "default_bands") == nullptr)
     389             :     {
     390          82 :         int nGrayBand = 0;
     391          82 :         int nRBand = 0;
     392          82 :         int nGBand = 0;
     393          82 :         int nBBand = 0;
     394         248 :         for (int i = 1; i <= nBands; i++)
     395             :         {
     396         166 :             const auto eInterp = GetRasterBand(i)->GetColorInterpretation();
     397         166 :             if (eInterp == GCI_GrayIndex)
     398             :             {
     399           4 :                 if (nGrayBand == 0)
     400           3 :                     nGrayBand = i;
     401             :                 else
     402           1 :                     nGrayBand = -1;
     403             :             }
     404         162 :             else if (eInterp == GCI_RedBand)
     405             :             {
     406           5 :                 if (nRBand == 0)
     407           4 :                     nRBand = i;
     408             :                 else
     409           1 :                     nRBand = -1;
     410             :             }
     411         157 :             else if (eInterp == GCI_GreenBand)
     412             :             {
     413           5 :                 if (nGBand == 0)
     414           4 :                     nGBand = i;
     415             :                 else
     416           1 :                     nGBand = -1;
     417             :             }
     418         152 :             else if (eInterp == GCI_BlueBand)
     419             :             {
     420           5 :                 if (nBBand == 0)
     421           4 :                     nBBand = i;
     422             :                 else
     423           1 :                     nBBand = -1;
     424             :             }
     425             :         }
     426          82 :         if (nRBand > 0 && nGBand > 0 && nBBand > 0)
     427             :         {
     428           3 :             bOK &= VSIFPrintfL(fp, "default bands = {%d, %d, %d}\n", nRBand,
     429           3 :                                nGBand, nBBand) >= 0;
     430             :         }
     431          79 :         else if (nGrayBand > 0 && nRBand == 0 && nGBand == 0 && nBBand == 0)
     432             :         {
     433           2 :             bOK &= VSIFPrintfL(fp, "default bands = {%d}\n", nGrayBand) >= 0;
     434             :         }
     435             :     }
     436             : 
     437          82 :     const int count = CSLCount(papszENVIMetadata);
     438             : 
     439             :     // For every item of metadata in the ENVI domain.
     440         740 :     for (int i = 0; i < count; i++)
     441             :     {
     442             :         // Split the entry into two parts at the = character.
     443         658 :         char *pszEntry = papszENVIMetadata[i];
     444         658 :         char **papszTokens = CSLTokenizeString2(
     445             :             pszEntry, "=", CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES);
     446             : 
     447         658 :         if (CSLCount(papszTokens) != 2)
     448             :         {
     449           1 :             CPLDebug("ENVI",
     450             :                      "Line of header file could not be split at = into "
     451             :                      "two elements: %s",
     452           1 :                      papszENVIMetadata[i]);
     453           1 :             CSLDestroy(papszTokens);
     454         657 :             continue;
     455             :         }
     456             :         // Replace _'s in the string with spaces
     457         657 :         std::string poKey(papszTokens[0]);
     458         657 :         std::replace(poKey.begin(), poKey.end(), '_', ' ');
     459             : 
     460             :         // Don't write it out if it is one of the bits of metadata that is
     461             :         // written out elsewhere in this routine.
     462        1804 :         if (poKey == "description" || poKey == "samples" || poKey == "lines" ||
     463        1233 :             poKey == "bands" || poKey == "header offset" ||
     464         747 :             poKey == "file type" || poKey == "data type" ||
     465         261 :             poKey == "interleave" || poKey == "byte order" ||
     466          16 :             poKey == "class names" || poKey == "band names" ||
     467          10 :             poKey == "map info" || poKey == "projection info" ||
     468           9 :             poKey == "data ignore value" || poKey == "data offset values" ||
     469        1315 :             poKey == "data gain values" || poKey == "coordinate system string")
     470             :         {
     471         656 :             CSLDestroy(papszTokens);
     472         656 :             continue;
     473             :         }
     474           1 :         bOK &= VSIFPrintfL(fp, "%s = %s\n", poKey.c_str(), papszTokens[1]) >= 0;
     475           1 :         CSLDestroy(papszTokens);
     476             :     }
     477             : 
     478          82 :     if (!bOK)
     479           0 :         return CE_Failure;
     480             : 
     481          82 :     bHeaderDirty = false;
     482          82 :     return eErr;
     483             : }
     484             : 
     485             : /************************************************************************/
     486             : /*                            GetFileList()                             */
     487             : /************************************************************************/
     488             : 
     489          54 : char **ENVIDataset::GetFileList()
     490             : 
     491             : {
     492             :     // Main data file, etc.
     493          54 :     char **papszFileList = RawDataset::GetFileList();
     494             : 
     495             :     // Header file.
     496          54 :     papszFileList = CSLAddString(papszFileList, pszHDRFilename);
     497             : 
     498             :     // Statistics file
     499          54 :     if (!osStaFilename.empty())
     500           0 :         papszFileList = CSLAddString(papszFileList, osStaFilename);
     501             : 
     502          54 :     return papszFileList;
     503             : }
     504             : 
     505             : /************************************************************************/
     506             : /*                           GetEPSGGeogCS()                            */
     507             : /*                                                                      */
     508             : /*      Try to establish what the EPSG code for this coordinate         */
     509             : /*      systems GEOGCS might be.  Returns -1 if no reasonable guess     */
     510             : /*      can be made.                                                    */
     511             : /*                                                                      */
     512             : /*      TODO: We really need to do some name lookups.                   */
     513             : /************************************************************************/
     514             : 
     515          65 : static int ENVIGetEPSGGeogCS(const OGRSpatialReference *poThis)
     516             : 
     517             : {
     518          65 :     const char *pszAuthName = poThis->GetAuthorityName("GEOGCS");
     519             : 
     520             :     // Do we already have it?
     521          65 :     if (pszAuthName != nullptr && EQUAL(pszAuthName, "epsg"))
     522          13 :         return atoi(poThis->GetAuthorityCode("GEOGCS"));
     523             : 
     524             :     // Get the datum and geogcs names.
     525          52 :     const char *pszGEOGCS = poThis->GetAttrValue("GEOGCS");
     526          52 :     const char *pszDatum = poThis->GetAttrValue("DATUM");
     527             : 
     528             :     // We can only operate on coordinate systems with a geogcs.
     529          52 :     if (pszGEOGCS == nullptr || pszDatum == nullptr)
     530           0 :         return -1;
     531             : 
     532             :     // Is this a "well known" geographic coordinate system?
     533           6 :     const bool bWGS = strstr(pszGEOGCS, "WGS") || strstr(pszDatum, "WGS") ||
     534           6 :                       strstr(pszGEOGCS, "World Geodetic System") ||
     535           6 :                       strstr(pszGEOGCS, "World_Geodetic_System") ||
     536          64 :                       strstr(pszDatum, "World Geodetic System") ||
     537           6 :                       strstr(pszDatum, "World_Geodetic_System");
     538             : 
     539          51 :     const bool bNAD = strstr(pszGEOGCS, "NAD") || strstr(pszDatum, "NAD") ||
     540          51 :                       strstr(pszGEOGCS, "North American") ||
     541          51 :                       strstr(pszGEOGCS, "North_American") ||
     542         154 :                       strstr(pszDatum, "North American") ||
     543          51 :                       strstr(pszDatum, "North_American");
     544             : 
     545          52 :     if (bWGS && (strstr(pszGEOGCS, "84") || strstr(pszDatum, "84")))
     546          46 :         return 4326;
     547             : 
     548           6 :     if (bWGS && (strstr(pszGEOGCS, "72") || strstr(pszDatum, "72")))
     549           0 :         return 4322;
     550             : 
     551           6 :     if (bNAD && (strstr(pszGEOGCS, "83") || strstr(pszDatum, "83")))
     552           1 :         return 4269;
     553             : 
     554           5 :     if (bNAD && (strstr(pszGEOGCS, "27") || strstr(pszDatum, "27")))
     555           0 :         return 4267;
     556             : 
     557             :     // If we know the datum, associate the most likely GCS with it.
     558           5 :     pszAuthName = poThis->GetAuthorityName("GEOGCS|DATUM");
     559             : 
     560           5 :     if (pszAuthName != nullptr && EQUAL(pszAuthName, "epsg") &&
     561           0 :         poThis->GetPrimeMeridian() == 0.0)
     562             :     {
     563           0 :         const int nDatum = atoi(poThis->GetAuthorityCode("GEOGCS|DATUM"));
     564             : 
     565           0 :         if (nDatum >= 6000 && nDatum <= 6999)
     566           0 :             return nDatum - 2000;
     567             :     }
     568             : 
     569           5 :     return -1;
     570             : }
     571             : 
     572             : /************************************************************************/
     573             : /*                        WriteProjectionInfo()                         */
     574             : /************************************************************************/
     575             : 
     576          80 : void ENVIDataset::WriteProjectionInfo()
     577             : 
     578             : {
     579             :     // Format the location (geotransform) portion of the map info line.
     580          80 :     CPLString osLocation;
     581          80 :     CPLString osRotation;
     582             : 
     583          80 :     const double dfPixelXSize = sqrt(adfGeoTransform[1] * adfGeoTransform[1] +
     584          80 :                                      adfGeoTransform[2] * adfGeoTransform[2]);
     585          80 :     const double dfPixelYSize = sqrt(adfGeoTransform[4] * adfGeoTransform[4] +
     586          80 :                                      adfGeoTransform[5] * adfGeoTransform[5]);
     587          80 :     const bool bHasNonDefaultGT =
     588          17 :         adfGeoTransform[0] != 0.0 || adfGeoTransform[1] != 1.0 ||
     589          15 :         adfGeoTransform[2] != 0.0 || adfGeoTransform[3] != 0.0 ||
     590          97 :         adfGeoTransform[4] != 0.0 || adfGeoTransform[5] != 1.0;
     591          80 :     if (adfGeoTransform[1] > 0.0 && adfGeoTransform[2] == 0.0 &&
     592          79 :         adfGeoTransform[4] == 0.0 && adfGeoTransform[5] > 0.0)
     593             :     {
     594          16 :         osRotation = ", rotation=180";
     595             :     }
     596          64 :     else if (bHasNonDefaultGT)
     597             :     {
     598             :         const double dfRotation1 =
     599          64 :             -atan2(-adfGeoTransform[2], adfGeoTransform[1]) * kdfRadToDeg;
     600             :         const double dfRotation2 =
     601          64 :             -atan2(-adfGeoTransform[4], -adfGeoTransform[5]) * kdfRadToDeg;
     602          64 :         const double dfRotation = (dfRotation1 + dfRotation2) / 2.0;
     603             : 
     604          64 :         if (fabs(dfRotation1 - dfRotation2) > 1e-5)
     605             :         {
     606           0 :             CPLDebug("ENVI", "rot1 = %.15g, rot2 = %.15g", dfRotation1,
     607             :                      dfRotation2);
     608           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     609             :                      "Geotransform matrix has non rotational terms");
     610             :         }
     611          64 :         if (fabs(dfRotation) > 1e-5)
     612             :         {
     613           1 :             osRotation.Printf(", rotation=%.15g", dfRotation);
     614             :         }
     615             :     }
     616             : 
     617             :     osLocation.Printf("1, 1, %.15g, %.15g, %.15g, %.15g", adfGeoTransform[0],
     618          80 :                       adfGeoTransform[3], dfPixelXSize, dfPixelYSize);
     619             : 
     620             :     // Minimal case - write out simple geotransform if we have a
     621             :     // non-default geotransform.
     622          80 :     if (m_oSRS.IsEmpty() || m_oSRS.IsLocal())
     623             :     {
     624          15 :         if (bHasNonDefaultGT)
     625             :         {
     626           2 :             const char *pszHemisphere = "North";
     627           2 :             if (VSIFPrintfL(fp, "map info = {Arbitrary, %s, %d, %s%s}\n",
     628             :                             osLocation.c_str(), 0, pszHemisphere,
     629           2 :                             osRotation.c_str()) < 0)
     630           0 :                 return;
     631             :         }
     632          15 :         return;
     633             :     }
     634             : 
     635             :     // Try to translate the datum and get major/minor ellipsoid values.
     636          65 :     const OGRSpatialReference &oSRS = m_oSRS;
     637          65 :     const int nEPSG_GCS = ENVIGetEPSGGeogCS(&oSRS);
     638         130 :     CPLString osDatum;
     639             : 
     640          65 :     if (nEPSG_GCS == 4326)
     641          57 :         osDatum = "WGS-84";
     642           8 :     else if (nEPSG_GCS == 4322)
     643           0 :         osDatum = "WGS-72";
     644           8 :     else if (nEPSG_GCS == 4269)
     645           1 :         osDatum = "North America 1983";
     646           7 :     else if (nEPSG_GCS == 4267)
     647           2 :         osDatum = "North America 1927";
     648           5 :     else if (nEPSG_GCS == 4230)
     649           0 :         osDatum = "European 1950";
     650           5 :     else if (nEPSG_GCS == 4277)
     651           0 :         osDatum = "Ordnance Survey of Great Britain '36";
     652           5 :     else if (nEPSG_GCS == 4291)
     653           0 :         osDatum = "SAD-69/Brazil";
     654           5 :     else if (nEPSG_GCS == 4283)
     655           0 :         osDatum = "Geocentric Datum of Australia 1994";
     656           5 :     else if (nEPSG_GCS == 4275)
     657           0 :         osDatum = "Nouvelle Triangulation Francaise IGN";
     658             : 
     659         195 :     const CPLString osCommaDatum = osDatum.empty() ? "" : ("," + osDatum);
     660             : 
     661          65 :     const double dfA = oSRS.GetSemiMajor();
     662          65 :     const double dfB = oSRS.GetSemiMinor();
     663             : 
     664             :     // Do we have unusual linear units?
     665          65 :     const double dfFeetPerMeter = 0.3048;
     666             :     const CPLString osOptionalUnits =
     667          65 :         fabs(oSRS.GetLinearUnits() - dfFeetPerMeter) < 0.0001 ? ", units=Feet"
     668         130 :                                                               : "";
     669             : 
     670             :     // Handle UTM case.
     671          65 :     const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
     672          65 :     int bNorth = FALSE;
     673          65 :     const int iUTMZone = oSRS.GetUTMZone(&bNorth);
     674          65 :     bool bOK = true;
     675          65 :     if (iUTMZone)
     676             :     {
     677           3 :         const char *pszHemisphere = bNorth ? "North" : "South";
     678             : 
     679           3 :         bOK &= VSIFPrintfL(fp, "map info = {UTM, %s, %d, %s%s%s%s}\n",
     680             :                            osLocation.c_str(), iUTMZone, pszHemisphere,
     681             :                            osCommaDatum.c_str(), osOptionalUnits.c_str(),
     682           3 :                            osRotation.c_str()) >= 0;
     683             :     }
     684          62 :     else if (oSRS.IsGeographic())
     685             :     {
     686          55 :         bOK &= VSIFPrintfL(fp, "map info = {Geographic Lat/Lon, %s%s%s}\n",
     687             :                            osLocation.c_str(), osCommaDatum.c_str(),
     688          55 :                            osRotation.c_str()) >= 0;
     689             :     }
     690           7 :     else if (pszProjName == nullptr)
     691             :     {
     692             :         // What to do?
     693             :     }
     694           7 :     else if (EQUAL(pszProjName, SRS_PT_NEW_ZEALAND_MAP_GRID))
     695             :     {
     696           0 :         bOK &= VSIFPrintfL(fp, "map info = {New Zealand Map Grid, %s%s%s%s}\n",
     697             :                            osLocation.c_str(), osCommaDatum.c_str(),
     698           0 :                            osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
     699             : 
     700           0 :         bOK &= VSIFPrintfL(fp,
     701             :                            "projection info = {39, %.16g, %.16g, %.16g, %.16g, "
     702             :                            "%.16g, %.16g%s, New Zealand Map Grid}\n",
     703             :                            dfA, dfB,
     704             :                            oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
     705             :                            oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
     706             :                            oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
     707             :                            oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
     708           0 :                            osCommaDatum.c_str()) >= 0;
     709             :     }
     710           7 :     else if (EQUAL(pszProjName, SRS_PT_TRANSVERSE_MERCATOR))
     711             :     {
     712           1 :         bOK &= VSIFPrintfL(fp, "map info = {Transverse Mercator, %s%s%s%s}\n",
     713             :                            osLocation.c_str(), osCommaDatum.c_str(),
     714           1 :                            osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
     715             : 
     716           1 :         bOK &= VSIFPrintfL(fp,
     717             :                            "projection info = {3, %.16g, %.16g, %.16g, "
     718             :                            "%.16g, %.16g, "
     719             :                            "%.16g, %.16g%s, Transverse Mercator}\n",
     720             :                            dfA, dfB,
     721             :                            oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
     722             :                            oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
     723             :                            oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
     724             :                            oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
     725             :                            oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0),
     726           1 :                            osCommaDatum.c_str()) >= 0;
     727             :     }
     728           6 :     else if (EQUAL(pszProjName, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP) ||
     729           4 :              EQUAL(pszProjName, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP_BELGIUM))
     730             :     {
     731           2 :         bOK &=
     732           2 :             VSIFPrintfL(fp, "map info = {Lambert Conformal Conic, %s%s%s%s}\n",
     733             :                         osLocation.c_str(), osCommaDatum.c_str(),
     734           2 :                         osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
     735             : 
     736           2 :         bOK &=
     737           2 :             VSIFPrintfL(
     738             :                 fp,
     739             :                 "projection info = {4, %.16g, %.16g, %.16g, %.16g, %.16g, "
     740             :                 "%.16g, %.16g, %.16g%s, Lambert Conformal Conic}\n",
     741             :                 dfA, dfB, oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
     742             :                 oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
     743             :                 oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
     744             :                 oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
     745             :                 oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0),
     746             :                 oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2, 0.0),
     747           2 :                 osCommaDatum.c_str()) >= 0;
     748             :     }
     749           4 :     else if (EQUAL(pszProjName,
     750             :                    SRS_PT_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN))
     751             :     {
     752           0 :         bOK &= VSIFPrintfL(fp,
     753             :                            "map info = {Hotine Oblique Mercator A, %s%s%s%s}\n",
     754             :                            osLocation.c_str(), osCommaDatum.c_str(),
     755           0 :                            osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
     756             : 
     757           0 :         bOK &=
     758           0 :             VSIFPrintfL(
     759             :                 fp,
     760             :                 "projection info = {5, %.16g, %.16g, %.16g, %.16g, %.16g, "
     761             :                 "%.16g, %.16g, %.16g, %.16g, %.16g%s, "
     762             :                 "Hotine Oblique Mercator A}\n",
     763             :                 dfA, dfB, oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
     764             :                 oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_POINT_1, 0.0),
     765             :                 oSRS.GetNormProjParm(SRS_PP_LONGITUDE_OF_POINT_1, 0.0),
     766             :                 oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_POINT_2, 0.0),
     767             :                 oSRS.GetNormProjParm(SRS_PP_LONGITUDE_OF_POINT_2, 0.0),
     768             :                 oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
     769             :                 oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
     770             :                 oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0),
     771           0 :                 osCommaDatum.c_str()) >= 0;
     772             :     }
     773           4 :     else if (EQUAL(pszProjName, SRS_PT_HOTINE_OBLIQUE_MERCATOR))
     774             :     {
     775           0 :         bOK &= VSIFPrintfL(fp,
     776             :                            "map info = {Hotine Oblique Mercator B, %s%s%s%s}\n",
     777             :                            osLocation.c_str(), osCommaDatum.c_str(),
     778           0 :                            osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
     779             : 
     780           0 :         bOK &=
     781           0 :             VSIFPrintfL(
     782             :                 fp,
     783             :                 "projection info = {6, %.16g, %.16g, %.16g, %.16g, %.16g, "
     784             :                 "%.16g, %.16g, %.16g%s, Hotine Oblique Mercator B}\n",
     785             :                 dfA, dfB, oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
     786             :                 oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
     787             :                 oSRS.GetNormProjParm(SRS_PP_AZIMUTH, 0.0),
     788             :                 oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
     789             :                 oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
     790             :                 oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0),
     791           0 :                 osCommaDatum.c_str()) >= 0;
     792             :     }
     793           4 :     else if (EQUAL(pszProjName, SRS_PT_STEREOGRAPHIC) ||
     794           4 :              EQUAL(pszProjName, SRS_PT_OBLIQUE_STEREOGRAPHIC))
     795             :     {
     796           0 :         bOK &= VSIFPrintfL(fp,
     797             :                            "map info = {Stereographic (ellipsoid), %s%s%s%s}\n",
     798             :                            osLocation.c_str(), osCommaDatum.c_str(),
     799           0 :                            osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
     800             : 
     801           0 :         bOK &=
     802           0 :             VSIFPrintfL(
     803             :                 fp,
     804             :                 "projection info = {7, %.16g, %.16g, %.16g, %.16g, %.16g, "
     805             :                 "%.16g, %.16g, %s, Stereographic (ellipsoid)}\n",
     806             :                 dfA, dfB, oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
     807             :                 oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
     808             :                 oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
     809             :                 oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
     810             :                 oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0),
     811           0 :                 osCommaDatum.c_str()) >= 0;
     812             :     }
     813           4 :     else if (EQUAL(pszProjName, SRS_PT_ALBERS_CONIC_EQUAL_AREA))
     814             :     {
     815           3 :         bOK &= VSIFPrintfL(fp,
     816             :                            "map info = {Albers Conical Equal Area, %s%s%s%s}\n",
     817             :                            osLocation.c_str(), osCommaDatum.c_str(),
     818           3 :                            osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
     819             : 
     820           3 :         bOK &=
     821           3 :             VSIFPrintfL(
     822             :                 fp,
     823             :                 "projection info = {9, %.16g, %.16g, %.16g, %.16g, %.16g, "
     824             :                 "%.16g, %.16g, %.16g%s, Albers Conical Equal Area}\n",
     825             :                 dfA, dfB, oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
     826             :                 oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
     827             :                 oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
     828             :                 oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
     829             :                 oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0),
     830             :                 oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2, 0.0),
     831           3 :                 osCommaDatum.c_str()) >= 0;
     832             :     }
     833           1 :     else if (EQUAL(pszProjName, SRS_PT_POLYCONIC))
     834             :     {
     835           0 :         bOK &= VSIFPrintfL(fp, "map info = {Polyconic, %s%s%s%s}\n",
     836             :                            osLocation.c_str(), osCommaDatum.c_str(),
     837           0 :                            osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
     838             : 
     839           0 :         bOK &=
     840           0 :             VSIFPrintfL(
     841             :                 fp,
     842             :                 "projection info = {10, %.16g, %.16g, %.16g, %.16g, %.16g, "
     843             :                 "%.16g%s, Polyconic}\n",
     844             :                 dfA, dfB, oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
     845             :                 oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
     846             :                 oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
     847             :                 oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
     848           0 :                 osCommaDatum.c_str()) >= 0;
     849             :     }
     850           1 :     else if (EQUAL(pszProjName, SRS_PT_LAMBERT_AZIMUTHAL_EQUAL_AREA))
     851             :     {
     852           1 :         bOK &= VSIFPrintfL(
     853             :                    fp, "map info = {Lambert Azimuthal Equal Area, %s%s%s%s}\n",
     854             :                    osLocation.c_str(), osCommaDatum.c_str(),
     855           1 :                    osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
     856             : 
     857           1 :         bOK &=
     858           1 :             VSIFPrintfL(
     859             :                 fp,
     860             :                 "projection info = {11, %.16g, %.16g, %.16g, %.16g, %.16g, "
     861             :                 "%.16g%s, Lambert Azimuthal Equal Area}\n",
     862             :                 dfA, dfB, oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
     863             :                 oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
     864             :                 oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
     865             :                 oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
     866           1 :                 osCommaDatum.c_str()) >= 0;
     867             :     }
     868           0 :     else if (EQUAL(pszProjName, SRS_PT_AZIMUTHAL_EQUIDISTANT))
     869             :     {
     870           0 :         bOK &= VSIFPrintfL(fp, "map info = {Azimuthal Equadistant, %s%s%s%s}\n",
     871             :                            osLocation.c_str(), osCommaDatum.c_str(),
     872           0 :                            osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
     873             : 
     874           0 :         bOK &=
     875           0 :             VSIFPrintfL(
     876             :                 fp,
     877             :                 "projection info = {12, %.16g, %.16g, %.16g, %.16g, %.16g, "
     878             :                 "%.16g%s, Azimuthal Equadistant}\n",
     879             :                 dfA, dfB, oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
     880             :                 oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
     881             :                 oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
     882             :                 oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
     883           0 :                 osCommaDatum.c_str()) >= 0;
     884             :     }
     885           0 :     else if (EQUAL(pszProjName, SRS_PT_POLAR_STEREOGRAPHIC))
     886             :     {
     887           0 :         bOK &= VSIFPrintfL(fp, "map info = {Polar Stereographic, %s%s%s%s}\n",
     888             :                            osLocation.c_str(), osCommaDatum.c_str(),
     889           0 :                            osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
     890             : 
     891           0 :         bOK &=
     892           0 :             VSIFPrintfL(
     893             :                 fp,
     894             :                 "projection info = {31, %.16g, %.16g, %.16g, %.16g, %.16g, "
     895             :                 "%.16g%s, Polar Stereographic}\n",
     896             :                 dfA, dfB, oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 90.0),
     897             :                 oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
     898             :                 oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
     899             :                 oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
     900           0 :                 osCommaDatum.c_str()) >= 0;
     901             :     }
     902             :     else
     903             :     {
     904           0 :         bOK &= VSIFPrintfL(fp, "map info = {%s, %s}\n", pszProjName,
     905           0 :                            osLocation.c_str()) >= 0;
     906             :     }
     907             : 
     908             :     // write out coordinate system string
     909          65 :     char *pszProjESRI = nullptr;
     910          65 :     const char *const apszOptions[] = {"FORMAT=WKT1_ESRI", nullptr};
     911          65 :     if (oSRS.exportToWkt(&pszProjESRI, apszOptions) == OGRERR_NONE)
     912             :     {
     913          65 :         if (strlen(pszProjESRI))
     914          65 :             bOK &= VSIFPrintfL(fp, "coordinate system string = {%s}\n",
     915          65 :                                pszProjESRI) >= 0;
     916             :     }
     917          65 :     CPLFree(pszProjESRI);
     918          65 :     pszProjESRI = nullptr;
     919             : 
     920          65 :     if (!bOK)
     921             :     {
     922           0 :         CPLError(CE_Failure, CPLE_FileIO, "Write error");
     923             :     }
     924             : }
     925             : 
     926             : /************************************************************************/
     927             : /*                ParseRpcCoeffsMetaDataString()                        */
     928             : /************************************************************************/
     929             : 
     930           4 : bool ENVIDataset::ParseRpcCoeffsMetaDataString(const char *psName,
     931             :                                                char **papszVal, int &idx)
     932             : {
     933             :     // Separate one string with 20 coefficients into an array of 20 strings.
     934           4 :     const char *psz20Vals = GetMetadataItem(psName, "RPC");
     935           4 :     if (!psz20Vals)
     936           0 :         return false;
     937             : 
     938           4 :     char **papszArr = CSLTokenizeString2(psz20Vals, " ", 0);
     939           4 :     if (!papszArr)
     940           0 :         return false;
     941             : 
     942           4 :     int x = 0;
     943          84 :     while ((x < 20) && (papszArr[x] != nullptr))
     944             :     {
     945          80 :         papszVal[idx++] = CPLStrdup(papszArr[x]);
     946          80 :         x++;
     947             :     }
     948             : 
     949           4 :     CSLDestroy(papszArr);
     950             : 
     951           4 :     return x == 20;
     952             : }
     953             : 
     954         823 : static char *CPLStrdupIfNotNull(const char *pszString)
     955             : {
     956         823 :     if (!pszString)
     957         810 :         return nullptr;
     958             : 
     959          13 :     return CPLStrdup(pszString);
     960             : }
     961             : 
     962             : /************************************************************************/
     963             : /*                          WriteRpcInfo()                              */
     964             : /************************************************************************/
     965             : 
     966             : // TODO: This whole function needs to be cleaned up.
     967          82 : bool ENVIDataset::WriteRpcInfo()
     968             : {
     969             :     // Write out 90 rpc coeffs into the envi header plus 3 envi specific rpc
     970             :     // values returns 0 if the coeffs are not present or not valid.
     971          82 :     int idx = 0;
     972             :     // TODO(schwehr): Make 93 a constant.
     973          82 :     char *papszVal[93] = {nullptr};
     974             : 
     975          82 :     papszVal[idx++] = CPLStrdupIfNotNull(GetMetadataItem("LINE_OFF", "RPC"));
     976          82 :     papszVal[idx++] = CPLStrdupIfNotNull(GetMetadataItem("SAMP_OFF", "RPC"));
     977          82 :     papszVal[idx++] = CPLStrdupIfNotNull(GetMetadataItem("LAT_OFF", "RPC"));
     978          82 :     papszVal[idx++] = CPLStrdupIfNotNull(GetMetadataItem("LONG_OFF", "RPC"));
     979          82 :     papszVal[idx++] = CPLStrdupIfNotNull(GetMetadataItem("HEIGHT_OFF", "RPC"));
     980          82 :     papszVal[idx++] = CPLStrdupIfNotNull(GetMetadataItem("LINE_SCALE", "RPC"));
     981          82 :     papszVal[idx++] = CPLStrdupIfNotNull(GetMetadataItem("SAMP_SCALE", "RPC"));
     982          82 :     papszVal[idx++] = CPLStrdupIfNotNull(GetMetadataItem("LAT_SCALE", "RPC"));
     983          82 :     papszVal[idx++] = CPLStrdupIfNotNull(GetMetadataItem("LONG_SCALE", "RPC"));
     984          82 :     papszVal[idx++] =
     985          82 :         CPLStrdupIfNotNull(GetMetadataItem("HEIGHT_SCALE", "RPC"));
     986             : 
     987          82 :     bool bRet = false;
     988             : 
     989          92 :     for (int x = 0; x < 10; x++)  // If we do not have 10 values we return 0.
     990             :     {
     991          91 :         if (!papszVal[x])
     992          81 :             goto end;
     993             :     }
     994             : 
     995           1 :     if (!ParseRpcCoeffsMetaDataString("LINE_NUM_COEFF", papszVal, idx))
     996           0 :         goto end;
     997             : 
     998           1 :     if (!ParseRpcCoeffsMetaDataString("LINE_DEN_COEFF", papszVal, idx))
     999           0 :         goto end;
    1000             : 
    1001           1 :     if (!ParseRpcCoeffsMetaDataString("SAMP_NUM_COEFF", papszVal, idx))
    1002           0 :         goto end;
    1003             : 
    1004           1 :     if (!ParseRpcCoeffsMetaDataString("SAMP_DEN_COEFF", papszVal, idx))
    1005           0 :         goto end;
    1006             : 
    1007           1 :     papszVal[idx++] =
    1008           1 :         CPLStrdupIfNotNull(GetMetadataItem("TILE_ROW_OFFSET", "RPC"));
    1009           1 :     papszVal[idx++] =
    1010           1 :         CPLStrdupIfNotNull(GetMetadataItem("TILE_COL_OFFSET", "RPC"));
    1011           1 :     papszVal[idx++] =
    1012           1 :         CPLStrdupIfNotNull(GetMetadataItem("ENVI_RPC_EMULATION", "RPC"));
    1013           1 :     CPLAssert(idx == 93);
    1014           4 :     for (int x = 90; x < 93; x++)
    1015             :     {
    1016           3 :         if (!papszVal[x])
    1017           0 :             goto end;
    1018             :     }
    1019             : 
    1020             :     // All the needed 93 values are present so write the rpcs into the envi
    1021             :     // header.
    1022           1 :     bRet = true;
    1023             :     {
    1024           1 :         int x = 1;
    1025           1 :         bRet &= VSIFPrintfL(fp, "rpc info = {\n") >= 0;
    1026          94 :         for (int iR = 0; iR < 93; iR++)
    1027             :         {
    1028          93 :             if (papszVal[iR][0] == '-')
    1029           8 :                 bRet &= VSIFPrintfL(fp, " %s", papszVal[iR]) >= 0;
    1030             :             else
    1031          85 :                 bRet &= VSIFPrintfL(fp, "  %s", papszVal[iR]) >= 0;
    1032             : 
    1033          93 :             if (iR < 92)
    1034          92 :                 bRet &= VSIFPrintfL(fp, ",") >= 0;
    1035             : 
    1036          93 :             if ((x % 4) == 0)
    1037          23 :                 bRet &= VSIFPrintfL(fp, "\n") >= 0;
    1038             : 
    1039          93 :             x++;
    1040          93 :             if (x > 4)
    1041          23 :                 x = 1;
    1042             :         }
    1043             :     }
    1044           1 :     bRet &= VSIFPrintfL(fp, "}\n") >= 0;
    1045             : 
    1046             :     // TODO(schwehr): Rewrite without goto.
    1047          82 : end:
    1048         985 :     for (int i = 0; i < idx; i++)
    1049         903 :         CPLFree(papszVal[i]);
    1050             : 
    1051          82 :     return bRet;
    1052             : }
    1053             : 
    1054             : /************************************************************************/
    1055             : /*                        WritePseudoGcpInfo()                          */
    1056             : /************************************************************************/
    1057             : 
    1058          81 : bool ENVIDataset::WritePseudoGcpInfo()
    1059             : {
    1060             :     // Write out gcps into the envi header
    1061             :     // returns 0 if the gcps are not present.
    1062             : 
    1063          81 :     const int iNum = std::min(GetGCPCount(), 4);
    1064          81 :     if (iNum == 0)
    1065          80 :         return false;
    1066             : 
    1067           1 :     const GDAL_GCP *pGcpStructs = GetGCPs();
    1068             : 
    1069             :     // double dfGCPPixel; /** Pixel (x) location of GCP on raster */
    1070             :     // double dfGCPLine;  /** Line (y) location of GCP on raster */
    1071             :     // double dfGCPX;     /** X position of GCP in georeferenced space */
    1072             :     // double dfGCPY;     /** Y position of GCP in georeferenced space */
    1073             : 
    1074           1 :     bool bRet = VSIFPrintfL(fp, "geo points = {\n") >= 0;
    1075           2 :     for (int iR = 0; iR < iNum; iR++)
    1076             :     {
    1077             :         // Add 1 to pixel and line for ENVI convention
    1078           1 :         bRet &=
    1079           2 :             VSIFPrintfL(fp, " %#0.4f, %#0.4f, %#0.8f, %#0.8f",
    1080           1 :                         1 + pGcpStructs[iR].dfGCPPixel,
    1081           1 :                         1 + pGcpStructs[iR].dfGCPLine, pGcpStructs[iR].dfGCPY,
    1082           1 :                         pGcpStructs[iR].dfGCPX) >= 0;
    1083           1 :         if (iR < iNum - 1)
    1084           0 :             bRet &= VSIFPrintfL(fp, ",\n") >= 0;
    1085             :     }
    1086             : 
    1087           1 :     bRet &= VSIFPrintfL(fp, "}\n") >= 0;
    1088             : 
    1089           1 :     return bRet;
    1090             : }
    1091             : 
    1092             : /************************************************************************/
    1093             : /*                          GetSpatialRef()                             */
    1094             : /************************************************************************/
    1095             : 
    1096          24 : const OGRSpatialReference *ENVIDataset::GetSpatialRef() const
    1097             : {
    1098          24 :     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    1099             : }
    1100             : 
    1101             : /************************************************************************/
    1102             : /*                          SetSpatialRef()                             */
    1103             : /************************************************************************/
    1104             : 
    1105          64 : CPLErr ENVIDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
    1106             : 
    1107             : {
    1108          64 :     m_oSRS.Clear();
    1109          64 :     if (poSRS)
    1110          64 :         m_oSRS = *poSRS;
    1111             : 
    1112          64 :     bHeaderDirty = true;
    1113             : 
    1114          64 :     return CE_None;
    1115             : }
    1116             : 
    1117             : /************************************************************************/
    1118             : /*                          GetGeoTransform()                           */
    1119             : /************************************************************************/
    1120             : 
    1121          58 : CPLErr ENVIDataset::GetGeoTransform(double *padfTransform)
    1122             : 
    1123             : {
    1124          58 :     memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
    1125             : 
    1126          58 :     if (bFoundMapinfo)
    1127          56 :         return CE_None;
    1128             : 
    1129           2 :     return CE_Failure;
    1130             : }
    1131             : 
    1132             : /************************************************************************/
    1133             : /*                          SetGeoTransform()                           */
    1134             : /************************************************************************/
    1135             : 
    1136          65 : CPLErr ENVIDataset::SetGeoTransform(double *padfTransform)
    1137             : {
    1138          65 :     memcpy(adfGeoTransform, padfTransform, sizeof(double) * 6);
    1139             : 
    1140          65 :     bHeaderDirty = true;
    1141          65 :     bFoundMapinfo = true;
    1142             : 
    1143          65 :     return CE_None;
    1144             : }
    1145             : 
    1146             : /************************************************************************/
    1147             : /*                           SetDescription()                           */
    1148             : /************************************************************************/
    1149             : 
    1150         249 : void ENVIDataset::SetDescription(const char *pszDescription)
    1151             : {
    1152         249 :     bHeaderDirty = true;
    1153         249 :     RawDataset::SetDescription(pszDescription);
    1154         249 : }
    1155             : 
    1156             : /************************************************************************/
    1157             : /*                             SetMetadata()                            */
    1158             : /************************************************************************/
    1159             : 
    1160          31 : CPLErr ENVIDataset::SetMetadata(char **papszMetadata, const char *pszDomain)
    1161             : {
    1162          31 :     if (pszDomain && (EQUAL(pszDomain, "RPC") || EQUAL(pszDomain, "ENVI")))
    1163             :     {
    1164           2 :         bHeaderDirty = true;
    1165             :     }
    1166          31 :     return RawDataset::SetMetadata(papszMetadata, pszDomain);
    1167             : }
    1168             : 
    1169             : /************************************************************************/
    1170             : /*                             SetMetadataItem()                        */
    1171             : /************************************************************************/
    1172             : 
    1173        3048 : CPLErr ENVIDataset::SetMetadataItem(const char *pszName, const char *pszValue,
    1174             :                                     const char *pszDomain)
    1175             : {
    1176        3048 :     if (pszDomain && (EQUAL(pszDomain, "RPC") || EQUAL(pszDomain, "ENVI")))
    1177             :     {
    1178        2557 :         bHeaderDirty = true;
    1179             :     }
    1180        3048 :     return RawDataset::SetMetadataItem(pszName, pszValue, pszDomain);
    1181             : }
    1182             : 
    1183             : /************************************************************************/
    1184             : /*                               SetGCPs()                              */
    1185             : /************************************************************************/
    1186             : 
    1187           1 : CPLErr ENVIDataset::SetGCPs(int nGCPCount, const GDAL_GCP *pasGCPList,
    1188             :                             const OGRSpatialReference *poSRS)
    1189             : {
    1190           1 :     bHeaderDirty = true;
    1191             : 
    1192           1 :     return RawDataset::SetGCPs(nGCPCount, pasGCPList, poSRS);
    1193             : }
    1194             : 
    1195             : /************************************************************************/
    1196             : /*                             SplitList()                              */
    1197             : /*                                                                      */
    1198             : /*      Split an ENVI value list into component fields, and strip       */
    1199             : /*      white space.                                                    */
    1200             : /************************************************************************/
    1201             : 
    1202         418 : char **ENVIDataset::SplitList(const char *pszCleanInput)
    1203             : 
    1204             : {
    1205         418 :     char *pszInput = CPLStrdup(pszCleanInput);
    1206             : 
    1207         418 :     if (pszInput[0] != '{')
    1208             :     {
    1209         128 :         CPLFree(pszInput);
    1210         128 :         return nullptr;
    1211             :     }
    1212             : 
    1213         290 :     int iChar = 1;
    1214         580 :     CPLStringList aosList;
    1215        1989 :     while (pszInput[iChar] != '}' && pszInput[iChar] != '\0')
    1216             :     {
    1217             :         // Find start of token.
    1218        1699 :         int iFStart = iChar;
    1219        3210 :         while (pszInput[iFStart] == ' ')
    1220        1511 :             iFStart++;
    1221             : 
    1222        1699 :         int iFEnd = iFStart;
    1223       11596 :         while (pszInput[iFEnd] != ',' && pszInput[iFEnd] != '}' &&
    1224        9897 :                pszInput[iFEnd] != '\0')
    1225        9897 :             iFEnd++;
    1226             : 
    1227        1699 :         if (pszInput[iFEnd] == '\0')
    1228           0 :             break;
    1229             : 
    1230        1699 :         iChar = iFEnd + 1;
    1231        1699 :         iFEnd = iFEnd - 1;
    1232             : 
    1233        1723 :         while (iFEnd > iFStart && pszInput[iFEnd] == ' ')
    1234          24 :             iFEnd--;
    1235             : 
    1236        1699 :         pszInput[iFEnd + 1] = '\0';
    1237        1699 :         aosList.AddString(pszInput + iFStart);
    1238             :     }
    1239             : 
    1240         290 :     CPLFree(pszInput);
    1241             : 
    1242         290 :     return aosList.StealList();
    1243             : }
    1244             : 
    1245             : /************************************************************************/
    1246             : /*                            SetENVIDatum()                            */
    1247             : /************************************************************************/
    1248             : 
    1249           2 : void ENVIDataset::SetENVIDatum(OGRSpatialReference *poSRS,
    1250             :                                const char *pszENVIDatumName)
    1251             : 
    1252             : {
    1253             :     // Datums.
    1254           2 :     if (EQUAL(pszENVIDatumName, "WGS-84"))
    1255           2 :         poSRS->SetWellKnownGeogCS("WGS84");
    1256           0 :     else if (EQUAL(pszENVIDatumName, "WGS-72"))
    1257           0 :         poSRS->SetWellKnownGeogCS("WGS72");
    1258           0 :     else if (EQUAL(pszENVIDatumName, "North America 1983"))
    1259           0 :         poSRS->SetWellKnownGeogCS("NAD83");
    1260           0 :     else if (EQUAL(pszENVIDatumName, "North America 1927") ||
    1261           0 :              strstr(pszENVIDatumName, "NAD27") ||
    1262           0 :              strstr(pszENVIDatumName, "NAD-27"))
    1263           0 :         poSRS->SetWellKnownGeogCS("NAD27");
    1264           0 :     else if (STARTS_WITH_CI(pszENVIDatumName, "European 1950"))
    1265           0 :         poSRS->SetWellKnownGeogCS("EPSG:4230");
    1266           0 :     else if (EQUAL(pszENVIDatumName, "Ordnance Survey of Great Britain '36"))
    1267           0 :         poSRS->SetWellKnownGeogCS("EPSG:4277");
    1268           0 :     else if (EQUAL(pszENVIDatumName, "SAD-69/Brazil"))
    1269           0 :         poSRS->SetWellKnownGeogCS("EPSG:4291");
    1270           0 :     else if (EQUAL(pszENVIDatumName, "Geocentric Datum of Australia 1994"))
    1271           0 :         poSRS->SetWellKnownGeogCS("EPSG:4283");
    1272           0 :     else if (EQUAL(pszENVIDatumName, "Australian Geodetic 1984"))
    1273           0 :         poSRS->SetWellKnownGeogCS("EPSG:4203");
    1274           0 :     else if (EQUAL(pszENVIDatumName, "Nouvelle Triangulation Francaise IGN"))
    1275           0 :         poSRS->SetWellKnownGeogCS("EPSG:4275");
    1276             : 
    1277             :     // Ellipsoids
    1278           0 :     else if (EQUAL(pszENVIDatumName, "GRS 80"))
    1279           0 :         poSRS->SetWellKnownGeogCS("NAD83");
    1280           0 :     else if (EQUAL(pszENVIDatumName, "Airy"))
    1281           0 :         poSRS->SetWellKnownGeogCS("EPSG:4001");
    1282           0 :     else if (EQUAL(pszENVIDatumName, "Australian National"))
    1283           0 :         poSRS->SetWellKnownGeogCS("EPSG:4003");
    1284           0 :     else if (EQUAL(pszENVIDatumName, "Bessel 1841"))
    1285           0 :         poSRS->SetWellKnownGeogCS("EPSG:4004");
    1286           0 :     else if (EQUAL(pszENVIDatumName, "Clark 1866"))
    1287           0 :         poSRS->SetWellKnownGeogCS("EPSG:4008");
    1288             :     else
    1289             :     {
    1290           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    1291             :                  "Unrecognized datum '%s', defaulting to WGS84.",
    1292             :                  pszENVIDatumName);
    1293           0 :         poSRS->SetWellKnownGeogCS("WGS84");
    1294             :     }
    1295           2 : }
    1296             : 
    1297             : /************************************************************************/
    1298             : /*                           SetENVIEllipse()                           */
    1299             : /************************************************************************/
    1300             : 
    1301           9 : void ENVIDataset::SetENVIEllipse(OGRSpatialReference *poSRS, char **papszPI_EI)
    1302             : 
    1303             : {
    1304           9 :     const double dfA = CPLAtofM(papszPI_EI[0]);
    1305           9 :     const double dfB = CPLAtofM(papszPI_EI[1]);
    1306             : 
    1307           9 :     double dfInvF = 0.0;
    1308           9 :     if (fabs(dfA - dfB) >= 0.1)
    1309           9 :         dfInvF = dfA / (dfA - dfB);
    1310             : 
    1311           9 :     poSRS->SetGeogCS("Ellipse Based", "Ellipse Based", "Unnamed", dfA, dfInvF);
    1312           9 : }
    1313             : 
    1314             : /************************************************************************/
    1315             : /*                           ProcessMapinfo()                           */
    1316             : /*                                                                      */
    1317             : /*      Extract projection, and geotransform from a mapinfo value in    */
    1318             : /*      the header.                                                     */
    1319             : /************************************************************************/
    1320             : 
    1321         104 : bool ENVIDataset::ProcessMapinfo(const char *pszMapinfo)
    1322             : 
    1323             : {
    1324         104 :     char **papszFields = SplitList(pszMapinfo);
    1325         104 :     const char *pszUnits = nullptr;
    1326         104 :     double dfRotation = 0.0;
    1327         104 :     bool bUpsideDown = false;
    1328         104 :     const int nCount = CSLCount(papszFields);
    1329             : 
    1330         104 :     if (nCount < 7)
    1331             :     {
    1332           0 :         CSLDestroy(papszFields);
    1333           0 :         return false;
    1334             :     }
    1335             : 
    1336             :     // Retrieve named values
    1337         966 :     for (int i = 0; i < nCount; ++i)
    1338             :     {
    1339         862 :         if (STARTS_WITH(papszFields[i], "units="))
    1340             :         {
    1341           2 :             pszUnits = papszFields[i] + strlen("units=");
    1342             :         }
    1343         860 :         else if (STARTS_WITH(papszFields[i], "rotation="))
    1344             :         {
    1345           9 :             dfRotation = CPLAtof(papszFields[i] + strlen("rotation="));
    1346           9 :             bUpsideDown = fabs(dfRotation) == 180.0;
    1347           9 :             dfRotation *= kdfDegToRad * -1.0;
    1348             :         }
    1349             :     }
    1350             : 
    1351             :     // Check if we have coordinate system string, and if so parse it.
    1352         104 :     char **papszCSS = nullptr;
    1353         104 :     const char *pszCSS = m_aosHeader["coordinate_system_string"];
    1354         104 :     if (pszCSS != nullptr)
    1355             :     {
    1356          75 :         papszCSS = CSLTokenizeString2(pszCSS, "{}", CSLT_PRESERVEQUOTES);
    1357             :     }
    1358             : 
    1359             :     // Check if we have projection info, and if so parse it.
    1360         104 :     char **papszPI = nullptr;
    1361         104 :     int nPICount = 0;
    1362         104 :     const char *pszPI = m_aosHeader["projection_info"];
    1363         104 :     if (pszPI != nullptr)
    1364             :     {
    1365          23 :         papszPI = SplitList(pszPI);
    1366          23 :         nPICount = CSLCount(papszPI);
    1367             :     }
    1368             : 
    1369             :     // Capture geotransform.
    1370         104 :     const double xReference = CPLAtof(papszFields[1]);
    1371         104 :     const double yReference = CPLAtof(papszFields[2]);
    1372         104 :     const double pixelEasting = CPLAtof(papszFields[3]);
    1373         104 :     const double pixelNorthing = CPLAtof(papszFields[4]);
    1374         104 :     const double xPixelSize = CPLAtof(papszFields[5]);
    1375         104 :     const double yPixelSize = CPLAtof(papszFields[6]);
    1376             : 
    1377         104 :     adfGeoTransform[0] = pixelEasting - (xReference - 1) * xPixelSize;
    1378         104 :     adfGeoTransform[1] = cos(dfRotation) * xPixelSize;
    1379         104 :     adfGeoTransform[2] = -sin(dfRotation) * xPixelSize;
    1380         104 :     adfGeoTransform[3] = pixelNorthing + (yReference - 1) * yPixelSize;
    1381         104 :     adfGeoTransform[4] = -sin(dfRotation) * yPixelSize;
    1382         104 :     adfGeoTransform[5] = -cos(dfRotation) * yPixelSize;
    1383         104 :     if (bUpsideDown)  // to avoid numeric approximations
    1384             :     {
    1385           5 :         adfGeoTransform[1] = xPixelSize;
    1386           5 :         adfGeoTransform[2] = 0;
    1387           5 :         adfGeoTransform[4] = 0;
    1388           5 :         adfGeoTransform[5] = yPixelSize;
    1389             :     }
    1390             : 
    1391             :     // TODO(schwehr): Symbolic constants for the fields.
    1392             :     // Capture projection.
    1393         104 :     OGRSpatialReference oSRS;
    1394         104 :     bool bGeogCRSSet = false;
    1395         104 :     if (oSRS.importFromESRI(papszCSS) != OGRERR_NONE)
    1396             :     {
    1397          29 :         oSRS.Clear();
    1398             : 
    1399          29 :         if (STARTS_WITH_CI(papszFields[0], "UTM") && nCount >= 9)
    1400             :         {
    1401          17 :             oSRS.SetUTM(atoi(papszFields[7]), !EQUAL(papszFields[8], "South"));
    1402          17 :             if (nCount >= 10 && strstr(papszFields[9], "=") == nullptr)
    1403           2 :                 SetENVIDatum(&oSRS, papszFields[9]);
    1404             :             else
    1405          15 :                 oSRS.SetWellKnownGeogCS("NAD27");
    1406          17 :             bGeogCRSSet = true;
    1407             :         }
    1408          12 :         else if (STARTS_WITH_CI(papszFields[0], "State Plane (NAD 27)") &&
    1409             :                  nCount > 7)
    1410             :         {
    1411           0 :             oSRS.SetStatePlane(ITTVISToUSGSZone(atoi(papszFields[7])), FALSE);
    1412           0 :             bGeogCRSSet = true;
    1413             :         }
    1414          12 :         else if (STARTS_WITH_CI(papszFields[0], "State Plane (NAD 83)") &&
    1415             :                  nCount > 7)
    1416             :         {
    1417           0 :             oSRS.SetStatePlane(ITTVISToUSGSZone(atoi(papszFields[7])), TRUE);
    1418           0 :             bGeogCRSSet = true;
    1419             :         }
    1420          12 :         else if (STARTS_WITH_CI(papszFields[0], "Geographic Lat") && nCount > 7)
    1421             :         {
    1422           0 :             if (strstr(papszFields[7], "=") == nullptr)
    1423           0 :                 SetENVIDatum(&oSRS, papszFields[7]);
    1424             :             else
    1425           0 :                 oSRS.SetWellKnownGeogCS("WGS84");
    1426           0 :             bGeogCRSSet = true;
    1427             :         }
    1428          12 :         else if (nPICount > 8 && atoi(papszPI[0]) == 3)  // TM
    1429             :         {
    1430           0 :             oSRS.SetTM(CPLAtofM(papszPI[3]), CPLAtofM(papszPI[4]),
    1431           0 :                        CPLAtofM(papszPI[7]), CPLAtofM(papszPI[5]),
    1432           0 :                        CPLAtofM(papszPI[6]));
    1433             :         }
    1434          12 :         else if (nPICount > 8 && atoi(papszPI[0]) == 4)
    1435             :         {
    1436             :             // Lambert Conformal Conic
    1437           0 :             oSRS.SetLCC(CPLAtofM(papszPI[7]), CPLAtofM(papszPI[8]),
    1438           0 :                         CPLAtofM(papszPI[3]), CPLAtofM(papszPI[4]),
    1439           0 :                         CPLAtofM(papszPI[5]), CPLAtofM(papszPI[6]));
    1440             :         }
    1441          12 :         else if (nPICount > 10 && atoi(papszPI[0]) == 5)
    1442             :         {
    1443             :             // Oblique Merc (2 point).
    1444           0 :             oSRS.SetHOM2PNO(CPLAtofM(papszPI[3]), CPLAtofM(papszPI[4]),
    1445           0 :                             CPLAtofM(papszPI[5]), CPLAtofM(papszPI[6]),
    1446           0 :                             CPLAtofM(papszPI[7]), CPLAtofM(papszPI[10]),
    1447           0 :                             CPLAtofM(papszPI[8]), CPLAtofM(papszPI[9]));
    1448             :         }
    1449          12 :         else if (nPICount > 8 && atoi(papszPI[0]) == 6)  // Oblique Merc
    1450             :         {
    1451           0 :             oSRS.SetHOM(CPLAtofM(papszPI[3]), CPLAtofM(papszPI[4]),
    1452           0 :                         CPLAtofM(papszPI[5]), 0.0, CPLAtofM(papszPI[8]),
    1453           0 :                         CPLAtofM(papszPI[6]), CPLAtofM(papszPI[7]));
    1454             :         }
    1455          12 :         else if (nPICount > 8 && atoi(papszPI[0]) == 7)  // Stereographic
    1456             :         {
    1457           0 :             oSRS.SetStereographic(CPLAtofM(papszPI[3]), CPLAtofM(papszPI[4]),
    1458           0 :                                   CPLAtofM(papszPI[7]), CPLAtofM(papszPI[5]),
    1459           0 :                                   CPLAtofM(papszPI[6]));
    1460             :         }
    1461          12 :         else if (nPICount > 8 && atoi(papszPI[0]) == 9)  // Albers Equal Area
    1462             :         {
    1463           9 :             oSRS.SetACEA(CPLAtofM(papszPI[7]), CPLAtofM(papszPI[8]),
    1464           9 :                          CPLAtofM(papszPI[3]), CPLAtofM(papszPI[4]),
    1465           9 :                          CPLAtofM(papszPI[5]), CPLAtofM(papszPI[6]));
    1466             :         }
    1467           3 :         else if (nPICount > 6 && atoi(papszPI[0]) == 10)  // Polyconic
    1468             :         {
    1469           0 :             oSRS.SetPolyconic(CPLAtofM(papszPI[3]), CPLAtofM(papszPI[4]),
    1470           0 :                               CPLAtofM(papszPI[5]), CPLAtofM(papszPI[6]));
    1471             :         }
    1472           3 :         else if (nPICount > 6 && atoi(papszPI[0]) == 11)  // LAEA
    1473             :         {
    1474           0 :             oSRS.SetLAEA(CPLAtofM(papszPI[3]), CPLAtofM(papszPI[4]),
    1475           0 :                          CPLAtofM(papszPI[5]), CPLAtofM(papszPI[6]));
    1476             :         }
    1477           3 :         else if (nPICount > 6 && atoi(papszPI[0]) == 12)  // Azimuthal Equid.
    1478             :         {
    1479           0 :             oSRS.SetAE(CPLAtofM(papszPI[3]), CPLAtofM(papszPI[4]),
    1480           0 :                        CPLAtofM(papszPI[5]), CPLAtofM(papszPI[6]));
    1481             :         }
    1482           3 :         else if (nPICount > 6 && atoi(papszPI[0]) == 31)  // Polar Stereographic
    1483             :         {
    1484           0 :             oSRS.SetPS(CPLAtofM(papszPI[3]), CPLAtofM(papszPI[4]), 1.0,
    1485           0 :                        CPLAtofM(papszPI[5]), CPLAtofM(papszPI[6]));
    1486             :         }
    1487             :     }
    1488             :     else
    1489             :     {
    1490          75 :         bGeogCRSSet = CPL_TO_BOOL(oSRS.IsProjected());
    1491             :     }
    1492             : 
    1493         104 :     CSLDestroy(papszCSS);
    1494             : 
    1495             :     // Still lots more that could be added for someone with the patience.
    1496             : 
    1497             :     // Fallback to localcs if we don't recognise things.
    1498         104 :     if (oSRS.IsEmpty())
    1499           3 :         oSRS.SetLocalCS(papszFields[0]);
    1500             : 
    1501             :     // Try to set datum from projection info line if we have a
    1502             :     // projected coordinate system without a GEOGCS explicitly set.
    1503         104 :     if (oSRS.IsProjected() && !bGeogCRSSet && nPICount > 3)
    1504             :     {
    1505             :         // Do we have a datum on the projection info line?
    1506           9 :         int iDatum = nPICount - 1;
    1507             : 
    1508             :         // Ignore units= items.
    1509           9 :         if (strstr(papszPI[iDatum], "=") != nullptr)
    1510           0 :             iDatum--;
    1511             : 
    1512             :         // Skip past the name.
    1513           9 :         iDatum--;
    1514             : 
    1515          18 :         const CPLString osDatumName = papszPI[iDatum];
    1516           9 :         if (osDatumName.find_first_of("abcdefghijklmnopqrstuvwxyz"
    1517           9 :                                       "ABCDEFGHIJKLMNOPQRSTUVWXYZ") !=
    1518             :             CPLString::npos)
    1519             :         {
    1520           0 :             SetENVIDatum(&oSRS, osDatumName);
    1521             :         }
    1522             :         else
    1523             :         {
    1524           9 :             SetENVIEllipse(&oSRS, papszPI + 1);
    1525             :         }
    1526             :     }
    1527             : 
    1528             :     // Try to process specialized units.
    1529         104 :     if (pszUnits != nullptr)
    1530             :     {
    1531             :         // Handle linear units first.
    1532           2 :         if (EQUAL(pszUnits, "Feet"))
    1533           0 :             oSRS.SetLinearUnitsAndUpdateParameters(SRS_UL_FOOT,
    1534             :                                                    CPLAtof(SRS_UL_FOOT_CONV));
    1535           2 :         else if (EQUAL(pszUnits, "Meters"))
    1536           2 :             oSRS.SetLinearUnitsAndUpdateParameters(SRS_UL_METER, 1.0);
    1537           0 :         else if (EQUAL(pszUnits, "Km"))
    1538           0 :             oSRS.SetLinearUnitsAndUpdateParameters("Kilometer", 1000.0);
    1539           0 :         else if (EQUAL(pszUnits, "Yards"))
    1540           0 :             oSRS.SetLinearUnitsAndUpdateParameters("Yard", 0.9144);
    1541           0 :         else if (EQUAL(pszUnits, "Miles"))
    1542           0 :             oSRS.SetLinearUnitsAndUpdateParameters("Mile", 1609.344);
    1543           0 :         else if (EQUAL(pszUnits, "Nautical Miles"))
    1544           0 :             oSRS.SetLinearUnitsAndUpdateParameters(
    1545             :                 SRS_UL_NAUTICAL_MILE, CPLAtof(SRS_UL_NAUTICAL_MILE_CONV));
    1546             : 
    1547             :         // Only handle angular units if we know the projection is geographic.
    1548           2 :         if (oSRS.IsGeographic())
    1549             :         {
    1550           0 :             if (EQUAL(pszUnits, "Radians"))
    1551             :             {
    1552           0 :                 oSRS.SetAngularUnits(SRS_UA_RADIAN, 1.0);
    1553             :             }
    1554             :             else
    1555             :             {
    1556             :                 // Degrees, minutes and seconds will all be represented
    1557             :                 // as degrees.
    1558           0 :                 oSRS.SetAngularUnits(SRS_UA_DEGREE,
    1559             :                                      CPLAtof(SRS_UA_DEGREE_CONV));
    1560             : 
    1561           0 :                 double conversionFactor = 1.0;
    1562           0 :                 if (EQUAL(pszUnits, "Minutes"))
    1563           0 :                     conversionFactor = 60.0;
    1564           0 :                 else if (EQUAL(pszUnits, "Seconds"))
    1565           0 :                     conversionFactor = 3600.0;
    1566           0 :                 adfGeoTransform[0] /= conversionFactor;
    1567           0 :                 adfGeoTransform[1] /= conversionFactor;
    1568           0 :                 adfGeoTransform[2] /= conversionFactor;
    1569           0 :                 adfGeoTransform[3] /= conversionFactor;
    1570           0 :                 adfGeoTransform[4] /= conversionFactor;
    1571           0 :                 adfGeoTransform[5] /= conversionFactor;
    1572             :             }
    1573             :         }
    1574             :     }
    1575             : 
    1576             :     // Try to identify the CRS with the database
    1577         104 :     auto poBestCRSMatch = oSRS.FindBestMatch();
    1578         104 :     if (poBestCRSMatch)
    1579             :     {
    1580          61 :         m_oSRS = *poBestCRSMatch;
    1581          61 :         poBestCRSMatch->Release();
    1582             :     }
    1583             :     else
    1584             :     {
    1585          43 :         m_oSRS = std::move(oSRS);
    1586             :     }
    1587         104 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1588             : 
    1589         104 :     CSLDestroy(papszFields);
    1590         104 :     CSLDestroy(papszPI);
    1591         104 :     return true;
    1592             : }
    1593             : 
    1594             : /************************************************************************/
    1595             : /*                           ProcessRPCinfo()                           */
    1596             : /*                                                                      */
    1597             : /*      Extract RPC transformation coefficients if they are present     */
    1598             : /*      and sets into the standard metadata fields for RPC.             */
    1599             : /************************************************************************/
    1600             : 
    1601           3 : void ENVIDataset::ProcessRPCinfo(const char *pszRPCinfo, int numCols,
    1602             :                                  int numRows)
    1603             : {
    1604           3 :     char **papszFields = SplitList(pszRPCinfo);
    1605           3 :     const int nCount = CSLCount(papszFields);
    1606             : 
    1607           3 :     if (nCount < 90)
    1608             :     {
    1609           0 :         CSLDestroy(papszFields);
    1610           0 :         return;
    1611             :     }
    1612             : 
    1613           3 :     char sVal[1280] = {'\0'};
    1614           3 :     CPLsnprintf(sVal, sizeof(sVal), "%.16g", CPLAtof(papszFields[0]));
    1615           3 :     SetMetadataItem("LINE_OFF", sVal, "RPC");
    1616           3 :     CPLsnprintf(sVal, sizeof(sVal), "%.16g", CPLAtof(papszFields[5]));
    1617           3 :     SetMetadataItem("LINE_SCALE", sVal, "RPC");
    1618           3 :     CPLsnprintf(sVal, sizeof(sVal), "%.16g", CPLAtof(papszFields[1]));
    1619           3 :     SetMetadataItem("SAMP_OFF", sVal, "RPC");
    1620           3 :     CPLsnprintf(sVal, sizeof(sVal), "%.16g", CPLAtof(papszFields[6]));
    1621           3 :     SetMetadataItem("SAMP_SCALE", sVal, "RPC");
    1622           3 :     CPLsnprintf(sVal, sizeof(sVal), "%.16g", CPLAtof(papszFields[2]));
    1623           3 :     SetMetadataItem("LAT_OFF", sVal, "RPC");
    1624           3 :     CPLsnprintf(sVal, sizeof(sVal), "%.16g", CPLAtof(papszFields[7]));
    1625           3 :     SetMetadataItem("LAT_SCALE", sVal, "RPC");
    1626           3 :     CPLsnprintf(sVal, sizeof(sVal), "%.16g", CPLAtof(papszFields[3]));
    1627           3 :     SetMetadataItem("LONG_OFF", sVal, "RPC");
    1628           3 :     CPLsnprintf(sVal, sizeof(sVal), "%.16g", CPLAtof(papszFields[8]));
    1629           3 :     SetMetadataItem("LONG_SCALE", sVal, "RPC");
    1630           3 :     CPLsnprintf(sVal, sizeof(sVal), "%.16g", CPLAtof(papszFields[4]));
    1631           3 :     SetMetadataItem("HEIGHT_OFF", sVal, "RPC");
    1632           3 :     CPLsnprintf(sVal, sizeof(sVal), "%.16g", CPLAtof(papszFields[9]));
    1633           3 :     SetMetadataItem("HEIGHT_SCALE", sVal, "RPC");
    1634             : 
    1635           3 :     sVal[0] = '\0';
    1636          63 :     for (int i = 0; i < 20; i++)
    1637          60 :         CPLsnprintf(sVal + strlen(sVal), sizeof(sVal) - strlen(sVal), "%.16g ",
    1638          60 :                     CPLAtof(papszFields[10 + i]));
    1639           3 :     SetMetadataItem("LINE_NUM_COEFF", sVal, "RPC");
    1640             : 
    1641           3 :     sVal[0] = '\0';
    1642          63 :     for (int i = 0; i < 20; i++)
    1643          60 :         CPLsnprintf(sVal + strlen(sVal), sizeof(sVal) - strlen(sVal), "%.16g ",
    1644          60 :                     CPLAtof(papszFields[30 + i]));
    1645           3 :     SetMetadataItem("LINE_DEN_COEFF", sVal, "RPC");
    1646             : 
    1647           3 :     sVal[0] = '\0';
    1648          63 :     for (int i = 0; i < 20; i++)
    1649          60 :         CPLsnprintf(sVal + strlen(sVal), sizeof(sVal) - strlen(sVal), "%.16g ",
    1650          60 :                     CPLAtof(papszFields[50 + i]));
    1651           3 :     SetMetadataItem("SAMP_NUM_COEFF", sVal, "RPC");
    1652             : 
    1653           3 :     sVal[0] = '\0';
    1654          63 :     for (int i = 0; i < 20; i++)
    1655          60 :         CPLsnprintf(sVal + strlen(sVal), sizeof(sVal) - strlen(sVal), "%.16g ",
    1656          60 :                     CPLAtof(papszFields[70 + i]));
    1657           3 :     SetMetadataItem("SAMP_DEN_COEFF", sVal, "RPC");
    1658             : 
    1659           3 :     CPLsnprintf(sVal, sizeof(sVal), "%.16g",
    1660           3 :                 CPLAtof(papszFields[3]) - CPLAtof(papszFields[8]));
    1661           3 :     SetMetadataItem("MIN_LONG", sVal, "RPC");
    1662             : 
    1663           3 :     CPLsnprintf(sVal, sizeof(sVal), "%.16g",
    1664           3 :                 CPLAtof(papszFields[3]) + CPLAtof(papszFields[8]));
    1665           3 :     SetMetadataItem("MAX_LONG", sVal, "RPC");
    1666             : 
    1667           3 :     CPLsnprintf(sVal, sizeof(sVal), "%.16g",
    1668           3 :                 CPLAtof(papszFields[2]) - CPLAtof(papszFields[7]));
    1669           3 :     SetMetadataItem("MIN_LAT", sVal, "RPC");
    1670             : 
    1671           3 :     CPLsnprintf(sVal, sizeof(sVal), "%.16g",
    1672           3 :                 CPLAtof(papszFields[2]) + CPLAtof(papszFields[7]));
    1673           3 :     SetMetadataItem("MAX_LAT", sVal, "RPC");
    1674             : 
    1675           3 :     if (nCount == 93)
    1676             :     {
    1677           3 :         SetMetadataItem("TILE_ROW_OFFSET", papszFields[90], "RPC");
    1678           3 :         SetMetadataItem("TILE_COL_OFFSET", papszFields[91], "RPC");
    1679           3 :         SetMetadataItem("ENVI_RPC_EMULATION", papszFields[92], "RPC");
    1680             :     }
    1681             : 
    1682             :     // Handle the chipping case where the image is a subset.
    1683           3 :     const double rowOffset = (nCount == 93) ? CPLAtof(papszFields[90]) : 0;
    1684           3 :     const double colOffset = (nCount == 93) ? CPLAtof(papszFields[91]) : 0;
    1685           3 :     if (rowOffset != 0.0 || colOffset != 0.0)
    1686             :     {
    1687           0 :         SetMetadataItem("ICHIP_SCALE_FACTOR", "1");
    1688           0 :         SetMetadataItem("ICHIP_ANAMORPH_CORR", "0");
    1689           0 :         SetMetadataItem("ICHIP_SCANBLK_NUM", "0");
    1690             : 
    1691           0 :         SetMetadataItem("ICHIP_OP_ROW_11", "0.5");
    1692           0 :         SetMetadataItem("ICHIP_OP_COL_11", "0.5");
    1693           0 :         SetMetadataItem("ICHIP_OP_ROW_12", "0.5");
    1694           0 :         SetMetadataItem("ICHIP_OP_COL_21", "0.5");
    1695           0 :         CPLsnprintf(sVal, sizeof(sVal), "%.16g", numCols - 0.5);
    1696           0 :         SetMetadataItem("ICHIP_OP_COL_12", sVal);
    1697           0 :         SetMetadataItem("ICHIP_OP_COL_22", sVal);
    1698           0 :         CPLsnprintf(sVal, sizeof(sVal), "%.16g", numRows - 0.5);
    1699           0 :         SetMetadataItem("ICHIP_OP_ROW_21", sVal);
    1700           0 :         SetMetadataItem("ICHIP_OP_ROW_22", sVal);
    1701             : 
    1702           0 :         CPLsnprintf(sVal, sizeof(sVal), "%.16g", rowOffset + 0.5);
    1703           0 :         SetMetadataItem("ICHIP_FI_ROW_11", sVal);
    1704           0 :         SetMetadataItem("ICHIP_FI_ROW_12", sVal);
    1705           0 :         CPLsnprintf(sVal, sizeof(sVal), "%.16g", colOffset + 0.5);
    1706           0 :         SetMetadataItem("ICHIP_FI_COL_11", sVal);
    1707           0 :         SetMetadataItem("ICHIP_FI_COL_21", sVal);
    1708           0 :         CPLsnprintf(sVal, sizeof(sVal), "%.16g", colOffset + numCols - 0.5);
    1709           0 :         SetMetadataItem("ICHIP_FI_COL_12", sVal);
    1710           0 :         SetMetadataItem("ICHIP_FI_COL_22", sVal);
    1711           0 :         CPLsnprintf(sVal, sizeof(sVal), "%.16g", rowOffset + numRows - 0.5);
    1712           0 :         SetMetadataItem("ICHIP_FI_ROW_21", sVal);
    1713           0 :         SetMetadataItem("ICHIP_FI_ROW_22", sVal);
    1714             :     }
    1715           3 :     CSLDestroy(papszFields);
    1716             : }
    1717             : 
    1718             : /************************************************************************/
    1719             : /*                             GetGCPCount()                            */
    1720             : /************************************************************************/
    1721             : 
    1722          95 : int ENVIDataset::GetGCPCount()
    1723             : {
    1724          95 :     int nGCPCount = RawDataset::GetGCPCount();
    1725          95 :     if (nGCPCount)
    1726           1 :         return nGCPCount;
    1727          94 :     return static_cast<int>(m_asGCPs.size());
    1728             : }
    1729             : 
    1730             : /************************************************************************/
    1731             : /*                              GetGCPs()                               */
    1732             : /************************************************************************/
    1733             : 
    1734           2 : const GDAL_GCP *ENVIDataset::GetGCPs()
    1735             : {
    1736           2 :     int nGCPCount = RawDataset::GetGCPCount();
    1737           2 :     if (nGCPCount)
    1738           1 :         return RawDataset::GetGCPs();
    1739           1 :     if (!m_asGCPs.empty())
    1740           1 :         return m_asGCPs.data();
    1741           0 :     return nullptr;
    1742             : }
    1743             : 
    1744             : /************************************************************************/
    1745             : /*                         ProcessGeoPoints()                           */
    1746             : /*                                                                      */
    1747             : /*      Extract GCPs                                                    */
    1748             : /************************************************************************/
    1749             : 
    1750           2 : void ENVIDataset::ProcessGeoPoints(const char *pszGeoPoints)
    1751             : {
    1752           2 :     char **papszFields = SplitList(pszGeoPoints);
    1753           2 :     const int nCount = CSLCount(papszFields);
    1754             : 
    1755           2 :     if ((nCount % 4) != 0)
    1756             :     {
    1757           0 :         CSLDestroy(papszFields);
    1758           0 :         return;
    1759             :     }
    1760           2 :     m_asGCPs.resize(nCount / 4);
    1761           2 :     if (!m_asGCPs.empty())
    1762             :     {
    1763           2 :         GDALInitGCPs(static_cast<int>(m_asGCPs.size()), m_asGCPs.data());
    1764             :     }
    1765           4 :     for (int i = 0; i < static_cast<int>(m_asGCPs.size()); i++)
    1766             :     {
    1767             :         // Subtract 1 to pixel and line for ENVI convention
    1768           2 :         m_asGCPs[i].dfGCPPixel = CPLAtof(papszFields[i * 4 + 0]) - 1;
    1769           2 :         m_asGCPs[i].dfGCPLine = CPLAtof(papszFields[i * 4 + 1]) - 1;
    1770           2 :         m_asGCPs[i].dfGCPY = CPLAtof(papszFields[i * 4 + 2]);
    1771           2 :         m_asGCPs[i].dfGCPX = CPLAtof(papszFields[i * 4 + 3]);
    1772           2 :         m_asGCPs[i].dfGCPZ = 0;
    1773             :     }
    1774           2 :     CSLDestroy(papszFields);
    1775             : }
    1776             : 
    1777           1 : static unsigned byteSwapUInt(unsigned swapMe)
    1778             : {
    1779           1 :     CPL_MSBPTR32(&swapMe);
    1780           1 :     return swapMe;
    1781             : }
    1782             : 
    1783         249 : void ENVIDataset::ProcessStatsFile()
    1784             : {
    1785         249 :     osStaFilename = CPLResetExtension(pszHDRFilename, "sta");
    1786         249 :     VSILFILE *fpStaFile = VSIFOpenL(osStaFilename, "rb");
    1787             : 
    1788         249 :     if (!fpStaFile)
    1789             :     {
    1790         248 :         osStaFilename = "";
    1791         248 :         return;
    1792             :     }
    1793             : 
    1794           1 :     int lTestHeader[10] = {0};
    1795           1 :     if (VSIFReadL(lTestHeader, sizeof(int), 10, fpStaFile) != 10)
    1796             :     {
    1797           0 :         CPL_IGNORE_RET_VAL(VSIFCloseL(fpStaFile));
    1798           0 :         osStaFilename = "";
    1799           0 :         return;
    1800             :     }
    1801             : 
    1802           1 :     const bool isFloat = byteSwapInt(lTestHeader[0]) == 1111838282;
    1803             : 
    1804           1 :     int nb = byteSwapInt(lTestHeader[3]);
    1805             : 
    1806           1 :     if (nb < 0 || nb > nBands)
    1807             :     {
    1808           0 :         CPLDebug("ENVI",
    1809             :                  ".sta file has statistics for %d bands, "
    1810             :                  "whereas the dataset has only %d bands",
    1811             :                  nb, nBands);
    1812           0 :         nb = nBands;
    1813             :     }
    1814             : 
    1815             :     // TODO(schwehr): What are 1, 4, 8, and 40?
    1816           1 :     unsigned lOffset = 0;
    1817           1 :     if (VSIFSeekL(fpStaFile, 40 + static_cast<vsi_l_offset>(nb + 1) * 4,
    1818           1 :                   SEEK_SET) == 0 &&
    1819           2 :         VSIFReadL(&lOffset, sizeof(lOffset), 1, fpStaFile) == 1 &&
    1820           1 :         VSIFSeekL(fpStaFile,
    1821           2 :                   40 + static_cast<vsi_l_offset>(nb + 1) * 8 +
    1822           1 :                       byteSwapUInt(lOffset) + nb,
    1823             :                   SEEK_SET) == 0)
    1824             :     {
    1825             :         // This should be the beginning of the statistics.
    1826           1 :         if (isFloat)
    1827             :         {
    1828           0 :             float *fStats = static_cast<float *>(CPLCalloc(nb * 4, 4));
    1829           0 :             if (static_cast<int>(VSIFReadL(fStats, 4, nb * 4, fpStaFile)) ==
    1830           0 :                 nb * 4)
    1831             :             {
    1832           0 :                 for (int i = 0; i < nb; i++)
    1833             :                 {
    1834           0 :                     GetRasterBand(i + 1)->SetStatistics(
    1835           0 :                         byteSwapFloat(fStats[i]), byteSwapFloat(fStats[nb + i]),
    1836           0 :                         byteSwapFloat(fStats[2 * nb + i]),
    1837           0 :                         byteSwapFloat(fStats[3 * nb + i]));
    1838             :                 }
    1839             :             }
    1840           0 :             CPLFree(fStats);
    1841             :         }
    1842             :         else
    1843             :         {
    1844           1 :             double *dStats = static_cast<double *>(CPLCalloc(nb * 4, 8));
    1845           1 :             if (static_cast<int>(VSIFReadL(dStats, 8, nb * 4, fpStaFile)) ==
    1846           1 :                 nb * 4)
    1847             :             {
    1848           7 :                 for (int i = 0; i < nb; i++)
    1849             :                 {
    1850           6 :                     const double dMin = byteSwapDouble(dStats[i]);
    1851           6 :                     const double dMax = byteSwapDouble(dStats[nb + i]);
    1852           6 :                     const double dMean = byteSwapDouble(dStats[2 * nb + i]);
    1853           6 :                     const double dStd = byteSwapDouble(dStats[3 * nb + i]);
    1854           6 :                     if (dMin != dMax && dStd != 0)
    1855           6 :                         GetRasterBand(i + 1)->SetStatistics(dMin, dMax, dMean,
    1856           6 :                                                             dStd);
    1857             :                 }
    1858             :             }
    1859           1 :             CPLFree(dStats);
    1860             :         }
    1861             :     }
    1862           1 :     CPL_IGNORE_RET_VAL(VSIFCloseL(fpStaFile));
    1863             : }
    1864             : 
    1865           2 : int ENVIDataset::byteSwapInt(int swapMe)
    1866             : {
    1867           2 :     CPL_MSBPTR32(&swapMe);
    1868           2 :     return swapMe;
    1869             : }
    1870             : 
    1871           0 : float ENVIDataset::byteSwapFloat(float swapMe)
    1872             : {
    1873           0 :     CPL_MSBPTR32(&swapMe);
    1874           0 :     return swapMe;
    1875             : }
    1876             : 
    1877          24 : double ENVIDataset::byteSwapDouble(double swapMe)
    1878             : {
    1879          24 :     CPL_MSBPTR64(&swapMe);
    1880          24 :     return swapMe;
    1881             : }
    1882             : 
    1883             : /************************************************************************/
    1884             : /*                             ReadHeader()                             */
    1885             : /************************************************************************/
    1886             : 
    1887             : // Always returns true.
    1888         251 : bool ENVIDataset::ReadHeader(VSILFILE *fpHdr)
    1889             : 
    1890             : {
    1891         251 :     CPLReadLine2L(fpHdr, 10000, nullptr);
    1892             : 
    1893             :     // Start forming sets of name/value pairs.
    1894             :     while (true)
    1895             :     {
    1896        2770 :         const char *pszNewLine = CPLReadLine2L(fpHdr, 10000, nullptr);
    1897        2770 :         if (pszNewLine == nullptr)
    1898         251 :             break;
    1899             : 
    1900             :         // Skip leading spaces. This may happen for example with
    1901             :         // AVIRIS datasets (https://aviris.jpl.nasa.gov/dataportal/) whose
    1902             :         // wavelength metadata starts with a leading space.
    1903        2521 :         while (*pszNewLine == ' ')
    1904           2 :             ++pszNewLine;
    1905        2519 :         if (strchr(pszNewLine, '=') == nullptr)
    1906           0 :             continue;
    1907             : 
    1908        2519 :         CPLString osWorkingLine(pszNewLine);
    1909             : 
    1910             :         // Collect additional lines if we have open sqiggly bracket.
    1911        2989 :         if (osWorkingLine.find("{") != std::string::npos &&
    1912         470 :             osWorkingLine.find("}") == std::string::npos)
    1913             :         {
    1914         161 :             do
    1915             :             {
    1916         397 :                 pszNewLine = CPLReadLine2L(fpHdr, 10000, nullptr);
    1917         397 :                 if (pszNewLine)
    1918             :                 {
    1919         397 :                     osWorkingLine += pszNewLine;
    1920             :                 }
    1921         397 :                 if (osWorkingLine.size() > 10 * 1024 * 1024)
    1922           0 :                     return false;
    1923         397 :             } while (pszNewLine != nullptr &&
    1924         397 :                      strchr(pszNewLine, '}') == nullptr);
    1925             :         }
    1926             : 
    1927             :         // Try to break input into name and value portions.  Trim whitespace.
    1928        2519 :         size_t iEqual = osWorkingLine.find("=");
    1929             : 
    1930        2519 :         if (iEqual != std::string::npos && iEqual > 0)
    1931             :         {
    1932        5038 :             CPLString osValue(osWorkingLine.substr(iEqual + 1));
    1933        2519 :             auto found = osValue.find_first_not_of(" \t");
    1934        2519 :             if (found != std::string::npos)
    1935        2519 :                 osValue = osValue.substr(found);
    1936             :             else
    1937           0 :                 osValue.clear();
    1938             : 
    1939        2519 :             osWorkingLine.resize(iEqual);
    1940        2519 :             iEqual--;
    1941        8489 :             while (iEqual > 0 && (osWorkingLine[iEqual] == ' ' ||
    1942        2519 :                                   osWorkingLine[iEqual] == '\t'))
    1943             :             {
    1944        3451 :                 osWorkingLine.resize(iEqual);
    1945        3451 :                 iEqual--;
    1946             :             }
    1947             : 
    1948             :             // Convert spaces in the name to underscores.
    1949       25880 :             for (int i = 0; osWorkingLine[i] != '\0'; i++)
    1950             :             {
    1951       23361 :                 if (osWorkingLine[i] == ' ')
    1952        1476 :                     osWorkingLine[i] = '_';
    1953             :             }
    1954             : 
    1955        2519 :             m_aosHeader.SetNameValue(osWorkingLine, osValue);
    1956             :         }
    1957        2519 :     }
    1958             : 
    1959         251 :     return true;
    1960             : }
    1961             : 
    1962             : /************************************************************************/
    1963             : /*                        GetRawBinaryLayout()                          */
    1964             : /************************************************************************/
    1965             : 
    1966           4 : bool ENVIDataset::GetRawBinaryLayout(GDALDataset::RawBinaryLayout &sLayout)
    1967             : {
    1968             :     const bool bIsCompressed =
    1969           4 :         atoi(m_aosHeader.FetchNameValueDef("file_compression", "0")) != 0;
    1970           4 :     if (bIsCompressed)
    1971           0 :         return false;
    1972           4 :     if (!RawDataset::GetRawBinaryLayout(sLayout))
    1973           0 :         return false;
    1974           4 :     sLayout.osRawFilename = GetDescription();
    1975           4 :     return true;
    1976             : }
    1977             : 
    1978             : /************************************************************************/
    1979             : /*                                Open()                                */
    1980             : /************************************************************************/
    1981             : 
    1982       28037 : GDALDataset *ENVIDataset::Open(GDALOpenInfo *poOpenInfo)
    1983             : {
    1984       28037 :     return Open(poOpenInfo, true);
    1985             : }
    1986             : 
    1987       28126 : ENVIDataset *ENVIDataset::Open(GDALOpenInfo *poOpenInfo, bool bFileSizeCheck)
    1988             : 
    1989             : {
    1990             :     // Assume the caller is pointing to the binary (i.e. .bil) file.
    1991       28126 :     if (poOpenInfo->nHeaderBytes < 2)
    1992       26621 :         return nullptr;
    1993             : 
    1994             :     // Do we have a .hdr file?  Try upper and lower case, and
    1995             :     // replacing the extension as well as appending the extension
    1996             :     // to whatever we currently have.
    1997             : 
    1998        1505 :     const char *pszMode = nullptr;
    1999        1505 :     if (poOpenInfo->eAccess == GA_Update)
    2000         135 :         pszMode = "r+";
    2001             :     else
    2002        1370 :         pszMode = "r";
    2003             : 
    2004        3009 :     CPLString osHdrFilename;
    2005        1504 :     VSILFILE *fpHeader = nullptr;
    2006        1504 :     char **papszSiblingFiles = poOpenInfo->GetSiblingFiles();
    2007        1504 :     if (papszSiblingFiles == nullptr)
    2008             :     {
    2009             :         // First try hdr as an extra extension
    2010             :         osHdrFilename =
    2011           6 :             CPLFormFilename(nullptr, poOpenInfo->pszFilename, "hdr");
    2012           6 :         fpHeader = VSIFOpenL(osHdrFilename, pszMode);
    2013             : 
    2014           6 :         if (fpHeader == nullptr && VSIIsCaseSensitiveFS(osHdrFilename))
    2015             :         {
    2016             :             osHdrFilename =
    2017           6 :                 CPLFormFilename(nullptr, poOpenInfo->pszFilename, "HDR");
    2018           6 :             fpHeader = VSIFOpenL(osHdrFilename, pszMode);
    2019             :         }
    2020             : 
    2021             :         // Otherwise, try .hdr as a replacement extension
    2022           6 :         if (fpHeader == nullptr)
    2023             :         {
    2024           6 :             osHdrFilename = CPLResetExtension(poOpenInfo->pszFilename, "hdr");
    2025           6 :             fpHeader = VSIFOpenL(osHdrFilename, pszMode);
    2026             :         }
    2027             : 
    2028           6 :         if (fpHeader == nullptr && VSIIsCaseSensitiveFS(osHdrFilename))
    2029             :         {
    2030           5 :             osHdrFilename = CPLResetExtension(poOpenInfo->pszFilename, "HDR");
    2031           5 :             fpHeader = VSIFOpenL(osHdrFilename, pszMode);
    2032             :         }
    2033             :     }
    2034             :     else
    2035             :     {
    2036             :         // Now we need to tear apart the filename to form a .HDR filename.
    2037        2996 :         CPLString osPath = CPLGetPath(poOpenInfo->pszFilename);
    2038        2996 :         CPLString osName = CPLGetFilename(poOpenInfo->pszFilename);
    2039             : 
    2040             :         // First try hdr as an extra extension
    2041        1498 :         int iFile = CSLFindString(papszSiblingFiles,
    2042             :                                   CPLFormFilename(nullptr, osName, "hdr"));
    2043        1498 :         if (iFile < 0)
    2044             :         {
    2045             :             // Otherwise, try .hdr as a replacement extension
    2046        1364 :             iFile = CSLFindString(papszSiblingFiles,
    2047             :                                   CPLResetExtension(osName, "hdr"));
    2048             :         }
    2049             : 
    2050        1498 :         if (iFile >= 0)
    2051             :         {
    2052             :             osHdrFilename =
    2053         330 :                 CPLFormFilename(osPath, papszSiblingFiles[iFile], nullptr);
    2054         330 :             fpHeader = VSIFOpenL(osHdrFilename, pszMode);
    2055             :         }
    2056             :     }
    2057             : 
    2058        1504 :     if (fpHeader == nullptr)
    2059        1173 :         return nullptr;
    2060             : 
    2061             :     // Check that the first line says "ENVI".
    2062         331 :     char szTestHdr[4] = {'\0'};
    2063             : 
    2064         331 :     if (VSIFReadL(szTestHdr, 4, 1, fpHeader) != 1)
    2065             :     {
    2066           0 :         CPL_IGNORE_RET_VAL(VSIFCloseL(fpHeader));
    2067           0 :         return nullptr;
    2068             :     }
    2069         331 :     if (!STARTS_WITH(szTestHdr, "ENVI"))
    2070             :     {
    2071          80 :         CPL_IGNORE_RET_VAL(VSIFCloseL(fpHeader));
    2072          80 :         return nullptr;
    2073             :     }
    2074             : 
    2075             :     // Create a corresponding GDALDataset.
    2076         502 :     auto poDS = std::make_unique<ENVIDataset>();
    2077         251 :     poDS->pszHDRFilename = CPLStrdup(osHdrFilename);
    2078         251 :     poDS->fp = fpHeader;
    2079             : 
    2080             :     // Read the header.
    2081         251 :     if (!poDS->ReadHeader(fpHeader))
    2082             :     {
    2083           0 :         return nullptr;
    2084             :     }
    2085             : 
    2086             :     // Has the user selected the .hdr file to open?
    2087         251 :     if (EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "hdr"))
    2088             :     {
    2089           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2090             :                  "The selected file is an ENVI header file, but to "
    2091             :                  "open ENVI datasets, the data file should be selected "
    2092             :                  "instead of the .hdr file.  Please try again selecting "
    2093             :                  "the data file corresponding to the header file:  "
    2094             :                  "%s",
    2095             :                  poOpenInfo->pszFilename);
    2096           0 :         return nullptr;
    2097             :     }
    2098             : 
    2099             :     // Has the user selected the .sta (stats) file to open?
    2100         251 :     if (EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "sta"))
    2101             :     {
    2102           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2103             :                  "The selected file is an ENVI statistics file. "
    2104             :                  "To open ENVI datasets, the data file should be selected "
    2105             :                  "instead of the .sta file.  Please try again selecting "
    2106             :                  "the data file corresponding to the statistics file:  "
    2107             :                  "%s",
    2108             :                  poOpenInfo->pszFilename);
    2109           0 :         return nullptr;
    2110             :     }
    2111             : 
    2112             :     // Extract required values from the .hdr.
    2113         251 :     int nLines = atoi(poDS->m_aosHeader.FetchNameValueDef("lines", "0"));
    2114             : 
    2115         251 :     int nSamples = atoi(poDS->m_aosHeader.FetchNameValueDef("samples", "0"));
    2116             : 
    2117         251 :     int nBands = atoi(poDS->m_aosHeader.FetchNameValueDef("bands", "0"));
    2118             : 
    2119             :     // In case, there is no interleave keyword, we try to derive it from the
    2120             :     // file extension.
    2121         251 :     CPLString osInterleave = poDS->m_aosHeader.FetchNameValueDef(
    2122         502 :         "interleave", CPLGetExtension(poOpenInfo->pszFilename));
    2123             : 
    2124         251 :     if (!STARTS_WITH_CI(osInterleave, "BSQ") &&
    2125         262 :         !STARTS_WITH_CI(osInterleave, "BIP") &&
    2126          11 :         !STARTS_WITH_CI(osInterleave, "BIL"))
    2127             :     {
    2128           0 :         CPLDebug("ENVI", "Unset or unknown value for 'interleave' keyword --> "
    2129             :                          "assuming BSQ interleaving");
    2130           0 :         osInterleave = "bsq";
    2131             :     }
    2132             : 
    2133         502 :     if (!GDALCheckDatasetDimensions(nSamples, nLines) ||
    2134         251 :         !GDALCheckBandCount(nBands, FALSE))
    2135             :     {
    2136           2 :         CPLError(CE_Failure, CPLE_AppDefined,
    2137             :                  "The file appears to have an associated ENVI header, but "
    2138             :                  "one or more of the samples, lines and bands "
    2139             :                  "keywords appears to be missing or invalid.");
    2140           2 :         return nullptr;
    2141             :     }
    2142             : 
    2143             :     int nHeaderSize =
    2144         249 :         atoi(poDS->m_aosHeader.FetchNameValueDef("header_offset", "0"));
    2145             : 
    2146             :     // Translate the datatype.
    2147         249 :     GDALDataType eType = GDT_Byte;
    2148             : 
    2149         249 :     const char *pszDataType = poDS->m_aosHeader["data_type"];
    2150         249 :     if (pszDataType != nullptr)
    2151             :     {
    2152         249 :         switch (atoi(pszDataType))
    2153             :         {
    2154         169 :             case 1:
    2155         169 :                 eType = GDT_Byte;
    2156         169 :                 break;
    2157             : 
    2158           9 :             case 2:
    2159           9 :                 eType = GDT_Int16;
    2160           9 :                 break;
    2161             : 
    2162           7 :             case 3:
    2163           7 :                 eType = GDT_Int32;
    2164           7 :                 break;
    2165             : 
    2166          12 :             case 4:
    2167          12 :                 eType = GDT_Float32;
    2168          12 :                 break;
    2169             : 
    2170           7 :             case 5:
    2171           7 :                 eType = GDT_Float64;
    2172           7 :                 break;
    2173             : 
    2174           8 :             case 6:
    2175           8 :                 eType = GDT_CFloat32;
    2176           8 :                 break;
    2177             : 
    2178           5 :             case 9:
    2179           5 :                 eType = GDT_CFloat64;
    2180           5 :                 break;
    2181             : 
    2182          17 :             case 12:
    2183          17 :                 eType = GDT_UInt16;
    2184          17 :                 break;
    2185             : 
    2186           7 :             case 13:
    2187           7 :                 eType = GDT_UInt32;
    2188           7 :                 break;
    2189             : 
    2190           4 :             case 14:
    2191           4 :                 eType = GDT_Int64;
    2192           4 :                 break;
    2193             : 
    2194           4 :             case 15:
    2195           4 :                 eType = GDT_UInt64;
    2196           4 :                 break;
    2197             : 
    2198           0 :             default:
    2199           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2200             :                          "The file does not have a value for the data_type "
    2201             :                          "that is recognised by the GDAL ENVI driver.");
    2202           0 :                 return nullptr;
    2203             :         }
    2204             :     }
    2205             : 
    2206             :     // Translate the byte order.
    2207         249 :     RawRasterBand::ByteOrder eByteOrder = RawRasterBand::NATIVE_BYTE_ORDER;
    2208             : 
    2209         249 :     const char *pszByteOrder = poDS->m_aosHeader["byte_order"];
    2210         249 :     if (pszByteOrder != nullptr)
    2211             :     {
    2212         249 :         eByteOrder = atoi(pszByteOrder) == 0
    2213         249 :                          ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
    2214             :                          : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN;
    2215             :     }
    2216             : 
    2217             :     // Warn about unsupported file types virtual mosaic and meta file.
    2218         249 :     const char *pszEnviFileType = poDS->m_aosHeader["file_type"];
    2219         249 :     if (pszEnviFileType != nullptr)
    2220             :     {
    2221             :         // When the file type is one of these we return an invalid file type err
    2222             :         // 'envi meta file'
    2223             :         // 'envi virtual mosaic'
    2224             :         // 'envi spectral library'
    2225             :         // 'envi fft result'
    2226             : 
    2227             :         // When the file type is one of these we open it
    2228             :         // 'envi standard'
    2229             :         // 'envi classification'
    2230             : 
    2231             :         // When the file type is anything else we attempt to open it as a
    2232             :         // raster.
    2233             : 
    2234             :         // envi gdal does not support any of these
    2235             :         // all others we will attempt to open
    2236         249 :         if (EQUAL(pszEnviFileType, "envi meta file") ||
    2237         249 :             EQUAL(pszEnviFileType, "envi virtual mosaic") ||
    2238         249 :             EQUAL(pszEnviFileType, "envi spectral library"))
    2239             :         {
    2240           0 :             CPLError(CE_Failure, CPLE_OpenFailed,
    2241             :                      "File %s contains an invalid file type in the ENVI .hdr "
    2242             :                      "GDAL does not support '%s' type files.",
    2243             :                      poOpenInfo->pszFilename, pszEnviFileType);
    2244           0 :             return nullptr;
    2245             :         }
    2246             :     }
    2247             : 
    2248             :     // Detect (gzipped) compressed datasets.
    2249             :     const bool bIsCompressed =
    2250         249 :         atoi(poDS->m_aosHeader.FetchNameValueDef("file_compression", "0")) != 0;
    2251             : 
    2252             :     // Capture some information from the file that is of interest.
    2253         249 :     poDS->nRasterXSize = nSamples;
    2254         249 :     poDS->nRasterYSize = nLines;
    2255         249 :     poDS->eAccess = poOpenInfo->eAccess;
    2256             : 
    2257             :     // Reopen file in update mode if necessary.
    2258         498 :     CPLString osImageFilename(poOpenInfo->pszFilename);
    2259         249 :     if (bIsCompressed)
    2260           1 :         osImageFilename = "/vsigzip/" + osImageFilename;
    2261         249 :     if (poOpenInfo->eAccess == GA_Update)
    2262             :     {
    2263          94 :         if (bIsCompressed)
    2264             :         {
    2265           0 :             CPLError(CE_Failure, CPLE_OpenFailed,
    2266             :                      "Cannot open compressed file in update mode.");
    2267           0 :             return nullptr;
    2268             :         }
    2269          94 :         poDS->fpImage = VSIFOpenL(osImageFilename, "rb+");
    2270             :     }
    2271             :     else
    2272             :     {
    2273         155 :         poDS->fpImage = VSIFOpenL(osImageFilename, "rb");
    2274             :     }
    2275             : 
    2276         249 :     if (poDS->fpImage == nullptr)
    2277             :     {
    2278           0 :         CPLError(CE_Failure, CPLE_OpenFailed,
    2279             :                  "Failed to re-open %s within ENVI driver.",
    2280             :                  poOpenInfo->pszFilename);
    2281           0 :         return nullptr;
    2282             :     }
    2283             : 
    2284             :     // Compute the line offset.
    2285         249 :     const int nDataSize = GDALGetDataTypeSizeBytes(eType);
    2286         249 :     int nPixelOffset = 0;
    2287         249 :     int nLineOffset = 0;
    2288         249 :     vsi_l_offset nBandOffset = 0;
    2289         249 :     CPLAssert(nDataSize != 0);
    2290         249 :     CPLAssert(nBands != 0);
    2291             : 
    2292         249 :     if (STARTS_WITH_CI(osInterleave, "bil"))
    2293             :     {
    2294          11 :         poDS->interleave = BIL;
    2295          11 :         poDS->SetMetadataItem("INTERLEAVE", "LINE", "IMAGE_STRUCTURE");
    2296          11 :         if (nSamples > std::numeric_limits<int>::max() / (nDataSize * nBands))
    2297             :         {
    2298           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Int overflow occurred.");
    2299           0 :             return nullptr;
    2300             :         }
    2301          11 :         nLineOffset = nDataSize * nSamples * nBands;
    2302          11 :         nPixelOffset = nDataSize;
    2303          11 :         nBandOffset = static_cast<vsi_l_offset>(nDataSize) * nSamples;
    2304             :     }
    2305         238 :     else if (STARTS_WITH_CI(osInterleave, "bip"))
    2306             :     {
    2307          36 :         poDS->interleave = BIP;
    2308          36 :         poDS->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    2309          36 :         if (nSamples > std::numeric_limits<int>::max() / (nDataSize * nBands))
    2310             :         {
    2311           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Int overflow occurred.");
    2312           0 :             return nullptr;
    2313             :         }
    2314          36 :         nLineOffset = nDataSize * nSamples * nBands;
    2315          36 :         nPixelOffset = nDataSize * nBands;
    2316          36 :         nBandOffset = nDataSize;
    2317             :     }
    2318             :     else
    2319             :     {
    2320         202 :         poDS->interleave = BSQ;
    2321         202 :         poDS->SetMetadataItem("INTERLEAVE", "BAND", "IMAGE_STRUCTURE");
    2322         202 :         if (nSamples > std::numeric_limits<int>::max() / nDataSize)
    2323             :         {
    2324           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Int overflow occurred.");
    2325           0 :             return nullptr;
    2326             :         }
    2327         202 :         nLineOffset = nDataSize * nSamples;
    2328         202 :         nPixelOffset = nDataSize;
    2329         202 :         nBandOffset = static_cast<vsi_l_offset>(nLineOffset) * nLines;
    2330             :     }
    2331             : 
    2332         249 :     const char *pszMajorFrameOffset = poDS->m_aosHeader["major_frame_offsets"];
    2333         249 :     if (pszMajorFrameOffset != nullptr)
    2334             :     {
    2335           0 :         char **papszMajorFrameOffsets = poDS->SplitList(pszMajorFrameOffset);
    2336             : 
    2337           0 :         const int nTempCount = CSLCount(papszMajorFrameOffsets);
    2338           0 :         if (nTempCount == 2)
    2339             :         {
    2340           0 :             int nOffset1 = atoi(papszMajorFrameOffsets[0]);
    2341           0 :             int nOffset2 = atoi(papszMajorFrameOffsets[1]);
    2342           0 :             if (nOffset1 >= 0 && nOffset2 >= 0 &&
    2343           0 :                 nHeaderSize < INT_MAX - nOffset1 &&
    2344           0 :                 nOffset1 < INT_MAX - nOffset2 &&
    2345           0 :                 nOffset1 + nOffset2 < INT_MAX - nLineOffset)
    2346             :             {
    2347           0 :                 nHeaderSize += nOffset1;
    2348           0 :                 nLineOffset += nOffset1 + nOffset2;
    2349             :             }
    2350             :         }
    2351           0 :         CSLDestroy(papszMajorFrameOffsets);
    2352             :     }
    2353             : 
    2354             :     // Currently each ENVIRasterBand allocates nPixelOffset * nRasterXSize bytes
    2355             :     // so for a pixel interleaved scheme, this will allocate lots of memory!
    2356             :     // Actually this is quadratic in the number of bands!
    2357             :     // Do a few sanity checks to avoid excessive memory allocation on
    2358             :     // small files.
    2359             :     // But ultimately we should fix RawRasterBand to have a shared buffer
    2360             :     // among bands.
    2361         406 :     if (bFileSizeCheck &&
    2362         157 :         !RAWDatasetCheckMemoryUsage(
    2363         157 :             poDS->nRasterXSize, poDS->nRasterYSize, nBands, nDataSize,
    2364         157 :             nPixelOffset, nLineOffset, nHeaderSize, nBandOffset, poDS->fpImage))
    2365             :     {
    2366           0 :         return nullptr;
    2367             :     }
    2368             : 
    2369             :     // Create band information objects.
    2370        1320 :     for (int i = 0; i < nBands; i++)
    2371             :     {
    2372             :         auto poBand = std::make_unique<ENVIRasterBand>(
    2373        1071 :             poDS.get(), i + 1, poDS->fpImage, nHeaderSize + nBandOffset * i,
    2374        1071 :             nPixelOffset, nLineOffset, eType, eByteOrder);
    2375        1071 :         if (!poBand->IsValid())
    2376           0 :             return nullptr;
    2377        1071 :         poDS->SetBand(i + 1, std::move(poBand));
    2378             :     }
    2379             : 
    2380             :     // Apply band names if we have them.
    2381             :     // Use wavelength for more descriptive information if possible.
    2382         249 :     const char *pszBandNames = poDS->m_aosHeader["band_names"];
    2383         249 :     const char *pszWaveLength = poDS->m_aosHeader["wavelength"];
    2384         249 :     if (pszBandNames != nullptr || pszWaveLength != nullptr)
    2385             :     {
    2386         128 :         char **papszBandNames = poDS->SplitList(pszBandNames);
    2387         128 :         char **papszWL = poDS->SplitList(pszWaveLength);
    2388         128 :         const char *pszFWHM = poDS->m_aosHeader["fwhm"];
    2389         128 :         char **papszFWHM = pszFWHM ? poDS->SplitList(pszFWHM) : nullptr;
    2390             : 
    2391         128 :         const char *pszWLUnits = nullptr;
    2392         128 :         const int nWLCount = CSLCount(papszWL);
    2393         128 :         const int nFWHMCount = CSLCount(papszFWHM);
    2394         128 :         if (papszWL)
    2395             :         {
    2396             :             // If WL information is present, process wavelength units.
    2397           8 :             pszWLUnits = poDS->m_aosHeader["wavelength_units"];
    2398           8 :             if (pszWLUnits)
    2399             :             {
    2400             :                 // Don't show unknown or index units.
    2401           6 :                 if (EQUAL(pszWLUnits, "Unknown") || EQUAL(pszWLUnits, "Index"))
    2402           0 :                     pszWLUnits = nullptr;
    2403             :             }
    2404           8 :             if (pszWLUnits)
    2405             :             {
    2406             :                 // Set wavelength units to dataset metadata.
    2407           6 :                 poDS->SetMetadataItem("wavelength_units", pszWLUnits);
    2408             :             }
    2409             :         }
    2410             : 
    2411         364 :         for (int i = 0; i < nBands; i++)
    2412             :         {
    2413             :             // First set up the wavelength names and units if available.
    2414         472 :             std::string osWavelength;
    2415         236 :             if (papszWL && nWLCount > i)
    2416             :             {
    2417          24 :                 osWavelength = papszWL[i];
    2418          24 :                 if (pszWLUnits)
    2419             :                 {
    2420          18 :                     osWavelength += " ";
    2421          18 :                     osWavelength += pszWLUnits;
    2422             :                 }
    2423             :             }
    2424             : 
    2425             :             // Build the final name for this band.
    2426         472 :             std::string osBandName;
    2427         236 :             if (papszBandNames && CSLCount(papszBandNames) > i)
    2428             :             {
    2429         212 :                 osBandName = papszBandNames[i];
    2430         212 :                 if (!osWavelength.empty())
    2431             :                 {
    2432           0 :                     osBandName += " (";
    2433           0 :                     osBandName += osWavelength;
    2434           0 :                     osBandName += ")";
    2435             :                 }
    2436             :             }
    2437             :             else
    2438             :             {
    2439             :                 // WL but no band names.
    2440          24 :                 osBandName = std::move(osWavelength);
    2441             :             }
    2442             : 
    2443             :             // Description is for internal GDAL usage.
    2444         236 :             poDS->GetRasterBand(i + 1)->SetDescription(osBandName.c_str());
    2445             : 
    2446             :             // Metadata field named Band_1, etc. Needed for ArcGIS integration.
    2447         472 :             CPLString osBandId = CPLSPrintf("Band_%i", i + 1);
    2448         236 :             poDS->SetMetadataItem(osBandId, osBandName.c_str());
    2449             : 
    2450             :             const auto ConvertWaveLength =
    2451         144 :                 [pszWLUnits](double dfVal) -> const char *
    2452             :             {
    2453          36 :                 if (EQUAL(pszWLUnits, "Micrometers") || EQUAL(pszWLUnits, "um"))
    2454             :                 {
    2455          12 :                     return CPLSPrintf("%.3f", dfVal);
    2456             :                 }
    2457          24 :                 else if (EQUAL(pszWLUnits, "Nanometers") ||
    2458          24 :                          EQUAL(pszWLUnits, "nm"))
    2459             :                 {
    2460          12 :                     return CPLSPrintf("%.3f", dfVal / 1000);
    2461             :                 }
    2462          12 :                 else if (EQUAL(pszWLUnits, "Millimeters") ||
    2463          12 :                          EQUAL(pszWLUnits, "mm"))
    2464             :                 {
    2465          12 :                     return CPLSPrintf("%.3f", dfVal * 1000);
    2466             :                 }
    2467             :                 else
    2468             :                 {
    2469           0 :                     return nullptr;
    2470             :                 }
    2471         236 :             };
    2472             : 
    2473             :             // Set wavelength metadata to band.
    2474         236 :             if (papszWL && nWLCount > i)
    2475             :             {
    2476          24 :                 poDS->GetRasterBand(i + 1)->SetMetadataItem("wavelength",
    2477          24 :                                                             papszWL[i]);
    2478             : 
    2479          24 :                 if (pszWLUnits)
    2480             :                 {
    2481          18 :                     poDS->GetRasterBand(i + 1)->SetMetadataItem(
    2482          18 :                         "wavelength_units", pszWLUnits);
    2483             : 
    2484          18 :                     if (const char *pszVal =
    2485          18 :                             ConvertWaveLength(CPLAtof(papszWL[i])))
    2486             :                     {
    2487          18 :                         poDS->GetRasterBand(i + 1)->SetMetadataItem(
    2488          18 :                             "CENTRAL_WAVELENGTH_UM", pszVal, "IMAGERY");
    2489             :                     }
    2490             :                 }
    2491             :             }
    2492             : 
    2493         236 :             if (papszFWHM && nFWHMCount > i && pszWLUnits)
    2494             :             {
    2495          18 :                 if (const char *pszVal =
    2496          18 :                         ConvertWaveLength(CPLAtof(papszFWHM[i])))
    2497             :                 {
    2498          18 :                     poDS->GetRasterBand(i + 1)->SetMetadataItem(
    2499          18 :                         "FWHM_UM", pszVal, "IMAGERY");
    2500             :                 }
    2501             :             }
    2502             :         }
    2503         128 :         CSLDestroy(papszWL);
    2504         128 :         CSLDestroy(papszBandNames);
    2505         128 :         CSLDestroy(papszFWHM);
    2506             :     }
    2507             : 
    2508             :     // Apply "default bands" if we have it to set RGB color interpretation.
    2509         249 :     const char *pszDefaultBands = poDS->m_aosHeader["default_bands"];
    2510         249 :     if (pszDefaultBands != nullptr)
    2511             :     {
    2512          24 :         const CPLStringList aosDefaultBands(poDS->SplitList(pszDefaultBands));
    2513          12 :         if (aosDefaultBands.size() == 3)
    2514             :         {
    2515           7 :             const int nRBand = atoi(aosDefaultBands[0]);
    2516           7 :             const int nGBand = atoi(aosDefaultBands[1]);
    2517           7 :             const int nBBand = atoi(aosDefaultBands[2]);
    2518           7 :             if (nRBand >= 1 && nRBand <= poDS->nBands && nGBand >= 1 &&
    2519           7 :                 nGBand <= poDS->nBands && nBBand >= 1 &&
    2520           7 :                 nBBand <= poDS->nBands && nRBand != nGBand &&
    2521          14 :                 nRBand != nBBand && nGBand != nBBand)
    2522             :             {
    2523           7 :                 poDS->GetRasterBand(nRBand)->SetColorInterpretation(
    2524           7 :                     GCI_RedBand);
    2525           7 :                 poDS->GetRasterBand(nGBand)->SetColorInterpretation(
    2526           7 :                     GCI_GreenBand);
    2527           7 :                 poDS->GetRasterBand(nBBand)->SetColorInterpretation(
    2528           7 :                     GCI_BlueBand);
    2529             :             }
    2530             :         }
    2531           5 :         else if (aosDefaultBands.size() == 1)
    2532             :         {
    2533           5 :             const int nGrayBand = atoi(aosDefaultBands[0]);
    2534           5 :             if (nGrayBand >= 1 && nGrayBand <= poDS->nBands)
    2535             :             {
    2536           5 :                 poDS->GetRasterBand(nGrayBand)->SetColorInterpretation(
    2537           5 :                     GCI_GrayIndex);
    2538             :             }
    2539             :         }
    2540             :     }
    2541             : 
    2542             :     // Apply data offset values
    2543         249 :     if (const char *pszDataOffsetValues =
    2544         249 :             poDS->m_aosHeader["data_offset_values"])
    2545             :     {
    2546           6 :         const CPLStringList aosValues(poDS->SplitList(pszDataOffsetValues));
    2547           3 :         if (aosValues.size() == poDS->nBands)
    2548             :         {
    2549          12 :             for (int i = 0; i < poDS->nBands; ++i)
    2550           9 :                 poDS->GetRasterBand(i + 1)->SetOffset(CPLAtof(aosValues[i]));
    2551             :         }
    2552             :     }
    2553             : 
    2554             :     // Apply data gain values
    2555         249 :     if (const char *pszDataGainValues = poDS->m_aosHeader["data_gain_values"])
    2556             :     {
    2557           6 :         const CPLStringList aosValues(poDS->SplitList(pszDataGainValues));
    2558           3 :         if (aosValues.size() == poDS->nBands)
    2559             :         {
    2560          12 :             for (int i = 0; i < poDS->nBands; ++i)
    2561             :             {
    2562           9 :                 poDS->GetRasterBand(i + 1)->SetScale(CPLAtof(aosValues[i]));
    2563             :             }
    2564             :         }
    2565             :     }
    2566             : 
    2567             :     // Apply class names if we have them.
    2568         249 :     const char *pszClassNames = poDS->m_aosHeader["class_names"];
    2569         249 :     if (pszClassNames != nullptr)
    2570             :     {
    2571           3 :         char **papszClassNames = poDS->SplitList(pszClassNames);
    2572             : 
    2573           3 :         poDS->GetRasterBand(1)->SetCategoryNames(papszClassNames);
    2574           3 :         CSLDestroy(papszClassNames);
    2575             :     }
    2576             : 
    2577             :     // Apply colormap if we have one.
    2578         249 :     const char *pszClassLookup = poDS->m_aosHeader["class_lookup"];
    2579         249 :     if (pszClassLookup != nullptr)
    2580             :     {
    2581           3 :         char **papszClassColors = poDS->SplitList(pszClassLookup);
    2582           3 :         const int nColorValueCount = CSLCount(papszClassColors);
    2583           6 :         GDALColorTable oCT;
    2584             : 
    2585           9 :         for (int i = 0; i * 3 + 2 < nColorValueCount; i++)
    2586             :         {
    2587             :             GDALColorEntry sEntry;
    2588             : 
    2589           6 :             sEntry.c1 = static_cast<short>(atoi(papszClassColors[i * 3 + 0]));
    2590           6 :             sEntry.c2 = static_cast<short>(atoi(papszClassColors[i * 3 + 1]));
    2591           6 :             sEntry.c3 = static_cast<short>(atoi(papszClassColors[i * 3 + 2]));
    2592           6 :             sEntry.c4 = 255;
    2593           6 :             oCT.SetColorEntry(i, &sEntry);
    2594             :         }
    2595             : 
    2596           3 :         CSLDestroy(papszClassColors);
    2597             : 
    2598           3 :         poDS->GetRasterBand(1)->SetColorTable(&oCT);
    2599           3 :         poDS->GetRasterBand(1)->SetColorInterpretation(GCI_PaletteIndex);
    2600             :     }
    2601             : 
    2602             :     // Set the nodata value if it is present.
    2603         249 :     const char *pszDataIgnoreValue = poDS->m_aosHeader["data_ignore_value"];
    2604         249 :     if (pszDataIgnoreValue != nullptr)
    2605             :     {
    2606           4 :         for (int i = 0; i < poDS->nBands; i++)
    2607           2 :             cpl::down_cast<RawRasterBand *>(poDS->GetRasterBand(i + 1))
    2608           2 :                 ->SetNoDataValue(CPLAtof(pszDataIgnoreValue));
    2609             :     }
    2610             : 
    2611             :     // Set all the header metadata into the ENVI domain.
    2612             :     {
    2613         249 :         char **pTmp = poDS->m_aosHeader.List();
    2614        2752 :         while (*pTmp != nullptr)
    2615             :         {
    2616        2503 :             char **pTokens = CSLTokenizeString2(
    2617             :                 *pTmp, "=", CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES);
    2618        2503 :             if (pTokens[0] != nullptr && pTokens[1] != nullptr &&
    2619        2503 :                 pTokens[2] == nullptr)
    2620             :             {
    2621        2494 :                 poDS->SetMetadataItem(pTokens[0], pTokens[1], "ENVI");
    2622             :             }
    2623        2503 :             CSLDestroy(pTokens);
    2624        2503 :             pTmp++;
    2625             :         }
    2626             :     }
    2627             : 
    2628             :     // Read the stats file if it is present.
    2629         249 :     poDS->ProcessStatsFile();
    2630             : 
    2631             :     // Look for mapinfo.
    2632         249 :     const char *pszMapInfo = poDS->m_aosHeader["map_info"];
    2633         249 :     if (pszMapInfo != nullptr)
    2634             :     {
    2635         104 :         poDS->bFoundMapinfo = CPL_TO_BOOL(poDS->ProcessMapinfo(pszMapInfo));
    2636             :     }
    2637             : 
    2638             :     // Look for RPC.
    2639         249 :     const char *pszRPCInfo = poDS->m_aosHeader["rpc_info"];
    2640         249 :     if (!poDS->bFoundMapinfo && pszRPCInfo != nullptr)
    2641             :     {
    2642           6 :         poDS->ProcessRPCinfo(pszRPCInfo, poDS->nRasterXSize,
    2643           3 :                              poDS->nRasterYSize);
    2644             :     }
    2645             : 
    2646             :     // Look for geo_points / GCP
    2647         249 :     const char *pszGeoPoints = poDS->m_aosHeader["geo_points"];
    2648         249 :     if (!poDS->bFoundMapinfo && pszGeoPoints != nullptr)
    2649             :     {
    2650           2 :         poDS->ProcessGeoPoints(pszGeoPoints);
    2651             :     }
    2652             : 
    2653             :     // Initialize any PAM information.
    2654         249 :     poDS->SetDescription(poOpenInfo->pszFilename);
    2655         249 :     poDS->TryLoadXML();
    2656             : 
    2657             :     // Check for overviews.
    2658         249 :     poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
    2659             : 
    2660             :     // SetMetadata() calls in Open() makes the header dirty.
    2661             :     // Don't re-write the header if nothing external has changed the metadata.
    2662         249 :     poDS->bHeaderDirty = false;
    2663             : 
    2664         249 :     return poDS.release();
    2665             : }
    2666             : 
    2667         188 : int ENVIDataset::GetEnviType(GDALDataType eType)
    2668             : {
    2669         188 :     int iENVIType = 1;
    2670         188 :     switch (eType)
    2671             :     {
    2672         115 :         case GDT_Byte:
    2673         115 :             iENVIType = 1;
    2674         115 :             break;
    2675           7 :         case GDT_Int16:
    2676           7 :             iENVIType = 2;
    2677           7 :             break;
    2678           6 :         case GDT_Int32:
    2679           6 :             iENVIType = 3;
    2680           6 :             break;
    2681           8 :         case GDT_Float32:
    2682           8 :             iENVIType = 4;
    2683           8 :             break;
    2684           6 :         case GDT_Float64:
    2685           6 :             iENVIType = 5;
    2686           6 :             break;
    2687           7 :         case GDT_CFloat32:
    2688           7 :             iENVIType = 6;
    2689           7 :             break;
    2690           6 :         case GDT_CFloat64:
    2691           6 :             iENVIType = 9;
    2692           6 :             break;
    2693          11 :         case GDT_UInt16:
    2694          11 :             iENVIType = 12;
    2695          11 :             break;
    2696           6 :         case GDT_UInt32:
    2697           6 :             iENVIType = 13;
    2698           6 :             break;
    2699           4 :         case GDT_Int64:
    2700           4 :             iENVIType = 14;
    2701           4 :             break;
    2702           4 :         case GDT_UInt64:
    2703           4 :             iENVIType = 15;
    2704           4 :             break;
    2705           8 :         default:
    2706           8 :             CPLError(CE_Failure, CPLE_AppDefined,
    2707             :                      "Attempt to create ENVI .hdr labelled dataset with an "
    2708             :                      "illegal data type (%s).",
    2709             :                      GDALGetDataTypeName(eType));
    2710           8 :             return 1;
    2711             :     }
    2712         180 :     return iENVIType;
    2713             : }
    2714             : 
    2715             : /************************************************************************/
    2716             : /*                               Create()                               */
    2717             : /************************************************************************/
    2718             : 
    2719         106 : GDALDataset *ENVIDataset::Create(const char *pszFilename, int nXSize,
    2720             :                                  int nYSize, int nBandsIn, GDALDataType eType,
    2721             :                                  char **papszOptions)
    2722             : 
    2723             : {
    2724             :     // Verify input options.
    2725         106 :     int iENVIType = GetEnviType(eType);
    2726         106 :     if (0 == iENVIType)
    2727           0 :         return nullptr;
    2728             : 
    2729             :     // Try to create the file.
    2730         106 :     VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
    2731             : 
    2732         106 :     if (fp == nullptr)
    2733             :     {
    2734           3 :         CPLError(CE_Failure, CPLE_OpenFailed,
    2735             :                  "Attempt to create file `%s' failed.", pszFilename);
    2736           3 :         return nullptr;
    2737             :     }
    2738             : 
    2739             :     // Just write out a couple of bytes to establish the binary
    2740             :     // file, and then close it.
    2741             :     {
    2742             :         const bool bRet =
    2743         103 :             VSIFWriteL(static_cast<void *>(const_cast<char *>("\0\0")), 2, 1,
    2744         103 :                        fp) == 1;
    2745         103 :         if (VSIFCloseL(fp) != 0 || !bRet)
    2746           1 :             return nullptr;
    2747             :     }
    2748             : 
    2749             :     // Create the .hdr filename.
    2750         102 :     const char *pszHDRFilename = nullptr;
    2751         102 :     const char *pszSuffix = CSLFetchNameValue(papszOptions, "SUFFIX");
    2752         102 :     if (pszSuffix && STARTS_WITH_CI(pszSuffix, "ADD"))
    2753           3 :         pszHDRFilename = CPLFormFilename(nullptr, pszFilename, "hdr");
    2754             :     else
    2755          99 :         pszHDRFilename = CPLResetExtension(pszFilename, "hdr");
    2756             : 
    2757             :     // Open the file.
    2758         102 :     fp = VSIFOpenL(pszHDRFilename, "wt");
    2759         102 :     if (fp == nullptr)
    2760             :     {
    2761           0 :         CPLError(CE_Failure, CPLE_OpenFailed,
    2762             :                  "Attempt to create file `%s' failed.", pszHDRFilename);
    2763           0 :         return nullptr;
    2764             :     }
    2765             : 
    2766             :     // Write out the header.
    2767             : #ifdef CPL_LSB
    2768         102 :     int iBigEndian = 0;
    2769             : #else
    2770             :     int iBigEndian = 1;
    2771             : #endif
    2772             : 
    2773             :     // Undocumented
    2774         102 :     const char *pszByteOrder = CSLFetchNameValue(papszOptions, "@BYTE_ORDER");
    2775         102 :     if (pszByteOrder && EQUAL(pszByteOrder, "LITTLE_ENDIAN"))
    2776           1 :         iBigEndian = 0;
    2777         101 :     else if (pszByteOrder && EQUAL(pszByteOrder, "BIG_ENDIAN"))
    2778           1 :         iBigEndian = 1;
    2779             : 
    2780         102 :     bool bRet = VSIFPrintfL(fp, "ENVI\n") > 0;
    2781         102 :     bRet &= VSIFPrintfL(fp, "samples = %d\nlines   = %d\nbands   = %d\n",
    2782         102 :                         nXSize, nYSize, nBandsIn) > 0;
    2783         102 :     bRet &=
    2784         102 :         VSIFPrintfL(fp, "header offset = 0\nfile type = ENVI Standard\n") > 0;
    2785         102 :     bRet &= VSIFPrintfL(fp, "data type = %d\n", iENVIType) > 0;
    2786         102 :     const char *pszInterleaving = CSLFetchNameValue(papszOptions, "INTERLEAVE");
    2787         102 :     if (pszInterleaving)
    2788             :     {
    2789          13 :         if (STARTS_WITH_CI(pszInterleaving, "bip"))
    2790           6 :             pszInterleaving = "bip";  // interleaved by pixel
    2791           7 :         else if (STARTS_WITH_CI(pszInterleaving, "bil"))
    2792           4 :             pszInterleaving = "bil";  // interleaved by line
    2793             :         else
    2794           3 :             pszInterleaving = "bsq";  // band sequential by default
    2795             :     }
    2796             :     else
    2797             :     {
    2798          89 :         pszInterleaving = "bsq";
    2799             :     }
    2800         102 :     bRet &= VSIFPrintfL(fp, "interleave = %s\n", pszInterleaving) > 0;
    2801         102 :     bRet &= VSIFPrintfL(fp, "byte order = %d\n", iBigEndian) > 0;
    2802             : 
    2803         102 :     if (VSIFCloseL(fp) != 0 || !bRet)
    2804           9 :         return nullptr;
    2805             : 
    2806          93 :     GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
    2807          93 :     ENVIDataset *poDS = Open(&oOpenInfo, false);
    2808          93 :     if (poDS)
    2809             :     {
    2810          92 :         poDS->SetFillFile();
    2811             :     }
    2812          93 :     return poDS;
    2813             : }
    2814             : 
    2815             : /************************************************************************/
    2816             : /*                           ENVIRasterBand()                           */
    2817             : /************************************************************************/
    2818             : 
    2819        1071 : ENVIRasterBand::ENVIRasterBand(GDALDataset *poDSIn, int nBandIn,
    2820             :                                VSILFILE *fpRawIn, vsi_l_offset nImgOffsetIn,
    2821             :                                int nPixelOffsetIn, int nLineOffsetIn,
    2822             :                                GDALDataType eDataTypeIn,
    2823        1071 :                                RawRasterBand::ByteOrder eByteOrderIn)
    2824             :     : RawRasterBand(poDSIn, nBandIn, fpRawIn, nImgOffsetIn, nPixelOffsetIn,
    2825             :                     nLineOffsetIn, eDataTypeIn, eByteOrderIn,
    2826        1071 :                     RawRasterBand::OwnFP::NO)
    2827             : {
    2828        1071 : }
    2829             : 
    2830             : /************************************************************************/
    2831             : /*                           SetDescription()                           */
    2832             : /************************************************************************/
    2833             : 
    2834         259 : void ENVIRasterBand::SetDescription(const char *pszDescription)
    2835             : {
    2836         259 :     cpl::down_cast<ENVIDataset *>(poDS)->bHeaderDirty = true;
    2837         259 :     RawRasterBand::SetDescription(pszDescription);
    2838         259 : }
    2839             : 
    2840             : /************************************************************************/
    2841             : /*                           SetCategoryNames()                         */
    2842             : /************************************************************************/
    2843             : 
    2844           4 : CPLErr ENVIRasterBand::SetCategoryNames(char **papszCategoryNamesIn)
    2845             : {
    2846           4 :     cpl::down_cast<ENVIDataset *>(poDS)->bHeaderDirty = true;
    2847           4 :     return RawRasterBand::SetCategoryNames(papszCategoryNamesIn);
    2848             : }
    2849             : 
    2850             : /************************************************************************/
    2851             : /*                            SetNoDataValue()                          */
    2852             : /************************************************************************/
    2853             : 
    2854          11 : CPLErr ENVIRasterBand::SetNoDataValue(double dfNoDataValue)
    2855             : {
    2856          11 :     cpl::down_cast<ENVIDataset *>(poDS)->bHeaderDirty = true;
    2857             : 
    2858          11 :     if (poDS->GetRasterCount() > 1)
    2859             :     {
    2860           8 :         int bOtherBandHasNoData = false;
    2861           8 :         const int nOtherBand = nBand > 1 ? 1 : 2;
    2862           8 :         double dfOtherBandNoData = poDS->GetRasterBand(nOtherBand)
    2863           8 :                                        ->GetNoDataValue(&bOtherBandHasNoData);
    2864          12 :         if (bOtherBandHasNoData &&
    2865           8 :             !(std::isnan(dfOtherBandNoData) && std::isnan(dfNoDataValue)) &&
    2866             :             dfOtherBandNoData != dfNoDataValue)
    2867             :         {
    2868           2 :             CPLError(CE_Warning, CPLE_AppDefined,
    2869             :                      "Nodata value of band %d (%.17g) is different from nodata "
    2870             :                      "value from band %d (%.17g). Only the later will be "
    2871             :                      "written in the ENVI header as the \"data ignore value\"",
    2872             :                      nBand, dfNoDataValue, nOtherBand, dfOtherBandNoData);
    2873             :         }
    2874             :     }
    2875             : 
    2876          11 :     return RawRasterBand::SetNoDataValue(dfNoDataValue);
    2877             : }
    2878             : 
    2879             : /************************************************************************/
    2880             : /*                         SetColorInterpretation()                     */
    2881             : /************************************************************************/
    2882             : 
    2883          49 : CPLErr ENVIRasterBand::SetColorInterpretation(GDALColorInterp eColorInterp)
    2884             : {
    2885          49 :     cpl::down_cast<ENVIDataset *>(poDS)->bHeaderDirty = true;
    2886          49 :     return RawRasterBand::SetColorInterpretation(eColorInterp);
    2887             : }
    2888             : 
    2889             : /************************************************************************/
    2890             : /*                             SetOffset()                              */
    2891             : /************************************************************************/
    2892             : 
    2893          10 : CPLErr ENVIRasterBand::SetOffset(double dfValue)
    2894             : {
    2895          10 :     cpl::down_cast<ENVIDataset *>(poDS)->bHeaderDirty = true;
    2896          10 :     return RawRasterBand::SetOffset(dfValue);
    2897             : }
    2898             : 
    2899             : /************************************************************************/
    2900             : /*                             SetScale()                               */
    2901             : /************************************************************************/
    2902             : 
    2903          10 : CPLErr ENVIRasterBand::SetScale(double dfValue)
    2904             : {
    2905          10 :     cpl::down_cast<ENVIDataset *>(poDS)->bHeaderDirty = true;
    2906          10 :     return RawRasterBand::SetScale(dfValue);
    2907             : }
    2908             : 
    2909             : /************************************************************************/
    2910             : /*                         GDALRegister_ENVI()                          */
    2911             : /************************************************************************/
    2912             : 
    2913        1595 : void GDALRegister_ENVI()
    2914             : {
    2915        1595 :     if (GDALGetDriverByName("ENVI") != nullptr)
    2916         302 :         return;
    2917             : 
    2918        1293 :     GDALDriver *poDriver = new GDALDriver();
    2919             : 
    2920        1293 :     poDriver->SetDescription("ENVI");
    2921        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    2922        1293 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "ENVI .hdr Labelled");
    2923        1293 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/envi.html");
    2924        1293 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "");
    2925        1293 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES,
    2926             :                               "Byte Int16 UInt16 Int32 UInt32 Int64 UInt64 "
    2927        1293 :                               "Float32 Float64 CFloat32 CFloat64");
    2928        1293 :     poDriver->SetMetadataItem(
    2929             :         GDAL_DMD_CREATIONOPTIONLIST,
    2930             :         "<CreationOptionList>"
    2931             :         "   <Option name='SUFFIX' type='string-select'>"
    2932             :         "       <Value>ADD</Value>"
    2933             :         "   </Option>"
    2934             :         "   <Option name='INTERLEAVE' type='string-select'>"
    2935             :         "       <Value>BIP</Value>"
    2936             :         "       <Value>BIL</Value>"
    2937             :         "       <Value>BSQ</Value>"
    2938             :         "   </Option>"
    2939        1293 :         "</CreationOptionList>");
    2940             : 
    2941        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    2942        1293 :     poDriver->pfnOpen = ENVIDataset::Open;
    2943        1293 :     poDriver->pfnCreate = ENVIDataset::Create;
    2944             : 
    2945        1293 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    2946             : }

Generated by: LCOV version 1.14