Coverage Report

Created: 2026-02-14 09:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/stacta/stactadataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  STACTA (Spatio-Temporal Asset Catalog Tiled Assets) driver
5
 * Author:   Even Rouault, <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2020, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "cpl_json.h"
14
#include "cpl_mem_cache.h"
15
#include "cpl_string.h"
16
#include "gdal_pam.h"
17
#include "gdal_frmts.h"
18
#include "gdal_utils.h"
19
#include "memdataset.h"
20
#include "tilematrixset.hpp"
21
#include "stactadataset.h"
22
23
#include <algorithm>
24
#include <array>
25
#include <limits>
26
#include <map>
27
#include <memory>
28
#include <vector>
29
30
// Implements a driver for
31
// https://github.com/stac-extensions/tiled-assets
32
33
/************************************************************************/
34
/*                         GetAllowedDrivers()                          */
35
/************************************************************************/
36
37
static CPLStringList GetAllowedDrivers()
38
56
{
39
56
    CPLStringList aosAllowedDrivers;
40
56
    aosAllowedDrivers.AddString("GTiff");
41
56
    aosAllowedDrivers.AddString("PNG");
42
56
    aosAllowedDrivers.AddString("JPEG");
43
56
    aosAllowedDrivers.AddString("JPEGXL");
44
56
    aosAllowedDrivers.AddString("WEBP");
45
56
    aosAllowedDrivers.AddString("JP2KAK");
46
56
    aosAllowedDrivers.AddString("JP2ECW");
47
56
    aosAllowedDrivers.AddString("JP2MrSID");
48
56
    aosAllowedDrivers.AddString("JP2OpenJPEG");
49
56
    return aosAllowedDrivers;
50
56
}
51
52
/************************************************************************/
53
/*                          STACTARasterBand()                          */
54
/************************************************************************/
55
56
STACTARasterBand::STACTARasterBand(STACTADataset *poDSIn, int nBandIn,
57
                                   GDALRasterBand *poProtoBand)
58
60
    : m_eColorInterp(poProtoBand->GetColorInterpretation())
59
60
{
60
60
    poDS = poDSIn;
61
60
    nBand = nBandIn;
62
60
    eDataType = poProtoBand->GetRasterDataType();
63
60
    poProtoBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
64
60
    nRasterXSize = poDSIn->GetRasterXSize();
65
60
    nRasterYSize = poDSIn->GetRasterYSize();
66
60
    m_dfNoData = poProtoBand->GetNoDataValue(&m_bHasNoDataValue);
67
60
}
68
69
/************************************************************************/
70
/*                             IReadBlock()                             */
71
/************************************************************************/
72
73
CPLErr STACTARasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
74
                                    void *pImage)
75
0
{
76
0
    auto poGDS = cpl::down_cast<STACTADataset *>(poDS);
77
0
    return poGDS->m_poDS->GetRasterBand(nBand)->ReadBlock(nBlockXOff,
78
0
                                                          nBlockYOff, pImage);
79
0
}
80
81
/************************************************************************/
82
/*                             IRasterIO()                              */
83
/************************************************************************/
84
85
CPLErr STACTARasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
86
                                   int nXSize, int nYSize, void *pData,
87
                                   int nBufXSize, int nBufYSize,
88
                                   GDALDataType eBufType, GSpacing nPixelSpace,
89
                                   GSpacing nLineSpace,
90
                                   GDALRasterIOExtraArg *psExtraArg)
91
48
{
92
48
    auto poGDS = cpl::down_cast<STACTADataset *>(poDS);
93
48
    if ((nBufXSize < nXSize || nBufYSize < nYSize) &&
94
0
        poGDS->m_apoOverviewDS.size() >= 1 && eRWFlag == GF_Read)
95
0
    {
96
0
        int bTried;
97
0
        CPLErr eErr = TryOverviewRasterIO(
98
0
            eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
99
0
            eBufType, nPixelSpace, nLineSpace, psExtraArg, &bTried);
100
0
        if (bTried)
101
0
            return eErr;
102
0
    }
103
104
48
    return poGDS->m_poDS->GetRasterBand(nBand)->RasterIO(
105
48
        eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
106
48
        eBufType, nPixelSpace, nLineSpace, psExtraArg);
107
48
}
108
109
/************************************************************************/
110
/*                             IRasterIO()                              */
111
/************************************************************************/
112
113
CPLErr STACTADataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
114
                                int nXSize, int nYSize, void *pData,
115
                                int nBufXSize, int nBufYSize,
116
                                GDALDataType eBufType, int nBandCount,
117
                                BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
118
                                GSpacing nLineSpace, GSpacing nBandSpace,
119
                                GDALRasterIOExtraArg *psExtraArg)
120
0
{
121
0
    if ((nBufXSize < nXSize || nBufYSize < nYSize) &&
122
0
        m_apoOverviewDS.size() >= 1 && eRWFlag == GF_Read)
123
0
    {
124
0
        int bTried;
125
0
        CPLErr eErr = TryOverviewRasterIO(
126
0
            eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
127
0
            eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace,
128
0
            nBandSpace, psExtraArg, &bTried);
129
0
        if (bTried)
130
0
            return eErr;
131
0
    }
132
133
0
    return m_poDS->RasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
134
0
                            nBufXSize, nBufYSize, eBufType, nBandCount,
135
0
                            panBandMap, nPixelSpace, nLineSpace, nBandSpace,
136
0
                            psExtraArg);
137
0
}
138
139
/************************************************************************/
140
/*                          GetOverviewCount()                          */
141
/************************************************************************/
142
143
int STACTARasterBand::GetOverviewCount()
144
25
{
145
25
    STACTADataset *poGDS = cpl::down_cast<STACTADataset *>(poDS);
146
25
    return static_cast<int>(poGDS->m_apoOverviewDS.size());
147
25
}
148
149
/************************************************************************/
150
/*                            GetOverview()                             */
151
/************************************************************************/
152
153
GDALRasterBand *STACTARasterBand::GetOverview(int nIdx)
154
15
{
155
15
    STACTADataset *poGDS = cpl::down_cast<STACTADataset *>(poDS);
156
15
    if (nIdx < 0 || nIdx >= GetOverviewCount())
157
0
        return nullptr;
158
15
    return poGDS->m_apoOverviewDS[nIdx]->GetRasterBand(nBand);
159
15
}
160
161
/************************************************************************/
162
/*                           GetNoDataValue()                           */
163
/************************************************************************/
164
165
double STACTARasterBand::GetNoDataValue(int *pbHasNoData)
166
40
{
167
40
    if (pbHasNoData)
168
30
        *pbHasNoData = m_bHasNoDataValue;
169
40
    return m_dfNoData;
170
40
}
171
172
/************************************************************************/
173
/*                        STACTARawRasterBand()                         */
174
/************************************************************************/
175
176
STACTARawRasterBand::STACTARawRasterBand(STACTARawDataset *poDSIn, int nBandIn,
177
                                         GDALRasterBand *poProtoBand)
178
0
    : m_eColorInterp(poProtoBand->GetColorInterpretation())
179
0
{
180
0
    poDS = poDSIn;
181
0
    nBand = nBandIn;
182
0
    eDataType = poProtoBand->GetRasterDataType();
183
0
    nBlockXSize = 256;
184
0
    nBlockYSize = 256;
185
0
    int nProtoBlockXSize;
186
0
    int nProtoBlockYSize;
187
    // Use tile block size if it divides the metatile dimension.
188
0
    poProtoBand->GetBlockSize(&nProtoBlockXSize, &nProtoBlockYSize);
189
0
    if ((poDSIn->m_nMetaTileWidth % nProtoBlockXSize) == 0 &&
190
0
        (poDSIn->m_nMetaTileHeight % nProtoBlockYSize) == 0)
191
0
    {
192
0
        nBlockXSize = nProtoBlockXSize;
193
0
        nBlockYSize = nProtoBlockYSize;
194
0
    }
195
0
    nRasterXSize = poDSIn->GetRasterXSize();
196
0
    nRasterYSize = poDSIn->GetRasterYSize();
197
0
    m_dfNoData = poProtoBand->GetNoDataValue(&m_bHasNoDataValue);
198
0
}
199
200
/************************************************************************/
201
/*                        STACTARawRasterBand()                         */
202
/************************************************************************/
203
204
STACTARawRasterBand::STACTARawRasterBand(STACTARawDataset *poDSIn, int nBandIn,
205
                                         GDALDataType eDT, bool bSetNoData,
206
                                         double dfNoData)
207
162
{
208
162
    poDS = poDSIn;
209
162
    nBand = nBandIn;
210
162
    eDataType = eDT;
211
162
    nBlockXSize = 256;
212
162
    nBlockYSize = 256;
213
162
    nRasterXSize = poDSIn->GetRasterXSize();
214
162
    nRasterYSize = poDSIn->GetRasterYSize();
215
162
    m_bHasNoDataValue = bSetNoData;
216
162
    m_dfNoData = dfNoData;
217
162
}
218
219
/************************************************************************/
220
/*                           GetNoDataValue()                           */
221
/************************************************************************/
222
223
double STACTARawRasterBand::GetNoDataValue(int *pbHasNoData)
224
136
{
225
136
    if (pbHasNoData)
226
122
        *pbHasNoData = m_bHasNoDataValue;
227
136
    return m_dfNoData;
228
136
}
229
230
/************************************************************************/
231
/*                             IReadBlock()                             */
232
/************************************************************************/
233
234
CPLErr STACTARawRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
235
                                       void *pImage)
236
0
{
237
0
    const int nXOff = nBlockXOff * nBlockXSize;
238
0
    const int nYOff = nBlockYOff * nBlockYSize;
239
0
    const int nXSize = std::min(nBlockXSize, nRasterXSize - nXOff);
240
0
    const int nYSize = std::min(nBlockYSize, nRasterYSize - nYOff);
241
0
    GDALRasterIOExtraArg sExtraArgs;
242
0
    INIT_RASTERIO_EXTRA_ARG(sExtraArgs);
243
0
    const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
244
0
    return IRasterIO(GF_Read, nXOff, nYOff, nXSize, nYSize, pImage, nBlockXSize,
245
0
                     nBlockYSize, eDataType, nDTSize,
246
0
                     static_cast<GSpacing>(nDTSize) * nBlockXSize, &sExtraArgs);
247
0
}
248
249
/************************************************************************/
250
/*                             IRasterIO()                              */
251
/************************************************************************/
252
253
CPLErr STACTARawRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
254
                                      int nXSize, int nYSize, void *pData,
255
                                      int nBufXSize, int nBufYSize,
256
                                      GDALDataType eBufType,
257
                                      GSpacing nPixelSpace, GSpacing nLineSpace,
258
                                      GDALRasterIOExtraArg *psExtraArg)
259
48
{
260
48
    CPLDebugOnly("STACTA", "Band %d RasterIO: %d,%d,%d,%d->%d,%d", nBand, nXOff,
261
48
                 nYOff, nXSize, nYSize, nBufXSize, nBufYSize);
262
48
    auto poGDS = cpl::down_cast<STACTARawDataset *>(poDS);
263
264
48
    const int nKernelRadius = 3;  // up to 3 for Lanczos
265
48
    const int nRadiusX =
266
48
        nKernelRadius * static_cast<int>(std::ceil(nXSize / nBufXSize));
267
48
    const int nRadiusY =
268
48
        nKernelRadius * static_cast<int>(std::ceil(nYSize / nBufYSize));
269
48
    const int nXOffMod = std::max(0, nXOff - nRadiusX);
270
48
    const int nYOffMod = std::max(0, nYOff - nRadiusY);
271
48
    const int nXSizeMod = static_cast<int>(std::min(
272
48
                              nXOff + nXSize + static_cast<GIntBig>(nRadiusX),
273
48
                              static_cast<GIntBig>(nRasterXSize))) -
274
48
                          nXOffMod;
275
48
    const int nYSizeMod = static_cast<int>(std::min(
276
48
                              nYOff + nYSize + static_cast<GIntBig>(nRadiusY),
277
48
                              static_cast<GIntBig>(nRasterYSize))) -
278
48
                          nYOffMod;
279
280
48
    const bool bRequestFitsInSingleMetaTile =
281
48
        nXOffMod / poGDS->m_nMetaTileWidth ==
282
48
            (nXOffMod + nXSizeMod - 1) / poGDS->m_nMetaTileWidth &&
283
6
        nYOffMod / poGDS->m_nMetaTileHeight ==
284
6
            (nYOffMod + nYSizeMod - 1) / poGDS->m_nMetaTileHeight;
285
286
48
    if (eRWFlag != GF_Read || ((nXSize != nBufXSize || nYSize != nBufYSize) &&
287
0
                               !bRequestFitsInSingleMetaTile))
288
0
    {
289
0
        if (!(eRWFlag == GF_Read && nXSizeMod <= 4096 && nYSizeMod <= 4096))
290
0
        {
291
            // If not reading at nominal resolution, fallback to default block
292
            // reading
293
0
            return GDALRasterBand::IRasterIO(
294
0
                eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize,
295
0
                nBufYSize, eBufType, nPixelSpace, nLineSpace, psExtraArg);
296
0
        }
297
0
    }
298
299
    // Use optimized dataset level RasterIO()
300
48
    return poGDS->IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
301
48
                            nBufXSize, nBufYSize, eBufType, 1, &nBand,
302
48
                            nPixelSpace, nLineSpace, 0, psExtraArg);
303
48
}
304
305
/************************************************************************/
306
/*                       DoVSICLOUDSubstitution()                       */
307
/************************************************************************/
308
309
static std::string DoVSICLOUDSubstitution(const std::string &osFilename)
310
0
{
311
0
    std::string ret;
312
0
    constexpr const char *HTTPS_PROTOCOL = "https://";
313
0
    if (cpl::starts_with(osFilename, HTTPS_PROTOCOL))
314
0
    {
315
0
        constexpr const char *AZURE_BLOB = ".blob.core.windows.net/";
316
0
        constexpr const char *AWS = ".amazonaws.com/";
317
0
        constexpr const char *GOOGLE_CLOUD_STORAGE =
318
0
            "https://storage.googleapis.com/";
319
0
        size_t nPos;
320
0
        if ((nPos = osFilename.find(AZURE_BLOB)) != std::string::npos)
321
0
        {
322
0
            ret = "/vsiaz/" + osFilename.substr(nPos + strlen(AZURE_BLOB));
323
0
        }
324
0
        else if ((nPos = osFilename.find(AWS)) != std::string::npos)
325
0
        {
326
0
            constexpr const char *DOT_S3_DOT = ".s3.";
327
0
            const auto nPos2 = osFilename.find(DOT_S3_DOT);
328
0
            if (nPos2 != std::string::npos)
329
0
            {
330
0
                ret = "/vsis3/" +
331
0
                      osFilename.substr(strlen(HTTPS_PROTOCOL),
332
0
                                        nPos2 - strlen(HTTPS_PROTOCOL)) +
333
0
                      "/" + osFilename.substr(nPos + strlen(AWS));
334
0
            }
335
0
        }
336
0
        else if (cpl::starts_with(osFilename, GOOGLE_CLOUD_STORAGE))
337
0
        {
338
0
            ret = "/vsigs/" + osFilename.substr(strlen(GOOGLE_CLOUD_STORAGE));
339
0
        }
340
0
    }
341
0
    return ret;
342
0
}
343
344
/************************************************************************/
345
/*                             IRasterIO()                              */
346
/************************************************************************/
347
348
CPLErr STACTARawDataset::IRasterIO(
349
    GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
350
    void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
351
    int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
352
    GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
353
48
{
354
48
    CPLDebugOnly("STACTA", "Dataset RasterIO: %d,%d,%d,%d->%d,%d", nXOff, nYOff,
355
48
                 nXSize, nYSize, nBufXSize, nBufYSize);
356
48
    const int nMinBlockX = nXOff / m_nMetaTileWidth;
357
48
    const int nMaxBlockX = (nXOff + nXSize - 1) / m_nMetaTileWidth;
358
48
    const int nMinBlockY = nYOff / m_nMetaTileHeight;
359
48
    const int nMaxBlockY = (nYOff + nYSize - 1) / m_nMetaTileHeight;
360
361
48
    const int nKernelRadius = 3;  // up to 3 for Lanczos
362
48
    const int nRadiusX =
363
48
        nKernelRadius * static_cast<int>(std::ceil(nXSize / nBufXSize));
364
48
    const int nRadiusY =
365
48
        nKernelRadius * static_cast<int>(std::ceil(nYSize / nBufYSize));
366
48
    const int nXOffMod = std::max(0, nXOff - nRadiusX);
367
48
    const int nYOffMod = std::max(0, nYOff - nRadiusY);
368
48
    const int nXSizeMod = static_cast<int>(std::min(
369
48
                              nXOff + nXSize + static_cast<GIntBig>(nRadiusX),
370
48
                              static_cast<GIntBig>(nRasterXSize))) -
371
48
                          nXOffMod;
372
48
    const int nYSizeMod = static_cast<int>(std::min(
373
48
                              nYOff + nYSize + static_cast<GIntBig>(nRadiusY),
374
48
                              static_cast<GIntBig>(nRasterYSize))) -
375
48
                          nYOffMod;
376
377
48
    const bool bRequestFitsInSingleMetaTile =
378
48
        nXOffMod / m_nMetaTileWidth ==
379
48
            (nXOffMod + nXSizeMod - 1) / m_nMetaTileWidth &&
380
6
        nYOffMod / m_nMetaTileHeight ==
381
6
            (nYOffMod + nYSizeMod - 1) / m_nMetaTileHeight;
382
48
    const auto eBandDT = GetRasterBand(1)->GetRasterDataType();
383
48
    const int nDTSize = GDALGetDataTypeSizeBytes(eBandDT);
384
385
48
    if (eRWFlag != GF_Read || ((nXSize != nBufXSize || nYSize != nBufYSize) &&
386
0
                               !bRequestFitsInSingleMetaTile))
387
0
    {
388
0
        if (eRWFlag == GF_Read && nXSizeMod <= 4096 && nYSizeMod <= 4096 &&
389
0
            nBandCount <= 10)
390
0
        {
391
            // If extracting from a small enough window, do a RasterIO()
392
            // at full resolution into a MEM dataset, and then proceeding to
393
            // resampling on it. This will avoid  to fallback on block based
394
            // approach.
395
0
            GDALRasterIOExtraArg sExtraArgs;
396
0
            INIT_RASTERIO_EXTRA_ARG(sExtraArgs);
397
0
            const size_t nXSizeModeMulYSizeModMulDTSize =
398
0
                static_cast<size_t>(nXSizeMod) * nYSizeMod * nDTSize;
399
0
            std::vector<GByte> abyBuf(nXSizeModeMulYSizeModMulDTSize *
400
0
                                      nBandCount);
401
0
            if (IRasterIO(GF_Read, nXOffMod, nYOffMod, nXSizeMod, nYSizeMod,
402
0
                          &abyBuf[0], nXSizeMod, nYSizeMod, eBandDT, nBandCount,
403
0
                          panBandMap, nDTSize,
404
0
                          static_cast<GSpacing>(nDTSize) * nXSizeMod,
405
0
                          static_cast<GSpacing>(nDTSize) * nXSizeMod *
406
0
                              nYSizeMod,
407
0
                          &sExtraArgs) != CE_None)
408
0
            {
409
0
                return CE_Failure;
410
0
            }
411
412
0
            auto poMEMDS = std::unique_ptr<MEMDataset>(MEMDataset::Create(
413
0
                "", nXSizeMod, nYSizeMod, 0, eBandDT, nullptr));
414
0
            for (int i = 0; i < nBandCount; i++)
415
0
            {
416
0
                auto hBand = MEMCreateRasterBandEx(
417
0
                    poMEMDS.get(), i + 1,
418
0
                    &abyBuf[0] + i * nXSizeModeMulYSizeModMulDTSize, eBandDT, 0,
419
0
                    0, false);
420
0
                poMEMDS->AddMEMBand(hBand);
421
0
            }
422
423
0
            sExtraArgs.eResampleAlg = psExtraArg->eResampleAlg;
424
0
            if (psExtraArg->bFloatingPointWindowValidity)
425
0
            {
426
0
                sExtraArgs.bFloatingPointWindowValidity = true;
427
0
                sExtraArgs.dfXOff = psExtraArg->dfXOff - nXOffMod;
428
0
                sExtraArgs.dfYOff = psExtraArg->dfYOff - nYOffMod;
429
0
                sExtraArgs.dfXSize = psExtraArg->dfXSize;
430
0
                sExtraArgs.dfYSize = psExtraArg->dfYSize;
431
0
            }
432
0
            return poMEMDS->RasterIO(
433
0
                GF_Read, nXOff - nXOffMod, nYOff - nYOffMod, nXSize, nYSize,
434
0
                pData, nBufXSize, nBufYSize, eBufType, nBandCount, nullptr,
435
0
                nPixelSpace, nLineSpace, nBandSpace, &sExtraArgs);
436
0
        }
437
438
        // If not reading at nominal resolution, fallback to default block
439
        // reading
440
0
        return GDALDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
441
0
                                      pData, nBufXSize, nBufYSize, eBufType,
442
0
                                      nBandCount, panBandMap, nPixelSpace,
443
0
                                      nLineSpace, nBandSpace, psExtraArg);
444
0
    }
445
446
48
    int nBufYOff = 0;
447
448
    // If the (uncompressed) size of a metatile is small enough, then download
449
    // it entirely to minimize the number of network requests
450
48
    const bool bDownloadWholeMetaTile =
451
48
        m_poMasterDS->m_bDownloadWholeMetaTile ||
452
48
        (static_cast<GIntBig>(m_nMetaTileWidth) * m_nMetaTileHeight * nBands *
453
48
             nDTSize <
454
48
         128 * 1024);
455
456
    // Split the request on each metatile that it intersects
457
48
    for (int iY = nMinBlockY; iY <= nMaxBlockY; iY++)
458
48
    {
459
48
        const int nTileYOff = std::max(0, nYOff - iY * m_nMetaTileHeight);
460
48
        const int nTileYSize =
461
48
            std::min((iY + 1) * m_nMetaTileHeight, nYOff + nYSize) -
462
48
            std::max(nYOff, iY * m_nMetaTileHeight);
463
464
48
        int nBufXOff = 0;
465
48
        for (int iX = nMinBlockX; iX <= nMaxBlockX; iX++)
466
48
        {
467
48
            CPLString osURL(m_osURLTemplate);
468
48
            osURL.replaceAll("{TileRow}",
469
48
                             CPLSPrintf("%d", iY + m_nMinMetaTileRow));
470
48
            osURL.replaceAll("{TileCol}",
471
48
                             CPLSPrintf("%d", iX + m_nMinMetaTileCol));
472
48
            if (m_poMasterDS->m_bVSICLOUDSubstitutionOK)
473
0
                osURL = DoVSICLOUDSubstitution(osURL);
474
475
48
            const int nTileXOff = std::max(0, nXOff - iX * m_nMetaTileWidth);
476
48
            const int nTileXSize =
477
48
                std::min((iX + 1) * m_nMetaTileWidth, nXOff + nXSize) -
478
48
                std::max(nXOff, iX * m_nMetaTileWidth);
479
480
48
            const int nBufXSizeEffective =
481
48
                bRequestFitsInSingleMetaTile ? nBufXSize : nTileXSize;
482
48
            const int nBufYSizeEffective =
483
48
                bRequestFitsInSingleMetaTile ? nBufYSize : nTileYSize;
484
485
48
            bool bMissingTile = false;
486
48
            do
487
48
            {
488
48
                std::unique_ptr<GDALDataset> *ppoTileDS =
489
48
                    m_poMasterDS->m_oCacheTileDS.getPtr(osURL);
490
48
                if (ppoTileDS == nullptr)
491
48
                {
492
493
                    // Avoid probing side car files
494
48
                    CPLConfigOptionSetter oSetter(
495
48
                        "GDAL_DISABLE_READDIR_ON_OPEN", "EMPTY_DIR",
496
48
                        /* bSetOnlyIfUndefined = */ true);
497
498
48
                    CPLStringList aosAllowedDrivers(GetAllowedDrivers());
499
48
                    std::unique_ptr<GDALDataset> poTileDS;
500
48
                    if (bDownloadWholeMetaTile && !VSIIsLocal(osURL.c_str()))
501
0
                    {
502
0
                        if (m_poMasterDS->m_bSkipMissingMetaTile)
503
0
                            CPLPushErrorHandler(CPLQuietErrorHandler);
504
0
                        VSILFILE *fp = VSIFOpenL(osURL, "rb");
505
0
                        if (m_poMasterDS->m_bSkipMissingMetaTile)
506
0
                            CPLPopErrorHandler();
507
0
                        if (fp == nullptr)
508
0
                        {
509
0
                            if (!m_poMasterDS->m_bTriedVSICLOUDSubstitution &&
510
0
                                cpl::starts_with(osURL, "https://"))
511
0
                            {
512
0
                                m_poMasterDS->m_bTriedVSICLOUDSubstitution =
513
0
                                    true;
514
0
                                std::string osNewURL =
515
0
                                    DoVSICLOUDSubstitution(osURL);
516
0
                                if (!osNewURL.empty())
517
0
                                {
518
0
                                    CPLDebug("STACTA", "Retrying with %s",
519
0
                                             osNewURL.c_str());
520
0
                                    if (m_poMasterDS->m_bSkipMissingMetaTile)
521
0
                                        CPLPushErrorHandler(
522
0
                                            CPLQuietErrorHandler);
523
0
                                    fp = VSIFOpenL(osNewURL.c_str(), "rb");
524
0
                                    if (m_poMasterDS->m_bSkipMissingMetaTile)
525
0
                                        CPLPopErrorHandler();
526
0
                                    if (fp != nullptr)
527
0
                                    {
528
0
                                        VSIFCloseL(fp);
529
0
                                        m_poMasterDS
530
0
                                            ->m_bVSICLOUDSubstitutionOK = true;
531
0
                                        osURL = std::move(osNewURL);
532
0
                                        break;
533
0
                                    }
534
0
                                }
535
0
                            }
536
0
                        }
537
0
                        if (fp == nullptr)
538
0
                        {
539
0
                            if (m_poMasterDS->m_bSkipMissingMetaTile)
540
0
                            {
541
0
                                m_poMasterDS->m_oCacheTileDS.insert(osURL,
542
0
                                                                    nullptr);
543
0
                                bMissingTile = true;
544
0
                                break;
545
0
                            }
546
0
                            CPLError(CE_Failure, CPLE_OpenFailed,
547
0
                                     "Cannot open %s", osURL.c_str());
548
0
                            return CE_Failure;
549
0
                        }
550
0
                        GByte *pabyBuf = nullptr;
551
0
                        vsi_l_offset nSize = 0;
552
0
                        if (!VSIIngestFile(fp, nullptr, &pabyBuf, &nSize, -1))
553
0
                        {
554
0
                            VSIFCloseL(fp);
555
0
                            return CE_Failure;
556
0
                        }
557
0
                        VSIFCloseL(fp);
558
0
                        const CPLString osMEMFilename(
559
0
                            VSIMemGenerateHiddenFilename(
560
0
                                std::string("stacta_")
561
0
                                    .append(CPLString(osURL)
562
0
                                                .replaceAll("/", "_")
563
0
                                                .replaceAll("\\", "_"))
564
0
                                    .c_str()));
565
0
                        VSIFCloseL(VSIFileFromMemBuffer(osMEMFilename, pabyBuf,
566
0
                                                        nSize, TRUE));
567
0
                        poTileDS = std::unique_ptr<GDALDataset>(
568
0
                            GDALDataset::Open(osMEMFilename,
569
0
                                              GDAL_OF_INTERNAL | GDAL_OF_RASTER,
570
0
                                              aosAllowedDrivers.List()));
571
0
                        if (poTileDS)
572
0
                            poTileDS->MarkSuppressOnClose();
573
0
                        else
574
0
                            VSIUnlink(osMEMFilename);
575
0
                    }
576
48
                    else if (bDownloadWholeMetaTile ||
577
48
                             (!STARTS_WITH(osURL, "http://") &&
578
48
                              !STARTS_WITH(osURL, "https://")))
579
48
                    {
580
48
                        aosAllowedDrivers.AddString("HTTP");
581
48
                        if (m_poMasterDS->m_bSkipMissingMetaTile)
582
0
                            CPLPushErrorHandler(CPLQuietErrorHandler);
583
48
                        poTileDS =
584
48
                            std::unique_ptr<GDALDataset>(GDALDataset::Open(
585
48
                                osURL, GDAL_OF_INTERNAL | GDAL_OF_RASTER,
586
48
                                aosAllowedDrivers.List()));
587
48
                        if (m_poMasterDS->m_bSkipMissingMetaTile)
588
0
                            CPLPopErrorHandler();
589
48
                    }
590
0
                    else
591
0
                    {
592
0
                        if (m_poMasterDS->m_bSkipMissingMetaTile)
593
0
                            CPLPushErrorHandler(CPLQuietErrorHandler);
594
0
                        poTileDS = std::unique_ptr<GDALDataset>(
595
0
                            GDALDataset::Open(("/vsicurl/" + osURL).c_str(),
596
0
                                              GDAL_OF_INTERNAL | GDAL_OF_RASTER,
597
0
                                              aosAllowedDrivers.List()));
598
0
                        if (m_poMasterDS->m_bSkipMissingMetaTile)
599
0
                            CPLPopErrorHandler();
600
0
                        if (poTileDS == nullptr)
601
0
                        {
602
0
                            if (!m_poMasterDS->m_bTriedVSICLOUDSubstitution &&
603
0
                                cpl::starts_with(osURL, "https://"))
604
0
                            {
605
0
                                m_poMasterDS->m_bTriedVSICLOUDSubstitution =
606
0
                                    true;
607
0
                                std::string osNewURL =
608
0
                                    DoVSICLOUDSubstitution(osURL);
609
0
                                if (!osNewURL.empty())
610
0
                                {
611
0
                                    CPLDebug("STACTA", "Retrying with %s",
612
0
                                             osNewURL.c_str());
613
0
                                    if (m_poMasterDS->m_bSkipMissingMetaTile)
614
0
                                        CPLPushErrorHandler(
615
0
                                            CPLQuietErrorHandler);
616
0
                                    poTileDS = std::unique_ptr<GDALDataset>(
617
0
                                        GDALDataset::Open(
618
0
                                            osNewURL.c_str(),
619
0
                                            GDAL_OF_INTERNAL | GDAL_OF_RASTER,
620
0
                                            aosAllowedDrivers.List()));
621
0
                                    if (m_poMasterDS->m_bSkipMissingMetaTile)
622
0
                                        CPLPopErrorHandler();
623
0
                                    if (poTileDS)
624
0
                                    {
625
0
                                        m_poMasterDS
626
0
                                            ->m_bVSICLOUDSubstitutionOK = true;
627
0
                                        osURL = std::move(osNewURL);
628
0
                                        m_osURLTemplate =
629
0
                                            DoVSICLOUDSubstitution(
630
0
                                                m_osURLTemplate);
631
0
                                        break;
632
0
                                    }
633
0
                                }
634
0
                            }
635
0
                        }
636
0
                    }
637
48
                    if (poTileDS == nullptr)
638
48
                    {
639
48
                        if (m_poMasterDS->m_bSkipMissingMetaTile)
640
0
                        {
641
0
                            m_poMasterDS->m_oCacheTileDS.insert(
642
0
                                osURL, std::move(poTileDS));
643
0
                            bMissingTile = true;
644
0
                            break;
645
0
                        }
646
48
                        CPLError(CE_Failure, CPLE_OpenFailed, "Cannot open %s",
647
48
                                 osURL.c_str());
648
48
                        return CE_Failure;
649
48
                    }
650
0
                    ppoTileDS = &m_poMasterDS->m_oCacheTileDS.insert(
651
0
                        osURL, std::move(poTileDS));
652
0
                }
653
0
                std::unique_ptr<GDALDataset> &poTileDS = *ppoTileDS;
654
0
                if (poTileDS == nullptr)
655
0
                {
656
0
                    bMissingTile = true;
657
0
                    break;
658
0
                }
659
660
0
                GDALRasterIOExtraArg sExtraArgs;
661
0
                INIT_RASTERIO_EXTRA_ARG(sExtraArgs);
662
0
                if (bRequestFitsInSingleMetaTile)
663
0
                {
664
0
                    sExtraArgs.eResampleAlg = psExtraArg->eResampleAlg;
665
0
                    if (psExtraArg->bFloatingPointWindowValidity)
666
0
                    {
667
0
                        sExtraArgs.bFloatingPointWindowValidity = true;
668
0
                        sExtraArgs.dfXOff =
669
0
                            psExtraArg->dfXOff - iX * m_nMetaTileWidth;
670
0
                        sExtraArgs.dfYOff =
671
0
                            psExtraArg->dfYOff - iY * m_nMetaTileHeight;
672
0
                        sExtraArgs.dfXSize = psExtraArg->dfXSize;
673
0
                        sExtraArgs.dfYSize = psExtraArg->dfYSize;
674
0
                    }
675
0
                }
676
0
                CPLDebugOnly("STACTA", "Reading %d,%d,%d,%d in %s", nTileXOff,
677
0
                             nTileYOff, nTileXSize, nTileYSize, osURL.c_str());
678
0
                if (poTileDS->RasterIO(
679
0
                        GF_Read, nTileXOff, nTileYOff, nTileXSize, nTileYSize,
680
0
                        static_cast<GByte *>(pData) + nBufXOff * nPixelSpace +
681
0
                            nBufYOff * nLineSpace,
682
0
                        nBufXSizeEffective, nBufYSizeEffective, eBufType,
683
0
                        nBandCount, panBandMap, nPixelSpace, nLineSpace,
684
0
                        nBandSpace, &sExtraArgs) != CE_None)
685
0
                {
686
0
                    return CE_Failure;
687
0
                }
688
0
            } while (false);
689
690
0
            if (bMissingTile)
691
0
            {
692
0
                CPLDebugOnly("STACTA", "Missing metatile %s", osURL.c_str());
693
0
                for (int iBand = 0; iBand < nBandCount; iBand++)
694
0
                {
695
0
                    int bHasNoData = FALSE;
696
0
                    double dfNodata = GetRasterBand(panBandMap[iBand])
697
0
                                          ->GetNoDataValue(&bHasNoData);
698
0
                    if (!bHasNoData)
699
0
                        dfNodata = 0;
700
0
                    for (int nYBufOff = 0; nYBufOff < nBufYSizeEffective;
701
0
                         nYBufOff++)
702
0
                    {
703
0
                        GByte *pabyDest = static_cast<GByte *>(pData) +
704
0
                                          iBand * nBandSpace +
705
0
                                          nBufXOff * nPixelSpace +
706
0
                                          (nBufYOff + nYBufOff) * nLineSpace;
707
0
                        GDALCopyWords(&dfNodata, GDT_Float64, 0, pabyDest,
708
0
                                      eBufType, static_cast<int>(nPixelSpace),
709
0
                                      nBufXSizeEffective);
710
0
                    }
711
0
                }
712
0
            }
713
714
0
            if (iX == nMinBlockX)
715
0
            {
716
0
                nBufXOff = m_nMetaTileWidth -
717
0
                           std::max(0, nXOff - nMinBlockX * m_nMetaTileWidth);
718
0
            }
719
0
            else
720
0
            {
721
0
                nBufXOff += m_nMetaTileWidth;
722
0
            }
723
0
        }
724
725
0
        if (iY == nMinBlockY)
726
0
        {
727
0
            nBufYOff = m_nMetaTileHeight -
728
0
                       std::max(0, nYOff - nMinBlockY * m_nMetaTileHeight);
729
0
        }
730
0
        else
731
0
        {
732
0
            nBufYOff += m_nMetaTileHeight;
733
0
        }
734
0
    }
735
736
0
    return CE_None;
737
48
}
738
739
/************************************************************************/
740
/*                          GetGeoTransform()                           */
741
/************************************************************************/
742
743
CPLErr STACTARawDataset::GetGeoTransform(GDALGeoTransform &gt) const
744
12
{
745
12
    gt = m_gt;
746
12
    return CE_None;
747
12
}
748
749
/************************************************************************/
750
/*                              Identify()                              */
751
/************************************************************************/
752
753
int STACTADataset::Identify(GDALOpenInfo *poOpenInfo)
754
318k
{
755
318k
    if (STARTS_WITH(poOpenInfo->pszFilename, "STACTA:"))
756
0
    {
757
0
        return true;
758
0
    }
759
760
318k
    const bool bIsSingleDriver = poOpenInfo->IsSingleAllowedDriver("STACTA");
761
318k
    if (bIsSingleDriver && (STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
762
0
                            STARTS_WITH(poOpenInfo->pszFilename, "https://")))
763
0
    {
764
0
        return true;
765
0
    }
766
767
318k
    if (
768
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
769
        (!bIsSingleDriver && !poOpenInfo->IsExtensionEqualToCI("json")) ||
770
#endif
771
318k
        poOpenInfo->nHeaderBytes == 0)
772
267k
    {
773
267k
        return false;
774
267k
    }
775
776
151k
    for (int i = 0; i < 2; i++)
777
101k
    {
778
        // TryToIngest() may reallocate pabyHeader, so do not move this
779
        // before the loop.
780
101k
        const char *pszHeader =
781
101k
            reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
782
212k
        while (*pszHeader != 0 &&
783
211k
               std::isspace(static_cast<unsigned char>(*pszHeader)))
784
110k
            ++pszHeader;
785
101k
        if (bIsSingleDriver)
786
0
        {
787
0
            return pszHeader[0] == '{';
788
0
        }
789
790
101k
        if (strstr(pszHeader, "\"stac_extensions\"") != nullptr &&
791
1.45k
            (strstr(pszHeader, "\"tiled-assets\"") != nullptr ||
792
721
             strstr(
793
721
                 pszHeader,
794
721
                 "https:\\/\\/stac-extensions.github.io\\/tiled-assets\\/") !=
795
721
                 nullptr ||
796
703
             strstr(pszHeader,
797
703
                    "https://stac-extensions.github.io/tiled-assets/") !=
798
703
                 nullptr))
799
814
        {
800
814
            return true;
801
814
        }
802
803
100k
        if (i == 0)
804
50.3k
        {
805
            // Should be enough for a STACTA .json file
806
50.3k
            poOpenInfo->TryToIngest(32768);
807
50.3k
        }
808
100k
    }
809
810
50.2k
    return false;
811
51.0k
}
812
813
/************************************************************************/
814
/*                                Open()                                */
815
/************************************************************************/
816
817
bool STACTADataset::Open(GDALOpenInfo *poOpenInfo)
818
407
{
819
407
    CPLString osFilename(poOpenInfo->pszFilename);
820
407
    CPLString osAssetName;
821
407
    CPLString osTMS;
822
407
    if (STARTS_WITH(poOpenInfo->pszFilename, "STACTA:"))
823
0
    {
824
0
        const CPLStringList aosTokens(CSLTokenizeString2(
825
0
            poOpenInfo->pszFilename, ":", CSLT_HONOURSTRINGS));
826
0
        if (aosTokens.size() != 2 && aosTokens.size() != 3 &&
827
0
            aosTokens.size() != 4)
828
0
            return false;
829
0
        osFilename = aosTokens[1];
830
0
        if (aosTokens.size() >= 3)
831
0
            osAssetName = aosTokens[2];
832
0
        if (aosTokens.size() == 4)
833
0
            osTMS = aosTokens[3];
834
0
    }
835
836
407
    CPLJSONDocument oDoc;
837
407
    if (STARTS_WITH(osFilename, "http://") ||
838
407
        STARTS_WITH(osFilename, "https://"))
839
0
    {
840
0
        if (!oDoc.LoadUrl(osFilename, nullptr))
841
0
            return false;
842
0
    }
843
407
    else
844
407
    {
845
407
        if (!oDoc.Load(osFilename))
846
337
            return false;
847
407
    }
848
70
    const auto oRoot = oDoc.GetRoot();
849
70
    const auto oProperties = oRoot["properties"];
850
70
    if (!oProperties.IsValid() ||
851
29
        oProperties.GetType() != CPLJSONObject::Type::Object)
852
41
    {
853
41
        CPLError(CE_Failure, CPLE_AppDefined, "Missing properties");
854
41
        return false;
855
41
    }
856
857
29
    const auto oAssetTemplates = oRoot["asset_templates"];
858
29
    if (!oAssetTemplates.IsValid() ||
859
28
        oAssetTemplates.GetType() != CPLJSONObject::Type::Object)
860
1
    {
861
1
        CPLError(CE_Failure, CPLE_AppDefined, "Missing asset_templates");
862
1
        return false;
863
1
    }
864
865
28
    const auto aoAssetTemplates = oAssetTemplates.GetChildren();
866
28
    if (aoAssetTemplates.size() == 0)
867
0
    {
868
0
        CPLError(CE_Failure, CPLE_AppDefined, "Empty asset_templates");
869
0
        return false;
870
0
    }
871
872
28
    const auto oTMSs = oProperties.GetObj("tiles:tile_matrix_sets");
873
28
    if (!oTMSs.IsValid() || oTMSs.GetType() != CPLJSONObject::Type::Object)
874
0
    {
875
0
        CPLError(CE_Failure, CPLE_AppDefined,
876
0
                 "Missing properties[\"tiles:tile_matrix_sets\"]");
877
0
        return false;
878
0
    }
879
28
    const auto aoTMSs = oTMSs.GetChildren();
880
28
    if (aoTMSs.empty())
881
0
    {
882
0
        CPLError(CE_Failure, CPLE_AppDefined,
883
0
                 "Empty properties[\"tiles:tile_matrix_sets\"]");
884
0
        return false;
885
0
    }
886
887
28
    if ((aoAssetTemplates.size() >= 2 || aoTMSs.size() >= 2) &&
888
2
        osAssetName.empty() && osTMS.empty())
889
2
    {
890
2
        int nSDSCount = 0;
891
2
        for (const auto &oAssetTemplate : aoAssetTemplates)
892
3
        {
893
3
            const CPLString osAssetNameSubDS = oAssetTemplate.GetName();
894
3
            const char *pszAssetNameSubDS = osAssetNameSubDS.c_str();
895
3
            if (aoTMSs.size() >= 2)
896
1
            {
897
1
                for (const auto &oTMS : aoTMSs)
898
2
                {
899
2
                    const CPLString osTMSSubDS = oTMS.GetName();
900
2
                    const char *pszTMSSubDS = osTMSSubDS.c_str();
901
2
                    GDALDataset::SetMetadataItem(
902
2
                        CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
903
2
                        CPLSPrintf("STACTA:\"%s\":%s:%s", osFilename.c_str(),
904
2
                                   pszAssetNameSubDS, pszTMSSubDS),
905
2
                        "SUBDATASETS");
906
2
                    GDALDataset::SetMetadataItem(
907
2
                        CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
908
2
                        CPLSPrintf("Asset %s, tile matrix set %s",
909
2
                                   pszAssetNameSubDS, pszTMSSubDS),
910
2
                        "SUBDATASETS");
911
2
                    nSDSCount++;
912
2
                }
913
1
            }
914
2
            else
915
2
            {
916
2
                GDALDataset::SetMetadataItem(
917
2
                    CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
918
2
                    CPLSPrintf("STACTA:\"%s\":%s", osFilename.c_str(),
919
2
                               pszAssetNameSubDS),
920
2
                    "SUBDATASETS");
921
2
                GDALDataset::SetMetadataItem(
922
2
                    CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
923
2
                    CPLSPrintf("Asset %s", pszAssetNameSubDS), "SUBDATASETS");
924
2
                nSDSCount++;
925
2
            }
926
3
        }
927
2
        return true;
928
2
    }
929
930
26
    if (osAssetName.empty())
931
26
    {
932
26
        osAssetName = aoAssetTemplates[0].GetName();
933
26
    }
934
26
    const auto oAssetTemplate = oAssetTemplates.GetObj(osAssetName);
935
26
    if (!oAssetTemplate.IsValid() ||
936
26
        oAssetTemplate.GetType() != CPLJSONObject::Type::Object)
937
0
    {
938
0
        CPLError(CE_Failure, CPLE_AppDefined,
939
0
                 "Cannot find asset_templates[\"%s\"]", osAssetName.c_str());
940
0
        return false;
941
0
    }
942
943
26
    if (osTMS.empty())
944
26
    {
945
26
        osTMS = aoTMSs[0].GetName();
946
26
    }
947
26
    const auto oTMS = oTMSs.GetObj(osTMS);
948
26
    if (!oTMS.IsValid() || oTMS.GetType() != CPLJSONObject::Type::Object)
949
0
    {
950
0
        CPLError(CE_Failure, CPLE_AppDefined,
951
0
                 "Cannot find properties[\"tiles:tile_matrix_sets\"][\"%s\"]",
952
0
                 osTMS.c_str());
953
0
        return false;
954
0
    }
955
956
26
    auto poTMS = gdal::TileMatrixSet::parse(
957
26
        oTMS.Format(CPLJSONObject::PrettyFormat::Plain).c_str());
958
26
    if (poTMS == nullptr)
959
4
        return false;
960
961
22
    CPLString osURLTemplate = oAssetTemplate.GetString("href");
962
22
    if (osURLTemplate.empty())
963
7
    {
964
7
        CPLError(CE_Failure, CPLE_AppDefined,
965
7
                 "Cannot find asset_templates[\"%s\"][\"href\"]",
966
7
                 osAssetName.c_str());
967
7
    }
968
22
    osURLTemplate.replaceAll("{TileMatrixSet}", osTMS);
969
970
    // UPDATE oMapVSIToURIPrefix in apps/gdalalg_raster_tile if updating below
971
22
    const std::map<std::string, std::string> oMapURIPrefixToVSI = {
972
22
        {"s3", "/vsis3/"},
973
22
        {"gs", "/vsigs/"},
974
22
        {"az", "/vsiaz/"},     // Not universally recognized
975
22
        {"azure", "/vsiaz/"},  // Not universally recognized
976
22
    };
977
978
22
    if (cpl::starts_with(osURLTemplate, "file://"))
979
0
    {
980
0
        osURLTemplate = osURLTemplate.substr(strlen("file://"));
981
0
    }
982
22
    else
983
22
    {
984
22
        const auto nPosColonSlashSlash = osURLTemplate.find("://");
985
22
        if (nPosColonSlashSlash != std::string::npos)
986
0
        {
987
0
            const auto oIter = oMapURIPrefixToVSI.find(
988
0
                osURLTemplate.substr(0, nPosColonSlashSlash));
989
0
            if (oIter != oMapURIPrefixToVSI.end())
990
0
            {
991
0
                osURLTemplate = std::string(oIter->second)
992
0
                                    .append(osURLTemplate.substr(
993
0
                                        nPosColonSlashSlash + strlen("://")));
994
0
            }
995
0
        }
996
22
    }
997
998
22
    if (!cpl::starts_with(osURLTemplate, "http://") &&
999
22
        !cpl::starts_with(osURLTemplate, "https://"))
1000
22
    {
1001
22
        if (STARTS_WITH(osURLTemplate, "./"))
1002
15
            osURLTemplate = osURLTemplate.substr(2);
1003
22
        osURLTemplate = CPLProjectRelativeFilenameSafe(
1004
22
            CPLGetDirnameSafe(osFilename).c_str(), osURLTemplate);
1005
22
    }
1006
1007
    // Parse optional tile matrix set limits
1008
22
    std::map<CPLString, Limits> oMapLimits;
1009
22
    const auto oTMLinks = oProperties.GetObj("tiles:tile_matrix_links");
1010
22
    if (oTMLinks.IsValid())
1011
21
    {
1012
21
        if (oTMLinks.GetType() != CPLJSONObject::Type::Object)
1013
0
        {
1014
0
            CPLError(
1015
0
                CE_Failure, CPLE_AppDefined,
1016
0
                "Invalid type for properties[\"tiles:tile_matrix_links\"]");
1017
0
            return false;
1018
0
        }
1019
1020
21
        auto oLimits = oTMLinks[osTMS]["limits"];
1021
21
        if (oLimits.IsValid() &&
1022
20
            oLimits.GetType() == CPLJSONObject::Type::Object)
1023
20
        {
1024
20
            for (const auto &oLimit : oLimits.GetChildren())
1025
60
            {
1026
60
                Limits limits;
1027
60
                limits.min_tile_col = oLimit.GetInteger("min_tile_col");
1028
60
                limits.max_tile_col = oLimit.GetInteger("max_tile_col");
1029
60
                limits.min_tile_row = oLimit.GetInteger("min_tile_row");
1030
60
                limits.max_tile_row = oLimit.GetInteger("max_tile_row");
1031
60
                oMapLimits[oLimit.GetName()] = limits;
1032
60
            }
1033
20
        }
1034
21
    }
1035
22
    const auto &tmsList = poTMS->tileMatrixList();
1036
22
    if (tmsList.empty())
1037
0
        return false;
1038
1039
22
    m_bSkipMissingMetaTile = CPLTestBool(CSLFetchNameValueDef(
1040
22
        poOpenInfo->papszOpenOptions, "SKIP_MISSING_METATILE",
1041
22
        CPLGetConfigOption("GDAL_STACTA_SKIP_MISSING_METATILE", "NO")));
1042
1043
    // STAC 1.1 uses bands instead of eo:bands and raster:bands
1044
22
    const auto oBands = oAssetTemplate.GetArray("bands");
1045
1046
    // Check if there are both eo:bands and raster:bands extension
1047
    // If so, we don't need to fetch a prototype metatile to derive the
1048
    // information we need (number of bands, data type and nodata value)
1049
22
    const auto oEoBands =
1050
22
        oBands.IsValid() ? oBands : oAssetTemplate.GetArray("eo:bands");
1051
22
    const auto oRasterBands =
1052
22
        oBands.IsValid() ? oBands : oAssetTemplate.GetArray("raster:bands");
1053
1054
22
    std::vector<GDALDataType> aeDT;
1055
22
    std::vector<double> adfNoData;
1056
22
    std::vector<bool> abSetNoData;
1057
22
    int nExpectedBandCount = 0;
1058
22
    if (oRasterBands.IsValid())
1059
14
    {
1060
14
        if (oEoBands.IsValid() && oEoBands.Size() != oRasterBands.Size())
1061
0
        {
1062
0
            CPLError(CE_Warning, CPLE_AppDefined,
1063
0
                     "Number of bands in eo:bands and raster:bands is not "
1064
0
                     "identical. Ignoring the later");
1065
0
        }
1066
14
        else
1067
14
        {
1068
14
            nExpectedBandCount = oRasterBands.Size();
1069
1070
14
            const struct
1071
14
            {
1072
14
                const char *pszStacDataType;
1073
14
                GDALDataType eGDALDataType;
1074
14
            } aDataTypeMapping[] = {
1075
14
                {"int8", GDT_Int8},
1076
14
                {"int16", GDT_Int16},
1077
14
                {"int32", GDT_Int32},
1078
14
                {"int64", GDT_Int64},
1079
14
                {"uint8", GDT_UInt8},
1080
14
                {"uint16", GDT_UInt16},
1081
14
                {"uint32", GDT_UInt32},
1082
14
                {"uint64", GDT_UInt64},
1083
                // float16: 16-bit float; unhandled
1084
14
                {"float32", GDT_Float32},
1085
14
                {"float64", GDT_Float64},
1086
14
                {"cint16", GDT_CInt16},
1087
14
                {"cint32", GDT_CInt32},
1088
14
                {"cfloat32", GDT_CFloat32},
1089
14
                {"cfloat64", GDT_CFloat64},
1090
14
            };
1091
1092
80
            for (int i = 0; i < nExpectedBandCount; ++i)
1093
70
            {
1094
70
                if (oRasterBands[i].GetType() != CPLJSONObject::Type::Object)
1095
0
                {
1096
0
                    CPLError(CE_Failure, CPLE_AppDefined,
1097
0
                             "Wrong raster:bands[%d]", i);
1098
0
                    return false;
1099
0
                }
1100
70
                const std::string osDataType =
1101
70
                    oRasterBands[i].GetString("data_type");
1102
70
                GDALDataType eDT = GDT_Unknown;
1103
70
                for (const auto &oTuple : aDataTypeMapping)
1104
599
                {
1105
599
                    if (osDataType == oTuple.pszStacDataType)
1106
66
                    {
1107
66
                        eDT = oTuple.eGDALDataType;
1108
66
                        break;
1109
66
                    }
1110
599
                }
1111
70
                if (eDT == GDT_Unknown)
1112
4
                {
1113
4
                    CPLError(CE_Failure, CPLE_AppDefined,
1114
4
                             "Wrong raster:bands[%d].data_type = %s", i,
1115
4
                             osDataType.c_str());
1116
4
                    return false;
1117
4
                }
1118
66
                aeDT.push_back(eDT);
1119
1120
66
                const auto oNoData = oRasterBands[i].GetObj("nodata");
1121
66
                if (oNoData.GetType() == CPLJSONObject::Type::String)
1122
27
                {
1123
27
                    const std::string osNoData = oNoData.ToString();
1124
27
                    if (osNoData == "inf")
1125
11
                    {
1126
11
                        abSetNoData.push_back(true);
1127
11
                        adfNoData.push_back(
1128
11
                            std::numeric_limits<double>::infinity());
1129
11
                    }
1130
16
                    else if (osNoData == "-inf")
1131
6
                    {
1132
6
                        abSetNoData.push_back(true);
1133
6
                        adfNoData.push_back(
1134
6
                            -std::numeric_limits<double>::infinity());
1135
6
                    }
1136
10
                    else if (osNoData == "nan")
1137
6
                    {
1138
6
                        abSetNoData.push_back(true);
1139
6
                        adfNoData.push_back(
1140
6
                            std::numeric_limits<double>::quiet_NaN());
1141
6
                    }
1142
4
                    else
1143
4
                    {
1144
4
                        CPLError(CE_Warning, CPLE_AppDefined,
1145
4
                                 "Invalid raster:bands[%d].nodata = %s", i,
1146
4
                                 osNoData.c_str());
1147
4
                        abSetNoData.push_back(false);
1148
4
                        adfNoData.push_back(
1149
4
                            std::numeric_limits<double>::quiet_NaN());
1150
4
                    }
1151
27
                }
1152
39
                else if (oNoData.GetType() == CPLJSONObject::Type::Integer ||
1153
26
                         oNoData.GetType() == CPLJSONObject::Type::Long ||
1154
26
                         oNoData.GetType() == CPLJSONObject::Type::Double)
1155
25
                {
1156
25
                    abSetNoData.push_back(true);
1157
25
                    adfNoData.push_back(oNoData.ToDouble());
1158
25
                }
1159
14
                else if (!oNoData.IsValid())
1160
14
                {
1161
14
                    abSetNoData.push_back(false);
1162
14
                    adfNoData.push_back(
1163
14
                        std::numeric_limits<double>::quiet_NaN());
1164
14
                }
1165
0
                else
1166
0
                {
1167
0
                    CPLError(CE_Warning, CPLE_AppDefined,
1168
0
                             "Invalid raster:bands[%d].nodata", i);
1169
0
                    abSetNoData.push_back(false);
1170
0
                    adfNoData.push_back(
1171
0
                        std::numeric_limits<double>::quiet_NaN());
1172
0
                }
1173
66
            }
1174
1175
10
            CPLAssert(aeDT.size() == abSetNoData.size());
1176
10
            CPLAssert(adfNoData.size() == abSetNoData.size());
1177
10
        }
1178
14
    }
1179
1180
18
    std::unique_ptr<GDALDataset> poProtoDS;
1181
18
    if (aeDT.empty())
1182
8
    {
1183
8
        for (int i = 0; i < static_cast<int>(tmsList.size()); i++)
1184
8
        {
1185
            // Open a metatile to get mostly its band data type
1186
8
            int nProtoTileCol = 0;
1187
8
            int nProtoTileRow = 0;
1188
8
            auto oIterLimit = oMapLimits.find(tmsList[i].mId);
1189
8
            if (oIterLimit != oMapLimits.end())
1190
6
            {
1191
6
                nProtoTileCol = oIterLimit->second.min_tile_col;
1192
6
                nProtoTileRow = oIterLimit->second.min_tile_row;
1193
6
            }
1194
8
            const CPLString osURL =
1195
8
                CPLString(osURLTemplate)
1196
8
                    .replaceAll("{TileMatrix}", tmsList[i].mId)
1197
8
                    .replaceAll("{TileRow}", CPLSPrintf("%d", nProtoTileRow))
1198
8
                    .replaceAll("{TileCol}", CPLSPrintf("%d", nProtoTileCol));
1199
8
            CPLString osProtoDSName = (STARTS_WITH(osURL, "http://") ||
1200
8
                                       STARTS_WITH(osURL, "https://"))
1201
8
                                          ? CPLString("/vsicurl/" + osURL)
1202
8
                                          : osURL;
1203
8
            CPLConfigOptionSetter oSetter("GDAL_DISABLE_READDIR_ON_OPEN",
1204
8
                                          "EMPTY_DIR",
1205
8
                                          /* bSetOnlyIfUndefined = */ true);
1206
8
            if (m_bSkipMissingMetaTile)
1207
0
                CPLPushErrorHandler(CPLQuietErrorHandler);
1208
8
            poProtoDS.reset(GDALDataset::Open(osProtoDSName.c_str(),
1209
8
                                              GDAL_OF_RASTER,
1210
8
                                              GetAllowedDrivers().List()));
1211
8
            if (m_bSkipMissingMetaTile)
1212
0
                CPLPopErrorHandler();
1213
8
            if (poProtoDS != nullptr)
1214
0
            {
1215
0
                break;
1216
0
            }
1217
1218
8
            if (!m_bTriedVSICLOUDSubstitution &&
1219
8
                cpl::starts_with(osURL, "https://"))
1220
0
            {
1221
0
                m_bTriedVSICLOUDSubstitution = true;
1222
0
                std::string osNewURL = DoVSICLOUDSubstitution(osURL);
1223
0
                if (!osNewURL.empty())
1224
0
                {
1225
0
                    CPLDebug("STACTA", "Retrying with %s", osNewURL.c_str());
1226
0
                    if (m_bSkipMissingMetaTile)
1227
0
                        CPLPushErrorHandler(CPLQuietErrorHandler);
1228
0
                    poProtoDS.reset(
1229
0
                        GDALDataset::Open(osNewURL.c_str(), GDAL_OF_RASTER,
1230
0
                                          GetAllowedDrivers().List()));
1231
0
                    if (m_bSkipMissingMetaTile)
1232
0
                        CPLPopErrorHandler();
1233
0
                    if (poProtoDS != nullptr)
1234
0
                    {
1235
0
                        osURLTemplate = DoVSICLOUDSubstitution(osURLTemplate);
1236
0
                        break;
1237
0
                    }
1238
0
                }
1239
0
            }
1240
1241
8
            if (!m_bSkipMissingMetaTile)
1242
8
            {
1243
8
                CPLError(CE_Failure, CPLE_OpenFailed, "Cannot open %s",
1244
8
                         osURL.c_str());
1245
8
                return false;
1246
8
            }
1247
8
        }
1248
0
        if (poProtoDS == nullptr)
1249
0
        {
1250
0
            if (m_bSkipMissingMetaTile)
1251
0
            {
1252
0
                CPLError(CE_Failure, CPLE_AppDefined,
1253
0
                         "Cannot find prototype dataset");
1254
0
                return false;
1255
0
            }
1256
0
        }
1257
0
        else
1258
0
        {
1259
0
            nExpectedBandCount = poProtoDS->GetRasterCount();
1260
0
        }
1261
0
    }
1262
1263
    // Iterate over tile matrices to create corresponding STACTARawDataset
1264
    // objects
1265
40
    for (int i = static_cast<int>(tmsList.size() - 1); i >= 0; i--)
1266
30
    {
1267
30
        const auto &oTM = tmsList[i];
1268
30
        int nMatrixWidth = oTM.mMatrixWidth;
1269
30
        int nMatrixHeight = oTM.mMatrixHeight;
1270
30
        auto oIterLimit = oMapLimits.find(tmsList[i].mId);
1271
30
        if (oIterLimit != oMapLimits.end())
1272
25
        {
1273
25
            nMatrixWidth = oIterLimit->second.max_tile_col -
1274
25
                           oIterLimit->second.min_tile_col + 1;
1275
25
            nMatrixHeight = oIterLimit->second.max_tile_row -
1276
25
                            oIterLimit->second.min_tile_row + 1;
1277
25
        }
1278
30
        if (nMatrixWidth <= 0 || oTM.mTileWidth > INT_MAX / nMatrixWidth ||
1279
30
            nMatrixHeight <= 0 || oTM.mTileHeight > INT_MAX / nMatrixHeight)
1280
3
        {
1281
3
            continue;
1282
3
        }
1283
27
        auto poRawDS = std::make_unique<STACTARawDataset>();
1284
27
        if (!poRawDS->InitRaster(poProtoDS.get(), aeDT, abSetNoData, adfNoData,
1285
27
                                 poTMS.get(), tmsList[i].mId, oTM, oMapLimits))
1286
0
        {
1287
0
            return false;
1288
0
        }
1289
27
        poRawDS->m_osURLTemplate = osURLTemplate;
1290
27
        poRawDS->m_osURLTemplate.replaceAll("{TileMatrix}", tmsList[i].mId);
1291
27
        poRawDS->m_poMasterDS = this;
1292
1293
27
        if (m_poDS == nullptr)
1294
10
        {
1295
10
            nRasterXSize = poRawDS->GetRasterXSize();
1296
10
            nRasterYSize = poRawDS->GetRasterYSize();
1297
10
            m_oSRS = poRawDS->m_oSRS;
1298
10
            m_gt = poRawDS->m_gt;
1299
10
            m_poDS = std::move(poRawDS);
1300
10
        }
1301
17
        else
1302
17
        {
1303
17
            const double dfMinX = m_gt.xorig;
1304
17
            const double dfMaxX = m_gt.xorig + GetRasterXSize() * m_gt.xscale;
1305
17
            const double dfMaxY = m_gt.yorig;
1306
17
            const double dfMinY = m_gt.yorig + GetRasterYSize() * m_gt.yscale;
1307
1308
17
            const double dfOvrMinX = poRawDS->m_gt.xorig;
1309
17
            const double dfOvrMaxX =
1310
17
                poRawDS->m_gt.xorig +
1311
17
                poRawDS->GetRasterXSize() * poRawDS->m_gt.xscale;
1312
17
            const double dfOvrMaxY = poRawDS->m_gt.yorig;
1313
17
            const double dfOvrMinY =
1314
17
                poRawDS->m_gt.yorig +
1315
17
                poRawDS->GetRasterYSize() * poRawDS->m_gt.yscale;
1316
1317
17
            if (fabs(dfMinX - dfOvrMinX) < 1e-10 * fabs(dfMinX) &&
1318
15
                fabs(dfMinY - dfOvrMinY) < 1e-10 * fabs(dfMinY) &&
1319
14
                fabs(dfMaxX - dfOvrMaxX) < 1e-10 * fabs(dfMaxX) &&
1320
11
                fabs(dfMaxY - dfOvrMaxY) < 1e-10 * fabs(dfMaxY))
1321
11
            {
1322
11
                m_apoOverviewDS.emplace_back(std::move(poRawDS));
1323
11
            }
1324
6
            else
1325
6
            {
1326
                // If this zoom level doesn't share the same origin and extent
1327
                // as the most resoluted one, then subset it
1328
6
                CPLStringList aosOptions;
1329
6
                aosOptions.AddString("-of");
1330
6
                aosOptions.AddString("VRT");
1331
6
                aosOptions.AddString("-projwin");
1332
6
                aosOptions.AddString(CPLSPrintf("%.17g", dfMinX));
1333
6
                aosOptions.AddString(CPLSPrintf("%.17g", dfMaxY));
1334
6
                aosOptions.AddString(CPLSPrintf("%.17g", dfMaxX));
1335
6
                aosOptions.AddString(CPLSPrintf("%.17g", dfMinY));
1336
6
                auto psOptions =
1337
6
                    GDALTranslateOptionsNew(aosOptions.List(), nullptr);
1338
6
                auto hDS =
1339
6
                    GDALTranslate("", GDALDataset::ToHandle(poRawDS.get()),
1340
6
                                  psOptions, nullptr);
1341
6
                GDALTranslateOptionsFree(psOptions);
1342
6
                if (hDS == nullptr)
1343
2
                    continue;
1344
4
                m_apoIntermediaryDS.emplace_back(std::move(poRawDS));
1345
4
                m_apoOverviewDS.emplace_back(GDALDataset::FromHandle(hDS));
1346
4
            }
1347
17
        }
1348
27
    }
1349
10
    if (m_poDS == nullptr)
1350
0
    {
1351
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find valid tile matrix");
1352
0
        return false;
1353
0
    }
1354
1355
    // Create main bands
1356
70
    for (int i = 0; i < m_poDS->GetRasterCount(); i++)
1357
60
    {
1358
60
        auto poSrcBand = m_poDS->GetRasterBand(i + 1);
1359
60
        auto poBand = new STACTARasterBand(this, i + 1, poSrcBand);
1360
60
        if (oEoBands.IsValid() && oEoBands.Size() == nExpectedBandCount)
1361
18
        {
1362
            // Set band metadata
1363
18
            if (oEoBands[i].GetType() == CPLJSONObject::Type::Object)
1364
18
            {
1365
18
                for (const auto &oItem : oEoBands[i].GetChildren())
1366
50
                {
1367
50
                    if (oBands.IsValid())
1368
44
                    {
1369
                        // STAC 1.1
1370
44
                        if (STARTS_WITH(oItem.GetName().c_str(), "eo:"))
1371
2
                        {
1372
2
                            poBand->GDALRasterBand::SetMetadataItem(
1373
2
                                oItem.GetName().c_str() + strlen("eo:"),
1374
2
                                oItem.ToString().c_str());
1375
2
                        }
1376
42
                        else if (oItem.GetName() != "data_type" &&
1377
30
                                 oItem.GetName() != "nodata" &&
1378
20
                                 oItem.GetName() != "unit" &&
1379
18
                                 oItem.GetName() != "raster:scale" &&
1380
16
                                 oItem.GetName() != "raster:offset" &&
1381
14
                                 oItem.GetName() != "raster:bits_per_sample")
1382
12
                        {
1383
12
                            poBand->GDALRasterBand::SetMetadataItem(
1384
12
                                oItem.GetName().c_str(),
1385
12
                                oItem.ToString().c_str());
1386
12
                        }
1387
44
                    }
1388
6
                    else
1389
6
                    {
1390
                        // STAC 1.0
1391
6
                        poBand->GDALRasterBand::SetMetadataItem(
1392
6
                            oItem.GetName().c_str(), oItem.ToString().c_str());
1393
6
                    }
1394
50
                }
1395
18
            }
1396
18
        }
1397
60
        if (oRasterBands.IsValid() &&
1398
60
            oRasterBands.Size() == nExpectedBandCount &&
1399
60
            oRasterBands[i].GetType() == CPLJSONObject::Type::Object)
1400
60
        {
1401
60
            poBand->m_osUnit = oRasterBands[i].GetString("unit");
1402
60
            const double dfScale = oRasterBands[i].GetDouble(
1403
60
                oBands.IsValid() ? "raster:scale" : "scale");
1404
60
            if (dfScale != 0)
1405
9
                poBand->m_dfScale = dfScale;
1406
60
            poBand->m_dfOffset = oRasterBands[i].GetDouble(
1407
60
                oBands.IsValid() ? "raster:offset" : "offset");
1408
60
            const int nBitsPerSample = oRasterBands[i].GetInteger(
1409
60
                oBands.IsValid() ? "raster:bits_per_sample"
1410
60
                                 : "bits_per_sample");
1411
60
            if (((nBitsPerSample >= 1 && nBitsPerSample <= 7) &&
1412
10
                 poBand->GetRasterDataType() == GDT_UInt8) ||
1413
52
                ((nBitsPerSample >= 9 && nBitsPerSample <= 15) &&
1414
0
                 poBand->GetRasterDataType() == GDT_UInt16))
1415
8
            {
1416
8
                poBand->GDALRasterBand::SetMetadataItem(
1417
8
                    "NBITS", CPLSPrintf("%d", nBitsPerSample),
1418
8
                    "IMAGE_STRUCTURE");
1419
8
            }
1420
60
        }
1421
60
        SetBand(i + 1, poBand);
1422
60
    }
1423
1424
    // Set dataset metadata
1425
10
    for (const auto &oItem : oProperties.GetChildren())
1426
59
    {
1427
59
        const auto osName = oItem.GetName();
1428
59
        if (osName != "tiles:tile_matrix_links" &&
1429
50
            osName != "tiles:tile_matrix_sets" &&
1430
40
            !cpl::starts_with(osName, "proj:"))
1431
40
        {
1432
40
            GDALDataset::SetMetadataItem(osName.c_str(),
1433
40
                                         oItem.ToString().c_str());
1434
40
        }
1435
59
    }
1436
1437
10
    if (poProtoDS)
1438
0
    {
1439
0
        const char *pszInterleave =
1440
0
            poProtoDS->GetMetadataItem("INTERLEAVE", "IMAGE_STRUCTURE");
1441
0
        GDALDataset::SetMetadataItem("INTERLEAVE",
1442
0
                                     pszInterleave ? pszInterleave : "PIXEL",
1443
0
                                     "IMAGE_STRUCTURE");
1444
0
    }
1445
10
    else
1446
10
    {
1447
        // A bit bold to assume that, but that should be a reasonable
1448
        // setting
1449
10
        GDALDataset::SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
1450
10
    }
1451
1452
10
    m_bDownloadWholeMetaTile = CPLTestBool(CSLFetchNameValueDef(
1453
10
        poOpenInfo->papszOpenOptions, "WHOLE_METATILE", "NO"));
1454
1455
10
    return true;
1456
10
}
1457
1458
/************************************************************************/
1459
/*                           ~STACTADataset()                           */
1460
/************************************************************************/
1461
1462
STACTADataset::~STACTADataset()
1463
407
{
1464
407
    m_poDS.reset();
1465
407
    m_apoOverviewDS.clear();
1466
407
    m_apoIntermediaryDS.clear();
1467
407
}
1468
1469
/************************************************************************/
1470
/*                             FlushCache()                             */
1471
/************************************************************************/
1472
1473
CPLErr STACTADataset::FlushCache(bool bAtClosing)
1474
0
{
1475
0
    m_oCacheTileDS.clear();
1476
0
    return GDALDataset::FlushCache(bAtClosing);
1477
0
}
1478
1479
/************************************************************************/
1480
/*                             InitRaster()                             */
1481
/************************************************************************/
1482
1483
bool STACTARawDataset::InitRaster(GDALDataset *poProtoDS,
1484
                                  const std::vector<GDALDataType> &aeDT,
1485
                                  const std::vector<bool> &abSetNoData,
1486
                                  const std::vector<double> &adfNoData,
1487
                                  const gdal::TileMatrixSet *poTMS,
1488
                                  const std::string &osTMId,
1489
                                  const gdal::TileMatrixSet::TileMatrix &oTM,
1490
                                  const std::map<CPLString, Limits> &oMapLimits)
1491
27
{
1492
27
    int nMatrixWidth = oTM.mMatrixWidth;
1493
27
    int nMatrixHeight = oTM.mMatrixHeight;
1494
27
    auto oIterLimit = oMapLimits.find(osTMId);
1495
27
    if (oIterLimit != oMapLimits.end())
1496
22
    {
1497
22
        m_nMinMetaTileCol = oIterLimit->second.min_tile_col;
1498
22
        m_nMinMetaTileRow = oIterLimit->second.min_tile_row;
1499
22
        nMatrixWidth = oIterLimit->second.max_tile_col - m_nMinMetaTileCol + 1;
1500
22
        nMatrixHeight = oIterLimit->second.max_tile_row - m_nMinMetaTileRow + 1;
1501
22
    }
1502
27
    m_nMetaTileWidth = oTM.mTileWidth;
1503
27
    m_nMetaTileHeight = oTM.mTileHeight;
1504
27
    nRasterXSize = nMatrixWidth * m_nMetaTileWidth;
1505
27
    nRasterYSize = nMatrixHeight * m_nMetaTileHeight;
1506
1507
27
    if (poProtoDS)
1508
0
    {
1509
0
        for (int i = 0; i < poProtoDS->GetRasterCount(); i++)
1510
0
        {
1511
0
            auto poProtoBand = poProtoDS->GetRasterBand(i + 1);
1512
0
            auto poBand = new STACTARawRasterBand(this, i + 1, poProtoBand);
1513
0
            SetBand(i + 1, poBand);
1514
0
        }
1515
0
    }
1516
27
    else
1517
27
    {
1518
189
        for (int i = 0; i < static_cast<int>(aeDT.size()); i++)
1519
162
        {
1520
162
            auto poBand = new STACTARawRasterBand(this, i + 1, aeDT[i],
1521
162
                                                  abSetNoData[i], adfNoData[i]);
1522
162
            SetBand(i + 1, poBand);
1523
162
        }
1524
27
    }
1525
1526
27
    CPLString osCRS = poTMS->crs().c_str();
1527
27
    if (osCRS == "http://www.opengis.net/def/crs/OGC/1.3/CRS84")
1528
27
        osCRS = "EPSG:4326";
1529
27
    if (m_oSRS.SetFromUserInput(osCRS) != OGRERR_NONE)
1530
0
    {
1531
0
        return false;
1532
0
    }
1533
27
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1534
27
    m_gt.xorig =
1535
27
        oTM.mTopLeftX + m_nMinMetaTileCol * m_nMetaTileWidth * oTM.mResX;
1536
27
    m_gt.xscale = oTM.mResX;
1537
27
    m_gt.yorig =
1538
27
        oTM.mTopLeftY - m_nMinMetaTileRow * m_nMetaTileHeight * oTM.mResY;
1539
27
    m_gt.yscale = -oTM.mResY;
1540
27
    SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
1541
1542
27
    return true;
1543
27
}
1544
1545
/************************************************************************/
1546
/*                           GetSpatialRef ()                           */
1547
/************************************************************************/
1548
1549
const OGRSpatialReference *STACTADataset::GetSpatialRef() const
1550
12
{
1551
12
    return nBands == 0 ? nullptr : &m_oSRS;
1552
12
}
1553
1554
/************************************************************************/
1555
/*                          GetGeoTransform()                           */
1556
/************************************************************************/
1557
1558
CPLErr STACTADataset::GetGeoTransform(GDALGeoTransform &gt) const
1559
12
{
1560
12
    gt = m_gt;
1561
12
    return nBands == 0 ? CE_Failure : CE_None;
1562
12
}
1563
1564
/************************************************************************/
1565
/*                             OpenStatic()                             */
1566
/************************************************************************/
1567
1568
GDALDataset *STACTADataset::OpenStatic(GDALOpenInfo *poOpenInfo)
1569
407
{
1570
407
    if (!Identify(poOpenInfo))
1571
0
        return nullptr;
1572
407
    auto poDS = std::make_unique<STACTADataset>();
1573
407
    if (!poDS->Open(poOpenInfo))
1574
395
        return nullptr;
1575
12
    return poDS.release();
1576
407
}
1577
1578
/************************************************************************/
1579
/*                        GDALRegister_STACTA()                         */
1580
/************************************************************************/
1581
1582
void GDALRegister_STACTA()
1583
1584
22
{
1585
22
    if (GDALGetDriverByName("STACTA") != nullptr)
1586
0
        return;
1587
1588
22
    GDALDriver *poDriver = new GDALDriver();
1589
1590
22
    poDriver->SetDescription("STACTA");
1591
22
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1592
22
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
1593
22
                              "Spatio-Temporal Asset Catalog Tiled Assets");
1594
22
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/stacta.html");
1595
22
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "json");
1596
22
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1597
22
    poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
1598
22
    poDriver->SetMetadataItem(
1599
22
        GDAL_DMD_OPENOPTIONLIST,
1600
22
        "<OpenOptionList>"
1601
22
        "   <Option name='WHOLE_METATILE' type='boolean' "
1602
22
        "description='Whether to download whole metatiles'/>"
1603
22
        "   <Option name='SKIP_MISSING_METATILE' type='boolean' "
1604
22
        "description='Whether to gracefully skip missing metatiles'/>"
1605
22
        "</OpenOptionList>");
1606
1607
22
    poDriver->pfnOpen = STACTADataset::OpenStatic;
1608
22
    poDriver->pfnIdentify = STACTADataset::Identify;
1609
1610
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
1611
22
}