LCOV - code coverage report
Current view: top level - frmts/nitf - ecrgtocdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 396 459 86.3 %
Date: 2025-08-01 13:10:27 Functions: 25 25 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  ECRG TOC read Translator
       4             :  * Purpose:  Implementation of ECRGTOCDataset and ECRGTOCSubDataset.
       5             :  * Author:   Even Rouault, even.rouault at spatialys.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2011, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_port.h"
      14             : 
      15             : #include <array>
      16             : #include <cassert>
      17             : #include <cmath>
      18             : #include <cstddef>
      19             : #include <cstdio>
      20             : #include <cstdlib>
      21             : #include <cstring>
      22             : #include <memory>
      23             : #include <string>
      24             : #include <vector>
      25             : 
      26             : #include "cpl_conv.h"
      27             : #include "cpl_error.h"
      28             : #include "cpl_minixml.h"
      29             : #include "cpl_string.h"
      30             : #include "gdal.h"
      31             : #include "gdal_frmts.h"
      32             : #include "gdal_pam.h"
      33             : #include "gdal_priv.h"
      34             : #include "gdal_proxy.h"
      35             : #include "ogr_srs_api.h"
      36             : #include "vrtdataset.h"
      37             : #include "nitfdrivercore.h"
      38             : 
      39             : /** Overview of used classes :
      40             :    - ECRGTOCDataset : lists the different subdatasets, listed in the .xml,
      41             :                       as subdatasets
      42             :    - ECRGTOCSubDataset : one of these subdatasets, implemented as a VRT, of
      43             :                          the relevant NITF tiles
      44             : */
      45             : 
      46             : namespace
      47             : {
      48             : typedef struct
      49             : {
      50             :     const char *pszName;
      51             :     const char *pszPath;
      52             :     int nScale;
      53             :     int nZone;
      54             : } FrameDesc;
      55             : }  // namespace
      56             : 
      57             : /************************************************************************/
      58             : /* ==================================================================== */
      59             : /*                            ECRGTOCDataset                            */
      60             : /* ==================================================================== */
      61             : /************************************************************************/
      62             : 
      63             : class ECRGTOCDataset final : public GDALPamDataset
      64             : {
      65             :     OGRSpatialReference m_oSRS{};
      66             :     char **papszSubDatasets = nullptr;
      67             :     GDALGeoTransform m_gt{};
      68             :     char **papszFileList = nullptr;
      69             : 
      70             :     CPL_DISALLOW_COPY_ASSIGN(ECRGTOCDataset)
      71             : 
      72             :   public:
      73          21 :     ECRGTOCDataset()
      74          21 :     {
      75          21 :         m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
      76          21 :         m_oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
      77          21 :     }
      78             : 
      79          42 :     virtual ~ECRGTOCDataset()
      80          21 :     {
      81          21 :         CSLDestroy(papszSubDatasets);
      82          21 :         CSLDestroy(papszFileList);
      83          42 :     }
      84             : 
      85             :     virtual char **GetMetadata(const char *pszDomain = "") override;
      86             : 
      87           1 :     virtual char **GetFileList() override
      88             :     {
      89           1 :         return CSLDuplicate(papszFileList);
      90             :     }
      91             : 
      92             :     void AddSubDataset(const char *pszFilename, const char *pszProductTitle,
      93             :                        const char *pszDiscId, const char *pszScale);
      94             : 
      95           1 :     virtual CPLErr GetGeoTransform(GDALGeoTransform &gt) const override
      96             :     {
      97           1 :         gt = m_gt;
      98           1 :         return CE_None;
      99             :     }
     100             : 
     101           1 :     const OGRSpatialReference *GetSpatialRef() const override
     102             :     {
     103           1 :         return &m_oSRS;
     104             :     }
     105             : 
     106             :     static GDALDataset *Build(const char *pszTOCFilename, CPLXMLNode *psXML,
     107             :                               const std::string &osProduct,
     108             :                               const std::string &osDiscId,
     109             :                               const std::string &osScale,
     110             :                               const char *pszFilename);
     111             : 
     112             :     static GDALDataset *Open(GDALOpenInfo *poOpenInfo);
     113             : };
     114             : 
     115             : /************************************************************************/
     116             : /* ==================================================================== */
     117             : /*                            ECRGTOCSubDataset                          */
     118             : /* ==================================================================== */
     119             : /************************************************************************/
     120             : 
     121             : class ECRGTOCSubDataset final : public VRTDataset
     122             : {
     123             :     char **papszFileList = nullptr;
     124             :     CPL_DISALLOW_COPY_ASSIGN(ECRGTOCSubDataset)
     125             : 
     126             :   public:
     127          11 :     ECRGTOCSubDataset(int nXSize, int nYSize) : VRTDataset(nXSize, nYSize)
     128             :     {
     129             :         /* Don't try to write a VRT file */
     130          11 :         SetWritable(FALSE);
     131             : 
     132             :         /* The driver is set to VRT in VRTDataset constructor. */
     133             :         /* We have to set it to the expected value ! */
     134          11 :         poDriver = GDALDriver::FromHandle(GDALGetDriverByName("ECRGTOC"));
     135          11 :     }
     136             : 
     137             :     ~ECRGTOCSubDataset() override;
     138             : 
     139           2 :     virtual char **GetFileList() override
     140             :     {
     141           2 :         return CSLDuplicate(papszFileList);
     142             :     }
     143             : 
     144             :     static GDALDataset *
     145             :     Build(const char *pszProductTitle, const char *pszDiscId, int nScale,
     146             :           int nCountSubDataset, const char *pszTOCFilename,
     147             :           const std::vector<FrameDesc> &aosFrameDesc, double dfGlobalMinX,
     148             :           double dfGlobalMinY, double dfGlobalMaxX, double dfGlobalMaxY,
     149             :           double dfGlobalPixelXSize, double dfGlobalPixelYSize);
     150             : };
     151             : 
     152          22 : ECRGTOCSubDataset::~ECRGTOCSubDataset()
     153             : {
     154          11 :     CSLDestroy(papszFileList);
     155          22 : }
     156             : 
     157             : /************************************************************************/
     158             : /*                           LaunderString()                            */
     159             : /************************************************************************/
     160             : 
     161          67 : static CPLString LaunderString(const char *pszStr)
     162             : {
     163          67 :     CPLString osRet(pszStr);
     164         632 :     for (size_t i = 0; i < osRet.size(); i++)
     165             :     {
     166         565 :         if (osRet[i] == ':' || osRet[i] == ' ')
     167          42 :             osRet[i] = '_';
     168             :     }
     169          67 :     return osRet;
     170             : }
     171             : 
     172             : /************************************************************************/
     173             : /*                           AddSubDataset()                            */
     174             : /************************************************************************/
     175             : 
     176           9 : void ECRGTOCDataset::AddSubDataset(const char *pszFilename,
     177             :                                    const char *pszProductTitle,
     178             :                                    const char *pszDiscId, const char *pszScale)
     179             : 
     180             : {
     181             :     char szName[80];
     182           9 :     const int nCount = CSLCount(papszSubDatasets) / 2;
     183             : 
     184           9 :     snprintf(szName, sizeof(szName), "SUBDATASET_%d_NAME", nCount + 1);
     185          27 :     papszSubDatasets = CSLSetNameValue(
     186             :         papszSubDatasets, szName,
     187             :         CPLSPrintf("ECRG_TOC_ENTRY:%s:%s:%s:%s",
     188          18 :                    LaunderString(pszProductTitle).c_str(),
     189          18 :                    LaunderString(pszDiscId).c_str(),
     190          18 :                    LaunderString(pszScale).c_str(), pszFilename));
     191             : 
     192           9 :     snprintf(szName, sizeof(szName), "SUBDATASET_%d_DESC", nCount + 1);
     193           9 :     papszSubDatasets =
     194           9 :         CSLSetNameValue(papszSubDatasets, szName,
     195             :                         CPLSPrintf("Product %s, disc %s, scale %s",
     196             :                                    pszProductTitle, pszDiscId, pszScale));
     197           9 : }
     198             : 
     199             : /************************************************************************/
     200             : /*                            GetMetadata()                             */
     201             : /************************************************************************/
     202             : 
     203           7 : char **ECRGTOCDataset::GetMetadata(const char *pszDomain)
     204             : 
     205             : {
     206           7 :     if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
     207           7 :         return papszSubDatasets;
     208             : 
     209           0 :     return GDALPamDataset::GetMetadata(pszDomain);
     210             : }
     211             : 
     212             : /************************************************************************/
     213             : /*                         GetScaleFromString()                         */
     214             : /************************************************************************/
     215             : 
     216          22 : static int GetScaleFromString(const char *pszScale)
     217             : {
     218          22 :     const char *pszPtr = strstr(pszScale, "1:");
     219          22 :     if (pszPtr)
     220          22 :         pszPtr = pszPtr + 2;
     221             :     else
     222           0 :         pszPtr = pszScale;
     223             : 
     224          22 :     int nScale = 0;
     225             :     char ch;
     226         112 :     while ((ch = *pszPtr) != '\0')
     227             :     {
     228         112 :         if (ch >= '0' && ch <= '9')
     229          68 :             nScale = nScale * 10 + ch - '0';
     230          44 :         else if (ch == ' ')
     231             :             ;
     232          22 :         else if (ch == 'k' || ch == 'K')
     233          22 :             return nScale * 1000;
     234           0 :         else if (ch == 'm' || ch == 'M')
     235           0 :             return nScale * 1000000;
     236             :         else
     237           0 :             return 0;
     238          90 :         pszPtr++;
     239             :     }
     240           0 :     return nScale;
     241             : }
     242             : 
     243             : /************************************************************************/
     244             : /*                            GetFromBase34()                           */
     245             : /************************************************************************/
     246             : 
     247          53 : static GIntBig GetFromBase34(const char *pszVal, int nMaxSize)
     248             : {
     249          53 :     GIntBig nFrameNumber = 0;
     250         583 :     for (int i = 0; i < nMaxSize; i++)
     251             :     {
     252         530 :         char ch = pszVal[i];
     253         530 :         if (ch == '\0')
     254           0 :             break;
     255             :         int chVal;
     256         530 :         if (ch >= 'A' && ch <= 'Z')
     257           0 :             ch += 'a' - 'A';
     258             :         /* i and o letters are excluded, */
     259         530 :         if (ch >= '0' && ch <= '9')
     260         477 :             chVal = ch - '0';
     261          53 :         else if (ch >= 'a' && ch <= 'h')
     262           0 :             chVal = ch - 'a' + 10;
     263          53 :         else if (ch >= 'j' && ch <= 'n')
     264           0 :             chVal = ch - 'a' + 10 - 1;
     265          53 :         else if (ch >= 'p' && ch <= 'z')
     266          53 :             chVal = ch - 'a' + 10 - 2;
     267             :         else
     268             :         {
     269           0 :             CPLDebug("ECRG", "Invalid base34 value : %s", pszVal);
     270           0 :             break;
     271             :         }
     272         530 :         nFrameNumber = nFrameNumber * 34 + chVal;
     273             :     }
     274             : 
     275          53 :     return nFrameNumber;
     276             : }
     277             : 
     278             : /************************************************************************/
     279             : /*                             GetExtent()                              */
     280             : /************************************************************************/
     281             : 
     282             : /* MIL-PRF-32283 - Table II. ECRG zone limits. */
     283             : /* starting with a fake zone 0 for convenience. */
     284             : constexpr int anZoneUpperLat[] = {0, 32, 48, 56, 64, 68, 72, 76, 80};
     285             : 
     286             : /* APPENDIX 70, TABLE III of MIL-A-89007 */
     287             : constexpr int anACst_ADRG[] = {369664, 302592, 245760, 199168,
     288             :                                163328, 137216, 110080, 82432};
     289             : constexpr int nBCst_ADRG = 400384;
     290             : 
     291             : // TODO: Why are these two functions done this way?
     292         106 : static int CEIL_ROUND(double a, double b)
     293             : {
     294         106 :     return static_cast<int>(ceil(a / b) * b);
     295             : }
     296             : 
     297         106 : static int NEAR_ROUND(double a, double b)
     298             : {
     299         106 :     return static_cast<int>(floor((a / b) + 0.5) * b);
     300             : }
     301             : 
     302             : constexpr int ECRG_PIXELS = 2304;
     303             : 
     304          53 : static void GetExtent(const char *pszFrameName, int nScale, int nZone,
     305             :                       double &dfMinX, double &dfMaxX, double &dfMinY,
     306             :                       double &dfMaxY, double &dfPixelXSize,
     307             :                       double &dfPixelYSize)
     308             : {
     309          53 :     const int nAbsZone = abs(nZone);
     310             : #ifdef DEBUG
     311          53 :     assert(nAbsZone > 0 && nAbsZone <= 8);
     312             : #endif
     313             : 
     314             :     /************************************************************************/
     315             :     /*  Compute east-west constant                                          */
     316             :     /************************************************************************/
     317             :     /* MIL-PRF-89038 - 60.1.2 - East-west pixel constant. */
     318             :     const int nEW_ADRG =
     319          53 :         CEIL_ROUND(anACst_ADRG[nAbsZone - 1] * (1e6 / nScale), 512);
     320          53 :     const int nEW_CADRG = NEAR_ROUND(nEW_ADRG / (150. / 100.), 256);
     321             :     /* MIL-PRF-32283 - D.2.1.2 - East-west pixel constant. */
     322          53 :     const int nEW = nEW_CADRG / 256 * 384;
     323             : 
     324             :     /************************************************************************/
     325             :     /*  Compute number of longitudinal frames                               */
     326             :     /************************************************************************/
     327             :     /* MIL-PRF-32283 - D.2.1.7 - Longitudinal frames and subframes */
     328          53 :     const int nCols =
     329          53 :         static_cast<int>(ceil(static_cast<double>(nEW) / ECRG_PIXELS));
     330             : 
     331             :     /************************************************************************/
     332             :     /*  Compute north-south constant                                        */
     333             :     /************************************************************************/
     334             :     /* MIL-PRF-89038 - 60.1.1 -  North-south. pixel constant */
     335          53 :     const int nNS_ADRG = CEIL_ROUND(nBCst_ADRG * (1e6 / nScale), 512) / 4;
     336          53 :     const int nNS_CADRG = NEAR_ROUND(nNS_ADRG / (150. / 100.), 256);
     337             :     /* MIL-PRF-32283 - D.2.1.1 - North-south. pixel constant and Frame
     338             :      * Width/Height */
     339          53 :     const int nNS = nNS_CADRG / 256 * 384;
     340             : 
     341             :     /************************************************************************/
     342             :     /*  Compute number of latitudinal frames and latitude of top of zone    */
     343             :     /************************************************************************/
     344          53 :     dfPixelYSize = 90.0 / nNS;
     345             : 
     346          53 :     const double dfFrameLatHeight = dfPixelYSize * ECRG_PIXELS;
     347             : 
     348             :     /* MIL-PRF-32283 - D.2.1.5 - Equatorward and poleward zone extents. */
     349          53 :     int nUpperZoneFrames =
     350          53 :         static_cast<int>(ceil(anZoneUpperLat[nAbsZone] / dfFrameLatHeight));
     351          53 :     int nBottomZoneFrames = static_cast<int>(
     352          53 :         floor(anZoneUpperLat[nAbsZone - 1] / dfFrameLatHeight));
     353          53 :     const int nRows = nUpperZoneFrames - nBottomZoneFrames;
     354             : 
     355             :     /* Not sure to really understand D.2.1.5.a. Testing needed */
     356          53 :     if (nZone < 0)
     357             :     {
     358           0 :         nUpperZoneFrames = -nBottomZoneFrames;
     359             :         /*nBottomZoneFrames = nUpperZoneFrames - nRows;*/
     360             :     }
     361             : 
     362          53 :     const double dfUpperZoneTopLat = dfFrameLatHeight * nUpperZoneFrames;
     363             : 
     364             :     /************************************************************************/
     365             :     /*  Compute coordinates of the frame in the zone                        */
     366             :     /************************************************************************/
     367             : 
     368             :     /* Converts the first 10 characters into a number from base 34 */
     369          53 :     const GIntBig nFrameNumber = GetFromBase34(pszFrameName, 10);
     370             : 
     371             :     /*  MIL-PRF-32283 - A.2.6.1 */
     372          53 :     const GIntBig nY = nFrameNumber / nCols;
     373          53 :     const GIntBig nX = nFrameNumber % nCols;
     374             : 
     375             :     /************************************************************************/
     376             :     /*  Compute extent of the frame                                         */
     377             :     /************************************************************************/
     378             : 
     379             :     /* The nY is counted from the bottom of the zone... Pfff */
     380          53 :     dfMaxY = dfUpperZoneTopLat - (nRows - 1 - nY) * dfFrameLatHeight;
     381          53 :     dfMinY = dfMaxY - dfFrameLatHeight;
     382             : 
     383          53 :     dfPixelXSize = 360.0 / nEW;
     384             : 
     385          53 :     const double dfFrameLongWidth = dfPixelXSize * ECRG_PIXELS;
     386          53 :     dfMinX = -180.0 + nX * dfFrameLongWidth;
     387          53 :     dfMaxX = dfMinX + dfFrameLongWidth;
     388             : 
     389             : #ifdef DEBUG_VERBOSE
     390             :     CPLDebug("ECRG",
     391             :              "Frame %s : minx=%.16g, maxy=%.16g, maxx=%.16g, miny=%.16g",
     392             :              pszFrameName, dfMinX, dfMaxY, dfMaxX, dfMinY);
     393             : #endif
     394          53 : }
     395             : 
     396             : /************************************************************************/
     397             : /*                          ECRGTOCSource                               */
     398             : /************************************************************************/
     399             : 
     400             : class ECRGTOCSource final : public VRTSimpleSource
     401             : {
     402             :     int m_nRasterXSize = 0;
     403             :     int m_nRasterYSize = 0;
     404             :     double m_dfMinX = 0;
     405             :     double m_dfMaxY = 0;
     406             :     double m_dfPixelXSize = 0;
     407             :     double m_dfPixelYSize = 0;
     408             : 
     409             :     bool ValidateOpenedBand(GDALRasterBand *) const override;
     410             : 
     411             :   public:
     412          57 :     ECRGTOCSource(const char *pszFilename, int nBandIn, int nRasterXSize,
     413             :                   int nRasterYSize, double dfDstXOff, double dfDstYOff,
     414             :                   double dfDstXSize, double dfDstYSize, double dfMinX,
     415             :                   double dfMaxY, double dfPixelXSize, double dfPixelYSize)
     416          57 :         : m_nRasterXSize(nRasterXSize), m_nRasterYSize(nRasterYSize),
     417             :           m_dfMinX(dfMinX), m_dfMaxY(dfMaxY), m_dfPixelXSize(dfPixelXSize),
     418          57 :           m_dfPixelYSize(dfPixelYSize)
     419             :     {
     420          57 :         SetSrcBand(pszFilename, nBandIn);
     421          57 :         SetSrcWindow(0, 0, nRasterXSize, nRasterYSize);
     422          57 :         SetDstWindow(dfDstXOff, dfDstYOff, dfDstXSize, dfDstYSize);
     423          57 :     }
     424             : };
     425             : 
     426             : /************************************************************************/
     427             : /*                       ValidateOpenedBand()                           */
     428             : /************************************************************************/
     429             : 
     430             : #define WARN_CHECK_DS(x)                                                       \
     431             :     do                                                                         \
     432             :     {                                                                          \
     433             :         if (!(x))                                                              \
     434             :         {                                                                      \
     435             :             CPLError(CE_Warning, CPLE_AppDefined,                              \
     436             :                      "For %s, assert '" #x "' failed",                         \
     437             :                      poSourceDS->GetDescription());                            \
     438             :             checkOK = false;                                                   \
     439             :         }                                                                      \
     440             :     } while (false)
     441             : 
     442          10 : bool ECRGTOCSource::ValidateOpenedBand(GDALRasterBand *poBand) const
     443             : {
     444          10 :     bool checkOK = true;
     445          10 :     auto poSourceDS = poBand->GetDataset();
     446           9 :     CPLAssert(poSourceDS);
     447             : 
     448           9 :     GDALGeoTransform l_gt;
     449           9 :     poSourceDS->GetGeoTransform(l_gt);
     450           9 :     WARN_CHECK_DS(fabs(l_gt[0] - m_dfMinX) < 1e-10);
     451          10 :     WARN_CHECK_DS(fabs(l_gt[3] - m_dfMaxY) < 1e-10);
     452          10 :     WARN_CHECK_DS(fabs(l_gt[1] - m_dfPixelXSize) < 1e-10);
     453          10 :     WARN_CHECK_DS(fabs(l_gt[5] - (-m_dfPixelYSize)) < 1e-10);
     454          10 :     WARN_CHECK_DS(l_gt[2] == 0 && l_gt[4] == 0);  // No rotation.
     455           9 :     WARN_CHECK_DS(poSourceDS->GetRasterCount() == 3);
     456           9 :     WARN_CHECK_DS(poSourceDS->GetRasterXSize() == m_nRasterXSize);
     457          10 :     WARN_CHECK_DS(poSourceDS->GetRasterYSize() == m_nRasterYSize);
     458          10 :     WARN_CHECK_DS(
     459             :         EQUAL(poSourceDS->GetProjectionRef(), SRS_WKT_WGS84_LAT_LONG));
     460          10 :     WARN_CHECK_DS(poSourceDS->GetRasterBand(1)->GetRasterDataType() ==
     461             :                   GDT_Byte);
     462          10 :     return checkOK;
     463             : }
     464             : 
     465             : /************************************************************************/
     466             : /*                           BuildFullName()                            */
     467             : /************************************************************************/
     468             : 
     469          53 : static std::string BuildFullName(const char *pszTOCFilename,
     470             :                                  const char *pszFramePath,
     471             :                                  const char *pszFrameName)
     472             : {
     473          53 :     char *pszPath = nullptr;
     474          53 :     if (pszFramePath[0] == '.' &&
     475           0 :         (pszFramePath[1] == '/' || pszFramePath[1] == '\\'))
     476           0 :         pszPath = CPLStrdup(pszFramePath + 2);
     477             :     else
     478          53 :         pszPath = CPLStrdup(pszFramePath);
     479         371 :     for (int i = 0; pszPath[i] != '\0'; i++)
     480             :     {
     481         318 :         if (pszPath[i] == '\\')
     482          53 :             pszPath[i] = '/';
     483             :     }
     484             :     const std::string osName =
     485         106 :         CPLFormFilenameSafe(pszPath, pszFrameName, nullptr);
     486          53 :     CPLFree(pszPath);
     487          53 :     pszPath = nullptr;
     488         106 :     std::string osTOCPath = CPLGetDirnameSafe(pszTOCFilename);
     489          53 :     const auto nPosFirstSlashInName = osName.find('/');
     490          53 :     if (nPosFirstSlashInName != std::string::npos)
     491             :     {
     492          53 :         if (osTOCPath.size() >= nPosFirstSlashInName + 1 &&
     493          53 :             (osTOCPath[osTOCPath.size() - (nPosFirstSlashInName + 1)] == '/' ||
     494          53 :              osTOCPath[osTOCPath.size() - (nPosFirstSlashInName + 1)] ==
     495         106 :                  '\\') &&
     496           0 :             strncmp(osTOCPath.c_str() + osTOCPath.size() - nPosFirstSlashInName,
     497             :                     osName.c_str(), nPosFirstSlashInName) == 0)
     498             :         {
     499           0 :             osTOCPath = CPLGetDirnameSafe(osTOCPath.c_str());
     500             :         }
     501             :     }
     502         106 :     return CPLProjectRelativeFilenameSafe(osTOCPath.c_str(), osName.c_str());
     503             : }
     504             : 
     505             : /************************************************************************/
     506             : /*                              Build()                                 */
     507             : /************************************************************************/
     508             : 
     509             : /* Builds a ECRGTOCSubDataset from the set of files of the toc entry */
     510          11 : GDALDataset *ECRGTOCSubDataset::Build(
     511             :     const char *pszProductTitle, const char *pszDiscId, int nScale,
     512             :     int nCountSubDataset, const char *pszTOCFilename,
     513             :     const std::vector<FrameDesc> &aosFrameDesc, double dfGlobalMinX,
     514             :     double dfGlobalMinY, double dfGlobalMaxX, double dfGlobalMaxY,
     515             :     double dfGlobalPixelXSize, double dfGlobalPixelYSize)
     516             : {
     517          11 :     GDALDriver *poDriver = GetGDALDriverManager()->GetDriverByName("VRT");
     518          11 :     if (poDriver == nullptr)
     519           0 :         return nullptr;
     520             : 
     521          11 :     const int nSizeX = static_cast<int>(
     522          11 :         (dfGlobalMaxX - dfGlobalMinX) / dfGlobalPixelXSize + 0.5);
     523          11 :     const int nSizeY = static_cast<int>(
     524          11 :         (dfGlobalMaxY - dfGlobalMinY) / dfGlobalPixelYSize + 0.5);
     525             : 
     526             :     /* ------------------------------------ */
     527             :     /* Create the VRT with the overall size */
     528             :     /* ------------------------------------ */
     529          11 :     ECRGTOCSubDataset *poVirtualDS = new ECRGTOCSubDataset(nSizeX, nSizeY);
     530             : 
     531          11 :     poVirtualDS->SetProjection(SRS_WKT_WGS84_LAT_LONG);
     532             : 
     533             :     GDALGeoTransform gt{
     534             :         dfGlobalMinX,       dfGlobalPixelXSize, 0, dfGlobalMaxY, 0,
     535          11 :         -dfGlobalPixelYSize};
     536          11 :     poVirtualDS->SetGeoTransform(gt);
     537             : 
     538          44 :     for (int i = 0; i < 3; i++)
     539             :     {
     540          33 :         poVirtualDS->AddBand(GDT_Byte, nullptr);
     541          33 :         GDALRasterBand *poBand = poVirtualDS->GetRasterBand(i + 1);
     542          33 :         poBand->SetColorInterpretation(
     543          33 :             static_cast<GDALColorInterp>(GCI_RedBand + i));
     544             :     }
     545             : 
     546          11 :     poVirtualDS->SetDescription(pszTOCFilename);
     547             : 
     548          11 :     poVirtualDS->SetMetadataItem("PRODUCT_TITLE", pszProductTitle);
     549          11 :     poVirtualDS->SetMetadataItem("DISC_ID", pszDiscId);
     550          11 :     if (nScale != -1)
     551          11 :         poVirtualDS->SetMetadataItem("SCALE", CPLString().Printf("%d", nScale));
     552             : 
     553             :     /* -------------------------------------------------------------------- */
     554             :     /*      Check for overviews.                                            */
     555             :     /* -------------------------------------------------------------------- */
     556             : 
     557          11 :     poVirtualDS->oOvManager.Initialize(
     558             :         poVirtualDS,
     559          22 :         CPLString().Printf("%s.%d", pszTOCFilename, nCountSubDataset));
     560             : 
     561          11 :     poVirtualDS->papszFileList = poVirtualDS->GDALDataset::GetFileList();
     562             : 
     563             :     // Rather hacky... Force GDAL_FORCE_CACHING=NO so that the
     564             :     // GDALProxyPoolRasterBand do not use the GDALRasterBand::IRasterIO()
     565             :     // default implementation, which would rely on the block size of
     566             :     // GDALProxyPoolRasterBand, which we don't know...
     567          11 :     CPLConfigOptionSetter oSetter("GDAL_FORCE_CACHING", "NO", false);
     568             : 
     569          30 :     for (int i = 0; i < static_cast<int>(aosFrameDesc.size()); i++)
     570             :     {
     571             :         const std::string osName = BuildFullName(
     572          38 :             pszTOCFilename, aosFrameDesc[i].pszPath, aosFrameDesc[i].pszName);
     573             : 
     574          19 :         double dfMinX = 0.0;
     575          19 :         double dfMaxX = 0.0;
     576          19 :         double dfMinY = 0.0;
     577          19 :         double dfMaxY = 0.0;
     578          19 :         double dfPixelXSize = 0.0;
     579          19 :         double dfPixelYSize = 0.0;
     580          19 :         ::GetExtent(aosFrameDesc[i].pszName, aosFrameDesc[i].nScale,
     581          19 :                     aosFrameDesc[i].nZone, dfMinX, dfMaxX, dfMinY, dfMaxY,
     582             :                     dfPixelXSize, dfPixelYSize);
     583             : 
     584          19 :         const int nFrameXSize =
     585          19 :             static_cast<int>((dfMaxX - dfMinX) / dfPixelXSize + 0.5);
     586          19 :         const int nFrameYSize =
     587          19 :             static_cast<int>((dfMaxY - dfMinY) / dfPixelYSize + 0.5);
     588             : 
     589          19 :         poVirtualDS->papszFileList =
     590          19 :             CSLAddString(poVirtualDS->papszFileList, osName.c_str());
     591             : 
     592          76 :         for (int j = 0; j < 3; j++)
     593             :         {
     594             :             VRTSourcedRasterBand *poBand =
     595          57 :                 cpl::down_cast<VRTSourcedRasterBand *>(
     596             :                     poVirtualDS->GetRasterBand(j + 1));
     597             :             /* Place the raster band at the right position in the VRT */
     598             :             auto poSource = new ECRGTOCSource(
     599          57 :                 osName.c_str(), j + 1, nFrameXSize, nFrameYSize,
     600          57 :                 static_cast<int>((dfMinX - dfGlobalMinX) / dfGlobalPixelXSize +
     601             :                                  0.5),
     602          57 :                 static_cast<int>((dfGlobalMaxY - dfMaxY) / dfGlobalPixelYSize +
     603             :                                  0.5),
     604          57 :                 static_cast<int>((dfMaxX - dfMinX) / dfGlobalPixelXSize + 0.5),
     605          57 :                 static_cast<int>((dfMaxY - dfMinY) / dfGlobalPixelYSize + 0.5),
     606          57 :                 dfMinX, dfMaxY, dfPixelXSize, dfPixelYSize);
     607          57 :             poBand->AddSource(poSource);
     608             :         }
     609             :     }
     610             : 
     611          11 :     poVirtualDS->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
     612             : 
     613          11 :     return poVirtualDS;
     614             : }
     615             : 
     616             : /************************************************************************/
     617             : /*                             Build()                                  */
     618             : /************************************************************************/
     619             : 
     620          21 : GDALDataset *ECRGTOCDataset::Build(const char *pszTOCFilename,
     621             :                                    CPLXMLNode *psXML,
     622             :                                    const std::string &osProduct,
     623             :                                    const std::string &osDiscId,
     624             :                                    const std::string &osScale,
     625             :                                    const char *pszOpenInfoFilename)
     626             : {
     627          21 :     CPLXMLNode *psTOC = CPLGetXMLNode(psXML, "=Table_of_Contents");
     628          21 :     if (psTOC == nullptr)
     629             :     {
     630           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     631             :                  "Cannot find Table_of_Contents element");
     632           0 :         return nullptr;
     633             :     }
     634             : 
     635          21 :     double dfGlobalMinX = 0.0;
     636          21 :     double dfGlobalMinY = 0.0;
     637          21 :     double dfGlobalMaxX = 0.0;
     638          21 :     double dfGlobalMaxY = 0.0;
     639          21 :     double dfGlobalPixelXSize = 0.0;
     640          21 :     double dfGlobalPixelYSize = 0.0;
     641          21 :     bool bGlobalExtentValid = false;
     642             : 
     643          21 :     ECRGTOCDataset *poDS = new ECRGTOCDataset();
     644          21 :     int nSubDatasets = 0;
     645             : 
     646          21 :     int bLookForSubDataset = !osProduct.empty() && !osDiscId.empty();
     647             : 
     648          21 :     int nCountSubDataset = 0;
     649             : 
     650          21 :     poDS->SetDescription(pszOpenInfoFilename);
     651          21 :     poDS->papszFileList = poDS->GDALDataset::GetFileList();
     652             : 
     653          62 :     for (CPLXMLNode *psIter1 = psTOC->psChild; psIter1 != nullptr;
     654          41 :          psIter1 = psIter1->psNext)
     655             :     {
     656          52 :         if (!(psIter1->eType == CXT_Element && psIter1->pszValue != nullptr &&
     657          52 :               strcmp(psIter1->pszValue, "product") == 0))
     658          31 :             continue;
     659             : 
     660             :         const char *pszProductTitle =
     661          21 :             CPLGetXMLValue(psIter1, "product_title", nullptr);
     662          21 :         if (pszProductTitle == nullptr)
     663             :         {
     664           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     665             :                      "Cannot find product_title attribute");
     666           0 :             continue;
     667             :         }
     668             : 
     669          35 :         if (bLookForSubDataset &&
     670          35 :             strcmp(LaunderString(pszProductTitle), osProduct.c_str()) != 0)
     671           1 :             continue;
     672             : 
     673          51 :         for (CPLXMLNode *psIter2 = psIter1->psChild; psIter2 != nullptr;
     674          31 :              psIter2 = psIter2->psNext)
     675             :         {
     676          42 :             if (!(psIter2->eType == CXT_Element &&
     677          22 :                   psIter2->pszValue != nullptr &&
     678          22 :                   strcmp(psIter2->pszValue, "disc") == 0))
     679          20 :                 continue;
     680             : 
     681          22 :             const char *pszDiscId = CPLGetXMLValue(psIter2, "id", nullptr);
     682          22 :             if (pszDiscId == nullptr)
     683             :             {
     684           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     685             :                          "Cannot find id attribute");
     686           0 :                 continue;
     687             :             }
     688             : 
     689          36 :             if (bLookForSubDataset &&
     690          36 :                 strcmp(LaunderString(pszDiscId), osDiscId.c_str()) != 0)
     691           2 :                 continue;
     692             : 
     693          20 :             CPLXMLNode *psFrameList = CPLGetXMLNode(psIter2, "frame_list");
     694          20 :             if (psFrameList == nullptr)
     695             :             {
     696           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     697             :                          "Cannot find frame_list element");
     698           0 :                 continue;
     699             :             }
     700             : 
     701          51 :             for (CPLXMLNode *psIter3 = psFrameList->psChild; psIter3 != nullptr;
     702          31 :                  psIter3 = psIter3->psNext)
     703             :             {
     704          42 :                 if (!(psIter3->eType == CXT_Element &&
     705          22 :                       psIter3->pszValue != nullptr &&
     706          22 :                       strcmp(psIter3->pszValue, "scale") == 0))
     707          22 :                     continue;
     708             : 
     709          22 :                 const char *pszSize = CPLGetXMLValue(psIter3, "size", nullptr);
     710          22 :                 if (pszSize == nullptr)
     711             :                 {
     712           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
     713             :                              "Cannot find size attribute");
     714           0 :                     continue;
     715             :                 }
     716             : 
     717          22 :                 int nScale = GetScaleFromString(pszSize);
     718          22 :                 if (nScale <= 0)
     719             :                 {
     720           0 :                     CPLError(CE_Warning, CPLE_AppDefined, "Invalid scale %s",
     721             :                              pszSize);
     722           0 :                     continue;
     723             :                 }
     724             : 
     725          22 :                 if (bLookForSubDataset)
     726             :                 {
     727          13 :                     if (!osScale.empty())
     728             :                     {
     729          12 :                         if (strcmp(LaunderString(pszSize), osScale.c_str()) !=
     730             :                             0)
     731             :                         {
     732           2 :                             continue;
     733             :                         }
     734             :                     }
     735             :                     else
     736             :                     {
     737           1 :                         int nCountScales = 0;
     738           1 :                         for (CPLXMLNode *psIter4 = psFrameList->psChild;
     739           3 :                              psIter4 != nullptr; psIter4 = psIter4->psNext)
     740             :                         {
     741           2 :                             if (!(psIter4->eType == CXT_Element &&
     742           1 :                                   psIter4->pszValue != nullptr &&
     743           1 :                                   strcmp(psIter4->pszValue, "scale") == 0))
     744           1 :                                 continue;
     745           1 :                             nCountScales++;
     746             :                         }
     747           1 :                         if (nCountScales > 1)
     748             :                         {
     749           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
     750             :                                      "Scale should be mentioned in "
     751             :                                      "subdatasets syntax since this disk "
     752             :                                      "contains several scales");
     753           0 :                             delete poDS;
     754          11 :                             return nullptr;
     755             :                         }
     756             :                     }
     757             :                 }
     758             : 
     759          20 :                 nCountSubDataset++;
     760             : 
     761          20 :                 std::vector<FrameDesc> aosFrameDesc;
     762          20 :                 int nValidFrames = 0;
     763             : 
     764          74 :                 for (CPLXMLNode *psIter4 = psIter3->psChild; psIter4 != nullptr;
     765          54 :                      psIter4 = psIter4->psNext)
     766             :                 {
     767          54 :                     if (!(psIter4->eType == CXT_Element &&
     768          34 :                           psIter4->pszValue != nullptr &&
     769          34 :                           strcmp(psIter4->pszValue, "frame") == 0))
     770          20 :                         continue;
     771             : 
     772             :                     const char *pszFrameName =
     773          34 :                         CPLGetXMLValue(psIter4, "name", nullptr);
     774          34 :                     if (pszFrameName == nullptr)
     775             :                     {
     776           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     777             :                                  "Cannot find name element");
     778           0 :                         continue;
     779             :                     }
     780             : 
     781          34 :                     if (strlen(pszFrameName) != 18)
     782             :                     {
     783           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     784             :                                  "Invalid value for name element : %s",
     785             :                                  pszFrameName);
     786           0 :                         continue;
     787             :                     }
     788             : 
     789             :                     const char *pszFramePath =
     790          34 :                         CPLGetXMLValue(psIter4, "frame_path", nullptr);
     791          34 :                     if (pszFramePath == nullptr)
     792             :                     {
     793           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     794             :                                  "Cannot find frame_path element");
     795           0 :                         continue;
     796             :                     }
     797             : 
     798             :                     const char *pszFrameZone =
     799          34 :                         CPLGetXMLValue(psIter4, "frame_zone", nullptr);
     800          34 :                     if (pszFrameZone == nullptr)
     801             :                     {
     802           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     803             :                                  "Cannot find frame_zone element");
     804           0 :                         continue;
     805             :                     }
     806          34 :                     if (strlen(pszFrameZone) != 1)
     807             :                     {
     808           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     809             :                                  "Invalid value for frame_zone element : %s",
     810             :                                  pszFrameZone);
     811           0 :                         continue;
     812             :                     }
     813          34 :                     char chZone = pszFrameZone[0];
     814          34 :                     int nZone = 0;
     815          34 :                     if (chZone >= '1' && chZone <= '9')
     816          34 :                         nZone = chZone - '0';
     817           0 :                     else if (chZone >= 'a' && chZone <= 'h')
     818           0 :                         nZone = -(chZone - 'a' + 1);
     819           0 :                     else if (chZone >= 'A' && chZone <= 'H')
     820           0 :                         nZone = -(chZone - 'A' + 1);
     821           0 :                     else if (chZone == 'j' || chZone == 'J')
     822           0 :                         nZone = -9;
     823             :                     else
     824             :                     {
     825           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     826             :                                  "Invalid value for frame_zone element : %s",
     827             :                                  pszFrameZone);
     828           0 :                         continue;
     829             :                     }
     830          34 :                     if (nZone == 9 || nZone == -9)
     831             :                     {
     832           0 :                         CPLError(
     833             :                             CE_Warning, CPLE_AppDefined,
     834             :                             "Polar zones unhandled by current implementation");
     835           0 :                         continue;
     836             :                     }
     837             : 
     838          34 :                     double dfMinX = 0.0;
     839          34 :                     double dfMaxX = 0.0;
     840          34 :                     double dfMinY = 0.0;
     841          34 :                     double dfMaxY = 0.0;
     842          34 :                     double dfPixelXSize = 0.0;
     843          34 :                     double dfPixelYSize = 0.0;
     844          34 :                     ::GetExtent(pszFrameName, nScale, nZone, dfMinX, dfMaxX,
     845             :                                 dfMinY, dfMaxY, dfPixelXSize, dfPixelYSize);
     846             : 
     847          34 :                     nValidFrames++;
     848             : 
     849             :                     const std::string osFullName = BuildFullName(
     850          68 :                         pszTOCFilename, pszFramePath, pszFrameName);
     851          34 :                     poDS->papszFileList =
     852          34 :                         CSLAddString(poDS->papszFileList, osFullName.c_str());
     853             : 
     854          34 :                     if (!bGlobalExtentValid)
     855             :                     {
     856          18 :                         dfGlobalMinX = dfMinX;
     857          18 :                         dfGlobalMinY = dfMinY;
     858          18 :                         dfGlobalMaxX = dfMaxX;
     859          18 :                         dfGlobalMaxY = dfMaxY;
     860          18 :                         dfGlobalPixelXSize = dfPixelXSize;
     861          18 :                         dfGlobalPixelYSize = dfPixelYSize;
     862          18 :                         bGlobalExtentValid = true;
     863             :                     }
     864             :                     else
     865             :                     {
     866          16 :                         if (dfMinX < dfGlobalMinX)
     867           0 :                             dfGlobalMinX = dfMinX;
     868          16 :                         if (dfMinY < dfGlobalMinY)
     869           0 :                             dfGlobalMinY = dfMinY;
     870          16 :                         if (dfMaxX > dfGlobalMaxX)
     871          15 :                             dfGlobalMaxX = dfMaxX;
     872          16 :                         if (dfMaxY > dfGlobalMaxY)
     873           1 :                             dfGlobalMaxY = dfMaxY;
     874          16 :                         if (dfPixelXSize < dfGlobalPixelXSize)
     875           0 :                             dfGlobalPixelXSize = dfPixelXSize;
     876          16 :                         if (dfPixelYSize < dfGlobalPixelYSize)
     877           0 :                             dfGlobalPixelYSize = dfPixelYSize;
     878             :                     }
     879             : 
     880          34 :                     nValidFrames++;
     881             : 
     882          34 :                     if (bLookForSubDataset)
     883             :                     {
     884             :                         FrameDesc frameDesc;
     885          19 :                         frameDesc.pszName = pszFrameName;
     886          19 :                         frameDesc.pszPath = pszFramePath;
     887          19 :                         frameDesc.nScale = nScale;
     888          19 :                         frameDesc.nZone = nZone;
     889          19 :                         aosFrameDesc.push_back(frameDesc);
     890             :                     }
     891             :                 }
     892             : 
     893          20 :                 if (bLookForSubDataset)
     894             :                 {
     895          11 :                     delete poDS;
     896          11 :                     if (nValidFrames == 0)
     897           0 :                         return nullptr;
     898          11 :                     return ECRGTOCSubDataset::Build(
     899             :                         pszProductTitle, pszDiscId, nScale, nCountSubDataset,
     900             :                         pszTOCFilename, aosFrameDesc, dfGlobalMinX,
     901             :                         dfGlobalMinY, dfGlobalMaxX, dfGlobalMaxY,
     902          11 :                         dfGlobalPixelXSize, dfGlobalPixelYSize);
     903             :                 }
     904             : 
     905           9 :                 if (nValidFrames)
     906             :                 {
     907           9 :                     poDS->AddSubDataset(pszOpenInfoFilename, pszProductTitle,
     908             :                                         pszDiscId, pszSize);
     909           9 :                     nSubDatasets++;
     910             :                 }
     911             :             }
     912             :         }
     913             :     }
     914             : 
     915          10 :     if (!bGlobalExtentValid)
     916             :     {
     917           3 :         delete poDS;
     918           3 :         return nullptr;
     919             :     }
     920             : 
     921           7 :     if (nSubDatasets == 1)
     922             :     {
     923          12 :         const char *pszSubDatasetName = CSLFetchNameValue(
     924           6 :             poDS->GetMetadata("SUBDATASETS"), "SUBDATASET_1_NAME");
     925           6 :         GDALOpenInfo oOpenInfo(pszSubDatasetName, GA_ReadOnly);
     926           6 :         delete poDS;
     927           6 :         GDALDataset *poRetDS = Open(&oOpenInfo);
     928           6 :         if (poRetDS)
     929           6 :             poRetDS->SetDescription(pszOpenInfoFilename);
     930           6 :         return poRetDS;
     931             :     }
     932             : 
     933           1 :     poDS->m_gt[0] = dfGlobalMinX;
     934           1 :     poDS->m_gt[1] = dfGlobalPixelXSize;
     935           1 :     poDS->m_gt[2] = 0.0;
     936           1 :     poDS->m_gt[3] = dfGlobalMaxY;
     937           1 :     poDS->m_gt[4] = 0.0;
     938           1 :     poDS->m_gt[5] = -dfGlobalPixelYSize;
     939             : 
     940           1 :     poDS->nRasterXSize = static_cast<int>(0.5 + (dfGlobalMaxX - dfGlobalMinX) /
     941             :                                                     dfGlobalPixelXSize);
     942           1 :     poDS->nRasterYSize = static_cast<int>(0.5 + (dfGlobalMaxY - dfGlobalMinY) /
     943             :                                                     dfGlobalPixelYSize);
     944             : 
     945             :     /* -------------------------------------------------------------------- */
     946             :     /*      Initialize any PAM information.                                 */
     947             :     /* -------------------------------------------------------------------- */
     948           1 :     poDS->TryLoadXML();
     949             : 
     950           1 :     return poDS;
     951             : }
     952             : 
     953             : /************************************************************************/
     954             : /*                                Open()                                */
     955             : /************************************************************************/
     956             : 
     957          30 : GDALDataset *ECRGTOCDataset::Open(GDALOpenInfo *poOpenInfo)
     958             : 
     959             : {
     960          30 :     if (!ECRGTOCDriverIdentify(poOpenInfo))
     961           0 :         return nullptr;
     962             : 
     963          30 :     const char *pszFilename = poOpenInfo->pszFilename;
     964          60 :     CPLString osFilename;
     965          60 :     CPLString osProduct, osDiscId, osScale;
     966             : 
     967          30 :     if (STARTS_WITH_CI(pszFilename, "ECRG_TOC_ENTRY:"))
     968             :     {
     969          23 :         pszFilename += strlen("ECRG_TOC_ENTRY:");
     970             : 
     971             :         /* PRODUCT:DISK:SCALE:FILENAME (or PRODUCT:DISK:FILENAME historically)
     972             :          */
     973             :         /* with FILENAME potentially C:\BLA... */
     974          23 :         char **papszTokens = CSLTokenizeString2(pszFilename, ":", 0);
     975          23 :         int nTokens = CSLCount(papszTokens);
     976          23 :         if (nTokens != 3 && nTokens != 4 && nTokens != 5)
     977             :         {
     978           4 :             CSLDestroy(papszTokens);
     979           4 :             return nullptr;
     980             :         }
     981             : 
     982          19 :         osProduct = papszTokens[0];
     983          19 :         osDiscId = papszTokens[1];
     984             : 
     985          19 :         if (nTokens == 3)
     986           5 :             osFilename = papszTokens[2];
     987          14 :         else if (nTokens == 4)
     988             :         {
     989          13 :             if (strlen(papszTokens[2]) == 1 &&
     990           1 :                 (papszTokens[3][0] == '\\' || papszTokens[3][0] == '/'))
     991             :             {
     992           1 :                 osFilename = papszTokens[2];
     993           1 :                 osFilename += ":";
     994           1 :                 osFilename += papszTokens[3];
     995             :             }
     996             :             else
     997             :             {
     998          12 :                 osScale = papszTokens[2];
     999          12 :                 osFilename = papszTokens[3];
    1000             :             }
    1001             :         }
    1002           1 :         else if (nTokens == 5 && strlen(papszTokens[3]) == 1 &&
    1003           1 :                  (papszTokens[4][0] == '\\' || papszTokens[4][0] == '/'))
    1004             :         {
    1005           1 :             osScale = papszTokens[2];
    1006           1 :             osFilename = papszTokens[3];
    1007           1 :             osFilename += ":";
    1008           1 :             osFilename += papszTokens[4];
    1009             :         }
    1010             :         else
    1011             :         {
    1012           0 :             CSLDestroy(papszTokens);
    1013           0 :             return nullptr;
    1014             :         }
    1015             : 
    1016          19 :         CSLDestroy(papszTokens);
    1017          19 :         pszFilename = osFilename.c_str();
    1018             :     }
    1019             : 
    1020             :     /* -------------------------------------------------------------------- */
    1021             :     /*      Parse the XML file                                              */
    1022             :     /* -------------------------------------------------------------------- */
    1023          26 :     CPLXMLNode *psXML = CPLParseXMLFile(pszFilename);
    1024          26 :     if (psXML == nullptr)
    1025             :     {
    1026           5 :         return nullptr;
    1027             :     }
    1028             : 
    1029          42 :     GDALDataset *poDS = Build(pszFilename, psXML, osProduct, osDiscId, osScale,
    1030          21 :                               poOpenInfo->pszFilename);
    1031          21 :     CPLDestroyXMLNode(psXML);
    1032             : 
    1033          21 :     if (poDS && poOpenInfo->eAccess == GA_Update)
    1034             :     {
    1035           0 :         ReportUpdateNotSupportedByDriver("ECRGTOC");
    1036           0 :         delete poDS;
    1037           0 :         return nullptr;
    1038             :     }
    1039             : 
    1040          21 :     return poDS;
    1041             : }
    1042             : 
    1043             : /************************************************************************/
    1044             : /*                         GDALRegister_ECRGTOC()                       */
    1045             : /************************************************************************/
    1046             : 
    1047        1961 : void GDALRegister_ECRGTOC()
    1048             : 
    1049             : {
    1050        1961 :     if (GDALGetDriverByName(ECRGTOC_DRIVER_NAME) != nullptr)
    1051         283 :         return;
    1052             : 
    1053        1678 :     GDALDriver *poDriver = new GDALDriver();
    1054        1678 :     ECRGTOCDriverSetCommonMetadata(poDriver);
    1055             : 
    1056        1678 :     poDriver->pfnOpen = ECRGTOCDataset::Open;
    1057             : 
    1058        1678 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1059             : }

Generated by: LCOV version 1.14