LCOV - code coverage report
Current view: top level - frmts/raw - envidataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1024 1295 79.1 %
Date: 2025-11-17 12:13:30 Functions: 45 47 95.7 %

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

Generated by: LCOV version 1.14