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

Generated by: LCOV version 1.14