LCOV - code coverage report
Current view: top level - frmts/plmosaic - plmosaicdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 626 659 95.0 %
Date: 2025-07-08 21:33:46 Functions: 33 34 97.1 %

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

Generated by: LCOV version 1.14