Coverage Report

Created: 2025-06-09 07:07

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