Coverage Report

Created: 2026-06-30 08:33

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