LCOV - code coverage report
Current view: top level - frmts/nitf - ecrgtocdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 398 465 85.6 %
Date: 2025-09-10 17:48:50 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 :     ~ECRGTOCDataset() override
      80          21 :     {
      81          21 :         CSLDestroy(papszSubDatasets);
      82          21 :         CSLDestroy(papszFileList);
      83          42 :     }
      84             : 
      85             :     char **GetMetadata(const char *pszDomain = "") override;
      86             : 
      87           1 :     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 :     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 :     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          10 :     CPLAssert(poSourceDS);
     447             : 
     448          10 :     GDALGeoTransform l_gt;
     449          10 :     poSourceDS->GetGeoTransform(l_gt);
     450          10 :     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          10 :     WARN_CHECK_DS(poSourceDS->GetRasterCount() == 3);
     456          10 :     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          22 :     auto poVirtualDS = std::make_unique<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          22 :     poVirtualDS->oOvManager.Initialize(
     558          11 :         poVirtualDS.get(),
     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          22 :     CPLConfigOptionSetter oSetter("GDAL_FORCE_CACHING", "NO", false);
     568             : 
     569          30 :     for (int i = 0; i < static_cast<int>(aosFrameDesc.size()); i++)
     570             :     {
     571          19 :         if (CPLHasPathTraversal(aosFrameDesc[i].pszName))
     572             :         {
     573           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     574           0 :                      "Path traversal detected in %s", aosFrameDesc[i].pszName);
     575           0 :             return nullptr;
     576             :         }
     577             :         const std::string osName = BuildFullName(
     578          38 :             pszTOCFilename, aosFrameDesc[i].pszPath, aosFrameDesc[i].pszName);
     579             : 
     580          19 :         double dfMinX = 0.0;
     581          19 :         double dfMaxX = 0.0;
     582          19 :         double dfMinY = 0.0;
     583          19 :         double dfMaxY = 0.0;
     584          19 :         double dfPixelXSize = 0.0;
     585          19 :         double dfPixelYSize = 0.0;
     586          19 :         ::GetExtent(aosFrameDesc[i].pszName, aosFrameDesc[i].nScale,
     587          19 :                     aosFrameDesc[i].nZone, dfMinX, dfMaxX, dfMinY, dfMaxY,
     588             :                     dfPixelXSize, dfPixelYSize);
     589             : 
     590          19 :         const int nFrameXSize =
     591          19 :             static_cast<int>((dfMaxX - dfMinX) / dfPixelXSize + 0.5);
     592          19 :         const int nFrameYSize =
     593          19 :             static_cast<int>((dfMaxY - dfMinY) / dfPixelYSize + 0.5);
     594             : 
     595          38 :         poVirtualDS->papszFileList =
     596          19 :             CSLAddString(poVirtualDS->papszFileList, osName.c_str());
     597             : 
     598          76 :         for (int j = 0; j < 3; j++)
     599             :         {
     600             :             VRTSourcedRasterBand *poBand =
     601          57 :                 cpl::down_cast<VRTSourcedRasterBand *>(
     602          57 :                     poVirtualDS->GetRasterBand(j + 1));
     603             :             /* Place the raster band at the right position in the VRT */
     604             :             auto poSource = new ECRGTOCSource(
     605          57 :                 osName.c_str(), j + 1, nFrameXSize, nFrameYSize,
     606          57 :                 static_cast<int>((dfMinX - dfGlobalMinX) / dfGlobalPixelXSize +
     607             :                                  0.5),
     608          57 :                 static_cast<int>((dfGlobalMaxY - dfMaxY) / dfGlobalPixelYSize +
     609             :                                  0.5),
     610          57 :                 static_cast<int>((dfMaxX - dfMinX) / dfGlobalPixelXSize + 0.5),
     611          57 :                 static_cast<int>((dfMaxY - dfMinY) / dfGlobalPixelYSize + 0.5),
     612          57 :                 dfMinX, dfMaxY, dfPixelXSize, dfPixelYSize);
     613          57 :             poBand->AddSource(poSource);
     614             :         }
     615             :     }
     616             : 
     617          11 :     poVirtualDS->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
     618             : 
     619          11 :     return poVirtualDS.release();
     620             : }
     621             : 
     622             : /************************************************************************/
     623             : /*                             Build()                                  */
     624             : /************************************************************************/
     625             : 
     626          21 : GDALDataset *ECRGTOCDataset::Build(const char *pszTOCFilename,
     627             :                                    CPLXMLNode *psXML,
     628             :                                    const std::string &osProduct,
     629             :                                    const std::string &osDiscId,
     630             :                                    const std::string &osScale,
     631             :                                    const char *pszOpenInfoFilename)
     632             : {
     633          21 :     CPLXMLNode *psTOC = CPLGetXMLNode(psXML, "=Table_of_Contents");
     634          21 :     if (psTOC == nullptr)
     635             :     {
     636           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     637             :                  "Cannot find Table_of_Contents element");
     638           0 :         return nullptr;
     639             :     }
     640             : 
     641          21 :     double dfGlobalMinX = 0.0;
     642          21 :     double dfGlobalMinY = 0.0;
     643          21 :     double dfGlobalMaxX = 0.0;
     644          21 :     double dfGlobalMaxY = 0.0;
     645          21 :     double dfGlobalPixelXSize = 0.0;
     646          21 :     double dfGlobalPixelYSize = 0.0;
     647          21 :     bool bGlobalExtentValid = false;
     648             : 
     649          42 :     auto poDS = std::make_unique<ECRGTOCDataset>();
     650          21 :     int nSubDatasets = 0;
     651             : 
     652          21 :     int bLookForSubDataset = !osProduct.empty() && !osDiscId.empty();
     653             : 
     654          21 :     int nCountSubDataset = 0;
     655             : 
     656          21 :     poDS->SetDescription(pszOpenInfoFilename);
     657          21 :     poDS->papszFileList = poDS->GDALDataset::GetFileList();
     658             : 
     659          62 :     for (CPLXMLNode *psIter1 = psTOC->psChild; psIter1 != nullptr;
     660          41 :          psIter1 = psIter1->psNext)
     661             :     {
     662          52 :         if (!(psIter1->eType == CXT_Element && psIter1->pszValue != nullptr &&
     663          52 :               strcmp(psIter1->pszValue, "product") == 0))
     664          31 :             continue;
     665             : 
     666             :         const char *pszProductTitle =
     667          21 :             CPLGetXMLValue(psIter1, "product_title", nullptr);
     668          21 :         if (pszProductTitle == nullptr)
     669             :         {
     670           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     671             :                      "Cannot find product_title attribute");
     672           0 :             continue;
     673             :         }
     674             : 
     675          35 :         if (bLookForSubDataset &&
     676          35 :             strcmp(LaunderString(pszProductTitle), osProduct.c_str()) != 0)
     677           1 :             continue;
     678             : 
     679          51 :         for (CPLXMLNode *psIter2 = psIter1->psChild; psIter2 != nullptr;
     680          31 :              psIter2 = psIter2->psNext)
     681             :         {
     682          42 :             if (!(psIter2->eType == CXT_Element &&
     683          22 :                   psIter2->pszValue != nullptr &&
     684          22 :                   strcmp(psIter2->pszValue, "disc") == 0))
     685          20 :                 continue;
     686             : 
     687          22 :             const char *pszDiscId = CPLGetXMLValue(psIter2, "id", nullptr);
     688          22 :             if (pszDiscId == nullptr)
     689             :             {
     690           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     691             :                          "Cannot find id attribute");
     692           0 :                 continue;
     693             :             }
     694             : 
     695          36 :             if (bLookForSubDataset &&
     696          36 :                 strcmp(LaunderString(pszDiscId), osDiscId.c_str()) != 0)
     697           2 :                 continue;
     698             : 
     699          20 :             CPLXMLNode *psFrameList = CPLGetXMLNode(psIter2, "frame_list");
     700          20 :             if (psFrameList == nullptr)
     701             :             {
     702           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     703             :                          "Cannot find frame_list element");
     704           0 :                 continue;
     705             :             }
     706             : 
     707          51 :             for (CPLXMLNode *psIter3 = psFrameList->psChild; psIter3 != nullptr;
     708          31 :                  psIter3 = psIter3->psNext)
     709             :             {
     710          42 :                 if (!(psIter3->eType == CXT_Element &&
     711          22 :                       psIter3->pszValue != nullptr &&
     712          22 :                       strcmp(psIter3->pszValue, "scale") == 0))
     713          22 :                     continue;
     714             : 
     715          22 :                 const char *pszSize = CPLGetXMLValue(psIter3, "size", nullptr);
     716          22 :                 if (pszSize == nullptr)
     717             :                 {
     718           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
     719             :                              "Cannot find size attribute");
     720           0 :                     continue;
     721             :                 }
     722             : 
     723          22 :                 int nScale = GetScaleFromString(pszSize);
     724          22 :                 if (nScale <= 0)
     725             :                 {
     726           0 :                     CPLError(CE_Warning, CPLE_AppDefined, "Invalid scale %s",
     727             :                              pszSize);
     728           0 :                     continue;
     729             :                 }
     730             : 
     731          22 :                 if (bLookForSubDataset)
     732             :                 {
     733          13 :                     if (!osScale.empty())
     734             :                     {
     735          12 :                         if (strcmp(LaunderString(pszSize), osScale.c_str()) !=
     736             :                             0)
     737             :                         {
     738           2 :                             continue;
     739             :                         }
     740             :                     }
     741             :                     else
     742             :                     {
     743           1 :                         int nCountScales = 0;
     744           1 :                         for (CPLXMLNode *psIter4 = psFrameList->psChild;
     745           3 :                              psIter4 != nullptr; psIter4 = psIter4->psNext)
     746             :                         {
     747           2 :                             if (!(psIter4->eType == CXT_Element &&
     748           1 :                                   psIter4->pszValue != nullptr &&
     749           1 :                                   strcmp(psIter4->pszValue, "scale") == 0))
     750           1 :                                 continue;
     751           1 :                             nCountScales++;
     752             :                         }
     753           1 :                         if (nCountScales > 1)
     754             :                         {
     755           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
     756             :                                      "Scale should be mentioned in "
     757             :                                      "subdatasets syntax since this disk "
     758             :                                      "contains several scales");
     759          11 :                             return nullptr;
     760             :                         }
     761             :                     }
     762             :                 }
     763             : 
     764          20 :                 nCountSubDataset++;
     765             : 
     766          20 :                 std::vector<FrameDesc> aosFrameDesc;
     767          20 :                 int nValidFrames = 0;
     768             : 
     769          74 :                 for (CPLXMLNode *psIter4 = psIter3->psChild; psIter4 != nullptr;
     770          54 :                      psIter4 = psIter4->psNext)
     771             :                 {
     772          54 :                     if (!(psIter4->eType == CXT_Element &&
     773          34 :                           psIter4->pszValue != nullptr &&
     774          34 :                           strcmp(psIter4->pszValue, "frame") == 0))
     775          20 :                         continue;
     776             : 
     777             :                     const char *pszFrameName =
     778          34 :                         CPLGetXMLValue(psIter4, "name", nullptr);
     779          34 :                     if (pszFrameName == nullptr)
     780             :                     {
     781           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     782             :                                  "Cannot find name element");
     783           0 :                         continue;
     784             :                     }
     785             : 
     786          34 :                     if (strlen(pszFrameName) != 18)
     787             :                     {
     788           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     789             :                                  "Invalid value for name element : %s",
     790             :                                  pszFrameName);
     791           0 :                         continue;
     792             :                     }
     793             : 
     794             :                     const char *pszFramePath =
     795          34 :                         CPLGetXMLValue(psIter4, "frame_path", nullptr);
     796          34 :                     if (pszFramePath == nullptr)
     797             :                     {
     798           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     799             :                                  "Cannot find frame_path element");
     800           0 :                         continue;
     801             :                     }
     802             : 
     803             :                     const char *pszFrameZone =
     804          34 :                         CPLGetXMLValue(psIter4, "frame_zone", nullptr);
     805          34 :                     if (pszFrameZone == nullptr)
     806             :                     {
     807           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     808             :                                  "Cannot find frame_zone element");
     809           0 :                         continue;
     810             :                     }
     811          34 :                     if (strlen(pszFrameZone) != 1)
     812             :                     {
     813           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     814             :                                  "Invalid value for frame_zone element : %s",
     815             :                                  pszFrameZone);
     816           0 :                         continue;
     817             :                     }
     818          34 :                     char chZone = pszFrameZone[0];
     819          34 :                     int nZone = 0;
     820          34 :                     if (chZone >= '1' && chZone <= '9')
     821          34 :                         nZone = chZone - '0';
     822           0 :                     else if (chZone >= 'a' && chZone <= 'h')
     823           0 :                         nZone = -(chZone - 'a' + 1);
     824           0 :                     else if (chZone >= 'A' && chZone <= 'H')
     825           0 :                         nZone = -(chZone - 'A' + 1);
     826           0 :                     else if (chZone == 'j' || chZone == 'J')
     827           0 :                         nZone = -9;
     828             :                     else
     829             :                     {
     830           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     831             :                                  "Invalid value for frame_zone element : %s",
     832             :                                  pszFrameZone);
     833           0 :                         continue;
     834             :                     }
     835          34 :                     if (nZone == 9 || nZone == -9)
     836             :                     {
     837           0 :                         CPLError(
     838             :                             CE_Warning, CPLE_AppDefined,
     839             :                             "Polar zones unhandled by current implementation");
     840           0 :                         continue;
     841             :                     }
     842             : 
     843          34 :                     double dfMinX = 0.0;
     844          34 :                     double dfMaxX = 0.0;
     845          34 :                     double dfMinY = 0.0;
     846          34 :                     double dfMaxY = 0.0;
     847          34 :                     double dfPixelXSize = 0.0;
     848          34 :                     double dfPixelYSize = 0.0;
     849          34 :                     ::GetExtent(pszFrameName, nScale, nZone, dfMinX, dfMaxX,
     850             :                                 dfMinY, dfMaxY, dfPixelXSize, dfPixelYSize);
     851             : 
     852          34 :                     nValidFrames++;
     853             : 
     854          34 :                     if (CPLHasPathTraversal(pszFrameName))
     855             :                     {
     856           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
     857             :                                  "Path traversal detected in %s", pszFrameName);
     858           0 :                         return nullptr;
     859             :                     }
     860             :                     const std::string osFullName = BuildFullName(
     861          68 :                         pszTOCFilename, pszFramePath, pszFrameName);
     862          68 :                     poDS->papszFileList =
     863          34 :                         CSLAddString(poDS->papszFileList, osFullName.c_str());
     864             : 
     865          34 :                     if (!bGlobalExtentValid)
     866             :                     {
     867          18 :                         dfGlobalMinX = dfMinX;
     868          18 :                         dfGlobalMinY = dfMinY;
     869          18 :                         dfGlobalMaxX = dfMaxX;
     870          18 :                         dfGlobalMaxY = dfMaxY;
     871          18 :                         dfGlobalPixelXSize = dfPixelXSize;
     872          18 :                         dfGlobalPixelYSize = dfPixelYSize;
     873          18 :                         bGlobalExtentValid = true;
     874             :                     }
     875             :                     else
     876             :                     {
     877          16 :                         if (dfMinX < dfGlobalMinX)
     878           0 :                             dfGlobalMinX = dfMinX;
     879          16 :                         if (dfMinY < dfGlobalMinY)
     880           0 :                             dfGlobalMinY = dfMinY;
     881          16 :                         if (dfMaxX > dfGlobalMaxX)
     882          15 :                             dfGlobalMaxX = dfMaxX;
     883          16 :                         if (dfMaxY > dfGlobalMaxY)
     884           1 :                             dfGlobalMaxY = dfMaxY;
     885          16 :                         if (dfPixelXSize < dfGlobalPixelXSize)
     886           0 :                             dfGlobalPixelXSize = dfPixelXSize;
     887          16 :                         if (dfPixelYSize < dfGlobalPixelYSize)
     888           0 :                             dfGlobalPixelYSize = dfPixelYSize;
     889             :                     }
     890             : 
     891          34 :                     nValidFrames++;
     892             : 
     893          34 :                     if (bLookForSubDataset)
     894             :                     {
     895             :                         FrameDesc frameDesc;
     896          19 :                         frameDesc.pszName = pszFrameName;
     897          19 :                         frameDesc.pszPath = pszFramePath;
     898          19 :                         frameDesc.nScale = nScale;
     899          19 :                         frameDesc.nZone = nZone;
     900          19 :                         aosFrameDesc.push_back(frameDesc);
     901             :                     }
     902             :                 }
     903             : 
     904          20 :                 if (bLookForSubDataset)
     905             :                 {
     906          11 :                     if (nValidFrames == 0)
     907           0 :                         return nullptr;
     908          11 :                     return ECRGTOCSubDataset::Build(
     909             :                         pszProductTitle, pszDiscId, nScale, nCountSubDataset,
     910             :                         pszTOCFilename, aosFrameDesc, dfGlobalMinX,
     911             :                         dfGlobalMinY, dfGlobalMaxX, dfGlobalMaxY,
     912          11 :                         dfGlobalPixelXSize, dfGlobalPixelYSize);
     913             :                 }
     914             : 
     915           9 :                 if (nValidFrames)
     916             :                 {
     917           9 :                     poDS->AddSubDataset(pszOpenInfoFilename, pszProductTitle,
     918             :                                         pszDiscId, pszSize);
     919           9 :                     nSubDatasets++;
     920             :                 }
     921             :             }
     922             :         }
     923             :     }
     924             : 
     925          10 :     if (!bGlobalExtentValid)
     926             :     {
     927           3 :         return nullptr;
     928             :     }
     929             : 
     930           7 :     if (nSubDatasets == 1)
     931             :     {
     932          12 :         const char *pszSubDatasetName = CSLFetchNameValue(
     933           6 :             poDS->GetMetadata("SUBDATASETS"), "SUBDATASET_1_NAME");
     934           6 :         GDALOpenInfo oOpenInfo(pszSubDatasetName, GA_ReadOnly);
     935           6 :         poDS.reset();
     936           6 :         GDALDataset *poRetDS = Open(&oOpenInfo);
     937           6 :         if (poRetDS)
     938           6 :             poRetDS->SetDescription(pszOpenInfoFilename);
     939           6 :         return poRetDS;
     940             :     }
     941             : 
     942           1 :     poDS->m_gt[0] = dfGlobalMinX;
     943           1 :     poDS->m_gt[1] = dfGlobalPixelXSize;
     944           1 :     poDS->m_gt[2] = 0.0;
     945           1 :     poDS->m_gt[3] = dfGlobalMaxY;
     946           1 :     poDS->m_gt[4] = 0.0;
     947           1 :     poDS->m_gt[5] = -dfGlobalPixelYSize;
     948             : 
     949           1 :     poDS->nRasterXSize = static_cast<int>(0.5 + (dfGlobalMaxX - dfGlobalMinX) /
     950             :                                                     dfGlobalPixelXSize);
     951           1 :     poDS->nRasterYSize = static_cast<int>(0.5 + (dfGlobalMaxY - dfGlobalMinY) /
     952             :                                                     dfGlobalPixelYSize);
     953             : 
     954             :     /* -------------------------------------------------------------------- */
     955             :     /*      Initialize any PAM information.                                 */
     956             :     /* -------------------------------------------------------------------- */
     957           1 :     poDS->TryLoadXML();
     958             : 
     959           1 :     return poDS.release();
     960             : }
     961             : 
     962             : /************************************************************************/
     963             : /*                                Open()                                */
     964             : /************************************************************************/
     965             : 
     966          30 : GDALDataset *ECRGTOCDataset::Open(GDALOpenInfo *poOpenInfo)
     967             : 
     968             : {
     969          30 :     if (!ECRGTOCDriverIdentify(poOpenInfo))
     970           0 :         return nullptr;
     971             : 
     972          30 :     const char *pszFilename = poOpenInfo->pszFilename;
     973          60 :     CPLString osFilename;
     974          60 :     CPLString osProduct, osDiscId, osScale;
     975             : 
     976          30 :     if (STARTS_WITH_CI(pszFilename, "ECRG_TOC_ENTRY:"))
     977             :     {
     978          23 :         pszFilename += strlen("ECRG_TOC_ENTRY:");
     979             : 
     980             :         /* PRODUCT:DISK:SCALE:FILENAME (or PRODUCT:DISK:FILENAME historically)
     981             :          */
     982             :         /* with FILENAME potentially C:\BLA... */
     983          23 :         char **papszTokens = CSLTokenizeString2(pszFilename, ":", 0);
     984          23 :         int nTokens = CSLCount(papszTokens);
     985          23 :         if (nTokens != 3 && nTokens != 4 && nTokens != 5)
     986             :         {
     987           4 :             CSLDestroy(papszTokens);
     988           4 :             return nullptr;
     989             :         }
     990             : 
     991          19 :         osProduct = papszTokens[0];
     992          19 :         osDiscId = papszTokens[1];
     993             : 
     994          19 :         if (nTokens == 3)
     995           5 :             osFilename = papszTokens[2];
     996          14 :         else if (nTokens == 4)
     997             :         {
     998          13 :             if (strlen(papszTokens[2]) == 1 &&
     999           1 :                 (papszTokens[3][0] == '\\' || papszTokens[3][0] == '/'))
    1000             :             {
    1001           1 :                 osFilename = papszTokens[2];
    1002           1 :                 osFilename += ":";
    1003           1 :                 osFilename += papszTokens[3];
    1004             :             }
    1005             :             else
    1006             :             {
    1007          12 :                 osScale = papszTokens[2];
    1008          12 :                 osFilename = papszTokens[3];
    1009             :             }
    1010             :         }
    1011           1 :         else if (nTokens == 5 && strlen(papszTokens[3]) == 1 &&
    1012           1 :                  (papszTokens[4][0] == '\\' || papszTokens[4][0] == '/'))
    1013             :         {
    1014           1 :             osScale = papszTokens[2];
    1015           1 :             osFilename = papszTokens[3];
    1016           1 :             osFilename += ":";
    1017           1 :             osFilename += papszTokens[4];
    1018             :         }
    1019             :         else
    1020             :         {
    1021           0 :             CSLDestroy(papszTokens);
    1022           0 :             return nullptr;
    1023             :         }
    1024             : 
    1025          19 :         CSLDestroy(papszTokens);
    1026          19 :         pszFilename = osFilename.c_str();
    1027             :     }
    1028             : 
    1029             :     /* -------------------------------------------------------------------- */
    1030             :     /*      Parse the XML file                                              */
    1031             :     /* -------------------------------------------------------------------- */
    1032          26 :     CPLXMLNode *psXML = CPLParseXMLFile(pszFilename);
    1033          26 :     if (psXML == nullptr)
    1034             :     {
    1035           5 :         return nullptr;
    1036             :     }
    1037             : 
    1038          42 :     GDALDataset *poDS = Build(pszFilename, psXML, osProduct, osDiscId, osScale,
    1039          21 :                               poOpenInfo->pszFilename);
    1040          21 :     CPLDestroyXMLNode(psXML);
    1041             : 
    1042          21 :     if (poDS && poOpenInfo->eAccess == GA_Update)
    1043             :     {
    1044           0 :         ReportUpdateNotSupportedByDriver("ECRGTOC");
    1045           0 :         delete poDS;
    1046           0 :         return nullptr;
    1047             :     }
    1048             : 
    1049          21 :     return poDS;
    1050             : }
    1051             : 
    1052             : /************************************************************************/
    1053             : /*                         GDALRegister_ECRGTOC()                       */
    1054             : /************************************************************************/
    1055             : 
    1056        2024 : void GDALRegister_ECRGTOC()
    1057             : 
    1058             : {
    1059        2024 :     if (GDALGetDriverByName(ECRGTOC_DRIVER_NAME) != nullptr)
    1060         283 :         return;
    1061             : 
    1062        1741 :     GDALDriver *poDriver = new GDALDriver();
    1063        1741 :     ECRGTOCDriverSetCommonMetadata(poDriver);
    1064             : 
    1065        1741 :     poDriver->pfnOpen = ECRGTOCDataset::Open;
    1066             : 
    1067        1741 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1068             : }

Generated by: LCOV version 1.14