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

Generated by: LCOV version 1.14