LCOV - code coverage report
Current view: top level - frmts/ers - ersdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 554 645 85.9 %
Date: 2024-05-08 14:54:11 Functions: 30 32 93.8 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  ERMapper .ers Driver
       4             :  * Purpose:  Implementation of .ers driver.
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2007, Frank Warmerdam <warmerdam@pobox.com>
       9             :  * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * Permission is hereby granted, free of charge, to any person obtaining a
      12             :  * copy of this software and associated documentation files (the "Software"),
      13             :  * to deal in the Software without restriction, including without limitation
      14             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      15             :  * and/or sell copies of the Software, and to permit persons to whom the
      16             :  * Software is furnished to do so, subject to the following conditions:
      17             :  *
      18             :  * The above copyright notice and this permission notice shall be included
      19             :  * in all copies or substantial portions of the Software.
      20             :  *
      21             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      22             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      23             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      24             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      25             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      26             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      27             :  * DEALINGS IN THE SOFTWARE.
      28             :  ****************************************************************************/
      29             : 
      30             : #include "cpl_string.h"
      31             : #include "ershdrnode.h"
      32             : #include "gdal_frmts.h"
      33             : #include "gdal_proxy.h"
      34             : #include "ogr_spatialref.h"
      35             : #include "rawdataset.h"
      36             : 
      37             : #include <limits>
      38             : 
      39             : /************************************************************************/
      40             : /* ==================================================================== */
      41             : /*                              ERSDataset                              */
      42             : /* ==================================================================== */
      43             : /************************************************************************/
      44             : 
      45             : class ERSRasterBand;
      46             : 
      47             : class ERSDataset final : public RawDataset
      48             : {
      49             :     friend class ERSRasterBand;
      50             : 
      51             :     VSILFILE *fpImage;  // Image data file.
      52             :     GDALDataset *poDepFile;
      53             : 
      54             :     int bGotTransform;
      55             :     double adfGeoTransform[6];
      56             :     OGRSpatialReference m_oSRS{};
      57             : 
      58             :     CPLString osRawFilename;
      59             : 
      60             :     int bHDRDirty;
      61             :     ERSHdrNode *poHeader;
      62             : 
      63             :     const char *Find(const char *, const char *);
      64             : 
      65             :     int nGCPCount;
      66             :     GDAL_GCP *pasGCPList;
      67             :     OGRSpatialReference m_oGCPSRS{};
      68             : 
      69             :     void ReadGCPs();
      70             : 
      71             :     int bHasNoDataValue;
      72             :     double dfNoDataValue;
      73             : 
      74             :     CPLString osProj, osProjForced;
      75             :     CPLString osDatum, osDatumForced;
      76             :     CPLString osUnits, osUnitsForced;
      77             :     void WriteProjectionInfo(const char *pszProj, const char *pszDatum,
      78             :                              const char *pszUnits);
      79             : 
      80             :     CPLStringList oERSMetadataList;
      81             : 
      82             :   protected:
      83             :     int CloseDependentDatasets() override;
      84             : 
      85             :     CPLErr Close() override;
      86             : 
      87             :   public:
      88             :     ERSDataset();
      89             :     ~ERSDataset() override;
      90             : 
      91             :     CPLErr FlushCache(bool bAtClosing) override;
      92             :     CPLErr GetGeoTransform(double *padfTransform) override;
      93             :     CPLErr SetGeoTransform(double *padfTransform) override;
      94             :     const OGRSpatialReference *GetSpatialRef() const override;
      95             :     CPLErr SetSpatialRef(const OGRSpatialReference *poSRS) override;
      96             :     char **GetFileList(void) override;
      97             : 
      98             :     int GetGCPCount() override;
      99             :     const OGRSpatialReference *GetGCPSpatialRef() const override;
     100             :     const GDAL_GCP *GetGCPs() override;
     101             :     CPLErr SetGCPs(int nGCPCountIn, const GDAL_GCP *pasGCPListIn,
     102             :                    const OGRSpatialReference *poSRS) override;
     103             : 
     104             :     char **GetMetadataDomainList() override;
     105             :     const char *GetMetadataItem(const char *pszName,
     106             :                                 const char *pszDomain = "") override;
     107             :     char **GetMetadata(const char *pszDomain = "") override;
     108             : 
     109             :     static GDALDataset *Open(GDALOpenInfo *);
     110             :     static int Identify(GDALOpenInfo *);
     111             :     static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
     112             :                                int nBandsIn, GDALDataType eType,
     113             :                                char **papszParamList);
     114             : };
     115             : 
     116             : /************************************************************************/
     117             : /*                            ERSDataset()                             */
     118             : /************************************************************************/
     119             : 
     120          62 : ERSDataset::ERSDataset()
     121             :     : fpImage(nullptr), poDepFile(nullptr), bGotTransform(FALSE),
     122             :       bHDRDirty(FALSE), poHeader(nullptr), nGCPCount(0), pasGCPList(nullptr),
     123          62 :       bHasNoDataValue(FALSE), dfNoDataValue(0.0)
     124             : {
     125          62 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     126          62 :     m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     127          62 :     adfGeoTransform[0] = 0.0;
     128          62 :     adfGeoTransform[1] = 1.0;
     129          62 :     adfGeoTransform[2] = 0.0;
     130          62 :     adfGeoTransform[3] = 0.0;
     131          62 :     adfGeoTransform[4] = 0.0;
     132          62 :     adfGeoTransform[5] = 1.0;
     133          62 : }
     134             : 
     135             : /************************************************************************/
     136             : /*                            ~ERSDataset()                            */
     137             : /************************************************************************/
     138             : 
     139         124 : ERSDataset::~ERSDataset()
     140             : 
     141             : {
     142          62 :     ERSDataset::Close();
     143         124 : }
     144             : 
     145             : /************************************************************************/
     146             : /*                              Close()                                 */
     147             : /************************************************************************/
     148             : 
     149         123 : CPLErr ERSDataset::Close()
     150             : {
     151         123 :     CPLErr eErr = CE_None;
     152         123 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
     153             :     {
     154          62 :         if (ERSDataset::FlushCache(true) != CE_None)
     155           0 :             eErr = CE_Failure;
     156             : 
     157          62 :         if (fpImage != nullptr)
     158             :         {
     159          60 :             VSIFCloseL(fpImage);
     160             :         }
     161             : 
     162          62 :         ERSDataset::CloseDependentDatasets();
     163             : 
     164          62 :         if (nGCPCount > 0)
     165             :         {
     166           3 :             GDALDeinitGCPs(nGCPCount, pasGCPList);
     167           3 :             CPLFree(pasGCPList);
     168             :         }
     169             : 
     170          62 :         if (poHeader != nullptr)
     171          62 :             delete poHeader;
     172             : 
     173          62 :         if (GDALPamDataset::Close() != CE_None)
     174           0 :             eErr = CE_Failure;
     175             :     }
     176         123 :     return eErr;
     177             : }
     178             : 
     179             : /************************************************************************/
     180             : /*                      CloseDependentDatasets()                        */
     181             : /************************************************************************/
     182             : 
     183          62 : int ERSDataset::CloseDependentDatasets()
     184             : {
     185          62 :     int bHasDroppedRef = GDALPamDataset::CloseDependentDatasets();
     186             : 
     187          62 :     if (poDepFile != nullptr)
     188             :     {
     189           1 :         bHasDroppedRef = TRUE;
     190             : 
     191           2 :         for (int iBand = 0; iBand < nBands; iBand++)
     192             :         {
     193           1 :             delete papoBands[iBand];
     194           1 :             papoBands[iBand] = nullptr;
     195             :         }
     196           1 :         nBands = 0;
     197             : 
     198           1 :         GDALClose((GDALDatasetH)poDepFile);
     199           1 :         poDepFile = nullptr;
     200             :     }
     201             : 
     202          62 :     return bHasDroppedRef;
     203             : }
     204             : 
     205             : /************************************************************************/
     206             : /*                             FlushCache()                             */
     207             : /************************************************************************/
     208             : 
     209          63 : CPLErr ERSDataset::FlushCache(bool bAtClosing)
     210             : 
     211             : {
     212          63 :     CPLErr eErr = CE_None;
     213          63 :     if (bHDRDirty)
     214             :     {
     215          37 :         VSILFILE *fpERS = VSIFOpenL(GetDescription(), "w");
     216          37 :         if (fpERS == nullptr)
     217             :         {
     218           0 :             eErr = CE_Failure;
     219           0 :             CPLError(CE_Failure, CPLE_OpenFailed,
     220           0 :                      "Unable to rewrite %s header.", GetDescription());
     221             :         }
     222             :         else
     223             :         {
     224          37 :             if (VSIFPrintfL(fpERS, "DatasetHeader Begin\n") <= 0)
     225           0 :                 eErr = CE_Failure;
     226          37 :             poHeader->WriteSelf(fpERS, 1);
     227          37 :             if (VSIFPrintfL(fpERS, "DatasetHeader End\n") <= 0)
     228           0 :                 eErr = CE_Failure;
     229          37 :             if (VSIFCloseL(fpERS) != 0)
     230           0 :                 eErr = CE_Failure;
     231             :         }
     232             :     }
     233             : 
     234          63 :     if (RawDataset::FlushCache(bAtClosing) != CE_None)
     235           0 :         eErr = CE_Failure;
     236          63 :     return eErr;
     237             : }
     238             : 
     239             : /************************************************************************/
     240             : /*                      GetMetadataDomainList()                         */
     241             : /************************************************************************/
     242             : 
     243           0 : char **ERSDataset::GetMetadataDomainList()
     244             : {
     245           0 :     return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
     246           0 :                                    TRUE, "ERS", nullptr);
     247             : }
     248             : 
     249             : /************************************************************************/
     250             : /*                           GetMetadataItem()                          */
     251             : /************************************************************************/
     252             : 
     253          60 : const char *ERSDataset::GetMetadataItem(const char *pszName,
     254             :                                         const char *pszDomain)
     255             : {
     256          60 :     if (pszDomain != nullptr && EQUAL(pszDomain, "ERS") && pszName != nullptr)
     257             :     {
     258          15 :         if (EQUAL(pszName, "PROJ"))
     259           5 :             return osProj.size() ? osProj.c_str() : nullptr;
     260          10 :         if (EQUAL(pszName, "DATUM"))
     261           5 :             return osDatum.size() ? osDatum.c_str() : nullptr;
     262           5 :         if (EQUAL(pszName, "UNITS"))
     263           5 :             return osUnits.size() ? osUnits.c_str() : nullptr;
     264             :     }
     265          45 :     return GDALPamDataset::GetMetadataItem(pszName, pszDomain);
     266             : }
     267             : 
     268             : /************************************************************************/
     269             : /*                            GetMetadata()                             */
     270             : /************************************************************************/
     271             : 
     272          17 : char **ERSDataset::GetMetadata(const char *pszDomain)
     273             : 
     274             : {
     275          17 :     if (pszDomain != nullptr && EQUAL(pszDomain, "ERS"))
     276             :     {
     277           1 :         oERSMetadataList.Clear();
     278           1 :         if (!osProj.empty())
     279             :             oERSMetadataList.AddString(
     280           1 :                 CPLSPrintf("%s=%s", "PROJ", osProj.c_str()));
     281           1 :         if (!osDatum.empty())
     282             :             oERSMetadataList.AddString(
     283           1 :                 CPLSPrintf("%s=%s", "DATUM", osDatum.c_str()));
     284           1 :         if (!osUnits.empty())
     285             :             oERSMetadataList.AddString(
     286           1 :                 CPLSPrintf("%s=%s", "UNITS", osUnits.c_str()));
     287           1 :         return oERSMetadataList.List();
     288             :     }
     289             : 
     290          16 :     return GDALPamDataset::GetMetadata(pszDomain);
     291             : }
     292             : 
     293             : /************************************************************************/
     294             : /*                            GetGCPCount()                             */
     295             : /************************************************************************/
     296             : 
     297           3 : int ERSDataset::GetGCPCount()
     298             : 
     299             : {
     300           3 :     return nGCPCount;
     301             : }
     302             : 
     303             : /************************************************************************/
     304             : /*                          GetGCPSpatialRef()                          */
     305             : /************************************************************************/
     306             : 
     307           1 : const OGRSpatialReference *ERSDataset::GetGCPSpatialRef() const
     308             : 
     309             : {
     310           1 :     return m_oGCPSRS.IsEmpty() ? nullptr : &m_oGCPSRS;
     311             : }
     312             : 
     313             : /************************************************************************/
     314             : /*                               GetGCPs()                              */
     315             : /************************************************************************/
     316             : 
     317           1 : const GDAL_GCP *ERSDataset::GetGCPs()
     318             : 
     319             : {
     320           1 :     return pasGCPList;
     321             : }
     322             : 
     323             : /************************************************************************/
     324             : /*                              SetGCPs()                               */
     325             : /************************************************************************/
     326             : 
     327           1 : CPLErr ERSDataset::SetGCPs(int nGCPCountIn, const GDAL_GCP *pasGCPListIn,
     328             :                            const OGRSpatialReference *poSRS)
     329             : 
     330             : {
     331             :     /* -------------------------------------------------------------------- */
     332             :     /*      Clean old gcps.                                                 */
     333             :     /* -------------------------------------------------------------------- */
     334           1 :     m_oGCPSRS.Clear();
     335             : 
     336           1 :     if (nGCPCount > 0)
     337             :     {
     338           0 :         GDALDeinitGCPs(nGCPCount, pasGCPList);
     339           0 :         CPLFree(pasGCPList);
     340             : 
     341           0 :         pasGCPList = nullptr;
     342           0 :         nGCPCount = 0;
     343             :     }
     344             : 
     345             :     /* -------------------------------------------------------------------- */
     346             :     /*      Copy new ones.                                                  */
     347             :     /* -------------------------------------------------------------------- */
     348           1 :     nGCPCount = nGCPCountIn;
     349           1 :     pasGCPList = GDALDuplicateGCPs(nGCPCount, pasGCPListIn);
     350           1 :     if (poSRS)
     351           1 :         m_oGCPSRS = *poSRS;
     352             : 
     353             :     /* -------------------------------------------------------------------- */
     354             :     /*      Setup the header contents corresponding to these GCPs.          */
     355             :     /* -------------------------------------------------------------------- */
     356           1 :     bHDRDirty = TRUE;
     357             : 
     358           1 :     poHeader->Set("RasterInfo.WarpControl.WarpType", "Polynomial");
     359           1 :     if (nGCPCount > 6)
     360           0 :         poHeader->Set("RasterInfo.WarpControl.WarpOrder", "2");
     361             :     else
     362           1 :         poHeader->Set("RasterInfo.WarpControl.WarpOrder", "1");
     363           1 :     poHeader->Set("RasterInfo.WarpControl.WarpSampling", "Nearest");
     364             : 
     365             :     /* -------------------------------------------------------------------- */
     366             :     /*      Translate the projection.                                       */
     367             :     /* -------------------------------------------------------------------- */
     368             :     char szERSProj[32], szERSDatum[32], szERSUnits[32];
     369             : 
     370           1 :     m_oGCPSRS.exportToERM(szERSProj, szERSDatum, szERSUnits);
     371             : 
     372             :     /* Write the above computed values, unless they have been overridden by */
     373             :     /* the creation options PROJ, DATUM or UNITS */
     374             : 
     375           1 :     poHeader->Set("RasterInfo.WarpControl.CoordinateSpace.Datum",
     376           2 :                   CPLString().Printf("\"%s\"", (osDatum.size())
     377           0 :                                                    ? osDatum.c_str()
     378           1 :                                                    : szERSDatum));
     379           1 :     poHeader->Set("RasterInfo.WarpControl.CoordinateSpace.Projection",
     380           2 :                   CPLString().Printf("\"%s\"", (osProj.size()) ? osProj.c_str()
     381           1 :                                                                : szERSProj));
     382           1 :     poHeader->Set("RasterInfo.WarpControl.CoordinateSpace.CoordinateType",
     383           2 :                   CPLString().Printf("EN"));
     384           1 :     poHeader->Set("RasterInfo.WarpControl.CoordinateSpace.Units",
     385           2 :                   CPLString().Printf("\"%s\"", (osUnits.size())
     386           0 :                                                    ? osUnits.c_str()
     387           1 :                                                    : szERSUnits));
     388           1 :     poHeader->Set("RasterInfo.WarpControl.CoordinateSpace.Rotation", "0:0:0.0");
     389             : 
     390             :     /* -------------------------------------------------------------------- */
     391             :     /*      Translate the GCPs.                                             */
     392             :     /* -------------------------------------------------------------------- */
     393           1 :     CPLString osControlPoints = "{\n";
     394             : 
     395           5 :     for (int iGCP = 0; iGCP < nGCPCount; iGCP++)
     396             :     {
     397           8 :         CPLString osLine;
     398             : 
     399           8 :         CPLString osId = pasGCPList[iGCP].pszId;
     400           4 :         if (osId.empty())
     401           4 :             osId.Printf("%d", iGCP + 1);
     402             : 
     403             :         osLine.Printf(
     404             :             "\t\t\t\t\"%s\"\tYes\tYes\t%.6f\t%.6f\t%.15g\t%.15g\t%.15g\n",
     405           4 :             osId.c_str(), pasGCPList[iGCP].dfGCPPixel,
     406           4 :             pasGCPList[iGCP].dfGCPLine, pasGCPList[iGCP].dfGCPX,
     407           4 :             pasGCPList[iGCP].dfGCPY, pasGCPList[iGCP].dfGCPZ);
     408           4 :         osControlPoints += osLine;
     409             :     }
     410           1 :     osControlPoints += "\t\t}";
     411             : 
     412           1 :     poHeader->Set("RasterInfo.WarpControl.ControlPoints", osControlPoints);
     413             : 
     414           2 :     return CE_None;
     415             : }
     416             : 
     417             : /************************************************************************/
     418             : /*                          GetSpatialRef()                             */
     419             : /************************************************************************/
     420             : 
     421           3 : const OGRSpatialReference *ERSDataset::GetSpatialRef() const
     422             : 
     423             : {
     424             :     // try xml first
     425           3 :     const auto poSRS = GDALPamDataset::GetSpatialRef();
     426           3 :     if (poSRS)
     427           0 :         return poSRS;
     428             : 
     429           3 :     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
     430             : }
     431             : 
     432             : /************************************************************************/
     433             : /*                           SetSpatialRef()                            */
     434             : /************************************************************************/
     435             : 
     436          34 : CPLErr ERSDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
     437             : 
     438             : {
     439          34 :     if (poSRS == nullptr && m_oSRS.IsEmpty())
     440           0 :         return CE_None;
     441          34 :     if (poSRS != nullptr && poSRS->IsSame(&m_oSRS))
     442           0 :         return CE_None;
     443             : 
     444          34 :     m_oSRS.Clear();
     445          34 :     if (poSRS)
     446          34 :         m_oSRS = *poSRS;
     447             : 
     448             :     char szERSProj[32], szERSDatum[32], szERSUnits[32];
     449             : 
     450          34 :     m_oSRS.exportToERM(szERSProj, szERSDatum, szERSUnits);
     451             : 
     452             :     /* Write the above computed values, unless they have been overridden by */
     453             :     /* the creation options PROJ, DATUM or UNITS */
     454          34 :     if (!osProjForced.empty())
     455           1 :         osProj = osProjForced;
     456             :     else
     457          33 :         osProj = szERSProj;
     458          34 :     if (!osDatumForced.empty())
     459           1 :         osDatum = osDatumForced;
     460             :     else
     461          33 :         osDatum = szERSDatum;
     462          34 :     if (!osUnitsForced.empty())
     463           1 :         osUnits = osUnitsForced;
     464             :     else
     465          33 :         osUnits = szERSUnits;
     466             : 
     467          34 :     WriteProjectionInfo(osProj, osDatum, osUnits);
     468             : 
     469          34 :     return CE_None;
     470             : }
     471             : 
     472             : /************************************************************************/
     473             : /*                         WriteProjectionInfo()                        */
     474             : /************************************************************************/
     475             : 
     476          36 : void ERSDataset::WriteProjectionInfo(const char *pszProj, const char *pszDatum,
     477             :                                      const char *pszUnits)
     478             : {
     479          36 :     bHDRDirty = TRUE;
     480          36 :     poHeader->Set("CoordinateSpace.Datum",
     481          72 :                   CPLString().Printf("\"%s\"", pszDatum));
     482          36 :     poHeader->Set("CoordinateSpace.Projection",
     483          72 :                   CPLString().Printf("\"%s\"", pszProj));
     484          36 :     poHeader->Set("CoordinateSpace.CoordinateType", CPLString().Printf("EN"));
     485          36 :     poHeader->Set("CoordinateSpace.Units",
     486          72 :                   CPLString().Printf("\"%s\"", pszUnits));
     487          36 :     poHeader->Set("CoordinateSpace.Rotation", "0:0:0.0");
     488             : 
     489             :     /* -------------------------------------------------------------------- */
     490             :     /*      It seems that CoordinateSpace needs to come before              */
     491             :     /*      RasterInfo.  Try moving it up manually.                         */
     492             :     /* -------------------------------------------------------------------- */
     493          36 :     int iRasterInfo = -1;
     494          36 :     int iCoordSpace = -1;
     495             : 
     496         250 :     for (int i = 0; i < poHeader->nItemCount; i++)
     497             :     {
     498         250 :         if (EQUAL(poHeader->papszItemName[i], "RasterInfo"))
     499          34 :             iRasterInfo = i;
     500             : 
     501         250 :         if (EQUAL(poHeader->papszItemName[i], "CoordinateSpace"))
     502             :         {
     503          36 :             iCoordSpace = i;
     504          36 :             break;
     505             :         }
     506             :     }
     507             : 
     508          36 :     if (iCoordSpace > iRasterInfo && iRasterInfo != -1)
     509             :     {
     510          68 :         for (int i = iCoordSpace; i > 0 && i != iRasterInfo; i--)
     511             :         {
     512             : 
     513          34 :             ERSHdrNode *poTemp = poHeader->papoItemChild[i];
     514          34 :             poHeader->papoItemChild[i] = poHeader->papoItemChild[i - 1];
     515          34 :             poHeader->papoItemChild[i - 1] = poTemp;
     516             : 
     517          34 :             char *pszTemp = poHeader->papszItemName[i];
     518          34 :             poHeader->papszItemName[i] = poHeader->papszItemName[i - 1];
     519          34 :             poHeader->papszItemName[i - 1] = pszTemp;
     520             : 
     521          34 :             pszTemp = poHeader->papszItemValue[i];
     522          34 :             poHeader->papszItemValue[i] = poHeader->papszItemValue[i - 1];
     523          34 :             poHeader->papszItemValue[i - 1] = pszTemp;
     524             :         }
     525             :     }
     526          36 : }
     527             : 
     528             : /************************************************************************/
     529             : /*                          GetGeoTransform()                           */
     530             : /************************************************************************/
     531             : 
     532          22 : CPLErr ERSDataset::GetGeoTransform(double *padfTransform)
     533             : 
     534             : {
     535          22 :     if (bGotTransform)
     536             :     {
     537          22 :         memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
     538          22 :         return CE_None;
     539             :     }
     540             : 
     541           0 :     return GDALPamDataset::GetGeoTransform(padfTransform);
     542             : }
     543             : 
     544             : /************************************************************************/
     545             : /*                          SetGeoTransform()                           */
     546             : /************************************************************************/
     547             : 
     548          32 : CPLErr ERSDataset::SetGeoTransform(double *padfTransform)
     549             : 
     550             : {
     551          32 :     if (memcmp(padfTransform, adfGeoTransform, sizeof(double) * 6) == 0)
     552           0 :         return CE_None;
     553             : 
     554          32 :     if (adfGeoTransform[2] != 0 || adfGeoTransform[4] != 0)
     555             :     {
     556           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     557             :                  "Rotated and skewed geotransforms not currently supported for "
     558             :                  "ERS driver.");
     559           0 :         return CE_Failure;
     560             :     }
     561             : 
     562          32 :     bGotTransform = TRUE;
     563          32 :     memcpy(adfGeoTransform, padfTransform, sizeof(double) * 6);
     564             : 
     565          32 :     bHDRDirty = TRUE;
     566             : 
     567          32 :     poHeader->Set("RasterInfo.CellInfo.Xdimension",
     568          64 :                   CPLString().Printf("%.15g", fabs(adfGeoTransform[1])));
     569          32 :     poHeader->Set("RasterInfo.CellInfo.Ydimension",
     570          64 :                   CPLString().Printf("%.15g", fabs(adfGeoTransform[5])));
     571          32 :     poHeader->Set("RasterInfo.RegistrationCoord.Eastings",
     572          64 :                   CPLString().Printf("%.15g", adfGeoTransform[0]));
     573          32 :     poHeader->Set("RasterInfo.RegistrationCoord.Northings",
     574          64 :                   CPLString().Printf("%.15g", adfGeoTransform[3]));
     575             : 
     576          64 :     if (CPLAtof(poHeader->Find("RasterInfo.RegistrationCellX", "0")) != 0.0 ||
     577          32 :         CPLAtof(poHeader->Find("RasterInfo.RegistrationCellY", "0")) != 0.0)
     578             :     {
     579             :         // Reset RegistrationCellX/Y to 0 if the header gets rewritten (#5493)
     580           0 :         poHeader->Set("RasterInfo.RegistrationCellX", "0");
     581           0 :         poHeader->Set("RasterInfo.RegistrationCellY", "0");
     582             :     }
     583             : 
     584          32 :     return CE_None;
     585             : }
     586             : 
     587             : /************************************************************************/
     588             : /*                             ERSDMS2Dec()                             */
     589             : /*                                                                      */
     590             : /*      Convert ERS DMS format to decimal degrees.   Input is like      */
     591             : /*      "-180:00:00".                                                   */
     592             : /************************************************************************/
     593             : 
     594          10 : static double ERSDMS2Dec(const char *pszDMS)
     595             : 
     596             : {
     597          10 :     char **papszTokens = CSLTokenizeStringComplex(pszDMS, ":", FALSE, FALSE);
     598             : 
     599          10 :     if (CSLCount(papszTokens) != 3)
     600             :     {
     601           0 :         CSLDestroy(papszTokens);
     602           0 :         return CPLAtof(pszDMS);
     603             :     }
     604             : 
     605          10 :     double dfResult = fabs(CPLAtof(papszTokens[0])) +
     606          10 :                       CPLAtof(papszTokens[1]) / 60.0 +
     607          10 :                       CPLAtof(papszTokens[2]) / 3600.0;
     608             : 
     609          10 :     if (CPLAtof(papszTokens[0]) < 0)
     610           8 :         dfResult *= -1;
     611             : 
     612          10 :     CSLDestroy(papszTokens);
     613          10 :     return dfResult;
     614             : }
     615             : 
     616             : /************************************************************************/
     617             : /*                            GetFileList()                             */
     618             : /************************************************************************/
     619             : 
     620          10 : char **ERSDataset::GetFileList()
     621             : 
     622             : {
     623             :     static thread_local int nRecLevel = 0;
     624          10 :     if (nRecLevel > 0)
     625           0 :         return nullptr;
     626             : 
     627             :     // Main data file, etc.
     628          10 :     char **papszFileList = GDALPamDataset::GetFileList();
     629             : 
     630             :     // Add raw data file if we have one.
     631          10 :     if (!osRawFilename.empty())
     632          10 :         papszFileList = CSLAddString(papszFileList, osRawFilename);
     633             : 
     634             :     // If we have a dependent file, merge its list of files in.
     635          10 :     if (poDepFile)
     636             :     {
     637           0 :         nRecLevel++;
     638           0 :         char **papszDepFiles = poDepFile->GetFileList();
     639           0 :         nRecLevel--;
     640           0 :         papszFileList = CSLInsertStrings(papszFileList, -1, papszDepFiles);
     641           0 :         CSLDestroy(papszDepFiles);
     642             :     }
     643             : 
     644          10 :     return papszFileList;
     645             : }
     646             : 
     647             : /************************************************************************/
     648             : /*                              ReadGCPs()                              */
     649             : /*                                                                      */
     650             : /*      Read the GCPs from the header.                                  */
     651             : /************************************************************************/
     652             : 
     653           2 : void ERSDataset::ReadGCPs()
     654             : 
     655             : {
     656             :     const char *pszCP =
     657           2 :         poHeader->Find("RasterInfo.WarpControl.ControlPoints", nullptr);
     658             : 
     659           2 :     if (pszCP == nullptr)
     660           0 :         return;
     661             : 
     662             :     /* -------------------------------------------------------------------- */
     663             :     /*      Parse the control points.  They will look something like:       */
     664             :     /*                                                                      */
     665             :     /*   "1035" Yes No 2344.650885 3546.419458 483270.73 3620906.21 3.105   */
     666             :     /* -------------------------------------------------------------------- */
     667           2 :     char **papszTokens = CSLTokenizeStringComplex(pszCP, "{ \t}", TRUE, FALSE);
     668           2 :     int nItemCount = CSLCount(papszTokens);
     669             : 
     670             :     /* -------------------------------------------------------------------- */
     671             :     /*      Work out if we have elevation values or not.                    */
     672             :     /* -------------------------------------------------------------------- */
     673             :     int nItemsPerLine;
     674             : 
     675           2 :     if (nItemCount == 7)
     676           0 :         nItemsPerLine = 7;
     677           2 :     else if (nItemCount == 8)
     678           0 :         nItemsPerLine = 8;
     679           2 :     else if (nItemCount < 14)
     680             :     {
     681           0 :         CPLDebug("ERS", "Invalid item count for ControlPoints");
     682           0 :         CSLDestroy(papszTokens);
     683           0 :         return;
     684             :     }
     685           2 :     else if (EQUAL(papszTokens[8], "Yes") || EQUAL(papszTokens[8], "No"))
     686           0 :         nItemsPerLine = 7;
     687           2 :     else if (EQUAL(papszTokens[9], "Yes") || EQUAL(papszTokens[9], "No"))
     688           2 :         nItemsPerLine = 8;
     689             :     else
     690             :     {
     691           0 :         CPLDebug("ERS", "Invalid format for ControlPoints");
     692           0 :         CSLDestroy(papszTokens);
     693           0 :         return;
     694             :     }
     695             : 
     696             :     /* -------------------------------------------------------------------- */
     697             :     /*      Setup GCPs.                                                     */
     698             :     /* -------------------------------------------------------------------- */
     699           2 :     CPLAssert(nGCPCount == 0);
     700             : 
     701           2 :     nGCPCount = nItemCount / nItemsPerLine;
     702           2 :     pasGCPList = (GDAL_GCP *)CPLCalloc(nGCPCount, sizeof(GDAL_GCP));
     703           2 :     GDALInitGCPs(nGCPCount, pasGCPList);
     704             : 
     705          10 :     for (int iGCP = 0; iGCP < nGCPCount; iGCP++)
     706             :     {
     707           8 :         GDAL_GCP *psGCP = pasGCPList + iGCP;
     708             : 
     709           8 :         CPLFree(psGCP->pszId);
     710           8 :         psGCP->pszId = CPLStrdup(papszTokens[iGCP * nItemsPerLine + 0]);
     711           8 :         psGCP->dfGCPPixel = CPLAtof(papszTokens[iGCP * nItemsPerLine + 3]);
     712           8 :         psGCP->dfGCPLine = CPLAtof(papszTokens[iGCP * nItemsPerLine + 4]);
     713           8 :         psGCP->dfGCPX = CPLAtof(papszTokens[iGCP * nItemsPerLine + 5]);
     714           8 :         psGCP->dfGCPY = CPLAtof(papszTokens[iGCP * nItemsPerLine + 6]);
     715           8 :         if (nItemsPerLine == 8)
     716           8 :             psGCP->dfGCPZ = CPLAtof(papszTokens[iGCP * nItemsPerLine + 7]);
     717             :     }
     718             : 
     719           2 :     CSLDestroy(papszTokens);
     720             : 
     721             :     /* -------------------------------------------------------------------- */
     722             :     /*      Parse the GCP projection.                                       */
     723             :     /* -------------------------------------------------------------------- */
     724             :     osProj =
     725           2 :         poHeader->Find("RasterInfo.WarpControl.CoordinateSpace.Projection", "");
     726             :     osDatum =
     727           2 :         poHeader->Find("RasterInfo.WarpControl.CoordinateSpace.Datum", "");
     728             :     osUnits =
     729           2 :         poHeader->Find("RasterInfo.WarpControl.CoordinateSpace.Units", "");
     730             : 
     731           2 :     m_oGCPSRS.importFromERM(!osProj.empty() ? osProj.c_str() : "RAW",
     732           2 :                             !osDatum.empty() ? osDatum.c_str() : "WGS84",
     733           2 :                             !osUnits.empty() ? osUnits.c_str() : "METERS");
     734             : }
     735             : 
     736             : /************************************************************************/
     737             : /* ==================================================================== */
     738             : /*                             ERSRasterBand                            */
     739             : /* ==================================================================== */
     740             : /************************************************************************/
     741             : 
     742             : class ERSRasterBand final : public RawRasterBand
     743             : {
     744             :   public:
     745             :     ERSRasterBand(GDALDataset *poDS, int nBand, VSILFILE *fpRaw,
     746             :                   vsi_l_offset nImgOffset, int nPixelOffset, int nLineOffset,
     747             :                   GDALDataType eDataType, int bNativeOrder);
     748             : 
     749             :     double GetNoDataValue(int *pbSuccess = nullptr) override;
     750             :     CPLErr SetNoDataValue(double) override;
     751             : };
     752             : 
     753             : /************************************************************************/
     754             : /*                           ERSRasterBand()                            */
     755             : /************************************************************************/
     756             : 
     757         102 : ERSRasterBand::ERSRasterBand(GDALDataset *poDSIn, int nBandIn,
     758             :                              VSILFILE *fpRawIn, vsi_l_offset nImgOffsetIn,
     759             :                              int nPixelOffsetIn, int nLineOffsetIn,
     760         102 :                              GDALDataType eDataTypeIn, int bNativeOrderIn)
     761             :     : RawRasterBand(poDSIn, nBandIn, fpRawIn, nImgOffsetIn, nPixelOffsetIn,
     762             :                     nLineOffsetIn, eDataTypeIn, bNativeOrderIn,
     763         102 :                     RawRasterBand::OwnFP::NO)
     764             : {
     765         102 : }
     766             : 
     767             : /************************************************************************/
     768             : /*                           GetNoDataValue()                           */
     769             : /************************************************************************/
     770             : 
     771          15 : double ERSRasterBand::GetNoDataValue(int *pbSuccess)
     772             : {
     773          15 :     ERSDataset *poGDS = cpl::down_cast<ERSDataset *>(poDS);
     774          15 :     if (poGDS->bHasNoDataValue)
     775             :     {
     776           1 :         if (pbSuccess)
     777           1 :             *pbSuccess = TRUE;
     778           1 :         return poGDS->dfNoDataValue;
     779             :     }
     780             : 
     781          14 :     return RawRasterBand::GetNoDataValue(pbSuccess);
     782             : }
     783             : 
     784             : /************************************************************************/
     785             : /*                           SetNoDataValue()                           */
     786             : /************************************************************************/
     787             : 
     788           1 : CPLErr ERSRasterBand::SetNoDataValue(double dfNoDataValue)
     789             : {
     790           1 :     ERSDataset *poGDS = cpl::down_cast<ERSDataset *>(poDS);
     791           1 :     if (!poGDS->bHasNoDataValue || poGDS->dfNoDataValue != dfNoDataValue)
     792             :     {
     793           1 :         poGDS->bHasNoDataValue = TRUE;
     794           1 :         poGDS->dfNoDataValue = dfNoDataValue;
     795             : 
     796           1 :         poGDS->bHDRDirty = TRUE;
     797           1 :         poGDS->poHeader->Set("RasterInfo.NullCellValue",
     798           2 :                              CPLString().Printf("%.16g", dfNoDataValue));
     799             :     }
     800           1 :     return CE_None;
     801             : }
     802             : 
     803             : /************************************************************************/
     804             : /*                              Identify()                              */
     805             : /************************************************************************/
     806             : 
     807       51178 : int ERSDataset::Identify(GDALOpenInfo *poOpenInfo)
     808             : 
     809             : {
     810             :     /* -------------------------------------------------------------------- */
     811             :     /*      We assume the user selects the .ers file.                       */
     812             :     /* -------------------------------------------------------------------- */
     813       51178 :     CPLString osHeader((const char *)poOpenInfo->pabyHeader,
     814      102353 :                        poOpenInfo->nHeaderBytes);
     815             : 
     816       51176 :     if (osHeader.ifind("Algorithm Begin") != std::string::npos)
     817             :     {
     818           0 :         CPLError(CE_Failure, CPLE_OpenFailed,
     819             :                  "%s appears to be an algorithm ERS file, which is not "
     820             :                  "currently supported.",
     821             :                  poOpenInfo->pszFilename);
     822           0 :         return FALSE;
     823             :     }
     824             : 
     825       51175 :     if (osHeader.ifind("DatasetHeader ") != std::string::npos)
     826          89 :         return TRUE;
     827             : 
     828       51086 :     return FALSE;
     829             : }
     830             : 
     831             : /************************************************************************/
     832             : /*                         ERSProxyRasterBand                           */
     833             : /************************************************************************/
     834             : 
     835             : namespace
     836             : {
     837             : 
     838          63 : static int &GetRecLevel()
     839             : {
     840             :     static thread_local int nRecLevel = 0;
     841          63 :     return nRecLevel;
     842             : }
     843             : 
     844             : class ERSProxyRasterBand final : public GDALProxyRasterBand
     845             : {
     846             :   public:
     847           1 :     explicit ERSProxyRasterBand(GDALRasterBand *poUnderlyingBand)
     848           1 :         : m_poUnderlyingBand(poUnderlyingBand)
     849             :     {
     850           1 :         poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
     851           1 :         eDataType = poUnderlyingBand->GetRasterDataType();
     852           1 :     }
     853             : 
     854             :     int GetOverviewCount() override;
     855             : 
     856             :   protected:
     857           1 :     GDALRasterBand *RefUnderlyingRasterBand(bool /*bForceOpen*/) const override
     858             :     {
     859           1 :         return m_poUnderlyingBand;
     860             :     }
     861             : 
     862             :   private:
     863             :     GDALRasterBand *m_poUnderlyingBand;
     864             : };
     865             : 
     866           0 : int ERSProxyRasterBand::GetOverviewCount()
     867             : {
     868           0 :     int &nRecLevel = GetRecLevel();
     869           0 :     nRecLevel++;
     870           0 :     int nRet = GDALProxyRasterBand::GetOverviewCount();
     871           0 :     nRecLevel--;
     872           0 :     return nRet;
     873             : }
     874             : 
     875             : }  // namespace
     876             : 
     877             : /************************************************************************/
     878             : /*                                Open()                                */
     879             : /************************************************************************/
     880             : 
     881          63 : GDALDataset *ERSDataset::Open(GDALOpenInfo *poOpenInfo)
     882             : 
     883             : {
     884          63 :     if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
     885           0 :         return nullptr;
     886             : 
     887          63 :     int &nRecLevel = GetRecLevel();
     888             :     // cppcheck-suppress knownConditionTrueFalse
     889          63 :     if (nRecLevel)
     890             :     {
     891           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     892             :                  "Attempt at recursively opening ERS dataset");
     893           1 :         return nullptr;
     894             :     }
     895             : 
     896             :     /* -------------------------------------------------------------------- */
     897             :     /*      Ingest the file as a tree of header nodes.                      */
     898             :     /* -------------------------------------------------------------------- */
     899          62 :     ERSHdrNode *poHeader = new ERSHdrNode();
     900             : 
     901          62 :     if (!poHeader->ParseHeader(poOpenInfo->fpL))
     902             :     {
     903           0 :         delete poHeader;
     904           0 :         VSIFCloseL(poOpenInfo->fpL);
     905           0 :         poOpenInfo->fpL = nullptr;
     906           0 :         return nullptr;
     907             :     }
     908             : 
     909          62 :     VSIFCloseL(poOpenInfo->fpL);
     910          62 :     poOpenInfo->fpL = nullptr;
     911             : 
     912             :     /* -------------------------------------------------------------------- */
     913             :     /*      Do we have the minimum required information from this header?   */
     914             :     /* -------------------------------------------------------------------- */
     915          62 :     if (poHeader->Find("RasterInfo.NrOfLines") == nullptr ||
     916         124 :         poHeader->Find("RasterInfo.NrOfCellsPerLine") == nullptr ||
     917          62 :         poHeader->Find("RasterInfo.NrOfBands") == nullptr)
     918             :     {
     919           0 :         if (poHeader->FindNode("Algorithm") != nullptr)
     920             :         {
     921           0 :             CPLError(CE_Failure, CPLE_OpenFailed,
     922             :                      "%s appears to be an algorithm ERS file, which is not "
     923             :                      "currently supported.",
     924             :                      poOpenInfo->pszFilename);
     925             :         }
     926           0 :         delete poHeader;
     927           0 :         return nullptr;
     928             :     }
     929             : 
     930             :     /* -------------------------------------------------------------------- */
     931             :     /*      Create a corresponding GDALDataset.                             */
     932             :     /* -------------------------------------------------------------------- */
     933         124 :     auto poDS = std::make_unique<ERSDataset>();
     934          62 :     poDS->poHeader = poHeader;
     935          62 :     poDS->eAccess = poOpenInfo->eAccess;
     936             : 
     937             :     /* -------------------------------------------------------------------- */
     938             :     /*      Capture some information from the file that is of interest.     */
     939             :     /* -------------------------------------------------------------------- */
     940          62 :     int nBands = atoi(poHeader->Find("RasterInfo.NrOfBands"));
     941          62 :     poDS->nRasterXSize = atoi(poHeader->Find("RasterInfo.NrOfCellsPerLine"));
     942          62 :     poDS->nRasterYSize = atoi(poHeader->Find("RasterInfo.NrOfLines"));
     943             : 
     944         124 :     if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize) ||
     945          62 :         !GDALCheckBandCount(nBands, FALSE))
     946             :     {
     947           0 :         return nullptr;
     948             :     }
     949             : 
     950             :     /* -------------------------------------------------------------------- */
     951             :     /*     Get the HeaderOffset if it exists in the header                  */
     952             :     /* -------------------------------------------------------------------- */
     953          62 :     GIntBig nHeaderOffset = 0;
     954          62 :     const char *pszHeaderOffset = poHeader->Find("HeaderOffset");
     955          62 :     if (pszHeaderOffset != nullptr)
     956             :     {
     957           2 :         nHeaderOffset = CPLAtoGIntBig(pszHeaderOffset);
     958           2 :         if (nHeaderOffset < 0)
     959             :         {
     960           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     961             :                      "Illegal value for HeaderOffset: %s", pszHeaderOffset);
     962           0 :             return nullptr;
     963             :         }
     964             :     }
     965             : 
     966             :     /* -------------------------------------------------------------------- */
     967             :     /*      Establish the data type.                                        */
     968             :     /* -------------------------------------------------------------------- */
     969             :     CPLString osCellType =
     970         124 :         poHeader->Find("RasterInfo.CellType", "Unsigned8BitInteger");
     971             :     GDALDataType eType;
     972          62 :     if (EQUAL(osCellType, "Unsigned8BitInteger"))
     973          29 :         eType = GDT_Byte;
     974          33 :     else if (EQUAL(osCellType, "Signed8BitInteger"))
     975           6 :         eType = GDT_Int8;
     976          27 :     else if (EQUAL(osCellType, "Unsigned16BitInteger"))
     977           3 :         eType = GDT_UInt16;
     978          24 :     else if (EQUAL(osCellType, "Signed16BitInteger"))
     979           6 :         eType = GDT_Int16;
     980          18 :     else if (EQUAL(osCellType, "Unsigned32BitInteger"))
     981           3 :         eType = GDT_UInt32;
     982          15 :     else if (EQUAL(osCellType, "Signed32BitInteger"))
     983           3 :         eType = GDT_Int32;
     984          12 :     else if (EQUAL(osCellType, "IEEE4ByteReal"))
     985           9 :         eType = GDT_Float32;
     986           3 :     else if (EQUAL(osCellType, "IEEE8ByteReal"))
     987           3 :         eType = GDT_Float64;
     988             :     else
     989             :     {
     990           0 :         CPLDebug("ERS", "Unknown CellType '%s'", osCellType.c_str());
     991           0 :         eType = GDT_Byte;
     992             :     }
     993             : 
     994             :     /* -------------------------------------------------------------------- */
     995             :     /*      Pick up the word order.                                         */
     996             :     /* -------------------------------------------------------------------- */
     997             :     const int bNative =
     998             : #ifdef CPL_LSB
     999          62 :         EQUAL(poHeader->Find("ByteOrder", "LSBFirst"), "LSBFirst")
    1000             : #else
    1001             :         EQUAL(poHeader->Find("ByteOrder", "MSBFirst"), "MSBFirst")
    1002             : #endif
    1003             :         ;
    1004             :     /* -------------------------------------------------------------------- */
    1005             :     /*      Figure out the name of the target file.                         */
    1006             :     /* -------------------------------------------------------------------- */
    1007         124 :     CPLString osPath = CPLGetPath(poOpenInfo->pszFilename);
    1008         124 :     CPLString osDataFile = poHeader->Find("DataFile", "");
    1009             : 
    1010          62 :     if (osDataFile.length() == 0)  // just strip off extension.
    1011             :     {
    1012          61 :         osDataFile = CPLGetFilename(poOpenInfo->pszFilename);
    1013          61 :         osDataFile = osDataFile.substr(0, osDataFile.find_last_of('.'));
    1014             :     }
    1015             : 
    1016         124 :     CPLString osDataFilePath = CPLFormFilename(osPath, osDataFile, nullptr);
    1017             : 
    1018             :     /* -------------------------------------------------------------------- */
    1019             :     /*      DataSetType = Translated files are links to things like ecw     */
    1020             :     /*      files.                                                          */
    1021             :     /* -------------------------------------------------------------------- */
    1022          62 :     if (EQUAL(poHeader->Find("DataSetType", ""), "Translated"))
    1023             :     {
    1024           2 :         nRecLevel++;
    1025           2 :         poDS->poDepFile = GDALDataset::FromHandle(
    1026             :             GDALOpen(osDataFilePath, poOpenInfo->eAccess));
    1027           2 :         nRecLevel--;
    1028             : 
    1029           2 :         if (poDS->poDepFile != nullptr &&
    1030           1 :             poDS->poDepFile->GetRasterXSize() == poDS->GetRasterXSize() &&
    1031           4 :             poDS->poDepFile->GetRasterYSize() == poDS->GetRasterYSize() &&
    1032           1 :             poDS->poDepFile->GetRasterCount() >= nBands)
    1033             :         {
    1034           2 :             for (int iBand = 0; iBand < nBands; iBand++)
    1035             :             {
    1036             :                 // Assume pixel interleaved.
    1037           2 :                 poDS->SetBand(iBand + 1,
    1038             :                               new ERSProxyRasterBand(
    1039           1 :                                   poDS->poDepFile->GetRasterBand(iBand + 1)));
    1040             :             }
    1041             :         }
    1042             :         else
    1043             :         {
    1044           1 :             delete poDS->poDepFile;
    1045           1 :             poDS->poDepFile = nullptr;
    1046             :         }
    1047             :     }
    1048             : 
    1049             :     /* ==================================================================== */
    1050             :     /*      While ERStorage indicates a raw file.                           */
    1051             :     /* ==================================================================== */
    1052          60 :     else if (EQUAL(poHeader->Find("DataSetType", ""), "ERStorage"))
    1053             :     {
    1054             :         // Open data file.
    1055          60 :         if (poOpenInfo->eAccess == GA_Update)
    1056          38 :             poDS->fpImage = VSIFOpenL(osDataFilePath, "r+");
    1057             :         else
    1058          22 :             poDS->fpImage = VSIFOpenL(osDataFilePath, "r");
    1059             : 
    1060          60 :         poDS->osRawFilename = std::move(osDataFilePath);
    1061             : 
    1062          60 :         if (poDS->fpImage != nullptr && nBands > 0)
    1063             :         {
    1064          60 :             int iWordSize = GDALGetDataTypeSizeBytes(eType);
    1065             : 
    1066          60 :             const auto knIntMax = std::numeric_limits<int>::max();
    1067         120 :             if (nBands > knIntMax / iWordSize ||
    1068          60 :                 poDS->nRasterXSize > knIntMax / (nBands * iWordSize))
    1069             :             {
    1070           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1071             :                          "int overflow: too large nBands and/or nRasterXSize");
    1072           0 :                 return nullptr;
    1073             :             }
    1074             : 
    1075          60 :             if (!RAWDatasetCheckMemoryUsage(
    1076          60 :                     poDS->nRasterXSize, poDS->nRasterYSize, nBands, iWordSize,
    1077          60 :                     iWordSize, iWordSize * nBands * poDS->nRasterXSize,
    1078             :                     nHeaderOffset,
    1079          60 :                     static_cast<vsi_l_offset>(iWordSize) * poDS->nRasterXSize,
    1080          60 :                     poDS->fpImage))
    1081             :             {
    1082           0 :                 return nullptr;
    1083             :             }
    1084          60 :             if (nHeaderOffset > std::numeric_limits<GIntBig>::max() -
    1085          60 :                                     static_cast<GIntBig>(nBands - 1) *
    1086          60 :                                         iWordSize * poDS->nRasterXSize)
    1087             :             {
    1088           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1089             :                          "int overflow: too large nHeaderOffset");
    1090           0 :                 return nullptr;
    1091             :             }
    1092             : 
    1093         162 :             for (int iBand = 0; iBand < nBands; iBand++)
    1094             :             {
    1095             :                 // Assume pixel interleaved.
    1096             :                 auto poBand = std::make_unique<ERSRasterBand>(
    1097         102 :                     poDS.get(), iBand + 1, poDS->fpImage,
    1098           0 :                     nHeaderOffset + static_cast<vsi_l_offset>(iWordSize) *
    1099         102 :                                         iBand * poDS->nRasterXSize,
    1100         102 :                     iWordSize, iWordSize * nBands * poDS->nRasterXSize, eType,
    1101         102 :                     bNative);
    1102         102 :                 if (!poBand->IsValid())
    1103           0 :                     return nullptr;
    1104         102 :                 poDS->SetBand(iBand + 1, std::move(poBand));
    1105             :             }
    1106             :         }
    1107             :     }
    1108             : 
    1109             :     /* -------------------------------------------------------------------- */
    1110             :     /*      Otherwise we have an error!                                     */
    1111             :     /* -------------------------------------------------------------------- */
    1112          62 :     if (poDS->nBands == 0)
    1113             :     {
    1114           1 :         return nullptr;
    1115             :     }
    1116             : 
    1117             :     /* -------------------------------------------------------------------- */
    1118             :     /*      Look for band descriptions.                                     */
    1119             :     /* -------------------------------------------------------------------- */
    1120          61 :     ERSHdrNode *poRI = poHeader->FindNode("RasterInfo");
    1121             : 
    1122         357 :     for (int iChild = 0, iBand = 0;
    1123         357 :          poRI != nullptr && iChild < poRI->nItemCount && iBand < poDS->nBands;
    1124             :          iChild++)
    1125             :     {
    1126         296 :         if (poRI->papoItemChild[iChild] != nullptr &&
    1127          33 :             EQUAL(poRI->papszItemName[iChild], "BandId"))
    1128             :         {
    1129             :             const char *pszValue =
    1130          12 :                 poRI->papoItemChild[iChild]->Find("Value", nullptr);
    1131             : 
    1132          12 :             iBand++;
    1133          12 :             if (pszValue)
    1134             :             {
    1135          12 :                 CPLPushErrorHandler(CPLQuietErrorHandler);
    1136          12 :                 poDS->GetRasterBand(iBand)->SetDescription(pszValue);
    1137          12 :                 CPLPopErrorHandler();
    1138             :             }
    1139             : 
    1140          12 :             pszValue = poRI->papoItemChild[iChild]->Find("Units", nullptr);
    1141          12 :             if (pszValue)
    1142             :             {
    1143           4 :                 CPLPushErrorHandler(CPLQuietErrorHandler);
    1144           4 :                 poDS->GetRasterBand(iBand)->SetUnitType(pszValue);
    1145           4 :                 CPLPopErrorHandler();
    1146             :             }
    1147             :         }
    1148             :     }
    1149             : 
    1150             :     /* -------------------------------------------------------------------- */
    1151             :     /*      Look for projection.                                            */
    1152             :     /* -------------------------------------------------------------------- */
    1153          61 :     poDS->osProj = poHeader->Find("CoordinateSpace.Projection", "");
    1154          61 :     poDS->osDatum = poHeader->Find("CoordinateSpace.Datum", "");
    1155          61 :     poDS->osUnits = poHeader->Find("CoordinateSpace.Units", "");
    1156             : 
    1157         219 :     poDS->m_oSRS.importFromERM(
    1158          61 :         !poDS->osProj.empty() ? poDS->osProj.c_str() : "RAW",
    1159          61 :         !poDS->osDatum.empty() ? poDS->osDatum.c_str() : "WGS84",
    1160          61 :         !poDS->osUnits.empty() ? poDS->osUnits.c_str() : "METERS");
    1161             : 
    1162             :     /* -------------------------------------------------------------------- */
    1163             :     /*      Look for the geotransform.                                      */
    1164             :     /* -------------------------------------------------------------------- */
    1165          61 :     if (poHeader->Find("RasterInfo.RegistrationCoord.Eastings", nullptr))
    1166             :     {
    1167           6 :         poDS->bGotTransform = TRUE;
    1168           6 :         poDS->adfGeoTransform[0] = CPLAtof(
    1169             :             poHeader->Find("RasterInfo.RegistrationCoord.Eastings", ""));
    1170          12 :         poDS->adfGeoTransform[1] =
    1171           6 :             CPLAtof(poHeader->Find("RasterInfo.CellInfo.Xdimension", "1.0"));
    1172           6 :         poDS->adfGeoTransform[2] = 0.0;
    1173           6 :         poDS->adfGeoTransform[3] = CPLAtof(
    1174             :             poHeader->Find("RasterInfo.RegistrationCoord.Northings", ""));
    1175           6 :         poDS->adfGeoTransform[4] = 0.0;
    1176           6 :         poDS->adfGeoTransform[5] =
    1177           6 :             -CPLAtof(poHeader->Find("RasterInfo.CellInfo.Ydimension", "1.0"));
    1178             :     }
    1179          60 :     else if (poHeader->Find("RasterInfo.RegistrationCoord.Latitude", nullptr) &&
    1180           5 :              poHeader->Find("RasterInfo.CellInfo.Xdimension", nullptr))
    1181             :     {
    1182           5 :         poDS->bGotTransform = TRUE;
    1183           5 :         poDS->adfGeoTransform[0] = ERSDMS2Dec(
    1184             :             poHeader->Find("RasterInfo.RegistrationCoord.Longitude", ""));
    1185          10 :         poDS->adfGeoTransform[1] =
    1186           5 :             CPLAtof(poHeader->Find("RasterInfo.CellInfo.Xdimension", ""));
    1187           5 :         poDS->adfGeoTransform[2] = 0.0;
    1188           5 :         poDS->adfGeoTransform[3] = ERSDMS2Dec(
    1189             :             poHeader->Find("RasterInfo.RegistrationCoord.Latitude", ""));
    1190           5 :         poDS->adfGeoTransform[4] = 0.0;
    1191           5 :         poDS->adfGeoTransform[5] =
    1192           5 :             -CPLAtof(poHeader->Find("RasterInfo.CellInfo.Ydimension", ""));
    1193             :     }
    1194             : 
    1195             :     /* -------------------------------------------------------------------- */
    1196             :     /*      Adjust if we have a registration cell.                          */
    1197             :     /* -------------------------------------------------------------------- */
    1198             : 
    1199             :     /* http://geospatial.intergraph.com/Libraries/Tech_Docs/ERDAS_ER_Mapper_Customization_Guide.sflb.ashx
    1200             :      */
    1201             :     /* Page 27 : */
    1202             :     /* RegistrationCellX and RegistrationCellY : The image X and Y
    1203             :        coordinates of the cell which corresponds to the Registration
    1204             :        Coordinate. Note that the RegistrationCellX and
    1205             :        RegistrationCellY can be fractional values. If
    1206             :        RegistrationCellX and RegistrationCellY are not specified,
    1207             :        they are assumed to be (0,0), which is the top left corner of the
    1208             :        image.
    1209             :        */
    1210             :     double dfCellX =
    1211          61 :         CPLAtof(poHeader->Find("RasterInfo.RegistrationCellX", "0"));
    1212             :     double dfCellY =
    1213          61 :         CPLAtof(poHeader->Find("RasterInfo.RegistrationCellY", "0"));
    1214             : 
    1215          61 :     if (poDS->bGotTransform)
    1216             :     {
    1217          11 :         poDS->adfGeoTransform[0] -= dfCellX * poDS->adfGeoTransform[1] +
    1218          11 :                                     dfCellY * poDS->adfGeoTransform[2];
    1219          22 :         poDS->adfGeoTransform[3] -= dfCellX * poDS->adfGeoTransform[4] +
    1220          11 :                                     dfCellY * poDS->adfGeoTransform[5];
    1221             :     }
    1222             : 
    1223             :     /* -------------------------------------------------------------------- */
    1224             :     /*      Check for null values.                                          */
    1225             :     /* -------------------------------------------------------------------- */
    1226          61 :     if (poHeader->Find("RasterInfo.NullCellValue", nullptr))
    1227             :     {
    1228           8 :         poDS->bHasNoDataValue = TRUE;
    1229          16 :         poDS->dfNoDataValue =
    1230           8 :             CPLAtofM(poHeader->Find("RasterInfo.NullCellValue"));
    1231             : 
    1232           8 :         if (poDS->poDepFile != nullptr)
    1233             :         {
    1234           0 :             CPLPushErrorHandler(CPLQuietErrorHandler);
    1235             : 
    1236           0 :             for (int iBand = 1; iBand <= poDS->nBands; iBand++)
    1237           0 :                 poDS->GetRasterBand(iBand)->SetNoDataValue(poDS->dfNoDataValue);
    1238             : 
    1239           0 :             CPLPopErrorHandler();
    1240             :         }
    1241             :     }
    1242             : 
    1243             :     /* -------------------------------------------------------------------- */
    1244             :     /*      Do we have an "All" region?                                     */
    1245             :     /* -------------------------------------------------------------------- */
    1246          61 :     ERSHdrNode *poAll = nullptr;
    1247             : 
    1248         366 :     for (int iChild = 0; poRI != nullptr && iChild < poRI->nItemCount; iChild++)
    1249             :     {
    1250         305 :         if (poRI->papoItemChild[iChild] != nullptr &&
    1251          39 :             EQUAL(poRI->papszItemName[iChild], "RegionInfo"))
    1252             :         {
    1253           3 :             if (EQUAL(poRI->papoItemChild[iChild]->Find("RegionName", ""),
    1254             :                       "All"))
    1255           3 :                 poAll = poRI->papoItemChild[iChild];
    1256             :         }
    1257             :     }
    1258             : 
    1259             :     /* -------------------------------------------------------------------- */
    1260             :     /*      Do we have statistics?                                          */
    1261             :     /* -------------------------------------------------------------------- */
    1262          61 :     if (poAll && poAll->FindNode("Stats"))
    1263             :     {
    1264           3 :         CPLPushErrorHandler(CPLQuietErrorHandler);
    1265             : 
    1266           6 :         for (int iBand = 1; iBand <= poDS->nBands; iBand++)
    1267             :         {
    1268             :             const char *pszValue =
    1269           3 :                 poAll->FindElem("Stats.MinimumValue", iBand - 1);
    1270             : 
    1271           3 :             if (pszValue)
    1272           3 :                 poDS->GetRasterBand(iBand)->SetMetadataItem(
    1273           3 :                     "STATISTICS_MINIMUM", pszValue);
    1274             : 
    1275           3 :             pszValue = poAll->FindElem("Stats.MaximumValue", iBand - 1);
    1276             : 
    1277           3 :             if (pszValue)
    1278           3 :                 poDS->GetRasterBand(iBand)->SetMetadataItem(
    1279           3 :                     "STATISTICS_MAXIMUM", pszValue);
    1280             : 
    1281           3 :             pszValue = poAll->FindElem("Stats.MeanValue", iBand - 1);
    1282             : 
    1283           3 :             if (pszValue)
    1284           3 :                 poDS->GetRasterBand(iBand)->SetMetadataItem("STATISTICS_MEAN",
    1285           3 :                                                             pszValue);
    1286             : 
    1287           3 :             pszValue = poAll->FindElem("Stats.MedianValue", iBand - 1);
    1288             : 
    1289           3 :             if (pszValue)
    1290           3 :                 poDS->GetRasterBand(iBand)->SetMetadataItem("STATISTICS_MEDIAN",
    1291           3 :                                                             pszValue);
    1292             :         }
    1293             : 
    1294           3 :         CPLPopErrorHandler();
    1295             :     }
    1296             : 
    1297             :     /* -------------------------------------------------------------------- */
    1298             :     /*      Do we have GCPs.                                                */
    1299             :     /* -------------------------------------------------------------------- */
    1300          61 :     if (poHeader->FindNode("RasterInfo.WarpControl"))
    1301           2 :         poDS->ReadGCPs();
    1302             : 
    1303             :     /* -------------------------------------------------------------------- */
    1304             :     /*      Initialize any PAM information.                                 */
    1305             :     /* -------------------------------------------------------------------- */
    1306          61 :     poDS->SetDescription(poOpenInfo->pszFilename);
    1307          61 :     poDS->TryLoadXML();
    1308             : 
    1309             :     // if no SR in xml, try aux
    1310          61 :     const OGRSpatialReference *poSRS = poDS->GDALPamDataset::GetSpatialRef();
    1311          61 :     if (poSRS == nullptr)
    1312             :     {
    1313             :         // try aux
    1314             :         auto poAuxDS = std::unique_ptr<GDALDataset>(GDALFindAssociatedAuxFile(
    1315         122 :             poOpenInfo->pszFilename, GA_ReadOnly, poDS.get()));
    1316          61 :         if (poAuxDS)
    1317             :         {
    1318           0 :             poSRS = poAuxDS->GetSpatialRef();
    1319           0 :             if (poSRS)
    1320             :             {
    1321           0 :                 poDS->m_oSRS = *poSRS;
    1322             :             }
    1323             :         }
    1324             :     }
    1325             :     /* -------------------------------------------------------------------- */
    1326             :     /*      Check for overviews.                                            */
    1327             :     /* -------------------------------------------------------------------- */
    1328          61 :     poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
    1329             : 
    1330          61 :     return poDS.release();
    1331             : }
    1332             : 
    1333             : /************************************************************************/
    1334             : /*                               Create()                               */
    1335             : /************************************************************************/
    1336             : 
    1337          67 : GDALDataset *ERSDataset::Create(const char *pszFilename, int nXSize, int nYSize,
    1338             :                                 int nBandsIn, GDALDataType eType,
    1339             :                                 char **papszOptions)
    1340             : 
    1341             : {
    1342             :     /* -------------------------------------------------------------------- */
    1343             :     /*      Verify settings.                                                */
    1344             :     /* -------------------------------------------------------------------- */
    1345          67 :     if (nBandsIn <= 0)
    1346             :     {
    1347           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    1348             :                  "ERS driver does not support %d bands.\n", nBandsIn);
    1349           1 :         return nullptr;
    1350             :     }
    1351             : 
    1352          66 :     if (eType != GDT_Byte && eType != GDT_Int8 && eType != GDT_Int16 &&
    1353          29 :         eType != GDT_UInt16 && eType != GDT_Int32 && eType != GDT_UInt32 &&
    1354          19 :         eType != GDT_Float32 && eType != GDT_Float64)
    1355             :     {
    1356          16 :         CPLError(
    1357             :             CE_Failure, CPLE_AppDefined,
    1358             :             "The ERS driver does not supporting creating files of types %s.",
    1359             :             GDALGetDataTypeName(eType));
    1360          16 :         return nullptr;
    1361             :     }
    1362             : 
    1363             :     /* -------------------------------------------------------------------- */
    1364             :     /*      Work out the name we want to use for the .ers and binary        */
    1365             :     /*      data files.                                                     */
    1366             :     /* -------------------------------------------------------------------- */
    1367         100 :     CPLString osBinFile, osErsFile;
    1368             : 
    1369          50 :     if (EQUAL(CPLGetExtension(pszFilename), "ers"))
    1370             :     {
    1371           7 :         osErsFile = pszFilename;
    1372           7 :         osBinFile = osErsFile.substr(0, osErsFile.length() - 4);
    1373             :     }
    1374             :     else
    1375             :     {
    1376          43 :         osBinFile = pszFilename;
    1377          43 :         osErsFile = osBinFile + ".ers";
    1378             :     }
    1379             : 
    1380             :     /* -------------------------------------------------------------------- */
    1381             :     /*      Work out some values we will write.                             */
    1382             :     /* -------------------------------------------------------------------- */
    1383          50 :     const char *pszCellType = "Unsigned8BitInteger";
    1384             : 
    1385          50 :     if (eType == GDT_Byte)
    1386          28 :         pszCellType = "Unsigned8BitInteger";
    1387          22 :     else if (eType == GDT_Int8)
    1388           3 :         pszCellType = "Signed8BitInteger";
    1389          19 :     else if (eType == GDT_Int16)
    1390           3 :         pszCellType = "Signed16BitInteger";
    1391          16 :     else if (eType == GDT_UInt16)
    1392           3 :         pszCellType = "Unsigned16BitInteger";
    1393          13 :     else if (eType == GDT_Int32)
    1394           3 :         pszCellType = "Signed32BitInteger";
    1395          10 :     else if (eType == GDT_UInt32)
    1396           3 :         pszCellType = "Unsigned32BitInteger";
    1397           7 :     else if (eType == GDT_Float32)
    1398           4 :         pszCellType = "IEEE4ByteReal";
    1399           3 :     else if (eType == GDT_Float64)
    1400           3 :         pszCellType = "IEEE8ByteReal";
    1401             :     else
    1402             :     {
    1403           0 :         CPLAssert(false);
    1404             :     }
    1405             : 
    1406             :     /* -------------------------------------------------------------------- */
    1407             :     /*      Handling for signed eight bit data.                             */
    1408             :     /* -------------------------------------------------------------------- */
    1409          50 :     const char *pszPixelType = CSLFetchNameValue(papszOptions, "PIXELTYPE");
    1410          50 :     if (pszPixelType && EQUAL(pszPixelType, "SIGNEDBYTE") && eType == GDT_Byte)
    1411           0 :         pszCellType = "Signed8BitInteger";
    1412             : 
    1413             :     /* -------------------------------------------------------------------- */
    1414             :     /*      Write binary file.                                              */
    1415             :     /* -------------------------------------------------------------------- */
    1416          50 :     VSILFILE *fpBin = VSIFOpenL(osBinFile, "w");
    1417             : 
    1418          50 :     if (fpBin == nullptr)
    1419             :     {
    1420           3 :         CPLError(CE_Failure, CPLE_FileIO, "Failed to create %s:\n%s",
    1421           3 :                  osBinFile.c_str(), VSIStrerror(errno));
    1422           3 :         return nullptr;
    1423             :     }
    1424             : 
    1425             :     GUIntBig nSize =
    1426          47 :         nXSize * (GUIntBig)nYSize * nBandsIn * (GDALGetDataTypeSize(eType) / 8);
    1427          47 :     GByte byZero = 0;
    1428          94 :     if (VSIFSeekL(fpBin, nSize - 1, SEEK_SET) != 0 ||
    1429          47 :         VSIFWriteL(&byZero, 1, 1, fpBin) != 1)
    1430             :     {
    1431          10 :         CPLError(CE_Failure, CPLE_FileIO, "Failed to write %s:\n%s",
    1432          10 :                  osBinFile.c_str(), VSIStrerror(errno));
    1433          10 :         VSIFCloseL(fpBin);
    1434          10 :         return nullptr;
    1435             :     }
    1436          37 :     VSIFCloseL(fpBin);
    1437             : 
    1438             :     /* -------------------------------------------------------------------- */
    1439             :     /*      Try writing header file.                                        */
    1440             :     /* -------------------------------------------------------------------- */
    1441          37 :     VSILFILE *fpERS = VSIFOpenL(osErsFile, "w");
    1442             : 
    1443          37 :     if (fpERS == nullptr)
    1444             :     {
    1445           0 :         CPLError(CE_Failure, CPLE_FileIO, "Failed to create %s:\n%s",
    1446           0 :                  osErsFile.c_str(), VSIStrerror(errno));
    1447           0 :         return nullptr;
    1448             :     }
    1449             : 
    1450          37 :     VSIFPrintfL(fpERS, "DatasetHeader Begin\n");
    1451          37 :     VSIFPrintfL(fpERS, "\tVersion\t\t = \"6.0\"\n");
    1452          37 :     VSIFPrintfL(fpERS, "\tName\t\t= \"%s\"\n", CPLGetFilename(osErsFile));
    1453             : 
    1454             :     // Last updated requires timezone info which we don't necessarily get
    1455             :     // get from VSICTime() so perhaps it is better to omit this.
    1456             :     //    VSIFPrintfL( fpERS, "\tLastUpdated\t= %s",
    1457             :     //                 VSICTime( VSITime( NULL ) ) );
    1458             : 
    1459          37 :     VSIFPrintfL(fpERS, "\tDataSetType\t= ERStorage\n");
    1460          37 :     VSIFPrintfL(fpERS, "\tDataType\t= Raster\n");
    1461          37 :     VSIFPrintfL(fpERS, "\tByteOrder\t= LSBFirst\n");
    1462          37 :     VSIFPrintfL(fpERS, "\tRasterInfo Begin\n");
    1463          37 :     VSIFPrintfL(fpERS, "\t\tCellType\t= %s\n", pszCellType);
    1464          37 :     VSIFPrintfL(fpERS, "\t\tNrOfLines\t= %d\n", nYSize);
    1465          37 :     VSIFPrintfL(fpERS, "\t\tNrOfCellsPerLine\t= %d\n", nXSize);
    1466          37 :     VSIFPrintfL(fpERS, "\t\tNrOfBands\t= %d\n", nBandsIn);
    1467          37 :     VSIFPrintfL(fpERS, "\tRasterInfo End\n");
    1468          37 :     if (VSIFPrintfL(fpERS, "DatasetHeader End\n") < 17)
    1469             :     {
    1470           0 :         CPLError(CE_Failure, CPLE_FileIO, "Failed to write %s:\n%s",
    1471           0 :                  osErsFile.c_str(), VSIStrerror(errno));
    1472           0 :         return nullptr;
    1473             :     }
    1474             : 
    1475          37 :     VSIFCloseL(fpERS);
    1476             : 
    1477             :     /* -------------------------------------------------------------------- */
    1478             :     /*      Reopen.                                                         */
    1479             :     /* -------------------------------------------------------------------- */
    1480          74 :     GDALOpenInfo oOpenInfo(osErsFile, GA_Update);
    1481          37 :     ERSDataset *poDS = (ERSDataset *)Open(&oOpenInfo);
    1482          37 :     if (poDS == nullptr)
    1483           0 :         return nullptr;
    1484             : 
    1485             :     /* -------------------------------------------------------------------- */
    1486             :     /*      Fetch DATUM, PROJ and UNITS creation option                     */
    1487             :     /* -------------------------------------------------------------------- */
    1488          37 :     const char *pszDatum = CSLFetchNameValue(papszOptions, "DATUM");
    1489          37 :     if (pszDatum)
    1490             :     {
    1491           2 :         poDS->osDatumForced = pszDatum;
    1492           2 :         poDS->osDatum = pszDatum;
    1493             :     }
    1494          37 :     const char *pszProj = CSLFetchNameValue(papszOptions, "PROJ");
    1495          37 :     if (pszProj)
    1496             :     {
    1497           2 :         poDS->osProjForced = pszProj;
    1498           2 :         poDS->osProj = pszProj;
    1499             :     }
    1500          37 :     const char *pszUnits = CSLFetchNameValue(papszOptions, "UNITS");
    1501          37 :     if (pszUnits)
    1502             :     {
    1503           2 :         poDS->osUnitsForced = pszUnits;
    1504           2 :         poDS->osUnits = pszUnits;
    1505             :     }
    1506             : 
    1507          37 :     if (pszDatum || pszProj || pszUnits)
    1508             :     {
    1509           2 :         poDS->WriteProjectionInfo(pszProj ? pszProj : "RAW",
    1510             :                                   pszDatum ? pszDatum : "RAW",
    1511             :                                   pszUnits ? pszUnits : "METERS");
    1512             :     }
    1513             : 
    1514          37 :     return poDS;
    1515             : }
    1516             : 
    1517             : /************************************************************************/
    1518             : /*                         GDALRegister_ERS()                           */
    1519             : /************************************************************************/
    1520             : 
    1521        1520 : void GDALRegister_ERS()
    1522             : 
    1523             : {
    1524        1520 :     if (GDALGetDriverByName("ERS") != nullptr)
    1525         301 :         return;
    1526             : 
    1527        1219 :     GDALDriver *poDriver = new GDALDriver();
    1528             : 
    1529        1219 :     poDriver->SetDescription("ERS");
    1530        1219 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1531        1219 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "ERMapper .ers Labelled");
    1532        1219 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/ers.html");
    1533        1219 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "ers");
    1534        1219 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES,
    1535             :                               "Byte Int8 Int16 UInt16 Int32 UInt32 "
    1536        1219 :                               "Float32 Float64");
    1537             : 
    1538        1219 :     poDriver->SetMetadataItem(
    1539             :         GDAL_DMD_CREATIONOPTIONLIST,
    1540             :         "<CreationOptionList>"
    1541             :         "   <Option name='PIXELTYPE' type='string' description='(deprecated, "
    1542             :         "use Int8 datatype) By setting this to SIGNEDBYTE, a new Byte file can "
    1543             :         "be forced to be written as signed byte'/>"
    1544             :         "   <Option name='PROJ' type='string' description='ERS Projection "
    1545             :         "Name'/>"
    1546             :         "   <Option name='DATUM' type='string' description='ERS Datum Name' />"
    1547             :         "   <Option name='UNITS' type='string-select' description='ERS "
    1548             :         "Projection Units'>"
    1549             :         "       <Value>METERS</Value>"
    1550             :         "       <Value>FEET</Value>"
    1551             :         "   </Option>"
    1552        1219 :         "</CreationOptionList>");
    1553             : 
    1554        1219 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1555             : 
    1556        1219 :     poDriver->pfnOpen = ERSDataset::Open;
    1557        1219 :     poDriver->pfnIdentify = ERSDataset::Identify;
    1558        1219 :     poDriver->pfnCreate = ERSDataset::Create;
    1559             : 
    1560        1219 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1561             : }

Generated by: LCOV version 1.14