LCOV - code coverage report
Current view: top level - frmts/sentinel2 - sentinel2dataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1925 2143 89.8 %
Date: 2026-06-19 21:24:00 Functions: 48 48 100.0 %

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

Generated by: LCOV version 1.14