Coverage Report

Created: 2025-06-09 07:43

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