Coverage Report

Created: 2026-11-12 08:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/nitf/ecrgtocdataset.cpp
Line
Count
Source
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
    ECRGTOCDataset()
74
0
    {
75
0
        m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
76
0
        m_oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
77
0
    }
78
79
    ~ECRGTOCDataset() override
80
0
    {
81
0
        CSLDestroy(papszSubDatasets);
82
0
        CSLDestroy(papszFileList);
83
0
    }
84
85
    CSLConstList GetMetadata(const char *pszDomain = "") override;
86
87
    char **GetFileList() override
88
0
    {
89
0
        return CSLDuplicate(papszFileList);
90
0
    }
91
92
    void AddSubDataset(const char *pszFilename, const char *pszProductTitle,
93
                       const char *pszDiscId, const char *pszScale);
94
95
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override
96
0
    {
97
0
        gt = m_gt;
98
0
        return CE_None;
99
0
    }
100
101
    const OGRSpatialReference *GetSpatialRef() const override
102
0
    {
103
0
        return &m_oSRS;
104
0
    }
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
0
    ECRGTOCSubDataset(int nXSize, int nYSize) : VRTDataset(nXSize, nYSize)
128
0
    {
129
        /* Don't try to write a VRT file */
130
0
        SetWritable(FALSE);
131
132
        /* The driver is set to VRT in VRTDataset constructor. */
133
        /* We have to set it to the expected value ! */
134
0
        poDriver = GDALDriver::FromHandle(GDALGetDriverByName("ECRGTOC"));
135
0
    }
136
137
    ~ECRGTOCSubDataset() override;
138
139
    char **GetFileList() override
140
0
    {
141
0
        return CSLDuplicate(papszFileList);
142
0
    }
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
ECRGTOCSubDataset::~ECRGTOCSubDataset()
153
0
{
154
0
    CSLDestroy(papszFileList);
155
0
}
156
157
/************************************************************************/
158
/*                           LaunderString()                            */
159
/************************************************************************/
160
161
static CPLString LaunderString(const char *pszStr)
162
0
{
163
0
    CPLString osRet(pszStr);
164
0
    for (size_t i = 0; i < osRet.size(); i++)
165
0
    {
166
0
        if (osRet[i] == ':' || osRet[i] == ' ')
167
0
            osRet[i] = '_';
168
0
    }
169
0
    return osRet;
170
0
}
171
172
/************************************************************************/
173
/*                           AddSubDataset()                            */
174
/************************************************************************/
175
176
void ECRGTOCDataset::AddSubDataset(const char *pszFilename,
177
                                   const char *pszProductTitle,
178
                                   const char *pszDiscId, const char *pszScale)
179
180
0
{
181
0
    char szName[80];
182
0
    const int nCount = CSLCount(papszSubDatasets) / 2;
183
184
0
    snprintf(szName, sizeof(szName), "SUBDATASET_%d_NAME", nCount + 1);
185
0
    papszSubDatasets = CSLSetNameValue(
186
0
        papszSubDatasets, szName,
187
0
        CPLSPrintf("ECRG_TOC_ENTRY:%s:%s:%s:%s",
188
0
                   LaunderString(pszProductTitle).c_str(),
189
0
                   LaunderString(pszDiscId).c_str(),
190
0
                   LaunderString(pszScale).c_str(), pszFilename));
191
192
0
    snprintf(szName, sizeof(szName), "SUBDATASET_%d_DESC", nCount + 1);
193
0
    papszSubDatasets =
194
0
        CSLSetNameValue(papszSubDatasets, szName,
195
0
                        CPLSPrintf("Product %s, disc %s, scale %s",
196
0
                                   pszProductTitle, pszDiscId, pszScale));
197
0
}
198
199
/************************************************************************/
200
/*                            GetMetadata()                             */
201
/************************************************************************/
202
203
CSLConstList ECRGTOCDataset::GetMetadata(const char *pszDomain)
204
205
0
{
206
0
    if (pszDomain != nullptr && EQUAL(pszDomain, GDAL_MDD_SUBDATASETS))
207
0
        return papszSubDatasets;
208
209
0
    return GDALPamDataset::GetMetadata(pszDomain);
210
0
}
211
212
/************************************************************************/
213
/*                         GetScaleFromString()                         */
214
/************************************************************************/
215
216
static int GetScaleFromString(const char *pszScale)
217
0
{
218
0
    const char *pszPtr = strstr(pszScale, "1:");
219
0
    if (pszPtr)
220
0
        pszPtr = pszPtr + 2;
221
0
    else
222
0
        pszPtr = pszScale;
223
224
0
    int nScale = 0;
225
0
    char ch;
226
0
    while ((ch = *pszPtr) != '\0')
227
0
    {
228
0
        if (ch >= '0' && ch <= '9')
229
0
            nScale = nScale * 10 + ch - '0';
230
0
        else if (ch == ' ')
231
0
            ;
232
0
        else if (ch == 'k' || ch == 'K')
233
0
            return nScale * 1000;
234
0
        else if (ch == 'm' || ch == 'M')
235
0
            return nScale * 1000000;
236
0
        else
237
0
            return 0;
238
0
        pszPtr++;
239
0
    }
240
0
    return nScale;
241
0
}
242
243
/************************************************************************/
244
/*                           GetFromBase34()                            */
245
/************************************************************************/
246
247
static GIntBig GetFromBase34(const char *pszVal, int nMaxSize)
248
0
{
249
0
    GIntBig nFrameNumber = 0;
250
0
    for (int i = 0; i < nMaxSize; i++)
251
0
    {
252
0
        char ch = pszVal[i];
253
0
        if (ch == '\0')
254
0
            break;
255
0
        int chVal;
256
0
        if (ch >= 'A' && ch <= 'Z')
257
0
            ch += 'a' - 'A';
258
        /* i and o letters are excluded, */
259
0
        if (ch >= '0' && ch <= '9')
260
0
            chVal = ch - '0';
261
0
        else if (ch >= 'a' && ch <= 'h')
262
0
            chVal = ch - 'a' + 10;
263
0
        else if (ch >= 'j' && ch <= 'n')
264
0
            chVal = ch - 'a' + 10 - 1;
265
0
        else if (ch >= 'p' && ch <= 'z')
266
0
            chVal = ch - 'a' + 10 - 2;
267
0
        else
268
0
        {
269
0
            CPLDebug("ECRG", "Invalid base34 value : %s", pszVal);
270
0
            break;
271
0
        }
272
0
        nFrameNumber = nFrameNumber * 34 + chVal;
273
0
    }
274
275
0
    return nFrameNumber;
276
0
}
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
static int CEIL_ROUND(double a, double b)
293
0
{
294
0
    return static_cast<int>(ceil(a / b) * b);
295
0
}
296
297
static int NEAR_ROUND(double a, double b)
298
0
{
299
0
    return static_cast<int>(floor((a / b) + 0.5) * b);
300
0
}
301
302
constexpr int ECRG_PIXELS = 2304;
303
304
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
0
{
309
0
    const int nAbsZone = abs(nZone);
310
#ifdef DEBUG
311
    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
0
    const int nEW_ADRG =
319
0
        CEIL_ROUND(anACst_ADRG[nAbsZone - 1] * (1e6 / nScale), 512);
320
0
    const int nEW_CADRG = NEAR_ROUND(nEW_ADRG / (150. / 100.), 256);
321
    /* MIL-PRF-32283 - D.2.1.2 - East-west pixel constant. */
322
0
    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
0
    const int nCols =
329
0
        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
0
    const int nNS_ADRG = CEIL_ROUND(nBCst_ADRG * (1e6 / nScale), 512) / 4;
336
0
    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
0
    const int nNS = nNS_CADRG / 256 * 384;
340
341
    /************************************************************************/
342
    /*  Compute number of latitudinal frames and latitude of top of zone    */
343
    /************************************************************************/
344
0
    dfPixelYSize = 90.0 / nNS;
345
346
0
    const double dfFrameLatHeight = dfPixelYSize * ECRG_PIXELS;
347
348
    /* MIL-PRF-32283 - D.2.1.5 - Equatorward and poleward zone extents. */
349
0
    int nUpperZoneFrames =
350
0
        static_cast<int>(ceil(anZoneUpperLat[nAbsZone] / dfFrameLatHeight));
351
0
    int nBottomZoneFrames = static_cast<int>(
352
0
        floor(anZoneUpperLat[nAbsZone - 1] / dfFrameLatHeight));
353
0
    const int nRows = nUpperZoneFrames - nBottomZoneFrames;
354
355
    /* Not sure to really understand D.2.1.5.a. Testing needed */
356
0
    if (nZone < 0)
357
0
    {
358
0
        nUpperZoneFrames = -nBottomZoneFrames;
359
        /*nBottomZoneFrames = nUpperZoneFrames - nRows;*/
360
0
    }
361
362
0
    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
0
    const GIntBig nFrameNumber = GetFromBase34(pszFrameName, 10);
370
371
    /*  MIL-PRF-32283 - A.2.6.1 */
372
0
    const GIntBig nY = nFrameNumber / nCols;
373
0
    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
0
    dfMaxY = dfUpperZoneTopLat - (nRows - 1 - nY) * dfFrameLatHeight;
381
0
    dfMinY = dfMaxY - dfFrameLatHeight;
382
383
0
    dfPixelXSize = 360.0 / nEW;
384
385
0
    const double dfFrameLongWidth = dfPixelXSize * ECRG_PIXELS;
386
0
    dfMinX = -180.0 + nX * dfFrameLongWidth;
387
0
    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
0
}
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
    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
0
        : m_nRasterXSize(nRasterXSize), m_nRasterYSize(nRasterYSize),
417
0
          m_dfMinX(dfMinX), m_dfMaxY(dfMaxY), m_dfPixelXSize(dfPixelXSize),
418
0
          m_dfPixelYSize(dfPixelYSize)
419
0
    {
420
0
        SetSrcBand(pszFilename, nBandIn);
421
0
        SetSrcWindow(0, 0, nRasterXSize, nRasterYSize);
422
0
        SetDstWindow(dfDstXOff, dfDstYOff, dfDstXSize, dfDstYSize);
423
0
    }
424
};
425
426
/************************************************************************/
427
/*                         ValidateOpenedBand()                         */
428
/************************************************************************/
429
430
#define WARN_CHECK_DS(x)                                                       \
431
0
    do                                                                         \
432
0
    {                                                                          \
433
0
        if (!(x))                                                              \
434
0
        {                                                                      \
435
0
            CPLError(CE_Warning, CPLE_AppDefined,                              \
436
0
                     "For %s, assert '" #x "' failed",                         \
437
0
                     poSourceDS->GetDescription());                            \
438
0
            checkOK = false;                                                   \
439
0
        }                                                                      \
440
0
    } while (false)
441
442
bool ECRGTOCSource::ValidateOpenedBand(GDALRasterBand *poBand) const
443
0
{
444
0
    bool checkOK = true;
445
0
    auto poSourceDS = poBand->GetDataset();
446
0
    CPLAssert(poSourceDS);
447
448
0
    GDALGeoTransform l_gt;
449
0
    poSourceDS->GetGeoTransform(l_gt);
450
0
    WARN_CHECK_DS(fabs(l_gt.xorig - m_dfMinX) < 1e-10);
451
0
    WARN_CHECK_DS(fabs(l_gt.yorig - m_dfMaxY) < 1e-10);
452
0
    WARN_CHECK_DS(fabs(l_gt.xscale - m_dfPixelXSize) < 1e-10);
453
0
    WARN_CHECK_DS(fabs(l_gt.yscale - (-m_dfPixelYSize)) < 1e-10);
454
0
    WARN_CHECK_DS(l_gt.xrot == 0 && l_gt.yrot == 0);  // No rotation.
455
0
    WARN_CHECK_DS(poSourceDS->GetRasterCount() == 3);
456
0
    WARN_CHECK_DS(poSourceDS->GetRasterXSize() == m_nRasterXSize);
457
0
    WARN_CHECK_DS(poSourceDS->GetRasterYSize() == m_nRasterYSize);
458
0
    WARN_CHECK_DS(
459
0
        EQUAL(poSourceDS->GetProjectionRef(), SRS_WKT_WGS84_LAT_LONG));
460
0
    WARN_CHECK_DS(poSourceDS->GetRasterBand(1)->GetRasterDataType() ==
461
0
                  GDT_UInt8);
462
0
    return checkOK;
463
0
}
464
465
/************************************************************************/
466
/*                           BuildFullName()                            */
467
/************************************************************************/
468
469
static std::string BuildFullName(const char *pszTOCFilename,
470
                                 const char *pszFramePath,
471
                                 const char *pszFrameName)
472
0
{
473
0
    char *pszPath = nullptr;
474
0
    if (pszFramePath[0] == '.' &&
475
0
        (pszFramePath[1] == '/' || pszFramePath[1] == '\\'))
476
0
        pszPath = CPLStrdup(pszFramePath + 2);
477
0
    else
478
0
        pszPath = CPLStrdup(pszFramePath);
479
0
    for (int i = 0; pszPath[i] != '\0'; i++)
480
0
    {
481
0
        if (pszPath[i] == '\\')
482
0
            pszPath[i] = '/';
483
0
    }
484
0
    const std::string osName =
485
0
        CPLFormFilenameSafe(pszPath, pszFrameName, nullptr);
486
0
    CPLFree(pszPath);
487
0
    pszPath = nullptr;
488
0
    std::string osTOCPath = CPLGetDirnameSafe(pszTOCFilename);
489
0
    const auto nPosFirstSlashInName = osName.find('/');
490
0
    if (nPosFirstSlashInName != std::string::npos)
491
0
    {
492
0
        if (osTOCPath.size() >= nPosFirstSlashInName + 1 &&
493
0
            (osTOCPath[osTOCPath.size() - (nPosFirstSlashInName + 1)] == '/' ||
494
0
             osTOCPath[osTOCPath.size() - (nPosFirstSlashInName + 1)] ==
495
0
                 '\\') &&
496
0
            strncmp(osTOCPath.c_str() + osTOCPath.size() - nPosFirstSlashInName,
497
0
                    osName.c_str(), nPosFirstSlashInName) == 0)
498
0
        {
499
0
            osTOCPath = CPLGetDirnameSafe(osTOCPath.c_str());
500
0
        }
501
0
    }
502
0
    return CPLProjectRelativeFilenameSafe(osTOCPath.c_str(), osName.c_str());
503
0
}
504
505
/************************************************************************/
506
/*                               Build()                                */
507
/************************************************************************/
508
509
/* Builds a ECRGTOCSubDataset from the set of files of the toc entry */
510
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
0
{
517
0
    GDALDriver *poDriver = GetGDALDriverManager()->GetDriverByName("VRT");
518
0
    if (poDriver == nullptr)
519
0
        return nullptr;
520
521
0
    const int nSizeX = static_cast<int>(
522
0
        (dfGlobalMaxX - dfGlobalMinX) / dfGlobalPixelXSize + 0.5);
523
0
    const int nSizeY = static_cast<int>(
524
0
        (dfGlobalMaxY - dfGlobalMinY) / dfGlobalPixelYSize + 0.5);
525
526
    /* ------------------------------------ */
527
    /* Create the VRT with the overall size */
528
    /* ------------------------------------ */
529
0
    auto poVirtualDS = std::make_unique<ECRGTOCSubDataset>(nSizeX, nSizeY);
530
531
0
    poVirtualDS->SetProjection(SRS_WKT_WGS84_LAT_LONG);
532
533
0
    GDALGeoTransform gt{
534
0
        dfGlobalMinX,       dfGlobalPixelXSize, 0, dfGlobalMaxY, 0,
535
0
        -dfGlobalPixelYSize};
536
0
    poVirtualDS->SetGeoTransform(gt);
537
538
0
    for (int i = 0; i < 3; i++)
539
0
    {
540
0
        poVirtualDS->AddBand(GDT_UInt8, nullptr);
541
0
        GDALRasterBand *poBand = poVirtualDS->GetRasterBand(i + 1);
542
0
        poBand->SetColorInterpretation(
543
0
            static_cast<GDALColorInterp>(GCI_RedBand + i));
544
0
    }
545
546
0
    poVirtualDS->SetDescription(pszTOCFilename);
547
548
0
    poVirtualDS->SetMetadataItem("PRODUCT_TITLE", pszProductTitle);
549
0
    poVirtualDS->SetMetadataItem("DISC_ID", pszDiscId);
550
0
    if (nScale != -1)
551
0
        poVirtualDS->SetMetadataItem("SCALE", CPLString().Printf("%d", nScale));
552
553
    /* -------------------------------------------------------------------- */
554
    /*      Check for overviews.                                            */
555
    /* -------------------------------------------------------------------- */
556
557
0
    poVirtualDS->oOvManager.Initialize(
558
0
        poVirtualDS.get(),
559
0
        CPLString().Printf("%s.%d", pszTOCFilename, nCountSubDataset));
560
561
0
    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
0
    CPLConfigOptionSetter oSetter("GDAL_FORCE_CACHING", "NO", false);
568
569
0
    for (int i = 0; i < static_cast<int>(aosFrameDesc.size()); i++)
570
0
    {
571
0
        if (CPLHasPathTraversal(aosFrameDesc[i].pszName))
572
0
        {
573
0
            CPLError(CE_Failure, CPLE_AppDefined,
574
0
                     "Path traversal detected in %s", aosFrameDesc[i].pszName);
575
0
            return nullptr;
576
0
        }
577
0
        const std::string osName = BuildFullName(
578
0
            pszTOCFilename, aosFrameDesc[i].pszPath, aosFrameDesc[i].pszName);
579
580
0
        double dfMinX = 0.0;
581
0
        double dfMaxX = 0.0;
582
0
        double dfMinY = 0.0;
583
0
        double dfMaxY = 0.0;
584
0
        double dfPixelXSize = 0.0;
585
0
        double dfPixelYSize = 0.0;
586
0
        ::GetExtent(aosFrameDesc[i].pszName, aosFrameDesc[i].nScale,
587
0
                    aosFrameDesc[i].nZone, dfMinX, dfMaxX, dfMinY, dfMaxY,
588
0
                    dfPixelXSize, dfPixelYSize);
589
590
0
        const int nFrameXSize =
591
0
            static_cast<int>((dfMaxX - dfMinX) / dfPixelXSize + 0.5);
592
0
        const int nFrameYSize =
593
0
            static_cast<int>((dfMaxY - dfMinY) / dfPixelYSize + 0.5);
594
595
0
        poVirtualDS->papszFileList =
596
0
            CSLAddString(poVirtualDS->papszFileList, osName.c_str());
597
598
0
        for (int j = 0; j < 3; j++)
599
0
        {
600
0
            VRTSourcedRasterBand *poBand =
601
0
                cpl::down_cast<VRTSourcedRasterBand *>(
602
0
                    poVirtualDS->GetRasterBand(j + 1));
603
            /* Place the raster band at the right position in the VRT */
604
0
            auto poSource = new ECRGTOCSource(
605
0
                osName.c_str(), j + 1, nFrameXSize, nFrameYSize,
606
0
                static_cast<int>((dfMinX - dfGlobalMinX) / dfGlobalPixelXSize +
607
0
                                 0.5),
608
0
                static_cast<int>((dfGlobalMaxY - dfMaxY) / dfGlobalPixelYSize +
609
0
                                 0.5),
610
0
                static_cast<int>((dfMaxX - dfMinX) / dfGlobalPixelXSize + 0.5),
611
0
                static_cast<int>((dfMaxY - dfMinY) / dfGlobalPixelYSize + 0.5),
612
0
                dfMinX, dfMaxY, dfPixelXSize, dfPixelYSize);
613
0
            poBand->AddSource(poSource);
614
0
        }
615
0
    }
616
617
0
    poVirtualDS->SetMetadataItem(GDALMD_INTERLEAVE, "PIXEL",
618
0
                                 GDAL_MDD_IMAGE_STRUCTURE);
619
620
0
    return poVirtualDS.release();
621
0
}
622
623
/************************************************************************/
624
/*                               Build()                                */
625
/************************************************************************/
626
627
GDALDataset *ECRGTOCDataset::Build(const char *pszTOCFilename,
628
                                   CPLXMLNode *psXML,
629
                                   const std::string &osProduct,
630
                                   const std::string &osDiscId,
631
                                   const std::string &osScale,
632
                                   const char *pszOpenInfoFilename)
633
24
{
634
24
    CPLXMLNode *psTOC = CPLGetXMLNode(psXML, "=Table_of_Contents");
635
24
    if (psTOC == nullptr)
636
24
    {
637
24
        CPLError(CE_Warning, CPLE_AppDefined,
638
24
                 "Cannot find Table_of_Contents element");
639
24
        return nullptr;
640
24
    }
641
642
0
    double dfGlobalMinX = 0.0;
643
0
    double dfGlobalMinY = 0.0;
644
0
    double dfGlobalMaxX = 0.0;
645
0
    double dfGlobalMaxY = 0.0;
646
0
    double dfGlobalPixelXSize = 0.0;
647
0
    double dfGlobalPixelYSize = 0.0;
648
0
    bool bGlobalExtentValid = false;
649
650
0
    auto poDS = std::make_unique<ECRGTOCDataset>();
651
0
    int nSubDatasets = 0;
652
653
0
    int bLookForSubDataset = !osProduct.empty() && !osDiscId.empty();
654
655
0
    int nCountSubDataset = 0;
656
657
0
    poDS->SetDescription(pszOpenInfoFilename);
658
0
    poDS->papszFileList = poDS->GDALDataset::GetFileList();
659
660
0
    for (CPLXMLNode *psIter1 = psTOC->psChild; psIter1 != nullptr;
661
0
         psIter1 = psIter1->psNext)
662
0
    {
663
0
        if (!(psIter1->eType == CXT_Element && psIter1->pszValue != nullptr &&
664
0
              strcmp(psIter1->pszValue, "product") == 0))
665
0
            continue;
666
667
0
        const char *pszProductTitle =
668
0
            CPLGetXMLValue(psIter1, "product_title", nullptr);
669
0
        if (pszProductTitle == nullptr)
670
0
        {
671
0
            CPLError(CE_Warning, CPLE_AppDefined,
672
0
                     "Cannot find product_title attribute");
673
0
            continue;
674
0
        }
675
676
0
        if (bLookForSubDataset &&
677
0
            strcmp(LaunderString(pszProductTitle), osProduct.c_str()) != 0)
678
0
            continue;
679
680
0
        for (CPLXMLNode *psIter2 = psIter1->psChild; psIter2 != nullptr;
681
0
             psIter2 = psIter2->psNext)
682
0
        {
683
0
            if (!(psIter2->eType == CXT_Element &&
684
0
                  psIter2->pszValue != nullptr &&
685
0
                  strcmp(psIter2->pszValue, "disc") == 0))
686
0
                continue;
687
688
0
            const char *pszDiscId = CPLGetXMLValue(psIter2, "id", nullptr);
689
0
            if (pszDiscId == nullptr)
690
0
            {
691
0
                CPLError(CE_Warning, CPLE_AppDefined,
692
0
                         "Cannot find id attribute");
693
0
                continue;
694
0
            }
695
696
0
            if (bLookForSubDataset &&
697
0
                strcmp(LaunderString(pszDiscId), osDiscId.c_str()) != 0)
698
0
                continue;
699
700
0
            CPLXMLNode *psFrameList = CPLGetXMLNode(psIter2, "frame_list");
701
0
            if (psFrameList == nullptr)
702
0
            {
703
0
                CPLError(CE_Warning, CPLE_AppDefined,
704
0
                         "Cannot find frame_list element");
705
0
                continue;
706
0
            }
707
708
0
            for (CPLXMLNode *psIter3 = psFrameList->psChild; psIter3 != nullptr;
709
0
                 psIter3 = psIter3->psNext)
710
0
            {
711
0
                if (!(psIter3->eType == CXT_Element &&
712
0
                      psIter3->pszValue != nullptr &&
713
0
                      strcmp(psIter3->pszValue, "scale") == 0))
714
0
                    continue;
715
716
0
                const char *pszSize = CPLGetXMLValue(psIter3, "size", nullptr);
717
0
                if (pszSize == nullptr)
718
0
                {
719
0
                    CPLError(CE_Warning, CPLE_AppDefined,
720
0
                             "Cannot find size attribute");
721
0
                    continue;
722
0
                }
723
724
0
                int nScale = GetScaleFromString(pszSize);
725
0
                if (nScale <= 0)
726
0
                {
727
0
                    CPLError(CE_Warning, CPLE_AppDefined, "Invalid scale %s",
728
0
                             pszSize);
729
0
                    continue;
730
0
                }
731
732
0
                if (bLookForSubDataset)
733
0
                {
734
0
                    if (!osScale.empty())
735
0
                    {
736
0
                        if (strcmp(LaunderString(pszSize), osScale.c_str()) !=
737
0
                            0)
738
0
                        {
739
0
                            continue;
740
0
                        }
741
0
                    }
742
0
                    else
743
0
                    {
744
0
                        int nCountScales = 0;
745
0
                        for (CPLXMLNode *psIter4 = psFrameList->psChild;
746
0
                             psIter4 != nullptr; psIter4 = psIter4->psNext)
747
0
                        {
748
0
                            if (!(psIter4->eType == CXT_Element &&
749
0
                                  psIter4->pszValue != nullptr &&
750
0
                                  strcmp(psIter4->pszValue, "scale") == 0))
751
0
                                continue;
752
0
                            nCountScales++;
753
0
                        }
754
0
                        if (nCountScales > 1)
755
0
                        {
756
0
                            CPLError(CE_Failure, CPLE_AppDefined,
757
0
                                     "Scale should be mentioned in "
758
0
                                     "subdatasets syntax since this disk "
759
0
                                     "contains several scales");
760
0
                            return nullptr;
761
0
                        }
762
0
                    }
763
0
                }
764
765
0
                nCountSubDataset++;
766
767
0
                std::vector<FrameDesc> aosFrameDesc;
768
0
                int nValidFrames = 0;
769
770
0
                for (CPLXMLNode *psIter4 = psIter3->psChild; psIter4 != nullptr;
771
0
                     psIter4 = psIter4->psNext)
772
0
                {
773
0
                    if (!(psIter4->eType == CXT_Element &&
774
0
                          psIter4->pszValue != nullptr &&
775
0
                          strcmp(psIter4->pszValue, "frame") == 0))
776
0
                        continue;
777
778
0
                    const char *pszFrameName =
779
0
                        CPLGetXMLValue(psIter4, "name", nullptr);
780
0
                    if (pszFrameName == nullptr)
781
0
                    {
782
0
                        CPLError(CE_Warning, CPLE_AppDefined,
783
0
                                 "Cannot find name element");
784
0
                        continue;
785
0
                    }
786
787
0
                    if (strlen(pszFrameName) != 18)
788
0
                    {
789
0
                        CPLError(CE_Warning, CPLE_AppDefined,
790
0
                                 "Invalid value for name element : %s",
791
0
                                 pszFrameName);
792
0
                        continue;
793
0
                    }
794
795
0
                    const char *pszFramePath =
796
0
                        CPLGetXMLValue(psIter4, "frame_path", nullptr);
797
0
                    if (pszFramePath == nullptr)
798
0
                    {
799
0
                        CPLError(CE_Warning, CPLE_AppDefined,
800
0
                                 "Cannot find frame_path element");
801
0
                        continue;
802
0
                    }
803
804
0
                    const char *pszFrameZone =
805
0
                        CPLGetXMLValue(psIter4, "frame_zone", nullptr);
806
0
                    if (pszFrameZone == nullptr)
807
0
                    {
808
0
                        CPLError(CE_Warning, CPLE_AppDefined,
809
0
                                 "Cannot find frame_zone element");
810
0
                        continue;
811
0
                    }
812
0
                    if (strlen(pszFrameZone) != 1)
813
0
                    {
814
0
                        CPLError(CE_Warning, CPLE_AppDefined,
815
0
                                 "Invalid value for frame_zone element : %s",
816
0
                                 pszFrameZone);
817
0
                        continue;
818
0
                    }
819
0
                    char chZone = pszFrameZone[0];
820
0
                    int nZone = 0;
821
0
                    if (chZone >= '1' && chZone <= '9')
822
0
                        nZone = chZone - '0';
823
0
                    else if (chZone >= 'a' && chZone <= 'h')
824
0
                        nZone = -(chZone - 'a' + 1);
825
0
                    else if (chZone >= 'A' && chZone <= 'H')
826
0
                        nZone = -(chZone - 'A' + 1);
827
0
                    else if (chZone == 'j' || chZone == 'J')
828
0
                        nZone = -9;
829
0
                    else
830
0
                    {
831
0
                        CPLError(CE_Warning, CPLE_AppDefined,
832
0
                                 "Invalid value for frame_zone element : %s",
833
0
                                 pszFrameZone);
834
0
                        continue;
835
0
                    }
836
0
                    if (nZone == 9 || nZone == -9)
837
0
                    {
838
0
                        CPLError(
839
0
                            CE_Warning, CPLE_AppDefined,
840
0
                            "Polar zones unhandled by current implementation");
841
0
                        continue;
842
0
                    }
843
844
0
                    double dfMinX = 0.0;
845
0
                    double dfMaxX = 0.0;
846
0
                    double dfMinY = 0.0;
847
0
                    double dfMaxY = 0.0;
848
0
                    double dfPixelXSize = 0.0;
849
0
                    double dfPixelYSize = 0.0;
850
0
                    ::GetExtent(pszFrameName, nScale, nZone, dfMinX, dfMaxX,
851
0
                                dfMinY, dfMaxY, dfPixelXSize, dfPixelYSize);
852
853
0
                    nValidFrames++;
854
855
0
                    if (CPLHasPathTraversal(pszFrameName))
856
0
                    {
857
0
                        CPLError(CE_Failure, CPLE_AppDefined,
858
0
                                 "Path traversal detected in %s", pszFrameName);
859
0
                        return nullptr;
860
0
                    }
861
0
                    const std::string osFullName = BuildFullName(
862
0
                        pszTOCFilename, pszFramePath, pszFrameName);
863
0
                    poDS->papszFileList =
864
0
                        CSLAddString(poDS->papszFileList, osFullName.c_str());
865
866
0
                    if (!bGlobalExtentValid)
867
0
                    {
868
0
                        dfGlobalMinX = dfMinX;
869
0
                        dfGlobalMinY = dfMinY;
870
0
                        dfGlobalMaxX = dfMaxX;
871
0
                        dfGlobalMaxY = dfMaxY;
872
0
                        dfGlobalPixelXSize = dfPixelXSize;
873
0
                        dfGlobalPixelYSize = dfPixelYSize;
874
0
                        bGlobalExtentValid = true;
875
0
                    }
876
0
                    else
877
0
                    {
878
0
                        if (dfMinX < dfGlobalMinX)
879
0
                            dfGlobalMinX = dfMinX;
880
0
                        if (dfMinY < dfGlobalMinY)
881
0
                            dfGlobalMinY = dfMinY;
882
0
                        if (dfMaxX > dfGlobalMaxX)
883
0
                            dfGlobalMaxX = dfMaxX;
884
0
                        if (dfMaxY > dfGlobalMaxY)
885
0
                            dfGlobalMaxY = dfMaxY;
886
0
                        if (dfPixelXSize < dfGlobalPixelXSize)
887
0
                            dfGlobalPixelXSize = dfPixelXSize;
888
0
                        if (dfPixelYSize < dfGlobalPixelYSize)
889
0
                            dfGlobalPixelYSize = dfPixelYSize;
890
0
                    }
891
892
0
                    nValidFrames++;
893
894
0
                    if (bLookForSubDataset)
895
0
                    {
896
0
                        FrameDesc frameDesc;
897
0
                        frameDesc.pszName = pszFrameName;
898
0
                        frameDesc.pszPath = pszFramePath;
899
0
                        frameDesc.nScale = nScale;
900
0
                        frameDesc.nZone = nZone;
901
0
                        aosFrameDesc.push_back(frameDesc);
902
0
                    }
903
0
                }
904
905
0
                if (bLookForSubDataset)
906
0
                {
907
0
                    if (nValidFrames == 0)
908
0
                        return nullptr;
909
0
                    return ECRGTOCSubDataset::Build(
910
0
                        pszProductTitle, pszDiscId, nScale, nCountSubDataset,
911
0
                        pszTOCFilename, aosFrameDesc, dfGlobalMinX,
912
0
                        dfGlobalMinY, dfGlobalMaxX, dfGlobalMaxY,
913
0
                        dfGlobalPixelXSize, dfGlobalPixelYSize);
914
0
                }
915
916
0
                if (nValidFrames)
917
0
                {
918
0
                    poDS->AddSubDataset(pszOpenInfoFilename, pszProductTitle,
919
0
                                        pszDiscId, pszSize);
920
0
                    nSubDatasets++;
921
0
                }
922
0
            }
923
0
        }
924
0
    }
925
926
0
    if (!bGlobalExtentValid)
927
0
    {
928
0
        return nullptr;
929
0
    }
930
931
0
    if (nSubDatasets == 1)
932
0
    {
933
0
        const char *pszSubDatasetName = CSLFetchNameValue(
934
0
            poDS->GetMetadata(GDAL_MDD_SUBDATASETS), "SUBDATASET_1_NAME");
935
0
        GDALOpenInfo oOpenInfo(pszSubDatasetName, GA_ReadOnly);
936
0
        poDS.reset();
937
0
        GDALDataset *poRetDS = Open(&oOpenInfo);
938
0
        if (poRetDS)
939
0
            poRetDS->SetDescription(pszOpenInfoFilename);
940
0
        return poRetDS;
941
0
    }
942
943
0
    poDS->m_gt.xorig = dfGlobalMinX;
944
0
    poDS->m_gt.xscale = dfGlobalPixelXSize;
945
0
    poDS->m_gt.xrot = 0.0;
946
0
    poDS->m_gt.yorig = dfGlobalMaxY;
947
0
    poDS->m_gt.yrot = 0.0;
948
0
    poDS->m_gt.yscale = -dfGlobalPixelYSize;
949
950
0
    poDS->nRasterXSize = static_cast<int>(0.5 + (dfGlobalMaxX - dfGlobalMinX) /
951
0
                                                    dfGlobalPixelXSize);
952
0
    poDS->nRasterYSize = static_cast<int>(0.5 + (dfGlobalMaxY - dfGlobalMinY) /
953
0
                                                    dfGlobalPixelYSize);
954
955
    /* -------------------------------------------------------------------- */
956
    /*      Initialize any PAM information.                                 */
957
    /* -------------------------------------------------------------------- */
958
0
    poDS->TryLoadXML();
959
960
0
    return poDS.release();
961
0
}
962
963
/************************************************************************/
964
/*                                Open()                                */
965
/************************************************************************/
966
967
GDALDataset *ECRGTOCDataset::Open(GDALOpenInfo *poOpenInfo)
968
969
544
{
970
544
    if (!ECRGTOCDriverIdentify(poOpenInfo))
971
0
        return nullptr;
972
973
544
    const char *pszFilename = poOpenInfo->pszFilename;
974
544
    CPLString osFilename;
975
544
    CPLString osProduct, osDiscId, osScale;
976
977
544
    if (STARTS_WITH_CI(pszFilename, "ECRG_TOC_ENTRY:"))
978
0
    {
979
0
        pszFilename += strlen("ECRG_TOC_ENTRY:");
980
981
        /* PRODUCT:DISK:SCALE:FILENAME (or PRODUCT:DISK:FILENAME historically)
982
         */
983
        /* with FILENAME potentially C:\BLA... */
984
0
        const CPLStringList aosTokens(CSLTokenizeString2(pszFilename, ":", 0));
985
0
        if (aosTokens.size() == 3)
986
0
            osFilename = aosTokens[2];
987
0
        else if (aosTokens.size() == 4)
988
0
        {
989
0
            if (strlen(aosTokens[2]) == 1 &&
990
0
                (aosTokens[3][0] == '\\' || aosTokens[3][0] == '/'))
991
0
            {
992
0
                osFilename = aosTokens[2];
993
0
                osFilename += ":";
994
0
                osFilename += aosTokens[3];
995
0
            }
996
0
            else
997
0
            {
998
0
                osScale = aosTokens[2];
999
0
                osFilename = aosTokens[3];
1000
0
            }
1001
0
        }
1002
0
        else if (aosTokens.size() == 5 && strlen(aosTokens[3]) == 1 &&
1003
0
                 (aosTokens[4][0] == '\\' || aosTokens[4][0] == '/'))
1004
0
        {
1005
0
            osScale = aosTokens[2];
1006
0
            osFilename = aosTokens[3];
1007
0
            osFilename += ":";
1008
0
            osFilename += aosTokens[4];
1009
0
        }
1010
0
        else
1011
0
        {
1012
0
            return nullptr;
1013
0
        }
1014
1015
0
        osProduct = aosTokens[0];
1016
0
        osDiscId = aosTokens[1];
1017
1018
0
        pszFilename = osFilename.c_str();
1019
0
    }
1020
1021
    /* -------------------------------------------------------------------- */
1022
    /*      Parse the XML file                                              */
1023
    /* -------------------------------------------------------------------- */
1024
544
    CPLXMLNode *psXML = CPLParseXMLFile(pszFilename);
1025
544
    if (psXML == nullptr)
1026
520
    {
1027
520
        return nullptr;
1028
520
    }
1029
1030
24
    GDALDataset *poDS = Build(pszFilename, psXML, osProduct, osDiscId, osScale,
1031
24
                              poOpenInfo->pszFilename);
1032
24
    CPLDestroyXMLNode(psXML);
1033
1034
24
    if (poDS && poOpenInfo->eAccess == GA_Update)
1035
0
    {
1036
0
        ReportUpdateNotSupportedByDriver("ECRGTOC");
1037
0
        delete poDS;
1038
0
        return nullptr;
1039
0
    }
1040
1041
24
    return poDS;
1042
24
}
1043
1044
/************************************************************************/
1045
/*                        GDALRegister_ECRGTOC()                        */
1046
/************************************************************************/
1047
1048
void GDALRegister_ECRGTOC()
1049
1050
24
{
1051
24
    if (GDALGetDriverByName(ECRGTOC_DRIVER_NAME) != nullptr)
1052
0
        return;
1053
1054
24
    GDALDriver *poDriver = new GDALDriver();
1055
24
    ECRGTOCDriverSetCommonMetadata(poDriver);
1056
1057
24
    poDriver->pfnOpen = ECRGTOCDataset::Open;
1058
1059
24
    GetGDALDriverManager()->RegisterDriver(poDriver);
1060
24
}