Coverage Report

Created: 2026-06-30 08:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/sentinel2/sentinel2dataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  Sentinel2 products
5
 * Author:   Even Rouault, <even.rouault at spatialys.com>
6
 * Funded by: Centre National d'Etudes Spatiales (CNES)
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 2015, Even Rouault, <even.rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "cpl_minixml.h"
15
#include "cpl_string.h"
16
#include "gdal_frmts.h"
17
#include "gdal_pam.h"
18
#include "ogr_spatialref.h"
19
#include "ogr_geometry.h"
20
#include "gdaljp2metadata.h"
21
#include "../vrt/vrtdataset.h"
22
23
#include <algorithm>
24
#include <map>
25
#include <set>
26
#include <vector>
27
28
#ifdef HAVE_UNISTD_H
29
#include <unistd.h>
30
#endif
31
32
#ifndef STARTS_WITH_CI
33
#define STARTS_WITH_CI(a, b) EQUALN(a, b, strlen(b))
34
#endif
35
36
52
#define DIGIT_ZERO '0'
37
38
typedef enum
39
{
40
    SENTINEL2_L1B,
41
    SENTINEL2_L1C,
42
    SENTINEL2_L2A
43
} SENTINEL2Level;
44
45
typedef enum
46
{
47
    MSI2Ap,
48
    MSI2A
49
} SENTINEL2ProductType;
50
51
typedef struct
52
{
53
    const char *pszBandName;
54
    int nResolution; /* meters */
55
    int nWaveLength; /* nanometers */
56
    int nBandWidth;  /* nanometers */
57
    GDALColorInterp eColorInterp;
58
} SENTINEL2BandDescription;
59
60
constexpr int RES_10M = 10;
61
constexpr int RES_20M = 20;
62
constexpr int RES_60M = 60;
63
64
/* clang-format off */
65
static const SENTINEL2BandDescription asBandDesc[] = {
66
    {"B1", RES_60M, 443, 20, GCI_CoastalBand},
67
    {"B2", RES_10M, 490, 65, GCI_BlueBand},
68
    {"B3", RES_10M, 560, 35, GCI_GreenBand},
69
    {"B4", RES_10M, 665, 30, GCI_RedBand},
70
    {"B5", RES_20M, 705, 15, GCI_RedEdgeBand},   // rededge071
71
    {"B6", RES_20M, 740, 15, GCI_RedEdgeBand},   // rededge075
72
    {"B7", RES_20M, 783, 20, GCI_RedEdgeBand},   // rededge078
73
    {"B8", RES_10M, 842, 115, GCI_NIRBand},      // nir
74
    {"B8A", RES_20M, 865, 20, GCI_NIRBand},      // nir08
75
    {"B9", RES_60M, 945, 20, GCI_NIRBand},       // nir09
76
    {"B10", RES_60M, 1375, 30, GCI_OtherIRBand}, // cirrus
77
    {"B11", RES_20M, 1610, 90, GCI_SWIRBand},    // swir16
78
    {"B12", RES_20M, 2190, 180, GCI_SWIRBand},   // swir11
79
};
80
81
/* clang-format on */
82
83
typedef enum
84
{
85
    TL_IMG_DATA,      /* Tile is located in IMG_DATA/ */
86
    TL_IMG_DATA_Rxxm, /* Tile is located in IMG_DATA/Rxxm/ */
87
    TL_QI_DATA        /* Tile is located in QI_DATA/ */
88
} SENTINEL2_L2A_Tilelocation;
89
90
struct SENTINEL2_L2A_BandDescription
91
{
92
    const char *pszBandName = nullptr;
93
    const char *pszBandDescription = nullptr;
94
    int nResolution = 0; /* meters */
95
    SENTINEL2_L2A_Tilelocation eLocation = TL_IMG_DATA;
96
};
97
98
class L1CSafeCompatGranuleDescription
99
{
100
  public:
101
    // GRANULE/L1C_T30TXT_A007999_20170102T111441/MTD_TL.xml
102
    CPLString osMTDTLPath{};
103
    // GRANULE/L1C_T30TXT_A007999_20170102T111441/IMG_DATA/T30TXT_20170102T111442_
104
    CPLString osBandPrefixPath{};
105
};
106
107
static const char *L2A_BandDescription_AOT =
108
    "Aerosol Optical Thickness map (at 550nm)";
109
static const char *L2A_BandDescription_WVP = "Scene-average Water Vapour map";
110
static const char *L2A_BandDescription_SCL = "Scene Classification";
111
static const char *L2A_BandDescription_CLD =
112
    "Raster mask values range from 0 for high confidence clear sky to 100 for "
113
    "high confidence cloudy";
114
static const char *L2A_BandDescription_SNW =
115
    "Raster mask values range from 0 for high confidence NO snow/ice to 100 "
116
    "for high confidence snow/ice";
117
118
static const SENTINEL2_L2A_BandDescription asL2ABandDesc[] = {
119
    {"AOT", L2A_BandDescription_AOT, RES_10M, TL_IMG_DATA_Rxxm},
120
    {"AOT", L2A_BandDescription_AOT, RES_20M, TL_IMG_DATA_Rxxm},
121
    {"AOT", L2A_BandDescription_AOT, RES_60M, TL_IMG_DATA_Rxxm},
122
    {"WVP", L2A_BandDescription_WVP, RES_10M, TL_IMG_DATA_Rxxm},
123
    {"WVP", L2A_BandDescription_WVP, RES_20M, TL_IMG_DATA_Rxxm},
124
    {"WVP", L2A_BandDescription_WVP, RES_60M, TL_IMG_DATA_Rxxm},
125
    {"SCL", L2A_BandDescription_SCL, RES_20M, TL_IMG_DATA_Rxxm},
126
    {"SCL", L2A_BandDescription_SCL, RES_60M, TL_IMG_DATA_Rxxm},
127
    {"CLD", L2A_BandDescription_CLD, RES_20M, TL_QI_DATA},
128
    {"CLD", L2A_BandDescription_CLD, RES_60M, TL_QI_DATA},
129
    {"SNW", L2A_BandDescription_SNW, RES_20M, TL_QI_DATA},
130
    {"SNW", L2A_BandDescription_SNW, RES_60M, TL_QI_DATA},
131
};
132
133
static bool SENTINEL2isZipped(const char *pszHeader, int nHeaderBytes);
134
static const char *SENTINEL2GetOption(GDALOpenInfo *poOpenInfo,
135
                                      const char *pszName,
136
                                      const char *pszDefaultVal = nullptr);
137
static bool SENTINEL2GetTileInfo(const char *pszFilename, int *pnWidth,
138
                                 int *pnHeight, int *pnBits);
139
140
/************************************************************************/
141
/*                            IsS2Prefixed()                            */
142
/************************************************************************/
143
144
// IsS2Prefixed(pszStr, "foo") checks that pszStr starts with
145
// "S2x_foo" where x=A/B/C/D
146
static bool IsS2Prefixed(const char *pszStr, const char *pszPrefixAfterS2X)
147
1.91M
{
148
1.91M
    return pszStr[0] == 'S' && pszStr[1] == '2' && pszStr[2] >= 'A' &&
149
710
           pszStr[2] <= 'Z' &&
150
686
           (*pszPrefixAfterS2X == 0 ||
151
686
            STARTS_WITH_CI(pszStr + 3, pszPrefixAfterS2X));
152
1.91M
}
153
154
/************************************************************************/
155
/*                         SENTINEL2GranuleInfo                         */
156
/************************************************************************/
157
158
class SENTINEL2GranuleInfo
159
{
160
  public:
161
    CPLString osPath{};
162
    CPLString osBandPrefixPath{};  // for Sentinel 2C SafeCompact
163
    double dfMinX = 0, dfMinY = 0, dfMaxX = 0, dfMaxY = 0;
164
    int nWidth = 0, nHeight = 0;
165
};
166
167
/************************************************************************/
168
/* ==================================================================== */
169
/*                         SENTINEL2Dataset                             */
170
/* ==================================================================== */
171
/************************************************************************/
172
173
class SENTINEL2DatasetContainer final : public GDALPamDataset
174
{
175
  public:
176
320
    SENTINEL2DatasetContainer() = default;
177
    ~SENTINEL2DatasetContainer() override;
178
};
179
180
320
SENTINEL2DatasetContainer::~SENTINEL2DatasetContainer() = default;
181
182
class SENTINEL2Dataset final : public VRTDataset
183
{
184
    std::vector<CPLString> aosNonJP2Files{};
185
186
    void AddL1CL2ABandMetadata(SENTINEL2Level eLevel, CPLXMLNode *psRoot,
187
                               const std::vector<CPLString> &aosBands);
188
189
    static SENTINEL2Dataset *CreateL1CL2ADataset(
190
        SENTINEL2Level eLevel, SENTINEL2ProductType pType, bool bIsSafeCompact,
191
        const std::vector<CPLString> &aosGranuleList,
192
        const std::vector<L1CSafeCompatGranuleDescription>
193
            &aoL1CSafeCompactGranuleList,
194
        std::vector<CPLString> &aosNonJP2Files, int nSubDSPrecision,
195
        bool bIsPreview, bool bIsTCI, int nSubDSEPSGCode, bool bAlpha,
196
        const std::vector<CPLString> &aosBands, int nSaturatedVal,
197
        int nNodataVal, const CPLString &osProductURI);
198
199
  public:
200
    SENTINEL2Dataset(int nXSize, int nYSize);
201
    ~SENTINEL2Dataset() override;
202
203
    char **GetFileList() override;
204
205
    static GDALDataset *Open(GDALOpenInfo *);
206
    static GDALDataset *OpenL1BUserProduct(GDALOpenInfo *);
207
    static GDALDataset *
208
    OpenL1BGranule(const char *pszFilename, CPLXMLNode **ppsRoot = nullptr,
209
                   int nResolutionOfInterest = 0,
210
                   std::set<CPLString> *poBandSet = nullptr);
211
    static GDALDataset *OpenL1BSubdataset(GDALOpenInfo *);
212
    static GDALDataset *OpenL1BSubdatasetWithGeoloc(GDALOpenInfo *);
213
    static GDALDataset *OpenL1C_L2A(const char *pszFilename,
214
                                    SENTINEL2Level eLevel);
215
    static GDALDataset *OpenL1CTile(const char *pszFilename,
216
                                    CPLXMLNode **ppsRootMainMTD = nullptr,
217
                                    int nResolutionOfInterest = 0,
218
                                    std::set<CPLString> *poBandSet = nullptr);
219
    static GDALDataset *OpenL1CTileSubdataset(GDALOpenInfo *);
220
    static GDALDataset *OpenL1C_L2ASubdataset(GDALOpenInfo *,
221
                                              SENTINEL2Level eLevel);
222
223
    static int Identify(GDALOpenInfo *);
224
};
225
226
/************************************************************************/
227
/* ==================================================================== */
228
/*                         SENTINEL2AlphaBand                           */
229
/* ==================================================================== */
230
/************************************************************************/
231
232
class SENTINEL2AlphaBand final : public VRTSourcedRasterBand
233
{
234
    int m_nSaturatedVal;
235
    int m_nNodataVal;
236
237
  public:
238
    SENTINEL2AlphaBand(GDALDataset *poDS, int nBand, GDALDataType eType,
239
                       int nXSize, int nYSize, int nSaturatedVal,
240
                       int nNodataVal);
241
242
    CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
243
                     GDALDataType, GSpacing nPixelSpace, GSpacing nLineSpace,
244
                     GDALRasterIOExtraArg *psExtraArg) override;
245
};
246
247
/************************************************************************/
248
/*                         SENTINEL2AlphaBand()                         */
249
/************************************************************************/
250
251
SENTINEL2AlphaBand::SENTINEL2AlphaBand(GDALDataset *poDSIn, int nBandIn,
252
                                       GDALDataType eType, int nXSize,
253
                                       int nYSize, int nSaturatedVal,
254
                                       int nNodataVal)
255
0
    : VRTSourcedRasterBand(poDSIn, nBandIn, eType, nXSize, nYSize),
256
0
      m_nSaturatedVal(nSaturatedVal), m_nNodataVal(nNodataVal)
257
0
{
258
0
}
259
260
/************************************************************************/
261
/*                             IRasterIO()                              */
262
/************************************************************************/
263
264
CPLErr SENTINEL2AlphaBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
265
                                     int nXSize, int nYSize, void *pData,
266
                                     int nBufXSize, int nBufYSize,
267
                                     GDALDataType eBufType,
268
                                     GSpacing nPixelSpace, GSpacing nLineSpace,
269
                                     GDALRasterIOExtraArg *psExtraArg)
270
0
{
271
    // Query the first band. Quite arbitrary, but hopefully all bands have
272
    // the same nodata/saturated pixels.
273
0
    CPLErr eErr = poDS->GetRasterBand(1)->RasterIO(
274
0
        eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
275
0
        eBufType, nPixelSpace, nLineSpace, psExtraArg);
276
0
    if (eErr == CE_None)
277
0
    {
278
0
        const char *pszNBITS =
279
0
            GetMetadataItem(GDALMD_NBITS, GDAL_MDD_IMAGE_STRUCTURE);
280
0
        const int nBits = (pszNBITS) ? atoi(pszNBITS) : 16;
281
0
        const GUInt16 nMaxVal = (nBits > 8 && nBits <= 16)
282
0
                                    ? static_cast<GUInt16>((1 << nBits) - 1)
283
0
                                    : 65535;
284
285
        // Replace pixels matching m_nSaturatedVal and m_nNodataVal by 0
286
        // and others by the maxVal.
287
0
        for (int iY = 0; iY < nBufYSize; iY++)
288
0
        {
289
0
            for (int iX = 0; iX < nBufXSize; iX++)
290
0
            {
291
                // Optimized path for likely most common case
292
0
                if (eBufType == GDT_UInt16)
293
0
                {
294
0
                    GUInt16 *panPtr = reinterpret_cast<GUInt16 *>(
295
0
                        static_cast<GByte *>(pData) + iY * nLineSpace +
296
0
                        iX * nPixelSpace);
297
0
                    if (*panPtr == 0 || *panPtr == m_nSaturatedVal ||
298
0
                        *panPtr == m_nNodataVal)
299
0
                    {
300
0
                        *panPtr = 0;
301
0
                    }
302
0
                    else
303
0
                        *panPtr = nMaxVal;
304
0
                }
305
                // Generic path for other datatypes
306
0
                else
307
0
                {
308
0
                    double dfVal;
309
0
                    GDALCopyWords(static_cast<GByte *>(pData) +
310
0
                                      iY * nLineSpace + iX * nPixelSpace,
311
0
                                  eBufType, 0, &dfVal, GDT_Float64, 0, 1);
312
0
                    if (dfVal == 0.0 || dfVal == m_nSaturatedVal ||
313
0
                        dfVal == m_nNodataVal)
314
0
                    {
315
0
                        dfVal = 0;
316
0
                    }
317
0
                    else
318
0
                        dfVal = nMaxVal;
319
0
                    GDALCopyWords(&dfVal, GDT_Float64, 0,
320
0
                                  static_cast<GByte *>(pData) +
321
0
                                      iY * nLineSpace + iX * nPixelSpace,
322
0
                                  eBufType, 0, 1);
323
0
                }
324
0
            }
325
0
        }
326
0
    }
327
328
0
    return eErr;
329
0
}
330
331
/************************************************************************/
332
/*                          SENTINEL2Dataset()                          */
333
/************************************************************************/
334
335
SENTINEL2Dataset::SENTINEL2Dataset(int nXSize, int nYSize)
336
0
    : VRTDataset(nXSize, nYSize)
337
0
{
338
0
    poDriver = nullptr;
339
0
    SetWritable(FALSE);
340
0
}
341
342
/************************************************************************/
343
/*                         ~SENTINEL2Dataset()                          */
344
/************************************************************************/
345
346
SENTINEL2Dataset::~SENTINEL2Dataset()
347
0
{
348
0
}
349
350
/************************************************************************/
351
/*                            GetFileList()                             */
352
/************************************************************************/
353
354
char **SENTINEL2Dataset::GetFileList()
355
0
{
356
0
    CPLStringList aosList;
357
0
    for (size_t i = 0; i < aosNonJP2Files.size(); i++)
358
0
        aosList.AddString(aosNonJP2Files[i]);
359
0
    char **papszFileList = VRTDataset::GetFileList();
360
0
    for (char **papszIter = papszFileList; papszIter && *papszIter; ++papszIter)
361
0
        aosList.AddString(*papszIter);
362
0
    CSLDestroy(papszFileList);
363
0
    return aosList.StealList();
364
0
}
365
366
/************************************************************************/
367
/*                              Identify()                              */
368
/************************************************************************/
369
370
int SENTINEL2Dataset::Identify(GDALOpenInfo *poOpenInfo)
371
477k
{
372
477k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "SENTINEL2_L1B:"))
373
0
        return TRUE;
374
477k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "SENTINEL2_L1B_WITH_GEOLOC:"))
375
0
        return TRUE;
376
477k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "SENTINEL2_L1C:"))
377
0
        return TRUE;
378
477k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "SENTINEL2_L1C_TILE:"))
379
0
        return TRUE;
380
477k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "SENTINEL2_L2A:"))
381
0
        return TRUE;
382
383
477k
    const char *pszJustFilename = CPLGetFilename(poOpenInfo->pszFilename);
384
385
    // We don't handle direct tile access for L1C SafeCompact products
386
    // We could, but this isn't just done yet.
387
477k
    if (EQUAL(pszJustFilename, "MTD_TL.xml"))
388
0
        return FALSE;
389
390
    /* Accept directly .zip as provided by https://scihub.esa.int/
391
     * First we check just by file name as it is faster than looking
392
     * inside to detect content. */
393
477k
    if ((IsS2Prefixed(pszJustFilename, "_MSIL1C_") ||
394
477k
         IsS2Prefixed(pszJustFilename, "_MSIL2A_") ||
395
477k
         IsS2Prefixed(pszJustFilename, "_OPER_PRD_MSI") ||
396
477k
         IsS2Prefixed(pszJustFilename, "_USER_PRD_MSI")) &&
397
100
        EQUAL(CPLGetExtensionSafe(pszJustFilename).c_str(), "zip"))
398
86
    {
399
86
        return TRUE;
400
86
    }
401
402
477k
    if (poOpenInfo->nHeaderBytes < 100)
403
390k
        return FALSE;
404
405
87.1k
    const char *pszHeader =
406
87.1k
        reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
407
408
87.1k
    if (strstr(pszHeader, "<n1:Level-1B_User_Product") != nullptr &&
409
380
        strstr(pszHeader, "User_Product_Level-1B.xsd") != nullptr)
410
166
        return TRUE;
411
412
87.0k
    if (strstr(pszHeader, "<n1:Level-1B_Granule_ID") != nullptr &&
413
916
        strstr(pszHeader, "S2_PDI_Level-1B_Granule_Metadata.xsd") != nullptr)
414
540
        return TRUE;
415
416
86.4k
    if (strstr(pszHeader, "<n1:Level-1C_User_Product") != nullptr &&
417
316
        strstr(pszHeader, "User_Product_Level-1C.xsd") != nullptr)
418
186
        return TRUE;
419
420
86.2k
    if (strstr(pszHeader, "<n1:Level-1C_Tile_ID") != nullptr &&
421
369
        strstr(pszHeader, "S2_PDI_Level-1C_Tile_Metadata.xsd") != nullptr)
422
208
        return TRUE;
423
424
86.0k
    if (strstr(pszHeader, "<n1:Level-2A_User_Product") != nullptr &&
425
250
        strstr(pszHeader, "User_Product_Level-2A") != nullptr)
426
30
        return TRUE;
427
428
86.0k
    if (SENTINEL2isZipped(pszHeader, poOpenInfo->nHeaderBytes))
429
0
        return TRUE;
430
431
86.0k
    return FALSE;
432
86.0k
}
433
434
/************************************************************************/
435
/*                      SENTINEL2_CPLXMLNodeHolder                      */
436
/************************************************************************/
437
438
class SENTINEL2_CPLXMLNodeHolder
439
{
440
    CPLXMLNode *m_psNode = nullptr;
441
442
    CPL_DISALLOW_COPY_ASSIGN(SENTINEL2_CPLXMLNodeHolder)
443
444
  public:
445
434
    explicit SENTINEL2_CPLXMLNodeHolder(CPLXMLNode *psNode) : m_psNode(psNode)
446
434
    {
447
434
    }
448
449
    ~SENTINEL2_CPLXMLNodeHolder()
450
434
    {
451
434
        if (m_psNode)
452
434
            CPLDestroyXMLNode(m_psNode);
453
434
    }
454
455
    CPLXMLNode *Release()
456
0
    {
457
0
        CPLXMLNode *psRet = m_psNode;
458
0
        m_psNode = nullptr;
459
0
        return psRet;
460
0
    }
461
};
462
463
/************************************************************************/
464
/*                                Open()                                */
465
/************************************************************************/
466
467
GDALDataset *SENTINEL2Dataset::Open(GDALOpenInfo *poOpenInfo)
468
651
{
469
651
    if (!Identify(poOpenInfo))
470
43
    {
471
43
        return nullptr;
472
43
    }
473
474
608
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "SENTINEL2_L1B:"))
475
0
    {
476
0
        CPLDebug("SENTINEL2", "Using OpenL1BSubdataset");
477
0
        return OpenL1BSubdataset(poOpenInfo);
478
0
    }
479
480
608
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "SENTINEL2_L1B_WITH_GEOLOC:"))
481
0
    {
482
0
        CPLDebug("SENTINEL2", "Using OpenL1BSubdatasetWithGeoloc");
483
0
        return OpenL1BSubdatasetWithGeoloc(poOpenInfo);
484
0
    }
485
486
608
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "SENTINEL2_L1C:"))
487
0
    {
488
0
        CPLDebug("SENTINEL2", "Using OpenL1C_L2ASubdataset");
489
0
        return OpenL1C_L2ASubdataset(poOpenInfo, SENTINEL2_L1C);
490
0
    }
491
492
608
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "SENTINEL2_L1C_TILE:"))
493
0
    {
494
0
        CPLDebug("SENTINEL2", "Using OpenL1CTileSubdataset");
495
0
        return OpenL1CTileSubdataset(poOpenInfo);
496
0
    }
497
498
608
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "SENTINEL2_L2A:"))
499
0
    {
500
0
        CPLDebug("SENTINEL2", "Using OpenL1C_L2ASubdataset");
501
0
        return OpenL1C_L2ASubdataset(poOpenInfo, SENTINEL2_L2A);
502
0
    }
503
504
608
    const char *pszJustFilename = CPLGetFilename(poOpenInfo->pszFilename);
505
608
    if ((IsS2Prefixed(pszJustFilename, "_OPER_PRD_MSI") ||
506
608
         IsS2Prefixed(pszJustFilename, "_USER_PRD_MSI")) &&
507
44
        EQUAL(CPLGetExtensionSafe(pszJustFilename).c_str(), "zip"))
508
43
    {
509
43
        const CPLString osBasename(CPLGetBasenameSafe(pszJustFilename));
510
43
        CPLString osFilename(poOpenInfo->pszFilename);
511
43
        CPLString osMTD(osBasename);
512
        // Normally given above constraints, osMTD.size() should be >= 16
513
        // but if pszJustFilename is too long, CPLGetBasenameSafe().c_str() will return
514
        // an empty string.
515
43
        if (osMTD.size() < 16)
516
0
            return nullptr;
517
43
        osMTD[9] = 'M';
518
43
        osMTD[10] = 'T';
519
43
        osMTD[11] = 'D';
520
43
        osMTD[13] = 'S';
521
43
        osMTD[14] = 'A';
522
43
        osMTD[15] = 'F';
523
43
        CPLString osSAFE(CPLString(osBasename) + ".SAFE");
524
43
        CPL_IGNORE_RET_VAL(osBasename);
525
43
        osFilename = osFilename + "/" + osSAFE + "/" + osMTD + ".xml";
526
43
        if (strncmp(osFilename, "/vsizip/", strlen("/vsizip/")) != 0)
527
43
            osFilename = "/vsizip/" + osFilename;
528
43
        CPLDebug("SENTINEL2", "Trying %s", osFilename.c_str());
529
43
        GDALOpenInfo oOpenInfo(osFilename, GA_ReadOnly);
530
43
        return Open(&oOpenInfo);
531
43
    }
532
565
    else if (IsS2Prefixed(pszJustFilename, "_MSIL1C_") &&
533
0
             EQUAL(CPLGetExtensionSafe(pszJustFilename).c_str(), "zip"))
534
0
    {
535
0
        CPLString osFilename(poOpenInfo->pszFilename);
536
0
        CPLString osSAFE(CPLGetBasenameSafe(pszJustFilename));
537
        // S2B_MSIL1C_20171004T233419_N0206_R001_T54DWM_20171005T001811.SAFE.zip
538
        // has .SAFE.zip extension, but other products have just a .zip
539
        // extension. So for the subdir in the zip only add .SAFE when needed
540
0
        if (!EQUAL(CPLGetExtensionSafe(osSAFE).c_str(), "SAFE"))
541
0
            osSAFE += ".SAFE";
542
0
        osFilename = osFilename + "/" + osSAFE + "/MTD_MSIL1C.xml";
543
0
        if (strncmp(osFilename, "/vsizip/", strlen("/vsizip/")) != 0)
544
0
            osFilename = "/vsizip/" + osFilename;
545
0
        CPLDebug("SENTINEL2", "Trying %s", osFilename.c_str());
546
0
        GDALOpenInfo oOpenInfo(osFilename, GA_ReadOnly);
547
0
        return Open(&oOpenInfo);
548
0
    }
549
565
    else if (IsS2Prefixed(pszJustFilename, "_MSIL2A_") &&
550
0
             EQUAL(CPLGetExtensionSafe(pszJustFilename).c_str(), "zip"))
551
0
    {
552
0
        CPLString osFilename(poOpenInfo->pszFilename);
553
0
        CPLString osSAFE(CPLGetBasenameSafe(pszJustFilename));
554
        // S2B_MSIL1C_20171004T233419_N0206_R001_T54DWM_20171005T001811.SAFE.zip
555
        // has .SAFE.zip extension, but other products have just a .zip
556
        // extension. So for the subdir in the zip only add .SAFE when needed
557
0
        if (!EQUAL(CPLGetExtensionSafe(osSAFE).c_str(), "SAFE"))
558
0
            osSAFE += ".SAFE";
559
0
        osFilename = osFilename + "/" + osSAFE + "/MTD_MSIL2A.xml";
560
0
        if (strncmp(osFilename, "/vsizip/", strlen("/vsizip/")) != 0)
561
0
            osFilename = "/vsizip/" + osFilename;
562
0
        CPLDebug("SENTINEL2", "Trying %s", osFilename.c_str());
563
0
        GDALOpenInfo oOpenInfo(osFilename, GA_ReadOnly);
564
0
        return Open(&oOpenInfo);
565
0
    }
566
567
565
    const char *pszHeader =
568
565
        reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
569
570
565
    if (strstr(pszHeader, "<n1:Level-1B_User_Product") != nullptr &&
571
134
        strstr(pszHeader, "User_Product_Level-1B.xsd") != nullptr)
572
83
    {
573
83
        CPLDebug("SENTINEL2", "Trying OpenL1BUserProduct");
574
83
        return OpenL1BUserProduct(poOpenInfo);
575
83
    }
576
577
482
    if (strstr(pszHeader, "<n1:Level-1B_Granule_ID") != nullptr &&
578
270
        strstr(pszHeader, "S2_PDI_Level-1B_Granule_Metadata.xsd") != nullptr)
579
270
    {
580
270
        CPLDebug("SENTINEL2", "Trying OpenL1BGranule");
581
270
        return OpenL1BGranule(poOpenInfo->pszFilename);
582
270
    }
583
584
212
    if (strstr(pszHeader, "<n1:Level-1C_User_Product") != nullptr &&
585
98
        strstr(pszHeader, "User_Product_Level-1C.xsd") != nullptr)
586
93
    {
587
93
        CPLDebug("SENTINEL2", "Trying OpenL1C_L2A");
588
93
        return OpenL1C_L2A(poOpenInfo->pszFilename, SENTINEL2_L1C);
589
93
    }
590
591
119
    if (strstr(pszHeader, "<n1:Level-1C_Tile_ID") != nullptr &&
592
104
        strstr(pszHeader, "S2_PDI_Level-1C_Tile_Metadata.xsd") != nullptr)
593
104
    {
594
104
        CPLDebug("SENTINEL2", "Trying OpenL1CTile");
595
104
        return OpenL1CTile(poOpenInfo->pszFilename);
596
104
    }
597
598
15
    if (strstr(pszHeader, "<n1:Level-2A_User_Product") != nullptr &&
599
15
        strstr(pszHeader, "User_Product_Level-2A") != nullptr)
600
15
    {
601
15
        CPLDebug("SENTINEL2", "Trying OpenL1C_L2A");
602
15
        return OpenL1C_L2A(poOpenInfo->pszFilename, SENTINEL2_L2A);
603
15
    }
604
605
0
    if (SENTINEL2isZipped(pszHeader, poOpenInfo->nHeaderBytes))
606
0
    {
607
0
        CPLString osFilename(poOpenInfo->pszFilename);
608
0
        if (strncmp(osFilename, "/vsizip/", strlen("/vsizip/")) != 0)
609
0
            osFilename = "/vsizip/" + osFilename;
610
611
0
        auto psDir = VSIOpenDir(osFilename.c_str(), 1, nullptr);
612
0
        if (psDir == nullptr)
613
0
        {
614
0
            CPLError(CE_Failure, CPLE_AppDefined,
615
0
                     "SENTINEL2: Cannot open ZIP file %s", osFilename.c_str());
616
0
            return nullptr;
617
0
        }
618
0
        while (const VSIDIREntry *psEntry = VSIGetNextDirEntry(psDir))
619
0
        {
620
0
            const char *pszInsideFilename = CPLGetFilename(psEntry->pszName);
621
0
            if (VSI_ISREG(psEntry->nMode) &&
622
0
                (STARTS_WITH_CI(pszInsideFilename, "MTD_MSIL2A") ||
623
0
                 STARTS_WITH_CI(pszInsideFilename, "MTD_MSIL1C") ||
624
0
                 IsS2Prefixed(pszInsideFilename, "_OPER_MTD_SAFL1B") ||
625
0
                 IsS2Prefixed(pszInsideFilename, "_OPER_MTD_SAFL1C") ||
626
0
                 IsS2Prefixed(pszInsideFilename, "_USER_MTD_SAFL2A") ||
627
0
                 IsS2Prefixed(pszInsideFilename, "_USER_MTD_SAFL2A")))
628
0
            {
629
0
                osFilename = osFilename + "/" + psEntry->pszName;
630
0
                CPLDebug("SENTINEL2", "Trying %s", osFilename.c_str());
631
0
                GDALOpenInfo oOpenInfo(osFilename, GA_ReadOnly);
632
0
                VSICloseDir(psDir);
633
0
                return Open(&oOpenInfo);
634
0
            }
635
0
        }
636
0
        VSICloseDir(psDir);
637
0
    }
638
639
0
    return nullptr;
640
0
}
641
642
/************************************************************************/
643
/*                         SENTINEL2isZipped()                          */
644
/************************************************************************/
645
646
static bool SENTINEL2isZipped(const char *pszHeader, int nHeaderBytes)
647
86.0k
{
648
86.0k
    if (nHeaderBytes < 50)
649
0
        return FALSE;
650
651
    /* According to Sentinel-2 Products Specification Document,
652
     * all files are located inside a folder with a specific name pattern
653
     * Ref: S2-PDGS-TAS-DI-PSD Issue: 14.6.
654
     */
655
86.0k
    return (
656
        // inside a ZIP file
657
86.0k
        memcmp(pszHeader, "\x50\x4b", 2) == 0 &&
658
411
        (
659
            // a "4.2.1 Compact Naming Convention" confirming file
660
411
            (memcmp(pszHeader + 34, "MSIL2A", 6) == 0 ||
661
411
             memcmp(pszHeader + 34, "MSIL1C", 6) == 0) ||
662
            // a "4.2 S2 User Product Naming Convention" confirming file
663
411
            (memcmp(pszHeader + 34, "OPER_PRD_MSIL2A", 15) == 0 ||
664
411
             memcmp(pszHeader + 34, "OPER_PRD_MSIL1B", 15) == 0 ||
665
411
             memcmp(pszHeader + 34, "OPER_PRD_MSIL1C", 15) == 0) ||
666
            // some old / validation naming convention
667
411
            (memcmp(pszHeader + 34, "USER_PRD_MSIL2A", 15) == 0 ||
668
411
             memcmp(pszHeader + 34, "USER_PRD_MSIL1B", 15) == 0 ||
669
411
             memcmp(pszHeader + 34, "USER_PRD_MSIL1C", 15) == 0)));
670
86.0k
}
671
672
/************************************************************************/
673
/*                        SENTINEL2GetBandDesc()                        */
674
/************************************************************************/
675
676
static const SENTINEL2BandDescription *
677
SENTINEL2GetBandDesc(const char *pszBandName)
678
91
{
679
91
    for (const auto &sBandDesc : asBandDesc)
680
637
    {
681
637
        if (EQUAL(sBandDesc.pszBandName, pszBandName))
682
91
            return &sBandDesc;
683
637
    }
684
0
    return nullptr;
685
91
}
686
687
/************************************************************************/
688
/*                      SENTINEL2GetL2ABandDesc()                       */
689
/************************************************************************/
690
691
static const SENTINEL2_L2A_BandDescription *
692
SENTINEL2GetL2ABandDesc(const char *pszBandName)
693
0
{
694
0
    for (const auto &sBandDesc : asL2ABandDesc)
695
0
    {
696
0
        if (EQUAL(sBandDesc.pszBandName, pszBandName))
697
0
            return &sBandDesc;
698
0
    }
699
0
    return nullptr;
700
0
}
701
702
/************************************************************************/
703
/*                      SENTINEL2GetGranuleInfo()                       */
704
/************************************************************************/
705
706
static bool SENTINEL2GetGranuleInfo(
707
    SENTINEL2Level eLevel, const CPLString &osGranuleMTDPath,
708
    int nDesiredResolution, int *pnEPSGCode = nullptr, double *pdfULX = nullptr,
709
    double *pdfULY = nullptr, int *pnResolution = nullptr,
710
    int *pnWidth = nullptr, int *pnHeight = nullptr)
711
30
{
712
30
    static bool bTryOptimization = true;
713
30
    CPLXMLNode *psRoot = nullptr;
714
715
30
    if (bTryOptimization)
716
30
    {
717
        /* Small optimization: in practice the interesting info are in the */
718
        /* first bytes of the Granule MTD, which can be very long sometimes */
719
        /* so only read them, and hack the buffer a bit to form a valid XML */
720
30
        char szBuffer[3072];
721
30
        VSILFILE *fp = VSIFOpenL(osGranuleMTDPath, "rb");
722
30
        size_t nRead = 0;
723
30
        if (fp == nullptr ||
724
0
            (nRead = VSIFReadL(szBuffer, 1, sizeof(szBuffer) - 1, fp)) == 0)
725
30
        {
726
30
            if (fp)
727
0
                VSIFCloseL(fp);
728
30
            CPLError(CE_Failure, CPLE_AppDefined,
729
30
                     "SENTINEL2GetGranuleInfo: Cannot read %s",
730
30
                     osGranuleMTDPath.c_str());
731
30
            return false;
732
30
        }
733
0
        szBuffer[nRead] = 0;
734
0
        VSIFCloseL(fp);
735
0
        char *pszTileGeocoding = strstr(szBuffer, "</Tile_Geocoding>");
736
0
        if (eLevel == SENTINEL2_L1C && pszTileGeocoding != nullptr &&
737
0
            strstr(szBuffer, "<n1:Level-1C_Tile_ID") != nullptr &&
738
0
            strstr(szBuffer, "<n1:Geometric_Info") != nullptr &&
739
0
            static_cast<size_t>(pszTileGeocoding - szBuffer) <
740
0
                sizeof(szBuffer) -
741
0
                    strlen("</Tile_Geocoding></n1:Geometric_Info></"
742
0
                           "n1:Level-1C_Tile_ID>") -
743
0
                    1)
744
0
        {
745
0
            strcpy(
746
0
                pszTileGeocoding,
747
0
                "</Tile_Geocoding></n1:Geometric_Info></n1:Level-1C_Tile_ID>");
748
0
            psRoot = CPLParseXMLString(szBuffer);
749
0
        }
750
0
        else if (eLevel == SENTINEL2_L2A && pszTileGeocoding != nullptr &&
751
0
                 strstr(szBuffer, "<n1:Level-2A_Tile_ID") != nullptr &&
752
0
                 strstr(szBuffer, "<n1:Geometric_Info") != nullptr &&
753
0
                 static_cast<size_t>(pszTileGeocoding - szBuffer) <
754
0
                     sizeof(szBuffer) -
755
0
                         strlen("</Tile_Geocoding></n1:Geometric_Info></"
756
0
                                "n1:Level-2A_Tile_ID>") -
757
0
                         1)
758
0
        {
759
0
            strcpy(
760
0
                pszTileGeocoding,
761
0
                "</Tile_Geocoding></n1:Geometric_Info></n1:Level-2A_Tile_ID>");
762
0
            psRoot = CPLParseXMLString(szBuffer);
763
0
        }
764
0
        else
765
0
            bTryOptimization = false;
766
0
    }
767
768
    // If the above doesn't work, then read the whole file...
769
0
    if (psRoot == nullptr)
770
0
        psRoot = CPLParseXMLFile(osGranuleMTDPath);
771
0
    if (psRoot == nullptr)
772
0
    {
773
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse XML file '%s'",
774
0
                 osGranuleMTDPath.c_str());
775
0
        return false;
776
0
    }
777
0
    SENTINEL2_CPLXMLNodeHolder oXMLHolder(psRoot);
778
0
    CPLStripXMLNamespace(psRoot, nullptr, TRUE);
779
780
0
    const char *pszNodePath =
781
0
        (eLevel == SENTINEL2_L1C)
782
0
            ? "=Level-1C_Tile_ID.Geometric_Info.Tile_Geocoding"
783
0
            : "=Level-2A_Tile_ID.Geometric_Info.Tile_Geocoding";
784
0
    CPLXMLNode *psTileGeocoding = CPLGetXMLNode(psRoot, pszNodePath);
785
0
    if (psTileGeocoding == nullptr)
786
0
    {
787
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s in %s",
788
0
                 pszNodePath, osGranuleMTDPath.c_str());
789
0
        return false;
790
0
    }
791
792
0
    const char *pszCSCode =
793
0
        CPLGetXMLValue(psTileGeocoding, "HORIZONTAL_CS_CODE", nullptr);
794
0
    if (pszCSCode == nullptr)
795
0
    {
796
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s in %s",
797
0
                 "HORIZONTAL_CS_CODE", osGranuleMTDPath.c_str());
798
0
        return false;
799
0
    }
800
0
    if (!STARTS_WITH_CI(pszCSCode, "EPSG:"))
801
0
    {
802
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid CS code (%s) for %s",
803
0
                 pszCSCode, osGranuleMTDPath.c_str());
804
0
        return false;
805
0
    }
806
0
    int nEPSGCode = atoi(pszCSCode + strlen("EPSG:"));
807
0
    if (pnEPSGCode != nullptr)
808
0
        *pnEPSGCode = nEPSGCode;
809
810
0
    for (CPLXMLNode *psIter = psTileGeocoding->psChild; psIter != nullptr;
811
0
         psIter = psIter->psNext)
812
0
    {
813
0
        if (psIter->eType != CXT_Element)
814
0
            continue;
815
0
        if (EQUAL(psIter->pszValue, "Size") &&
816
0
            (nDesiredResolution == 0 ||
817
0
             atoi(CPLGetXMLValue(psIter, "resolution", "")) ==
818
0
                 nDesiredResolution))
819
0
        {
820
0
            nDesiredResolution = atoi(CPLGetXMLValue(psIter, "resolution", ""));
821
0
            const char *pszRows = CPLGetXMLValue(psIter, "NROWS", nullptr);
822
0
            if (pszRows == nullptr)
823
0
            {
824
0
                CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s in %s",
825
0
                         "NROWS", osGranuleMTDPath.c_str());
826
0
                return false;
827
0
            }
828
0
            const char *pszCols = CPLGetXMLValue(psIter, "NCOLS", nullptr);
829
0
            if (pszCols == nullptr)
830
0
            {
831
0
                CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s in %s",
832
0
                         "NCOLS", osGranuleMTDPath.c_str());
833
0
                return false;
834
0
            }
835
0
            if (pnResolution)
836
0
                *pnResolution = nDesiredResolution;
837
0
            if (pnWidth)
838
0
                *pnWidth = atoi(pszCols);
839
0
            if (pnHeight)
840
0
                *pnHeight = atoi(pszRows);
841
0
        }
842
0
        else if (EQUAL(psIter->pszValue, "Geoposition") &&
843
0
                 (nDesiredResolution == 0 ||
844
0
                  atoi(CPLGetXMLValue(psIter, "resolution", "")) ==
845
0
                      nDesiredResolution))
846
0
        {
847
0
            nDesiredResolution = atoi(CPLGetXMLValue(psIter, "resolution", ""));
848
0
            const char *pszULX = CPLGetXMLValue(psIter, "ULX", nullptr);
849
0
            if (pszULX == nullptr)
850
0
            {
851
0
                CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s in %s",
852
0
                         "ULX", osGranuleMTDPath.c_str());
853
0
                return false;
854
0
            }
855
0
            const char *pszULY = CPLGetXMLValue(psIter, "ULY", nullptr);
856
0
            if (pszULY == nullptr)
857
0
            {
858
0
                CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s in %s",
859
0
                         "ULY", osGranuleMTDPath.c_str());
860
0
                return false;
861
0
            }
862
0
            if (pnResolution)
863
0
                *pnResolution = nDesiredResolution;
864
0
            if (pdfULX)
865
0
                *pdfULX = CPLAtof(pszULX);
866
0
            if (pdfULY)
867
0
                *pdfULY = CPLAtof(pszULY);
868
0
        }
869
0
    }
870
871
0
    return true;
872
0
}
873
874
/************************************************************************/
875
/*                     SENTINEL2GetPathSeparator()                      */
876
/************************************************************************/
877
878
// For the sake of simplifying our unit tests, we limit the use of \\ to when
879
// it is strictly necessary. Otherwise we could use CPLFormFilenameSafe()...
880
static char SENTINEL2GetPathSeparator(const char *pszBasename)
881
3.90k
{
882
3.90k
    if (STARTS_WITH_CI(pszBasename, "\\\\?\\"))
883
0
        return '\\';
884
3.90k
    else
885
3.90k
        return '/';
886
3.90k
}
887
888
/************************************************************************/
889
/*                      SENTINEL2GetGranuleList()                       */
890
/************************************************************************/
891
892
static bool SENTINEL2GetGranuleList(
893
    CPLXMLNode *psMainMTD, SENTINEL2Level eLevel, const char *pszFilename,
894
    std::vector<CPLString> &osList, std::set<int> *poSetResolutions = nullptr,
895
    std::map<int, std::set<CPLString>> *poMapResolutionsToBands = nullptr)
896
12
{
897
12
    const char *pszNodePath =
898
12
        (eLevel == SENTINEL2_L1B)   ? "Level-1B_User_Product"
899
12
        : (eLevel == SENTINEL2_L1C) ? "Level-1C_User_Product"
900
7
                                    : "Level-2A_User_Product";
901
902
12
    CPLXMLNode *psRoot =
903
12
        CPLGetXMLNode(psMainMTD, CPLSPrintf("=%s", pszNodePath));
904
12
    if (psRoot == nullptr)
905
0
    {
906
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find =%s", pszNodePath);
907
0
        return false;
908
0
    }
909
12
    pszNodePath = "General_Info.Product_Info";
910
12
    CPLXMLNode *psProductInfo = CPLGetXMLNode(psRoot, pszNodePath);
911
12
    if (psProductInfo == nullptr && eLevel == SENTINEL2_L2A)
912
5
    {
913
5
        pszNodePath = "General_Info.L2A_Product_Info";
914
5
        psProductInfo = CPLGetXMLNode(psRoot, pszNodePath);
915
5
    }
916
12
    if (psProductInfo == nullptr)
917
0
    {
918
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s", pszNodePath);
919
0
        return false;
920
0
    }
921
922
12
    pszNodePath = "Product_Organisation";
923
12
    CPLXMLNode *psProductOrganisation =
924
12
        CPLGetXMLNode(psProductInfo, pszNodePath);
925
12
    if (psProductOrganisation == nullptr && eLevel == SENTINEL2_L2A)
926
5
    {
927
5
        pszNodePath = "L2A_Product_Organisation";
928
5
        psProductOrganisation = CPLGetXMLNode(psProductInfo, pszNodePath);
929
5
    }
930
12
    if (psProductOrganisation == nullptr)
931
0
    {
932
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s", pszNodePath);
933
0
        return false;
934
0
    }
935
936
12
    CPLString osDirname(CPLGetDirnameSafe(pszFilename));
937
12
#if !defined(_WIN32)
938
12
    char szPointerFilename[2048];
939
12
    int nBytes = static_cast<int>(
940
12
        readlink(pszFilename, szPointerFilename, sizeof(szPointerFilename)));
941
12
    if (nBytes != -1)
942
0
    {
943
0
        const int nOffset =
944
0
            std::min(nBytes, static_cast<int>(sizeof(szPointerFilename) - 1));
945
0
        szPointerFilename[nOffset] = '\0';
946
0
        osDirname = CPLGetDirnameSafe(szPointerFilename);
947
0
    }
948
12
#endif
949
950
12
    const bool bIsMSI2Ap =
951
12
        EQUAL(CPLGetXMLValue(psProductInfo, "PRODUCT_TYPE", ""), "S2MSI2Ap");
952
12
    const bool bIsCompact = EQUAL(
953
12
        CPLGetXMLValue(psProductInfo, "PRODUCT_FORMAT", ""), "SAFE_COMPACT");
954
12
    CPLString oGranuleId("L2A_");
955
12
    std::set<CPLString> aoSetGranuleId;
956
29
    for (CPLXMLNode *psIter = psProductOrganisation->psChild; psIter != nullptr;
957
17
         psIter = psIter->psNext)
958
17
    {
959
17
        if (psIter->eType != CXT_Element ||
960
16
            !EQUAL(psIter->pszValue, "Granule_List"))
961
1
        {
962
1
            continue;
963
1
        }
964
33
        for (CPLXMLNode *psIter2 = psIter->psChild; psIter2 != nullptr;
965
17
             psIter2 = psIter2->psNext)
966
17
        {
967
17
            if (psIter2->eType != CXT_Element ||
968
16
                (!EQUAL(psIter2->pszValue, "Granule") &&
969
13
                 !EQUAL(psIter2->pszValue, "Granules")))
970
1
            {
971
1
                continue;
972
1
            }
973
16
            const char *pszGranuleId =
974
16
                CPLGetXMLValue(psIter2, "granuleIdentifier", nullptr);
975
16
            if (pszGranuleId == nullptr)
976
0
            {
977
0
                CPLDebug("SENTINEL2", "Missing granuleIdentifier attribute");
978
0
                continue;
979
0
            }
980
981
16
            if (eLevel == SENTINEL2_L2A)
982
7
            {
983
138
                for (CPLXMLNode *psIter3 = psIter2->psChild; psIter3 != nullptr;
984
131
                     psIter3 = psIter3->psNext)
985
131
                {
986
131
                    if (psIter3->eType != CXT_Element ||
987
110
                        (!EQUAL(psIter3->pszValue, "IMAGE_ID_2A") &&
988
42
                         !EQUAL(psIter3->pszValue, "IMAGE_FILE") &&
989
42
                         !EQUAL(psIter3->pszValue, "IMAGE_FILE_2A")))
990
21
                    {
991
21
                        continue;
992
21
                    }
993
110
                    const char *pszTileName =
994
110
                        CPLGetXMLValue(psIter3, nullptr, "");
995
110
                    size_t nLen = strlen(pszTileName);
996
                    // If granule name ends with resolution: _60m
997
110
                    if (nLen > 4 && pszTileName[nLen - 4] == '_' &&
998
110
                        pszTileName[nLen - 1] == 'm')
999
110
                    {
1000
110
                        int nResolution = atoi(pszTileName + nLen - 3);
1001
110
                        if (poSetResolutions != nullptr)
1002
110
                            (*poSetResolutions).insert(nResolution);
1003
110
                        if (poMapResolutionsToBands != nullptr)
1004
110
                        {
1005
110
                            nLen -= 4;
1006
110
                            if (nLen > 4 && pszTileName[nLen - 4] == '_' &&
1007
90
                                pszTileName[nLen - 3] == 'B')
1008
72
                            {
1009
72
                                (*poMapResolutionsToBands)[nResolution].insert(
1010
72
                                    CPLString(pszTileName).substr(nLen - 2, 2));
1011
72
                            }
1012
38
                            else if (nLen > strlen("S2A_USER_MSI_") &&
1013
38
                                     pszTileName[8] == '_' &&
1014
20
                                     pszTileName[12] == '_' &&
1015
20
                                     !EQUALN(pszTileName + 9, "MSI", 3))
1016
20
                            {
1017
20
                                (*poMapResolutionsToBands)[nResolution].insert(
1018
20
                                    CPLString(pszTileName).substr(9, 3));
1019
20
                            }
1020
110
                        }
1021
110
                    }
1022
110
                }
1023
7
            }
1024
1025
            // For L2A we can have several time the same granuleIdentifier
1026
            // for the different resolutions
1027
16
            if (aoSetGranuleId.find(pszGranuleId) != aoSetGranuleId.end())
1028
2
                continue;
1029
14
            aoSetGranuleId.insert(pszGranuleId);
1030
1031
            /* S2A_OPER_MSI_L1C_TL_SGS__20151024T023555_A001758_T53JLJ_N01.04
1032
             * --> */
1033
            /* S2A_OPER_MTD_L1C_TL_SGS__20151024T023555_A001758_T53JLJ */
1034
            // S2B_OPER_MSI_L2A_TL_MPS__20180823T122014_A007641_T34VFJ_N02.08
1035
14
            CPLString osGranuleMTD = pszGranuleId;
1036
14
            if (bIsCompact == 0 &&
1037
14
                osGranuleMTD.size() > strlen("S2A_OPER_MSI_") &&
1038
14
                osGranuleMTD[8] == '_' && osGranuleMTD[12] == '_' &&
1039
14
                osGranuleMTD[osGranuleMTD.size() - 7] == '_' &&
1040
14
                osGranuleMTD[osGranuleMTD.size() - 6] == 'N' &&
1041
13
                osGranuleMTD[7] == 'R')
1042
13
            {
1043
13
                osGranuleMTD[9] = 'M';
1044
13
                osGranuleMTD[10] = 'T';
1045
13
                osGranuleMTD[11] = 'D';
1046
13
                osGranuleMTD.resize(osGranuleMTD.size() - 7);
1047
13
            }
1048
1
            else if (bIsMSI2Ap)
1049
0
            {
1050
0
                osGranuleMTD = "MTD_TL";
1051
0
                oGranuleId = "L2A_";
1052
                // S2A_MSIL2A_20170823T094031_N0205_R036_T34VFJ_20170823T094252.SAFE
1053
                // S2A_USER_MSI_L2A_TL_SGS__20170823T133142_A011330_T34VFJ_N02.05
1054
                // --> L2A_T34VFJ_A011330_20170823T094252
1055
0
                const char *pszProductURI =
1056
0
                    CPLGetXMLValue(psProductInfo, "PRODUCT_URI_2A", nullptr);
1057
0
                if (pszProductURI != nullptr)
1058
0
                {
1059
0
                    CPLString psProductURI(pszProductURI);
1060
0
                    if (psProductURI.size() < 60)
1061
0
                    {
1062
0
                        CPLDebug("SENTINEL2", "Invalid PRODUCT_URI_2A");
1063
0
                        continue;
1064
0
                    }
1065
0
                    oGranuleId += psProductURI.substr(38, 7);
1066
0
                    oGranuleId += CPLString(pszGranuleId).substr(41, 8).c_str();
1067
0
                    oGranuleId += psProductURI.substr(45, 15);
1068
0
                    pszGranuleId = oGranuleId.c_str();
1069
0
                }
1070
0
            }
1071
1
            else
1072
1
            {
1073
1
                CPLDebug("SENTINEL2", "Invalid granule ID: %s", pszGranuleId);
1074
1
                continue;
1075
1
            }
1076
13
            osGranuleMTD += ".xml";
1077
1078
13
            const char chSeparator = SENTINEL2GetPathSeparator(osDirname);
1079
13
            CPLString osGranuleMTDPath = osDirname;
1080
13
            osGranuleMTDPath += chSeparator;
1081
13
            osGranuleMTDPath += "GRANULE";
1082
13
            osGranuleMTDPath += chSeparator;
1083
13
            osGranuleMTDPath += pszGranuleId;
1084
13
            osGranuleMTDPath += chSeparator;
1085
13
            osGranuleMTDPath += osGranuleMTD;
1086
13
            if (CPLHasPathTraversal(osGranuleMTDPath.c_str()))
1087
0
            {
1088
0
                CPLError(CE_Failure, CPLE_NotSupported,
1089
0
                         "Path traversal detected in %s",
1090
0
                         osGranuleMTDPath.c_str());
1091
0
                return false;
1092
0
            }
1093
13
            osList.push_back(std::move(osGranuleMTDPath));
1094
13
        }
1095
16
    }
1096
1097
12
    return true;
1098
12
}
1099
1100
/************************************************************************/
1101
/*                  SENTINEL2GetUserProductMetadata()                   */
1102
/************************************************************************/
1103
1104
static char **SENTINEL2GetUserProductMetadata(CPLXMLNode *psMainMTD,
1105
                                              const char *pszRootNode)
1106
21
{
1107
21
    CPLStringList aosList;
1108
1109
21
    CPLXMLNode *psRoot =
1110
21
        CPLGetXMLNode(psMainMTD, CPLSPrintf("=%s", pszRootNode));
1111
21
    if (psRoot == nullptr)
1112
0
    {
1113
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find =%s", pszRootNode);
1114
0
        return nullptr;
1115
0
    }
1116
21
    const char *psPIPath = "General_Info.Product_Info";
1117
21
    CPLXMLNode *psProductInfo = CPLGetXMLNode(psRoot, psPIPath);
1118
21
    if (psProductInfo == nullptr && EQUAL(pszRootNode, "Level-2A_User_Product"))
1119
11
    {
1120
11
        psPIPath = "General_Info.L2A_Product_Info";
1121
11
        psProductInfo = CPLGetXMLNode(psRoot, psPIPath);
1122
11
    }
1123
21
    if (psProductInfo == nullptr)
1124
0
    {
1125
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find =%s", psPIPath);
1126
0
        return nullptr;
1127
0
    }
1128
21
    int nDataTakeCounter = 1;
1129
282
    for (CPLXMLNode *psIter = psProductInfo->psChild; psIter != nullptr;
1130
261
         psIter = psIter->psNext)
1131
261
    {
1132
261
        if (psIter->eType != CXT_Element)
1133
1
            continue;
1134
260
        if (psIter->psChild != nullptr && psIter->psChild->eType == CXT_Text)
1135
185
        {
1136
185
            aosList.AddNameValue(psIter->pszValue, psIter->psChild->pszValue);
1137
185
        }
1138
75
        else if (EQUAL(psIter->pszValue, "Datatake"))
1139
21
        {
1140
21
            CPLString osPrefix(CPLSPrintf("DATATAKE_%d_", nDataTakeCounter));
1141
21
            nDataTakeCounter++;
1142
21
            const char *pszId =
1143
21
                CPLGetXMLValue(psIter, "datatakeIdentifier", nullptr);
1144
21
            if (pszId)
1145
21
                aosList.AddNameValue((osPrefix + "ID").c_str(), pszId);
1146
147
            for (CPLXMLNode *psIter2 = psIter->psChild; psIter2 != nullptr;
1147
126
                 psIter2 = psIter2->psNext)
1148
126
            {
1149
126
                if (psIter2->eType != CXT_Element)
1150
21
                    continue;
1151
105
                if (psIter2->psChild != nullptr &&
1152
105
                    psIter2->psChild->eType == CXT_Text)
1153
105
                {
1154
105
                    aosList.AddNameValue((osPrefix + psIter2->pszValue).c_str(),
1155
105
                                         psIter2->psChild->pszValue);
1156
105
                }
1157
105
            }
1158
21
        }
1159
260
    }
1160
1161
21
    const char *psICPath = "General_Info.Product_Image_Characteristics";
1162
21
    CPLXMLNode *psIC = CPLGetXMLNode(psRoot, psICPath);
1163
21
    if (psIC == nullptr)
1164
11
    {
1165
11
        psICPath = "General_Info.L2A_Product_Image_Characteristics";
1166
11
        psIC = CPLGetXMLNode(psRoot, psICPath);
1167
11
    }
1168
21
    if (psIC != nullptr)
1169
21
    {
1170
381
        for (CPLXMLNode *psIter = psIC->psChild; psIter != nullptr;
1171
360
             psIter = psIter->psNext)
1172
360
        {
1173
360
            if (psIter->eType != CXT_Element ||
1174
354
                !EQUAL(psIter->pszValue, "Special_Values"))
1175
318
            {
1176
318
                continue;
1177
318
            }
1178
42
            const char *pszText =
1179
42
                CPLGetXMLValue(psIter, "SPECIAL_VALUE_TEXT", nullptr);
1180
42
            const char *pszIndex =
1181
42
                CPLGetXMLValue(psIter, "SPECIAL_VALUE_INDEX", nullptr);
1182
42
            if (pszText && pszIndex)
1183
42
            {
1184
42
                aosList.AddNameValue(
1185
42
                    (CPLString("SPECIAL_VALUE_") + pszText).c_str(), pszIndex);
1186
42
            }
1187
42
        }
1188
1189
21
        const char *pszQuantValue =
1190
21
            CPLGetXMLValue(psIC, "QUANTIFICATION_VALUE", nullptr);
1191
21
        if (pszQuantValue != nullptr)
1192
5
            aosList.AddNameValue("QUANTIFICATION_VALUE", pszQuantValue);
1193
1194
21
        const char *pszRCU =
1195
21
            CPLGetXMLValue(psIC, "Reflectance_Conversion.U", nullptr);
1196
21
        if (pszRCU != nullptr)
1197
15
            aosList.AddNameValue("REFLECTANCE_CONVERSION_U", pszRCU);
1198
1199
        // L2A specific
1200
21
        CPLXMLNode *psQVL =
1201
21
            CPLGetXMLNode(psIC, "L1C_L2A_Quantification_Values_List");
1202
21
        if (psQVL == nullptr)
1203
10
        {
1204
10
            psQVL = CPLGetXMLNode(psIC, "Quantification_Values_List");
1205
10
        }
1206
21
        for (CPLXMLNode *psIter = psQVL ? psQVL->psChild : nullptr;
1207
69
             psIter != nullptr; psIter = psIter->psNext)
1208
48
        {
1209
48
            if (psIter->eType != CXT_Element)
1210
4
            {
1211
4
                continue;
1212
4
            }
1213
44
            aosList.AddNameValue(psIter->pszValue,
1214
44
                                 CPLGetXMLValue(psIter, nullptr, nullptr));
1215
44
            const char *pszUnit = CPLGetXMLValue(psIter, "unit", nullptr);
1216
44
            if (pszUnit)
1217
44
                aosList.AddNameValue(CPLSPrintf("%s_UNIT", psIter->pszValue),
1218
44
                                     pszUnit);
1219
44
        }
1220
1221
21
        const char *pszRefBand =
1222
21
            CPLGetXMLValue(psIC, "REFERENCE_BAND", nullptr);
1223
21
        if (pszRefBand != nullptr)
1224
15
        {
1225
15
            const unsigned int nIdx = atoi(pszRefBand);
1226
15
            if (nIdx < CPL_ARRAYSIZE(asBandDesc))
1227
15
                aosList.AddNameValue("REFERENCE_BAND",
1228
15
                                     asBandDesc[nIdx].pszBandName);
1229
15
        }
1230
21
    }
1231
1232
21
    CPLXMLNode *psQII = CPLGetXMLNode(psRoot, "Quality_Indicators_Info");
1233
21
    if (psQII != nullptr)
1234
21
    {
1235
21
        const char *pszCC =
1236
21
            CPLGetXMLValue(psQII, "Cloud_Coverage_Assessment", nullptr);
1237
21
        if (pszCC)
1238
21
            aosList.AddNameValue("CLOUD_COVERAGE_ASSESSMENT", pszCC);
1239
1240
21
        const char *pszDegradedAnc = CPLGetXMLValue(
1241
21
            psQII, "Technical_Quality_Assessment.DEGRADED_ANC_DATA_PERCENTAGE",
1242
21
            nullptr);
1243
21
        if (pszDegradedAnc)
1244
21
            aosList.AddNameValue("DEGRADED_ANC_DATA_PERCENTAGE",
1245
21
                                 pszDegradedAnc);
1246
1247
21
        const char *pszDegradedMSI = CPLGetXMLValue(
1248
21
            psQII, "Technical_Quality_Assessment.DEGRADED_MSI_DATA_PERCENTAGE",
1249
21
            nullptr);
1250
21
        if (pszDegradedMSI)
1251
21
            aosList.AddNameValue("DEGRADED_MSI_DATA_PERCENTAGE",
1252
21
                                 pszDegradedMSI);
1253
1254
21
        CPLXMLNode *psQualInspect =
1255
21
            CPLGetXMLNode(psQII, "Quality_Control_Checks.Quality_Inspections");
1256
21
        for (CPLXMLNode *psIter =
1257
21
                 (psQualInspect ? psQualInspect->psChild : nullptr);
1258
127
             psIter != nullptr; psIter = psIter->psNext)
1259
106
        {
1260
            // MSIL2A approach
1261
106
            if (psIter->psChild != nullptr &&
1262
105
                psIter->psChild->psChild != nullptr &&
1263
5
                psIter->psChild->psNext != nullptr &&
1264
5
                psIter->psChild->psChild->eType == CXT_Text &&
1265
5
                psIter->psChild->psNext->eType == CXT_Text)
1266
5
            {
1267
5
                aosList.AddNameValue(psIter->psChild->psChild->pszValue,
1268
5
                                     psIter->psChild->psNext->pszValue);
1269
5
                continue;
1270
5
            }
1271
1272
101
            if (psIter->eType != CXT_Element)
1273
1
                continue;
1274
100
            if (psIter->psChild != nullptr &&
1275
100
                psIter->psChild->eType == CXT_Text)
1276
100
            {
1277
100
                aosList.AddNameValue(psIter->pszValue,
1278
100
                                     psIter->psChild->pszValue);
1279
100
            }
1280
100
        }
1281
1282
21
        CPLXMLNode *psICCQI = CPLGetXMLNode(psQII, "Image_Content_QI");
1283
21
        if (psICCQI == nullptr)
1284
21
        {
1285
21
            CPLXMLNode *psL2A_QII =
1286
21
                CPLGetXMLNode(psRoot, "L2A_Quality_Indicators_Info");
1287
21
            if (psL2A_QII != nullptr)
1288
11
            {
1289
11
                psICCQI = CPLGetXMLNode(psL2A_QII, "Image_Content_QI");
1290
11
            }
1291
21
        }
1292
21
        if (psICCQI != nullptr)
1293
11
        {
1294
185
            for (CPLXMLNode *psIter = psICCQI->psChild; psIter != nullptr;
1295
174
                 psIter = psIter->psNext)
1296
174
            {
1297
174
                if (psIter->eType != CXT_Element)
1298
2
                    continue;
1299
172
                if (psIter->psChild != nullptr &&
1300
172
                    psIter->psChild->eType == CXT_Text)
1301
172
                {
1302
172
                    aosList.AddNameValue(psIter->pszValue,
1303
172
                                         psIter->psChild->pszValue);
1304
172
                }
1305
172
            }
1306
11
        }
1307
21
    }
1308
1309
21
    return aosList.StealList();
1310
21
}
1311
1312
/************************************************************************/
1313
/*                     SENTINEL2GetResolutionSet()                      */
1314
/************************************************************************/
1315
1316
static bool SENTINEL2GetResolutionSet(
1317
    CPLXMLNode *psProductInfo, std::set<int> &oSetResolutions,
1318
    std::map<int, std::set<CPLString>> &oMapResolutionsToBands)
1319
7
{
1320
1321
7
    CPLXMLNode *psBandList =
1322
7
        CPLGetXMLNode(psProductInfo, "Query_Options.Band_List");
1323
7
    if (psBandList == nullptr)
1324
0
    {
1325
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s",
1326
0
                 "Query_Options.Band_List");
1327
0
        return false;
1328
0
    }
1329
1330
98
    for (CPLXMLNode *psIter = psBandList->psChild; psIter != nullptr;
1331
91
         psIter = psIter->psNext)
1332
91
    {
1333
91
        if (psIter->eType != CXT_Element ||
1334
91
            !EQUAL(psIter->pszValue, "BAND_NAME"))
1335
0
        {
1336
0
            continue;
1337
0
        }
1338
91
        const char *pszBandName = CPLGetXMLValue(psIter, nullptr, "");
1339
91
        const SENTINEL2BandDescription *psBandDesc =
1340
91
            SENTINEL2GetBandDesc(pszBandName);
1341
91
        if (psBandDesc == nullptr)
1342
0
        {
1343
0
            CPLDebug("SENTINEL2", "Unknown band name %s", pszBandName);
1344
0
            continue;
1345
0
        }
1346
91
        oSetResolutions.insert(psBandDesc->nResolution);
1347
91
        CPLString osName = psBandDesc->pszBandName + 1; /* skip B character */
1348
91
        if (atoi(osName) < 10)
1349
70
            osName = "0" + osName;
1350
91
        oMapResolutionsToBands[psBandDesc->nResolution].insert(
1351
91
            std::move(osName));
1352
91
    }
1353
7
    if (oSetResolutions.empty())
1354
0
    {
1355
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find any band");
1356
0
        return false;
1357
0
    }
1358
7
    return true;
1359
7
}
1360
1361
/************************************************************************/
1362
/*                 SENTINEL2GetPolygonWKTFromPosList()                  */
1363
/************************************************************************/
1364
1365
static CPLString SENTINEL2GetPolygonWKTFromPosList(const char *pszPosList)
1366
24
{
1367
24
    CPLString osPolygon;
1368
24
    char **papszTokens = CSLTokenizeString(pszPosList);
1369
24
    int nTokens = CSLCount(papszTokens);
1370
24
    int nDim = 2;
1371
24
    if ((nTokens % 3) == 0 && nTokens >= 3 * 4 &&
1372
3
        EQUAL(papszTokens[0], papszTokens[nTokens - 3]) &&
1373
3
        EQUAL(papszTokens[1], papszTokens[nTokens - 2]) &&
1374
3
        EQUAL(papszTokens[2], papszTokens[nTokens - 1]))
1375
3
    {
1376
3
        nDim = 3;
1377
3
    }
1378
24
    if ((nTokens % nDim) == 0)
1379
23
    {
1380
23
        osPolygon = "POLYGON((";
1381
135
        for (char **papszIter = papszTokens; *papszIter; papszIter += nDim)
1382
112
        {
1383
112
            if (papszIter != papszTokens)
1384
89
                osPolygon += ", ";
1385
112
            osPolygon += papszIter[1];
1386
112
            osPolygon += " ";
1387
112
            osPolygon += papszIter[0];
1388
112
            if (nDim == 3)
1389
15
            {
1390
15
                osPolygon += " ";
1391
15
                osPolygon += papszIter[2];
1392
15
            }
1393
112
        }
1394
23
        osPolygon += "))";
1395
23
    }
1396
24
    CSLDestroy(papszTokens);
1397
24
    return osPolygon;
1398
24
}
1399
1400
/************************************************************************/
1401
/*                 SENTINEL2GetBandListForResolution()                  */
1402
/************************************************************************/
1403
1404
static CPLString
1405
SENTINEL2GetBandListForResolution(const std::set<CPLString> &oBandnames)
1406
12
{
1407
12
    CPLString osBandNames;
1408
12
    for (std::set<CPLString>::const_iterator oIterBandnames =
1409
12
             oBandnames.begin();
1410
64
         oIterBandnames != oBandnames.end(); ++oIterBandnames)
1411
52
    {
1412
52
        if (!osBandNames.empty())
1413
40
            osBandNames += ", ";
1414
52
        const char *pszName = *oIterBandnames;
1415
52
        if (*pszName == DIGIT_ZERO)
1416
40
            pszName++;
1417
52
        if (atoi(pszName) > 0)
1418
52
            osBandNames += "B" + CPLString(pszName);
1419
0
        else
1420
0
            osBandNames += pszName;
1421
52
    }
1422
12
    return osBandNames;
1423
12
}
1424
1425
/************************************************************************/
1426
/*                         OpenL1BUserProduct()                         */
1427
/************************************************************************/
1428
1429
GDALDataset *SENTINEL2Dataset::OpenL1BUserProduct(GDALOpenInfo *poOpenInfo)
1430
83
{
1431
83
    CPLXMLNode *psRoot = CPLParseXMLFile(poOpenInfo->pszFilename);
1432
83
    if (psRoot == nullptr)
1433
23
    {
1434
23
        CPLDebug("SENTINEL2", "Cannot parse XML file '%s'",
1435
23
                 poOpenInfo->pszFilename);
1436
23
        return nullptr;
1437
23
    }
1438
1439
60
    char *pszOriginalXML = CPLSerializeXMLTree(psRoot);
1440
60
    CPLString osOriginalXML;
1441
60
    if (pszOriginalXML)
1442
60
        osOriginalXML = pszOriginalXML;
1443
60
    CPLFree(pszOriginalXML);
1444
1445
60
    SENTINEL2_CPLXMLNodeHolder oXMLHolder(psRoot);
1446
60
    CPLStripXMLNamespace(psRoot, nullptr, TRUE);
1447
1448
60
    CPLXMLNode *psProductInfo = CPLGetXMLNode(
1449
60
        psRoot, "=Level-1B_User_Product.General_Info.Product_Info");
1450
60
    if (psProductInfo == nullptr)
1451
55
    {
1452
55
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s",
1453
55
                 "=Level-1B_User_Product.General_Info.Product_Info");
1454
55
        return nullptr;
1455
55
    }
1456
1457
5
    auto poDS = std::make_unique<SENTINEL2DatasetContainer>();
1458
5
    char **papszMD =
1459
5
        SENTINEL2GetUserProductMetadata(psRoot, "Level-1B_User_Product");
1460
5
    poDS->GDALDataset::SetMetadata(papszMD);
1461
5
    CSLDestroy(papszMD);
1462
1463
5
    if (!osOriginalXML.empty())
1464
5
    {
1465
5
        char *apszXMLMD[2] = {nullptr};
1466
5
        apszXMLMD[0] = const_cast<char *>(osOriginalXML.c_str());
1467
5
        apszXMLMD[1] = nullptr;
1468
5
        poDS->GDALDataset::SetMetadata(apszXMLMD, "xml:SENTINEL2");
1469
5
    }
1470
1471
5
    const char *pszPosList = CPLGetXMLValue(
1472
5
        psRoot,
1473
5
        "=Level-1B_User_Product.Geometric_Info.Product_Footprint."
1474
5
        "Product_Footprint.Global_Footprint.EXT_POS_LIST",
1475
5
        nullptr);
1476
5
    if (pszPosList != nullptr)
1477
5
    {
1478
5
        CPLString osPolygon = SENTINEL2GetPolygonWKTFromPosList(pszPosList);
1479
5
        if (!osPolygon.empty())
1480
4
            poDS->GDALDataset::SetMetadataItem("FOOTPRINT", osPolygon.c_str());
1481
5
    }
1482
1483
5
    const char *pszMode = CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
1484
5
                                               "L1B_MODE", "DEFAULT");
1485
1486
    // Detect if this L1B product has associated geolocation arrays, as provided
1487
    // by the Sen2VM MPC project.
1488
5
    const std::string osDatastripRoot = CPLFormFilenameSafe(
1489
5
        CPLGetPathSafe(poOpenInfo->pszFilename).c_str(), "DATASTRIP", nullptr);
1490
5
    VSIStatBufL sStat;
1491
5
    if (VSIStatL(osDatastripRoot.c_str(), &sStat) == 0 &&
1492
0
        VSI_ISDIR(sStat.st_mode) && !EQUAL(pszMode, "GRANULE"))
1493
0
    {
1494
0
        int iSubDSNum = 1;
1495
0
        const CPLStringList aosList(VSIReadDir(osDatastripRoot.c_str()));
1496
0
        for (const char *pszDatastripId : aosList)
1497
0
        {
1498
0
            if (IsS2Prefixed(pszDatastripId, "_OPER_MSI_L1B_"))
1499
0
            {
1500
0
                const std::string osGEO_DATA = CPLFormFilenameSafe(
1501
0
                    CPLFormFilenameSafe(osDatastripRoot.c_str(), pszDatastripId,
1502
0
                                        nullptr)
1503
0
                        .c_str(),
1504
0
                    "GEO_DATA", nullptr);
1505
0
                const CPLStringList aosListVRT(VSIReadDir(osGEO_DATA.c_str()));
1506
0
                std::vector<std::string> aosVRTs;
1507
0
                for (const char *pszVRT : aosListVRT)
1508
0
                {
1509
0
                    if (EQUAL(CPLGetExtensionSafe(pszVRT).c_str(), "VRT"))
1510
0
                    {
1511
0
                        aosVRTs.push_back(pszVRT);
1512
0
                    }
1513
0
                }
1514
0
                std::sort(aosVRTs.begin(), aosVRTs.end());
1515
0
                for (const std::string &osVRT : aosVRTs)
1516
0
                {
1517
0
                    poDS->GDALDataset::SetMetadataItem(
1518
0
                        CPLSPrintf("SUBDATASET_%d_NAME", iSubDSNum),
1519
0
                        CPLSPrintf("SENTINEL2_L1B_WITH_GEOLOC:\"%s\":%s",
1520
0
                                   poOpenInfo->pszFilename,
1521
0
                                   CPLGetBasenameSafe(osVRT.c_str()).c_str()),
1522
0
                        GDAL_MDD_SUBDATASETS);
1523
0
                    ++iSubDSNum;
1524
0
                }
1525
0
            }
1526
0
        }
1527
0
        if (iSubDSNum > 1)
1528
0
        {
1529
0
            return poDS.release();
1530
0
        }
1531
0
        if (EQUAL(pszMode, "DATASTRIP"))
1532
0
        {
1533
0
            CPLError(CE_Failure, CPLE_AppDefined,
1534
0
                     "Could not find VRT geolocation files");
1535
0
            return nullptr;
1536
0
        }
1537
0
    }
1538
1539
5
    std::set<int> oSetResolutions;
1540
5
    std::map<int, std::set<CPLString>> oMapResolutionsToBands;
1541
5
    if (!SENTINEL2GetResolutionSet(psProductInfo, oSetResolutions,
1542
5
                                   oMapResolutionsToBands))
1543
0
    {
1544
0
        CPLDebug("SENTINEL2", "Failed to get resolution set");
1545
0
        return nullptr;
1546
0
    }
1547
1548
5
    std::vector<CPLString> aosGranuleList;
1549
5
    if (!SENTINEL2GetGranuleList(psRoot, SENTINEL2_L1B, poOpenInfo->pszFilename,
1550
5
                                 aosGranuleList))
1551
0
    {
1552
0
        CPLDebug("SENTINEL2", "Failed to get granule list");
1553
0
        return nullptr;
1554
0
    }
1555
1556
    /* Create subdatsets per granules and resolution (10, 20, 60m) */
1557
5
    int iSubDSNum = 1;
1558
9
    for (size_t i = 0; i < aosGranuleList.size(); i++)
1559
4
    {
1560
4
        for (std::set<int>::const_iterator oIterRes = oSetResolutions.begin();
1561
16
             oIterRes != oSetResolutions.end(); ++oIterRes)
1562
12
        {
1563
12
            const int nResolution = *oIterRes;
1564
1565
12
            poDS->GDALDataset::SetMetadataItem(
1566
12
                CPLSPrintf("SUBDATASET_%d_NAME", iSubDSNum),
1567
12
                CPLSPrintf("SENTINEL2_L1B:%s:%dm", aosGranuleList[i].c_str(),
1568
12
                           nResolution),
1569
12
                GDAL_MDD_SUBDATASETS);
1570
1571
12
            CPLString osBandNames = SENTINEL2GetBandListForResolution(
1572
12
                oMapResolutionsToBands[nResolution]);
1573
1574
12
            CPLString osDesc(
1575
12
                CPLSPrintf("Bands %s of granule %s with %dm resolution",
1576
12
                           osBandNames.c_str(),
1577
12
                           CPLGetFilename(aosGranuleList[i]), nResolution));
1578
12
            poDS->GDALDataset::SetMetadataItem(
1579
12
                CPLSPrintf("SUBDATASET_%d_DESC", iSubDSNum), osDesc.c_str(),
1580
12
                GDAL_MDD_SUBDATASETS);
1581
1582
12
            iSubDSNum++;
1583
12
        }
1584
4
    }
1585
1586
5
    return poDS.release();
1587
5
}
1588
1589
/************************************************************************/
1590
/*                   SENTINEL2GetL1BGranuleMetadata()                   */
1591
/************************************************************************/
1592
1593
static char **SENTINEL2GetL1BGranuleMetadata(CPLXMLNode *psMainMTD)
1594
247
{
1595
247
    CPLStringList aosList;
1596
1597
247
    CPLXMLNode *psRoot = CPLGetXMLNode(psMainMTD, "=Level-1B_Granule_ID");
1598
247
    if (psRoot == nullptr)
1599
244
    {
1600
244
        CPLError(CE_Failure, CPLE_AppDefined,
1601
244
                 "Cannot find =Level-1B_Granule_ID");
1602
244
        return nullptr;
1603
244
    }
1604
3
    CPLXMLNode *psGeneralInfo = CPLGetXMLNode(psRoot, "General_Info");
1605
3
    for (CPLXMLNode *psIter =
1606
3
             (psGeneralInfo ? psGeneralInfo->psChild : nullptr);
1607
21
         psIter != nullptr; psIter = psIter->psNext)
1608
18
    {
1609
18
        if (psIter->eType != CXT_Element)
1610
0
            continue;
1611
18
        const char *pszValue = CPLGetXMLValue(psIter, nullptr, nullptr);
1612
18
        if (pszValue != nullptr)
1613
15
        {
1614
15
            aosList.AddNameValue(psIter->pszValue, pszValue);
1615
15
        }
1616
18
    }
1617
1618
3
    CPLXMLNode *psGeometryHeader = CPLGetXMLNode(
1619
3
        psRoot, "Geometric_Info.Granule_Position.Geometric_Header");
1620
3
    if (psGeometryHeader != nullptr)
1621
3
    {
1622
3
        const char *pszVal = CPLGetXMLValue(
1623
3
            psGeometryHeader, "Incidence_Angles.ZENITH_ANGLE", nullptr);
1624
3
        if (pszVal)
1625
2
            aosList.AddNameValue("INCIDENCE_ZENITH_ANGLE", pszVal);
1626
1627
3
        pszVal = CPLGetXMLValue(psGeometryHeader,
1628
3
                                "Incidence_Angles.AZIMUTH_ANGLE", nullptr);
1629
3
        if (pszVal)
1630
3
            aosList.AddNameValue("INCIDENCE_AZIMUTH_ANGLE", pszVal);
1631
1632
3
        pszVal = CPLGetXMLValue(psGeometryHeader, "Solar_Angles.ZENITH_ANGLE",
1633
3
                                nullptr);
1634
3
        if (pszVal)
1635
3
            aosList.AddNameValue("SOLAR_ZENITH_ANGLE", pszVal);
1636
1637
3
        pszVal = CPLGetXMLValue(psGeometryHeader, "Solar_Angles.AZIMUTH_ANGLE",
1638
3
                                nullptr);
1639
3
        if (pszVal)
1640
3
            aosList.AddNameValue("SOLAR_AZIMUTH_ANGLE", pszVal);
1641
3
    }
1642
1643
3
    CPLXMLNode *psQII = CPLGetXMLNode(psRoot, "Quality_Indicators_Info");
1644
3
    if (psQII != nullptr)
1645
3
    {
1646
3
        CPLXMLNode *psICCQI = CPLGetXMLNode(psQII, "Image_Content_QI");
1647
3
        for (CPLXMLNode *psIter = (psICCQI ? psICCQI->psChild : nullptr);
1648
9
             psIter != nullptr; psIter = psIter->psNext)
1649
6
        {
1650
6
            if (psIter->eType != CXT_Element)
1651
0
                continue;
1652
6
            if (psIter->psChild != nullptr &&
1653
6
                psIter->psChild->eType == CXT_Text)
1654
6
            {
1655
6
                aosList.AddNameValue(psIter->pszValue,
1656
6
                                     psIter->psChild->pszValue);
1657
6
            }
1658
6
        }
1659
3
    }
1660
1661
3
    return aosList.StealList();
1662
247
}
1663
1664
/************************************************************************/
1665
/*                        SENTINEL2GetTilename()                        */
1666
/************************************************************************/
1667
1668
static CPLString
1669
SENTINEL2GetTilename(const std::string &osGranulePath,
1670
                     const std::string &osGranuleName,
1671
                     const std::string &osBandName,
1672
                     const std::string &osProductURI = CPLString(),
1673
                     bool bIsPreview = false, int nPrecisionL2A = 0)
1674
3.88k
{
1675
3.88k
    bool granuleNameMatchTilename = true;
1676
3.88k
    CPLString osJPEG2000Name(osGranuleName);
1677
3.88k
    if (osJPEG2000Name.size() > 7 &&
1678
2.24k
        osJPEG2000Name[osJPEG2000Name.size() - 7] == '_' &&
1679
0
        osJPEG2000Name[osJPEG2000Name.size() - 6] == 'N')
1680
0
    {
1681
0
        osJPEG2000Name.resize(osJPEG2000Name.size() - 7);
1682
0
    }
1683
1684
3.88k
    const SENTINEL2_L2A_BandDescription *psL2ABandDesc =
1685
3.88k
        (nPrecisionL2A) ? SENTINEL2GetL2ABandDesc(osBandName.c_str()) : nullptr;
1686
1687
3.88k
    CPLString osTile(osGranulePath);
1688
3.88k
    const char chSeparator = SENTINEL2GetPathSeparator(osTile);
1689
3.88k
    if (!osTile.empty())
1690
3.88k
        osTile += chSeparator;
1691
3.88k
    bool procBaseLineIs1 = false;
1692
3.88k
    if (osJPEG2000Name.size() > 12 && osJPEG2000Name[8] == '_' &&
1693
0
        osJPEG2000Name[12] == '_')
1694
0
        procBaseLineIs1 = true;
1695
3.88k
    if (bIsPreview ||
1696
3.88k
        (psL2ABandDesc != nullptr && (psL2ABandDesc->eLocation == TL_QI_DATA)))
1697
0
    {
1698
0
        osTile += "QI_DATA";
1699
0
        osTile += chSeparator;
1700
0
        if (procBaseLineIs1)
1701
0
        {
1702
0
            if (atoi(osBandName.c_str()) > 0)
1703
0
            {
1704
0
                osJPEG2000Name[9] = 'P';
1705
0
                osJPEG2000Name[10] = 'V';
1706
0
                osJPEG2000Name[11] = 'I';
1707
0
            }
1708
0
            else if (nPrecisionL2A && osBandName.size() == 3)
1709
0
            {
1710
0
                osJPEG2000Name[9] = osBandName[0];
1711
0
                osJPEG2000Name[10] = osBandName[1];
1712
0
                osJPEG2000Name[11] = osBandName[2];
1713
0
            }
1714
0
            osTile += osJPEG2000Name;
1715
0
        }
1716
0
        else
1717
0
        {
1718
0
            osTile += "MSK_";
1719
0
            osTile += osBandName;
1720
0
            osTile += "PRB";
1721
0
        }
1722
0
        if (nPrecisionL2A && !bIsPreview)
1723
0
            osTile += CPLSPrintf("_%02dm", nPrecisionL2A);
1724
0
    }
1725
3.88k
    else
1726
3.88k
    {
1727
3.88k
        osTile += "IMG_DATA";
1728
3.88k
        osTile += chSeparator;
1729
3.88k
        if (((psL2ABandDesc != nullptr &&
1730
0
              psL2ABandDesc->eLocation == TL_IMG_DATA_Rxxm) ||
1731
3.88k
             (psL2ABandDesc == nullptr && nPrecisionL2A != 0)) &&
1732
0
            (!procBaseLineIs1 || osBandName != "SCL"))
1733
0
        {
1734
0
            osTile += CPLSPrintf("R%02dm", nPrecisionL2A);
1735
0
            osTile += chSeparator;
1736
0
        }
1737
3.88k
        if (procBaseLineIs1)
1738
0
        {
1739
0
            if (atoi(osBandName.c_str()) > 0)
1740
0
            {
1741
0
                osJPEG2000Name[9] = 'M';
1742
0
                osJPEG2000Name[10] = 'S';
1743
0
                osJPEG2000Name[11] = 'I';
1744
0
            }
1745
0
            else if (nPrecisionL2A && osBandName.size() == 3)
1746
0
            {
1747
0
                osJPEG2000Name[9] = osBandName[0];
1748
0
                osJPEG2000Name[10] = osBandName[1];
1749
0
                osJPEG2000Name[11] = osBandName[2];
1750
0
            }
1751
0
        }
1752
3.88k
        else if (osProductURI.size() > 44 &&
1753
0
                 osProductURI.substr(3, 8) == "_MSIL2A_")
1754
0
        {
1755
0
            osTile += osProductURI.substr(38, 6);
1756
0
            osTile += osProductURI.substr(10, 16);
1757
0
            granuleNameMatchTilename = false;
1758
0
        }
1759
3.88k
        else
1760
3.88k
        {
1761
3.88k
            CPLDebug("SENTINEL2", "Invalid granule path: %s",
1762
3.88k
                     osGranulePath.c_str());
1763
3.88k
        }
1764
3.88k
        if (granuleNameMatchTilename)
1765
3.88k
            osTile += osJPEG2000Name;
1766
3.88k
        if (atoi(osBandName.c_str()) > 0)
1767
3.88k
        {
1768
3.88k
            osTile += "_B";
1769
3.88k
            if (osBandName.size() == 3 && osBandName[0] == '0')
1770
299
                osTile += osBandName.substr(1);
1771
3.58k
            else
1772
3.58k
                osTile += osBandName;
1773
3.88k
        }
1774
0
        else if (!procBaseLineIs1)
1775
0
        {
1776
0
            osTile += "_";
1777
0
            osTile += osBandName;
1778
0
        }
1779
3.88k
        if (nPrecisionL2A)
1780
0
            osTile += CPLSPrintf("_%02dm", nPrecisionL2A);
1781
3.88k
    }
1782
3.88k
    osTile += ".jp2";
1783
3.88k
    return osTile;
1784
3.88k
}
1785
1786
/************************************************************************/
1787
/*             SENTINEL2GetMainMTDFilenameFromGranuleMTD()              */
1788
/************************************************************************/
1789
1790
static CPLString
1791
SENTINEL2GetMainMTDFilenameFromGranuleMTD(const char *pszFilename)
1792
299
{
1793
    // Look for product MTD file
1794
299
    CPLString osTopDir(CPLFormFilenameSafe(
1795
299
        CPLFormFilenameSafe(CPLGetDirnameSafe(pszFilename).c_str(), "..",
1796
299
                            nullptr)
1797
299
            .c_str(),
1798
299
        "..", nullptr));
1799
1800
    // Workaround to avoid long filenames on Windows
1801
299
    if (CPLIsFilenameRelative(pszFilename))
1802
0
    {
1803
        // GRANULE/bla/bla.xml
1804
0
        const std::string osPath = CPLGetPathSafe(pszFilename);
1805
0
        if (osPath.find('/') != std::string::npos ||
1806
0
            osPath.find('\\') != std::string::npos)
1807
0
        {
1808
0
            osTopDir = CPLGetPathSafe(CPLGetPathSafe(osPath.c_str()).c_str());
1809
0
            if (osTopDir == "")
1810
0
                osTopDir = ".";
1811
0
        }
1812
0
    }
1813
1814
299
    char **papszContents = VSIReadDir(osTopDir);
1815
299
    CPLString osMainMTD;
1816
551
    for (char **papszIter = papszContents; papszIter && *papszIter; ++papszIter)
1817
252
    {
1818
252
        if (strlen(*papszIter) >= strlen("S2A_XXXX_MTD") &&
1819
47
            IsS2Prefixed(*papszIter, "") &&
1820
0
            EQUALN(*papszIter + strlen("S2A_XXXX"), "_MTD", 4))
1821
0
        {
1822
0
            osMainMTD = CPLFormFilenameSafe(osTopDir, *papszIter, nullptr);
1823
0
            break;
1824
0
        }
1825
252
    }
1826
299
    CSLDestroy(papszContents);
1827
299
    return osMainMTD;
1828
299
}
1829
1830
/************************************************************************/
1831
/*           SENTINEL2GetResolutionSetAndMainMDFromGranule()            */
1832
/************************************************************************/
1833
1834
static void SENTINEL2GetResolutionSetAndMainMDFromGranule(
1835
    const char *pszFilename, const char *pszRootPathWithoutEqual,
1836
    int nResolutionOfInterest, std::set<int> &oSetResolutions,
1837
    std::map<int, std::set<CPLString>> &oMapResolutionsToBands, char **&papszMD,
1838
    CPLXMLNode **ppsRootMainMTD)
1839
299
{
1840
299
    CPLString osMainMTD(SENTINEL2GetMainMTDFilenameFromGranuleMTD(pszFilename));
1841
1842
    // Parse product MTD if available
1843
299
    papszMD = nullptr;
1844
299
    if (!osMainMTD.empty() &&
1845
        /* env var for debug only */
1846
0
        CPLTestBool(CPLGetConfigOption("SENTINEL2_USE_MAIN_MTD", "YES")))
1847
0
    {
1848
0
        CPLXMLNode *psRootMainMTD = CPLParseXMLFile(osMainMTD);
1849
0
        if (psRootMainMTD != nullptr)
1850
0
        {
1851
0
            CPLStripXMLNamespace(psRootMainMTD, nullptr, TRUE);
1852
1853
0
            CPLXMLNode *psProductInfo = CPLGetXMLNode(
1854
0
                psRootMainMTD, CPLSPrintf("=%s.General_Info.Product_Info",
1855
0
                                          pszRootPathWithoutEqual));
1856
0
            if (psProductInfo != nullptr)
1857
0
            {
1858
0
                SENTINEL2GetResolutionSet(psProductInfo, oSetResolutions,
1859
0
                                          oMapResolutionsToBands);
1860
0
            }
1861
1862
0
            papszMD = SENTINEL2GetUserProductMetadata(psRootMainMTD,
1863
0
                                                      pszRootPathWithoutEqual);
1864
0
            if (ppsRootMainMTD != nullptr)
1865
0
                *ppsRootMainMTD = psRootMainMTD;
1866
0
            else
1867
0
                CPLDestroyXMLNode(psRootMainMTD);
1868
0
        }
1869
0
    }
1870
299
    else
1871
299
    {
1872
        // If no main MTD file found, then probe all bands for resolution (of
1873
        // interest if there's one, or all resolutions otherwise)
1874
299
        for (const auto &sBandDesc : asBandDesc)
1875
3.88k
        {
1876
3.88k
            if (nResolutionOfInterest != 0 &&
1877
0
                sBandDesc.nResolution != nResolutionOfInterest)
1878
0
            {
1879
0
                continue;
1880
0
            }
1881
3.88k
            CPLString osBandName =
1882
3.88k
                sBandDesc.pszBandName + 1; /* skip B character */
1883
3.88k
            if (atoi(osBandName) < 10)
1884
2.99k
                osBandName = "0" + osBandName;
1885
1886
3.88k
            CPLString osTile(SENTINEL2GetTilename(
1887
3.88k
                CPLGetPathSafe(pszFilename).c_str(),
1888
3.88k
                CPLGetBasenameSafe(pszFilename).c_str(), osBandName));
1889
3.88k
            VSIStatBufL sStat;
1890
3.88k
            if (VSIStatExL(osTile, &sStat, VSI_STAT_EXISTS_FLAG) == 0)
1891
0
            {
1892
0
                oMapResolutionsToBands[sBandDesc.nResolution].insert(
1893
0
                    std::move(osBandName));
1894
0
                oSetResolutions.insert(sBandDesc.nResolution);
1895
0
            }
1896
3.88k
        }
1897
299
    }
1898
299
}
1899
1900
/************************************************************************/
1901
/*                           OpenL1BGranule()                           */
1902
/************************************************************************/
1903
1904
GDALDataset *SENTINEL2Dataset::OpenL1BGranule(const char *pszFilename,
1905
                                              CPLXMLNode **ppsRoot,
1906
                                              int nResolutionOfInterest,
1907
                                              std::set<CPLString> *poBandSet)
1908
270
{
1909
270
    CPLXMLNode *psRoot = CPLParseXMLFile(pszFilename);
1910
270
    if (psRoot == nullptr)
1911
23
    {
1912
23
        CPLDebug("SENTINEL2", "Cannot parse XML file '%s'", pszFilename);
1913
23
        return nullptr;
1914
23
    }
1915
1916
247
    char *pszOriginalXML = CPLSerializeXMLTree(psRoot);
1917
247
    CPLString osOriginalXML;
1918
247
    if (pszOriginalXML)
1919
247
        osOriginalXML = pszOriginalXML;
1920
247
    CPLFree(pszOriginalXML);
1921
1922
247
    SENTINEL2_CPLXMLNodeHolder oXMLHolder(psRoot);
1923
247
    CPLStripXMLNamespace(psRoot, nullptr, TRUE);
1924
1925
247
    SENTINEL2DatasetContainer *poDS = new SENTINEL2DatasetContainer();
1926
1927
247
    if (!osOriginalXML.empty())
1928
247
    {
1929
247
        char *apszXMLMD[2];
1930
247
        apszXMLMD[0] = const_cast<char *>(osOriginalXML.c_str());
1931
247
        apszXMLMD[1] = nullptr;
1932
247
        poDS->GDALDataset::SetMetadata(apszXMLMD, "xml:SENTINEL2");
1933
247
    }
1934
1935
247
    std::set<int> oSetResolutions;
1936
247
    std::map<int, std::set<CPLString>> oMapResolutionsToBands;
1937
247
    char **papszMD = nullptr;
1938
247
    SENTINEL2GetResolutionSetAndMainMDFromGranule(
1939
247
        pszFilename, "Level-1B_User_Product", nResolutionOfInterest,
1940
247
        oSetResolutions, oMapResolutionsToBands, papszMD, nullptr);
1941
247
    if (poBandSet != nullptr)
1942
0
        *poBandSet = oMapResolutionsToBands[nResolutionOfInterest];
1943
1944
247
    char **papszGranuleMD = SENTINEL2GetL1BGranuleMetadata(psRoot);
1945
247
    papszMD = CSLMerge(papszMD, papszGranuleMD);
1946
247
    CSLDestroy(papszGranuleMD);
1947
1948
    // Remove CLOUD_COVERAGE_ASSESSMENT that comes from main metadata, if
1949
    // granule CLOUDY_PIXEL_PERCENTAGE is present.
1950
247
    if (CSLFetchNameValue(papszMD, "CLOUDY_PIXEL_PERCENTAGE") != nullptr &&
1951
3
        CSLFetchNameValue(papszMD, "CLOUD_COVERAGE_ASSESSMENT") != nullptr)
1952
0
    {
1953
0
        papszMD =
1954
0
            CSLSetNameValue(papszMD, "CLOUD_COVERAGE_ASSESSMENT", nullptr);
1955
0
    }
1956
1957
247
    poDS->GDALDataset::SetMetadata(papszMD);
1958
247
    CSLDestroy(papszMD);
1959
1960
    // Get the footprint
1961
247
    const char *pszPosList =
1962
247
        CPLGetXMLValue(psRoot,
1963
247
                       "=Level-1B_Granule_ID.Geometric_Info.Granule_Footprint."
1964
247
                       "Granule_Footprint.Footprint.EXT_POS_LIST",
1965
247
                       nullptr);
1966
247
    if (pszPosList != nullptr)
1967
3
    {
1968
3
        CPLString osPolygon = SENTINEL2GetPolygonWKTFromPosList(pszPosList);
1969
3
        if (!osPolygon.empty())
1970
3
            poDS->GDALDataset::SetMetadataItem("FOOTPRINT", osPolygon.c_str());
1971
3
    }
1972
1973
    /* Create subdatsets per resolution (10, 20, 60m) */
1974
247
    int iSubDSNum = 1;
1975
247
    for (std::set<int>::const_iterator oIterRes = oSetResolutions.begin();
1976
247
         oIterRes != oSetResolutions.end(); ++oIterRes)
1977
0
    {
1978
0
        const int nResolution = *oIterRes;
1979
1980
0
        poDS->GDALDataset::SetMetadataItem(
1981
0
            CPLSPrintf("SUBDATASET_%d_NAME", iSubDSNum),
1982
0
            CPLSPrintf("SENTINEL2_L1B:%s:%dm", pszFilename, nResolution),
1983
0
            GDAL_MDD_SUBDATASETS);
1984
1985
0
        CPLString osBandNames = SENTINEL2GetBandListForResolution(
1986
0
            oMapResolutionsToBands[nResolution]);
1987
1988
0
        CPLString osDesc(CPLSPrintf("Bands %s with %dm resolution",
1989
0
                                    osBandNames.c_str(), nResolution));
1990
0
        poDS->GDALDataset::SetMetadataItem(
1991
0
            CPLSPrintf("SUBDATASET_%d_DESC", iSubDSNum), osDesc.c_str(),
1992
0
            GDAL_MDD_SUBDATASETS);
1993
1994
0
        iSubDSNum++;
1995
0
    }
1996
1997
247
    if (ppsRoot != nullptr)
1998
0
    {
1999
0
        *ppsRoot = oXMLHolder.Release();
2000
0
    }
2001
2002
247
    return poDS;
2003
270
}
2004
2005
/************************************************************************/
2006
/*                      SENTINEL2SetBandMetadata()                      */
2007
/************************************************************************/
2008
2009
static void SENTINEL2SetBandMetadata(GDALRasterBand *poBand,
2010
                                     const std::string &osBandName)
2011
0
{
2012
0
    CPLString osLookupBandName(osBandName);
2013
0
    if (osLookupBandName[0] == '0')
2014
0
        osLookupBandName = osLookupBandName.substr(1);
2015
0
    if (atoi(osLookupBandName) > 0)
2016
0
        osLookupBandName = "B" + osLookupBandName;
2017
2018
0
    CPLString osBandDesc(osLookupBandName);
2019
0
    const SENTINEL2BandDescription *psBandDesc =
2020
0
        SENTINEL2GetBandDesc(osLookupBandName);
2021
0
    if (psBandDesc != nullptr)
2022
0
    {
2023
0
        osBandDesc +=
2024
0
            CPLSPrintf(", central wavelength %d nm", psBandDesc->nWaveLength);
2025
0
        poBand->SetColorInterpretation(psBandDesc->eColorInterp);
2026
0
        poBand->SetMetadataItem("BANDNAME", psBandDesc->pszBandName);
2027
0
        poBand->SetMetadataItem("BANDWIDTH",
2028
0
                                CPLSPrintf("%d", psBandDesc->nBandWidth));
2029
0
        poBand->SetMetadataItem("BANDWIDTH_UNIT", "nm");
2030
0
        poBand->SetMetadataItem("WAVELENGTH",
2031
0
                                CPLSPrintf("%d", psBandDesc->nWaveLength));
2032
0
        poBand->SetMetadataItem("WAVELENGTH_UNIT", "nm");
2033
2034
0
        poBand->SetMetadataItem(
2035
0
            GDALMD_CENTRAL_WAVELENGTH_UM,
2036
0
            CPLSPrintf("%.3f", double(psBandDesc->nWaveLength) / 1000),
2037
0
            GDAL_MDD_IMAGERY);
2038
0
        poBand->SetMetadataItem(
2039
0
            GDALMD_FWHM_UM,
2040
0
            CPLSPrintf("%.3f", double(psBandDesc->nBandWidth) / 1000),
2041
0
            GDAL_MDD_IMAGERY);
2042
0
    }
2043
0
    else
2044
0
    {
2045
0
        const SENTINEL2_L2A_BandDescription *psL2ABandDesc =
2046
0
            SENTINEL2GetL2ABandDesc(osBandName.c_str());
2047
0
        if (psL2ABandDesc != nullptr)
2048
0
        {
2049
0
            osBandDesc += ", ";
2050
0
            osBandDesc += psL2ABandDesc->pszBandDescription;
2051
0
        }
2052
2053
0
        poBand->SetMetadataItem("BANDNAME", osBandName.c_str());
2054
0
    }
2055
0
    poBand->SetDescription(osBandDesc);
2056
0
}
2057
2058
/************************************************************************/
2059
/*                         OpenL1BSubdataset()                          */
2060
/************************************************************************/
2061
2062
GDALDataset *SENTINEL2Dataset::OpenL1BSubdataset(GDALOpenInfo *poOpenInfo)
2063
0
{
2064
0
    CPLString osFilename;
2065
0
    CPLAssert(STARTS_WITH_CI(poOpenInfo->pszFilename, "SENTINEL2_L1B:"));
2066
0
    osFilename = poOpenInfo->pszFilename + strlen("SENTINEL2_L1B:");
2067
0
    const char *pszPrecision = strrchr(osFilename.c_str(), ':');
2068
0
    if (pszPrecision == nullptr || pszPrecision == osFilename.c_str())
2069
0
    {
2070
0
        CPLError(CE_Failure, CPLE_AppDefined,
2071
0
                 "Invalid syntax for SENTINEL2_L1B:");
2072
0
        return nullptr;
2073
0
    }
2074
0
    const int nSubDSPrecision = atoi(pszPrecision + 1);
2075
0
    if (nSubDSPrecision != RES_10M && nSubDSPrecision != RES_20M &&
2076
0
        nSubDSPrecision != RES_60M)
2077
0
    {
2078
0
        CPLError(CE_Failure, CPLE_NotSupported, "Unsupported precision: %d",
2079
0
                 nSubDSPrecision);
2080
0
        return nullptr;
2081
0
    }
2082
0
    osFilename.resize(pszPrecision - osFilename.c_str());
2083
2084
0
    CPLXMLNode *psRoot = nullptr;
2085
0
    std::set<CPLString> oSetBands;
2086
0
    GDALDataset *poTmpDS =
2087
0
        OpenL1BGranule(osFilename, &psRoot, nSubDSPrecision, &oSetBands);
2088
0
    if (poTmpDS == nullptr)
2089
0
    {
2090
0
        CPLDebug("SENTINEL2", "Failed to open L1B granule %s",
2091
0
                 osFilename.c_str());
2092
0
        return nullptr;
2093
0
    }
2094
2095
0
    SENTINEL2_CPLXMLNodeHolder oXMLHolder(psRoot);
2096
2097
0
    std::vector<CPLString> aosBands;
2098
0
    for (std::set<CPLString>::const_iterator oIterBandnames = oSetBands.begin();
2099
0
         oIterBandnames != oSetBands.end(); ++oIterBandnames)
2100
0
    {
2101
0
        aosBands.push_back(*oIterBandnames);
2102
0
    }
2103
    /* Put 2=Blue, 3=Green, 4=Band bands in RGB order for conveniency */
2104
0
    if (aosBands.size() >= 3 && aosBands[0] == "02" && aosBands[1] == "03" &&
2105
0
        aosBands[2] == "04")
2106
0
    {
2107
0
        aosBands[0] = "04";
2108
0
        aosBands[2] = "02";
2109
0
    }
2110
2111
0
    int nBits = 0;   /* 0 = unknown yet*/
2112
0
    int nValMax = 0; /* 0 = unknown yet*/
2113
0
    int nRows = 0;
2114
0
    int nCols = 0;
2115
0
    CPLXMLNode *psGranuleDimensions = CPLGetXMLNode(
2116
0
        psRoot, "=Level-1B_Granule_ID.Geometric_Info.Granule_Dimensions");
2117
0
    if (psGranuleDimensions == nullptr)
2118
0
    {
2119
0
        for (size_t i = 0; i < aosBands.size(); i++)
2120
0
        {
2121
0
            CPLString osTile(SENTINEL2GetTilename(
2122
0
                CPLGetPathSafe(osFilename).c_str(),
2123
0
                CPLGetBasenameSafe(osFilename).c_str(), aosBands[i]));
2124
0
            if (SENTINEL2GetTileInfo(osTile, &nCols, &nRows, &nBits))
2125
0
            {
2126
0
                if (nBits <= 16)
2127
0
                    nValMax = (1 << nBits) - 1;
2128
0
                else
2129
0
                {
2130
0
                    CPLDebug("SENTINEL2", "Unexpected bit depth %d", nBits);
2131
0
                    nValMax = 65535;
2132
0
                }
2133
0
                break;
2134
0
            }
2135
0
        }
2136
0
    }
2137
0
    else
2138
0
    {
2139
0
        for (CPLXMLNode *psIter = psGranuleDimensions->psChild;
2140
0
             psIter != nullptr; psIter = psIter->psNext)
2141
0
        {
2142
0
            if (psIter->eType != CXT_Element)
2143
0
                continue;
2144
0
            if (EQUAL(psIter->pszValue, "Size") &&
2145
0
                atoi(CPLGetXMLValue(psIter, "resolution", "")) ==
2146
0
                    nSubDSPrecision)
2147
0
            {
2148
0
                const char *pszRows = CPLGetXMLValue(psIter, "NROWS", nullptr);
2149
0
                if (pszRows == nullptr)
2150
0
                {
2151
0
                    CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s",
2152
0
                             "NROWS");
2153
0
                    delete poTmpDS;
2154
0
                    return nullptr;
2155
0
                }
2156
0
                const char *pszCols = CPLGetXMLValue(psIter, "NCOLS", nullptr);
2157
0
                if (pszCols == nullptr)
2158
0
                {
2159
0
                    CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s",
2160
0
                             "NCOLS");
2161
0
                    delete poTmpDS;
2162
0
                    return nullptr;
2163
0
                }
2164
0
                nRows = atoi(pszRows);
2165
0
                nCols = atoi(pszCols);
2166
0
                break;
2167
0
            }
2168
0
        }
2169
0
    }
2170
0
    if (nRows <= 0 || nCols <= 0)
2171
0
    {
2172
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find granule dimension");
2173
0
        delete poTmpDS;
2174
0
        return nullptr;
2175
0
    }
2176
2177
0
    SENTINEL2Dataset *poDS = new SENTINEL2Dataset(nCols, nRows);
2178
0
    poDS->aosNonJP2Files.push_back(osFilename);
2179
2180
    // Transfer metadata
2181
0
    poDS->GDALDataset::SetMetadata(poTmpDS->GetMetadata());
2182
0
    poDS->GDALDataset::SetMetadata(poTmpDS->GetMetadata("xml:SENTINEL2"),
2183
0
                                   "xml:SENTINEL2");
2184
2185
0
    delete poTmpDS;
2186
2187
    /* -------------------------------------------------------------------- */
2188
    /*      Initialize bands.                                               */
2189
    /* -------------------------------------------------------------------- */
2190
2191
0
    int nSaturatedVal = atoi(CSLFetchNameValueDef(
2192
0
        poDS->GetMetadata(), "SPECIAL_VALUE_SATURATED", "-1"));
2193
0
    int nNodataVal = atoi(CSLFetchNameValueDef(poDS->GetMetadata(),
2194
0
                                               "SPECIAL_VALUE_NODATA", "-1"));
2195
2196
0
    const bool bAlpha =
2197
0
        CPLTestBool(SENTINEL2GetOption(poOpenInfo, "ALPHA", "FALSE"));
2198
0
    const int nBands = ((bAlpha) ? 1 : 0) + static_cast<int>(aosBands.size());
2199
0
    const int nAlphaBand = (!bAlpha) ? 0 : nBands;
2200
0
    const GDALDataType eDT = GDT_UInt16;
2201
2202
0
    for (int nBand = 1; nBand <= nBands; nBand++)
2203
0
    {
2204
0
        VRTSourcedRasterBand *poBand = nullptr;
2205
2206
0
        if (nBand != nAlphaBand)
2207
0
        {
2208
0
            poBand = new VRTSourcedRasterBand(
2209
0
                poDS, nBand, eDT, poDS->nRasterXSize, poDS->nRasterYSize);
2210
0
        }
2211
0
        else
2212
0
        {
2213
0
            poBand = new SENTINEL2AlphaBand(
2214
0
                poDS, nBand, eDT, poDS->nRasterXSize, poDS->nRasterYSize,
2215
0
                nSaturatedVal, nNodataVal);
2216
0
        }
2217
2218
0
        poDS->SetBand(nBand, poBand);
2219
0
        if (nBand == nAlphaBand)
2220
0
            poBand->SetColorInterpretation(GCI_AlphaBand);
2221
2222
0
        CPLString osBandName;
2223
0
        if (nBand != nAlphaBand)
2224
0
        {
2225
0
            osBandName = aosBands[nBand - 1];
2226
0
            SENTINEL2SetBandMetadata(poBand, osBandName);
2227
0
        }
2228
0
        else
2229
0
            osBandName = aosBands[0];
2230
2231
0
        CPLString osTile(SENTINEL2GetTilename(
2232
0
            CPLGetPathSafe(osFilename).c_str(),
2233
0
            CPLGetBasenameSafe(osFilename).c_str(), osBandName));
2234
2235
0
        bool bTileFound = false;
2236
0
        if (nValMax == 0)
2237
0
        {
2238
            /* It is supposed to be 12 bits, but some products have 15 bits */
2239
0
            if (SENTINEL2GetTileInfo(osTile, nullptr, nullptr, &nBits))
2240
0
            {
2241
0
                bTileFound = true;
2242
0
                if (nBits <= 16)
2243
0
                    nValMax = (1 << nBits) - 1;
2244
0
                else
2245
0
                {
2246
0
                    CPLDebug("SENTINEL2", "Unexpected bit depth %d", nBits);
2247
0
                    nValMax = 65535;
2248
0
                }
2249
0
            }
2250
0
        }
2251
0
        else
2252
0
        {
2253
0
            VSIStatBufL sStat;
2254
0
            if (VSIStatExL(osTile, &sStat, VSI_STAT_EXISTS_FLAG) == 0)
2255
0
                bTileFound = true;
2256
0
        }
2257
0
        if (!bTileFound)
2258
0
        {
2259
0
            CPLError(CE_Warning, CPLE_AppDefined,
2260
0
                     "Tile %s not found on filesystem. Skipping it",
2261
0
                     osTile.c_str());
2262
0
            continue;
2263
0
        }
2264
2265
0
        if (nBand != nAlphaBand)
2266
0
        {
2267
0
            poBand->AddSimpleSource(osTile, 1, 0, 0, poDS->nRasterXSize,
2268
0
                                    poDS->nRasterYSize, 0, 0,
2269
0
                                    poDS->nRasterXSize, poDS->nRasterYSize);
2270
0
        }
2271
0
        else
2272
0
        {
2273
0
            poBand->AddComplexSource(osTile, 1, 0, 0, poDS->nRasterXSize,
2274
0
                                     poDS->nRasterYSize, 0, 0,
2275
0
                                     poDS->nRasterXSize, poDS->nRasterYSize,
2276
0
                                     nValMax /* offset */, 0 /* scale */);
2277
0
        }
2278
2279
0
        if ((nBits % 8) != 0)
2280
0
        {
2281
0
            poBand->SetMetadataItem(GDALMD_NBITS, CPLSPrintf("%d", nBits),
2282
0
                                    GDAL_MDD_IMAGE_STRUCTURE);
2283
0
        }
2284
0
    }
2285
2286
    /* -------------------------------------------------------------------- */
2287
    /*      Add georeferencing.                                             */
2288
    /* -------------------------------------------------------------------- */
2289
    // const char* pszDirection =
2290
    // poDS->GetMetadataItem("DATATAKE_1_SENSING_ORBIT_DIRECTION");
2291
0
    const char *pszFootprint = poDS->GetMetadataItem("FOOTPRINT");
2292
0
    if (pszFootprint != nullptr)
2293
0
    {
2294
        // For descending orbits, we have observed that the order of points in
2295
        // the polygon is UL, LL, LR, UR. That might not be true for ascending
2296
        // orbits but let's assume it...
2297
0
        OGRGeometry *poGeom = nullptr;
2298
0
        if (OGRGeometryFactory::createFromWkt(pszFootprint, nullptr, &poGeom) ==
2299
0
                OGRERR_NONE &&
2300
0
            poGeom != nullptr &&
2301
0
            wkbFlatten(poGeom->getGeometryType()) == wkbPolygon)
2302
0
        {
2303
0
            OGRLinearRing *poRing =
2304
0
                reinterpret_cast<OGRPolygon *>(poGeom)->getExteriorRing();
2305
0
            if (poRing != nullptr && poRing->getNumPoints() == 5)
2306
0
            {
2307
0
                GDAL_GCP asGCPList[5];
2308
0
                memset(asGCPList, 0, sizeof(asGCPList));
2309
0
                for (int i = 0; i < 4; i++)
2310
0
                {
2311
0
                    asGCPList[i].dfGCPX = poRing->getX(i);
2312
0
                    asGCPList[i].dfGCPY = poRing->getY(i);
2313
0
                    asGCPList[i].dfGCPZ = poRing->getZ(i);
2314
0
                }
2315
0
                asGCPList[0].dfGCPPixel = 0;
2316
0
                asGCPList[0].dfGCPLine = 0;
2317
0
                asGCPList[1].dfGCPPixel = 0;
2318
0
                asGCPList[1].dfGCPLine = poDS->nRasterYSize;
2319
0
                asGCPList[2].dfGCPPixel = poDS->nRasterXSize;
2320
0
                asGCPList[2].dfGCPLine = poDS->nRasterYSize;
2321
0
                asGCPList[3].dfGCPPixel = poDS->nRasterXSize;
2322
0
                asGCPList[3].dfGCPLine = 0;
2323
2324
0
                int nGCPCount = 4;
2325
0
                CPLXMLNode *psGeometryHeader =
2326
0
                    CPLGetXMLNode(psRoot, "=Level-1B_Granule_ID.Geometric_Info."
2327
0
                                          "Granule_Position.Geometric_Header");
2328
0
                if (psGeometryHeader != nullptr)
2329
0
                {
2330
0
                    const char *pszGC = CPLGetXMLValue(
2331
0
                        psGeometryHeader, "GROUND_CENTER", nullptr);
2332
0
                    const char *pszQLCenter =
2333
0
                        CPLGetXMLValue(psGeometryHeader, "QL_CENTER", nullptr);
2334
0
                    if (pszGC != nullptr && pszQLCenter != nullptr &&
2335
0
                        EQUAL(pszQLCenter, "0 0"))
2336
0
                    {
2337
0
                        char **papszTokens = CSLTokenizeString(pszGC);
2338
0
                        if (CSLCount(papszTokens) >= 2)
2339
0
                        {
2340
0
                            nGCPCount = 5;
2341
0
                            asGCPList[4].dfGCPX = CPLAtof(papszTokens[1]);
2342
0
                            asGCPList[4].dfGCPY = CPLAtof(papszTokens[0]);
2343
0
                            if (CSLCount(papszTokens) >= 3)
2344
0
                                asGCPList[4].dfGCPZ = CPLAtof(papszTokens[2]);
2345
0
                            asGCPList[4].dfGCPLine = poDS->nRasterYSize / 2.0;
2346
0
                            asGCPList[4].dfGCPPixel = poDS->nRasterXSize / 2.0;
2347
0
                        }
2348
0
                        CSLDestroy(papszTokens);
2349
0
                    }
2350
0
                }
2351
2352
0
                poDS->SetGCPs(nGCPCount, asGCPList, SRS_WKT_WGS84_LAT_LONG);
2353
0
                GDALDeinitGCPs(nGCPCount, asGCPList);
2354
0
            }
2355
0
        }
2356
0
        delete poGeom;
2357
0
    }
2358
2359
    /* -------------------------------------------------------------------- */
2360
    /*      Initialize overview information.                                */
2361
    /* -------------------------------------------------------------------- */
2362
0
    poDS->SetDescription(poOpenInfo->pszFilename);
2363
0
    CPLString osOverviewFile;
2364
0
    osOverviewFile =
2365
0
        CPLSPrintf("%s_%dm.tif.ovr", osFilename.c_str(), nSubDSPrecision);
2366
0
    poDS->SetMetadataItem("OVERVIEW_FILE", osOverviewFile, "OVERVIEWS");
2367
0
    poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
2368
2369
0
    return poDS;
2370
0
}
2371
2372
/************************************************************************/
2373
/*                    OpenL1BSubdatasetWithGeoloc()                     */
2374
/************************************************************************/
2375
2376
GDALDataset *
2377
SENTINEL2Dataset::OpenL1BSubdatasetWithGeoloc(GDALOpenInfo *poOpenInfo)
2378
0
{
2379
0
    CPLString osFilename;
2380
0
    CPLAssert(
2381
0
        STARTS_WITH_CI(poOpenInfo->pszFilename, "SENTINEL2_L1B_WITH_GEOLOC:"));
2382
0
    const CPLStringList aosTokens(
2383
0
        CSLTokenizeString2(poOpenInfo->pszFilename, ":",
2384
0
                           CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES));
2385
0
    if (aosTokens.size() != 3)
2386
0
    {
2387
0
        CPLError(CE_Failure, CPLE_AppDefined,
2388
0
                 "OpenL1BSubdatasetWithGeoloc(): invalid number of tokens in "
2389
0
                 "subdataset name");
2390
0
        return nullptr;
2391
0
    }
2392
0
    const char *pszMainXMLFilename = aosTokens[1];
2393
0
    const char *pszGeolocVRT = aosTokens[2];
2394
2395
0
    const size_t nLen = strlen(pszGeolocVRT);
2396
0
    if (nLen <= strlen("_Dxx_Byy") || pszGeolocVRT[nLen - 7] != 'D' ||
2397
0
        pszGeolocVRT[nLen - 3] != 'B')
2398
0
    {
2399
0
        CPLError(CE_Failure, CPLE_AppDefined,
2400
0
                 "Invalid subdataset component name");
2401
0
        return nullptr;
2402
0
    }
2403
2404
    // Open main XML file
2405
    // Products accessible to expert users through CDSE (Copernicus Data Space Ecosystem)
2406
    // might not contain all granules referenced in the datastrip. Take that
2407
    // into account by checking granules effectively declared in the top level
2408
    // XML file  to avoid rejecting them. The geolocation VRT is referenced
2409
    // with respect to the first expected granule.
2410
0
    CPLXMLNode *psRoot = CPLParseXMLFile(pszMainXMLFilename);
2411
0
    if (psRoot == nullptr)
2412
0
    {
2413
0
        CPLDebug("SENTINEL2", "Cannot parse XML file '%s'", pszMainXMLFilename);
2414
0
        return nullptr;
2415
0
    }
2416
2417
0
    SENTINEL2_CPLXMLNodeHolder oXMLHolder(psRoot);
2418
0
    CPLStripXMLNamespace(psRoot, nullptr, TRUE);
2419
0
    std::vector<CPLString> aosGranuleList;
2420
0
    if (!SENTINEL2GetGranuleList(psRoot, SENTINEL2_L1B, pszMainXMLFilename,
2421
0
                                 aosGranuleList))
2422
0
    {
2423
0
        CPLDebug("SENTINEL2", "Failed to get granule list");
2424
0
        return nullptr;
2425
0
    }
2426
0
    std::set<std::string> aoSetGranuleId;
2427
0
    for (const auto &aosGranulePath : aosGranuleList)
2428
0
    {
2429
0
        std::string osGranuleId =
2430
0
            CPLGetFilename(CPLGetDirnameSafe(aosGranulePath.c_str()).c_str());
2431
0
        aoSetGranuleId.insert(std::move(osGranuleId));
2432
0
    }
2433
2434
    // Find in which datastrip we are
2435
0
    const std::string osDatastrip(
2436
0
        CPLString(pszGeolocVRT, nLen - strlen("_Dxx_Byy"))
2437
0
            .replaceAll("_GEO_", "_MSI_"));
2438
0
    const std::string osDetectorId(pszGeolocVRT + nLen - 6, 2);
2439
0
    const std::string osBandId(pszGeolocVRT + nLen - 2, 2);
2440
2441
0
    const char chSeparator = SENTINEL2GetPathSeparator(pszMainXMLFilename);
2442
0
    const char achSeparator[] = {chSeparator, 0};
2443
2444
0
    const std::string osDatastripRoot(
2445
0
        std::string(CPLGetDirnameSafe(pszMainXMLFilename))
2446
0
            .append(achSeparator)
2447
0
            .append("DATASTRIP"));
2448
0
    const CPLStringList aosList(VSIReadDir(osDatastripRoot.c_str()));
2449
0
    std::string osDatastripSubdir;
2450
0
    for (const char *pszDatastripId : aosList)
2451
0
    {
2452
0
        if (STARTS_WITH_CI(pszDatastripId, osDatastrip.c_str()))
2453
0
        {
2454
0
            osDatastripSubdir = pszDatastripId;
2455
0
            break;
2456
0
        }
2457
0
    }
2458
0
    if (osDatastripSubdir.empty())
2459
0
    {
2460
0
        CPLError(CE_Failure, CPLE_AppDefined,
2461
0
                 "Cannot find a file in %s starting with %s",
2462
0
                 osDatastripRoot.c_str(), osDatastrip.c_str());
2463
0
        return nullptr;
2464
0
    }
2465
2466
0
    const std::string osDatastripSubdirFull = std::string(osDatastripRoot)
2467
0
                                                  .append(achSeparator)
2468
0
                                                  .append(osDatastripSubdir);
2469
0
    CPL_IGNORE_RET_VAL(osDatastripRoot);
2470
0
    const std::string osXMLDatastrip =
2471
0
        std::string(osDatastripSubdirFull)
2472
0
            .append(achSeparator)
2473
0
            .append(CPLString(osDatastrip).replaceAll("_MSI_", "_MTD_"))
2474
0
            .append(".xml");
2475
0
    if (CPLHasPathTraversal(osXMLDatastrip.c_str()))
2476
0
    {
2477
0
        CPLError(CE_Failure, CPLE_NotSupported, "Path traversal detected in %s",
2478
0
                 osXMLDatastrip.c_str());
2479
0
        return nullptr;
2480
0
    }
2481
0
    CPLXMLTreeCloser poXML(CPLParseXMLFile(osXMLDatastrip.c_str()));
2482
0
    if (!poXML)
2483
0
    {
2484
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse XML file '%s'",
2485
0
                 osXMLDatastrip.c_str());
2486
0
        return nullptr;
2487
0
    }
2488
0
    CPLStripXMLNamespace(poXML.get(), nullptr, TRUE);
2489
2490
0
    const CPLXMLNode *psDetectorList =
2491
0
        CPLGetXMLNode(poXML.get(), "=Level-1B_DataStrip_ID.Image_Data_Info."
2492
0
                                   "Granules_Information.Detector_List");
2493
0
    if (!psDetectorList)
2494
0
    {
2495
0
        CPLError(CE_Failure, CPLE_AppDefined,
2496
0
                 "Cannot find <Detector_List> in %s", osXMLDatastrip.c_str());
2497
0
        return nullptr;
2498
0
    }
2499
2500
0
    struct GranuleDesc
2501
0
    {
2502
0
        const char *pszGranuleId = nullptr;
2503
0
        int nPosition = 0;
2504
0
    };
2505
2506
0
    std::vector<GranuleDesc> asGranules;
2507
2508
    // Get the list of granules for the current detector and band
2509
0
    for (const CPLXMLNode *psDetector = psDetectorList->psChild; psDetector;
2510
0
         psDetector = psDetector->psNext)
2511
0
    {
2512
0
        if (psDetector->eType == CXT_Element &&
2513
0
            EQUAL(psDetector->pszValue, "Detector") &&
2514
0
            CPLGetXMLValue(psDetector, "detectorId", "") == osDetectorId)
2515
0
        {
2516
0
            const CPLXMLNode *psGranuleList =
2517
0
                CPLGetXMLNode(psDetector, "Granule_List");
2518
0
            if (psGranuleList)
2519
0
            {
2520
0
                for (const CPLXMLNode *psGranule = psGranuleList->psChild;
2521
0
                     psGranule; psGranule = psGranule->psNext)
2522
0
                {
2523
0
                    if (psGranule->eType == CXT_Element &&
2524
0
                        EQUAL(psGranule->pszValue, "Granule"))
2525
0
                    {
2526
0
                        const char *pszGranuleId =
2527
0
                            CPLGetXMLValue(psGranule, "granuleId", "");
2528
0
                        const char *pszPosition =
2529
0
                            CPLGetXMLValue(psGranule, "POSITION", "");
2530
0
                        GranuleDesc sDesc;
2531
0
                        sDesc.pszGranuleId = pszGranuleId;
2532
0
                        sDesc.nPosition = atoi(pszPosition);
2533
0
                        asGranules.push_back(sDesc);
2534
0
                    }
2535
0
                }
2536
0
            }
2537
0
            break;
2538
0
        }
2539
0
    }
2540
0
    if (asGranules.empty())
2541
0
    {
2542
0
        CPLError(CE_Failure, CPLE_AppDefined,
2543
0
                 "Cannot find granules for detector %s in %s",
2544
0
                 osDetectorId.c_str(), osXMLDatastrip.c_str());
2545
0
        return nullptr;
2546
0
    }
2547
0
    std::sort(asGranules.begin(), asGranules.end(),
2548
0
              [](const GranuleDesc &sDescA, const GranuleDesc &sDescB)
2549
0
              { return sDescA.nPosition < sDescB.nPosition; });
2550
0
    const int nGranuleCount = static_cast<int>(asGranules.size());
2551
0
    constexpr int ROWS_PER_10M_GRANULE = 2304;
2552
0
    int nIdxFirstExpectedGranule = -1;
2553
0
    int nIdxLastExpectedGranule = -1;
2554
0
    for (int i = 0; i < nGranuleCount; ++i)
2555
0
    {
2556
0
        if (asGranules[i].nPosition != 1 + i * ROWS_PER_10M_GRANULE)
2557
0
        {
2558
0
            CPLError(
2559
0
                CE_Failure, CPLE_NotSupported,
2560
0
                "Granule %s is declared to be at position %d, was expecting %d",
2561
0
                asGranules[i].pszGranuleId, asGranules[i].nPosition,
2562
0
                1 + i * ROWS_PER_10M_GRANULE);
2563
0
            return nullptr;
2564
0
        }
2565
0
        if (cpl::contains(aoSetGranuleId, asGranules[i].pszGranuleId))
2566
0
        {
2567
0
            if (nIdxFirstExpectedGranule < 0)
2568
0
                nIdxFirstExpectedGranule = i;
2569
0
            nIdxLastExpectedGranule = i;
2570
0
        }
2571
0
    }
2572
2573
0
    if (nIdxFirstExpectedGranule < 0)
2574
0
    {
2575
0
        CPLError(CE_Failure, CPLE_AppDefined, "No matching expected granule!");
2576
0
        return nullptr;
2577
0
    }
2578
0
    const int nExpectedGranuleCount =
2579
0
        nIdxLastExpectedGranule - nIdxFirstExpectedGranule + 1;
2580
2581
0
    const std::string osBandName = std::string("B").append(
2582
0
        osBandId == "8A"
2583
0
            ? osBandId
2584
0
            : std::string(CPLSPrintf("%d", atoi(osBandId.c_str()))));
2585
0
    const SENTINEL2BandDescription *psBandDesc =
2586
0
        SENTINEL2GetBandDesc(osBandName.c_str());
2587
0
    if (psBandDesc == nullptr)
2588
0
    {
2589
0
        CPLError(CE_Failure, CPLE_NotSupported, "Unknown band id %s",
2590
0
                 osBandId.c_str());
2591
0
        return nullptr;
2592
0
    }
2593
2594
0
    const int nResolution = psBandDesc->nResolution;
2595
0
    const int nRowsPerGranule = nResolution == RES_10M   ? ROWS_PER_10M_GRANULE
2596
0
                                : nResolution == RES_20M ? 1152
2597
0
                                                         : 384;
2598
0
    const int nColsPerGranule = nResolution == RES_10M   ? 2552
2599
0
                                : nResolution == RES_20M ? 1276
2600
0
                                                         : 425;
2601
0
    auto poDS = std::make_unique<SENTINEL2Dataset>(
2602
0
        nColsPerGranule, nRowsPerGranule * nExpectedGranuleCount);
2603
2604
0
    constexpr GDALDataType eDT = GDT_UInt16;
2605
0
    auto poBand = new VRTSourcedRasterBand(
2606
0
        poDS.get(), 1, eDT, poDS->nRasterXSize, poDS->nRasterYSize);
2607
0
    poDS->SetBand(1, poBand);
2608
2609
0
    SENTINEL2SetBandMetadata(poBand, osBandId);
2610
2611
    // Create the virtual raster by adding each granule
2612
0
    const std::string osGranuleRoot =
2613
0
        std::string(CPLGetDirnameSafe(pszMainXMLFilename))
2614
0
            .append(achSeparator)
2615
0
            .append("GRANULE");
2616
0
    int nValMax = 0;
2617
0
    int nBits = 0;
2618
0
    for (int i = nIdxFirstExpectedGranule; i <= nIdxLastExpectedGranule; ++i)
2619
0
    {
2620
0
        const auto &sGranule = asGranules[i];
2621
0
        const std::string osTile(
2622
0
            SENTINEL2GetTilename(std::string(osGranuleRoot)
2623
0
                                     .append(achSeparator)
2624
0
                                     .append(sGranule.pszGranuleId),
2625
0
                                 sGranule.pszGranuleId, osBandId.c_str()));
2626
2627
0
        bool bTileFound = false;
2628
0
        if (nValMax == 0)
2629
0
        {
2630
            /* It is supposed to be 12 bits, but some products have 15 bits */
2631
0
            int nGranuleWidth = 0;
2632
0
            int nGranuleHeight = 0;
2633
0
            if (SENTINEL2GetTileInfo(osTile.c_str(), &nGranuleWidth,
2634
0
                                     &nGranuleHeight, &nBits))
2635
0
            {
2636
0
                bTileFound = true;
2637
0
                if (nBits <= 16)
2638
0
                    nValMax = (1 << nBits) - 1;
2639
0
                else
2640
0
                {
2641
0
                    CPLDebug("SENTINEL2", "Unexpected bit depth %d", nBits);
2642
0
                    nValMax = 65535;
2643
0
                }
2644
0
                if (nGranuleWidth != nColsPerGranule ||
2645
0
                    nGranuleHeight != nRowsPerGranule)
2646
0
                {
2647
0
                    CPLError(CE_Failure, CPLE_AppDefined,
2648
0
                             "Tile %s has not expected dimensions. "
2649
0
                             "Got %dx%d, expected %dx%d",
2650
0
                             osTile.c_str(), nGranuleWidth, nGranuleHeight,
2651
0
                             nColsPerGranule, nRowsPerGranule);
2652
0
                    return nullptr;
2653
0
                }
2654
0
            }
2655
0
        }
2656
0
        else
2657
0
        {
2658
0
            VSIStatBufL sStat;
2659
0
            if (VSIStatExL(osTile.c_str(), &sStat, VSI_STAT_EXISTS_FLAG) == 0)
2660
0
                bTileFound = true;
2661
0
        }
2662
0
        if (!bTileFound)
2663
0
        {
2664
0
            CPLError(CE_Failure, CPLE_AppDefined, "Tile %s not found.",
2665
0
                     osTile.c_str());
2666
0
            return nullptr;
2667
0
        }
2668
2669
0
        poBand->AddSimpleSource(
2670
0
            osTile.c_str(), 1, 0, 0, nColsPerGranule, nRowsPerGranule, 0,
2671
0
            (i - nIdxFirstExpectedGranule) * nRowsPerGranule, nColsPerGranule,
2672
0
            nRowsPerGranule);
2673
0
    }
2674
2675
0
    if ((nBits % 8) != 0)
2676
0
    {
2677
0
        poBand->SetMetadataItem(GDALMD_NBITS, CPLSPrintf("%d", nBits),
2678
0
                                GDAL_MDD_IMAGE_STRUCTURE);
2679
0
    }
2680
2681
    // Get metadata from top MTD XML filename
2682
0
    std::set<int> oSetResolutions;
2683
0
    std::map<int, std::set<CPLString>> oMapResolutionsToBands;
2684
0
    char **papszMD = nullptr;
2685
0
    std::string osGranuleMTD = CPLFormFilenameSafe(
2686
0
        CPLFormFilenameSafe(osGranuleRoot.c_str(), asGranules[0].pszGranuleId,
2687
0
                            nullptr)
2688
0
            .c_str(),
2689
0
        asGranules[0].pszGranuleId, nullptr);
2690
0
    if (osGranuleMTD.size() > strlen("_NXX.YY") &&
2691
0
        osGranuleMTD[osGranuleMTD.size() - 7] == '_' &&
2692
0
        osGranuleMTD[osGranuleMTD.size() - 6] == 'N')
2693
0
    {
2694
0
        osGranuleMTD.resize(osGranuleMTD.size() - strlen("_NXX.YY"));
2695
0
    }
2696
0
    SENTINEL2GetResolutionSetAndMainMDFromGranule(
2697
0
        osGranuleMTD.c_str(), "Level-1B_User_Product", nResolution,
2698
0
        oSetResolutions, oMapResolutionsToBands, papszMD, nullptr);
2699
0
    for (const auto [pszKey, pszValue] :
2700
0
         cpl::IterateNameValue(const_cast<CSLConstList>(papszMD)))
2701
0
    {
2702
0
        if (!EQUAL(pszKey, "FOOTPRINT"))
2703
0
            poDS->SetMetadataItem(pszKey, pszValue);
2704
0
    }
2705
0
    CSLDestroy(papszMD);
2706
2707
    // Attach geolocation array
2708
0
    const std::string osGeolocVRT = std::string(osDatastripSubdirFull)
2709
0
                                        .append(achSeparator)
2710
0
                                        .append("GEO_DATA")
2711
0
                                        .append(achSeparator)
2712
0
                                        .append(pszGeolocVRT)
2713
0
                                        .append(".vrt");
2714
0
    CPL_IGNORE_RET_VAL(osDatastripSubdirFull);
2715
0
    auto poGeolocDS = std::unique_ptr<GDALDataset>(
2716
0
        GDALDataset::Open(osGeolocVRT.c_str(), GDAL_OF_RASTER));
2717
0
    if (poGeolocDS)
2718
0
    {
2719
0
        CPLStringList osMD(CSLDuplicate(poGeolocDS->GetMetadata()));
2720
0
        osMD.SetNameValue("X_DATASET", osGeolocVRT.c_str());
2721
0
        osMD.SetNameValue("Y_DATASET", osGeolocVRT.c_str());
2722
0
        const char *pszSRS = osMD.FetchNameValue("SRS");
2723
0
        OGRSpatialReference oSRS;
2724
0
        if (oSRS.SetFromUserInput(
2725
0
                pszSRS,
2726
0
                OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
2727
0
            OGRERR_NONE)
2728
0
        {
2729
0
            osMD.SetNameValue("SRS", oSRS.exportToWkt().c_str());
2730
0
        }
2731
2732
0
        poDS->SetMetadata(osMD.List(), GDAL_MDD_GEOLOCATION);
2733
0
    }
2734
2735
0
    return poDS.release();
2736
0
}
2737
2738
/************************************************************************/
2739
/*               SENTINEL2GetGranuleList_L1CSafeCompact()               */
2740
/************************************************************************/
2741
2742
static bool SENTINEL2GetGranuleList_L1CSafeCompact(
2743
    CPLXMLNode *psMainMTD, const char *pszFilename,
2744
    std::vector<L1CSafeCompatGranuleDescription> &osList)
2745
3
{
2746
3
    CPLXMLNode *psProductInfo = CPLGetXMLNode(
2747
3
        psMainMTD, "=Level-1C_User_Product.General_Info.Product_Info");
2748
3
    if (psProductInfo == nullptr)
2749
0
    {
2750
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s",
2751
0
                 "=Level-1C_User_Product.General_Info.Product_Info");
2752
0
        return false;
2753
0
    }
2754
2755
3
    CPLXMLNode *psProductOrganisation =
2756
3
        CPLGetXMLNode(psProductInfo, "Product_Organisation");
2757
3
    if (psProductOrganisation == nullptr)
2758
0
    {
2759
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s",
2760
0
                 "Product_Organisation");
2761
0
        return false;
2762
0
    }
2763
2764
3
    CPLString osDirname(CPLGetDirnameSafe(pszFilename));
2765
3
#if !defined(_WIN32)
2766
3
    char szPointerFilename[2048];
2767
3
    int nBytes = static_cast<int>(
2768
3
        readlink(pszFilename, szPointerFilename, sizeof(szPointerFilename)));
2769
3
    if (nBytes != -1)
2770
0
    {
2771
0
        const int nOffset =
2772
0
            std::min(nBytes, static_cast<int>(sizeof(szPointerFilename) - 1));
2773
0
        szPointerFilename[nOffset] = '\0';
2774
0
        osDirname = CPLGetDirnameSafe(szPointerFilename);
2775
0
    }
2776
3
#endif
2777
2778
3
    const char chSeparator = SENTINEL2GetPathSeparator(osDirname);
2779
6
    for (CPLXMLNode *psIter = psProductOrganisation->psChild; psIter != nullptr;
2780
3
         psIter = psIter->psNext)
2781
3
    {
2782
3
        if (psIter->eType != CXT_Element ||
2783
3
            !EQUAL(psIter->pszValue, "Granule_List"))
2784
0
        {
2785
0
            continue;
2786
0
        }
2787
6
        for (CPLXMLNode *psIter2 = psIter->psChild; psIter2 != nullptr;
2788
3
             psIter2 = psIter2->psNext)
2789
3
        {
2790
3
            if (psIter2->eType != CXT_Element ||
2791
3
                !EQUAL(psIter2->pszValue, "Granule"))
2792
0
            {
2793
0
                continue;
2794
0
            }
2795
2796
3
            const char *pszImageFile =
2797
3
                CPLGetXMLValue(psIter2, "IMAGE_FILE", nullptr);
2798
3
            if (pszImageFile == nullptr || strlen(pszImageFile) < 3)
2799
0
            {
2800
0
                CPLDebug("SENTINEL2", "Missing IMAGE_FILE element");
2801
0
                continue;
2802
0
            }
2803
3
            L1CSafeCompatGranuleDescription oDesc;
2804
3
            oDesc.osBandPrefixPath = osDirname + chSeparator + pszImageFile;
2805
3
            if (CPLHasPathTraversal(oDesc.osBandPrefixPath.c_str()))
2806
0
            {
2807
0
                CPLError(CE_Failure, CPLE_NotSupported,
2808
0
                         "Path traversal detected in %s",
2809
0
                         oDesc.osBandPrefixPath.c_str());
2810
0
                return false;
2811
0
            }
2812
3
            oDesc.osBandPrefixPath.resize(oDesc.osBandPrefixPath.size() -
2813
3
                                          3);  // strip B12
2814
            // GRANULE/L1C_T30TXT_A007999_20170102T111441/IMG_DATA/T30TXT_20170102T111442_B12
2815
            // --> GRANULE/L1C_T30TXT_A007999_20170102T111441/MTD_TL.xml
2816
3
            oDesc.osMTDTLPath =
2817
3
                osDirname + chSeparator +
2818
3
                CPLGetDirnameSafe(CPLGetDirnameSafe(pszImageFile).c_str()) +
2819
3
                chSeparator + "MTD_TL.xml";
2820
3
            osList.push_back(std::move(oDesc));
2821
3
        }
2822
3
    }
2823
2824
3
    return true;
2825
3
}
2826
2827
/************************************************************************/
2828
/*               SENTINEL2GetGranuleList_L2ASafeCompact()               */
2829
/************************************************************************/
2830
2831
static bool SENTINEL2GetGranuleList_L2ASafeCompact(
2832
    CPLXMLNode *psMainMTD, const char *pszFilename,
2833
    std::vector<L1CSafeCompatGranuleDescription> &osList)
2834
6
{
2835
6
    const char *pszNodePath =
2836
6
        "=Level-2A_User_Product.General_Info.Product_Info";
2837
6
    CPLXMLNode *psProductInfo = CPLGetXMLNode(psMainMTD, pszNodePath);
2838
6
    if (psProductInfo == nullptr)
2839
6
    {
2840
6
        pszNodePath = "=Level-2A_User_Product.General_Info.L2A_Product_Info";
2841
6
        psProductInfo = CPLGetXMLNode(psMainMTD, pszNodePath);
2842
6
    }
2843
6
    if (psProductInfo == nullptr)
2844
0
    {
2845
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s", pszNodePath);
2846
0
        return false;
2847
0
    }
2848
2849
6
    CPLXMLNode *psProductOrganisation =
2850
6
        CPLGetXMLNode(psProductInfo, "Product_Organisation");
2851
6
    if (psProductOrganisation == nullptr)
2852
6
    {
2853
6
        psProductOrganisation =
2854
6
            CPLGetXMLNode(psProductInfo, "L2A_Product_Organisation");
2855
6
        if (psProductOrganisation == nullptr)
2856
0
        {
2857
0
            CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s",
2858
0
                     "Product_Organisation");
2859
0
            return false;
2860
0
        }
2861
6
    }
2862
2863
6
    CPLString osDirname(CPLGetDirnameSafe(pszFilename));
2864
6
#if !defined(_WIN32)
2865
6
    char szPointerFilename[2048];
2866
6
    int nBytes = static_cast<int>(
2867
6
        readlink(pszFilename, szPointerFilename, sizeof(szPointerFilename)));
2868
6
    if (nBytes != -1)
2869
0
    {
2870
0
        const int nOffset =
2871
0
            std::min(nBytes, static_cast<int>(sizeof(szPointerFilename) - 1));
2872
0
        szPointerFilename[nOffset] = '\0';
2873
0
        osDirname = CPLGetDirnameSafe(szPointerFilename);
2874
0
    }
2875
6
#endif
2876
2877
6
    const char chSeparator = SENTINEL2GetPathSeparator(osDirname);
2878
25
    for (CPLXMLNode *psIter = psProductOrganisation->psChild; psIter != nullptr;
2879
19
         psIter = psIter->psNext)
2880
19
    {
2881
19
        if (psIter->eType != CXT_Element ||
2882
18
            !EQUAL(psIter->pszValue, "Granule_List"))
2883
1
        {
2884
1
            continue;
2885
1
        }
2886
37
        for (CPLXMLNode *psIter2 = psIter->psChild; psIter2 != nullptr;
2887
19
             psIter2 = psIter2->psNext)
2888
19
        {
2889
19
            if (psIter2->eType != CXT_Element ||
2890
18
                !EQUAL(psIter2->pszValue, "Granule"))
2891
1
            {
2892
1
                continue;
2893
1
            }
2894
2895
18
            const char *pszImageFile =
2896
18
                CPLGetXMLValue(psIter2, "IMAGE_FILE", nullptr);
2897
18
            if (pszImageFile == nullptr)
2898
18
            {
2899
18
                pszImageFile =
2900
18
                    CPLGetXMLValue(psIter2, "IMAGE_FILE_2A", nullptr);
2901
18
                if (pszImageFile == nullptr || strlen(pszImageFile) < 3)
2902
0
                {
2903
0
                    CPLDebug("SENTINEL2", "Missing IMAGE_FILE element");
2904
0
                    continue;
2905
0
                }
2906
18
            }
2907
18
            L1CSafeCompatGranuleDescription oDesc;
2908
18
            oDesc.osBandPrefixPath = osDirname + chSeparator + pszImageFile;
2909
18
            if (oDesc.osBandPrefixPath.size() < 36)
2910
0
            {
2911
0
                CPLDebug("SENTINEL2", "Band prefix path too short");
2912
0
                continue;
2913
0
            }
2914
18
            if (CPLHasPathTraversal(oDesc.osBandPrefixPath.c_str()))
2915
0
            {
2916
0
                CPLError(CE_Failure, CPLE_NotSupported,
2917
0
                         "Path traversal detected in %s",
2918
0
                         oDesc.osBandPrefixPath.c_str());
2919
0
                return false;
2920
0
            }
2921
18
            oDesc.osBandPrefixPath.resize(oDesc.osBandPrefixPath.size() - 36);
2922
            // GRANULE/L1C_T30TXT_A007999_20170102T111441/IMG_DATA/T30TXT_20170102T111442_B12_60m
2923
            // --> GRANULE/L1C_T30TXT_A007999_20170102T111441/MTD_TL.xml
2924
18
            oDesc.osMTDTLPath =
2925
18
                osDirname + chSeparator +
2926
18
                CPLGetDirnameSafe(CPLGetDirnameSafe(pszImageFile).c_str());
2927
18
            if (oDesc.osMTDTLPath.size() < 9)
2928
0
            {
2929
0
                CPLDebug("SENTINEL2", "MTDTL path too short");
2930
0
                continue;
2931
0
            }
2932
18
            oDesc.osMTDTLPath.resize(oDesc.osMTDTLPath.size() - 9);
2933
18
            oDesc.osMTDTLPath = oDesc.osMTDTLPath + chSeparator + "MTD_TL.xml";
2934
18
            osList.push_back(std::move(oDesc));
2935
18
        }
2936
18
    }
2937
2938
6
    return true;
2939
6
}
2940
2941
/************************************************************************/
2942
/*                            OpenL1C_L2A()                             */
2943
/************************************************************************/
2944
2945
GDALDataset *SENTINEL2Dataset::OpenL1C_L2A(const char *pszFilename,
2946
                                           SENTINEL2Level eLevel)
2947
108
{
2948
108
    CPLXMLNode *psRoot = CPLParseXMLFile(pszFilename);
2949
108
    if (psRoot == nullptr)
2950
33
    {
2951
33
        CPLDebug("SENTINEL2", "Cannot parse XML file '%s'", pszFilename);
2952
33
        return nullptr;
2953
33
    }
2954
2955
75
    char *pszOriginalXML = CPLSerializeXMLTree(psRoot);
2956
75
    CPLString osOriginalXML;
2957
75
    if (pszOriginalXML)
2958
75
        osOriginalXML = pszOriginalXML;
2959
75
    CPLFree(pszOriginalXML);
2960
2961
75
    SENTINEL2_CPLXMLNodeHolder oXMLHolder(psRoot);
2962
75
    CPLStripXMLNamespace(psRoot, nullptr, TRUE);
2963
2964
75
    const char *pszNodePath =
2965
75
        (eLevel == SENTINEL2_L1C)
2966
75
            ? "=Level-1C_User_Product.General_Info.Product_Info"
2967
75
            : "=Level-2A_User_Product.General_Info.Product_Info";
2968
75
    CPLXMLNode *psProductInfo = CPLGetXMLNode(psRoot, pszNodePath);
2969
75
    if (psProductInfo == nullptr && eLevel == SENTINEL2_L2A)
2970
11
    {
2971
11
        pszNodePath = "=Level-2A_User_Product.General_Info.L2A_Product_Info";
2972
11
        psProductInfo = CPLGetXMLNode(psRoot, pszNodePath);
2973
11
    }
2974
75
    if (psProductInfo == nullptr)
2975
59
    {
2976
59
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s", pszNodePath);
2977
59
        return nullptr;
2978
59
    }
2979
2980
16
    const bool bIsSafeCompact =
2981
16
        EQUAL(CPLGetXMLValue(psProductInfo, "Query_Options.PRODUCT_FORMAT", ""),
2982
16
              "SAFE_COMPACT");
2983
2984
16
    std::set<int> oSetResolutions;
2985
16
    std::map<int, std::set<CPLString>> oMapResolutionsToBands;
2986
16
    if (bIsSafeCompact)
2987
9
    {
2988
9
        for (const auto &sBandDesc : asBandDesc)
2989
117
        {
2990
            // L2A does not contain B10
2991
117
            if (eLevel == SENTINEL2_L2A &&
2992
78
                strcmp(sBandDesc.pszBandName, "B10") == 0)
2993
6
                continue;
2994
111
            oSetResolutions.insert(sBandDesc.nResolution);
2995
111
            CPLString osName = sBandDesc.pszBandName + 1; /* skip B character */
2996
111
            if (atoi(osName) < 10)
2997
90
                osName = "0" + osName;
2998
111
            oMapResolutionsToBands[sBandDesc.nResolution].insert(
2999
111
                std::move(osName));
3000
111
        }
3001
9
        if (eLevel == SENTINEL2_L2A)
3002
6
        {
3003
6
            for (const auto &sL2ABandDesc : asL2ABandDesc)
3004
72
            {
3005
72
                oSetResolutions.insert(sL2ABandDesc.nResolution);
3006
72
                oMapResolutionsToBands[sL2ABandDesc.nResolution].insert(
3007
72
                    sL2ABandDesc.pszBandName);
3008
72
            }
3009
6
        }
3010
9
    }
3011
7
    else if (eLevel == SENTINEL2_L1C &&
3012
2
             !SENTINEL2GetResolutionSet(psProductInfo, oSetResolutions,
3013
2
                                        oMapResolutionsToBands))
3014
0
    {
3015
0
        CPLDebug("SENTINEL2", "Failed to get resolution set");
3016
0
        return nullptr;
3017
0
    }
3018
3019
16
    std::vector<CPLString> aosGranuleList;
3020
16
    if (bIsSafeCompact)
3021
9
    {
3022
9
        std::vector<L1CSafeCompatGranuleDescription>
3023
9
            aoL1CSafeCompactGranuleList;
3024
9
        if (eLevel == SENTINEL2_L1C &&
3025
3
            !SENTINEL2GetGranuleList_L1CSafeCompact(
3026
3
                psRoot, pszFilename, aoL1CSafeCompactGranuleList))
3027
0
        {
3028
0
            CPLDebug("SENTINEL2", "Failed to get granule list");
3029
0
            return nullptr;
3030
0
        }
3031
9
        else if (eLevel == SENTINEL2_L2A &&
3032
6
                 !SENTINEL2GetGranuleList_L2ASafeCompact(
3033
6
                     psRoot, pszFilename, aoL1CSafeCompactGranuleList))
3034
0
        {
3035
0
            CPLDebug("SENTINEL2", "Failed to get granule list");
3036
0
            return nullptr;
3037
0
        }
3038
30
        for (size_t i = 0; i < aoL1CSafeCompactGranuleList.size(); ++i)
3039
21
        {
3040
21
            aosGranuleList.push_back(
3041
21
                aoL1CSafeCompactGranuleList[i].osMTDTLPath);
3042
21
        }
3043
9
    }
3044
7
    else if (!SENTINEL2GetGranuleList(
3045
7
                 psRoot, eLevel, pszFilename, aosGranuleList,
3046
7
                 (eLevel == SENTINEL2_L1C) ? nullptr : &oSetResolutions,
3047
7
                 (eLevel == SENTINEL2_L1C) ? nullptr : &oMapResolutionsToBands))
3048
0
    {
3049
0
        CPLDebug("SENTINEL2", "Failed to get granule list");
3050
0
        return nullptr;
3051
0
    }
3052
16
    if (oSetResolutions.empty())
3053
0
    {
3054
0
        CPLDebug("SENTINEL2", "Resolution set is empty");
3055
0
        return nullptr;
3056
0
    }
3057
3058
16
    std::set<int> oSetEPSGCodes;
3059
46
    for (size_t i = 0; i < aosGranuleList.size(); i++)
3060
30
    {
3061
30
        int nEPSGCode = 0;
3062
30
        if (SENTINEL2GetGranuleInfo(eLevel, aosGranuleList[i],
3063
30
                                    *(oSetResolutions.begin()), &nEPSGCode))
3064
0
        {
3065
0
            oSetEPSGCodes.insert(nEPSGCode);
3066
0
        }
3067
30
    }
3068
3069
16
    SENTINEL2DatasetContainer *poDS = new SENTINEL2DatasetContainer();
3070
16
    char **papszMD = SENTINEL2GetUserProductMetadata(
3071
16
        psRoot, (eLevel == SENTINEL2_L1C) ? "Level-1C_User_Product"
3072
16
                                          : "Level-2A_User_Product");
3073
16
    poDS->GDALDataset::SetMetadata(papszMD);
3074
16
    CSLDestroy(papszMD);
3075
3076
16
    if (!osOriginalXML.empty())
3077
16
    {
3078
16
        char *apszXMLMD[2];
3079
16
        apszXMLMD[0] = const_cast<char *>(osOriginalXML.c_str());
3080
16
        apszXMLMD[1] = nullptr;
3081
16
        poDS->GDALDataset::SetMetadata(apszXMLMD, "xml:SENTINEL2");
3082
16
    }
3083
3084
16
    const char *pszPrefix =
3085
16
        (eLevel == SENTINEL2_L1C) ? "SENTINEL2_L1C" : "SENTINEL2_L2A";
3086
3087
    /* Create subdatsets per resolution (10, 20, 60m) and EPSG codes */
3088
16
    int iSubDSNum = 1;
3089
16
    for (std::set<int>::const_iterator oIterRes = oSetResolutions.begin();
3090
56
         oIterRes != oSetResolutions.end(); ++oIterRes)
3091
40
    {
3092
40
        const int nResolution = *oIterRes;
3093
3094
40
        for (std::set<int>::const_iterator oIterEPSG = oSetEPSGCodes.begin();
3095
40
             oIterEPSG != oSetEPSGCodes.end(); ++oIterEPSG)
3096
0
        {
3097
0
            const int nEPSGCode = *oIterEPSG;
3098
0
            poDS->GDALDataset::SetMetadataItem(
3099
0
                CPLSPrintf("SUBDATASET_%d_NAME", iSubDSNum),
3100
0
                CPLSPrintf("%s:%s:%dm:EPSG_%d", pszPrefix, pszFilename,
3101
0
                           nResolution, nEPSGCode),
3102
0
                GDAL_MDD_SUBDATASETS);
3103
3104
0
            CPLString osBandNames = SENTINEL2GetBandListForResolution(
3105
0
                oMapResolutionsToBands[nResolution]);
3106
3107
0
            CPLString osDesc(CPLSPrintf("Bands %s with %dm resolution",
3108
0
                                        osBandNames.c_str(), nResolution));
3109
0
            if (nEPSGCode >= 32601 && nEPSGCode <= 32660)
3110
0
                osDesc += CPLSPrintf(", UTM %dN", nEPSGCode - 32600);
3111
0
            else if (nEPSGCode >= 32701 && nEPSGCode <= 32760)
3112
0
                osDesc += CPLSPrintf(", UTM %dS", nEPSGCode - 32700);
3113
0
            else
3114
0
                osDesc += CPLSPrintf(", EPSG:%d", nEPSGCode);
3115
0
            poDS->GDALDataset::SetMetadataItem(
3116
0
                CPLSPrintf("SUBDATASET_%d_DESC", iSubDSNum), osDesc.c_str(),
3117
0
                GDAL_MDD_SUBDATASETS);
3118
3119
0
            iSubDSNum++;
3120
0
        }
3121
40
    }
3122
3123
    /* Expose TCI or PREVIEW subdatasets */
3124
16
    if (bIsSafeCompact)
3125
9
    {
3126
9
        for (std::set<int>::const_iterator oIterEPSG = oSetEPSGCodes.begin();
3127
9
             oIterEPSG != oSetEPSGCodes.end(); ++oIterEPSG)
3128
0
        {
3129
0
            const int nEPSGCode = *oIterEPSG;
3130
0
            poDS->GDALDataset::SetMetadataItem(
3131
0
                CPLSPrintf("SUBDATASET_%d_NAME", iSubDSNum),
3132
0
                CPLSPrintf("%s:%s:TCI:EPSG_%d", pszPrefix, pszFilename,
3133
0
                           nEPSGCode),
3134
0
                GDAL_MDD_SUBDATASETS);
3135
3136
0
            CPLString osDesc("True color image");
3137
0
            if (nEPSGCode >= 32601 && nEPSGCode <= 32660)
3138
0
                osDesc += CPLSPrintf(", UTM %dN", nEPSGCode - 32600);
3139
0
            else if (nEPSGCode >= 32701 && nEPSGCode <= 32760)
3140
0
                osDesc += CPLSPrintf(", UTM %dS", nEPSGCode - 32700);
3141
0
            else
3142
0
                osDesc += CPLSPrintf(", EPSG:%d", nEPSGCode);
3143
0
            poDS->GDALDataset::SetMetadataItem(
3144
0
                CPLSPrintf("SUBDATASET_%d_DESC", iSubDSNum), osDesc.c_str(),
3145
0
                GDAL_MDD_SUBDATASETS);
3146
3147
0
            iSubDSNum++;
3148
0
        }
3149
9
    }
3150
7
    else
3151
7
    {
3152
7
        for (std::set<int>::const_iterator oIterEPSG = oSetEPSGCodes.begin();
3153
7
             oIterEPSG != oSetEPSGCodes.end(); ++oIterEPSG)
3154
0
        {
3155
0
            const int nEPSGCode = *oIterEPSG;
3156
0
            poDS->GDALDataset::SetMetadataItem(
3157
0
                CPLSPrintf("SUBDATASET_%d_NAME", iSubDSNum),
3158
0
                CPLSPrintf("%s:%s:PREVIEW:EPSG_%d", pszPrefix, pszFilename,
3159
0
                           nEPSGCode),
3160
0
                GDAL_MDD_SUBDATASETS);
3161
3162
0
            CPLString osDesc("RGB preview");
3163
0
            if (nEPSGCode >= 32601 && nEPSGCode <= 32660)
3164
0
                osDesc += CPLSPrintf(", UTM %dN", nEPSGCode - 32600);
3165
0
            else if (nEPSGCode >= 32701 && nEPSGCode <= 32760)
3166
0
                osDesc += CPLSPrintf(", UTM %dS", nEPSGCode - 32700);
3167
0
            else
3168
0
                osDesc += CPLSPrintf(", EPSG:%d", nEPSGCode);
3169
0
            poDS->GDALDataset::SetMetadataItem(
3170
0
                CPLSPrintf("SUBDATASET_%d_DESC", iSubDSNum), osDesc.c_str(),
3171
0
                GDAL_MDD_SUBDATASETS);
3172
3173
0
            iSubDSNum++;
3174
0
        }
3175
7
    }
3176
3177
16
    pszNodePath =
3178
16
        (eLevel == SENTINEL2_L1C)
3179
16
            ? "=Level-1C_User_Product.Geometric_Info.Product_Footprint."
3180
5
              "Product_Footprint.Global_Footprint.EXT_POS_LIST"
3181
16
            : "=Level-2A_User_Product.Geometric_Info.Product_Footprint."
3182
11
              "Product_Footprint.Global_Footprint.EXT_POS_LIST";
3183
16
    const char *pszPosList = CPLGetXMLValue(psRoot, pszNodePath, nullptr);
3184
16
    if (pszPosList != nullptr)
3185
16
    {
3186
16
        CPLString osPolygon = SENTINEL2GetPolygonWKTFromPosList(pszPosList);
3187
16
        if (!osPolygon.empty())
3188
16
            poDS->GDALDataset::SetMetadataItem("FOOTPRINT", osPolygon.c_str());
3189
16
    }
3190
3191
16
    return poDS;
3192
16
}
3193
3194
/************************************************************************/
3195
/*                    SENTINEL2GetL1BCTileMetadata()                    */
3196
/************************************************************************/
3197
3198
static char **SENTINEL2GetL1BCTileMetadata(CPLXMLNode *psMainMTD)
3199
52
{
3200
52
    CPLStringList aosList;
3201
3202
52
    CPLXMLNode *psRoot = CPLGetXMLNode(psMainMTD, "=Level-1C_Tile_ID");
3203
52
    if (psRoot == nullptr)
3204
50
    {
3205
50
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find =Level-1C_Tile_ID");
3206
50
        return nullptr;
3207
50
    }
3208
2
    CPLXMLNode *psGeneralInfo = CPLGetXMLNode(psRoot, "General_Info");
3209
2
    for (CPLXMLNode *psIter =
3210
2
             (psGeneralInfo ? psGeneralInfo->psChild : nullptr);
3211
12
         psIter != nullptr; psIter = psIter->psNext)
3212
10
    {
3213
10
        if (psIter->eType != CXT_Element)
3214
0
            continue;
3215
10
        const char *pszValue = CPLGetXMLValue(psIter, nullptr, nullptr);
3216
10
        if (pszValue != nullptr)
3217
8
        {
3218
8
            aosList.AddNameValue(psIter->pszValue, pszValue);
3219
8
        }
3220
10
    }
3221
3222
2
    CPLXMLNode *psQII = CPLGetXMLNode(psRoot, "Quality_Indicators_Info");
3223
2
    if (psQII != nullptr)
3224
2
    {
3225
2
        CPLXMLNode *psICCQI = CPLGetXMLNode(psQII, "Image_Content_QI");
3226
2
        for (CPLXMLNode *psIter = (psICCQI ? psICCQI->psChild : nullptr);
3227
7
             psIter != nullptr; psIter = psIter->psNext)
3228
5
        {
3229
5
            if (psIter->eType != CXT_Element)
3230
1
                continue;
3231
4
            if (psIter->psChild != nullptr &&
3232
4
                psIter->psChild->eType == CXT_Text)
3233
4
            {
3234
4
                aosList.AddNameValue(psIter->pszValue,
3235
4
                                     psIter->psChild->pszValue);
3236
4
            }
3237
4
        }
3238
2
    }
3239
3240
2
    return aosList.StealList();
3241
52
}
3242
3243
/************************************************************************/
3244
/*                            OpenL1CTile()                             */
3245
/************************************************************************/
3246
3247
GDALDataset *SENTINEL2Dataset::OpenL1CTile(const char *pszFilename,
3248
                                           CPLXMLNode **ppsRootMainMTD,
3249
                                           int nResolutionOfInterest,
3250
                                           std::set<CPLString> *poBandSet)
3251
104
{
3252
104
    CPLXMLNode *psRoot = CPLParseXMLFile(pszFilename);
3253
104
    if (psRoot == nullptr)
3254
52
    {
3255
52
        CPLDebug("SENTINEL2", "Cannot parse XML file '%s'", pszFilename);
3256
52
        return nullptr;
3257
52
    }
3258
3259
52
    char *pszOriginalXML = CPLSerializeXMLTree(psRoot);
3260
52
    CPLString osOriginalXML;
3261
52
    if (pszOriginalXML)
3262
52
        osOriginalXML = pszOriginalXML;
3263
52
    CPLFree(pszOriginalXML);
3264
3265
52
    SENTINEL2_CPLXMLNodeHolder oXMLHolder(psRoot);
3266
52
    CPLStripXMLNamespace(psRoot, nullptr, TRUE);
3267
3268
52
    std::set<int> oSetResolutions;
3269
52
    std::map<int, std::set<CPLString>> oMapResolutionsToBands;
3270
52
    char **papszMD = nullptr;
3271
52
    SENTINEL2GetResolutionSetAndMainMDFromGranule(
3272
52
        pszFilename, "Level-1C_User_Product", nResolutionOfInterest,
3273
52
        oSetResolutions, oMapResolutionsToBands, papszMD, ppsRootMainMTD);
3274
52
    if (poBandSet != nullptr)
3275
0
        *poBandSet = oMapResolutionsToBands[nResolutionOfInterest];
3276
3277
52
    SENTINEL2DatasetContainer *poDS = new SENTINEL2DatasetContainer();
3278
3279
52
    char **papszGranuleMD = SENTINEL2GetL1BCTileMetadata(psRoot);
3280
52
    papszMD = CSLMerge(papszMD, papszGranuleMD);
3281
52
    CSLDestroy(papszGranuleMD);
3282
3283
    // Remove CLOUD_COVERAGE_ASSESSMENT that comes from main metadata, if
3284
    // granule CLOUDY_PIXEL_PERCENTAGE is present.
3285
52
    if (CSLFetchNameValue(papszMD, "CLOUDY_PIXEL_PERCENTAGE") != nullptr &&
3286
2
        CSLFetchNameValue(papszMD, "CLOUD_COVERAGE_ASSESSMENT") != nullptr)
3287
0
    {
3288
0
        papszMD =
3289
0
            CSLSetNameValue(papszMD, "CLOUD_COVERAGE_ASSESSMENT", nullptr);
3290
0
    }
3291
3292
52
    poDS->GDALDataset::SetMetadata(papszMD);
3293
52
    CSLDestroy(papszMD);
3294
3295
52
    if (!osOriginalXML.empty())
3296
52
    {
3297
52
        char *apszXMLMD[2];
3298
52
        apszXMLMD[0] = const_cast<char *>(osOriginalXML.c_str());
3299
52
        apszXMLMD[1] = nullptr;
3300
52
        poDS->GDALDataset::SetMetadata(apszXMLMD, "xml:SENTINEL2");
3301
52
    }
3302
3303
    /* Create subdatsets per resolution (10, 20, 60m) */
3304
52
    int iSubDSNum = 1;
3305
52
    for (std::set<int>::const_iterator oIterRes = oSetResolutions.begin();
3306
52
         oIterRes != oSetResolutions.end(); ++oIterRes)
3307
0
    {
3308
0
        const int nResolution = *oIterRes;
3309
3310
0
        poDS->GDALDataset::SetMetadataItem(
3311
0
            CPLSPrintf("SUBDATASET_%d_NAME", iSubDSNum),
3312
0
            CPLSPrintf("%s:%s:%dm", "SENTINEL2_L1C_TILE", pszFilename,
3313
0
                       nResolution),
3314
0
            GDAL_MDD_SUBDATASETS);
3315
3316
0
        CPLString osBandNames = SENTINEL2GetBandListForResolution(
3317
0
            oMapResolutionsToBands[nResolution]);
3318
3319
0
        CPLString osDesc(CPLSPrintf("Bands %s with %dm resolution",
3320
0
                                    osBandNames.c_str(), nResolution));
3321
0
        poDS->GDALDataset::SetMetadataItem(
3322
0
            CPLSPrintf("SUBDATASET_%d_DESC", iSubDSNum), osDesc.c_str(),
3323
0
            GDAL_MDD_SUBDATASETS);
3324
3325
0
        iSubDSNum++;
3326
0
    }
3327
3328
    /* Expose PREVIEW subdataset */
3329
52
    poDS->GDALDataset::SetMetadataItem(
3330
52
        CPLSPrintf("SUBDATASET_%d_NAME", iSubDSNum),
3331
52
        CPLSPrintf("%s:%s:PREVIEW", "SENTINEL2_L1C_TILE", pszFilename),
3332
52
        GDAL_MDD_SUBDATASETS);
3333
3334
52
    CPLString osDesc("RGB preview");
3335
52
    poDS->GDALDataset::SetMetadataItem(
3336
52
        CPLSPrintf("SUBDATASET_%d_DESC", iSubDSNum), osDesc.c_str(),
3337
52
        GDAL_MDD_SUBDATASETS);
3338
3339
52
    return poDS;
3340
104
}
3341
3342
/************************************************************************/
3343
/*                         SENTINEL2GetOption()                         */
3344
/************************************************************************/
3345
3346
static const char *SENTINEL2GetOption(GDALOpenInfo *poOpenInfo,
3347
                                      const char *pszName,
3348
                                      const char *pszDefaultVal)
3349
0
{
3350
0
#ifdef GDAL_DMD_OPENOPTIONLIST
3351
0
    const char *pszVal =
3352
0
        CSLFetchNameValue(poOpenInfo->papszOpenOptions, pszName);
3353
0
    if (pszVal != nullptr)
3354
0
        return pszVal;
3355
#else
3356
    (void)poOpenInfo;
3357
#endif
3358
0
    return CPLGetConfigOption(CPLSPrintf("SENTINEL2_%s", pszName),
3359
0
                              pszDefaultVal);
3360
0
}
3361
3362
/************************************************************************/
3363
/*                            LaunderUnit()                             */
3364
/************************************************************************/
3365
3366
static CPLString LaunderUnit(const char *pszUnit)
3367
0
{
3368
0
    CPLString osUnit;
3369
0
    for (int i = 0; pszUnit[i] != '\0';)
3370
0
    {
3371
0
        if (strncmp(pszUnit + i,
3372
0
                    "\xC2"
3373
0
                    "\xB2",
3374
0
                    2) == 0) /* square / 2 */
3375
0
        {
3376
0
            i += 2;
3377
0
            osUnit += "2";
3378
0
        }
3379
0
        else if (strncmp(pszUnit + i,
3380
0
                         "\xC2"
3381
0
                         "\xB5",
3382
0
                         2) == 0) /* micro */
3383
0
        {
3384
0
            i += 2;
3385
0
            osUnit += "u";
3386
0
        }
3387
0
        else
3388
0
        {
3389
0
            osUnit += pszUnit[i];
3390
0
            i++;
3391
0
        }
3392
0
    }
3393
0
    return osUnit;
3394
0
}
3395
3396
/************************************************************************/
3397
/*                        SENTINEL2GetTileInfo()                        */
3398
/************************************************************************/
3399
3400
static bool SENTINEL2GetTileInfo(const char *pszFilename, int *pnWidth,
3401
                                 int *pnHeight, int *pnBits)
3402
0
{
3403
0
    static const unsigned char jp2_box_jp[] = {0x6a, 0x50, 0x20,
3404
0
                                               0x20}; /* 'jP  ' */
3405
0
    VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
3406
0
    if (fp == nullptr)
3407
0
        return false;
3408
0
    GByte abyHeader[8];
3409
0
    if (VSIFReadL(abyHeader, 8, 1, fp) != 1)
3410
0
    {
3411
0
        VSIFCloseL(fp);
3412
0
        return false;
3413
0
    }
3414
0
    if (memcmp(abyHeader + 4, jp2_box_jp, 4) == 0)
3415
0
    {
3416
0
        bool bRet = false;
3417
        /* Just parse the ihdr box instead of doing a full dataset opening */
3418
0
        GDALJP2Box oBox(fp);
3419
0
        if (oBox.ReadFirst())
3420
0
        {
3421
0
            while (strlen(oBox.GetType()) > 0)
3422
0
            {
3423
0
                if (EQUAL(oBox.GetType(), "jp2h"))
3424
0
                {
3425
0
                    GDALJP2Box oChildBox(fp);
3426
0
                    if (!oChildBox.ReadFirstChild(&oBox))
3427
0
                        break;
3428
3429
0
                    while (strlen(oChildBox.GetType()) > 0)
3430
0
                    {
3431
0
                        if (EQUAL(oChildBox.GetType(), "ihdr"))
3432
0
                        {
3433
0
                            GByte *pabyData = oChildBox.ReadBoxData();
3434
0
                            GIntBig nLength = oChildBox.GetDataLength();
3435
0
                            if (pabyData != nullptr && nLength >= 4 + 4 + 2 + 1)
3436
0
                            {
3437
0
                                bRet = true;
3438
0
                                if (pnHeight)
3439
0
                                {
3440
0
                                    memcpy(pnHeight, pabyData, 4);
3441
0
                                    CPL_MSBPTR32(pnHeight);
3442
0
                                }
3443
0
                                if (pnWidth != nullptr)
3444
0
                                {
3445
                                    // cppcheck-suppress nullPointer
3446
0
                                    memcpy(pnWidth, pabyData + 4, 4);
3447
0
                                    CPL_MSBPTR32(pnWidth);
3448
0
                                }
3449
0
                                if (pnBits)
3450
0
                                {
3451
0
                                    GByte byPBC = pabyData[4 + 4 + 2];
3452
0
                                    if (byPBC != 255)
3453
0
                                    {
3454
0
                                        *pnBits = 1 + (byPBC & 0x7f);
3455
0
                                    }
3456
0
                                    else
3457
0
                                        *pnBits = 0;
3458
0
                                }
3459
0
                            }
3460
0
                            CPLFree(pabyData);
3461
0
                            break;
3462
0
                        }
3463
0
                        if (!oChildBox.ReadNextChild(&oBox))
3464
0
                            break;
3465
0
                    }
3466
0
                    break;
3467
0
                }
3468
3469
0
                if (!oBox.ReadNext())
3470
0
                    break;
3471
0
            }
3472
0
        }
3473
0
        VSIFCloseL(fp);
3474
0
        return bRet;
3475
0
    }
3476
0
    else /* for unit tests, we use TIFF */
3477
0
    {
3478
0
        VSIFCloseL(fp);
3479
0
        auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
3480
0
            pszFilename, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
3481
0
        bool bRet = false;
3482
0
        if (poDS != nullptr)
3483
0
        {
3484
0
            if (poDS->GetRasterCount() != 0)
3485
0
            {
3486
0
                bRet = true;
3487
0
                if (pnWidth)
3488
0
                    *pnWidth = poDS->GetRasterXSize();
3489
0
                if (pnHeight)
3490
0
                    *pnHeight = poDS->GetRasterYSize();
3491
0
                if (pnBits)
3492
0
                {
3493
0
                    const char *pszNBits =
3494
0
                        poDS->GetRasterBand(1)->GetMetadataItem(
3495
0
                            GDALMD_NBITS, GDAL_MDD_IMAGE_STRUCTURE);
3496
0
                    if (pszNBits == nullptr)
3497
0
                    {
3498
0
                        GDALDataType eDT =
3499
0
                            poDS->GetRasterBand(1)->GetRasterDataType();
3500
0
                        pszNBits =
3501
0
                            CPLSPrintf("%d", GDALGetDataTypeSizeBits(eDT));
3502
0
                    }
3503
0
                    *pnBits = atoi(pszNBits);
3504
0
                }
3505
0
            }
3506
0
        }
3507
0
        return bRet;
3508
0
    }
3509
0
}
3510
3511
/************************************************************************/
3512
/*                       OpenL1C_L2ASubdataset()                        */
3513
/************************************************************************/
3514
3515
GDALDataset *SENTINEL2Dataset::OpenL1C_L2ASubdataset(GDALOpenInfo *poOpenInfo,
3516
                                                     SENTINEL2Level eLevel)
3517
0
{
3518
0
    CPLString osFilename;
3519
0
    const char *pszPrefix =
3520
0
        (eLevel == SENTINEL2_L1C) ? "SENTINEL2_L1C" : "SENTINEL2_L2A";
3521
0
    CPLAssert(STARTS_WITH_CI(poOpenInfo->pszFilename, pszPrefix));
3522
0
    osFilename = poOpenInfo->pszFilename + strlen(pszPrefix) + 1;
3523
0
    const char *pszColumn = strrchr(osFilename.c_str(), ':');
3524
0
    if (pszColumn == nullptr || pszColumn == osFilename.c_str())
3525
0
    {
3526
0
        CPLError(CE_Failure, CPLE_AppDefined,
3527
0
                 "Invalid syntax for %s:", pszPrefix);
3528
0
        return nullptr;
3529
0
    }
3530
0
    const auto nPos = static_cast<size_t>(pszColumn - osFilename.c_str());
3531
0
    const std::string osEPSGCode = osFilename.substr(nPos + 1);
3532
0
    osFilename.resize(nPos);
3533
0
    const char *pszPrecision = strrchr(osFilename.c_str(), ':');
3534
0
    if (pszPrecision == nullptr)
3535
0
    {
3536
0
        CPLError(CE_Failure, CPLE_AppDefined,
3537
0
                 "Invalid syntax for %s:", pszPrefix);
3538
0
        return nullptr;
3539
0
    }
3540
3541
0
    if (!STARTS_WITH_CI(osEPSGCode.c_str(), "EPSG_"))
3542
0
    {
3543
0
        CPLError(CE_Failure, CPLE_AppDefined,
3544
0
                 "Invalid syntax for %s:", pszPrefix);
3545
0
        return nullptr;
3546
0
    }
3547
3548
0
    const int nSubDSEPSGCode = atoi(osEPSGCode.c_str() + strlen("EPSG_"));
3549
0
    const bool bIsPreview = STARTS_WITH_CI(pszPrecision + 1, "PREVIEW");
3550
0
    const bool bIsTCI = STARTS_WITH_CI(pszPrecision + 1, "TCI");
3551
0
    const int nSubDSPrecision = (bIsPreview) ? 320
3552
0
                                : (bIsTCI)   ? RES_10M
3553
0
                                             : atoi(pszPrecision + 1);
3554
0
    if (!bIsTCI && !bIsPreview && nSubDSPrecision != RES_10M &&
3555
0
        nSubDSPrecision != RES_20M && nSubDSPrecision != RES_60M)
3556
0
    {
3557
0
        CPLError(CE_Failure, CPLE_NotSupported, "Unsupported precision: %d",
3558
0
                 nSubDSPrecision);
3559
0
        return nullptr;
3560
0
    }
3561
3562
0
    osFilename.resize(pszPrecision - osFilename.c_str());
3563
0
    std::vector<CPLString> aosNonJP2Files;
3564
0
    aosNonJP2Files.push_back(osFilename);
3565
3566
0
    CPLXMLNode *psRoot = CPLParseXMLFile(osFilename);
3567
0
    if (psRoot == nullptr)
3568
0
    {
3569
0
        CPLDebug("SENTINEL2", "Cannot parse XML file '%s'", osFilename.c_str());
3570
0
        return nullptr;
3571
0
    }
3572
3573
0
    char *pszOriginalXML = CPLSerializeXMLTree(psRoot);
3574
0
    CPLString osOriginalXML;
3575
0
    if (pszOriginalXML)
3576
0
        osOriginalXML = pszOriginalXML;
3577
0
    CPLFree(pszOriginalXML);
3578
3579
0
    SENTINEL2_CPLXMLNodeHolder oXMLHolder(psRoot);
3580
0
    CPLStripXMLNamespace(psRoot, nullptr, TRUE);
3581
3582
0
    CPLXMLNode *psProductInfo =
3583
0
        eLevel == SENTINEL2_L1C
3584
0
            ? CPLGetXMLNode(psRoot,
3585
0
                            "=Level-1C_User_Product.General_Info.Product_Info")
3586
0
            : CPLGetXMLNode(psRoot,
3587
0
                            "=Level-2A_User_Product.General_Info.Product_Info");
3588
0
    if (psProductInfo == nullptr && eLevel == SENTINEL2_L2A)
3589
0
    {
3590
0
        psProductInfo = CPLGetXMLNode(
3591
0
            psRoot, "=Level-2A_User_Product.General_Info.L2A_Product_Info");
3592
0
    }
3593
0
    if (psProductInfo == nullptr)
3594
0
    {
3595
0
        CPLDebug("SENTINEL2", "Product Info not found");
3596
0
        return nullptr;
3597
0
    }
3598
3599
0
    const bool bIsSafeCompact =
3600
0
        EQUAL(CPLGetXMLValue(psProductInfo, "Query_Options.PRODUCT_FORMAT", ""),
3601
0
              "SAFE_COMPACT");
3602
3603
0
    const char *pszProductURI =
3604
0
        CPLGetXMLValue(psProductInfo, "PRODUCT_URI", nullptr);
3605
0
    SENTINEL2ProductType pType = MSI2A;
3606
0
    if (pszProductURI == nullptr)
3607
0
    {
3608
0
        pszProductURI =
3609
0
            CPLGetXMLValue(psProductInfo, "PRODUCT_URI_2A", nullptr);
3610
0
        pType = MSI2Ap;
3611
0
    }
3612
0
    if (pszProductURI == nullptr)
3613
0
        pszProductURI = "";
3614
3615
0
    std::vector<CPLString> aosGranuleList;
3616
0
    std::map<int, std::set<CPLString>> oMapResolutionsToBands;
3617
0
    std::vector<L1CSafeCompatGranuleDescription> aoL1CSafeCompactGranuleList;
3618
0
    if (bIsSafeCompact)
3619
0
    {
3620
0
        for (const auto &sBandDesc : asBandDesc)
3621
0
        {
3622
            // L2 does not contain B10
3623
0
            if (eLevel == SENTINEL2_L2A &&
3624
0
                strcmp(sBandDesc.pszBandName, "B10") == 0)
3625
0
                continue;
3626
0
            CPLString osName = sBandDesc.pszBandName + 1; /* skip B character */
3627
0
            if (atoi(osName) < 10)
3628
0
                osName = "0" + osName;
3629
0
            oMapResolutionsToBands[sBandDesc.nResolution].insert(
3630
0
                std::move(osName));
3631
0
        }
3632
0
        if (eLevel == SENTINEL2_L2A)
3633
0
        {
3634
0
            for (const auto &sL2ABandDesc : asL2ABandDesc)
3635
0
            {
3636
0
                oMapResolutionsToBands[sL2ABandDesc.nResolution].insert(
3637
0
                    sL2ABandDesc.pszBandName);
3638
0
            }
3639
0
        }
3640
0
        if (eLevel == SENTINEL2_L1C &&
3641
0
            !SENTINEL2GetGranuleList_L1CSafeCompact(
3642
0
                psRoot, osFilename, aoL1CSafeCompactGranuleList))
3643
0
        {
3644
0
            CPLDebug("SENTINEL2", "Failed to get granule list");
3645
0
            return nullptr;
3646
0
        }
3647
0
        if (eLevel == SENTINEL2_L2A &&
3648
0
            !SENTINEL2GetGranuleList_L2ASafeCompact(
3649
0
                psRoot, osFilename, aoL1CSafeCompactGranuleList))
3650
0
        {
3651
0
            CPLDebug("SENTINEL2", "Failed to get granule list");
3652
0
            return nullptr;
3653
0
        }
3654
0
        for (size_t i = 0; i < aoL1CSafeCompactGranuleList.size(); ++i)
3655
0
        {
3656
0
            aosGranuleList.push_back(
3657
0
                aoL1CSafeCompactGranuleList[i].osMTDTLPath);
3658
0
        }
3659
0
    }
3660
0
    else if (!SENTINEL2GetGranuleList(
3661
0
                 psRoot, eLevel, osFilename, aosGranuleList, nullptr,
3662
0
                 (eLevel == SENTINEL2_L1C) ? nullptr : &oMapResolutionsToBands))
3663
0
    {
3664
0
        CPLDebug("SENTINEL2", "Failed to get granule list");
3665
0
        return nullptr;
3666
0
    }
3667
3668
0
    std::vector<CPLString> aosBands;
3669
0
    std::set<CPLString> oSetBands;
3670
0
    if (bIsPreview || bIsTCI)
3671
0
    {
3672
0
        aosBands.push_back("04");
3673
0
        aosBands.push_back("03");
3674
0
        aosBands.push_back("02");
3675
0
    }
3676
0
    else if (eLevel == SENTINEL2_L1C && !bIsSafeCompact)
3677
0
    {
3678
0
        CPLXMLNode *psBandList =
3679
0
            CPLGetXMLNode(psRoot, "=Level-1C_User_Product.General_Info.Product_"
3680
0
                                  "Info.Query_Options.Band_List");
3681
0
        if (psBandList == nullptr)
3682
0
        {
3683
0
            CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s",
3684
0
                     "Query_Options.Band_List");
3685
0
            return nullptr;
3686
0
        }
3687
3688
0
        for (CPLXMLNode *psIter = psBandList->psChild; psIter != nullptr;
3689
0
             psIter = psIter->psNext)
3690
0
        {
3691
0
            if (psIter->eType != CXT_Element ||
3692
0
                !EQUAL(psIter->pszValue, "BAND_NAME"))
3693
0
                continue;
3694
0
            const char *pszBandName = CPLGetXMLValue(psIter, nullptr, "");
3695
0
            const SENTINEL2BandDescription *psBandDesc =
3696
0
                SENTINEL2GetBandDesc(pszBandName);
3697
0
            if (psBandDesc == nullptr)
3698
0
            {
3699
0
                CPLDebug("SENTINEL2", "Unknown band name %s", pszBandName);
3700
0
                continue;
3701
0
            }
3702
0
            if (psBandDesc->nResolution != nSubDSPrecision)
3703
0
                continue;
3704
0
            CPLString osName =
3705
0
                psBandDesc->pszBandName + 1; /* skip B character */
3706
0
            if (atoi(osName) < 10)
3707
0
                osName = "0" + osName;
3708
0
            oSetBands.insert(std::move(osName));
3709
0
        }
3710
0
        if (oSetBands.empty())
3711
0
        {
3712
0
            CPLDebug("SENTINEL2", "Band set is empty");
3713
0
            return nullptr;
3714
0
        }
3715
0
    }
3716
0
    else
3717
0
    {
3718
0
        oSetBands = oMapResolutionsToBands[nSubDSPrecision];
3719
0
    }
3720
3721
0
    if (aosBands.empty())
3722
0
    {
3723
0
        for (std::set<CPLString>::const_iterator oIterBandnames =
3724
0
                 oSetBands.begin();
3725
0
             oIterBandnames != oSetBands.end(); ++oIterBandnames)
3726
0
        {
3727
0
            aosBands.push_back(*oIterBandnames);
3728
0
        }
3729
        /* Put 2=Blue, 3=Green, 4=Band bands in RGB order for conveniency */
3730
0
        if (aosBands.size() >= 3 && aosBands[0] == "02" &&
3731
0
            aosBands[1] == "03" && aosBands[2] == "04")
3732
0
        {
3733
0
            aosBands[0] = "04";
3734
0
            aosBands[2] = "02";
3735
0
        }
3736
0
    }
3737
3738
    /* -------------------------------------------------------------------- */
3739
    /*      Create dataset.                                                 */
3740
    /* -------------------------------------------------------------------- */
3741
3742
0
    char **papszMD = SENTINEL2GetUserProductMetadata(
3743
0
        psRoot, (eLevel == SENTINEL2_L1C) ? "Level-1C_User_Product"
3744
0
                                          : "Level-2A_User_Product");
3745
3746
0
    const int nSaturatedVal =
3747
0
        atoi(CSLFetchNameValueDef(papszMD, "SPECIAL_VALUE_SATURATED", "-1"));
3748
0
    const int nNodataVal =
3749
0
        atoi(CSLFetchNameValueDef(papszMD, "SPECIAL_VALUE_NODATA", "-1"));
3750
3751
0
    const bool bAlpha =
3752
0
        CPLTestBool(SENTINEL2GetOption(poOpenInfo, "ALPHA", "FALSE"));
3753
3754
0
    SENTINEL2Dataset *poDS = CreateL1CL2ADataset(
3755
0
        eLevel, pType, bIsSafeCompact, aosGranuleList,
3756
0
        aoL1CSafeCompactGranuleList, aosNonJP2Files, nSubDSPrecision,
3757
0
        bIsPreview, bIsTCI, nSubDSEPSGCode, bAlpha, aosBands, nSaturatedVal,
3758
0
        nNodataVal, CPLString(pszProductURI));
3759
0
    if (poDS == nullptr)
3760
0
    {
3761
0
        CSLDestroy(papszMD);
3762
0
        return nullptr;
3763
0
    }
3764
3765
0
    if (!osOriginalXML.empty())
3766
0
    {
3767
0
        char *apszXMLMD[2] = {nullptr};
3768
0
        apszXMLMD[0] = const_cast<char *>(osOriginalXML.c_str());
3769
0
        apszXMLMD[1] = nullptr;
3770
0
        poDS->GDALDataset::SetMetadata(apszXMLMD, "xml:SENTINEL2");
3771
0
    }
3772
3773
0
    poDS->GDALDataset::SetMetadata(papszMD);
3774
0
    CSLDestroy(papszMD);
3775
3776
    /* -------------------------------------------------------------------- */
3777
    /*      Add extra band metadata.                                        */
3778
    /* -------------------------------------------------------------------- */
3779
0
    poDS->AddL1CL2ABandMetadata(eLevel, psRoot, aosBands);
3780
3781
    /* -------------------------------------------------------------------- */
3782
    /*      Initialize overview information.                                */
3783
    /* -------------------------------------------------------------------- */
3784
0
    poDS->SetDescription(poOpenInfo->pszFilename);
3785
0
    CPLString osOverviewFile;
3786
0
    if (bIsPreview)
3787
0
        osOverviewFile = CPLSPrintf("%s_PREVIEW_EPSG_%d.tif.ovr",
3788
0
                                    osFilename.c_str(), nSubDSEPSGCode);
3789
0
    else if (bIsTCI)
3790
0
        osOverviewFile = CPLSPrintf("%s_TCI_EPSG_%d.tif.ovr",
3791
0
                                    osFilename.c_str(), nSubDSEPSGCode);
3792
0
    else
3793
0
        osOverviewFile =
3794
0
            CPLSPrintf("%s_%dm_EPSG_%d.tif.ovr", osFilename.c_str(),
3795
0
                       nSubDSPrecision, nSubDSEPSGCode);
3796
0
    poDS->SetMetadataItem("OVERVIEW_FILE", osOverviewFile, "OVERVIEWS");
3797
0
    poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
3798
3799
0
    return poDS;
3800
0
}
3801
3802
/************************************************************************/
3803
/*                       AddL1CL2ABandMetadata()                        */
3804
/************************************************************************/
3805
3806
void SENTINEL2Dataset::AddL1CL2ABandMetadata(
3807
    SENTINEL2Level eLevel, CPLXMLNode *psRoot,
3808
    const std::vector<CPLString> &aosBands)
3809
0
{
3810
0
    CPLXMLNode *psIC =
3811
0
        CPLGetXMLNode(psRoot, (eLevel == SENTINEL2_L1C)
3812
0
                                  ? "=Level-1C_User_Product.General_Info."
3813
0
                                    "Product_Image_Characteristics"
3814
0
                                  : "=Level-2A_User_Product.General_Info."
3815
0
                                    "Product_Image_Characteristics");
3816
0
    if (psIC == nullptr)
3817
0
    {
3818
0
        psIC = CPLGetXMLNode(psRoot, "=Level-2A_User_Product.General_Info.L2A_"
3819
0
                                     "Product_Image_Characteristics");
3820
0
    }
3821
0
    if (psIC != nullptr)
3822
0
    {
3823
0
        CPLXMLNode *psSIL =
3824
0
            CPLGetXMLNode(psIC, "Reflectance_Conversion.Solar_Irradiance_List");
3825
0
        if (psSIL != nullptr)
3826
0
        {
3827
0
            for (CPLXMLNode *psIter = psSIL->psChild; psIter != nullptr;
3828
0
                 psIter = psIter->psNext)
3829
0
            {
3830
0
                if (psIter->eType != CXT_Element ||
3831
0
                    !EQUAL(psIter->pszValue, "SOLAR_IRRADIANCE"))
3832
0
                {
3833
0
                    continue;
3834
0
                }
3835
0
                const char *pszBandId =
3836
0
                    CPLGetXMLValue(psIter, "bandId", nullptr);
3837
0
                const char *pszUnit = CPLGetXMLValue(psIter, "unit", nullptr);
3838
0
                const char *pszValue = CPLGetXMLValue(psIter, nullptr, nullptr);
3839
0
                if (pszBandId != nullptr && pszUnit != nullptr &&
3840
0
                    pszValue != nullptr)
3841
0
                {
3842
0
                    const unsigned int nIdx = atoi(pszBandId);
3843
0
                    if (nIdx < CPL_ARRAYSIZE(asBandDesc))
3844
0
                    {
3845
0
                        for (int i = 0; i < nBands; i++)
3846
0
                        {
3847
0
                            GDALRasterBand *poBand = GetRasterBand(i + 1);
3848
0
                            const char *pszBandName =
3849
0
                                poBand->GetMetadataItem("BANDNAME");
3850
0
                            if (pszBandName != nullptr &&
3851
0
                                EQUAL(asBandDesc[nIdx].pszBandName,
3852
0
                                      pszBandName))
3853
0
                            {
3854
0
                                poBand->GDALRasterBand::SetMetadataItem(
3855
0
                                    "SOLAR_IRRADIANCE", pszValue);
3856
0
                                poBand->GDALRasterBand::SetMetadataItem(
3857
0
                                    "SOLAR_IRRADIANCE_UNIT",
3858
0
                                    LaunderUnit(pszUnit));
3859
0
                                break;
3860
0
                            }
3861
0
                        }
3862
0
                    }
3863
0
                }
3864
0
            }
3865
0
        }
3866
3867
0
        CPLXMLNode *psOL = CPLGetXMLNode(
3868
0
            psIC, (eLevel == SENTINEL2_L1C) ? "Radiometric_Offset_List"
3869
0
                                            : "BOA_ADD_OFFSET_VALUES_LIST");
3870
0
        if (psOL != nullptr)
3871
0
        {
3872
0
            for (CPLXMLNode *psIter = psOL->psChild; psIter != nullptr;
3873
0
                 psIter = psIter->psNext)
3874
0
            {
3875
0
                if (psIter->eType != CXT_Element ||
3876
0
                    !EQUAL(psIter->pszValue, (eLevel == SENTINEL2_L1C)
3877
0
                                                 ? "RADIO_ADD_OFFSET"
3878
0
                                                 : "BOA_ADD_OFFSET"))
3879
0
                {
3880
0
                    continue;
3881
0
                }
3882
0
                const char *pszBandId =
3883
0
                    CPLGetXMLValue(psIter, "band_id", nullptr);
3884
0
                const char *pszValue = CPLGetXMLValue(psIter, nullptr, nullptr);
3885
0
                if (pszBandId != nullptr && pszValue != nullptr)
3886
0
                {
3887
0
                    const unsigned int nIdx = atoi(pszBandId);
3888
0
                    if (nIdx < CPL_ARRAYSIZE(asBandDesc))
3889
0
                    {
3890
0
                        for (int i = 0; i < nBands; i++)
3891
0
                        {
3892
0
                            GDALRasterBand *poBand = GetRasterBand(i + 1);
3893
0
                            const char *pszBandName =
3894
0
                                poBand->GetMetadataItem("BANDNAME");
3895
0
                            if (pszBandName != nullptr &&
3896
0
                                EQUAL(asBandDesc[nIdx].pszBandName,
3897
0
                                      pszBandName))
3898
0
                            {
3899
0
                                poBand->GDALRasterBand::SetMetadataItem(
3900
0
                                    (eLevel == SENTINEL2_L1C)
3901
0
                                        ? "RADIO_ADD_OFFSET"
3902
0
                                        : "BOA_ADD_OFFSET",
3903
0
                                    pszValue);
3904
0
                                break;
3905
0
                            }
3906
0
                        }
3907
0
                    }
3908
0
                }
3909
0
            }
3910
0
        }
3911
0
    }
3912
3913
    /* -------------------------------------------------------------------- */
3914
    /*      Add Scene Classification category values (L2A)                  */
3915
    /* -------------------------------------------------------------------- */
3916
0
    CPLXMLNode *psSCL = CPLGetXMLNode(
3917
0
        psRoot, "=Level-2A_User_Product.General_Info."
3918
0
                "Product_Image_Characteristics.Scene_Classification_List");
3919
0
    if (psSCL == nullptr)
3920
0
    {
3921
0
        psSCL = CPLGetXMLNode(
3922
0
            psRoot,
3923
0
            "=Level-2A_User_Product.General_Info."
3924
0
            "L2A_Product_Image_Characteristics.L2A_Scene_Classification_List");
3925
0
    }
3926
0
    int nSCLBand = 0;
3927
0
    for (int nBand = 1; nBand <= static_cast<int>(aosBands.size()); nBand++)
3928
0
    {
3929
0
        if (EQUAL(aosBands[nBand - 1], "SCL"))
3930
0
        {
3931
0
            nSCLBand = nBand;
3932
0
            break;
3933
0
        }
3934
0
    }
3935
0
    if (psSCL != nullptr && nSCLBand > 0)
3936
0
    {
3937
0
        std::vector<std::string> aosCategories(100);
3938
0
        size_t nMaxIdx = 0;
3939
0
        bool bFirst = true;
3940
0
        for (CPLXMLNode *psIter = psSCL->psChild; psIter != nullptr;
3941
0
             psIter = psIter->psNext)
3942
0
        {
3943
0
            if (psIter->eType != CXT_Element ||
3944
0
                (!EQUAL(psIter->pszValue, "L2A_Scene_Classification_ID") &&
3945
0
                 !EQUAL(psIter->pszValue, "Scene_Classification_ID")))
3946
0
            {
3947
0
                continue;
3948
0
            }
3949
0
            const char *pszText =
3950
0
                CPLGetXMLValue(psIter, "SCENE_CLASSIFICATION_TEXT", nullptr);
3951
0
            if (pszText == nullptr)
3952
0
                pszText = CPLGetXMLValue(
3953
0
                    psIter, "L2A_SCENE_CLASSIFICATION_TEXT", nullptr);
3954
0
            const char *pszIdx =
3955
0
                CPLGetXMLValue(psIter, "SCENE_CLASSIFICATION_INDEX", nullptr);
3956
0
            if (pszIdx == nullptr)
3957
0
                pszIdx = CPLGetXMLValue(
3958
0
                    psIter, "L2A_SCENE_CLASSIFICATION_INDEX", nullptr);
3959
0
            if (pszText && pszIdx)
3960
0
            {
3961
0
                const size_t nIdx = atoi(pszIdx);
3962
0
                if (nIdx < aosCategories.size())
3963
0
                {
3964
0
                    if (bFirst)
3965
0
                        nMaxIdx = nIdx;
3966
0
                    else
3967
0
                        nMaxIdx = std::max(nMaxIdx, nIdx);
3968
0
                    bFirst = false;
3969
0
                    if (STARTS_WITH_CI(pszText, "SC_"))
3970
0
                        aosCategories[nIdx] = pszText + 3;
3971
0
                    else
3972
0
                        aosCategories[nIdx] = pszText;
3973
0
                }
3974
0
            }
3975
0
        }
3976
0
        if (!bFirst)
3977
0
        {
3978
0
            aosCategories.resize(nMaxIdx + 1);
3979
0
            GetRasterBand(nSCLBand)->SetCategoryNames(
3980
0
                CPLStringList(aosCategories).List());
3981
0
        }
3982
0
    }
3983
0
}
3984
3985
/************************************************************************/
3986
/*                        CreateL1CL2ADataset()                         */
3987
/************************************************************************/
3988
3989
SENTINEL2Dataset *SENTINEL2Dataset::CreateL1CL2ADataset(
3990
    SENTINEL2Level eLevel, SENTINEL2ProductType pType, bool bIsSafeCompact,
3991
    const std::vector<CPLString> &aosGranuleList,
3992
    const std::vector<L1CSafeCompatGranuleDescription>
3993
        &aoL1CSafeCompactGranuleList,
3994
    std::vector<CPLString> &aosNonJP2Files, int nSubDSPrecision,
3995
    bool bIsPreview, bool bIsTCI,
3996
    int nSubDSEPSGCode, /* or -1 if not known at this point */
3997
    bool bAlpha, const std::vector<CPLString> &aosBands, int nSaturatedVal,
3998
    int nNodataVal, const CPLString &osProductURI)
3999
0
{
4000
4001
    /* Iterate over granule metadata to know the layer extent */
4002
    /* and the location of each granule */
4003
0
    double dfMinX = 1.0e20;
4004
0
    double dfMinY = 1.0e20;
4005
0
    double dfMaxX = -1.0e20;
4006
0
    double dfMaxY = -1.0e20;
4007
0
    std::vector<SENTINEL2GranuleInfo> aosGranuleInfoList;
4008
0
    const int nDesiredResolution = (bIsPreview || bIsTCI) ? 0 : nSubDSPrecision;
4009
4010
0
    if (bIsSafeCompact)
4011
0
    {
4012
0
        CPLAssert(aosGranuleList.size() == aoL1CSafeCompactGranuleList.size());
4013
0
    }
4014
4015
0
    for (size_t i = 0; i < aosGranuleList.size(); i++)
4016
0
    {
4017
0
        int nEPSGCode = 0;
4018
0
        double dfULX = 0.0;
4019
0
        double dfULY = 0.0;
4020
0
        int nResolution = 0;
4021
0
        int nWidth = 0;
4022
0
        int nHeight = 0;
4023
0
        if (SENTINEL2GetGranuleInfo(eLevel, aosGranuleList[i],
4024
0
                                    nDesiredResolution, &nEPSGCode, &dfULX,
4025
0
                                    &dfULY, &nResolution, &nWidth, &nHeight) &&
4026
0
            (nSubDSEPSGCode == nEPSGCode || nSubDSEPSGCode < 0) &&
4027
0
            nResolution != 0)
4028
0
        {
4029
0
            nSubDSEPSGCode = nEPSGCode;
4030
0
            aosNonJP2Files.push_back(aosGranuleList[i]);
4031
4032
0
            if (dfULX < dfMinX)
4033
0
                dfMinX = dfULX;
4034
0
            if (dfULY > dfMaxY)
4035
0
                dfMaxY = dfULY;
4036
0
            const double dfLRX = dfULX + nResolution * nWidth;
4037
0
            const double dfLRY = dfULY - nResolution * nHeight;
4038
0
            if (dfLRX > dfMaxX)
4039
0
                dfMaxX = dfLRX;
4040
0
            if (dfLRY < dfMinY)
4041
0
                dfMinY = dfLRY;
4042
4043
0
            SENTINEL2GranuleInfo oGranuleInfo;
4044
0
            oGranuleInfo.osPath = CPLGetPathSafe(aosGranuleList[i]);
4045
0
            if (bIsSafeCompact)
4046
0
            {
4047
0
                oGranuleInfo.osBandPrefixPath =
4048
0
                    aoL1CSafeCompactGranuleList[i].osBandPrefixPath;
4049
0
            }
4050
0
            oGranuleInfo.dfMinX = dfULX;
4051
0
            oGranuleInfo.dfMinY = dfLRY;
4052
0
            oGranuleInfo.dfMaxX = dfLRX;
4053
0
            oGranuleInfo.dfMaxY = dfULY;
4054
0
            oGranuleInfo.nWidth = nWidth / (nSubDSPrecision / nResolution);
4055
0
            oGranuleInfo.nHeight = nHeight / (nSubDSPrecision / nResolution);
4056
0
            aosGranuleInfoList.push_back(std::move(oGranuleInfo));
4057
0
        }
4058
0
    }
4059
0
    if (dfMinX > dfMaxX)
4060
0
    {
4061
0
        CPLError(CE_Failure, CPLE_NotSupported,
4062
0
                 "No granule found for EPSG code %d", nSubDSEPSGCode);
4063
0
        return nullptr;
4064
0
    }
4065
4066
0
    const int nRasterXSize =
4067
0
        static_cast<int>((dfMaxX - dfMinX) / nSubDSPrecision + 0.5);
4068
0
    const int nRasterYSize =
4069
0
        static_cast<int>((dfMaxY - dfMinY) / nSubDSPrecision + 0.5);
4070
0
    SENTINEL2Dataset *poDS = new SENTINEL2Dataset(nRasterXSize, nRasterYSize);
4071
4072
0
#if defined(__GNUC__)
4073
0
#pragma GCC diagnostic push
4074
0
#pragma GCC diagnostic ignored "-Wnull-dereference"
4075
0
#endif
4076
0
    poDS->aosNonJP2Files = aosNonJP2Files;
4077
0
#if defined(__GNUC__)
4078
0
#pragma GCC diagnostic pop
4079
0
#endif
4080
4081
0
    OGRSpatialReference oSRS;
4082
0
    char *pszProjection = nullptr;
4083
0
    if (oSRS.importFromEPSG(nSubDSEPSGCode) == OGRERR_NONE &&
4084
0
        oSRS.exportToWkt(&pszProjection) == OGRERR_NONE)
4085
0
    {
4086
0
        poDS->SetProjection(pszProjection);
4087
0
        CPLFree(pszProjection);
4088
0
    }
4089
0
    else
4090
0
    {
4091
0
        CPLDebug("SENTINEL2", "Invalid EPSG code %d", nSubDSEPSGCode);
4092
0
    }
4093
4094
0
    poDS->SetGeoTransform(GDALGeoTransform(dfMinX, nSubDSPrecision, 0, dfMaxY,
4095
0
                                           0, -nSubDSPrecision));
4096
0
    poDS->GDALDataset::SetMetadataItem(GDALMD_COMPRESSION, "JPEG2000",
4097
0
                                       GDAL_MDD_IMAGE_STRUCTURE);
4098
0
    if (bIsPreview || bIsTCI)
4099
0
        poDS->GDALDataset::SetMetadataItem(GDALMD_INTERLEAVE, "PIXEL",
4100
0
                                           GDAL_MDD_IMAGE_STRUCTURE);
4101
4102
0
    int nBits = (bIsPreview || bIsTCI) ? 8 : 0 /* 0 = unknown yet*/;
4103
0
    int nValMax = (bIsPreview || bIsTCI) ? 255 : 0 /* 0 = unknown yet*/;
4104
0
    const int nBands =
4105
0
        (bIsPreview || bIsTCI)
4106
0
            ? 3
4107
0
            : ((bAlpha) ? 1 : 0) + static_cast<int>(aosBands.size());
4108
0
    const int nAlphaBand = (bIsPreview || bIsTCI || !bAlpha) ? 0 : nBands;
4109
0
    const GDALDataType eDT = (bIsPreview || bIsTCI) ? GDT_UInt8 : GDT_UInt16;
4110
4111
0
    for (int nBand = 1; nBand <= nBands; nBand++)
4112
0
    {
4113
0
        VRTSourcedRasterBand *poBand = nullptr;
4114
4115
0
        if (nBand != nAlphaBand)
4116
0
        {
4117
0
            poBand = new VRTSourcedRasterBand(
4118
0
                poDS, nBand, eDT, poDS->nRasterXSize, poDS->nRasterYSize);
4119
0
        }
4120
0
        else
4121
0
        {
4122
0
            poBand = new SENTINEL2AlphaBand(
4123
0
                poDS, nBand, eDT, poDS->nRasterXSize, poDS->nRasterYSize,
4124
0
                nSaturatedVal, nNodataVal);
4125
0
        }
4126
4127
0
        poDS->SetBand(nBand, poBand);
4128
0
        if (nBand == nAlphaBand)
4129
0
            poBand->SetColorInterpretation(GCI_AlphaBand);
4130
4131
0
        CPLString osBandName;
4132
0
        if (nBand != nAlphaBand)
4133
0
        {
4134
0
            osBandName = aosBands[nBand - 1];
4135
0
            SENTINEL2SetBandMetadata(poBand, osBandName);
4136
0
        }
4137
0
        else
4138
0
            osBandName = aosBands[0];
4139
4140
0
        for (size_t iSrc = 0; iSrc < aosGranuleInfoList.size(); iSrc++)
4141
0
        {
4142
0
            const SENTINEL2GranuleInfo &oGranuleInfo = aosGranuleInfoList[iSrc];
4143
0
            CPLString osTile;
4144
4145
0
            if (bIsSafeCompact && eLevel != SENTINEL2_L2A)
4146
0
            {
4147
0
                if (bIsTCI)
4148
0
                {
4149
0
                    osTile = oGranuleInfo.osBandPrefixPath + "TCI.jp2";
4150
0
                }
4151
0
                else
4152
0
                {
4153
0
                    osTile = oGranuleInfo.osBandPrefixPath + "B";
4154
0
                    if (osBandName.size() == 1)
4155
0
                        osTile += "0" + osBandName;
4156
0
                    else if (osBandName.size() == 3)
4157
0
                        osTile += osBandName.substr(1);
4158
0
                    else
4159
0
                        osTile += osBandName;
4160
0
                    osTile += ".jp2";
4161
0
                }
4162
0
            }
4163
0
            else
4164
0
            {
4165
0
                osTile = SENTINEL2GetTilename(
4166
0
                    oGranuleInfo.osPath, CPLGetFilename(oGranuleInfo.osPath),
4167
0
                    osBandName, osProductURI, bIsPreview,
4168
0
                    (eLevel == SENTINEL2_L1C) ? 0 : nSubDSPrecision);
4169
0
                if (bIsSafeCompact && eLevel == SENTINEL2_L2A &&
4170
0
                    pType == MSI2Ap && osTile.size() >= 34 &&
4171
0
                    osTile.substr(osTile.size() - 18, 3) != "MSK")
4172
0
                {
4173
0
                    osTile.insert(osTile.size() - 34, "L2A_");
4174
0
                }
4175
0
                if (bIsTCI && osTile.size() >= 14)
4176
0
                {
4177
0
                    osTile.replace(osTile.size() - 11, 3, "TCI");
4178
0
                }
4179
0
            }
4180
4181
0
            bool bTileFound = false;
4182
0
            if (nValMax == 0)
4183
0
            {
4184
                /* It is supposed to be 12 bits, but some products have 15 bits
4185
                 */
4186
0
                if (SENTINEL2GetTileInfo(osTile, nullptr, nullptr, &nBits))
4187
0
                {
4188
0
                    bTileFound = true;
4189
0
                    if (nBits <= 16)
4190
0
                        nValMax = (1 << nBits) - 1;
4191
0
                    else
4192
0
                    {
4193
0
                        CPLDebug("SENTINEL2", "Unexpected bit depth %d", nBits);
4194
0
                        nValMax = 65535;
4195
0
                    }
4196
0
                }
4197
0
            }
4198
0
            else
4199
0
            {
4200
0
                VSIStatBufL sStat;
4201
0
                if (VSIStatExL(osTile, &sStat, VSI_STAT_EXISTS_FLAG) == 0)
4202
0
                    bTileFound = true;
4203
0
            }
4204
0
            if (!bTileFound)
4205
0
            {
4206
0
                CPLError(CE_Warning, CPLE_AppDefined,
4207
0
                         "Tile %s not found on filesystem. Skipping it",
4208
0
                         osTile.c_str());
4209
0
                continue;
4210
0
            }
4211
4212
0
            const int nDstXOff = static_cast<int>(
4213
0
                (oGranuleInfo.dfMinX - dfMinX) / nSubDSPrecision + 0.5);
4214
0
            const int nDstYOff = static_cast<int>(
4215
0
                (dfMaxY - oGranuleInfo.dfMaxY) / nSubDSPrecision + 0.5);
4216
4217
0
            if (nBand != nAlphaBand)
4218
0
            {
4219
0
                poBand->AddSimpleSource(
4220
0
                    osTile, (bIsPreview || bIsTCI) ? nBand : 1, 0, 0,
4221
0
                    oGranuleInfo.nWidth, oGranuleInfo.nHeight, nDstXOff,
4222
0
                    nDstYOff, oGranuleInfo.nWidth, oGranuleInfo.nHeight);
4223
0
            }
4224
0
            else
4225
0
            {
4226
0
                poBand->AddComplexSource(
4227
0
                    osTile, 1, 0, 0, oGranuleInfo.nWidth, oGranuleInfo.nHeight,
4228
0
                    nDstXOff, nDstYOff, oGranuleInfo.nWidth,
4229
0
                    oGranuleInfo.nHeight, nValMax /* offset */, 0 /* scale */);
4230
0
            }
4231
0
        }
4232
4233
0
        if ((nBits % 8) != 0)
4234
0
        {
4235
0
            poBand->SetMetadataItem(GDALMD_NBITS, CPLSPrintf("%d", nBits),
4236
0
                                    GDAL_MDD_IMAGE_STRUCTURE);
4237
0
        }
4238
0
    }
4239
4240
0
    return poDS;
4241
0
}
4242
4243
/************************************************************************/
4244
/*                       OpenL1CTileSubdataset()                        */
4245
/************************************************************************/
4246
4247
GDALDataset *SENTINEL2Dataset::OpenL1CTileSubdataset(GDALOpenInfo *poOpenInfo)
4248
0
{
4249
0
    CPLString osFilename;
4250
0
    CPLAssert(STARTS_WITH_CI(poOpenInfo->pszFilename, "SENTINEL2_L1C_TILE:"));
4251
0
    osFilename = poOpenInfo->pszFilename + strlen("SENTINEL2_L1C_TILE:");
4252
0
    const char *pszPrecision = strrchr(osFilename.c_str(), ':');
4253
0
    if (pszPrecision == nullptr || pszPrecision == osFilename.c_str())
4254
0
    {
4255
0
        CPLError(CE_Failure, CPLE_AppDefined,
4256
0
                 "Invalid syntax for SENTINEL2_L1C_TILE:");
4257
0
        return nullptr;
4258
0
    }
4259
0
    const bool bIsPreview = STARTS_WITH_CI(pszPrecision + 1, "PREVIEW");
4260
0
    const int nSubDSPrecision = (bIsPreview) ? 320 : atoi(pszPrecision + 1);
4261
0
    if (!bIsPreview && nSubDSPrecision != RES_10M &&
4262
0
        nSubDSPrecision != RES_20M && nSubDSPrecision != RES_60M)
4263
0
    {
4264
0
        CPLError(CE_Failure, CPLE_NotSupported, "Unsupported precision: %d",
4265
0
                 nSubDSPrecision);
4266
0
        return nullptr;
4267
0
    }
4268
0
    osFilename.resize(pszPrecision - osFilename.c_str());
4269
4270
0
    std::set<CPLString> oSetBands;
4271
0
    CPLXMLNode *psRootMainMTD = nullptr;
4272
0
    GDALDataset *poTmpDS =
4273
0
        OpenL1CTile(osFilename, &psRootMainMTD, nSubDSPrecision, &oSetBands);
4274
0
    SENTINEL2_CPLXMLNodeHolder oXMLHolder(psRootMainMTD);
4275
0
    if (poTmpDS == nullptr)
4276
0
        return nullptr;
4277
4278
0
    std::vector<CPLString> aosBands;
4279
0
    if (bIsPreview)
4280
0
    {
4281
0
        aosBands.push_back("04");
4282
0
        aosBands.push_back("03");
4283
0
        aosBands.push_back("02");
4284
0
    }
4285
0
    else
4286
0
    {
4287
0
        for (std::set<CPLString>::const_iterator oIterBandnames =
4288
0
                 oSetBands.begin();
4289
0
             oIterBandnames != oSetBands.end(); ++oIterBandnames)
4290
0
        {
4291
0
            aosBands.push_back(*oIterBandnames);
4292
0
        }
4293
        /* Put 2=Blue, 3=Green, 4=Band bands in RGB order for conveniency */
4294
0
        if (aosBands.size() >= 3 && aosBands[0] == "02" &&
4295
0
            aosBands[1] == "03" && aosBands[2] == "04")
4296
0
        {
4297
0
            aosBands[0] = "04";
4298
0
            aosBands[2] = "02";
4299
0
        }
4300
0
    }
4301
4302
    /* -------------------------------------------------------------------- */
4303
    /*      Create dataset.                                                 */
4304
    /* -------------------------------------------------------------------- */
4305
4306
0
    std::vector<CPLString> aosGranuleList;
4307
0
    aosGranuleList.push_back(osFilename);
4308
4309
0
    const int nSaturatedVal = atoi(CSLFetchNameValueDef(
4310
0
        poTmpDS->GetMetadata(), "SPECIAL_VALUE_SATURATED", "-1"));
4311
0
    const int nNodataVal = atoi(CSLFetchNameValueDef(
4312
0
        poTmpDS->GetMetadata(), "SPECIAL_VALUE_NODATA", "-1"));
4313
4314
0
    const bool bAlpha =
4315
0
        CPLTestBool(SENTINEL2GetOption(poOpenInfo, "ALPHA", "FALSE"));
4316
4317
0
    std::vector<CPLString> aosNonJP2Files;
4318
0
    SENTINEL2Dataset *poDS = CreateL1CL2ADataset(
4319
0
        SENTINEL2_L1C, MSI2A,
4320
0
        false,  // bIsSafeCompact
4321
0
        aosGranuleList, std::vector<L1CSafeCompatGranuleDescription>(),
4322
0
        aosNonJP2Files, nSubDSPrecision, bIsPreview,
4323
0
        false,  // bIsTCI
4324
0
        -1 /*nSubDSEPSGCode*/, bAlpha, aosBands, nSaturatedVal, nNodataVal,
4325
0
        CPLString());
4326
0
    if (poDS == nullptr)
4327
0
    {
4328
0
        delete poTmpDS;
4329
0
        return nullptr;
4330
0
    }
4331
4332
    // Transfer metadata
4333
0
    poDS->GDALDataset::SetMetadata(poTmpDS->GetMetadata());
4334
0
    poDS->GDALDataset::SetMetadata(poTmpDS->GetMetadata("xml:SENTINEL2"),
4335
0
                                   "xml:SENTINEL2");
4336
4337
0
    delete poTmpDS;
4338
4339
    /* -------------------------------------------------------------------- */
4340
    /*      Add extra band metadata.                                        */
4341
    /* -------------------------------------------------------------------- */
4342
0
    if (psRootMainMTD != nullptr)
4343
0
        poDS->AddL1CL2ABandMetadata(SENTINEL2_L1C, psRootMainMTD, aosBands);
4344
4345
    /* -------------------------------------------------------------------- */
4346
    /*      Initialize overview information.                                */
4347
    /* -------------------------------------------------------------------- */
4348
0
    poDS->SetDescription(poOpenInfo->pszFilename);
4349
0
    CPLString osOverviewFile;
4350
0
    if (bIsPreview)
4351
0
        osOverviewFile = CPLSPrintf("%s_PREVIEW.tif.ovr", osFilename.c_str());
4352
0
    else
4353
0
        osOverviewFile =
4354
0
            CPLSPrintf("%s_%dm.tif.ovr", osFilename.c_str(), nSubDSPrecision);
4355
0
    poDS->SetMetadataItem("OVERVIEW_FILE", osOverviewFile, "OVERVIEWS");
4356
0
    poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
4357
4358
0
    return poDS;
4359
0
}
4360
4361
/************************************************************************/
4362
/*                       GDALRegister_SENTINEL2()                       */
4363
/************************************************************************/
4364
4365
void GDALRegister_SENTINEL2()
4366
22
{
4367
22
    if (GDALGetDriverByName("SENTINEL2") != nullptr)
4368
0
        return;
4369
4370
22
    GDALDriver *poDriver = new GDALDriver();
4371
4372
22
    poDriver->SetDescription("SENTINEL2");
4373
22
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
4374
22
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
4375
22
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Sentinel 2");
4376
22
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
4377
22
                              "drivers/raster/sentinel2.html");
4378
22
    poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
4379
4380
22
    poDriver->SetMetadataItem(
4381
22
        GDAL_DMD_OPENOPTIONLIST,
4382
22
        "<OpenOptionList>"
4383
22
        "  <Option name='ALPHA' type='boolean' description='Whether to expose "
4384
22
        "an alpha band' default='NO'/>"
4385
22
        "   <Option name='L1B_MODE' type='string-select' "
4386
22
        "default='DEFAULT'>\n"
4387
22
        "       <Value>DEFAULT</Value>\n"
4388
22
        "       <Value>GRANULE</Value>\n"
4389
22
        "       <Value>DATASTRIP</Value>\n"
4390
22
        "   </Option>\n"
4391
22
        "</OpenOptionList>");
4392
4393
22
    poDriver->pfnOpen = SENTINEL2Dataset::Open;
4394
22
    poDriver->pfnIdentify = SENTINEL2Dataset::Identify;
4395
4396
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
4397
22
}