LCOV - code coverage report
Current view: top level - frmts/plmosaic - plmosaicdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 638 671 95.1 %
Date: 2025-02-20 10:14:44 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 = CPLGetPathSafe(CPLGenerateTempFilenameSafe("").c_str());
     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       53410 : int PLMosaicDataset::Identify(GDALOpenInfo *poOpenInfo)
     397             : 
     398             : {
     399       53410 :     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             : 
     413             :     /* Ensure the PLMosaic driver uses a unique default user agent to help
     414             :      * identify usage. */
     415          56 :     CPLString osUserAgent = CPLGetConfigOption("GDAL_HTTP_USERAGENT", "");
     416          56 :     if (osUserAgent.empty())
     417          56 :         papszOptions = CSLAddString(
     418             :             papszOptions, CPLSPrintf("USERAGENT=PLMosaic Driver GDAL/%d.%d.%d",
     419             :                                      GDAL_VERSION_MAJOR, GDAL_VERSION_MINOR,
     420             :                                      GDAL_VERSION_REV));
     421             : 
     422             :     /* Use basic auth, rather than Authorization headers since curl would
     423             :      * forward it to S3 */
     424             :     papszOptions =
     425          56 :         CSLAddString(papszOptions, CPLSPrintf("USERPWD=%s:", osAPIKey.c_str()));
     426             : 
     427         112 :     return papszOptions;
     428             : }
     429             : 
     430             : /************************************************************************/
     431             : /*                               Download()                             */
     432             : /************************************************************************/
     433             : 
     434          56 : CPLHTTPResult *PLMosaicDataset::Download(const char *pszURL, int bQuiet404Error)
     435             : {
     436          56 :     char **papszOptions = CSLAddString(GetBaseHTTPOptions(), nullptr);
     437          56 :     CPLHTTPResult *psResult = nullptr;
     438          56 :     if (STARTS_WITH(osBaseURL, "/vsimem/") && STARTS_WITH(pszURL, "/vsimem/"))
     439             :     {
     440          18 :         CPLDebug("PLSCENES", "Fetching %s", pszURL);
     441             :         psResult = reinterpret_cast<CPLHTTPResult *>(
     442          18 :             CPLCalloc(1, sizeof(CPLHTTPResult)));
     443          18 :         vsi_l_offset nDataLength = 0;
     444          36 :         CPLString osURL(pszURL);
     445          18 :         if (osURL.back() == '/')
     446           1 :             osURL.pop_back();
     447          18 :         GByte *pabyBuf = VSIGetMemFileBuffer(osURL, &nDataLength, FALSE);
     448          18 :         if (pabyBuf)
     449             :         {
     450          16 :             psResult->pabyData = reinterpret_cast<GByte *>(
     451          16 :                 VSIMalloc(1 + static_cast<size_t>(nDataLength)));
     452          16 :             if (psResult->pabyData)
     453             :             {
     454          16 :                 memcpy(psResult->pabyData, pabyBuf,
     455             :                        static_cast<size_t>(nDataLength));
     456          16 :                 psResult->pabyData[nDataLength] = 0;
     457          16 :                 psResult->nDataLen = static_cast<int>(nDataLength);
     458             :             }
     459             :         }
     460             :         else
     461             :         {
     462           2 :             psResult->pszErrBuf =
     463           2 :                 CPLStrdup(CPLSPrintf("Error 404. Cannot find %s", pszURL));
     464             :         }
     465             :     }
     466             :     else
     467             :     {
     468          38 :         if (bQuiet404Error)
     469          25 :             CPLPushErrorHandler(CPLQuietErrorHandler);
     470          38 :         psResult = CPLHTTPFetch(pszURL, papszOptions);
     471          38 :         if (bQuiet404Error)
     472          25 :             CPLPopErrorHandler();
     473             :     }
     474          56 :     CSLDestroy(papszOptions);
     475             : 
     476          56 :     if (psResult->pszErrBuf != nullptr)
     477             :     {
     478          19 :         if (!(bQuiet404Error && strstr(psResult->pszErrBuf, "404")))
     479             :         {
     480           2 :             CPLError(CE_Failure, CPLE_AppDefined, "%s",
     481           2 :                      psResult->pabyData
     482             :                          ? reinterpret_cast<const char *>(psResult->pabyData)
     483             :                          : psResult->pszErrBuf);
     484             :         }
     485          19 :         CPLHTTPDestroyResult(psResult);
     486          19 :         return nullptr;
     487             :     }
     488             : 
     489          37 :     if (psResult->pabyData == nullptr)
     490             :     {
     491           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     492             :                  "Empty content returned by server");
     493           0 :         CPLHTTPDestroyResult(psResult);
     494           0 :         return nullptr;
     495             :     }
     496             : 
     497          37 :     return psResult;
     498             : }
     499             : 
     500             : /************************************************************************/
     501             : /*                               RunRequest()                           */
     502             : /************************************************************************/
     503             : 
     504          32 : json_object *PLMosaicDataset::RunRequest(const char *pszURL, int bQuiet404Error)
     505             : {
     506          32 :     CPLHTTPResult *psResult = Download(pszURL, bQuiet404Error);
     507          32 :     if (psResult == nullptr)
     508             :     {
     509           3 :         return nullptr;
     510             :     }
     511             : 
     512          29 :     json_object *poObj = nullptr;
     513          29 :     const char *pszText = reinterpret_cast<const char *>(psResult->pabyData);
     514          29 :     if (!OGRJSonParse(pszText, &poObj, true))
     515             :     {
     516           3 :         CPLHTTPDestroyResult(psResult);
     517           3 :         return nullptr;
     518             :     }
     519             : 
     520          26 :     CPLHTTPDestroyResult(psResult);
     521             : 
     522          26 :     if (json_object_get_type(poObj) != json_type_object)
     523             :     {
     524           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     525             :                  "Return is not a JSON dictionary");
     526           0 :         json_object_put(poObj);
     527           0 :         poObj = nullptr;
     528             :     }
     529             : 
     530          26 :     return poObj;
     531             : }
     532             : 
     533             : /************************************************************************/
     534             : /*                           PLMosaicGetParameter()                     */
     535             : /************************************************************************/
     536             : 
     537         141 : static CPLString PLMosaicGetParameter(GDALOpenInfo *poOpenInfo,
     538             :                                       char **papszOptions, const char *pszName,
     539             :                                       const char *pszDefaultVal)
     540             : {
     541             :     return CSLFetchNameValueDef(
     542             :         papszOptions, pszName,
     543         141 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, pszName,
     544         141 :                              pszDefaultVal));
     545             : }
     546             : 
     547             : /************************************************************************/
     548             : /*                                Open()                                */
     549             : /************************************************************************/
     550             : 
     551          30 : GDALDataset *PLMosaicDataset::Open(GDALOpenInfo *poOpenInfo)
     552             : 
     553             : {
     554          30 :     if (!Identify(poOpenInfo))
     555           0 :         return nullptr;
     556             : 
     557          30 :     PLMosaicDataset *poDS = new PLMosaicDataset();
     558             : 
     559             :     poDS->osBaseURL = CPLGetConfigOption(
     560          30 :         "PL_URL", "https://api.planet.com/basemaps/v1/mosaics");
     561             : 
     562          60 :     char **papszOptions = CSLTokenizeStringComplex(
     563          30 :         poOpenInfo->pszFilename + strlen("PLMosaic:"), ",", TRUE, FALSE);
     564          37 :     for (char **papszIter = papszOptions; papszIter && *papszIter; papszIter++)
     565             :     {
     566           8 :         char *pszKey = nullptr;
     567           8 :         const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
     568           8 :         if (pszValue != nullptr)
     569             :         {
     570           8 :             if (!EQUAL(pszKey, "api_key") && !EQUAL(pszKey, "mosaic") &&
     571           3 :                 !EQUAL(pszKey, "cache_path") && !EQUAL(pszKey, "trust_cache") &&
     572           1 :                 !EQUAL(pszKey, "use_tiles"))
     573             :             {
     574           1 :                 CPLError(CE_Failure, CPLE_NotSupported, "Unsupported option %s",
     575             :                          pszKey);
     576           1 :                 CPLFree(pszKey);
     577           1 :                 delete poDS;
     578           1 :                 CSLDestroy(papszOptions);
     579           1 :                 return nullptr;
     580             :             }
     581           7 :             CPLFree(pszKey);
     582             :         }
     583             :     }
     584             : 
     585          58 :     poDS->osAPIKey = PLMosaicGetParameter(poOpenInfo, papszOptions, "api_key",
     586          29 :                                           CPLGetConfigOption("PL_API_KEY", ""));
     587             : 
     588          29 :     if (poDS->osAPIKey.empty())
     589             :     {
     590           1 :         CPLError(
     591             :             CE_Failure, CPLE_AppDefined,
     592             :             "Missing PL_API_KEY configuration option or API_KEY open option");
     593           1 :         delete poDS;
     594           1 :         CSLDestroy(papszOptions);
     595           1 :         return nullptr;
     596             :     }
     597             : 
     598             :     poDS->osMosaic =
     599          28 :         PLMosaicGetParameter(poOpenInfo, papszOptions, "mosaic", "");
     600             : 
     601             :     poDS->osCachePathRoot =
     602          56 :         PLMosaicGetParameter(poOpenInfo, papszOptions, "cache_path",
     603          28 :                              CPLGetConfigOption("PL_CACHE_PATH", ""));
     604             : 
     605          28 :     poDS->bTrustCache = CPLTestBool(
     606          56 :         PLMosaicGetParameter(poOpenInfo, papszOptions, "trust_cache", "FALSE"));
     607             : 
     608          28 :     poDS->bUseTMSForMain = CPLTestBool(
     609          56 :         PLMosaicGetParameter(poOpenInfo, papszOptions, "use_tiles", "FALSE"));
     610             : 
     611          28 :     CSLDestroy(papszOptions);
     612          28 :     papszOptions = nullptr;
     613             : 
     614          28 :     if (!poDS->osMosaic.empty())
     615             :     {
     616          20 :         if (!poDS->OpenMosaic())
     617             :         {
     618           8 :             delete poDS;
     619           8 :             poDS = nullptr;
     620             :         }
     621             :     }
     622             :     else
     623             :     {
     624          16 :         auto aosNameList = poDS->ListSubdatasets();
     625           8 :         if (aosNameList.empty())
     626             :         {
     627           5 :             delete poDS;
     628           5 :             poDS = nullptr;
     629             :         }
     630           3 :         else if (aosNameList.size() == 1)
     631             :         {
     632           4 :             const CPLString osOldFilename(poOpenInfo->pszFilename);
     633             :             const CPLString osMosaicConnectionString =
     634           4 :                 CPLSPrintf("PLMOSAIC:mosaic=%s", aosNameList[0].c_str());
     635           2 :             delete poDS;
     636             :             GDALOpenInfo oOpenInfo(osMosaicConnectionString.c_str(),
     637           4 :                                    GA_ReadOnly);
     638           2 :             oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
     639           2 :             poDS = reinterpret_cast<PLMosaicDataset *>(Open(&oOpenInfo));
     640           2 :             if (poDS)
     641           2 :                 poDS->SetDescription(osOldFilename);
     642             :         }
     643             :         else
     644             :         {
     645           2 :             CPLStringList aosSubdatasets;
     646           3 :             for (const auto &osName : aosNameList)
     647             :             {
     648           2 :                 const int nDatasetIdx = aosSubdatasets.Count() / 2 + 1;
     649             :                 aosSubdatasets.AddNameValue(
     650             :                     CPLSPrintf("SUBDATASET_%d_NAME", nDatasetIdx),
     651           2 :                     CPLSPrintf("PLMOSAIC:mosaic=%s", osName.c_str()));
     652             :                 aosSubdatasets.AddNameValue(
     653             :                     CPLSPrintf("SUBDATASET_%d_DESC", nDatasetIdx),
     654           2 :                     CPLSPrintf("Mosaic %s", osName.c_str()));
     655             :             }
     656           1 :             poDS->SetMetadata(aosSubdatasets.List(), "SUBDATASETS");
     657             :         }
     658             :     }
     659             : 
     660          28 :     if (poDS)
     661          15 :         poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
     662             : 
     663          28 :     return poDS;
     664             : }
     665             : 
     666             : /************************************************************************/
     667             : /*                           ReplaceSubString()                         */
     668             : /************************************************************************/
     669             : 
     670          36 : static void ReplaceSubString(CPLString &osTarget, CPLString osPattern,
     671             :                              CPLString osReplacement)
     672             : 
     673             : {
     674             :     // Assumes only one occurrence of osPattern.
     675          36 :     size_t pos = osTarget.find(osPattern);
     676          36 :     if (pos == CPLString::npos)
     677           0 :         return;
     678             : 
     679          36 :     osTarget.replace(pos, osPattern.size(), osReplacement);
     680             : }
     681             : 
     682             : /************************************************************************/
     683             : /*                            GetMosaicCachePath()                      */
     684             : /************************************************************************/
     685             : 
     686          32 : CPLString PLMosaicDataset::GetMosaicCachePath()
     687             : {
     688          32 :     if (!osCachePathRoot.empty())
     689             :     {
     690             :         const CPLString osCachePath(
     691          62 :             CPLFormFilenameSafe(osCachePathRoot, "plmosaic_cache", nullptr));
     692             :         const CPLString osMosaicPath(
     693          62 :             CPLFormFilenameSafe(osCachePath, osMosaic, nullptr));
     694             : 
     695          31 :         return osMosaicPath;
     696             :     }
     697           1 :     return "";
     698             : }
     699             : 
     700             : /************************************************************************/
     701             : /*                     CreateMosaicCachePathIfNecessary()               */
     702             : /************************************************************************/
     703             : 
     704           9 : void PLMosaicDataset::CreateMosaicCachePathIfNecessary()
     705             : {
     706           9 :     if (!osCachePathRoot.empty())
     707             :     {
     708             :         const CPLString osCachePath(
     709          16 :             CPLFormFilenameSafe(osCachePathRoot, "plmosaic_cache", nullptr));
     710             :         const CPLString osMosaicPath(
     711          16 :             CPLFormFilenameSafe(osCachePath, osMosaic, nullptr));
     712             : 
     713             :         VSIStatBufL sStatBuf;
     714           8 :         if (VSIStatL(osMosaicPath, &sStatBuf) != 0)
     715             :         {
     716           6 :             CPLPushErrorHandler(CPLQuietErrorHandler);
     717           6 :             CPL_IGNORE_RET_VAL(VSIMkdir(osCachePathRoot, 0755));
     718           6 :             CPL_IGNORE_RET_VAL(VSIMkdir(osCachePath, 0755));
     719           6 :             CPL_IGNORE_RET_VAL(VSIMkdir(osMosaicPath, 0755));
     720           6 :             CPLPopErrorHandler();
     721             :         }
     722             :     }
     723           9 : }
     724             : 
     725             : /************************************************************************/
     726             : /*                     LongLatToSphericalMercator()                     */
     727             : /************************************************************************/
     728             : 
     729           2 : static void LongLatToSphericalMercator(double *x, double *y)
     730             : {
     731           2 :     double X = SPHERICAL_RADIUS * (*x) / 180 * M_PI;
     732           2 :     double Y = SPHERICAL_RADIUS * log(tan(M_PI / 4 + 0.5 * (*y) / 180 * M_PI));
     733           2 :     *x = X;
     734           2 :     *y = Y;
     735           2 : }
     736             : 
     737             : /************************************************************************/
     738             : /*                               OpenMosaic()                           */
     739             : /************************************************************************/
     740             : 
     741          20 : int PLMosaicDataset::OpenMosaic()
     742             : {
     743          40 :     CPLString osURL;
     744             : 
     745          20 :     osURL = osBaseURL;
     746          20 :     if (osURL.back() != '/')
     747          20 :         osURL += '/';
     748          20 :     char *pszEscaped = CPLEscapeString(osMosaic, -1, CPLES_URL);
     749          20 :     osURL += "?name__is=" + CPLString(pszEscaped);
     750          20 :     CPLFree(pszEscaped);
     751             : 
     752          20 :     json_object *poObj = RunRequest(osURL);
     753          20 :     if (poObj == nullptr)
     754             :     {
     755           2 :         return FALSE;
     756             :     }
     757             : 
     758          18 :     json_object *poMosaics = CPL_json_object_object_get(poObj, "mosaics");
     759          18 :     json_object *poMosaic = nullptr;
     760          35 :     if (poMosaics == nullptr ||
     761          17 :         json_object_get_type(poMosaics) != json_type_array ||
     762          17 :         json_object_array_length(poMosaics) != 1 ||
     763          52 :         (poMosaic = json_object_array_get_idx(poMosaics, 0)) == nullptr ||
     764          17 :         json_object_get_type(poMosaic) != json_type_object)
     765             :     {
     766           1 :         CPLError(CE_Failure, CPLE_AppDefined, "No mosaic %s", osMosaic.c_str());
     767           1 :         json_object_put(poObj);
     768           1 :         return FALSE;
     769             :     }
     770             : 
     771          17 :     json_object *poId = CPL_json_object_object_get(poMosaic, "id");
     772             :     json_object *poCoordinateSystem =
     773          17 :         CPL_json_object_object_get(poMosaic, "coordinate_system");
     774          17 :     json_object *poDataType = CPL_json_object_object_get(poMosaic, "datatype");
     775             :     json_object *poQuadSize =
     776          17 :         json_ex_get_object_by_path(poMosaic, "grid.quad_size");
     777             :     json_object *poResolution =
     778          17 :         json_ex_get_object_by_path(poMosaic, "grid.resolution");
     779          17 :     json_object *poLinks = CPL_json_object_object_get(poMosaic, "_links");
     780          17 :     json_object *poLinksTiles = nullptr;
     781          17 :     json_object *poBBox = CPL_json_object_object_get(poMosaic, "bbox");
     782          17 :     if (poLinks != nullptr && json_object_get_type(poLinks) == json_type_object)
     783             :     {
     784          11 :         poLinksTiles = CPL_json_object_object_get(poLinks, "tiles");
     785             :     }
     786          17 :     if (poId == nullptr || json_object_get_type(poId) != json_type_string ||
     787          16 :         poCoordinateSystem == nullptr ||
     788          16 :         json_object_get_type(poCoordinateSystem) != json_type_string ||
     789          16 :         poDataType == nullptr ||
     790          16 :         json_object_get_type(poDataType) != json_type_string ||
     791          16 :         poQuadSize == nullptr ||
     792          16 :         json_object_get_type(poQuadSize) != json_type_int ||
     793          34 :         poResolution == nullptr ||
     794          16 :         (json_object_get_type(poResolution) != json_type_int &&
     795          16 :          json_object_get_type(poResolution) != json_type_double))
     796             :     {
     797           1 :         CPLError(CE_Failure, CPLE_NotSupported, "Missing required parameter");
     798           1 :         json_object_put(poObj);
     799           1 :         return FALSE;
     800             :     }
     801             : 
     802          32 :     CPLString osId(json_object_get_string(poId));
     803             : 
     804          16 :     const char *pszSRS = json_object_get_string(poCoordinateSystem);
     805          16 :     if (!EQUAL(pszSRS, "EPSG:3857"))
     806             :     {
     807           1 :         CPLError(CE_Failure, CPLE_NotSupported,
     808             :                  "Unsupported coordinate_system = %s", pszSRS);
     809           1 :         json_object_put(poObj);
     810           1 :         return FALSE;
     811             :     }
     812             : 
     813          15 :     m_oSRS.SetFromUserInput(
     814             :         pszSRS, OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
     815             : 
     816             :     json_object *poQuadDownload =
     817          15 :         CPL_json_object_object_get(poMosaic, "quad_download");
     818          15 :     bQuadDownload = CPL_TO_BOOL(json_object_get_boolean(poQuadDownload));
     819             : 
     820          15 :     GDALDataType eDT = GDT_Unknown;
     821          15 :     const char *pszDataType = json_object_get_string(poDataType);
     822          15 :     if (EQUAL(pszDataType, "byte"))
     823          13 :         eDT = GDT_Byte;
     824           2 :     else if (EQUAL(pszDataType, "uint16"))
     825           1 :         eDT = GDT_UInt16;
     826           1 :     else if (EQUAL(pszDataType, "int16"))
     827           0 :         eDT = GDT_Int16;
     828             :     else
     829             :     {
     830           1 :         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data_type = %s",
     831             :                  pszDataType);
     832           1 :         json_object_put(poObj);
     833           1 :         return FALSE;
     834             :     }
     835             : 
     836          14 :     if (eDT == GDT_Byte && !bQuadDownload)
     837           2 :         bUseTMSForMain = true;
     838             : 
     839          14 :     if (bUseTMSForMain && eDT != GDT_Byte)
     840             :     {
     841           1 :         CPLError(
     842             :             CE_Failure, CPLE_NotSupported,
     843             :             "Cannot use tile API for full resolution data on non Byte mosaic");
     844           1 :         bUseTMSForMain = FALSE;
     845             :     }
     846             : 
     847          14 :     nQuadSize = json_object_get_int(poQuadSize);
     848          14 :     if (nQuadSize <= 0 || (nQuadSize % 256) != 0)
     849             :     {
     850           1 :         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported quad_size = %d",
     851             :                  nQuadSize);
     852           1 :         json_object_put(poObj);
     853           1 :         return FALSE;
     854             :     }
     855             : 
     856          13 :     const double dfResolution = json_object_get_double(poResolution);
     857          13 :     if (EQUAL(pszSRS, "EPSG:3857"))
     858             :     {
     859          13 :         double dfZoomLevel = log(GM_ZOOM_0 / dfResolution) / log(2.0);
     860          13 :         nZoomLevelMax = static_cast<int>(dfZoomLevel + 0.1);
     861          13 :         if (fabs(dfZoomLevel - nZoomLevelMax) > 1e-5)
     862             :         {
     863           1 :             CPLError(CE_Failure, CPLE_NotSupported,
     864             :                      "Unsupported resolution = %.12g", dfResolution);
     865           1 :             json_object_put(poObj);
     866           1 :             return FALSE;
     867             :         }
     868             : 
     869          12 :         bHasGeoTransform = TRUE;
     870          12 :         adfGeoTransform[0] = GM_ORIGIN;
     871          12 :         adfGeoTransform[1] = dfResolution;
     872          12 :         adfGeoTransform[2] = 0;
     873          12 :         adfGeoTransform[3] = -GM_ORIGIN;
     874          12 :         adfGeoTransform[4] = 0;
     875          12 :         adfGeoTransform[5] = -dfResolution;
     876          12 :         nRasterXSize = static_cast<int>(2 * -GM_ORIGIN / dfResolution + 0.5);
     877          12 :         nRasterYSize = nRasterXSize;
     878             : 
     879          13 :         if (poBBox != nullptr &&
     880          13 :             json_object_get_type(poBBox) == json_type_array &&
     881           1 :             json_object_array_length(poBBox) == 4)
     882             :         {
     883             :             double xmin =
     884           1 :                 json_object_get_double(json_object_array_get_idx(poBBox, 0));
     885             :             double ymin =
     886           1 :                 json_object_get_double(json_object_array_get_idx(poBBox, 1));
     887             :             double xmax =
     888           1 :                 json_object_get_double(json_object_array_get_idx(poBBox, 2));
     889             :             double ymax =
     890           1 :                 json_object_get_double(json_object_array_get_idx(poBBox, 3));
     891           1 :             LongLatToSphericalMercator(&xmin, &ymin);
     892           1 :             LongLatToSphericalMercator(&xmax, &ymax);
     893           1 :             xmin = std::max(xmin, GM_ORIGIN);
     894           1 :             ymin = std::max(ymin, GM_ORIGIN);
     895           1 :             xmax = std::min(xmax, -GM_ORIGIN);
     896           1 :             ymax = std::min(ymax, -GM_ORIGIN);
     897             : 
     898           1 :             double dfTileSize = dfResolution * nQuadSize;
     899           1 :             xmin = floor(xmin / dfTileSize) * dfTileSize;
     900           1 :             ymin = floor(ymin / dfTileSize) * dfTileSize;
     901           1 :             xmax = ceil(xmax / dfTileSize) * dfTileSize;
     902           1 :             ymax = ceil(ymax / dfTileSize) * dfTileSize;
     903           1 :             adfGeoTransform[0] = xmin;
     904           1 :             adfGeoTransform[3] = ymax;
     905           1 :             nRasterXSize = static_cast<int>((xmax - xmin) / dfResolution + 0.5);
     906           1 :             nRasterYSize = static_cast<int>((ymax - ymin) / dfResolution + 0.5);
     907           1 :             nMetaTileXShift =
     908           1 :                 static_cast<int>((xmin - GM_ORIGIN) / dfTileSize + 0.5);
     909           1 :             nMetaTileYShift =
     910           1 :                 static_cast<int>((ymin - GM_ORIGIN) / dfTileSize + 0.5);
     911             :         }
     912             :     }
     913             : 
     914          12 :     osQuadsURL = osBaseURL;
     915          12 :     if (osQuadsURL.back() != '/')
     916          12 :         osQuadsURL += '/';
     917          12 :     osQuadsURL += osId + "/quads/";
     918             : 
     919             :     // Use WMS/TMS driver for overviews (only for byte)
     920          11 :     if (eDT == GDT_Byte && EQUAL(pszSRS, "EPSG:3857") &&
     921          23 :         poLinksTiles != nullptr &&
     922          10 :         json_object_get_type(poLinksTiles) == json_type_string)
     923             :     {
     924          10 :         const char *pszLinksTiles = json_object_get_string(poLinksTiles);
     925          10 :         if (strstr(pszLinksTiles, "{x}") == nullptr ||
     926           9 :             strstr(pszLinksTiles, "{y}") == nullptr ||
     927           9 :             strstr(pszLinksTiles, "{z}") == nullptr)
     928             :         {
     929           1 :             CPLError(CE_Warning, CPLE_NotSupported, "Invalid _links.tiles = %s",
     930             :                      pszLinksTiles);
     931             :         }
     932             :         else
     933             :         {
     934          18 :             CPLString osCacheStr;
     935           9 :             if (!osCachePathRoot.empty())
     936             :             {
     937           7 :                 osCacheStr = "    <Cache><Path>";
     938           7 :                 osCacheStr += GetMosaicCachePath();
     939           7 :                 osCacheStr += "</Path><Unique>False</Unique></Cache>\n";
     940             :             }
     941             : 
     942          18 :             CPLString osTMSURL(pszLinksTiles);
     943           9 :             ReplaceSubString(osTMSURL, "{x}", "${x}");
     944           9 :             ReplaceSubString(osTMSURL, "{y}", "${y}");
     945           9 :             ReplaceSubString(osTMSURL, "{z}", "${z}");
     946           9 :             ReplaceSubString(osTMSURL, "{0-3}", "0");
     947             : 
     948         148 :             for (int nZoomLevel = nZoomLevelMax; nZoomLevel >= 0; nZoomLevel--)
     949             :             {
     950         140 :                 const int nZShift = nZoomLevelMax - nZoomLevel;
     951         140 :                 int nOvrXSize = nRasterXSize >> nZShift;
     952         140 :                 int nOvrYSize = nRasterYSize >> nZShift;
     953         140 :                 if (nOvrXSize == 0 || nOvrYSize == 0)
     954             :                     break;
     955             : 
     956             :                 CPLString osTMS = CPLSPrintf(
     957             :                     "<GDAL_WMS>\n"
     958             :                     "    <Service name=\"TMS\">\n"
     959             :                     "        <ServerUrl>%s</ServerUrl>\n"
     960             :                     "    </Service>\n"
     961             :                     "    <DataWindow>\n"
     962             :                     "        <UpperLeftX>%.16g</UpperLeftX>\n"
     963             :                     "        <UpperLeftY>%.16g</UpperLeftY>\n"
     964             :                     "        <LowerRightX>%.16g</LowerRightX>\n"
     965             :                     "        <LowerRightY>%.16g</LowerRightY>\n"
     966             :                     "        <SizeX>%d</SizeX>\n"
     967             :                     "        <SizeY>%d</SizeY>\n"
     968             :                     "        <TileLevel>%d</TileLevel>\n"
     969             :                     "        <YOrigin>top</YOrigin>\n"
     970             :                     "    </DataWindow>\n"
     971             :                     "    <Projection>%s</Projection>\n"
     972             :                     "    <BlockSizeX>256</BlockSizeX>\n"
     973             :                     "    <BlockSizeY>256</BlockSizeY>\n"
     974             :                     "    <BandsCount>4</BandsCount>\n"
     975             :                     "%s"
     976             :                     "</GDAL_WMS>",
     977             :                     osTMSURL.c_str(), GM_ORIGIN, -GM_ORIGIN, -GM_ORIGIN,
     978             :                     GM_ORIGIN, 256 << nZoomLevel, 256 << nZoomLevel, nZoomLevel,
     979         140 :                     pszSRS, osCacheStr.c_str());
     980             : 
     981         140 :                 GDALDataset *poTMSDS = GDALDataset::FromHandle(
     982             :                     GDALOpenEx(osTMS, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
     983             :                                nullptr, nullptr, nullptr));
     984         140 :                 if (poTMSDS)
     985             :                 {
     986         140 :                     double dfThisResolution = dfResolution * (1 << nZShift);
     987             : 
     988         140 :                     VRTDatasetH hVRTDS = VRTCreate(nOvrXSize, nOvrYSize);
     989         700 :                     for (int iBand = 1; iBand <= 4; iBand++)
     990             :                     {
     991         560 :                         VRTAddBand(hVRTDS, GDT_Byte, nullptr);
     992             :                     }
     993             : 
     994             :                     int nSrcXOff, nSrcYOff, nDstXOff, nDstYOff;
     995             : 
     996         140 :                     nSrcXOff = static_cast<int>(
     997         140 :                         0.5 +
     998         140 :                         (adfGeoTransform[0] - GM_ORIGIN) / dfThisResolution);
     999         140 :                     nDstXOff = 0;
    1000             : 
    1001         140 :                     nSrcYOff = static_cast<int>(
    1002         140 :                         0.5 +
    1003         140 :                         (-GM_ORIGIN - adfGeoTransform[3]) / dfThisResolution);
    1004         140 :                     nDstYOff = 0;
    1005             : 
    1006         700 :                     for (int iBand = 1; iBand <= 4; iBand++)
    1007             :                     {
    1008             :                         VRTSourcedRasterBandH hVRTBand =
    1009         560 :                             reinterpret_cast<VRTSourcedRasterBandH>(
    1010             :                                 GDALGetRasterBand(hVRTDS, iBand));
    1011         560 :                         VRTAddSimpleSource(
    1012             :                             hVRTBand, GDALGetRasterBand(poTMSDS, iBand),
    1013             :                             nSrcXOff, nSrcYOff, nOvrXSize, nOvrYSize, nDstXOff,
    1014             :                             nDstYOff, nOvrXSize, nOvrYSize, "NEAR",
    1015             :                             VRT_NODATA_UNSET);
    1016             :                     }
    1017         140 :                     poTMSDS->Dereference();
    1018             : 
    1019         140 :                     apoTMSDS.push_back(GDALDataset::FromHandle(hVRTDS));
    1020             :                 }
    1021             : 
    1022         140 :                 if (nOvrXSize < 256 && nOvrYSize < 256)
    1023           1 :                     break;
    1024             :             }
    1025             :         }
    1026             :     }
    1027             : 
    1028          12 :     if (bUseTMSForMain && apoTMSDS.empty())
    1029             :     {
    1030           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    1031             :                  "Cannot find tile definition, so use_tiles will be ignored");
    1032           1 :         bUseTMSForMain = FALSE;
    1033             :     }
    1034             : 
    1035          60 :     for (int i = 0; i < 4; i++)
    1036          48 :         SetBand(i + 1, new PLMosaicRasterBand(this, i + 1, eDT));
    1037             : 
    1038             :     json_object *poFirstAcquired =
    1039          12 :         CPL_json_object_object_get(poMosaic, "first_acquired");
    1040          24 :     if (poFirstAcquired != nullptr &&
    1041          12 :         json_object_get_type(poFirstAcquired) == json_type_string)
    1042             :     {
    1043          12 :         SetMetadataItem("FIRST_ACQUIRED",
    1044          12 :                         json_object_get_string(poFirstAcquired));
    1045             :     }
    1046             :     json_object *poLastAcquired =
    1047          12 :         CPL_json_object_object_get(poMosaic, "last_acquired");
    1048          24 :     if (poLastAcquired != nullptr &&
    1049          12 :         json_object_get_type(poLastAcquired) == json_type_string)
    1050             :     {
    1051          12 :         SetMetadataItem("LAST_ACQUIRED",
    1052          12 :                         json_object_get_string(poLastAcquired));
    1053             :     }
    1054          12 :     json_object *poName = CPL_json_object_object_get(poMosaic, "name");
    1055          12 :     if (poName != nullptr && json_object_get_type(poName) == json_type_string)
    1056             :     {
    1057          12 :         SetMetadataItem("NAME", json_object_get_string(poName));
    1058             :     }
    1059             : 
    1060          12 :     json_object_put(poObj);
    1061          12 :     return TRUE;
    1062             : }
    1063             : 
    1064             : /************************************************************************/
    1065             : /*                          ListSubdatasets()                           */
    1066             : /************************************************************************/
    1067             : 
    1068           8 : std::vector<CPLString> PLMosaicDataset::ListSubdatasets()
    1069             : {
    1070           8 :     std::vector<CPLString> aosNameList;
    1071          16 :     CPLString osURL(osBaseURL);
    1072          13 :     while (osURL.size())
    1073             :     {
    1074           9 :         json_object *poObj = RunRequest(osURL);
    1075           9 :         if (poObj == nullptr)
    1076             :         {
    1077           3 :             return aosNameList;
    1078             :         }
    1079             : 
    1080           6 :         osURL = "";
    1081           6 :         json_object *poLinks = CPL_json_object_object_get(poObj, "_links");
    1082           8 :         if (poLinks != nullptr &&
    1083           2 :             json_object_get_type(poLinks) == json_type_object)
    1084             :         {
    1085           2 :             json_object *poNext = CPL_json_object_object_get(poLinks, "_next");
    1086           3 :             if (poNext != nullptr &&
    1087           1 :                 json_object_get_type(poNext) == json_type_string)
    1088             :             {
    1089           1 :                 osURL = json_object_get_string(poNext);
    1090             :             }
    1091             :         }
    1092             : 
    1093           6 :         json_object *poMosaics = CPL_json_object_object_get(poObj, "mosaics");
    1094          11 :         if (poMosaics == nullptr ||
    1095           5 :             json_object_get_type(poMosaics) != json_type_array)
    1096             :         {
    1097           1 :             json_object_put(poObj);
    1098           1 :             return aosNameList;
    1099             :         }
    1100             : 
    1101           5 :         const auto nMosaics = json_object_array_length(poMosaics);
    1102          10 :         for (auto i = decltype(nMosaics){0}; i < nMosaics; i++)
    1103             :         {
    1104           5 :             const char *pszName = nullptr;
    1105           5 :             const char *pszCoordinateSystem = nullptr;
    1106           5 :             json_object *poMosaic = json_object_array_get_idx(poMosaics, i);
    1107           5 :             bool bAccessible = false;
    1108           5 :             if (poMosaic && json_object_get_type(poMosaic) == json_type_object)
    1109             :             {
    1110             :                 json_object *poName =
    1111           5 :                     CPL_json_object_object_get(poMosaic, "name");
    1112          10 :                 if (poName != nullptr &&
    1113           5 :                     json_object_get_type(poName) == json_type_string)
    1114             :                 {
    1115           5 :                     pszName = json_object_get_string(poName);
    1116             :                 }
    1117             : 
    1118             :                 json_object *poCoordinateSystem =
    1119           5 :                     CPL_json_object_object_get(poMosaic, "coordinate_system");
    1120          10 :                 if (poCoordinateSystem &&
    1121           5 :                     json_object_get_type(poCoordinateSystem) ==
    1122             :                         json_type_string)
    1123             :                 {
    1124             :                     pszCoordinateSystem =
    1125           5 :                         json_object_get_string(poCoordinateSystem);
    1126             :                 }
    1127             : 
    1128             :                 json_object *poDataType =
    1129           5 :                     CPL_json_object_object_get(poMosaic, "datatype");
    1130           5 :                 if (poDataType &&
    1131           0 :                     json_object_get_type(poDataType) == json_type_string &&
    1132           5 :                     EQUAL(json_object_get_string(poDataType), "byte") &&
    1133           0 :                     !CSLTestBoolean(CPLGetConfigOption(
    1134             :                         "PL_MOSAIC_LIST_QUAD_DOWNLOAD_ONLY", "NO")))
    1135             :                 {
    1136           0 :                     bAccessible = true;  // through tile API
    1137             :                 }
    1138             :                 else
    1139             :                 {
    1140             :                     json_object *poQuadDownload =
    1141           5 :                         CPL_json_object_object_get(poMosaic, "quad_download");
    1142             :                     bAccessible =
    1143           5 :                         CPL_TO_BOOL(json_object_get_boolean(poQuadDownload));
    1144             :                 }
    1145             :             }
    1146             : 
    1147           5 :             if (bAccessible && pszName && pszCoordinateSystem &&
    1148           5 :                 EQUAL(pszCoordinateSystem, "EPSG:3857"))
    1149             :             {
    1150           4 :                 aosNameList.push_back(pszName);
    1151             :             }
    1152             :         }
    1153             : 
    1154           5 :         json_object_put(poObj);
    1155             :     }
    1156           4 :     return aosNameList;
    1157             : }
    1158             : 
    1159             : /************************************************************************/
    1160             : /*                            GetSpatialRef()                           */
    1161             : /************************************************************************/
    1162             : 
    1163           1 : const OGRSpatialReference *PLMosaicDataset::GetSpatialRef() const
    1164             : 
    1165             : {
    1166           1 :     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    1167             : }
    1168             : 
    1169             : /************************************************************************/
    1170             : /*                            GetGeoTransform()                         */
    1171             : /************************************************************************/
    1172             : 
    1173           2 : CPLErr PLMosaicDataset::GetGeoTransform(double *padfGeoTransform)
    1174             : {
    1175           2 :     memcpy(padfGeoTransform, adfGeoTransform, 6 * sizeof(double));
    1176           2 :     return (bHasGeoTransform) ? CE_None : CE_Failure;
    1177             : }
    1178             : 
    1179             : /************************************************************************/
    1180             : /*                          formatTileName()                            */
    1181             : /************************************************************************/
    1182             : 
    1183          66 : CPLString PLMosaicDataset::formatTileName(int tile_x, int tile_y)
    1184             : 
    1185             : {
    1186          66 :     return CPLSPrintf("%d-%d", tile_x, tile_y);
    1187             : }
    1188             : 
    1189             : /************************************************************************/
    1190             : /*                          InsertNewDataset()                          */
    1191             : /************************************************************************/
    1192             : 
    1193          25 : void PLMosaicDataset::InsertNewDataset(const CPLString &osKey,
    1194             :                                        GDALDataset *poDS)
    1195             : {
    1196          25 :     if (static_cast<int>(oMapLinkedDatasets.size()) == nCacheMaxSize)
    1197             :     {
    1198           6 :         CPLDebug("PLMOSAIC", "Discarding older entry %s from cache",
    1199           6 :                  psTail->osKey.c_str());
    1200           6 :         oMapLinkedDatasets.erase(psTail->osKey);
    1201           6 :         PLLinkedDataset *psNewTail = psTail->psPrev;
    1202           6 :         psNewTail->psNext = nullptr;
    1203           6 :         if (psTail->poDS)
    1204           0 :             GDALClose(psTail->poDS);
    1205           6 :         delete psTail;
    1206           6 :         psTail = psNewTail;
    1207             :     }
    1208             : 
    1209          25 :     PLLinkedDataset *psLinkedDataset = new PLLinkedDataset();
    1210          25 :     if (psHead)
    1211          15 :         psHead->psPrev = psLinkedDataset;
    1212          25 :     psLinkedDataset->osKey = osKey;
    1213          25 :     psLinkedDataset->psNext = psHead;
    1214          25 :     psLinkedDataset->poDS = poDS;
    1215          25 :     psHead = psLinkedDataset;
    1216          25 :     if (psTail == nullptr)
    1217          10 :         psTail = psHead;
    1218          25 :     oMapLinkedDatasets[osKey] = psLinkedDataset;
    1219          25 : }
    1220             : 
    1221             : /************************************************************************/
    1222             : /*                         OpenAndInsertNewDataset()                    */
    1223             : /************************************************************************/
    1224             : 
    1225             : GDALDataset *
    1226           9 : PLMosaicDataset::OpenAndInsertNewDataset(const CPLString &osTmpFilename,
    1227             :                                          const CPLString &osTilename)
    1228             : {
    1229           9 :     const char *const apszAllowedDrivers[2] = {"GTiff", nullptr};
    1230           9 :     GDALDataset *poDS = GDALDataset::FromHandle(
    1231             :         GDALOpenEx(osTmpFilename, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
    1232             :                    apszAllowedDrivers, nullptr, nullptr));
    1233           9 :     if (poDS != nullptr)
    1234             :     {
    1235           7 :         if (poDS->GetRasterXSize() != nQuadSize ||
    1236           7 :             poDS->GetRasterYSize() != nQuadSize || poDS->GetRasterCount() != 4)
    1237             :         {
    1238           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1239             :                      "Inconsistent metatile characteristics");
    1240           0 :             GDALClose(poDS);
    1241           0 :             poDS = nullptr;
    1242             :         }
    1243             :     }
    1244             :     else
    1245             :     {
    1246           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid GTiff dataset: %s",
    1247             :                  osTilename.c_str());
    1248             :     }
    1249             : 
    1250           9 :     InsertNewDataset(osTilename, poDS);
    1251           9 :     return poDS;
    1252             : }
    1253             : 
    1254             : /************************************************************************/
    1255             : /*                            GetMetaTile()                             */
    1256             : /************************************************************************/
    1257             : 
    1258          62 : GDALDataset *PLMosaicDataset::GetMetaTile(int tile_x, int tile_y)
    1259             : {
    1260         124 :     const CPLString osTilename = formatTileName(tile_x, tile_y);
    1261             :     std::map<CPLString, PLLinkedDataset *>::const_iterator it =
    1262          62 :         oMapLinkedDatasets.find(osTilename);
    1263          62 :     if (it == oMapLinkedDatasets.end())
    1264             :     {
    1265          50 :         CPLString osTmpFilename;
    1266             : 
    1267          50 :         const CPLString osMosaicPath(GetMosaicCachePath());
    1268             :         osTmpFilename =
    1269          25 :             CPLFormFilenameSafe(osMosaicPath,
    1270             :                                 CPLSPrintf("%s_%s.tif", osMosaic.c_str(),
    1271             :                                            CPLGetFilename(osTilename)),
    1272          25 :                                 nullptr);
    1273             :         VSIStatBufL sStatBuf;
    1274             : 
    1275          50 :         CPLString osURL = osQuadsURL;
    1276          25 :         osURL += osTilename;
    1277          25 :         osURL += "/full";
    1278             : 
    1279          25 :         if (!osCachePathRoot.empty() && VSIStatL(osTmpFilename, &sStatBuf) == 0)
    1280             :         {
    1281           3 :             if (bTrustCache)
    1282             :             {
    1283           1 :                 return OpenAndInsertNewDataset(osTmpFilename, osTilename);
    1284             :             }
    1285             : 
    1286           2 :             CPLDebug("PLMOSAIC",
    1287             :                      "File %s exists. Checking if it is up-to-date...",
    1288             :                      osTmpFilename.c_str());
    1289             :             // Currently we only check by file size, which should be good enough
    1290             :             // as the metatiles are compressed, so a change in content is likely
    1291             :             // to cause a change in filesize. Use of a signature would be better
    1292             :             // though if available in the metadata
    1293             :             VSIStatBufL sRemoteTileStatBuf;
    1294           2 :             char *pszEscapedURL = CPLEscapeString(
    1295           4 :                 (osURL + "?api_key=" + osAPIKey).c_str(), -1, CPLES_URL);
    1296           2 :             CPLString osVSICURLUrl(STARTS_WITH(osURL, "/vsimem/")
    1297           4 :                                        ? osURL
    1298             :                                        : "/vsicurl?use_head=no&url=" +
    1299           6 :                                              CPLString(pszEscapedURL));
    1300           2 :             CPLFree(pszEscapedURL);
    1301           2 :             if (VSIStatL(osVSICURLUrl, &sRemoteTileStatBuf) == 0 &&
    1302           0 :                 sRemoteTileStatBuf.st_size == sStatBuf.st_size)
    1303             :             {
    1304           0 :                 CPLDebug("PLMOSAIC", "Cached tile is up-to-date");
    1305           0 :                 return OpenAndInsertNewDataset(osTmpFilename, osTilename);
    1306             :             }
    1307             :             else
    1308             :             {
    1309           2 :                 CPLDebug("PLMOSAIC", "Cached tile is not up-to-date");
    1310           2 :                 VSIUnlink(osTmpFilename);
    1311             :             }
    1312             :         }
    1313             : 
    1314             :         // Fetch the GeoTIFF now
    1315             : 
    1316          24 :         CPLHTTPResult *psResult = Download(osURL, TRUE);
    1317          24 :         if (psResult == nullptr)
    1318             :         {
    1319          16 :             InsertNewDataset(osTilename, nullptr);
    1320          16 :             return nullptr;
    1321             :         }
    1322             : 
    1323           8 :         CreateMosaicCachePathIfNecessary();
    1324             : 
    1325           8 :         bool bUnlink = false;
    1326             :         VSILFILE *fp =
    1327           8 :             osCachePathRoot.size() ? VSIFOpenL(osTmpFilename, "wb") : nullptr;
    1328           8 :         if (fp)
    1329             :         {
    1330           6 :             VSIFWriteL(psResult->pabyData, 1, psResult->nDataLen, fp);
    1331           6 :             VSIFCloseL(fp);
    1332             :         }
    1333             :         else
    1334             :         {
    1335             :             // In case there's no temporary path or it is not writable
    1336             :             // use a in-memory dataset, and limit the cache to only one
    1337           2 :             if (!osCachePathRoot.empty() && nCacheMaxSize > 1)
    1338             :             {
    1339           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1340             :                          "Cannot write into %s. Using /vsimem and reduce cache "
    1341             :                          "to 1 entry",
    1342             :                          osCachePathRoot.c_str());
    1343           1 :                 FlushDatasetsCache();
    1344           1 :                 nCacheMaxSize = 1;
    1345             :             }
    1346           2 :             bUnlink = true;
    1347             :             osTmpFilename = VSIMemGenerateHiddenFilename(
    1348             :                 CPLSPrintf("single_tile_plmosaic_cache_%s_%d_%d.tif",
    1349           2 :                            osMosaic.c_str(), tile_x, tile_y));
    1350           2 :             fp = VSIFOpenL(osTmpFilename, "wb");
    1351           2 :             if (fp)
    1352             :             {
    1353           2 :                 VSIFWriteL(psResult->pabyData, 1, psResult->nDataLen, fp);
    1354           2 :                 VSIFCloseL(fp);
    1355             :             }
    1356             :         }
    1357           8 :         CPLHTTPDestroyResult(psResult);
    1358           8 :         GDALDataset *poDS = OpenAndInsertNewDataset(osTmpFilename, osTilename);
    1359             : 
    1360           8 :         if (bUnlink)
    1361           2 :             VSIUnlink(osTilename);
    1362             : 
    1363           8 :         return poDS;
    1364             :     }
    1365             : 
    1366             :     // Move link to head of MRU list
    1367          37 :     PLLinkedDataset *psLinkedDataset = it->second;
    1368          37 :     GDALDataset *poDS = psLinkedDataset->poDS;
    1369          37 :     if (psLinkedDataset != psHead)
    1370             :     {
    1371          18 :         if (psLinkedDataset == psTail)
    1372           2 :             psTail = psLinkedDataset->psPrev;
    1373          18 :         if (psLinkedDataset->psPrev)
    1374          18 :             psLinkedDataset->psPrev->psNext = psLinkedDataset->psNext;
    1375          18 :         if (psLinkedDataset->psNext)
    1376          16 :             psLinkedDataset->psNext->psPrev = psLinkedDataset->psPrev;
    1377          18 :         psLinkedDataset->psNext = psHead;
    1378          18 :         psLinkedDataset->psPrev = nullptr;
    1379          18 :         psHead->psPrev = psLinkedDataset;
    1380          18 :         psHead = psLinkedDataset;
    1381             :     }
    1382             : 
    1383          37 :     return poDS;
    1384             : }
    1385             : 
    1386             : /************************************************************************/
    1387             : /*                         GetLocationInfo()                            */
    1388             : /************************************************************************/
    1389             : 
    1390           4 : const char *PLMosaicDataset::GetLocationInfo(int nPixel, int nLine)
    1391             : {
    1392             :     int nBlockXSize, nBlockYSize;
    1393           4 :     GetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
    1394             : 
    1395           4 :     const int nBlockXOff = nPixel / nBlockXSize;
    1396           4 :     const int nBlockYOff = nLine / nBlockYSize;
    1397           4 :     const int bottom_yblock =
    1398           4 :         (nRasterYSize - nBlockYOff * nBlockYSize) / nBlockYSize - 1;
    1399             : 
    1400           4 :     const int meta_tile_x =
    1401           4 :         nMetaTileXShift + (nBlockXOff * nBlockXSize) / nQuadSize;
    1402           4 :     const int meta_tile_y =
    1403           4 :         nMetaTileYShift + (bottom_yblock * nBlockYSize) / nQuadSize;
    1404             : 
    1405           8 :     CPLString osQuadURL = osQuadsURL;
    1406           8 :     CPLString osTilename = formatTileName(meta_tile_x, meta_tile_y);
    1407           4 :     osQuadURL += osTilename;
    1408             : 
    1409           4 :     if (meta_tile_x != nLastMetaTileX || meta_tile_y != nLastMetaTileY)
    1410             :     {
    1411           3 :         const CPLString osQuadScenesURL = osQuadURL + "/items";
    1412             : 
    1413           3 :         json_object_put(poLastItemsInformation);
    1414           3 :         poLastItemsInformation = RunRequest(osQuadScenesURL, TRUE);
    1415             : 
    1416           3 :         nLastMetaTileX = meta_tile_x;
    1417           3 :         nLastMetaTileY = meta_tile_y;
    1418             :     }
    1419             : 
    1420           4 :     osLastRetGetLocationInfo.clear();
    1421             : 
    1422           4 :     CPLXMLNode *psRoot = CPLCreateXMLNode(nullptr, CXT_Element, "LocationInfo");
    1423             : 
    1424           4 :     if (poLastItemsInformation)
    1425             :     {
    1426             :         json_object *poItems =
    1427           2 :             CPL_json_object_object_get(poLastItemsInformation, "items");
    1428           4 :         if (poItems && json_object_get_type(poItems) == json_type_array &&
    1429           2 :             json_object_array_length(poItems) != 0)
    1430             :         {
    1431             :             CPLXMLNode *psScenes =
    1432           2 :                 CPLCreateXMLNode(psRoot, CXT_Element, "Scenes");
    1433           2 :             const auto nItemsLength = json_object_array_length(poItems);
    1434           4 :             for (auto i = decltype(nItemsLength){0}; i < nItemsLength; i++)
    1435             :             {
    1436           2 :                 json_object *poObj = json_object_array_get_idx(poItems, i);
    1437           2 :                 if (poObj && json_object_get_type(poObj) == json_type_object)
    1438             :                 {
    1439             :                     json_object *poLink =
    1440           2 :                         CPL_json_object_object_get(poObj, "link");
    1441           2 :                     if (poLink)
    1442             :                     {
    1443             :                         CPLXMLNode *psScene =
    1444           2 :                             CPLCreateXMLNode(psScenes, CXT_Element, "Scene");
    1445             :                         CPLXMLNode *psItem =
    1446           2 :                             CPLCreateXMLNode(psScene, CXT_Element, "link");
    1447           2 :                         CPLCreateXMLNode(psItem, CXT_Text,
    1448             :                                          json_object_get_string(poLink));
    1449             :                     }
    1450             :                 }
    1451             :             }
    1452             :         }
    1453             :     }
    1454             : 
    1455           4 :     char *pszXML = CPLSerializeXMLTree(psRoot);
    1456           4 :     CPLDestroyXMLNode(psRoot);
    1457           4 :     osLastRetGetLocationInfo = pszXML;
    1458           4 :     CPLFree(pszXML);
    1459             : 
    1460           8 :     return osLastRetGetLocationInfo.c_str();
    1461             : }
    1462             : 
    1463             : /************************************************************************/
    1464             : /*                             IRasterIO()                              */
    1465             : /************************************************************************/
    1466             : 
    1467           6 : CPLErr PLMosaicDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
    1468             :                                   int nXSize, int nYSize, void *pData,
    1469             :                                   int nBufXSize, int nBufYSize,
    1470             :                                   GDALDataType eBufType, int nBandCount,
    1471             :                                   BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
    1472             :                                   GSpacing nLineSpace, GSpacing nBandSpace,
    1473             :                                   GDALRasterIOExtraArg *psExtraArg)
    1474             : {
    1475           6 :     if (bUseTMSForMain && !apoTMSDS.empty())
    1476           1 :         return apoTMSDS[0]->RasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
    1477             :                                      pData, nBufXSize, nBufYSize, eBufType,
    1478             :                                      nBandCount, panBandMap, nPixelSpace,
    1479           1 :                                      nLineSpace, nBandSpace, psExtraArg);
    1480             : 
    1481           5 :     return BlockBasedRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
    1482             :                               nBufXSize, nBufYSize, eBufType, nBandCount,
    1483             :                               panBandMap, nPixelSpace, nLineSpace, nBandSpace,
    1484           5 :                               psExtraArg);
    1485             : }
    1486             : 
    1487             : /************************************************************************/
    1488             : /*                      GDALRegister_PLMOSAIC()                         */
    1489             : /************************************************************************/
    1490             : 
    1491        1686 : void GDALRegister_PLMOSAIC()
    1492             : 
    1493             : {
    1494        1686 :     if (GDALGetDriverByName("PLMOSAIC") != nullptr)
    1495         302 :         return;
    1496             : 
    1497        1384 :     GDALDriver *poDriver = new GDALDriver();
    1498             : 
    1499        1384 :     poDriver->SetDescription("PLMOSAIC");
    1500        1384 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1501        1384 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Planet Labs Mosaics API");
    1502        1384 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
    1503        1384 :                               "drivers/raster/plmosaic.html");
    1504             : 
    1505        1384 :     poDriver->SetMetadataItem(GDAL_DMD_CONNECTION_PREFIX, "PLMOSAIC:");
    1506             : 
    1507        1384 :     poDriver->SetMetadataItem(
    1508             :         GDAL_DMD_OPENOPTIONLIST,
    1509             :         "<OpenOptionList>"
    1510             :         "  <Option name='API_KEY' type='string' description='Account API key' "
    1511             :         "required='true'/>"
    1512             :         "  <Option name='MOSAIC' type='string' description='Mosaic name'/>"
    1513             :         "  <Option name='CACHE_PATH' type='string' description='Directory "
    1514             :         "where to put cached quads'/>"
    1515             :         "  <Option name='TRUST_CACHE' type='boolean' description='Whether "
    1516             :         "already cached quads should be trusted as the most recent version' "
    1517             :         "default='NO'/>"
    1518             :         "  <Option name='USE_TILES' type='boolean' description='Whether to use "
    1519             :         "the tile API even for full resolution data (only for Byte mosaics)' "
    1520             :         "default='NO'/>"
    1521        1384 :         "</OpenOptionList>");
    1522             : 
    1523        1384 :     poDriver->pfnIdentify = PLMosaicDataset::Identify;
    1524        1384 :     poDriver->pfnOpen = PLMosaicDataset::Open;
    1525             : 
    1526        1384 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1527             : }

Generated by: LCOV version 1.14