LCOV - code coverage report
Current view: top level - frmts/plmosaic - plmosaicdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 634 667 95.1 %
Date: 2024-11-21 22:18:42 Functions: 33 34 97.1 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  PLMosaic driver
       4             :  * Purpose:  PLMosaic driver
       5             :  * Author:   Even Rouault, <even dot rouault at spatialys dot com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2015-2018, Planet Labs
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_http.h"
      14             : #include "cpl_minixml.h"
      15             : #include "gdal_frmts.h"
      16             : #include "gdal_pam.h"
      17             : #include "gdal_priv.h"
      18             : #include "ogr_spatialref.h"
      19             : #include "ogrsf_frmts.h"
      20             : #include "../vrt/gdal_vrt.h"
      21             : #include "ogrlibjsonutils.h"
      22             : 
      23             : #include <algorithm>
      24             : 
      25             : #define SPHERICAL_RADIUS 6378137.0
      26             : #define GM_ORIGIN -20037508.340
      27             : #define GM_ZOOM_0 ((2 * -(GM_ORIGIN)) / 256)
      28             : 
      29             : /************************************************************************/
      30             : /* ==================================================================== */
      31             : /*                           PLMosaicDataset                            */
      32             : /* ==================================================================== */
      33             : /************************************************************************/
      34             : 
      35             : class PLLinkedDataset;
      36             : 
      37             : class PLLinkedDataset
      38             : {
      39             :   public:
      40             :     CPLString osKey;
      41             :     GDALDataset *poDS;
      42             :     PLLinkedDataset *psPrev;
      43             :     PLLinkedDataset *psNext;
      44             : 
      45          25 :     PLLinkedDataset() : poDS(nullptr), psPrev(nullptr), psNext(nullptr)
      46             :     {
      47          25 :     }
      48             : };
      49             : 
      50             : class PLMosaicRasterBand;
      51             : 
      52             : class PLMosaicDataset final : public GDALPamDataset
      53             : {
      54             :     friend class PLMosaicRasterBand;
      55             : 
      56             :     int bMustCleanPersistent;
      57             :     CPLString osCachePathRoot;
      58             :     int bTrustCache;
      59             :     CPLString osBaseURL;
      60             :     CPLString osAPIKey;
      61             :     CPLString osMosaic;
      62             :     OGRSpatialReference m_oSRS{};
      63             :     int nQuadSize;
      64             :     CPLString osQuadsURL;
      65             :     int bHasGeoTransform;
      66             :     double adfGeoTransform[6];
      67             :     int nZoomLevelMax;
      68             :     int bUseTMSForMain;
      69             :     std::vector<GDALDataset *> apoTMSDS;
      70             :     int nMetaTileXShift = 0;
      71             :     int nMetaTileYShift = 0;
      72             :     bool bQuadDownload = false;
      73             : 
      74             :     int nCacheMaxSize;
      75             :     std::map<CPLString, PLLinkedDataset *> oMapLinkedDatasets;
      76             :     PLLinkedDataset *psHead;
      77             :     PLLinkedDataset *psTail;
      78             :     void FlushDatasetsCache();
      79             :     CPLString GetMosaicCachePath();
      80             :     void CreateMosaicCachePathIfNecessary();
      81             : 
      82             :     int nLastMetaTileX;
      83             :     int nLastMetaTileY;
      84             :     json_object *poLastItemsInformation = nullptr;
      85             :     CPLString osLastRetGetLocationInfo;
      86             :     const char *GetLocationInfo(int nPixel, int nLine);
      87             : 
      88             :     char **GetBaseHTTPOptions();
      89             :     CPLHTTPResult *Download(const char *pszURL, int bQuiet404Error = FALSE);
      90             :     json_object *RunRequest(const char *pszURL, int bQuiet404Error = FALSE);
      91             :     int OpenMosaic();
      92             :     std::vector<CPLString> ListSubdatasets();
      93             : 
      94             :     static CPLString formatTileName(int tile_x, int tile_y);
      95             :     void InsertNewDataset(const CPLString &osKey, GDALDataset *poDS);
      96             :     GDALDataset *OpenAndInsertNewDataset(const CPLString &osTmpFilename,
      97             :                                          const CPLString &osTilename);
      98             : 
      99             :   public:
     100             :     PLMosaicDataset();
     101             :     virtual ~PLMosaicDataset();
     102             : 
     103             :     static int Identify(GDALOpenInfo *poOpenInfo);
     104             :     static GDALDataset *Open(GDALOpenInfo *);
     105             : 
     106             :     virtual CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
     107             :                              int nXSize, int nYSize, void *pData, int nBufXSize,
     108             :                              int nBufYSize, GDALDataType eBufType,
     109             :                              int nBandCount, BANDMAP_TYPE panBandMap,
     110             :                              GSpacing nPixelSpace, GSpacing nLineSpace,
     111             :                              GSpacing nBandSpace,
     112             :                              GDALRasterIOExtraArg *psExtraArg) override;
     113             : 
     114             :     virtual CPLErr FlushCache(bool bAtClosing) override;
     115             : 
     116             :     const OGRSpatialReference *GetSpatialRef() const override;
     117             :     virtual CPLErr GetGeoTransform(double *padfGeoTransform) override;
     118             : 
     119             :     GDALDataset *GetMetaTile(int tile_x, int tile_y);
     120             : };
     121             : 
     122             : /************************************************************************/
     123             : /* ==================================================================== */
     124             : /*                         PLMosaicRasterBand                           */
     125             : /* ==================================================================== */
     126             : /************************************************************************/
     127             : 
     128             : class PLMosaicRasterBand final : public GDALRasterBand
     129             : {
     130             :     friend class PLMosaicDataset;
     131             : 
     132             :   public:
     133             :     PLMosaicRasterBand(PLMosaicDataset *poDS, int nBand,
     134             :                        GDALDataType eDataType);
     135             : 
     136             :     virtual CPLErr IReadBlock(int, int, void *) override;
     137             :     virtual CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
     138             :                              int nXSize, int nYSize, void *pData, int nBufXSize,
     139             :                              int nBufYSize, GDALDataType eBufType,
     140             :                              GSpacing nPixelSpace, GSpacing nLineSpace,
     141             :                              GDALRasterIOExtraArg *psExtraArg) override;
     142             : 
     143             :     virtual const char *GetMetadataItem(const char *pszName,
     144             :                                         const char *pszDomain = "") override;
     145             : 
     146             :     virtual GDALColorInterp GetColorInterpretation() override;
     147             : 
     148             :     virtual int GetOverviewCount() override;
     149             :     virtual GDALRasterBand *GetOverview(int iOvrLevel) override;
     150             : };
     151             : 
     152             : /************************************************************************/
     153             : /*                        PLMosaicRasterBand()                          */
     154             : /************************************************************************/
     155             : 
     156          48 : PLMosaicRasterBand::PLMosaicRasterBand(PLMosaicDataset *poDSIn, int nBandIn,
     157          48 :                                        GDALDataType eDataTypeIn)
     158             : 
     159             : {
     160          48 :     eDataType = eDataTypeIn;
     161          48 :     nBlockXSize = 256;
     162          48 :     nBlockYSize = 256;
     163             : 
     164          48 :     poDS = poDSIn;
     165          48 :     nBand = nBandIn;
     166             : 
     167          48 :     if (eDataType == GDT_UInt16)
     168             :     {
     169           4 :         if (nBand <= 3)
     170           3 :             SetMetadataItem("NBITS", "12", "IMAGE_STRUCTURE");
     171             :     }
     172          48 : }
     173             : 
     174             : /************************************************************************/
     175             : /*                             IReadBlock()                             */
     176             : /************************************************************************/
     177             : 
     178          63 : CPLErr PLMosaicRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
     179             :                                       void *pImage)
     180             : {
     181          63 :     PLMosaicDataset *poMOSDS = reinterpret_cast<PLMosaicDataset *>(poDS);
     182             : 
     183             : #ifdef DEBUG_VERBOSE
     184             :     CPLDebug("PLMOSAIC", "IReadBlock(band=%d, x=%d, y=%d)", nBand, nBlockYOff,
     185             :              nBlockYOff);
     186             : #endif
     187             : 
     188          63 :     if (poMOSDS->bUseTMSForMain && !poMOSDS->apoTMSDS.empty())
     189           1 :         return poMOSDS->apoTMSDS[0]->GetRasterBand(nBand)->ReadBlock(
     190           1 :             nBlockXOff, nBlockYOff, pImage);
     191             : 
     192          62 :     const int bottom_yblock =
     193          62 :         (nRasterYSize - nBlockYOff * nBlockYSize) / nBlockYSize - 1;
     194             : 
     195          62 :     const int meta_tile_x = poMOSDS->nMetaTileXShift +
     196          62 :                             (nBlockXOff * nBlockXSize) / poMOSDS->nQuadSize;
     197          62 :     const int meta_tile_y = poMOSDS->nMetaTileYShift +
     198          62 :                             (bottom_yblock * nBlockYSize) / poMOSDS->nQuadSize;
     199          62 :     const int sub_tile_x = nBlockXOff % (poMOSDS->nQuadSize / nBlockXSize);
     200          62 :     const int sub_tile_y = nBlockYOff % (poMOSDS->nQuadSize / nBlockYSize);
     201             : 
     202          62 :     GDALDataset *poMetaTileDS = poMOSDS->GetMetaTile(meta_tile_x, meta_tile_y);
     203          62 :     if (poMetaTileDS == nullptr)
     204             :     {
     205          38 :         memset(pImage, 0,
     206          38 :                static_cast<size_t>(nBlockXSize) * nBlockYSize *
     207          38 :                    GDALGetDataTypeSizeBytes(eDataType));
     208          38 :         return CE_None;
     209             :     }
     210             : 
     211          24 :     return poMetaTileDS->GetRasterBand(nBand)->RasterIO(
     212          24 :         GF_Read, sub_tile_x * nBlockXSize, sub_tile_y * nBlockYSize,
     213             :         nBlockXSize, nBlockYSize, pImage, nBlockXSize, nBlockYSize, eDataType,
     214          24 :         0, 0, nullptr);
     215             : }
     216             : 
     217             : /************************************************************************/
     218             : /*                             IRasterIO()                              */
     219             : /************************************************************************/
     220             : 
     221          63 : CPLErr PLMosaicRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
     222             :                                      int nXSize, int nYSize, void *pData,
     223             :                                      int nBufXSize, int nBufYSize,
     224             :                                      GDALDataType eBufType,
     225             :                                      GSpacing nPixelSpace, GSpacing nLineSpace,
     226             :                                      GDALRasterIOExtraArg *psExtraArg)
     227             : {
     228          63 :     PLMosaicDataset *poMOSDS = reinterpret_cast<PLMosaicDataset *>(poDS);
     229          63 :     if (poMOSDS->bUseTMSForMain && !poMOSDS->apoTMSDS.empty())
     230           1 :         return poMOSDS->apoTMSDS[0]->GetRasterBand(nBand)->RasterIO(
     231             :             eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
     232           1 :             eBufType, nPixelSpace, nLineSpace, psExtraArg);
     233             : 
     234          62 :     return GDALRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
     235             :                                      pData, nBufXSize, nBufYSize, eBufType,
     236          62 :                                      nPixelSpace, nLineSpace, psExtraArg);
     237             : }
     238             : 
     239             : /************************************************************************/
     240             : /*                         GetMetadataItem()                            */
     241             : /************************************************************************/
     242             : 
     243           4 : const char *PLMosaicRasterBand::GetMetadataItem(const char *pszName,
     244             :                                                 const char *pszDomain)
     245             : {
     246           4 :     PLMosaicDataset *poMOSDS = reinterpret_cast<PLMosaicDataset *>(poDS);
     247             :     int nPixel, nLine;
     248           4 :     if (poMOSDS->bQuadDownload && pszName != nullptr && pszDomain != nullptr &&
     249          12 :         EQUAL(pszDomain, "LocationInfo") &&
     250           4 :         sscanf(pszName, "Pixel_%d_%d", &nPixel, &nLine) == 2)
     251             :     {
     252           4 :         return poMOSDS->GetLocationInfo(nPixel, nLine);
     253             :     }
     254             : 
     255           0 :     return GDALRasterBand::GetMetadataItem(pszName, pszDomain);
     256             : }
     257             : 
     258             : /************************************************************************/
     259             : /*                         GetOverviewCount()                           */
     260             : /************************************************************************/
     261             : 
     262           3 : int PLMosaicRasterBand::GetOverviewCount()
     263             : {
     264           3 :     PLMosaicDataset *poGDS = reinterpret_cast<PLMosaicDataset *>(poDS);
     265           3 :     return std::max(0, static_cast<int>(poGDS->apoTMSDS.size()) - 1);
     266             : }
     267             : 
     268             : /************************************************************************/
     269             : /*                            GetOverview()                             */
     270             : /************************************************************************/
     271             : 
     272           4 : GDALRasterBand *PLMosaicRasterBand::GetOverview(int iOvrLevel)
     273             : {
     274           4 :     PLMosaicDataset *poGDS = reinterpret_cast<PLMosaicDataset *>(poDS);
     275           7 :     if (iOvrLevel < 0 ||
     276           3 :         iOvrLevel >= static_cast<int>(poGDS->apoTMSDS.size()) - 1)
     277           3 :         return nullptr;
     278             : 
     279           1 :     poGDS->CreateMosaicCachePathIfNecessary();
     280             : 
     281           1 :     return poGDS->apoTMSDS[iOvrLevel + 1]->GetRasterBand(nBand);
     282             : }
     283             : 
     284             : /************************************************************************/
     285             : /*                       GetColorInterpretation()                       */
     286             : /************************************************************************/
     287             : 
     288           0 : GDALColorInterp PLMosaicRasterBand::GetColorInterpretation()
     289             : {
     290           0 :     switch (nBand)
     291             :     {
     292           0 :         case 1:
     293           0 :             return GCI_RedBand;
     294           0 :         case 2:
     295           0 :             return GCI_GreenBand;
     296           0 :         case 3:
     297           0 :             return GCI_BlueBand;
     298           0 :         case 4:
     299           0 :             return GCI_AlphaBand;
     300           0 :         default:
     301           0 :             CPLAssert(false);
     302             :             return GCI_GrayIndex;
     303             :     }
     304             : }
     305             : 
     306             : /************************************************************************/
     307             : /* ==================================================================== */
     308             : /*                           PLMosaicDataset                            */
     309             : /* ==================================================================== */
     310             : /************************************************************************/
     311             : 
     312             : /************************************************************************/
     313             : /*                        PLMosaicDataset()                            */
     314             : /************************************************************************/
     315             : 
     316          30 : PLMosaicDataset::PLMosaicDataset()
     317             :     : bMustCleanPersistent(FALSE), bTrustCache(FALSE), nQuadSize(0),
     318             :       bHasGeoTransform(FALSE), nZoomLevelMax(0), bUseTMSForMain(FALSE),
     319             :       nCacheMaxSize(10), psHead(nullptr), psTail(nullptr), nLastMetaTileX(-1),
     320          30 :       nLastMetaTileY(-1)
     321             : {
     322          30 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     323          30 :     adfGeoTransform[0] = 0;
     324          30 :     adfGeoTransform[1] = 1;
     325          30 :     adfGeoTransform[2] = 0;
     326          30 :     adfGeoTransform[3] = 0;
     327          30 :     adfGeoTransform[4] = 0;
     328          30 :     adfGeoTransform[5] = 1;
     329             : 
     330          30 :     SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
     331          30 :     osCachePathRoot = CPLGetPath(CPLGenerateTempFilename(""));
     332          30 : }
     333             : 
     334             : /************************************************************************/
     335             : /*                         ~PLMosaicDataset()                           */
     336             : /************************************************************************/
     337             : 
     338          60 : PLMosaicDataset::~PLMosaicDataset()
     339             : 
     340             : {
     341          30 :     PLMosaicDataset::FlushCache(true);
     342         170 :     for (auto &poDS : apoTMSDS)
     343         140 :         delete poDS;
     344          30 :     if (poLastItemsInformation)
     345           0 :         json_object_put(poLastItemsInformation);
     346          30 :     if (bMustCleanPersistent)
     347             :     {
     348          28 :         char **papszOptions = CSLSetNameValue(nullptr, "CLOSE_PERSISTENT",
     349             :                                               CPLSPrintf("PLMOSAIC:%p", this));
     350          28 :         CPLHTTPDestroyResult(CPLHTTPFetch(osBaseURL, papszOptions));
     351          28 :         CSLDestroy(papszOptions);
     352             :     }
     353          60 : }
     354             : 
     355             : /************************************************************************/
     356             : /*                      FlushDatasetsCache()                            */
     357             : /************************************************************************/
     358             : 
     359          36 : void PLMosaicDataset::FlushDatasetsCache()
     360             : {
     361          55 :     for (PLLinkedDataset *psIter = psHead; psIter != nullptr;)
     362             :     {
     363          19 :         PLLinkedDataset *psNext = psIter->psNext;
     364          19 :         if (psIter->poDS)
     365           7 :             GDALClose(psIter->poDS);
     366          19 :         delete psIter;
     367          19 :         psIter = psNext;
     368             :     }
     369          36 :     psHead = nullptr;
     370          36 :     psTail = nullptr;
     371          36 :     oMapLinkedDatasets.clear();
     372          36 : }
     373             : 
     374             : /************************************************************************/
     375             : /*                            FlushCache()                              */
     376             : /************************************************************************/
     377             : 
     378          35 : CPLErr PLMosaicDataset::FlushCache(bool bAtClosing)
     379             : {
     380          35 :     FlushDatasetsCache();
     381             : 
     382          35 :     nLastMetaTileX = -1;
     383          35 :     nLastMetaTileY = -1;
     384          35 :     if (poLastItemsInformation)
     385           2 :         json_object_put(poLastItemsInformation);
     386          35 :     poLastItemsInformation = nullptr;
     387          35 :     osLastRetGetLocationInfo.clear();
     388             : 
     389          35 :     return GDALDataset::FlushCache(bAtClosing);
     390             : }
     391             : 
     392             : /************************************************************************/
     393             : /*                            Identify()                                */
     394             : /************************************************************************/
     395             : 
     396       52409 : int PLMosaicDataset::Identify(GDALOpenInfo *poOpenInfo)
     397             : 
     398             : {
     399       52409 :     return STARTS_WITH_CI(poOpenInfo->pszFilename, "PLMOSAIC:");
     400             : }
     401             : 
     402             : /************************************************************************/
     403             : /*                          GetBaseHTTPOptions()                         */
     404             : /************************************************************************/
     405             : 
     406          56 : char **PLMosaicDataset::GetBaseHTTPOptions()
     407             : {
     408          56 :     bMustCleanPersistent = TRUE;
     409             : 
     410             :     char **papszOptions =
     411          56 :         CSLAddString(nullptr, CPLSPrintf("PERSISTENT=PLMOSAIC:%p", this));
     412             :     /* Use basic auth, rather than Authorization headers since curl would
     413             :      * forward it to S3 */
     414             :     papszOptions =
     415          56 :         CSLAddString(papszOptions, CPLSPrintf("USERPWD=%s:", osAPIKey.c_str()));
     416             : 
     417          56 :     return papszOptions;
     418             : }
     419             : 
     420             : /************************************************************************/
     421             : /*                               Download()                             */
     422             : /************************************************************************/
     423             : 
     424          56 : CPLHTTPResult *PLMosaicDataset::Download(const char *pszURL, int bQuiet404Error)
     425             : {
     426          56 :     char **papszOptions = CSLAddString(GetBaseHTTPOptions(), nullptr);
     427          56 :     CPLHTTPResult *psResult = nullptr;
     428          56 :     if (STARTS_WITH(osBaseURL, "/vsimem/") && STARTS_WITH(pszURL, "/vsimem/"))
     429             :     {
     430          18 :         CPLDebug("PLSCENES", "Fetching %s", pszURL);
     431             :         psResult = reinterpret_cast<CPLHTTPResult *>(
     432          18 :             CPLCalloc(1, sizeof(CPLHTTPResult)));
     433          18 :         vsi_l_offset nDataLength = 0;
     434          36 :         CPLString osURL(pszURL);
     435          18 :         if (osURL.back() == '/')
     436           1 :             osURL.pop_back();
     437          18 :         GByte *pabyBuf = VSIGetMemFileBuffer(osURL, &nDataLength, FALSE);
     438          18 :         if (pabyBuf)
     439             :         {
     440          16 :             psResult->pabyData = reinterpret_cast<GByte *>(
     441          16 :                 VSIMalloc(1 + static_cast<size_t>(nDataLength)));
     442          16 :             if (psResult->pabyData)
     443             :             {
     444          16 :                 memcpy(psResult->pabyData, pabyBuf,
     445             :                        static_cast<size_t>(nDataLength));
     446          16 :                 psResult->pabyData[nDataLength] = 0;
     447          16 :                 psResult->nDataLen = static_cast<int>(nDataLength);
     448             :             }
     449             :         }
     450             :         else
     451             :         {
     452           2 :             psResult->pszErrBuf =
     453           2 :                 CPLStrdup(CPLSPrintf("Error 404. Cannot find %s", pszURL));
     454             :         }
     455             :     }
     456             :     else
     457             :     {
     458          38 :         if (bQuiet404Error)
     459          25 :             CPLPushErrorHandler(CPLQuietErrorHandler);
     460          38 :         psResult = CPLHTTPFetch(pszURL, papszOptions);
     461          38 :         if (bQuiet404Error)
     462          25 :             CPLPopErrorHandler();
     463             :     }
     464          56 :     CSLDestroy(papszOptions);
     465             : 
     466          56 :     if (psResult->pszErrBuf != nullptr)
     467             :     {
     468          19 :         if (!(bQuiet404Error && strstr(psResult->pszErrBuf, "404")))
     469             :         {
     470           2 :             CPLError(CE_Failure, CPLE_AppDefined, "%s",
     471           2 :                      psResult->pabyData
     472             :                          ? reinterpret_cast<const char *>(psResult->pabyData)
     473             :                          : psResult->pszErrBuf);
     474             :         }
     475          19 :         CPLHTTPDestroyResult(psResult);
     476          19 :         return nullptr;
     477             :     }
     478             : 
     479          37 :     if (psResult->pabyData == nullptr)
     480             :     {
     481           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     482             :                  "Empty content returned by server");
     483           0 :         CPLHTTPDestroyResult(psResult);
     484           0 :         return nullptr;
     485             :     }
     486             : 
     487          37 :     return psResult;
     488             : }
     489             : 
     490             : /************************************************************************/
     491             : /*                               RunRequest()                           */
     492             : /************************************************************************/
     493             : 
     494          32 : json_object *PLMosaicDataset::RunRequest(const char *pszURL, int bQuiet404Error)
     495             : {
     496          32 :     CPLHTTPResult *psResult = Download(pszURL, bQuiet404Error);
     497          32 :     if (psResult == nullptr)
     498             :     {
     499           3 :         return nullptr;
     500             :     }
     501             : 
     502          29 :     json_object *poObj = nullptr;
     503          29 :     const char *pszText = reinterpret_cast<const char *>(psResult->pabyData);
     504          29 :     if (!OGRJSonParse(pszText, &poObj, true))
     505             :     {
     506           3 :         CPLHTTPDestroyResult(psResult);
     507           3 :         return nullptr;
     508             :     }
     509             : 
     510          26 :     CPLHTTPDestroyResult(psResult);
     511             : 
     512          26 :     if (json_object_get_type(poObj) != json_type_object)
     513             :     {
     514           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     515             :                  "Return is not a JSON dictionary");
     516           0 :         json_object_put(poObj);
     517           0 :         poObj = nullptr;
     518             :     }
     519             : 
     520          26 :     return poObj;
     521             : }
     522             : 
     523             : /************************************************************************/
     524             : /*                           PLMosaicGetParameter()                     */
     525             : /************************************************************************/
     526             : 
     527         141 : static CPLString PLMosaicGetParameter(GDALOpenInfo *poOpenInfo,
     528             :                                       char **papszOptions, const char *pszName,
     529             :                                       const char *pszDefaultVal)
     530             : {
     531             :     return CSLFetchNameValueDef(
     532             :         papszOptions, pszName,
     533         141 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, pszName,
     534         141 :                              pszDefaultVal));
     535             : }
     536             : 
     537             : /************************************************************************/
     538             : /*                                Open()                                */
     539             : /************************************************************************/
     540             : 
     541          30 : GDALDataset *PLMosaicDataset::Open(GDALOpenInfo *poOpenInfo)
     542             : 
     543             : {
     544          30 :     if (!Identify(poOpenInfo))
     545           0 :         return nullptr;
     546             : 
     547          30 :     PLMosaicDataset *poDS = new PLMosaicDataset();
     548             : 
     549             :     poDS->osBaseURL = CPLGetConfigOption(
     550          30 :         "PL_URL", "https://api.planet.com/basemaps/v1/mosaics");
     551             : 
     552          60 :     char **papszOptions = CSLTokenizeStringComplex(
     553          30 :         poOpenInfo->pszFilename + strlen("PLMosaic:"), ",", TRUE, FALSE);
     554          37 :     for (char **papszIter = papszOptions; papszIter && *papszIter; papszIter++)
     555             :     {
     556           8 :         char *pszKey = nullptr;
     557           8 :         const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
     558           8 :         if (pszValue != nullptr)
     559             :         {
     560           8 :             if (!EQUAL(pszKey, "api_key") && !EQUAL(pszKey, "mosaic") &&
     561           3 :                 !EQUAL(pszKey, "cache_path") && !EQUAL(pszKey, "trust_cache") &&
     562           1 :                 !EQUAL(pszKey, "use_tiles"))
     563             :             {
     564           1 :                 CPLError(CE_Failure, CPLE_NotSupported, "Unsupported option %s",
     565             :                          pszKey);
     566           1 :                 CPLFree(pszKey);
     567           1 :                 delete poDS;
     568           1 :                 CSLDestroy(papszOptions);
     569           1 :                 return nullptr;
     570             :             }
     571           7 :             CPLFree(pszKey);
     572             :         }
     573             :     }
     574             : 
     575          58 :     poDS->osAPIKey = PLMosaicGetParameter(poOpenInfo, papszOptions, "api_key",
     576          29 :                                           CPLGetConfigOption("PL_API_KEY", ""));
     577             : 
     578          29 :     if (poDS->osAPIKey.empty())
     579             :     {
     580           1 :         CPLError(
     581             :             CE_Failure, CPLE_AppDefined,
     582             :             "Missing PL_API_KEY configuration option or API_KEY open option");
     583           1 :         delete poDS;
     584           1 :         CSLDestroy(papszOptions);
     585           1 :         return nullptr;
     586             :     }
     587             : 
     588             :     poDS->osMosaic =
     589          28 :         PLMosaicGetParameter(poOpenInfo, papszOptions, "mosaic", "");
     590             : 
     591             :     poDS->osCachePathRoot =
     592          56 :         PLMosaicGetParameter(poOpenInfo, papszOptions, "cache_path",
     593          28 :                              CPLGetConfigOption("PL_CACHE_PATH", ""));
     594             : 
     595          28 :     poDS->bTrustCache = CPLTestBool(
     596          56 :         PLMosaicGetParameter(poOpenInfo, papszOptions, "trust_cache", "FALSE"));
     597             : 
     598          28 :     poDS->bUseTMSForMain = CPLTestBool(
     599          56 :         PLMosaicGetParameter(poOpenInfo, papszOptions, "use_tiles", "FALSE"));
     600             : 
     601          28 :     CSLDestroy(papszOptions);
     602          28 :     papszOptions = nullptr;
     603             : 
     604          28 :     if (!poDS->osMosaic.empty())
     605             :     {
     606          20 :         if (!poDS->OpenMosaic())
     607             :         {
     608           8 :             delete poDS;
     609           8 :             poDS = nullptr;
     610             :         }
     611             :     }
     612             :     else
     613             :     {
     614          16 :         auto aosNameList = poDS->ListSubdatasets();
     615           8 :         if (aosNameList.empty())
     616             :         {
     617           5 :             delete poDS;
     618           5 :             poDS = nullptr;
     619             :         }
     620           3 :         else if (aosNameList.size() == 1)
     621             :         {
     622           4 :             const CPLString osOldFilename(poOpenInfo->pszFilename);
     623             :             const CPLString osMosaicConnectionString =
     624           4 :                 CPLSPrintf("PLMOSAIC:mosaic=%s", aosNameList[0].c_str());
     625           2 :             delete poDS;
     626             :             GDALOpenInfo oOpenInfo(osMosaicConnectionString.c_str(),
     627           4 :                                    GA_ReadOnly);
     628           2 :             oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
     629           2 :             poDS = reinterpret_cast<PLMosaicDataset *>(Open(&oOpenInfo));
     630           2 :             if (poDS)
     631           2 :                 poDS->SetDescription(osOldFilename);
     632             :         }
     633             :         else
     634             :         {
     635           2 :             CPLStringList aosSubdatasets;
     636           3 :             for (const auto &osName : aosNameList)
     637             :             {
     638           2 :                 const int nDatasetIdx = aosSubdatasets.Count() / 2 + 1;
     639             :                 aosSubdatasets.AddNameValue(
     640             :                     CPLSPrintf("SUBDATASET_%d_NAME", nDatasetIdx),
     641           2 :                     CPLSPrintf("PLMOSAIC:mosaic=%s", osName.c_str()));
     642             :                 aosSubdatasets.AddNameValue(
     643             :                     CPLSPrintf("SUBDATASET_%d_DESC", nDatasetIdx),
     644           2 :                     CPLSPrintf("Mosaic %s", osName.c_str()));
     645             :             }
     646           1 :             poDS->SetMetadata(aosSubdatasets.List(), "SUBDATASETS");
     647             :         }
     648             :     }
     649             : 
     650          28 :     if (poDS)
     651          15 :         poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
     652             : 
     653          28 :     return poDS;
     654             : }
     655             : 
     656             : /************************************************************************/
     657             : /*                           ReplaceSubString()                         */
     658             : /************************************************************************/
     659             : 
     660          36 : static void ReplaceSubString(CPLString &osTarget, CPLString osPattern,
     661             :                              CPLString osReplacement)
     662             : 
     663             : {
     664             :     // Assumes only one occurrence of osPattern.
     665          36 :     size_t pos = osTarget.find(osPattern);
     666          36 :     if (pos == CPLString::npos)
     667           0 :         return;
     668             : 
     669          36 :     osTarget.replace(pos, osPattern.size(), osReplacement);
     670             : }
     671             : 
     672             : /************************************************************************/
     673             : /*                            GetMosaicCachePath()                      */
     674             : /************************************************************************/
     675             : 
     676          32 : CPLString PLMosaicDataset::GetMosaicCachePath()
     677             : {
     678          32 :     if (!osCachePathRoot.empty())
     679             :     {
     680             :         const CPLString osCachePath(
     681          62 :             CPLFormFilename(osCachePathRoot, "plmosaic_cache", nullptr));
     682             :         const CPLString osMosaicPath(
     683          62 :             CPLFormFilename(osCachePath, osMosaic, nullptr));
     684             : 
     685          31 :         return osMosaicPath;
     686             :     }
     687           1 :     return "";
     688             : }
     689             : 
     690             : /************************************************************************/
     691             : /*                     CreateMosaicCachePathIfNecessary()               */
     692             : /************************************************************************/
     693             : 
     694           9 : void PLMosaicDataset::CreateMosaicCachePathIfNecessary()
     695             : {
     696           9 :     if (!osCachePathRoot.empty())
     697             :     {
     698             :         const CPLString osCachePath(
     699          16 :             CPLFormFilename(osCachePathRoot, "plmosaic_cache", nullptr));
     700             :         const CPLString osMosaicPath(
     701          16 :             CPLFormFilename(osCachePath, osMosaic, nullptr));
     702             : 
     703             :         VSIStatBufL sStatBuf;
     704           8 :         if (VSIStatL(osMosaicPath, &sStatBuf) != 0)
     705             :         {
     706           6 :             CPLPushErrorHandler(CPLQuietErrorHandler);
     707           6 :             CPL_IGNORE_RET_VAL(VSIMkdir(osCachePathRoot, 0755));
     708           6 :             CPL_IGNORE_RET_VAL(VSIMkdir(osCachePath, 0755));
     709           6 :             CPL_IGNORE_RET_VAL(VSIMkdir(osMosaicPath, 0755));
     710           6 :             CPLPopErrorHandler();
     711             :         }
     712             :     }
     713           9 : }
     714             : 
     715             : /************************************************************************/
     716             : /*                     LongLatToSphericalMercator()                     */
     717             : /************************************************************************/
     718             : 
     719           2 : static void LongLatToSphericalMercator(double *x, double *y)
     720             : {
     721           2 :     double X = SPHERICAL_RADIUS * (*x) / 180 * M_PI;
     722           2 :     double Y = SPHERICAL_RADIUS * log(tan(M_PI / 4 + 0.5 * (*y) / 180 * M_PI));
     723           2 :     *x = X;
     724           2 :     *y = Y;
     725           2 : }
     726             : 
     727             : /************************************************************************/
     728             : /*                               OpenMosaic()                           */
     729             : /************************************************************************/
     730             : 
     731          20 : int PLMosaicDataset::OpenMosaic()
     732             : {
     733          40 :     CPLString osURL;
     734             : 
     735          20 :     osURL = osBaseURL;
     736          20 :     if (osURL.back() != '/')
     737          20 :         osURL += '/';
     738          20 :     char *pszEscaped = CPLEscapeString(osMosaic, -1, CPLES_URL);
     739          20 :     osURL += "?name__is=" + CPLString(pszEscaped);
     740          20 :     CPLFree(pszEscaped);
     741             : 
     742          20 :     json_object *poObj = RunRequest(osURL);
     743          20 :     if (poObj == nullptr)
     744             :     {
     745           2 :         return FALSE;
     746             :     }
     747             : 
     748          18 :     json_object *poMosaics = CPL_json_object_object_get(poObj, "mosaics");
     749          18 :     json_object *poMosaic = nullptr;
     750          35 :     if (poMosaics == nullptr ||
     751          17 :         json_object_get_type(poMosaics) != json_type_array ||
     752          17 :         json_object_array_length(poMosaics) != 1 ||
     753          52 :         (poMosaic = json_object_array_get_idx(poMosaics, 0)) == nullptr ||
     754          17 :         json_object_get_type(poMosaic) != json_type_object)
     755             :     {
     756           1 :         CPLError(CE_Failure, CPLE_AppDefined, "No mosaic %s", osMosaic.c_str());
     757           1 :         json_object_put(poObj);
     758           1 :         return FALSE;
     759             :     }
     760             : 
     761          17 :     json_object *poId = CPL_json_object_object_get(poMosaic, "id");
     762             :     json_object *poCoordinateSystem =
     763          17 :         CPL_json_object_object_get(poMosaic, "coordinate_system");
     764          17 :     json_object *poDataType = CPL_json_object_object_get(poMosaic, "datatype");
     765             :     json_object *poQuadSize =
     766          17 :         json_ex_get_object_by_path(poMosaic, "grid.quad_size");
     767             :     json_object *poResolution =
     768          17 :         json_ex_get_object_by_path(poMosaic, "grid.resolution");
     769          17 :     json_object *poLinks = CPL_json_object_object_get(poMosaic, "_links");
     770          17 :     json_object *poLinksTiles = nullptr;
     771          17 :     json_object *poBBox = CPL_json_object_object_get(poMosaic, "bbox");
     772          17 :     if (poLinks != nullptr && json_object_get_type(poLinks) == json_type_object)
     773             :     {
     774          11 :         poLinksTiles = CPL_json_object_object_get(poLinks, "tiles");
     775             :     }
     776          17 :     if (poId == nullptr || json_object_get_type(poId) != json_type_string ||
     777          16 :         poCoordinateSystem == nullptr ||
     778          16 :         json_object_get_type(poCoordinateSystem) != json_type_string ||
     779          16 :         poDataType == nullptr ||
     780          16 :         json_object_get_type(poDataType) != json_type_string ||
     781          16 :         poQuadSize == nullptr ||
     782          16 :         json_object_get_type(poQuadSize) != json_type_int ||
     783          34 :         poResolution == nullptr ||
     784          16 :         (json_object_get_type(poResolution) != json_type_int &&
     785          16 :          json_object_get_type(poResolution) != json_type_double))
     786             :     {
     787           1 :         CPLError(CE_Failure, CPLE_NotSupported, "Missing required parameter");
     788           1 :         json_object_put(poObj);
     789           1 :         return FALSE;
     790             :     }
     791             : 
     792          32 :     CPLString osId(json_object_get_string(poId));
     793             : 
     794          16 :     const char *pszSRS = json_object_get_string(poCoordinateSystem);
     795          16 :     if (!EQUAL(pszSRS, "EPSG:3857"))
     796             :     {
     797           1 :         CPLError(CE_Failure, CPLE_NotSupported,
     798             :                  "Unsupported coordinate_system = %s", pszSRS);
     799           1 :         json_object_put(poObj);
     800           1 :         return FALSE;
     801             :     }
     802             : 
     803          15 :     m_oSRS.SetFromUserInput(
     804             :         pszSRS, OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
     805             : 
     806             :     json_object *poQuadDownload =
     807          15 :         CPL_json_object_object_get(poMosaic, "quad_download");
     808          15 :     bQuadDownload = CPL_TO_BOOL(json_object_get_boolean(poQuadDownload));
     809             : 
     810          15 :     GDALDataType eDT = GDT_Unknown;
     811          15 :     const char *pszDataType = json_object_get_string(poDataType);
     812          15 :     if (EQUAL(pszDataType, "byte"))
     813          13 :         eDT = GDT_Byte;
     814           2 :     else if (EQUAL(pszDataType, "uint16"))
     815           1 :         eDT = GDT_UInt16;
     816           1 :     else if (EQUAL(pszDataType, "int16"))
     817           0 :         eDT = GDT_Int16;
     818             :     else
     819             :     {
     820           1 :         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data_type = %s",
     821             :                  pszDataType);
     822           1 :         json_object_put(poObj);
     823           1 :         return FALSE;
     824             :     }
     825             : 
     826          14 :     if (eDT == GDT_Byte && !bQuadDownload)
     827           2 :         bUseTMSForMain = true;
     828             : 
     829          14 :     if (bUseTMSForMain && eDT != GDT_Byte)
     830             :     {
     831           1 :         CPLError(
     832             :             CE_Failure, CPLE_NotSupported,
     833             :             "Cannot use tile API for full resolution data on non Byte mosaic");
     834           1 :         bUseTMSForMain = FALSE;
     835             :     }
     836             : 
     837          14 :     nQuadSize = json_object_get_int(poQuadSize);
     838          14 :     if (nQuadSize <= 0 || (nQuadSize % 256) != 0)
     839             :     {
     840           1 :         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported quad_size = %d",
     841             :                  nQuadSize);
     842           1 :         json_object_put(poObj);
     843           1 :         return FALSE;
     844             :     }
     845             : 
     846          13 :     const double dfResolution = json_object_get_double(poResolution);
     847          13 :     if (EQUAL(pszSRS, "EPSG:3857"))
     848             :     {
     849          13 :         double dfZoomLevel = log(GM_ZOOM_0 / dfResolution) / log(2.0);
     850          13 :         nZoomLevelMax = static_cast<int>(dfZoomLevel + 0.1);
     851          13 :         if (fabs(dfZoomLevel - nZoomLevelMax) > 1e-5)
     852             :         {
     853           1 :             CPLError(CE_Failure, CPLE_NotSupported,
     854             :                      "Unsupported resolution = %.12g", dfResolution);
     855           1 :             json_object_put(poObj);
     856           1 :             return FALSE;
     857             :         }
     858             : 
     859          12 :         bHasGeoTransform = TRUE;
     860          12 :         adfGeoTransform[0] = GM_ORIGIN;
     861          12 :         adfGeoTransform[1] = dfResolution;
     862          12 :         adfGeoTransform[2] = 0;
     863          12 :         adfGeoTransform[3] = -GM_ORIGIN;
     864          12 :         adfGeoTransform[4] = 0;
     865          12 :         adfGeoTransform[5] = -dfResolution;
     866          12 :         nRasterXSize = static_cast<int>(2 * -GM_ORIGIN / dfResolution + 0.5);
     867          12 :         nRasterYSize = nRasterXSize;
     868             : 
     869          13 :         if (poBBox != nullptr &&
     870          13 :             json_object_get_type(poBBox) == json_type_array &&
     871           1 :             json_object_array_length(poBBox) == 4)
     872             :         {
     873             :             double xmin =
     874           1 :                 json_object_get_double(json_object_array_get_idx(poBBox, 0));
     875             :             double ymin =
     876           1 :                 json_object_get_double(json_object_array_get_idx(poBBox, 1));
     877             :             double xmax =
     878           1 :                 json_object_get_double(json_object_array_get_idx(poBBox, 2));
     879             :             double ymax =
     880           1 :                 json_object_get_double(json_object_array_get_idx(poBBox, 3));
     881           1 :             LongLatToSphericalMercator(&xmin, &ymin);
     882           1 :             LongLatToSphericalMercator(&xmax, &ymax);
     883           1 :             xmin = std::max(xmin, GM_ORIGIN);
     884           1 :             ymin = std::max(ymin, GM_ORIGIN);
     885           1 :             xmax = std::min(xmax, -GM_ORIGIN);
     886           1 :             ymax = std::min(ymax, -GM_ORIGIN);
     887             : 
     888           1 :             double dfTileSize = dfResolution * nQuadSize;
     889           1 :             xmin = floor(xmin / dfTileSize) * dfTileSize;
     890           1 :             ymin = floor(ymin / dfTileSize) * dfTileSize;
     891           1 :             xmax = ceil(xmax / dfTileSize) * dfTileSize;
     892           1 :             ymax = ceil(ymax / dfTileSize) * dfTileSize;
     893           1 :             adfGeoTransform[0] = xmin;
     894           1 :             adfGeoTransform[3] = ymax;
     895           1 :             nRasterXSize = static_cast<int>((xmax - xmin) / dfResolution + 0.5);
     896           1 :             nRasterYSize = static_cast<int>((ymax - ymin) / dfResolution + 0.5);
     897           1 :             nMetaTileXShift =
     898           1 :                 static_cast<int>((xmin - GM_ORIGIN) / dfTileSize + 0.5);
     899           1 :             nMetaTileYShift =
     900           1 :                 static_cast<int>((ymin - GM_ORIGIN) / dfTileSize + 0.5);
     901             :         }
     902             :     }
     903             : 
     904          12 :     osQuadsURL = osBaseURL;
     905          12 :     if (osQuadsURL.back() != '/')
     906          12 :         osQuadsURL += '/';
     907          12 :     osQuadsURL += osId + "/quads/";
     908             : 
     909             :     // Use WMS/TMS driver for overviews (only for byte)
     910          11 :     if (eDT == GDT_Byte && EQUAL(pszSRS, "EPSG:3857") &&
     911          23 :         poLinksTiles != nullptr &&
     912          10 :         json_object_get_type(poLinksTiles) == json_type_string)
     913             :     {
     914          10 :         const char *pszLinksTiles = json_object_get_string(poLinksTiles);
     915          10 :         if (strstr(pszLinksTiles, "{x}") == nullptr ||
     916           9 :             strstr(pszLinksTiles, "{y}") == nullptr ||
     917           9 :             strstr(pszLinksTiles, "{z}") == nullptr)
     918             :         {
     919           1 :             CPLError(CE_Warning, CPLE_NotSupported, "Invalid _links.tiles = %s",
     920             :                      pszLinksTiles);
     921             :         }
     922             :         else
     923             :         {
     924          18 :             CPLString osCacheStr;
     925           9 :             if (!osCachePathRoot.empty())
     926             :             {
     927           7 :                 osCacheStr = "    <Cache><Path>";
     928           7 :                 osCacheStr += GetMosaicCachePath();
     929           7 :                 osCacheStr += "</Path><Unique>False</Unique></Cache>\n";
     930             :             }
     931             : 
     932          18 :             CPLString osTMSURL(pszLinksTiles);
     933           9 :             ReplaceSubString(osTMSURL, "{x}", "${x}");
     934           9 :             ReplaceSubString(osTMSURL, "{y}", "${y}");
     935           9 :             ReplaceSubString(osTMSURL, "{z}", "${z}");
     936           9 :             ReplaceSubString(osTMSURL, "{0-3}", "0");
     937             : 
     938         148 :             for (int nZoomLevel = nZoomLevelMax; nZoomLevel >= 0; nZoomLevel--)
     939             :             {
     940         140 :                 const int nZShift = nZoomLevelMax - nZoomLevel;
     941         140 :                 int nOvrXSize = nRasterXSize >> nZShift;
     942         140 :                 int nOvrYSize = nRasterYSize >> nZShift;
     943         140 :                 if (nOvrXSize == 0 || nOvrYSize == 0)
     944             :                     break;
     945             : 
     946             :                 CPLString osTMS = CPLSPrintf(
     947             :                     "<GDAL_WMS>\n"
     948             :                     "    <Service name=\"TMS\">\n"
     949             :                     "        <ServerUrl>%s</ServerUrl>\n"
     950             :                     "    </Service>\n"
     951             :                     "    <DataWindow>\n"
     952             :                     "        <UpperLeftX>%.16g</UpperLeftX>\n"
     953             :                     "        <UpperLeftY>%.16g</UpperLeftY>\n"
     954             :                     "        <LowerRightX>%.16g</LowerRightX>\n"
     955             :                     "        <LowerRightY>%.16g</LowerRightY>\n"
     956             :                     "        <SizeX>%d</SizeX>\n"
     957             :                     "        <SizeY>%d</SizeY>\n"
     958             :                     "        <TileLevel>%d</TileLevel>\n"
     959             :                     "        <YOrigin>top</YOrigin>\n"
     960             :                     "    </DataWindow>\n"
     961             :                     "    <Projection>%s</Projection>\n"
     962             :                     "    <BlockSizeX>256</BlockSizeX>\n"
     963             :                     "    <BlockSizeY>256</BlockSizeY>\n"
     964             :                     "    <BandsCount>4</BandsCount>\n"
     965             :                     "%s"
     966             :                     "</GDAL_WMS>",
     967             :                     osTMSURL.c_str(), GM_ORIGIN, -GM_ORIGIN, -GM_ORIGIN,
     968             :                     GM_ORIGIN, 256 << nZoomLevel, 256 << nZoomLevel, nZoomLevel,
     969         140 :                     pszSRS, osCacheStr.c_str());
     970             : 
     971         140 :                 GDALDataset *poTMSDS = GDALDataset::FromHandle(
     972             :                     GDALOpenEx(osTMS, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
     973             :                                nullptr, nullptr, nullptr));
     974         140 :                 if (poTMSDS)
     975             :                 {
     976         140 :                     double dfThisResolution = dfResolution * (1 << nZShift);
     977             : 
     978         140 :                     VRTDatasetH hVRTDS = VRTCreate(nOvrXSize, nOvrYSize);
     979         700 :                     for (int iBand = 1; iBand <= 4; iBand++)
     980             :                     {
     981         560 :                         VRTAddBand(hVRTDS, GDT_Byte, nullptr);
     982             :                     }
     983             : 
     984             :                     int nSrcXOff, nSrcYOff, nDstXOff, nDstYOff;
     985             : 
     986         140 :                     nSrcXOff = static_cast<int>(
     987         140 :                         0.5 +
     988         140 :                         (adfGeoTransform[0] - GM_ORIGIN) / dfThisResolution);
     989         140 :                     nDstXOff = 0;
     990             : 
     991         140 :                     nSrcYOff = static_cast<int>(
     992         140 :                         0.5 +
     993         140 :                         (-GM_ORIGIN - adfGeoTransform[3]) / dfThisResolution);
     994         140 :                     nDstYOff = 0;
     995             : 
     996         700 :                     for (int iBand = 1; iBand <= 4; iBand++)
     997             :                     {
     998             :                         VRTSourcedRasterBandH hVRTBand =
     999         560 :                             reinterpret_cast<VRTSourcedRasterBandH>(
    1000             :                                 GDALGetRasterBand(hVRTDS, iBand));
    1001         560 :                         VRTAddSimpleSource(
    1002             :                             hVRTBand, GDALGetRasterBand(poTMSDS, iBand),
    1003             :                             nSrcXOff, nSrcYOff, nOvrXSize, nOvrYSize, nDstXOff,
    1004             :                             nDstYOff, nOvrXSize, nOvrYSize, "NEAR",
    1005             :                             VRT_NODATA_UNSET);
    1006             :                     }
    1007         140 :                     poTMSDS->Dereference();
    1008             : 
    1009         140 :                     apoTMSDS.push_back(GDALDataset::FromHandle(hVRTDS));
    1010             :                 }
    1011             : 
    1012         140 :                 if (nOvrXSize < 256 && nOvrYSize < 256)
    1013           1 :                     break;
    1014             :             }
    1015             :         }
    1016             :     }
    1017             : 
    1018          12 :     if (bUseTMSForMain && apoTMSDS.empty())
    1019             :     {
    1020           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    1021             :                  "Cannot find tile definition, so use_tiles will be ignored");
    1022           1 :         bUseTMSForMain = FALSE;
    1023             :     }
    1024             : 
    1025          60 :     for (int i = 0; i < 4; i++)
    1026          48 :         SetBand(i + 1, new PLMosaicRasterBand(this, i + 1, eDT));
    1027             : 
    1028             :     json_object *poFirstAcquired =
    1029          12 :         CPL_json_object_object_get(poMosaic, "first_acquired");
    1030          24 :     if (poFirstAcquired != nullptr &&
    1031          12 :         json_object_get_type(poFirstAcquired) == json_type_string)
    1032             :     {
    1033          12 :         SetMetadataItem("FIRST_ACQUIRED",
    1034          12 :                         json_object_get_string(poFirstAcquired));
    1035             :     }
    1036             :     json_object *poLastAcquired =
    1037          12 :         CPL_json_object_object_get(poMosaic, "last_acquired");
    1038          24 :     if (poLastAcquired != nullptr &&
    1039          12 :         json_object_get_type(poLastAcquired) == json_type_string)
    1040             :     {
    1041          12 :         SetMetadataItem("LAST_ACQUIRED",
    1042          12 :                         json_object_get_string(poLastAcquired));
    1043             :     }
    1044          12 :     json_object *poName = CPL_json_object_object_get(poMosaic, "name");
    1045          12 :     if (poName != nullptr && json_object_get_type(poName) == json_type_string)
    1046             :     {
    1047          12 :         SetMetadataItem("NAME", json_object_get_string(poName));
    1048             :     }
    1049             : 
    1050          12 :     json_object_put(poObj);
    1051          12 :     return TRUE;
    1052             : }
    1053             : 
    1054             : /************************************************************************/
    1055             : /*                          ListSubdatasets()                           */
    1056             : /************************************************************************/
    1057             : 
    1058           8 : std::vector<CPLString> PLMosaicDataset::ListSubdatasets()
    1059             : {
    1060           8 :     std::vector<CPLString> aosNameList;
    1061          16 :     CPLString osURL(osBaseURL);
    1062          13 :     while (osURL.size())
    1063             :     {
    1064           9 :         json_object *poObj = RunRequest(osURL);
    1065           9 :         if (poObj == nullptr)
    1066             :         {
    1067           3 :             return aosNameList;
    1068             :         }
    1069             : 
    1070           6 :         osURL = "";
    1071           6 :         json_object *poLinks = CPL_json_object_object_get(poObj, "_links");
    1072           8 :         if (poLinks != nullptr &&
    1073           2 :             json_object_get_type(poLinks) == json_type_object)
    1074             :         {
    1075           2 :             json_object *poNext = CPL_json_object_object_get(poLinks, "_next");
    1076           3 :             if (poNext != nullptr &&
    1077           1 :                 json_object_get_type(poNext) == json_type_string)
    1078             :             {
    1079           1 :                 osURL = json_object_get_string(poNext);
    1080             :             }
    1081             :         }
    1082             : 
    1083           6 :         json_object *poMosaics = CPL_json_object_object_get(poObj, "mosaics");
    1084          11 :         if (poMosaics == nullptr ||
    1085           5 :             json_object_get_type(poMosaics) != json_type_array)
    1086             :         {
    1087           1 :             json_object_put(poObj);
    1088           1 :             return aosNameList;
    1089             :         }
    1090             : 
    1091           5 :         const auto nMosaics = json_object_array_length(poMosaics);
    1092          10 :         for (auto i = decltype(nMosaics){0}; i < nMosaics; i++)
    1093             :         {
    1094           5 :             const char *pszName = nullptr;
    1095           5 :             const char *pszCoordinateSystem = nullptr;
    1096           5 :             json_object *poMosaic = json_object_array_get_idx(poMosaics, i);
    1097           5 :             bool bAccessible = false;
    1098           5 :             if (poMosaic && json_object_get_type(poMosaic) == json_type_object)
    1099             :             {
    1100             :                 json_object *poName =
    1101           5 :                     CPL_json_object_object_get(poMosaic, "name");
    1102          10 :                 if (poName != nullptr &&
    1103           5 :                     json_object_get_type(poName) == json_type_string)
    1104             :                 {
    1105           5 :                     pszName = json_object_get_string(poName);
    1106             :                 }
    1107             : 
    1108             :                 json_object *poCoordinateSystem =
    1109           5 :                     CPL_json_object_object_get(poMosaic, "coordinate_system");
    1110          10 :                 if (poCoordinateSystem &&
    1111           5 :                     json_object_get_type(poCoordinateSystem) ==
    1112             :                         json_type_string)
    1113             :                 {
    1114             :                     pszCoordinateSystem =
    1115           5 :                         json_object_get_string(poCoordinateSystem);
    1116             :                 }
    1117             : 
    1118             :                 json_object *poDataType =
    1119           5 :                     CPL_json_object_object_get(poMosaic, "datatype");
    1120           5 :                 if (poDataType &&
    1121           0 :                     json_object_get_type(poDataType) == json_type_string &&
    1122           5 :                     EQUAL(json_object_get_string(poDataType), "byte") &&
    1123           0 :                     !CSLTestBoolean(CPLGetConfigOption(
    1124             :                         "PL_MOSAIC_LIST_QUAD_DOWNLOAD_ONLY", "NO")))
    1125             :                 {
    1126           0 :                     bAccessible = true;  // through tile API
    1127             :                 }
    1128             :                 else
    1129             :                 {
    1130             :                     json_object *poQuadDownload =
    1131           5 :                         CPL_json_object_object_get(poMosaic, "quad_download");
    1132             :                     bAccessible =
    1133           5 :                         CPL_TO_BOOL(json_object_get_boolean(poQuadDownload));
    1134             :                 }
    1135             :             }
    1136             : 
    1137           5 :             if (bAccessible && pszName && pszCoordinateSystem &&
    1138           5 :                 EQUAL(pszCoordinateSystem, "EPSG:3857"))
    1139             :             {
    1140           4 :                 aosNameList.push_back(pszName);
    1141             :             }
    1142             :         }
    1143             : 
    1144           5 :         json_object_put(poObj);
    1145             :     }
    1146           4 :     return aosNameList;
    1147             : }
    1148             : 
    1149             : /************************************************************************/
    1150             : /*                            GetSpatialRef()                           */
    1151             : /************************************************************************/
    1152             : 
    1153           1 : const OGRSpatialReference *PLMosaicDataset::GetSpatialRef() const
    1154             : 
    1155             : {
    1156           1 :     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    1157             : }
    1158             : 
    1159             : /************************************************************************/
    1160             : /*                            GetGeoTransform()                         */
    1161             : /************************************************************************/
    1162             : 
    1163           2 : CPLErr PLMosaicDataset::GetGeoTransform(double *padfGeoTransform)
    1164             : {
    1165           2 :     memcpy(padfGeoTransform, adfGeoTransform, 6 * sizeof(double));
    1166           2 :     return (bHasGeoTransform) ? CE_None : CE_Failure;
    1167             : }
    1168             : 
    1169             : /************************************************************************/
    1170             : /*                          formatTileName()                            */
    1171             : /************************************************************************/
    1172             : 
    1173          66 : CPLString PLMosaicDataset::formatTileName(int tile_x, int tile_y)
    1174             : 
    1175             : {
    1176          66 :     return CPLSPrintf("%d-%d", tile_x, tile_y);
    1177             : }
    1178             : 
    1179             : /************************************************************************/
    1180             : /*                          InsertNewDataset()                          */
    1181             : /************************************************************************/
    1182             : 
    1183          25 : void PLMosaicDataset::InsertNewDataset(const CPLString &osKey,
    1184             :                                        GDALDataset *poDS)
    1185             : {
    1186          25 :     if (static_cast<int>(oMapLinkedDatasets.size()) == nCacheMaxSize)
    1187             :     {
    1188           6 :         CPLDebug("PLMOSAIC", "Discarding older entry %s from cache",
    1189           6 :                  psTail->osKey.c_str());
    1190           6 :         oMapLinkedDatasets.erase(psTail->osKey);
    1191           6 :         PLLinkedDataset *psNewTail = psTail->psPrev;
    1192           6 :         psNewTail->psNext = nullptr;
    1193           6 :         if (psTail->poDS)
    1194           0 :             GDALClose(psTail->poDS);
    1195           6 :         delete psTail;
    1196           6 :         psTail = psNewTail;
    1197             :     }
    1198             : 
    1199          25 :     PLLinkedDataset *psLinkedDataset = new PLLinkedDataset();
    1200          25 :     if (psHead)
    1201          15 :         psHead->psPrev = psLinkedDataset;
    1202          25 :     psLinkedDataset->osKey = osKey;
    1203          25 :     psLinkedDataset->psNext = psHead;
    1204          25 :     psLinkedDataset->poDS = poDS;
    1205          25 :     psHead = psLinkedDataset;
    1206          25 :     if (psTail == nullptr)
    1207          10 :         psTail = psHead;
    1208          25 :     oMapLinkedDatasets[osKey] = psLinkedDataset;
    1209          25 : }
    1210             : 
    1211             : /************************************************************************/
    1212             : /*                         OpenAndInsertNewDataset()                    */
    1213             : /************************************************************************/
    1214             : 
    1215             : GDALDataset *
    1216           9 : PLMosaicDataset::OpenAndInsertNewDataset(const CPLString &osTmpFilename,
    1217             :                                          const CPLString &osTilename)
    1218             : {
    1219           9 :     const char *const apszAllowedDrivers[2] = {"GTiff", nullptr};
    1220           9 :     GDALDataset *poDS = GDALDataset::FromHandle(
    1221             :         GDALOpenEx(osTmpFilename, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
    1222             :                    apszAllowedDrivers, nullptr, nullptr));
    1223           9 :     if (poDS != nullptr)
    1224             :     {
    1225           7 :         if (poDS->GetRasterXSize() != nQuadSize ||
    1226           7 :             poDS->GetRasterYSize() != nQuadSize || poDS->GetRasterCount() != 4)
    1227             :         {
    1228           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1229             :                      "Inconsistent metatile characteristics");
    1230           0 :             GDALClose(poDS);
    1231           0 :             poDS = nullptr;
    1232             :         }
    1233             :     }
    1234             :     else
    1235             :     {
    1236           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid GTiff dataset: %s",
    1237             :                  osTilename.c_str());
    1238             :     }
    1239             : 
    1240           9 :     InsertNewDataset(osTilename, poDS);
    1241           9 :     return poDS;
    1242             : }
    1243             : 
    1244             : /************************************************************************/
    1245             : /*                            GetMetaTile()                             */
    1246             : /************************************************************************/
    1247             : 
    1248          62 : GDALDataset *PLMosaicDataset::GetMetaTile(int tile_x, int tile_y)
    1249             : {
    1250         124 :     const CPLString osTilename = formatTileName(tile_x, tile_y);
    1251             :     std::map<CPLString, PLLinkedDataset *>::const_iterator it =
    1252          62 :         oMapLinkedDatasets.find(osTilename);
    1253          62 :     if (it == oMapLinkedDatasets.end())
    1254             :     {
    1255          50 :         CPLString osTmpFilename;
    1256             : 
    1257          50 :         const CPLString osMosaicPath(GetMosaicCachePath());
    1258             :         osTmpFilename =
    1259             :             CPLFormFilename(osMosaicPath,
    1260             :                             CPLSPrintf("%s_%s.tif", osMosaic.c_str(),
    1261             :                                        CPLGetFilename(osTilename)),
    1262          25 :                             nullptr);
    1263             :         VSIStatBufL sStatBuf;
    1264             : 
    1265          50 :         CPLString osURL = osQuadsURL;
    1266          25 :         osURL += osTilename;
    1267          25 :         osURL += "/full";
    1268             : 
    1269          25 :         if (!osCachePathRoot.empty() && VSIStatL(osTmpFilename, &sStatBuf) == 0)
    1270             :         {
    1271           3 :             if (bTrustCache)
    1272             :             {
    1273           1 :                 return OpenAndInsertNewDataset(osTmpFilename, osTilename);
    1274             :             }
    1275             : 
    1276           2 :             CPLDebug("PLMOSAIC",
    1277             :                      "File %s exists. Checking if it is up-to-date...",
    1278             :                      osTmpFilename.c_str());
    1279             :             // Currently we only check by file size, which should be good enough
    1280             :             // as the metatiles are compressed, so a change in content is likely
    1281             :             // to cause a change in filesize. Use of a signature would be better
    1282             :             // though if available in the metadata
    1283             :             VSIStatBufL sRemoteTileStatBuf;
    1284           2 :             char *pszEscapedURL = CPLEscapeString(
    1285           4 :                 (osURL + "?api_key=" + osAPIKey).c_str(), -1, CPLES_URL);
    1286           2 :             CPLString osVSICURLUrl(STARTS_WITH(osURL, "/vsimem/")
    1287           4 :                                        ? osURL
    1288             :                                        : "/vsicurl?use_head=no&url=" +
    1289           6 :                                              CPLString(pszEscapedURL));
    1290           2 :             CPLFree(pszEscapedURL);
    1291           2 :             if (VSIStatL(osVSICURLUrl, &sRemoteTileStatBuf) == 0 &&
    1292           0 :                 sRemoteTileStatBuf.st_size == sStatBuf.st_size)
    1293             :             {
    1294           0 :                 CPLDebug("PLMOSAIC", "Cached tile is up-to-date");
    1295           0 :                 return OpenAndInsertNewDataset(osTmpFilename, osTilename);
    1296             :             }
    1297             :             else
    1298             :             {
    1299           2 :                 CPLDebug("PLMOSAIC", "Cached tile is not up-to-date");
    1300           2 :                 VSIUnlink(osTmpFilename);
    1301             :             }
    1302             :         }
    1303             : 
    1304             :         // Fetch the GeoTIFF now
    1305             : 
    1306          24 :         CPLHTTPResult *psResult = Download(osURL, TRUE);
    1307          24 :         if (psResult == nullptr)
    1308             :         {
    1309          16 :             InsertNewDataset(osTilename, nullptr);
    1310          16 :             return nullptr;
    1311             :         }
    1312             : 
    1313           8 :         CreateMosaicCachePathIfNecessary();
    1314             : 
    1315           8 :         bool bUnlink = false;
    1316             :         VSILFILE *fp =
    1317           8 :             osCachePathRoot.size() ? VSIFOpenL(osTmpFilename, "wb") : nullptr;
    1318           8 :         if (fp)
    1319             :         {
    1320           6 :             VSIFWriteL(psResult->pabyData, 1, psResult->nDataLen, fp);
    1321           6 :             VSIFCloseL(fp);
    1322             :         }
    1323             :         else
    1324             :         {
    1325             :             // In case there's no temporary path or it is not writable
    1326             :             // use a in-memory dataset, and limit the cache to only one
    1327           2 :             if (!osCachePathRoot.empty() && nCacheMaxSize > 1)
    1328             :             {
    1329           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1330             :                          "Cannot write into %s. Using /vsimem and reduce cache "
    1331             :                          "to 1 entry",
    1332             :                          osCachePathRoot.c_str());
    1333           1 :                 FlushDatasetsCache();
    1334           1 :                 nCacheMaxSize = 1;
    1335             :             }
    1336           2 :             bUnlink = true;
    1337             :             osTmpFilename = VSIMemGenerateHiddenFilename(
    1338             :                 CPLSPrintf("single_tile_plmosaic_cache_%s_%d_%d.tif",
    1339           2 :                            osMosaic.c_str(), tile_x, tile_y));
    1340           2 :             fp = VSIFOpenL(osTmpFilename, "wb");
    1341           2 :             if (fp)
    1342             :             {
    1343           2 :                 VSIFWriteL(psResult->pabyData, 1, psResult->nDataLen, fp);
    1344           2 :                 VSIFCloseL(fp);
    1345             :             }
    1346             :         }
    1347           8 :         CPLHTTPDestroyResult(psResult);
    1348           8 :         GDALDataset *poDS = OpenAndInsertNewDataset(osTmpFilename, osTilename);
    1349             : 
    1350           8 :         if (bUnlink)
    1351           2 :             VSIUnlink(osTilename);
    1352             : 
    1353           8 :         return poDS;
    1354             :     }
    1355             : 
    1356             :     // Move link to head of MRU list
    1357          37 :     PLLinkedDataset *psLinkedDataset = it->second;
    1358          37 :     GDALDataset *poDS = psLinkedDataset->poDS;
    1359          37 :     if (psLinkedDataset != psHead)
    1360             :     {
    1361          18 :         if (psLinkedDataset == psTail)
    1362           2 :             psTail = psLinkedDataset->psPrev;
    1363          18 :         if (psLinkedDataset->psPrev)
    1364          18 :             psLinkedDataset->psPrev->psNext = psLinkedDataset->psNext;
    1365          18 :         if (psLinkedDataset->psNext)
    1366          16 :             psLinkedDataset->psNext->psPrev = psLinkedDataset->psPrev;
    1367          18 :         psLinkedDataset->psNext = psHead;
    1368          18 :         psLinkedDataset->psPrev = nullptr;
    1369          18 :         psHead->psPrev = psLinkedDataset;
    1370          18 :         psHead = psLinkedDataset;
    1371             :     }
    1372             : 
    1373          37 :     return poDS;
    1374             : }
    1375             : 
    1376             : /************************************************************************/
    1377             : /*                         GetLocationInfo()                            */
    1378             : /************************************************************************/
    1379             : 
    1380           4 : const char *PLMosaicDataset::GetLocationInfo(int nPixel, int nLine)
    1381             : {
    1382             :     int nBlockXSize, nBlockYSize;
    1383           4 :     GetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
    1384             : 
    1385           4 :     const int nBlockXOff = nPixel / nBlockXSize;
    1386           4 :     const int nBlockYOff = nLine / nBlockYSize;
    1387           4 :     const int bottom_yblock =
    1388           4 :         (nRasterYSize - nBlockYOff * nBlockYSize) / nBlockYSize - 1;
    1389             : 
    1390           4 :     const int meta_tile_x =
    1391           4 :         nMetaTileXShift + (nBlockXOff * nBlockXSize) / nQuadSize;
    1392           4 :     const int meta_tile_y =
    1393           4 :         nMetaTileYShift + (bottom_yblock * nBlockYSize) / nQuadSize;
    1394             : 
    1395           8 :     CPLString osQuadURL = osQuadsURL;
    1396           8 :     CPLString osTilename = formatTileName(meta_tile_x, meta_tile_y);
    1397           4 :     osQuadURL += osTilename;
    1398             : 
    1399           4 :     if (meta_tile_x != nLastMetaTileX || meta_tile_y != nLastMetaTileY)
    1400             :     {
    1401           3 :         const CPLString osQuadScenesURL = osQuadURL + "/items";
    1402             : 
    1403           3 :         json_object_put(poLastItemsInformation);
    1404           3 :         poLastItemsInformation = RunRequest(osQuadScenesURL, TRUE);
    1405             : 
    1406           3 :         nLastMetaTileX = meta_tile_x;
    1407           3 :         nLastMetaTileY = meta_tile_y;
    1408             :     }
    1409             : 
    1410           4 :     osLastRetGetLocationInfo.clear();
    1411             : 
    1412           4 :     CPLXMLNode *psRoot = CPLCreateXMLNode(nullptr, CXT_Element, "LocationInfo");
    1413             : 
    1414           4 :     if (poLastItemsInformation)
    1415             :     {
    1416             :         json_object *poItems =
    1417           2 :             CPL_json_object_object_get(poLastItemsInformation, "items");
    1418           4 :         if (poItems && json_object_get_type(poItems) == json_type_array &&
    1419           2 :             json_object_array_length(poItems) != 0)
    1420             :         {
    1421             :             CPLXMLNode *psScenes =
    1422           2 :                 CPLCreateXMLNode(psRoot, CXT_Element, "Scenes");
    1423           2 :             const auto nItemsLength = json_object_array_length(poItems);
    1424           4 :             for (auto i = decltype(nItemsLength){0}; i < nItemsLength; i++)
    1425             :             {
    1426           2 :                 json_object *poObj = json_object_array_get_idx(poItems, i);
    1427           2 :                 if (poObj && json_object_get_type(poObj) == json_type_object)
    1428             :                 {
    1429             :                     json_object *poLink =
    1430           2 :                         CPL_json_object_object_get(poObj, "link");
    1431           2 :                     if (poLink)
    1432             :                     {
    1433             :                         CPLXMLNode *psScene =
    1434           2 :                             CPLCreateXMLNode(psScenes, CXT_Element, "Scene");
    1435             :                         CPLXMLNode *psItem =
    1436           2 :                             CPLCreateXMLNode(psScene, CXT_Element, "link");
    1437           2 :                         CPLCreateXMLNode(psItem, CXT_Text,
    1438             :                                          json_object_get_string(poLink));
    1439             :                     }
    1440             :                 }
    1441             :             }
    1442             :         }
    1443             :     }
    1444             : 
    1445           4 :     char *pszXML = CPLSerializeXMLTree(psRoot);
    1446           4 :     CPLDestroyXMLNode(psRoot);
    1447           4 :     osLastRetGetLocationInfo = pszXML;
    1448           4 :     CPLFree(pszXML);
    1449             : 
    1450           8 :     return osLastRetGetLocationInfo.c_str();
    1451             : }
    1452             : 
    1453             : /************************************************************************/
    1454             : /*                             IRasterIO()                              */
    1455             : /************************************************************************/
    1456             : 
    1457           6 : CPLErr PLMosaicDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
    1458             :                                   int nXSize, int nYSize, void *pData,
    1459             :                                   int nBufXSize, int nBufYSize,
    1460             :                                   GDALDataType eBufType, int nBandCount,
    1461             :                                   BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
    1462             :                                   GSpacing nLineSpace, GSpacing nBandSpace,
    1463             :                                   GDALRasterIOExtraArg *psExtraArg)
    1464             : {
    1465           6 :     if (bUseTMSForMain && !apoTMSDS.empty())
    1466           1 :         return apoTMSDS[0]->RasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
    1467             :                                      pData, nBufXSize, nBufYSize, eBufType,
    1468             :                                      nBandCount, panBandMap, nPixelSpace,
    1469           1 :                                      nLineSpace, nBandSpace, psExtraArg);
    1470             : 
    1471           5 :     return BlockBasedRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
    1472             :                               nBufXSize, nBufYSize, eBufType, nBandCount,
    1473             :                               panBandMap, nPixelSpace, nLineSpace, nBandSpace,
    1474           5 :                               psExtraArg);
    1475             : }
    1476             : 
    1477             : /************************************************************************/
    1478             : /*                      GDALRegister_PLMOSAIC()                         */
    1479             : /************************************************************************/
    1480             : 
    1481        1595 : void GDALRegister_PLMOSAIC()
    1482             : 
    1483             : {
    1484        1595 :     if (GDALGetDriverByName("PLMOSAIC") != nullptr)
    1485         302 :         return;
    1486             : 
    1487        1293 :     GDALDriver *poDriver = new GDALDriver();
    1488             : 
    1489        1293 :     poDriver->SetDescription("PLMOSAIC");
    1490        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1491        1293 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Planet Labs Mosaics API");
    1492        1293 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
    1493        1293 :                               "drivers/raster/plmosaic.html");
    1494             : 
    1495        1293 :     poDriver->SetMetadataItem(GDAL_DMD_CONNECTION_PREFIX, "PLMOSAIC:");
    1496             : 
    1497        1293 :     poDriver->SetMetadataItem(
    1498             :         GDAL_DMD_OPENOPTIONLIST,
    1499             :         "<OpenOptionList>"
    1500             :         "  <Option name='API_KEY' type='string' description='Account API key' "
    1501             :         "required='true'/>"
    1502             :         "  <Option name='MOSAIC' type='string' description='Mosaic name'/>"
    1503             :         "  <Option name='CACHE_PATH' type='string' description='Directory "
    1504             :         "where to put cached quads'/>"
    1505             :         "  <Option name='TRUST_CACHE' type='boolean' description='Whether "
    1506             :         "already cached quads should be trusted as the most recent version' "
    1507             :         "default='NO'/>"
    1508             :         "  <Option name='USE_TILES' type='boolean' description='Whether to use "
    1509             :         "the tile API even for full resolution data (only for Byte mosaics)' "
    1510             :         "default='NO'/>"
    1511        1293 :         "</OpenOptionList>");
    1512             : 
    1513        1293 :     poDriver->pfnIdentify = PLMosaicDataset::Identify;
    1514        1293 :     poDriver->pfnOpen = PLMosaicDataset::Open;
    1515             : 
    1516        1293 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1517             : }

Generated by: LCOV version 1.14