LCOV - code coverage report
Current view: top level - frmts/plmosaic - plmosaicdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 626 659 95.0 %
Date: 2025-09-10 17:48:50 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             : struct PLLinkedDataset
      36             : {
      37             :   public:
      38             :     CPLString osKey{};
      39             :     GDALDataset *poDS{};
      40             :     PLLinkedDataset *psPrev{};
      41             :     PLLinkedDataset *psNext{};
      42             : 
      43          25 :     PLLinkedDataset() = default;
      44             : 
      45             :     CPL_DISALLOW_COPY_ASSIGN(PLLinkedDataset)
      46             : };
      47             : 
      48             : class PLMosaicRasterBand;
      49             : 
      50             : class PLMosaicDataset final : public GDALPamDataset
      51             : {
      52             :     friend class PLMosaicRasterBand;
      53             : 
      54             :     int bMustCleanPersistent{};
      55             :     CPLString osCachePathRoot{};
      56             :     int bTrustCache{};
      57             :     CPLString osBaseURL{};
      58             :     CPLString osAPIKey{};
      59             :     CPLString osMosaic{};
      60             :     OGRSpatialReference m_oSRS{};
      61             :     int nQuadSize{};
      62             :     CPLString osQuadsURL{};
      63             :     int bHasGeoTransform{};
      64             :     GDALGeoTransform m_gt{};
      65             :     int nZoomLevelMax{};
      66             :     int bUseTMSForMain{};
      67             :     std::vector<GDALDataset *> apoTMSDS{};
      68             :     int nMetaTileXShift = 0;
      69             :     int nMetaTileYShift = 0;
      70             :     bool bQuadDownload = false;
      71             : 
      72             :     int nCacheMaxSize{};
      73             :     std::map<CPLString, PLLinkedDataset *> oMapLinkedDatasets{};
      74             :     PLLinkedDataset *psHead{};
      75             :     PLLinkedDataset *psTail{};
      76             :     void FlushDatasetsCache();
      77             :     CPLString GetMosaicCachePath();
      78             :     void CreateMosaicCachePathIfNecessary();
      79             : 
      80             :     int nLastMetaTileX{};
      81             :     int nLastMetaTileY{};
      82             :     json_object *poLastItemsInformation = nullptr;
      83             :     CPLString osLastRetGetLocationInfo{};
      84             :     const char *GetLocationInfo(int nPixel, int nLine);
      85             : 
      86             :     char **GetBaseHTTPOptions();
      87             :     CPLHTTPResult *Download(const char *pszURL, int bQuiet404Error = FALSE);
      88             :     json_object *RunRequest(const char *pszURL, int bQuiet404Error = FALSE);
      89             :     int OpenMosaic();
      90             :     std::vector<CPLString> ListSubdatasets();
      91             : 
      92             :     static CPLString formatTileName(int tile_x, int tile_y);
      93             :     void InsertNewDataset(const CPLString &osKey, GDALDataset *poDS);
      94             :     GDALDataset *OpenAndInsertNewDataset(const CPLString &osTmpFilename,
      95             :                                          const CPLString &osTilename);
      96             : 
      97             :     CPL_DISALLOW_COPY_ASSIGN(PLMosaicDataset)
      98             : 
      99             :   public:
     100             :     PLMosaicDataset();
     101             :     ~PLMosaicDataset() override;
     102             : 
     103             :     static int Identify(GDALOpenInfo *poOpenInfo);
     104             :     static GDALDataset *Open(GDALOpenInfo *);
     105             : 
     106             :     CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize,
     107             :                      int nYSize, void *pData, int nBufXSize, int nBufYSize,
     108             :                      GDALDataType eBufType, int nBandCount,
     109             :                      BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
     110             :                      GSpacing nLineSpace, GSpacing nBandSpace,
     111             :                      GDALRasterIOExtraArg *psExtraArg) override;
     112             : 
     113             :     CPLErr FlushCache(bool bAtClosing) override;
     114             : 
     115             :     const OGRSpatialReference *GetSpatialRef() const override;
     116             :     CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
     117             : 
     118             :     GDALDataset *GetMetaTile(int tile_x, int tile_y);
     119             : };
     120             : 
     121             : /************************************************************************/
     122             : /* ==================================================================== */
     123             : /*                         PLMosaicRasterBand                           */
     124             : /* ==================================================================== */
     125             : /************************************************************************/
     126             : 
     127             : class PLMosaicRasterBand final : public GDALRasterBand
     128             : {
     129             :     friend class PLMosaicDataset;
     130             : 
     131             :   public:
     132             :     PLMosaicRasterBand(PLMosaicDataset *poDS, int nBand,
     133             :                        GDALDataType eDataType);
     134             : 
     135             :     CPLErr IReadBlock(int, int, void *) override;
     136             :     CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize,
     137             :                      int nYSize, void *pData, int nBufXSize, int nBufYSize,
     138             :                      GDALDataType eBufType, GSpacing nPixelSpace,
     139             :                      GSpacing nLineSpace,
     140             :                      GDALRasterIOExtraArg *psExtraArg) override;
     141             : 
     142             :     virtual const char *GetMetadataItem(const char *pszName,
     143             :                                         const char *pszDomain = "") override;
     144             : 
     145             :     GDALColorInterp GetColorInterpretation() override;
     146             : 
     147             :     int GetOverviewCount() override;
     148             :     GDALRasterBand *GetOverview(int iOvrLevel) override;
     149             : };
     150             : 
     151             : /************************************************************************/
     152             : /*                        PLMosaicRasterBand()                          */
     153             : /************************************************************************/
     154             : 
     155          48 : PLMosaicRasterBand::PLMosaicRasterBand(PLMosaicDataset *poDSIn, int nBandIn,
     156          48 :                                        GDALDataType eDataTypeIn)
     157             : 
     158             : {
     159          48 :     eDataType = eDataTypeIn;
     160          48 :     nBlockXSize = 256;
     161          48 :     nBlockYSize = 256;
     162             : 
     163          48 :     poDS = poDSIn;
     164          48 :     nBand = nBandIn;
     165             : 
     166          48 :     if (eDataType == GDT_UInt16)
     167             :     {
     168           4 :         if (nBand <= 3)
     169           3 :             SetMetadataItem("NBITS", "12", "IMAGE_STRUCTURE");
     170             :     }
     171          48 : }
     172             : 
     173             : /************************************************************************/
     174             : /*                             IReadBlock()                             */
     175             : /************************************************************************/
     176             : 
     177          63 : CPLErr PLMosaicRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
     178             :                                       void *pImage)
     179             : {
     180          63 :     PLMosaicDataset *poMOSDS = cpl::down_cast<PLMosaicDataset *>(poDS);
     181             : 
     182             : #ifdef DEBUG_VERBOSE
     183             :     CPLDebug("PLMOSAIC", "IReadBlock(band=%d, x=%d, y=%d)", nBand, nBlockYOff,
     184             :              nBlockYOff);
     185             : #endif
     186             : 
     187          63 :     if (poMOSDS->bUseTMSForMain && !poMOSDS->apoTMSDS.empty())
     188           1 :         return poMOSDS->apoTMSDS[0]->GetRasterBand(nBand)->ReadBlock(
     189           1 :             nBlockXOff, nBlockYOff, pImage);
     190             : 
     191          62 :     const int bottom_yblock =
     192          62 :         (nRasterYSize - nBlockYOff * nBlockYSize) / nBlockYSize - 1;
     193             : 
     194          62 :     const int meta_tile_x = poMOSDS->nMetaTileXShift +
     195          62 :                             (nBlockXOff * nBlockXSize) / poMOSDS->nQuadSize;
     196          62 :     const int meta_tile_y = poMOSDS->nMetaTileYShift +
     197          62 :                             (bottom_yblock * nBlockYSize) / poMOSDS->nQuadSize;
     198          62 :     const int sub_tile_x = nBlockXOff % (poMOSDS->nQuadSize / nBlockXSize);
     199          62 :     const int sub_tile_y = nBlockYOff % (poMOSDS->nQuadSize / nBlockYSize);
     200             : 
     201          62 :     GDALDataset *poMetaTileDS = poMOSDS->GetMetaTile(meta_tile_x, meta_tile_y);
     202          62 :     if (poMetaTileDS == nullptr)
     203             :     {
     204          38 :         memset(pImage, 0,
     205          38 :                static_cast<size_t>(nBlockXSize) * nBlockYSize *
     206          38 :                    GDALGetDataTypeSizeBytes(eDataType));
     207          38 :         return CE_None;
     208             :     }
     209             : 
     210          24 :     return poMetaTileDS->GetRasterBand(nBand)->RasterIO(
     211          24 :         GF_Read, sub_tile_x * nBlockXSize, sub_tile_y * nBlockYSize,
     212             :         nBlockXSize, nBlockYSize, pImage, nBlockXSize, nBlockYSize, eDataType,
     213          24 :         0, 0, nullptr);
     214             : }
     215             : 
     216             : /************************************************************************/
     217             : /*                             IRasterIO()                              */
     218             : /************************************************************************/
     219             : 
     220          63 : CPLErr PLMosaicRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
     221             :                                      int nXSize, int nYSize, void *pData,
     222             :                                      int nBufXSize, int nBufYSize,
     223             :                                      GDALDataType eBufType,
     224             :                                      GSpacing nPixelSpace, GSpacing nLineSpace,
     225             :                                      GDALRasterIOExtraArg *psExtraArg)
     226             : {
     227          63 :     PLMosaicDataset *poMOSDS = cpl::down_cast<PLMosaicDataset *>(poDS);
     228          63 :     if (poMOSDS->bUseTMSForMain && !poMOSDS->apoTMSDS.empty())
     229           1 :         return poMOSDS->apoTMSDS[0]->GetRasterBand(nBand)->RasterIO(
     230             :             eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
     231           1 :             eBufType, nPixelSpace, nLineSpace, psExtraArg);
     232             : 
     233          62 :     return GDALRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
     234             :                                      pData, nBufXSize, nBufYSize, eBufType,
     235          62 :                                      nPixelSpace, nLineSpace, psExtraArg);
     236             : }
     237             : 
     238             : /************************************************************************/
     239             : /*                         GetMetadataItem()                            */
     240             : /************************************************************************/
     241             : 
     242           4 : const char *PLMosaicRasterBand::GetMetadataItem(const char *pszName,
     243             :                                                 const char *pszDomain)
     244             : {
     245           4 :     PLMosaicDataset *poMOSDS = cpl::down_cast<PLMosaicDataset *>(poDS);
     246             :     int nPixel, nLine;
     247           4 :     if (poMOSDS->bQuadDownload && pszName != nullptr && pszDomain != nullptr &&
     248          12 :         EQUAL(pszDomain, "LocationInfo") &&
     249           4 :         sscanf(pszName, "Pixel_%d_%d", &nPixel, &nLine) == 2)
     250             :     {
     251           4 :         return poMOSDS->GetLocationInfo(nPixel, nLine);
     252             :     }
     253             : 
     254           0 :     return GDALRasterBand::GetMetadataItem(pszName, pszDomain);
     255             : }
     256             : 
     257             : /************************************************************************/
     258             : /*                         GetOverviewCount()                           */
     259             : /************************************************************************/
     260             : 
     261           3 : int PLMosaicRasterBand::GetOverviewCount()
     262             : {
     263           3 :     PLMosaicDataset *poGDS = cpl::down_cast<PLMosaicDataset *>(poDS);
     264           3 :     return std::max(0, static_cast<int>(poGDS->apoTMSDS.size()) - 1);
     265             : }
     266             : 
     267             : /************************************************************************/
     268             : /*                            GetOverview()                             */
     269             : /************************************************************************/
     270             : 
     271           4 : GDALRasterBand *PLMosaicRasterBand::GetOverview(int iOvrLevel)
     272             : {
     273           4 :     PLMosaicDataset *poGDS = cpl::down_cast<PLMosaicDataset *>(poDS);
     274           7 :     if (iOvrLevel < 0 ||
     275           3 :         iOvrLevel >= static_cast<int>(poGDS->apoTMSDS.size()) - 1)
     276           3 :         return nullptr;
     277             : 
     278           1 :     poGDS->CreateMosaicCachePathIfNecessary();
     279             : 
     280           1 :     return poGDS->apoTMSDS[iOvrLevel + 1]->GetRasterBand(nBand);
     281             : }
     282             : 
     283             : /************************************************************************/
     284             : /*                       GetColorInterpretation()                       */
     285             : /************************************************************************/
     286             : 
     287           0 : GDALColorInterp PLMosaicRasterBand::GetColorInterpretation()
     288             : {
     289           0 :     switch (nBand)
     290             :     {
     291           0 :         case 1:
     292           0 :             return GCI_RedBand;
     293           0 :         case 2:
     294           0 :             return GCI_GreenBand;
     295           0 :         case 3:
     296           0 :             return GCI_BlueBand;
     297           0 :         case 4:
     298           0 :             return GCI_AlphaBand;
     299           0 :         default:
     300           0 :             CPLAssert(false);
     301             :             return GCI_GrayIndex;
     302             :     }
     303             : }
     304             : 
     305             : /************************************************************************/
     306             : /* ==================================================================== */
     307             : /*                           PLMosaicDataset                            */
     308             : /* ==================================================================== */
     309             : /************************************************************************/
     310             : 
     311             : /************************************************************************/
     312             : /*                        PLMosaicDataset()                            */
     313             : /************************************************************************/
     314             : 
     315          30 : PLMosaicDataset::PLMosaicDataset()
     316             :     : bMustCleanPersistent(FALSE), bTrustCache(FALSE), nQuadSize(0),
     317             :       bHasGeoTransform(FALSE), nZoomLevelMax(0), bUseTMSForMain(FALSE),
     318             :       nCacheMaxSize(10), psHead(nullptr), psTail(nullptr), nLastMetaTileX(-1),
     319          30 :       nLastMetaTileY(-1)
     320             : {
     321          30 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     322             : 
     323          30 :     SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
     324          30 :     osCachePathRoot = CPLGetPathSafe(CPLGenerateTempFilenameSafe("").c_str());
     325          30 : }
     326             : 
     327             : /************************************************************************/
     328             : /*                         ~PLMosaicDataset()                           */
     329             : /************************************************************************/
     330             : 
     331          60 : PLMosaicDataset::~PLMosaicDataset()
     332             : 
     333             : {
     334          30 :     PLMosaicDataset::FlushCache(true);
     335         170 :     for (auto &poDS : apoTMSDS)
     336         140 :         delete poDS;
     337          30 :     if (poLastItemsInformation)
     338           0 :         json_object_put(poLastItemsInformation);
     339          30 :     if (bMustCleanPersistent)
     340             :     {
     341          28 :         char **papszOptions = CSLSetNameValue(nullptr, "CLOSE_PERSISTENT",
     342             :                                               CPLSPrintf("PLMOSAIC:%p", this));
     343          28 :         CPLHTTPDestroyResult(CPLHTTPFetch(osBaseURL, papszOptions));
     344          28 :         CSLDestroy(papszOptions);
     345             :     }
     346          60 : }
     347             : 
     348             : /************************************************************************/
     349             : /*                      FlushDatasetsCache()                            */
     350             : /************************************************************************/
     351             : 
     352          36 : void PLMosaicDataset::FlushDatasetsCache()
     353             : {
     354          55 :     for (PLLinkedDataset *psIter = psHead; psIter != nullptr;)
     355             :     {
     356          19 :         PLLinkedDataset *psNext = psIter->psNext;
     357          19 :         if (psIter->poDS)
     358           7 :             GDALClose(psIter->poDS);
     359          19 :         delete psIter;
     360          19 :         psIter = psNext;
     361             :     }
     362          36 :     psHead = nullptr;
     363          36 :     psTail = nullptr;
     364          36 :     oMapLinkedDatasets.clear();
     365          36 : }
     366             : 
     367             : /************************************************************************/
     368             : /*                            FlushCache()                              */
     369             : /************************************************************************/
     370             : 
     371          35 : CPLErr PLMosaicDataset::FlushCache(bool bAtClosing)
     372             : {
     373          35 :     FlushDatasetsCache();
     374             : 
     375          35 :     nLastMetaTileX = -1;
     376          35 :     nLastMetaTileY = -1;
     377          35 :     if (poLastItemsInformation)
     378           2 :         json_object_put(poLastItemsInformation);
     379          35 :     poLastItemsInformation = nullptr;
     380          35 :     osLastRetGetLocationInfo.clear();
     381             : 
     382          35 :     return GDALDataset::FlushCache(bAtClosing);
     383             : }
     384             : 
     385             : /************************************************************************/
     386             : /*                            Identify()                                */
     387             : /************************************************************************/
     388             : 
     389       58961 : int PLMosaicDataset::Identify(GDALOpenInfo *poOpenInfo)
     390             : 
     391             : {
     392       58961 :     return STARTS_WITH_CI(poOpenInfo->pszFilename, "PLMOSAIC:");
     393             : }
     394             : 
     395             : /************************************************************************/
     396             : /*                          GetBaseHTTPOptions()                         */
     397             : /************************************************************************/
     398             : 
     399          56 : char **PLMosaicDataset::GetBaseHTTPOptions()
     400             : {
     401          56 :     bMustCleanPersistent = TRUE;
     402             : 
     403             :     char **papszOptions =
     404          56 :         CSLAddString(nullptr, CPLSPrintf("PERSISTENT=PLMOSAIC:%p", this));
     405             : 
     406             :     /* Ensure the PLMosaic driver uses a unique default user agent to help
     407             :      * identify usage. */
     408          56 :     CPLString osUserAgent = CPLGetConfigOption("GDAL_HTTP_USERAGENT", "");
     409          56 :     if (osUserAgent.empty())
     410          56 :         papszOptions = CSLAddString(
     411             :             papszOptions, CPLSPrintf("USERAGENT=PLMosaic Driver GDAL/%d.%d.%d",
     412             :                                      GDAL_VERSION_MAJOR, GDAL_VERSION_MINOR,
     413             :                                      GDAL_VERSION_REV));
     414             : 
     415             :     /* Use basic auth, rather than Authorization headers since curl would
     416             :      * forward it to S3 */
     417             :     papszOptions =
     418          56 :         CSLAddString(papszOptions, CPLSPrintf("USERPWD=%s:", osAPIKey.c_str()));
     419             : 
     420         112 :     return papszOptions;
     421             : }
     422             : 
     423             : /************************************************************************/
     424             : /*                               Download()                             */
     425             : /************************************************************************/
     426             : 
     427          56 : CPLHTTPResult *PLMosaicDataset::Download(const char *pszURL, int bQuiet404Error)
     428             : {
     429          56 :     char **papszOptions = CSLAddString(GetBaseHTTPOptions(), nullptr);
     430          56 :     CPLHTTPResult *psResult = nullptr;
     431          56 :     if (STARTS_WITH(osBaseURL, "/vsimem/") && STARTS_WITH(pszURL, "/vsimem/"))
     432             :     {
     433          18 :         CPLDebug("PLSCENES", "Fetching %s", pszURL);
     434             :         psResult = reinterpret_cast<CPLHTTPResult *>(
     435          18 :             CPLCalloc(1, sizeof(CPLHTTPResult)));
     436          18 :         vsi_l_offset nDataLength = 0;
     437          36 :         CPLString osURL(pszURL);
     438          18 :         if (osURL.back() == '/')
     439           1 :             osURL.pop_back();
     440          18 :         GByte *pabyBuf = VSIGetMemFileBuffer(osURL, &nDataLength, FALSE);
     441          18 :         if (pabyBuf)
     442             :         {
     443          16 :             psResult->pabyData = reinterpret_cast<GByte *>(
     444          16 :                 VSIMalloc(1 + static_cast<size_t>(nDataLength)));
     445          16 :             if (psResult->pabyData)
     446             :             {
     447          16 :                 memcpy(psResult->pabyData, pabyBuf,
     448             :                        static_cast<size_t>(nDataLength));
     449          16 :                 psResult->pabyData[nDataLength] = 0;
     450          16 :                 psResult->nDataLen = static_cast<int>(nDataLength);
     451             :             }
     452             :         }
     453             :         else
     454             :         {
     455           2 :             psResult->pszErrBuf =
     456           2 :                 CPLStrdup(CPLSPrintf("Error 404. Cannot find %s", pszURL));
     457             :         }
     458             :     }
     459             :     else
     460             :     {
     461          38 :         if (bQuiet404Error)
     462          25 :             CPLPushErrorHandler(CPLQuietErrorHandler);
     463          38 :         psResult = CPLHTTPFetch(pszURL, papszOptions);
     464          38 :         if (bQuiet404Error)
     465          25 :             CPLPopErrorHandler();
     466             :     }
     467          56 :     CSLDestroy(papszOptions);
     468             : 
     469          56 :     if (psResult->pszErrBuf != nullptr)
     470             :     {
     471          19 :         if (!(bQuiet404Error && strstr(psResult->pszErrBuf, "404")))
     472             :         {
     473           2 :             CPLError(CE_Failure, CPLE_AppDefined, "%s",
     474           2 :                      psResult->pabyData
     475             :                          ? reinterpret_cast<const char *>(psResult->pabyData)
     476             :                          : psResult->pszErrBuf);
     477             :         }
     478          19 :         CPLHTTPDestroyResult(psResult);
     479          19 :         return nullptr;
     480             :     }
     481             : 
     482          37 :     if (psResult->pabyData == nullptr)
     483             :     {
     484           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     485             :                  "Empty content returned by server");
     486           0 :         CPLHTTPDestroyResult(psResult);
     487           0 :         return nullptr;
     488             :     }
     489             : 
     490          37 :     return psResult;
     491             : }
     492             : 
     493             : /************************************************************************/
     494             : /*                               RunRequest()                           */
     495             : /************************************************************************/
     496             : 
     497          32 : json_object *PLMosaicDataset::RunRequest(const char *pszURL, int bQuiet404Error)
     498             : {
     499          32 :     CPLHTTPResult *psResult = Download(pszURL, bQuiet404Error);
     500          32 :     if (psResult == nullptr)
     501             :     {
     502           3 :         return nullptr;
     503             :     }
     504             : 
     505          29 :     json_object *poObj = nullptr;
     506          29 :     const char *pszText = reinterpret_cast<const char *>(psResult->pabyData);
     507          29 :     if (!OGRJSonParse(pszText, &poObj, true))
     508             :     {
     509           3 :         CPLHTTPDestroyResult(psResult);
     510           3 :         return nullptr;
     511             :     }
     512             : 
     513          26 :     CPLHTTPDestroyResult(psResult);
     514             : 
     515          26 :     if (json_object_get_type(poObj) != json_type_object)
     516             :     {
     517           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     518             :                  "Return is not a JSON dictionary");
     519           0 :         json_object_put(poObj);
     520           0 :         poObj = nullptr;
     521             :     }
     522             : 
     523          26 :     return poObj;
     524             : }
     525             : 
     526             : /************************************************************************/
     527             : /*                           PLMosaicGetParameter()                     */
     528             : /************************************************************************/
     529             : 
     530         141 : static CPLString PLMosaicGetParameter(GDALOpenInfo *poOpenInfo,
     531             :                                       char **papszOptions, const char *pszName,
     532             :                                       const char *pszDefaultVal)
     533             : {
     534             :     return CSLFetchNameValueDef(
     535             :         papszOptions, pszName,
     536         141 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, pszName,
     537         141 :                              pszDefaultVal));
     538             : }
     539             : 
     540             : /************************************************************************/
     541             : /*                                Open()                                */
     542             : /************************************************************************/
     543             : 
     544          30 : GDALDataset *PLMosaicDataset::Open(GDALOpenInfo *poOpenInfo)
     545             : 
     546             : {
     547          30 :     if (!Identify(poOpenInfo))
     548           0 :         return nullptr;
     549             : 
     550          30 :     PLMosaicDataset *poDS = new PLMosaicDataset();
     551             : 
     552             :     poDS->osBaseURL = CPLGetConfigOption(
     553          30 :         "PL_URL", "https://api.planet.com/basemaps/v1/mosaics");
     554             : 
     555          60 :     char **papszOptions = CSLTokenizeStringComplex(
     556          30 :         poOpenInfo->pszFilename + strlen("PLMosaic:"), ",", TRUE, FALSE);
     557          37 :     for (char **papszIter = papszOptions; papszIter && *papszIter; papszIter++)
     558             :     {
     559           8 :         char *pszKey = nullptr;
     560           8 :         const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
     561           8 :         if (pszValue != nullptr)
     562             :         {
     563           8 :             if (!EQUAL(pszKey, "api_key") && !EQUAL(pszKey, "mosaic") &&
     564           3 :                 !EQUAL(pszKey, "cache_path") && !EQUAL(pszKey, "trust_cache") &&
     565           1 :                 !EQUAL(pszKey, "use_tiles"))
     566             :             {
     567           1 :                 CPLError(CE_Failure, CPLE_NotSupported, "Unsupported option %s",
     568             :                          pszKey);
     569           1 :                 CPLFree(pszKey);
     570           1 :                 delete poDS;
     571           1 :                 CSLDestroy(papszOptions);
     572           1 :                 return nullptr;
     573             :             }
     574           7 :             CPLFree(pszKey);
     575             :         }
     576             :     }
     577             : 
     578          58 :     poDS->osAPIKey = PLMosaicGetParameter(poOpenInfo, papszOptions, "api_key",
     579          29 :                                           CPLGetConfigOption("PL_API_KEY", ""));
     580             : 
     581          29 :     if (poDS->osAPIKey.empty())
     582             :     {
     583           1 :         CPLError(
     584             :             CE_Failure, CPLE_AppDefined,
     585             :             "Missing PL_API_KEY configuration option or API_KEY open option");
     586           1 :         delete poDS;
     587           1 :         CSLDestroy(papszOptions);
     588           1 :         return nullptr;
     589             :     }
     590             : 
     591             :     poDS->osMosaic =
     592          28 :         PLMosaicGetParameter(poOpenInfo, papszOptions, "mosaic", "");
     593             : 
     594             :     poDS->osCachePathRoot =
     595          56 :         PLMosaicGetParameter(poOpenInfo, papszOptions, "cache_path",
     596          28 :                              CPLGetConfigOption("PL_CACHE_PATH", ""));
     597             : 
     598          28 :     poDS->bTrustCache = CPLTestBool(
     599          56 :         PLMosaicGetParameter(poOpenInfo, papszOptions, "trust_cache", "FALSE"));
     600             : 
     601          28 :     poDS->bUseTMSForMain = CPLTestBool(
     602          56 :         PLMosaicGetParameter(poOpenInfo, papszOptions, "use_tiles", "FALSE"));
     603             : 
     604          28 :     CSLDestroy(papszOptions);
     605          28 :     papszOptions = nullptr;
     606             : 
     607          28 :     if (!poDS->osMosaic.empty())
     608             :     {
     609          20 :         if (!poDS->OpenMosaic())
     610             :         {
     611           8 :             delete poDS;
     612           8 :             poDS = nullptr;
     613             :         }
     614             :     }
     615             :     else
     616             :     {
     617          16 :         auto aosNameList = poDS->ListSubdatasets();
     618           8 :         if (aosNameList.empty())
     619             :         {
     620           5 :             delete poDS;
     621           5 :             poDS = nullptr;
     622             :         }
     623           3 :         else if (aosNameList.size() == 1)
     624             :         {
     625           4 :             const CPLString osOldFilename(poOpenInfo->pszFilename);
     626             :             const CPLString osMosaicConnectionString =
     627           4 :                 CPLSPrintf("PLMOSAIC:mosaic=%s", aosNameList[0].c_str());
     628           2 :             delete poDS;
     629             :             GDALOpenInfo oOpenInfo(osMosaicConnectionString.c_str(),
     630           4 :                                    GA_ReadOnly);
     631           2 :             oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
     632           2 :             poDS = cpl::down_cast<PLMosaicDataset *>(Open(&oOpenInfo));
     633           2 :             if (poDS)
     634           2 :                 poDS->SetDescription(osOldFilename);
     635             :         }
     636             :         else
     637             :         {
     638           2 :             CPLStringList aosSubdatasets;
     639           3 :             for (const auto &osName : aosNameList)
     640             :             {
     641           2 :                 const int nDatasetIdx = aosSubdatasets.Count() / 2 + 1;
     642             :                 aosSubdatasets.AddNameValue(
     643             :                     CPLSPrintf("SUBDATASET_%d_NAME", nDatasetIdx),
     644           2 :                     CPLSPrintf("PLMOSAIC:mosaic=%s", osName.c_str()));
     645             :                 aosSubdatasets.AddNameValue(
     646             :                     CPLSPrintf("SUBDATASET_%d_DESC", nDatasetIdx),
     647           2 :                     CPLSPrintf("Mosaic %s", osName.c_str()));
     648             :             }
     649           1 :             poDS->SetMetadata(aosSubdatasets.List(), "SUBDATASETS");
     650             :         }
     651             :     }
     652             : 
     653          28 :     if (poDS)
     654          15 :         poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
     655             : 
     656          28 :     return poDS;
     657             : }
     658             : 
     659             : /************************************************************************/
     660             : /*                           ReplaceSubString()                         */
     661             : /************************************************************************/
     662             : 
     663          36 : static void ReplaceSubString(CPLString &osTarget, CPLString osPattern,
     664             :                              CPLString osReplacement)
     665             : 
     666             : {
     667             :     // Assumes only one occurrence of osPattern.
     668          36 :     size_t pos = osTarget.find(osPattern);
     669          36 :     if (pos == CPLString::npos)
     670           0 :         return;
     671             : 
     672          36 :     osTarget.replace(pos, osPattern.size(), osReplacement);
     673             : }
     674             : 
     675             : /************************************************************************/
     676             : /*                            GetMosaicCachePath()                      */
     677             : /************************************************************************/
     678             : 
     679          32 : CPLString PLMosaicDataset::GetMosaicCachePath()
     680             : {
     681          32 :     if (!osCachePathRoot.empty())
     682             :     {
     683             :         const CPLString osCachePath(
     684          31 :             CPLFormFilenameSafe(osCachePathRoot, "plmosaic_cache", nullptr));
     685          62 :         return CPLFormFilenameSafe(osCachePath, osMosaic, nullptr);
     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 :             CPLFormFilenameSafe(osCachePathRoot, "plmosaic_cache", nullptr));
     700             :         const CPLString osMosaicPath(
     701          16 :             CPLFormFilenameSafe(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 :         m_gt[0] = GM_ORIGIN;
     861          12 :         m_gt[1] = dfResolution;
     862          12 :         m_gt[2] = 0;
     863          12 :         m_gt[3] = -GM_ORIGIN;
     864          12 :         m_gt[4] = 0;
     865          12 :         m_gt[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 :             m_gt[0] = xmin;
     894           1 :             m_gt[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>(0.5 + (m_gt[0] - GM_ORIGIN) /
     987             :                                                           dfThisResolution);
     988         140 :                     nDstXOff = 0;
     989             : 
     990         140 :                     nSrcYOff = static_cast<int>(0.5 + (-GM_ORIGIN - m_gt[3]) /
     991             :                                                           dfThisResolution);
     992         140 :                     nDstYOff = 0;
     993             : 
     994         700 :                     for (int iBand = 1; iBand <= 4; iBand++)
     995             :                     {
     996             :                         VRTSourcedRasterBandH hVRTBand =
     997         560 :                             reinterpret_cast<VRTSourcedRasterBandH>(
     998             :                                 GDALGetRasterBand(hVRTDS, iBand));
     999         560 :                         VRTAddSimpleSource(
    1000             :                             hVRTBand, GDALGetRasterBand(poTMSDS, iBand),
    1001             :                             nSrcXOff, nSrcYOff, nOvrXSize, nOvrYSize, nDstXOff,
    1002             :                             nDstYOff, nOvrXSize, nOvrYSize, "NEAR",
    1003             :                             VRT_NODATA_UNSET);
    1004             :                     }
    1005         140 :                     poTMSDS->Dereference();
    1006             : 
    1007         140 :                     apoTMSDS.push_back(GDALDataset::FromHandle(hVRTDS));
    1008             :                 }
    1009             : 
    1010         140 :                 if (nOvrXSize < 256 && nOvrYSize < 256)
    1011           1 :                     break;
    1012             :             }
    1013             :         }
    1014             :     }
    1015             : 
    1016          12 :     if (bUseTMSForMain && apoTMSDS.empty())
    1017             :     {
    1018           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    1019             :                  "Cannot find tile definition, so use_tiles will be ignored");
    1020           1 :         bUseTMSForMain = FALSE;
    1021             :     }
    1022             : 
    1023          60 :     for (int i = 0; i < 4; i++)
    1024          48 :         SetBand(i + 1, new PLMosaicRasterBand(this, i + 1, eDT));
    1025             : 
    1026             :     json_object *poFirstAcquired =
    1027          12 :         CPL_json_object_object_get(poMosaic, "first_acquired");
    1028          24 :     if (poFirstAcquired != nullptr &&
    1029          12 :         json_object_get_type(poFirstAcquired) == json_type_string)
    1030             :     {
    1031          12 :         SetMetadataItem("FIRST_ACQUIRED",
    1032          12 :                         json_object_get_string(poFirstAcquired));
    1033             :     }
    1034             :     json_object *poLastAcquired =
    1035          12 :         CPL_json_object_object_get(poMosaic, "last_acquired");
    1036          24 :     if (poLastAcquired != nullptr &&
    1037          12 :         json_object_get_type(poLastAcquired) == json_type_string)
    1038             :     {
    1039          12 :         SetMetadataItem("LAST_ACQUIRED",
    1040          12 :                         json_object_get_string(poLastAcquired));
    1041             :     }
    1042          12 :     json_object *poName = CPL_json_object_object_get(poMosaic, "name");
    1043          12 :     if (poName != nullptr && json_object_get_type(poName) == json_type_string)
    1044             :     {
    1045          12 :         SetMetadataItem("NAME", json_object_get_string(poName));
    1046             :     }
    1047             : 
    1048          12 :     json_object_put(poObj);
    1049          12 :     return TRUE;
    1050             : }
    1051             : 
    1052             : /************************************************************************/
    1053             : /*                          ListSubdatasets()                           */
    1054             : /************************************************************************/
    1055             : 
    1056           8 : std::vector<CPLString> PLMosaicDataset::ListSubdatasets()
    1057             : {
    1058           8 :     std::vector<CPLString> aosNameList;
    1059          16 :     CPLString osURL(osBaseURL);
    1060          13 :     while (osURL.size())
    1061             :     {
    1062           9 :         json_object *poObj = RunRequest(osURL);
    1063           9 :         if (poObj == nullptr)
    1064             :         {
    1065           3 :             return aosNameList;
    1066             :         }
    1067             : 
    1068           6 :         osURL = "";
    1069           6 :         json_object *poLinks = CPL_json_object_object_get(poObj, "_links");
    1070           8 :         if (poLinks != nullptr &&
    1071           2 :             json_object_get_type(poLinks) == json_type_object)
    1072             :         {
    1073           2 :             json_object *poNext = CPL_json_object_object_get(poLinks, "_next");
    1074           3 :             if (poNext != nullptr &&
    1075           1 :                 json_object_get_type(poNext) == json_type_string)
    1076             :             {
    1077           1 :                 osURL = json_object_get_string(poNext);
    1078             :             }
    1079             :         }
    1080             : 
    1081           6 :         json_object *poMosaics = CPL_json_object_object_get(poObj, "mosaics");
    1082          11 :         if (poMosaics == nullptr ||
    1083           5 :             json_object_get_type(poMosaics) != json_type_array)
    1084             :         {
    1085           1 :             json_object_put(poObj);
    1086           1 :             return aosNameList;
    1087             :         }
    1088             : 
    1089           5 :         const auto nMosaics = json_object_array_length(poMosaics);
    1090          10 :         for (auto i = decltype(nMosaics){0}; i < nMosaics; i++)
    1091             :         {
    1092           5 :             const char *pszName = nullptr;
    1093           5 :             const char *pszCoordinateSystem = nullptr;
    1094           5 :             json_object *poMosaic = json_object_array_get_idx(poMosaics, i);
    1095           5 :             bool bAccessible = false;
    1096           5 :             if (poMosaic && json_object_get_type(poMosaic) == json_type_object)
    1097             :             {
    1098             :                 json_object *poName =
    1099           5 :                     CPL_json_object_object_get(poMosaic, "name");
    1100          10 :                 if (poName != nullptr &&
    1101           5 :                     json_object_get_type(poName) == json_type_string)
    1102             :                 {
    1103           5 :                     pszName = json_object_get_string(poName);
    1104             :                 }
    1105             : 
    1106             :                 json_object *poCoordinateSystem =
    1107           5 :                     CPL_json_object_object_get(poMosaic, "coordinate_system");
    1108          10 :                 if (poCoordinateSystem &&
    1109           5 :                     json_object_get_type(poCoordinateSystem) ==
    1110             :                         json_type_string)
    1111             :                 {
    1112             :                     pszCoordinateSystem =
    1113           5 :                         json_object_get_string(poCoordinateSystem);
    1114             :                 }
    1115             : 
    1116             :                 json_object *poDataType =
    1117           5 :                     CPL_json_object_object_get(poMosaic, "datatype");
    1118           5 :                 if (poDataType &&
    1119           0 :                     json_object_get_type(poDataType) == json_type_string &&
    1120           5 :                     EQUAL(json_object_get_string(poDataType), "byte") &&
    1121           0 :                     !CSLTestBoolean(CPLGetConfigOption(
    1122             :                         "PL_MOSAIC_LIST_QUAD_DOWNLOAD_ONLY", "NO")))
    1123             :                 {
    1124           0 :                     bAccessible = true;  // through tile API
    1125             :                 }
    1126             :                 else
    1127             :                 {
    1128             :                     json_object *poQuadDownload =
    1129           5 :                         CPL_json_object_object_get(poMosaic, "quad_download");
    1130             :                     bAccessible =
    1131           5 :                         CPL_TO_BOOL(json_object_get_boolean(poQuadDownload));
    1132             :                 }
    1133             :             }
    1134             : 
    1135           5 :             if (bAccessible && pszName && pszCoordinateSystem &&
    1136           5 :                 EQUAL(pszCoordinateSystem, "EPSG:3857"))
    1137             :             {
    1138           4 :                 aosNameList.push_back(pszName);
    1139             :             }
    1140             :         }
    1141             : 
    1142           5 :         json_object_put(poObj);
    1143             :     }
    1144           4 :     return aosNameList;
    1145             : }
    1146             : 
    1147             : /************************************************************************/
    1148             : /*                            GetSpatialRef()                           */
    1149             : /************************************************************************/
    1150             : 
    1151           1 : const OGRSpatialReference *PLMosaicDataset::GetSpatialRef() const
    1152             : 
    1153             : {
    1154           1 :     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    1155             : }
    1156             : 
    1157             : /************************************************************************/
    1158             : /*                            GetGeoTransform()                         */
    1159             : /************************************************************************/
    1160             : 
    1161           2 : CPLErr PLMosaicDataset::GetGeoTransform(GDALGeoTransform &gt) const
    1162             : {
    1163           2 :     gt = m_gt;
    1164           2 :     return (bHasGeoTransform) ? CE_None : CE_Failure;
    1165             : }
    1166             : 
    1167             : /************************************************************************/
    1168             : /*                          formatTileName()                            */
    1169             : /************************************************************************/
    1170             : 
    1171          66 : CPLString PLMosaicDataset::formatTileName(int tile_x, int tile_y)
    1172             : 
    1173             : {
    1174          66 :     return CPLSPrintf("%d-%d", tile_x, tile_y);
    1175             : }
    1176             : 
    1177             : /************************************************************************/
    1178             : /*                          InsertNewDataset()                          */
    1179             : /************************************************************************/
    1180             : 
    1181          25 : void PLMosaicDataset::InsertNewDataset(const CPLString &osKey,
    1182             :                                        GDALDataset *poDS)
    1183             : {
    1184          25 :     if (static_cast<int>(oMapLinkedDatasets.size()) == nCacheMaxSize)
    1185             :     {
    1186           6 :         CPLDebug("PLMOSAIC", "Discarding older entry %s from cache",
    1187           6 :                  psTail->osKey.c_str());
    1188           6 :         oMapLinkedDatasets.erase(psTail->osKey);
    1189           6 :         PLLinkedDataset *psNewTail = psTail->psPrev;
    1190           6 :         psNewTail->psNext = nullptr;
    1191           6 :         if (psTail->poDS)
    1192           0 :             GDALClose(psTail->poDS);
    1193           6 :         delete psTail;
    1194           6 :         psTail = psNewTail;
    1195             :     }
    1196             : 
    1197          25 :     PLLinkedDataset *psLinkedDataset = new PLLinkedDataset();
    1198          25 :     if (psHead)
    1199          15 :         psHead->psPrev = psLinkedDataset;
    1200          25 :     psLinkedDataset->osKey = osKey;
    1201          25 :     psLinkedDataset->psNext = psHead;
    1202          25 :     psLinkedDataset->poDS = poDS;
    1203          25 :     psHead = psLinkedDataset;
    1204          25 :     if (psTail == nullptr)
    1205          10 :         psTail = psHead;
    1206          25 :     oMapLinkedDatasets[osKey] = psLinkedDataset;
    1207          25 : }
    1208             : 
    1209             : /************************************************************************/
    1210             : /*                         OpenAndInsertNewDataset()                    */
    1211             : /************************************************************************/
    1212             : 
    1213             : GDALDataset *
    1214           9 : PLMosaicDataset::OpenAndInsertNewDataset(const CPLString &osTmpFilename,
    1215             :                                          const CPLString &osTilename)
    1216             : {
    1217           9 :     const char *const apszAllowedDrivers[2] = {"GTiff", nullptr};
    1218           9 :     GDALDataset *poDS = GDALDataset::FromHandle(
    1219             :         GDALOpenEx(osTmpFilename, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
    1220             :                    apszAllowedDrivers, nullptr, nullptr));
    1221           9 :     if (poDS != nullptr)
    1222             :     {
    1223           7 :         if (poDS->GetRasterXSize() != nQuadSize ||
    1224           7 :             poDS->GetRasterYSize() != nQuadSize || poDS->GetRasterCount() != 4)
    1225             :         {
    1226           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1227             :                      "Inconsistent metatile characteristics");
    1228           0 :             GDALClose(poDS);
    1229           0 :             poDS = nullptr;
    1230             :         }
    1231             :     }
    1232             :     else
    1233             :     {
    1234           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid GTiff dataset: %s",
    1235             :                  osTilename.c_str());
    1236             :     }
    1237             : 
    1238           9 :     InsertNewDataset(osTilename, poDS);
    1239           9 :     return poDS;
    1240             : }
    1241             : 
    1242             : /************************************************************************/
    1243             : /*                            GetMetaTile()                             */
    1244             : /************************************************************************/
    1245             : 
    1246          62 : GDALDataset *PLMosaicDataset::GetMetaTile(int tile_x, int tile_y)
    1247             : {
    1248         124 :     const CPLString osTilename = formatTileName(tile_x, tile_y);
    1249             :     std::map<CPLString, PLLinkedDataset *>::const_iterator it =
    1250          62 :         oMapLinkedDatasets.find(osTilename);
    1251          62 :     if (it == oMapLinkedDatasets.end())
    1252             :     {
    1253          50 :         CPLString osTmpFilename;
    1254             : 
    1255          50 :         const CPLString osMosaicPath(GetMosaicCachePath());
    1256             :         osTmpFilename =
    1257          25 :             CPLFormFilenameSafe(osMosaicPath,
    1258             :                                 CPLSPrintf("%s_%s.tif", osMosaic.c_str(),
    1259             :                                            CPLGetFilename(osTilename)),
    1260          25 :                                 nullptr);
    1261             :         VSIStatBufL sStatBuf;
    1262             : 
    1263          50 :         CPLString osURL = osQuadsURL;
    1264          25 :         osURL += osTilename;
    1265          25 :         osURL += "/full";
    1266             : 
    1267          25 :         if (!osCachePathRoot.empty() && VSIStatL(osTmpFilename, &sStatBuf) == 0)
    1268             :         {
    1269           3 :             if (bTrustCache)
    1270             :             {
    1271           1 :                 return OpenAndInsertNewDataset(osTmpFilename, osTilename);
    1272             :             }
    1273             : 
    1274           2 :             CPLDebug("PLMOSAIC",
    1275             :                      "File %s exists. Checking if it is up-to-date...",
    1276             :                      osTmpFilename.c_str());
    1277             :             // Currently we only check by file size, which should be good enough
    1278             :             // as the metatiles are compressed, so a change in content is likely
    1279             :             // to cause a change in filesize. Use of a signature would be better
    1280             :             // though if available in the metadata
    1281             :             VSIStatBufL sRemoteTileStatBuf;
    1282           2 :             char *pszEscapedURL = CPLEscapeString(
    1283           4 :                 (osURL + "?api_key=" + osAPIKey).c_str(), -1, CPLES_URL);
    1284           2 :             CPLString osVSICURLUrl(STARTS_WITH(osURL, "/vsimem/")
    1285           4 :                                        ? osURL
    1286             :                                        : "/vsicurl?use_head=no&url=" +
    1287           6 :                                              CPLString(pszEscapedURL));
    1288           2 :             CPLFree(pszEscapedURL);
    1289           2 :             if (VSIStatL(osVSICURLUrl, &sRemoteTileStatBuf) == 0 &&
    1290           0 :                 sRemoteTileStatBuf.st_size == sStatBuf.st_size)
    1291             :             {
    1292           0 :                 CPLDebug("PLMOSAIC", "Cached tile is up-to-date");
    1293           0 :                 return OpenAndInsertNewDataset(osTmpFilename, osTilename);
    1294             :             }
    1295             :             else
    1296             :             {
    1297           2 :                 CPLDebug("PLMOSAIC", "Cached tile is not up-to-date");
    1298           2 :                 VSIUnlink(osTmpFilename);
    1299             :             }
    1300             :         }
    1301             : 
    1302             :         // Fetch the GeoTIFF now
    1303             : 
    1304          24 :         CPLHTTPResult *psResult = Download(osURL, TRUE);
    1305          24 :         if (psResult == nullptr)
    1306             :         {
    1307          16 :             InsertNewDataset(osTilename, nullptr);
    1308          16 :             return nullptr;
    1309             :         }
    1310             : 
    1311           8 :         CreateMosaicCachePathIfNecessary();
    1312             : 
    1313           8 :         bool bUnlink = false;
    1314             :         VSILFILE *fp =
    1315           8 :             osCachePathRoot.size() ? VSIFOpenL(osTmpFilename, "wb") : nullptr;
    1316           8 :         if (fp)
    1317             :         {
    1318           6 :             VSIFWriteL(psResult->pabyData, 1, psResult->nDataLen, fp);
    1319           6 :             VSIFCloseL(fp);
    1320             :         }
    1321             :         else
    1322             :         {
    1323             :             // In case there's no temporary path or it is not writable
    1324             :             // use a in-memory dataset, and limit the cache to only one
    1325           2 :             if (!osCachePathRoot.empty() && nCacheMaxSize > 1)
    1326             :             {
    1327           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1328             :                          "Cannot write into %s. Using /vsimem and reduce cache "
    1329             :                          "to 1 entry",
    1330             :                          osCachePathRoot.c_str());
    1331           1 :                 FlushDatasetsCache();
    1332           1 :                 nCacheMaxSize = 1;
    1333             :             }
    1334           2 :             bUnlink = true;
    1335             :             osTmpFilename = VSIMemGenerateHiddenFilename(
    1336             :                 CPLSPrintf("single_tile_plmosaic_cache_%s_%d_%d.tif",
    1337           2 :                            osMosaic.c_str(), tile_x, tile_y));
    1338           2 :             fp = VSIFOpenL(osTmpFilename, "wb");
    1339           2 :             if (fp)
    1340             :             {
    1341           2 :                 VSIFWriteL(psResult->pabyData, 1, psResult->nDataLen, fp);
    1342           2 :                 VSIFCloseL(fp);
    1343             :             }
    1344             :         }
    1345           8 :         CPLHTTPDestroyResult(psResult);
    1346           8 :         GDALDataset *poDS = OpenAndInsertNewDataset(osTmpFilename, osTilename);
    1347             : 
    1348           8 :         if (bUnlink)
    1349           2 :             VSIUnlink(osTilename);
    1350             : 
    1351           8 :         return poDS;
    1352             :     }
    1353             : 
    1354             :     // Move link to head of MRU list
    1355          37 :     PLLinkedDataset *psLinkedDataset = it->second;
    1356          37 :     GDALDataset *poDS = psLinkedDataset->poDS;
    1357          37 :     if (psLinkedDataset != psHead)
    1358             :     {
    1359          18 :         if (psLinkedDataset == psTail)
    1360           2 :             psTail = psLinkedDataset->psPrev;
    1361          18 :         if (psLinkedDataset->psPrev)
    1362          18 :             psLinkedDataset->psPrev->psNext = psLinkedDataset->psNext;
    1363          18 :         if (psLinkedDataset->psNext)
    1364          16 :             psLinkedDataset->psNext->psPrev = psLinkedDataset->psPrev;
    1365          18 :         psLinkedDataset->psNext = psHead;
    1366          18 :         psLinkedDataset->psPrev = nullptr;
    1367          18 :         psHead->psPrev = psLinkedDataset;
    1368          18 :         psHead = psLinkedDataset;
    1369             :     }
    1370             : 
    1371          37 :     return poDS;
    1372             : }
    1373             : 
    1374             : /************************************************************************/
    1375             : /*                         GetLocationInfo()                            */
    1376             : /************************************************************************/
    1377             : 
    1378           4 : const char *PLMosaicDataset::GetLocationInfo(int nPixel, int nLine)
    1379             : {
    1380             :     int nBlockXSize, nBlockYSize;
    1381           4 :     GetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
    1382             : 
    1383           4 :     const int nBlockXOff = nPixel / nBlockXSize;
    1384           4 :     const int nBlockYOff = nLine / nBlockYSize;
    1385           4 :     const int bottom_yblock =
    1386           4 :         (nRasterYSize - nBlockYOff * nBlockYSize) / nBlockYSize - 1;
    1387             : 
    1388           4 :     const int meta_tile_x =
    1389           4 :         nMetaTileXShift + (nBlockXOff * nBlockXSize) / nQuadSize;
    1390           4 :     const int meta_tile_y =
    1391           4 :         nMetaTileYShift + (bottom_yblock * nBlockYSize) / nQuadSize;
    1392             : 
    1393           8 :     CPLString osQuadURL = osQuadsURL;
    1394           8 :     CPLString osTilename = formatTileName(meta_tile_x, meta_tile_y);
    1395           4 :     osQuadURL += osTilename;
    1396             : 
    1397           4 :     if (meta_tile_x != nLastMetaTileX || meta_tile_y != nLastMetaTileY)
    1398             :     {
    1399           3 :         const CPLString osQuadScenesURL = osQuadURL + "/items";
    1400             : 
    1401           3 :         json_object_put(poLastItemsInformation);
    1402           3 :         poLastItemsInformation = RunRequest(osQuadScenesURL, TRUE);
    1403             : 
    1404           3 :         nLastMetaTileX = meta_tile_x;
    1405           3 :         nLastMetaTileY = meta_tile_y;
    1406             :     }
    1407             : 
    1408           4 :     osLastRetGetLocationInfo.clear();
    1409             : 
    1410           4 :     CPLXMLNode *psRoot = CPLCreateXMLNode(nullptr, CXT_Element, "LocationInfo");
    1411             : 
    1412           4 :     if (poLastItemsInformation)
    1413             :     {
    1414             :         json_object *poItems =
    1415           2 :             CPL_json_object_object_get(poLastItemsInformation, "items");
    1416           4 :         if (poItems && json_object_get_type(poItems) == json_type_array &&
    1417           2 :             json_object_array_length(poItems) != 0)
    1418             :         {
    1419             :             CPLXMLNode *psScenes =
    1420           2 :                 CPLCreateXMLNode(psRoot, CXT_Element, "Scenes");
    1421           2 :             const auto nItemsLength = json_object_array_length(poItems);
    1422           4 :             for (auto i = decltype(nItemsLength){0}; i < nItemsLength; i++)
    1423             :             {
    1424           2 :                 json_object *poObj = json_object_array_get_idx(poItems, i);
    1425           2 :                 if (poObj && json_object_get_type(poObj) == json_type_object)
    1426             :                 {
    1427             :                     json_object *poLink =
    1428           2 :                         CPL_json_object_object_get(poObj, "link");
    1429           2 :                     if (poLink)
    1430             :                     {
    1431             :                         CPLXMLNode *psScene =
    1432           2 :                             CPLCreateXMLNode(psScenes, CXT_Element, "Scene");
    1433             :                         CPLXMLNode *psItem =
    1434           2 :                             CPLCreateXMLNode(psScene, CXT_Element, "link");
    1435           2 :                         CPLCreateXMLNode(psItem, CXT_Text,
    1436             :                                          json_object_get_string(poLink));
    1437             :                     }
    1438             :                 }
    1439             :             }
    1440             :         }
    1441             :     }
    1442             : 
    1443           4 :     char *pszXML = CPLSerializeXMLTree(psRoot);
    1444           4 :     CPLDestroyXMLNode(psRoot);
    1445           4 :     osLastRetGetLocationInfo = pszXML;
    1446           4 :     CPLFree(pszXML);
    1447             : 
    1448           8 :     return osLastRetGetLocationInfo.c_str();
    1449             : }
    1450             : 
    1451             : /************************************************************************/
    1452             : /*                             IRasterIO()                              */
    1453             : /************************************************************************/
    1454             : 
    1455           6 : CPLErr PLMosaicDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
    1456             :                                   int nXSize, int nYSize, void *pData,
    1457             :                                   int nBufXSize, int nBufYSize,
    1458             :                                   GDALDataType eBufType, int nBandCount,
    1459             :                                   BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
    1460             :                                   GSpacing nLineSpace, GSpacing nBandSpace,
    1461             :                                   GDALRasterIOExtraArg *psExtraArg)
    1462             : {
    1463           6 :     if (bUseTMSForMain && !apoTMSDS.empty())
    1464           1 :         return apoTMSDS[0]->RasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
    1465             :                                      pData, nBufXSize, nBufYSize, eBufType,
    1466             :                                      nBandCount, panBandMap, nPixelSpace,
    1467           1 :                                      nLineSpace, nBandSpace, psExtraArg);
    1468             : 
    1469           5 :     return BlockBasedRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
    1470             :                               nBufXSize, nBufYSize, eBufType, nBandCount,
    1471             :                               panBandMap, nPixelSpace, nLineSpace, nBandSpace,
    1472           5 :                               psExtraArg);
    1473             : }
    1474             : 
    1475             : /************************************************************************/
    1476             : /*                      GDALRegister_PLMOSAIC()                         */
    1477             : /************************************************************************/
    1478             : 
    1479        2024 : void GDALRegister_PLMOSAIC()
    1480             : 
    1481             : {
    1482        2024 :     if (GDALGetDriverByName("PLMOSAIC") != nullptr)
    1483         283 :         return;
    1484             : 
    1485        1741 :     GDALDriver *poDriver = new GDALDriver();
    1486             : 
    1487        1741 :     poDriver->SetDescription("PLMOSAIC");
    1488        1741 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1489        1741 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Planet Labs Mosaics API");
    1490        1741 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
    1491        1741 :                               "drivers/raster/plmosaic.html");
    1492             : 
    1493        1741 :     poDriver->SetMetadataItem(GDAL_DMD_CONNECTION_PREFIX, "PLMOSAIC:");
    1494             : 
    1495        1741 :     poDriver->SetMetadataItem(
    1496             :         GDAL_DMD_OPENOPTIONLIST,
    1497             :         "<OpenOptionList>"
    1498             :         "  <Option name='API_KEY' type='string' description='Account API key' "
    1499             :         "required='true'/>"
    1500             :         "  <Option name='MOSAIC' type='string' description='Mosaic name'/>"
    1501             :         "  <Option name='CACHE_PATH' type='string' description='Directory "
    1502             :         "where to put cached quads'/>"
    1503             :         "  <Option name='TRUST_CACHE' type='boolean' description='Whether "
    1504             :         "already cached quads should be trusted as the most recent version' "
    1505             :         "default='NO'/>"
    1506             :         "  <Option name='USE_TILES' type='boolean' description='Whether to use "
    1507             :         "the tile API even for full resolution data (only for Byte mosaics)' "
    1508             :         "default='NO'/>"
    1509        1741 :         "</OpenOptionList>");
    1510             : 
    1511        1741 :     poDriver->pfnIdentify = PLMosaicDataset::Identify;
    1512        1741 :     poDriver->pfnOpen = PLMosaicDataset::Open;
    1513             : 
    1514        1741 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1515             : }

Generated by: LCOV version 1.14