Coverage Report

Created: 2026-10-28 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/gtiff/cogdriver.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  COG Driver
4
 * Purpose:  Cloud optimized GeoTIFF write support.
5
 * Author:   Even Rouault <even dot rouault at spatialys dot com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2019, Even Rouault <even dot rouault at spatialys dot com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "cpl_port.h"
14
15
#include "gdalalgorithm.h"
16
#include "gdal_proxy.h"
17
#include "gdal_priv.h"
18
#include "gdal_frmts.h"
19
#include "gdalpython.h"
20
#include "gtiff.h"
21
#include "gtiffdataset.h"
22
#include "gt_overview.h"
23
#include "gdal_utils.h"
24
#include "gdalwarper.h"
25
#include "cogdriver.h"
26
#include "geotiff.h"
27
28
#include "tilematrixset.hpp"
29
30
#include <algorithm>
31
#include <memory>
32
#include <mutex>
33
#include <vector>
34
35
static bool gbHasLZW = false;
36
37
using namespace GDALPy;
38
39
/************************************************************************/
40
/*                         HasZSTDCompression()                         */
41
/************************************************************************/
42
43
static bool HasZSTDCompression()
44
0
{
45
0
    TIFFCodec *codecs = TIFFGetConfiguredCODECs();
46
0
    bool bHasZSTD = false;
47
0
    for (TIFFCodec *c = codecs; c->name; ++c)
48
0
    {
49
0
        if (c->scheme == COMPRESSION_ZSTD)
50
0
        {
51
0
            bHasZSTD = true;
52
0
            break;
53
0
        }
54
0
    }
55
0
    _TIFFfree(codecs);
56
0
    return bHasZSTD;
57
0
}
58
59
/************************************************************************/
60
/*                           GetTmpFilename()                           */
61
/************************************************************************/
62
63
static CPLString GetTmpFilename(const char *pszFilename, const char *pszExt)
64
0
{
65
0
    const bool bSupportsRandomWrite =
66
0
        VSISupportsRandomWrite(pszFilename, false);
67
0
    CPLString osTmpFilename;
68
0
    if (!bSupportsRandomWrite ||
69
0
        CPLGetConfigOption("CPL_TMPDIR", nullptr) != nullptr)
70
0
    {
71
0
        osTmpFilename = CPLGenerateTempFilenameSafe(
72
0
            CPLGetBasenameSafe(pszFilename).c_str());
73
0
    }
74
0
    else
75
0
        osTmpFilename = pszFilename;
76
0
    osTmpFilename += '.';
77
0
    osTmpFilename += pszExt;
78
0
    VSIUnlink(osTmpFilename);
79
0
    return osTmpFilename;
80
0
}
81
82
/************************************************************************/
83
/*                           GetResampling()                            */
84
/************************************************************************/
85
86
static const char *GetResampling(GDALDataset *poSrcDS)
87
0
{
88
0
    return poSrcDS->GetRasterBand(1)->GetColorTable() ||
89
0
                   GDALDataTypeIsComplex(
90
0
                       poSrcDS->GetRasterBand(1)->GetRasterDataType())
91
0
               ? "NEAREST"
92
0
               : "CUBIC";
93
0
}
94
95
/************************************************************************/
96
/*                            GetPredictor()                            */
97
/************************************************************************/
98
static const char *GetPredictor(GDALDataset *poSrcDS, const char *pszPredictor)
99
0
{
100
0
    if (pszPredictor == nullptr)
101
0
        return nullptr;
102
103
0
    if (EQUAL(pszPredictor, "YES") || EQUAL(pszPredictor, "ON") ||
104
0
        EQUAL(pszPredictor, "TRUE"))
105
0
    {
106
0
        if (GDALDataTypeIsFloating(
107
0
                poSrcDS->GetRasterBand(1)->GetRasterDataType()))
108
0
            return "3";
109
0
        else
110
0
            return "2";
111
0
    }
112
0
    else if (EQUAL(pszPredictor, "STANDARD") || EQUAL(pszPredictor, "2"))
113
0
    {
114
0
        return "2";
115
0
    }
116
0
    else if (EQUAL(pszPredictor, "FLOATING_POINT") || EQUAL(pszPredictor, "3"))
117
0
    {
118
0
        return "3";
119
0
    }
120
0
    return nullptr;
121
0
}
122
123
/************************************************************************/
124
/*                          COGGetTargetSRS()                           */
125
/************************************************************************/
126
127
static bool COGGetTargetSRS(const char *const *papszOptions,
128
                            CPLString &osTargetSRS,
129
                            std::unique_ptr<gdal::TileMatrixSet> &poTM)
130
0
{
131
0
    osTargetSRS = CSLFetchNameValueDef(papszOptions, "TARGET_SRS", "");
132
0
    CPLString osTilingScheme(
133
0
        CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM"));
134
0
    if (EQUAL(osTargetSRS, "") && EQUAL(osTilingScheme, "CUSTOM"))
135
0
        return false;
136
137
0
    if (!EQUAL(osTilingScheme, "CUSTOM"))
138
0
    {
139
0
        poTM = gdal::TileMatrixSet::parse(osTilingScheme);
140
0
        if (poTM == nullptr)
141
0
            return false;
142
0
        if (!poTM->haveAllLevelsSameTopLeft())
143
0
        {
144
0
            CPLError(CE_Failure, CPLE_NotSupported,
145
0
                     "Unsupported tiling scheme: not all zoom levels have same "
146
0
                     "top left corner");
147
0
            return false;
148
0
        }
149
0
        if (!poTM->haveAllLevelsSameTileSize())
150
0
        {
151
0
            CPLError(CE_Failure, CPLE_NotSupported,
152
0
                     "Unsupported tiling scheme: not all zoom levels have same "
153
0
                     "tile size");
154
0
            return false;
155
0
        }
156
0
        if (poTM->hasVariableMatrixWidth())
157
0
        {
158
0
            CPLError(CE_Failure, CPLE_NotSupported,
159
0
                     "Unsupported tiling scheme: some levels have variable "
160
0
                     "matrix width");
161
0
            return false;
162
0
        }
163
0
        if (!osTargetSRS.empty())
164
0
        {
165
0
            CPLError(CE_Warning, CPLE_AppDefined, "Ignoring TARGET_SRS option");
166
0
        }
167
0
        osTargetSRS = poTM->crs();
168
169
        // "Normalize" SRS as AUTH:CODE
170
0
        OGRSpatialReference oTargetSRS;
171
0
        oTargetSRS.SetFromUserInput(
172
0
            osTargetSRS,
173
0
            OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
174
0
        const char *pszAuthCode = oTargetSRS.GetAuthorityCode();
175
0
        const char *pszAuthName = oTargetSRS.GetAuthorityName();
176
0
        if (pszAuthName && pszAuthCode)
177
0
        {
178
0
            osTargetSRS = pszAuthName;
179
0
            osTargetSRS += ':';
180
0
            osTargetSRS += pszAuthCode;
181
0
        }
182
0
    }
183
184
0
    return true;
185
0
}
186
187
// Used by gdalwarp
188
bool COGGetTargetSRS(const char *const *papszOptions, CPLString &osTargetSRS)
189
0
{
190
0
    std::unique_ptr<gdal::TileMatrixSet> poTM;
191
0
    return COGGetTargetSRS(papszOptions, osTargetSRS, poTM);
192
0
}
193
194
// Used by gdalwarp
195
std::string COGGetResampling(GDALDataset *poSrcDS,
196
                             const char *const *papszOptions)
197
0
{
198
0
    return CSLFetchNameValueDef(papszOptions, "WARP_RESAMPLING",
199
0
                                CSLFetchNameValueDef(papszOptions, "RESAMPLING",
200
0
                                                     GetResampling(poSrcDS)));
201
0
}
202
203
/************************************************************************/
204
/*                    COGGetWarpingCharacteristics()                    */
205
/************************************************************************/
206
207
static bool COGGetWarpingCharacteristics(
208
    GDALDataset *poSrcDS, const char *const *papszOptions,
209
    CPLString &osResampling, CPLString &osTargetSRS, int &nXSize, int &nYSize,
210
    double &dfMinX, double &dfMinY, double &dfMaxX, double &dfMaxY,
211
    double &dfRes, std::unique_ptr<gdal::TileMatrixSet> &poTM, int &nZoomLevel,
212
    int &nAlignedLevels)
213
0
{
214
0
    if (!COGGetTargetSRS(papszOptions, osTargetSRS, poTM))
215
0
        return false;
216
217
0
    CPLStringList aosTO;
218
0
    aosTO.SetNameValue("DST_SRS", osTargetSRS);
219
0
    void *hTransformArg = nullptr;
220
221
0
    OGRSpatialReference oTargetSRS;
222
0
    oTargetSRS.SetFromUserInput(
223
0
        osTargetSRS,
224
0
        OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
225
0
    const char *pszAuthCode = oTargetSRS.GetAuthorityCode();
226
0
    const int nEPSGCode = pszAuthCode ? atoi(pszAuthCode) : 0;
227
228
    // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
229
    // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
230
    // EPSG:3857.
231
0
    GDALGeoTransform srcGT;
232
0
    std::unique_ptr<GDALDataset> poTmpDS;
233
0
    if (nEPSGCode == 3857 && poSrcDS->GetGeoTransform(srcGT) == CE_None &&
234
0
        srcGT[2] == 0 && srcGT[4] == 0 && srcGT[5] < 0)
235
0
    {
236
0
        const auto poSrcSRS = poSrcDS->GetSpatialRef();
237
0
        if (poSrcSRS && poSrcSRS->IsGeographic() &&
238
0
            !poSrcSRS->IsDerivedGeographic())
239
0
        {
240
0
            double maxLat = srcGT[3];
241
0
            double minLat = srcGT[3] + poSrcDS->GetRasterYSize() * srcGT[5];
242
            // Corresponds to the latitude of below MAX_GM
243
0
            constexpr double MAX_LAT = 85.0511287798066;
244
0
            bool bModified = false;
245
0
            if (maxLat > MAX_LAT)
246
0
            {
247
0
                maxLat = MAX_LAT;
248
0
                bModified = true;
249
0
            }
250
0
            if (minLat < -MAX_LAT)
251
0
            {
252
0
                minLat = -MAX_LAT;
253
0
                bModified = true;
254
0
            }
255
0
            if (bModified)
256
0
            {
257
0
                CPLStringList aosOptions;
258
0
                aosOptions.AddString("-of");
259
0
                aosOptions.AddString("VRT");
260
0
                aosOptions.AddString("-projwin");
261
0
                aosOptions.AddString(CPLSPrintf("%.17g", srcGT[0]));
262
0
                aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
263
0
                aosOptions.AddString(CPLSPrintf(
264
0
                    "%.17g", srcGT[0] + poSrcDS->GetRasterXSize() * srcGT[1]));
265
0
                aosOptions.AddString(CPLSPrintf("%.17g", minLat));
266
0
                auto psOptions =
267
0
                    GDALTranslateOptionsNew(aosOptions.List(), nullptr);
268
0
                poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
269
0
                    "", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
270
0
                GDALTranslateOptionsFree(psOptions);
271
0
                if (poTmpDS)
272
0
                {
273
0
                    hTransformArg = GDALCreateGenImgProjTransformer2(
274
0
                        GDALDataset::FromHandle(poTmpDS.get()), nullptr,
275
0
                        aosTO.List());
276
0
                    if (hTransformArg == nullptr)
277
0
                    {
278
0
                        return false;
279
0
                    }
280
0
                }
281
0
            }
282
0
        }
283
0
    }
284
0
    if (hTransformArg == nullptr)
285
0
    {
286
0
        hTransformArg =
287
0
            GDALCreateGenImgProjTransformer2(poSrcDS, nullptr, aosTO.List());
288
0
        if (hTransformArg == nullptr)
289
0
        {
290
0
            return false;
291
0
        }
292
0
    }
293
294
0
    GDALTransformerInfo *psInfo =
295
0
        static_cast<GDALTransformerInfo *>(hTransformArg);
296
0
    GDALGeoTransform gt;
297
0
    double adfExtent[4];
298
299
0
    if (GDALSuggestedWarpOutput2(poTmpDS ? poTmpDS.get() : poSrcDS,
300
0
                                 psInfo->pfnTransform, hTransformArg, gt.data(),
301
0
                                 &nXSize, &nYSize, adfExtent, 0) != CE_None)
302
0
    {
303
0
        GDALDestroyGenImgProjTransformer(hTransformArg);
304
0
        return false;
305
0
    }
306
307
0
    GDALDestroyGenImgProjTransformer(hTransformArg);
308
0
    hTransformArg = nullptr;
309
0
    poTmpDS.reset();
310
311
0
    dfMinX = adfExtent[0];
312
0
    dfMinY = adfExtent[1];
313
0
    dfMaxX = adfExtent[2];
314
0
    dfMaxY = adfExtent[3];
315
0
    dfRes = gt.xscale;
316
317
0
    const CPLString osExtent(CSLFetchNameValueDef(papszOptions, "EXTENT", ""));
318
0
    const CPLString osRes(CSLFetchNameValueDef(papszOptions, "RES", ""));
319
0
    if (poTM)
320
0
    {
321
0
        if (!osExtent.empty())
322
0
        {
323
0
            CPLError(CE_Warning, CPLE_AppDefined, "Ignoring EXTENT option");
324
0
        }
325
0
        if (!osRes.empty())
326
0
        {
327
0
            CPLError(CE_Warning, CPLE_AppDefined, "Ignoring RES option");
328
0
        }
329
0
        const bool bInvertAxis =
330
0
            oTargetSRS.EPSGTreatsAsLatLong() != FALSE ||
331
0
            oTargetSRS.EPSGTreatsAsNorthingEasting() != FALSE;
332
333
0
        const auto &bbox = poTM->bbox();
334
0
        if (bbox.mCrs == poTM->crs())
335
0
        {
336
0
            if (dfMaxX <
337
0
                    (bInvertAxis ? bbox.mLowerCornerY : bbox.mLowerCornerX) ||
338
0
                dfMinX >
339
0
                    (bInvertAxis ? bbox.mUpperCornerY : bbox.mUpperCornerX) ||
340
0
                dfMaxY <
341
0
                    (bInvertAxis ? bbox.mLowerCornerX : bbox.mLowerCornerY) ||
342
0
                dfMinY >
343
0
                    (bInvertAxis ? bbox.mUpperCornerX : bbox.mUpperCornerY))
344
0
            {
345
0
                CPLError(CE_Failure, CPLE_AppDefined,
346
0
                         "Raster extent completely outside of tile matrix set "
347
0
                         "bounding box");
348
0
                return false;
349
0
            }
350
0
        }
351
352
0
        const auto &tmList = poTM->tileMatrixList();
353
0
        const int nBlockSize = atoi(CSLFetchNameValueDef(
354
0
            papszOptions, "BLOCKSIZE", CPLSPrintf("%d", tmList[0].mTileWidth)));
355
0
        dfRes = 0.0;
356
357
0
        const char *pszZoomLevel =
358
0
            CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
359
0
        if (pszZoomLevel)
360
0
        {
361
0
            nZoomLevel = atoi(pszZoomLevel);
362
0
            if (nZoomLevel < 0 || nZoomLevel >= static_cast<int>(tmList.size()))
363
0
            {
364
0
                CPLError(CE_Failure, CPLE_AppDefined,
365
0
                         "Invalid zoom level: should be in [0,%d]",
366
0
                         static_cast<int>(tmList.size()) - 1);
367
0
                return false;
368
0
            }
369
0
        }
370
0
        else
371
0
        {
372
0
            double dfComputedRes = gt.xscale;
373
0
            double dfPrevRes = 0.0;
374
0
            for (; nZoomLevel < static_cast<int>(tmList.size()); nZoomLevel++)
375
0
            {
376
0
                dfRes = tmList[nZoomLevel].mResX * tmList[0].mTileWidth /
377
0
                        nBlockSize;
378
0
                if (dfComputedRes > dfRes ||
379
0
                    fabs(dfComputedRes - dfRes) / dfRes <= 1e-8)
380
0
                    break;
381
0
                dfPrevRes = dfRes;
382
0
            }
383
0
            if (nZoomLevel == static_cast<int>(tmList.size()))
384
0
            {
385
0
                CPLError(CE_Failure, CPLE_AppDefined,
386
0
                         "Could not find an appropriate zoom level");
387
0
                return false;
388
0
            }
389
390
0
            if (nZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > 1e-8)
391
0
            {
392
0
                const char *pszZoomLevelStrategy = CSLFetchNameValueDef(
393
0
                    papszOptions, "ZOOM_LEVEL_STRATEGY", "AUTO");
394
0
                if (EQUAL(pszZoomLevelStrategy, "LOWER"))
395
0
                {
396
0
                    nZoomLevel--;
397
0
                }
398
0
                else if (EQUAL(pszZoomLevelStrategy, "UPPER"))
399
0
                {
400
                    /* do nothing */
401
0
                }
402
0
                else
403
0
                {
404
0
                    if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
405
0
                        nZoomLevel--;
406
0
                }
407
0
            }
408
0
        }
409
0
        CPLDebug("COG", "Using ZOOM_LEVEL %d", nZoomLevel);
410
0
        dfRes = tmList[nZoomLevel].mResX * tmList[0].mTileWidth / nBlockSize;
411
412
0
        const double dfOriX =
413
0
            bInvertAxis ? tmList[0].mTopLeftY : tmList[0].mTopLeftX;
414
0
        const double dfOriY =
415
0
            bInvertAxis ? tmList[0].mTopLeftX : tmList[0].mTopLeftY;
416
0
        const double dfTileExtent = dfRes * nBlockSize;
417
0
        constexpr double TOLERANCE_IN_PIXEL = 0.499;
418
0
        const double dfEps = TOLERANCE_IN_PIXEL * dfRes;
419
0
        int nTLTileX = static_cast<int>(
420
0
            std::floor((dfMinX - dfOriX + dfEps) / dfTileExtent));
421
0
        int nTLTileY = static_cast<int>(
422
0
            std::floor((dfOriY - dfMaxY + dfEps) / dfTileExtent));
423
0
        int nBRTileX = static_cast<int>(
424
0
            std::ceil((dfMaxX - dfOriX - dfEps) / dfTileExtent));
425
0
        int nBRTileY = static_cast<int>(
426
0
            std::ceil((dfOriY - dfMinY - dfEps) / dfTileExtent));
427
428
0
        nAlignedLevels =
429
0
            std::min(std::min(10, atoi(CSLFetchNameValueDef(
430
0
                                      papszOptions, "ALIGNED_LEVELS", "0"))),
431
0
                     nZoomLevel);
432
0
        int nAccDivisor = 1;
433
0
        for (int i = 0; i < nAlignedLevels - 1; i++)
434
0
        {
435
0
            const int nCurLevel = nZoomLevel - i;
436
0
            const double dfResRatio =
437
0
                tmList[nCurLevel - 1].mResX / tmList[nCurLevel].mResX;
438
            // Magical number that has a great number of divisors
439
            // For example if previous scale denom was 50K and current one
440
            // is 20K, then dfResRatio = 2.5 and dfScaledInvResRatio = 24
441
            // We must then simplify 60 / 24 as 5 / 2, and make sure to
442
            // align tile coordinates on multiple of the 5 numerator
443
0
            constexpr int MAGICAL = 60;
444
0
            const double dfScaledInvResRatio = MAGICAL / dfResRatio;
445
0
            if (dfScaledInvResRatio < 1 || dfScaledInvResRatio > 60 ||
446
0
                std::abs(std::round(dfScaledInvResRatio) -
447
0
                         dfScaledInvResRatio) > 1e-10)
448
0
            {
449
0
                CPLError(CE_Failure, CPLE_AppDefined,
450
0
                         "Unsupported ratio of resolution for "
451
0
                         "ALIGNED_LEVELS between zoom level %d and %d = %g",
452
0
                         nCurLevel - 1, nCurLevel, dfResRatio);
453
0
                return false;
454
0
            }
455
0
            const int nScaledInvResRatio =
456
0
                static_cast<int>(std::round(dfScaledInvResRatio));
457
0
            int nNumerator = 0;
458
0
            for (int nDivisor = nScaledInvResRatio; nDivisor >= 2; --nDivisor)
459
0
            {
460
0
                if ((MAGICAL % nDivisor) == 0 &&
461
0
                    (nScaledInvResRatio % nDivisor) == 0)
462
0
                {
463
0
                    nNumerator = MAGICAL / nDivisor;
464
0
                    break;
465
0
                }
466
0
            }
467
0
            if (nNumerator == 0)
468
0
            {
469
0
                CPLError(CE_Failure, CPLE_AppDefined,
470
0
                         "Unsupported ratio of resolution for "
471
0
                         "ALIGNED_LEVELS between zoom level %d and %d = %g",
472
0
                         nCurLevel - 1, nCurLevel, dfResRatio);
473
0
                return false;
474
0
            }
475
0
            nAccDivisor *= nNumerator;
476
0
        }
477
0
        if (nAccDivisor > 1)
478
0
        {
479
0
            nTLTileX = (nTLTileX / nAccDivisor) * nAccDivisor;
480
0
            nTLTileY = (nTLTileY / nAccDivisor) * nAccDivisor;
481
0
            nBRTileY = DIV_ROUND_UP(nBRTileY, nAccDivisor) * nAccDivisor;
482
0
            nBRTileX = DIV_ROUND_UP(nBRTileX, nAccDivisor) * nAccDivisor;
483
0
        }
484
485
0
        if (nTLTileX < 0 || nTLTileY < 0 ||
486
0
            nBRTileX > tmList[nZoomLevel].mMatrixWidth ||
487
0
            nBRTileY > tmList[nZoomLevel].mMatrixHeight)
488
0
        {
489
0
            CPLError(CE_Warning, CPLE_AppDefined,
490
0
                     "Raster extent partially outside of tile matrix "
491
0
                     "bounding box. Clamping it to it");
492
0
        }
493
0
        nTLTileX = std::max(0, nTLTileX);
494
0
        nTLTileY = std::max(0, nTLTileY);
495
0
        nBRTileX = std::min(tmList[nZoomLevel].mMatrixWidth, nBRTileX);
496
0
        nBRTileY = std::min(tmList[nZoomLevel].mMatrixHeight, nBRTileY);
497
498
0
        dfMinX = dfOriX + nTLTileX * dfTileExtent;
499
0
        dfMinY = dfOriY - nBRTileY * dfTileExtent;
500
0
        dfMaxX = dfOriX + nBRTileX * dfTileExtent;
501
0
        dfMaxY = dfOriY - nTLTileY * dfTileExtent;
502
0
    }
503
0
    else if (!osExtent.empty() || !osRes.empty())
504
0
    {
505
0
        CPLStringList aosTokens(CSLTokenizeString2(osExtent, ",", 0));
506
0
        if (aosTokens.size() != 4)
507
0
        {
508
0
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid value for EXTENT");
509
0
            return false;
510
0
        }
511
0
        dfMinX = CPLAtof(aosTokens[0]);
512
0
        dfMinY = CPLAtof(aosTokens[1]);
513
0
        dfMaxX = CPLAtof(aosTokens[2]);
514
0
        dfMaxY = CPLAtof(aosTokens[3]);
515
0
        if (!osRes.empty())
516
0
            dfRes = CPLAtof(osRes);
517
0
    }
518
519
0
    nXSize = static_cast<int>(std::round((dfMaxX - dfMinX) / dfRes));
520
0
    nYSize = static_cast<int>(std::round((dfMaxY - dfMinY) / dfRes));
521
522
0
    osResampling = COGGetResampling(poSrcDS, papszOptions);
523
524
0
    return true;
525
0
}
526
527
// Used by gdalwarp
528
bool COGGetWarpingCharacteristics(GDALDataset *poSrcDS,
529
                                  const char *const *papszOptions,
530
                                  CPLString &osResampling,
531
                                  CPLString &osTargetSRS, int &nXSize,
532
                                  int &nYSize, double &dfMinX, double &dfMinY,
533
                                  double &dfMaxX, double &dfMaxY)
534
0
{
535
0
    std::unique_ptr<gdal::TileMatrixSet> poTM;
536
0
    int nZoomLevel = 0;
537
0
    int nAlignedLevels = 0;
538
0
    double dfRes;
539
0
    return COGGetWarpingCharacteristics(poSrcDS, papszOptions, osResampling,
540
0
                                        osTargetSRS, nXSize, nYSize, dfMinX,
541
0
                                        dfMinY, dfMaxX, dfMaxY, dfRes, poTM,
542
0
                                        nZoomLevel, nAlignedLevels);
543
0
}
544
545
/************************************************************************/
546
/*                        COGHasWarpingOptions()                        */
547
/************************************************************************/
548
549
bool COGHasWarpingOptions(CSLConstList papszOptions)
550
0
{
551
0
    return CSLFetchNameValue(papszOptions, "TARGET_SRS") != nullptr ||
552
0
           !EQUAL(CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM"),
553
0
                  "CUSTOM");
554
0
}
555
556
/************************************************************************/
557
/*                      COGRemoveWarpingOptions()                       */
558
/************************************************************************/
559
560
void COGRemoveWarpingOptions(CPLStringList &aosOptions)
561
0
{
562
0
    aosOptions.SetNameValue("TARGET_SRS", nullptr);
563
0
    aosOptions.SetNameValue("TILING_SCHEME", nullptr);
564
0
    aosOptions.SetNameValue("EXTENT", nullptr);
565
0
    aosOptions.SetNameValue("RES", nullptr);
566
0
    aosOptions.SetNameValue("ALIGNED_LEVELS", nullptr);
567
0
    aosOptions.SetNameValue("ZOOM_LEVEL_STRATEGY", nullptr);
568
0
}
569
570
/************************************************************************/
571
/*                        CreateReprojectedDS()                         */
572
/************************************************************************/
573
574
static std::unique_ptr<GDALDataset> CreateReprojectedDS(
575
    const char *pszDstFilename, GDALDataset *poSrcDS,
576
    const char *const *papszOptions, const CPLString &osResampling,
577
    const CPLString &osTargetSRS, const int nXSize, const int nYSize,
578
    const double dfMinX, const double dfMinY, const double dfMaxX,
579
    const double dfMaxY, const double dfRes, GDALProgressFunc pfnProgress,
580
    void *pProgressData, double &dfCurPixels, double &dfTotalPixelsToProcess)
581
0
{
582
0
    char **papszArg = nullptr;
583
    // We could have done a warped VRT, but overview building on it might be
584
    // slow, so materialize as GTiff
585
0
    papszArg = CSLAddString(papszArg, "-of");
586
0
    papszArg = CSLAddString(papszArg, "GTiff");
587
0
    papszArg = CSLAddString(papszArg, "-co");
588
0
    papszArg = CSLAddString(papszArg, "TILED=YES");
589
0
    papszArg = CSLAddString(papszArg, "-co");
590
0
    papszArg = CSLAddString(papszArg, "SPARSE_OK=YES");
591
0
    const char *pszBIGTIFF = CSLFetchNameValue(papszOptions, "BIGTIFF");
592
0
    if (pszBIGTIFF)
593
0
    {
594
0
        papszArg = CSLAddString(papszArg, "-co");
595
0
        papszArg = CSLAddString(papszArg,
596
0
                                (CPLString("BIGTIFF=") + pszBIGTIFF).c_str());
597
0
    }
598
0
    papszArg = CSLAddString(papszArg, "-co");
599
0
    papszArg = CSLAddString(papszArg, HasZSTDCompression() ? "COMPRESS=ZSTD"
600
0
                                                           : "COMPRESS=LZW");
601
0
    papszArg = CSLAddString(papszArg, "-t_srs");
602
0
    papszArg = CSLAddString(papszArg, osTargetSRS);
603
0
    papszArg = CSLAddString(papszArg, "-te");
604
0
    papszArg = CSLAddString(papszArg, CPLSPrintf("%.17g", dfMinX));
605
0
    papszArg = CSLAddString(papszArg, CPLSPrintf("%.17g", dfMinY));
606
0
    papszArg = CSLAddString(papszArg, CPLSPrintf("%.17g", dfMaxX));
607
0
    papszArg = CSLAddString(papszArg, CPLSPrintf("%.17g", dfMaxY));
608
0
    papszArg = CSLAddString(papszArg, "-ts");
609
0
    papszArg = CSLAddString(papszArg, CPLSPrintf("%d", nXSize));
610
0
    papszArg = CSLAddString(papszArg, CPLSPrintf("%d", nYSize));
611
612
    // to be kept in sync with gdalwarp_lib.cpp
613
0
    constexpr double RELATIVE_ERROR_RES_SHARED_BY_COG_AND_GDALWARP = 1e-8;
614
0
    if (fabs((dfMaxX - dfMinX) / dfRes - nXSize) <=
615
0
            RELATIVE_ERROR_RES_SHARED_BY_COG_AND_GDALWARP &&
616
0
        fabs((dfMaxY - dfMinY) / dfRes - nYSize) <=
617
0
            RELATIVE_ERROR_RES_SHARED_BY_COG_AND_GDALWARP)
618
0
    {
619
        // Try to produce exactly square pixels
620
0
        papszArg = CSLAddString(papszArg, "-tr");
621
0
        papszArg = CSLAddString(papszArg, CPLSPrintf("%.17g", dfRes));
622
0
        papszArg = CSLAddString(papszArg, CPLSPrintf("%.17g", dfRes));
623
0
    }
624
0
    else
625
0
    {
626
0
        CPLDebug("COG", "Cannot pass -tr option to GDALWarp() due to extent, "
627
0
                        "size and resolution not consistent enough");
628
0
    }
629
630
0
    int bHasNoData = FALSE;
631
0
    poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
632
0
    if (!bHasNoData &&
633
0
        CPLTestBool(CSLFetchNameValueDef(papszOptions, "ADD_ALPHA", "YES")))
634
0
    {
635
0
        papszArg = CSLAddString(papszArg, "-dstalpha");
636
0
    }
637
0
    papszArg = CSLAddString(papszArg, "-r");
638
0
    papszArg = CSLAddString(papszArg, osResampling);
639
0
    papszArg = CSLAddString(papszArg, "-wo");
640
0
    papszArg = CSLAddString(papszArg, "SAMPLE_GRID=YES");
641
0
    const char *pszNumThreads = CSLFetchNameValue(papszOptions, "NUM_THREADS");
642
0
    if (pszNumThreads)
643
0
    {
644
0
        papszArg = CSLAddString(papszArg, "-wo");
645
0
        papszArg = CSLAddString(
646
0
            papszArg, (CPLString("NUM_THREADS=") + pszNumThreads).c_str());
647
0
    }
648
649
0
    const auto poFirstBand = poSrcDS->GetRasterBand(1);
650
0
    const bool bHasMask = poFirstBand->GetMaskFlags() == GMF_PER_DATASET;
651
652
0
    const int nBands = poSrcDS->GetRasterCount();
653
0
    const char *pszOverviews =
654
0
        CSLFetchNameValueDef(papszOptions, "OVERVIEWS", "AUTO");
655
0
    const bool bUseExistingOrNone = EQUAL(pszOverviews, "FORCE_USE_EXISTING") ||
656
0
                                    EQUAL(pszOverviews, "NONE");
657
0
    dfTotalPixelsToProcess =
658
0
        double(nXSize) * nYSize * (nBands + (bHasMask ? 1 : 0)) +
659
0
        ((bHasMask && !bUseExistingOrNone) ? double(nXSize) * nYSize / 3 : 0) +
660
0
        (!bUseExistingOrNone ? double(nXSize) * nYSize * nBands / 3 : 0) +
661
0
        double(nXSize) * nYSize * (nBands + (bHasMask ? 1 : 0)) * 4. / 3;
662
663
0
    auto psOptions = GDALWarpAppOptionsNew(papszArg, nullptr);
664
0
    CSLDestroy(papszArg);
665
0
    if (psOptions == nullptr)
666
0
        return nullptr;
667
668
0
    const double dfNextPixels =
669
0
        double(nXSize) * nYSize * (nBands + (bHasMask ? 1 : 0));
670
0
    void *pScaledProgress = GDALCreateScaledProgress(
671
0
        dfCurPixels / dfTotalPixelsToProcess,
672
0
        dfNextPixels / dfTotalPixelsToProcess, pfnProgress, pProgressData);
673
0
    dfCurPixels = dfNextPixels;
674
675
0
    CPLDebug("COG", "Reprojecting source dataset: start");
676
0
    GDALWarpAppOptionsSetProgress(psOptions, GDALScaledProgress,
677
0
                                  pScaledProgress);
678
0
    CPLString osTmpFile(GetTmpFilename(pszDstFilename, "warped.tif.tmp"));
679
0
    auto hSrcDS = GDALDataset::ToHandle(poSrcDS);
680
681
0
    std::unique_ptr<CPLConfigOptionSetter> poWarpThreadSetter;
682
0
    if (pszNumThreads)
683
0
    {
684
0
        poWarpThreadSetter = std::make_unique<CPLConfigOptionSetter>(
685
0
            "GDAL_NUM_THREADS", pszNumThreads, false);
686
0
    }
687
688
0
    auto hRet = GDALWarp(osTmpFile, nullptr, 1, &hSrcDS, psOptions, nullptr);
689
0
    CPL_IGNORE_RET_VAL(poWarpThreadSetter);
690
0
    GDALWarpAppOptionsFree(psOptions);
691
0
    CPLDebug("COG", "Reprojecting source dataset: end");
692
693
0
    GDALDestroyScaledProgress(pScaledProgress);
694
695
0
    return std::unique_ptr<GDALDataset>(GDALDataset::FromHandle(hRet));
696
0
}
697
698
/************************************************************************/
699
/*                            GDALCOGCreator                            */
700
/************************************************************************/
701
702
struct GDALCOGCreator final
703
{
704
    std::unique_ptr<GDALDataset> m_poReprojectedDS{};
705
    std::unique_ptr<GDALDataset> m_poRGBMaskDS{};
706
    std::unique_ptr<GDALDataset> m_poVRTWithOrWithoutStats{};
707
    CPLString m_osTmpOverviewFilename{};
708
    CPLString m_osTmpMskOverviewFilename{};
709
710
    ~GDALCOGCreator();
711
712
    std::unique_ptr<GDALDataset> Create(const char *pszFilename,
713
                                        GDALDataset *const poSrcDS,
714
                                        CSLConstList papszOptions,
715
                                        GDALProgressFunc pfnProgress,
716
                                        void *pProgressData);
717
};
718
719
/************************************************************************/
720
/*                  GDALCOGCreator::~GDALCOGCreator()                   */
721
/************************************************************************/
722
723
GDALCOGCreator::~GDALCOGCreator()
724
0
{
725
    // Destroy m_poRGBMaskDS before m_poReprojectedDS since the former
726
    // may reference the later
727
0
    m_poRGBMaskDS.reset();
728
729
    // Config option just for testing purposes
730
0
    const bool bDeleteTempFiles =
731
0
        CPLTestBool(CPLGetConfigOption("COG_DELETE_TEMP_FILES", "YES"));
732
0
    if (bDeleteTempFiles)
733
0
    {
734
0
        if (m_poReprojectedDS)
735
0
        {
736
0
            CPLString osProjectedDSName(m_poReprojectedDS->GetDescription());
737
0
            m_poReprojectedDS.reset();
738
0
            VSIUnlink(osProjectedDSName);
739
0
        }
740
0
        if (!m_osTmpOverviewFilename.empty())
741
0
        {
742
0
            VSIUnlink(m_osTmpOverviewFilename);
743
0
        }
744
0
        if (!m_osTmpMskOverviewFilename.empty())
745
0
        {
746
0
            VSIUnlink(m_osTmpMskOverviewFilename);
747
0
        }
748
0
    }
749
0
}
750
751
/************************************************************************/
752
/*                       GDALCOGCreator::Create()                       */
753
/************************************************************************/
754
755
std::unique_ptr<GDALDataset>
756
GDALCOGCreator::Create(const char *pszFilename, GDALDataset *const poSrcDS,
757
                       CSLConstList papszOptions, GDALProgressFunc pfnProgress,
758
                       void *pProgressData)
759
0
{
760
0
    if (pfnProgress == nullptr)
761
0
        pfnProgress = GDALDummyProgress;
762
763
0
    if (poSrcDS->GetRasterCount() == 0)
764
0
    {
765
0
        CPLError(CE_Failure, CPLE_NotSupported,
766
0
                 "COG driver does not support 0-band source raster");
767
0
        return nullptr;
768
0
    }
769
770
0
    const CPLString osCompress = CSLFetchNameValueDef(
771
0
        papszOptions, "COMPRESS", gbHasLZW ? "LZW" : "NONE");
772
773
0
    const char *pszInterleave =
774
0
        CSLFetchNameValueDef(papszOptions, GDALMD_INTERLEAVE, "PIXEL");
775
0
    if (EQUAL(osCompress, "WEBP"))
776
0
    {
777
0
        if (!EQUAL(pszInterleave, "PIXEL"))
778
0
        {
779
0
            CPLError(CE_Failure, CPLE_NotSupported,
780
0
                     "COMPRESS=WEBP only supported for INTERLEAVE=PIXEL");
781
0
            return nullptr;
782
0
        }
783
0
    }
784
785
0
    CPLConfigOptionSetter oSetterReportDirtyBlockFlushing(
786
0
        "GDAL_REPORT_DIRTY_BLOCK_FLUSHING", "NO", true);
787
788
0
    const char *pszStatistics =
789
0
        CSLFetchNameValueDef(papszOptions, "STATISTICS", "AUTO");
790
0
    auto poSrcFirstBand = poSrcDS->GetRasterBand(1);
791
0
    const bool bSrcHasStatistics =
792
0
        poSrcFirstBand->GetMetadataItem("STATISTICS_MINIMUM") &&
793
0
        poSrcFirstBand->GetMetadataItem("STATISTICS_MAXIMUM") &&
794
0
        poSrcFirstBand->GetMetadataItem("STATISTICS_MEAN") &&
795
0
        poSrcFirstBand->GetMetadataItem("STATISTICS_STDDEV");
796
0
    bool bNeedStats = false;
797
0
    bool bRemoveStats = false;
798
0
    bool bWrkHasStatistics = bSrcHasStatistics;
799
0
    if (EQUAL(pszStatistics, "AUTO"))
800
0
    {
801
        // nothing
802
0
    }
803
0
    else if (CPLTestBool(pszStatistics))
804
0
    {
805
0
        bNeedStats = true;
806
0
    }
807
0
    else
808
0
    {
809
0
        bRemoveStats = true;
810
0
    }
811
812
0
    double dfCurPixels = 0;
813
0
    double dfTotalPixelsToProcess = 0;
814
0
    GDALDataset *poCurDS = poSrcDS;
815
816
0
    std::unique_ptr<gdal::TileMatrixSet> poTM;
817
0
    int nZoomLevel = 0;
818
0
    int nAlignedLevels = 0;
819
0
    if (COGHasWarpingOptions(papszOptions))
820
0
    {
821
0
        CPLString osTargetResampling;
822
0
        CPLString osTargetSRS;
823
0
        int nTargetXSize = 0;
824
0
        int nTargetYSize = 0;
825
0
        double dfTargetMinX = 0;
826
0
        double dfTargetMinY = 0;
827
0
        double dfTargetMaxX = 0;
828
0
        double dfTargetMaxY = 0;
829
0
        double dfRes = 0;
830
0
        if (!COGGetWarpingCharacteristics(
831
0
                poCurDS, papszOptions, osTargetResampling, osTargetSRS,
832
0
                nTargetXSize, nTargetYSize, dfTargetMinX, dfTargetMinY,
833
0
                dfTargetMaxX, dfTargetMaxY, dfRes, poTM, nZoomLevel,
834
0
                nAlignedLevels))
835
0
        {
836
0
            return nullptr;
837
0
        }
838
839
        // Collect information on source dataset to see if it already
840
        // matches the warping specifications
841
0
        CPLString osSrcSRS;
842
0
        const auto poSrcSRS = poCurDS->GetSpatialRef();
843
0
        if (poSrcSRS)
844
0
        {
845
0
            const char *pszAuthName = poSrcSRS->GetAuthorityName();
846
0
            const char *pszAuthCode = poSrcSRS->GetAuthorityCode();
847
0
            if (pszAuthName && pszAuthCode)
848
0
            {
849
0
                osSrcSRS = pszAuthName;
850
0
                osSrcSRS += ':';
851
0
                osSrcSRS += pszAuthCode;
852
0
            }
853
0
        }
854
0
        double dfSrcMinX = 0;
855
0
        double dfSrcMinY = 0;
856
0
        double dfSrcMaxX = 0;
857
0
        double dfSrcMaxY = 0;
858
0
        GDALGeoTransform srcGT;
859
0
        const int nSrcXSize = poCurDS->GetRasterXSize();
860
0
        const int nSrcYSize = poCurDS->GetRasterYSize();
861
0
        if (poCurDS->GetGeoTransform(srcGT) == CE_None)
862
0
        {
863
0
            dfSrcMinX = srcGT[0];
864
0
            dfSrcMaxY = srcGT[3];
865
0
            dfSrcMaxX = srcGT[0] + nSrcXSize * srcGT[1];
866
0
            dfSrcMinY = srcGT[3] + nSrcYSize * srcGT[5];
867
0
        }
868
869
0
        const auto IsCloseEnough = [](double a, double b)
870
0
        {
871
0
            constexpr double EPSILON = 1e-10;
872
0
            return std::fabs(a - b) <= EPSILON ||
873
0
                   std::fabs(a - b) <= EPSILON * std::fabs(a);
874
0
        };
875
0
        if (nTargetXSize == nSrcXSize && nTargetYSize == nSrcYSize &&
876
0
            osTargetSRS == osSrcSRS && IsCloseEnough(dfSrcMinX, dfTargetMinX) &&
877
0
            IsCloseEnough(dfSrcMinY, dfTargetMinY) &&
878
0
            IsCloseEnough(dfSrcMaxX, dfTargetMaxX) &&
879
0
            IsCloseEnough(dfSrcMaxY, dfTargetMaxY))
880
0
        {
881
0
            CPLDebug("COG",
882
0
                     "Skipping reprojection step: "
883
0
                     "source dataset matches reprojection specifications");
884
0
        }
885
0
        else
886
0
        {
887
0
            m_poReprojectedDS = CreateReprojectedDS(
888
0
                pszFilename, poCurDS, papszOptions, osTargetResampling,
889
0
                osTargetSRS, nTargetXSize, nTargetYSize, dfTargetMinX,
890
0
                dfTargetMinY, dfTargetMaxX, dfTargetMaxY, dfRes, pfnProgress,
891
0
                pProgressData, dfCurPixels, dfTotalPixelsToProcess);
892
0
            if (!m_poReprojectedDS)
893
0
                return nullptr;
894
0
            poCurDS = m_poReprojectedDS.get();
895
896
0
            if (bSrcHasStatistics && !bNeedStats && !bRemoveStats)
897
0
            {
898
0
                bNeedStats = true;
899
0
            }
900
0
            bWrkHasStatistics = false;
901
0
        }
902
0
    }
903
904
0
    if (EQUAL(osCompress, "JPEG") && EQUAL(pszInterleave, "PIXEL") &&
905
0
        (poCurDS->GetRasterCount() == 2 || poCurDS->GetRasterCount() == 4) &&
906
0
        poCurDS->GetRasterBand(poCurDS->GetRasterCount())
907
0
                ->GetColorInterpretation() == GCI_AlphaBand)
908
0
    {
909
0
        char **papszArg = nullptr;
910
0
        papszArg = CSLAddString(papszArg, "-of");
911
0
        papszArg = CSLAddString(papszArg, "VRT");
912
0
        papszArg = CSLAddString(papszArg, "-b");
913
0
        papszArg = CSLAddString(papszArg, "1");
914
0
        if (poCurDS->GetRasterCount() == 2)
915
0
        {
916
0
            papszArg = CSLAddString(papszArg, "-mask");
917
0
            papszArg = CSLAddString(papszArg, "2");
918
0
        }
919
0
        else
920
0
        {
921
0
            CPLAssert(poCurDS->GetRasterCount() == 4);
922
0
            papszArg = CSLAddString(papszArg, "-b");
923
0
            papszArg = CSLAddString(papszArg, "2");
924
0
            papszArg = CSLAddString(papszArg, "-b");
925
0
            papszArg = CSLAddString(papszArg, "3");
926
0
            papszArg = CSLAddString(papszArg, "-mask");
927
0
            papszArg = CSLAddString(papszArg, "4");
928
0
        }
929
0
        GDALTranslateOptions *psOptions =
930
0
            GDALTranslateOptionsNew(papszArg, nullptr);
931
0
        CSLDestroy(papszArg);
932
0
        GDALDatasetH hRGBMaskDS = GDALTranslate(
933
0
            "", GDALDataset::ToHandle(poCurDS), psOptions, nullptr);
934
0
        GDALTranslateOptionsFree(psOptions);
935
0
        if (!hRGBMaskDS)
936
0
        {
937
0
            return nullptr;
938
0
        }
939
0
        m_poRGBMaskDS.reset(GDALDataset::FromHandle(hRGBMaskDS));
940
0
        poCurDS = m_poRGBMaskDS.get();
941
942
0
        if (bSrcHasStatistics && !bNeedStats && !bRemoveStats)
943
0
        {
944
0
            bNeedStats = true;
945
0
        }
946
0
        else if (bRemoveStats && bWrkHasStatistics)
947
0
        {
948
0
            poCurDS->ClearStatistics();
949
0
            bRemoveStats = false;
950
0
        }
951
0
    }
952
953
0
    const int nBands = poCurDS->GetRasterCount();
954
0
    const int nXSize = poCurDS->GetRasterXSize();
955
0
    const int nYSize = poCurDS->GetRasterYSize();
956
957
0
    const auto CreateVRTWithOrWithoutStats = [this, &poCurDS]()
958
0
    {
959
0
        const char *const apszOptions[] = {"-of", "VRT", nullptr};
960
0
        GDALTranslateOptions *psOptions =
961
0
            GDALTranslateOptionsNew(const_cast<char **>(apszOptions), nullptr);
962
0
        GDALDatasetH hVRTDS = GDALTranslate("", GDALDataset::ToHandle(poCurDS),
963
0
                                            psOptions, nullptr);
964
0
        GDALTranslateOptionsFree(psOptions);
965
0
        if (!hVRTDS)
966
0
            return false;
967
0
        m_poVRTWithOrWithoutStats.reset(GDALDataset::FromHandle(hVRTDS));
968
0
        poCurDS = m_poVRTWithOrWithoutStats.get();
969
0
        return true;
970
0
    };
971
972
0
    if (bNeedStats)
973
0
    {
974
0
        if (poSrcDS == poCurDS && !CreateVRTWithOrWithoutStats())
975
0
        {
976
0
            return nullptr;
977
0
        }
978
0
        poCurDS->ClearStatistics();
979
980
        // Avoid source files to be modified
981
0
        CPLConfigOptionSetter enablePamDirtyDisabler(
982
0
            "GDAL_PAM_ENABLE_MARK_DIRTY", "NO", true);
983
984
0
        for (int i = 1; i <= nBands; ++i)
985
0
        {
986
0
            poCurDS->GetRasterBand(i)->ComputeStatistics(
987
0
                /*bApproxOK=*/FALSE, nullptr, nullptr, nullptr, nullptr,
988
0
                nullptr, nullptr, nullptr);
989
0
        }
990
0
    }
991
0
    else if (bRemoveStats && bWrkHasStatistics)
992
0
    {
993
0
        if (!CreateVRTWithOrWithoutStats())
994
0
            return nullptr;
995
996
0
        m_poVRTWithOrWithoutStats->ClearStatistics();
997
0
    }
998
999
0
    CPLString osBlockSize(CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", ""));
1000
0
    if (osBlockSize.empty())
1001
0
    {
1002
0
        if (poTM)
1003
0
        {
1004
0
            osBlockSize.Printf("%d", poTM->tileMatrixList()[0].mTileWidth);
1005
0
        }
1006
0
        else
1007
0
        {
1008
0
            osBlockSize = "512";
1009
0
        }
1010
0
    }
1011
1012
0
    const int nOvrThresholdSize = atoi(osBlockSize);
1013
1014
0
    const auto poFirstBand = poCurDS->GetRasterBand(1);
1015
0
    const bool bHasMask = poFirstBand->GetMaskFlags() == GMF_PER_DATASET;
1016
1017
0
    CPLString osOverviews =
1018
0
        CSLFetchNameValueDef(papszOptions, "OVERVIEWS", "AUTO");
1019
0
    const bool bUseExistingOrNone =
1020
0
        EQUAL(osOverviews, "FORCE_USE_EXISTING") || EQUAL(osOverviews, "NONE");
1021
1022
0
    const int nOverviewCount =
1023
0
        atoi(CSLFetchNameValueDef(papszOptions, "OVERVIEW_COUNT", "-1"));
1024
1025
0
    const bool bGenerateMskOvr =
1026
0
        !bUseExistingOrNone && bHasMask &&
1027
0
        (nXSize > nOvrThresholdSize || nYSize > nOvrThresholdSize ||
1028
0
         nOverviewCount > 0) &&
1029
0
        (EQUAL(osOverviews, "IGNORE_EXISTING") ||
1030
0
         poFirstBand->GetMaskBand()->GetOverviewCount() == 0);
1031
0
    const bool bGenerateOvr =
1032
0
        !bUseExistingOrNone &&
1033
0
        (nXSize > nOvrThresholdSize || nYSize > nOvrThresholdSize ||
1034
0
         nOverviewCount > 0) &&
1035
0
        (EQUAL(osOverviews, "IGNORE_EXISTING") ||
1036
0
         poFirstBand->GetOverviewCount() == 0);
1037
1038
0
    std::vector<std::pair<int, int>> asOverviewDims;
1039
0
    int nTmpXSize = nXSize;
1040
0
    int nTmpYSize = nYSize;
1041
0
    if (poTM)
1042
0
    {
1043
0
        const auto &tmList = poTM->tileMatrixList();
1044
0
        int nCurLevel = nZoomLevel;
1045
0
        while (true)
1046
0
        {
1047
0
            if (nOverviewCount < 0)
1048
0
            {
1049
0
                if (nTmpXSize <= nOvrThresholdSize &&
1050
0
                    nTmpYSize <= nOvrThresholdSize)
1051
0
                    break;
1052
0
            }
1053
0
            else if (static_cast<int>(asOverviewDims.size()) ==
1054
0
                         nOverviewCount ||
1055
0
                     (nTmpXSize == 1 && nTmpYSize == 1))
1056
0
            {
1057
0
                break;
1058
0
            }
1059
0
            const double dfResRatio =
1060
0
                (nCurLevel >= 1)
1061
0
                    ? tmList[nCurLevel - 1].mResX / tmList[nCurLevel].mResX
1062
0
                    : 2;
1063
0
            nTmpXSize = static_cast<int>(nTmpXSize / dfResRatio + 0.5);
1064
0
            nTmpYSize = static_cast<int>(nTmpYSize / dfResRatio + 0.5);
1065
0
            if (nTmpXSize == 0)
1066
0
                nTmpXSize = 1;
1067
0
            if (nTmpYSize == 0)
1068
0
                nTmpYSize = 1;
1069
0
            asOverviewDims.emplace_back(std::pair(nTmpXSize, nTmpYSize));
1070
0
            nCurLevel--;
1071
0
        }
1072
0
    }
1073
0
    else if (bGenerateMskOvr || bGenerateOvr)
1074
0
    {
1075
0
        if (!bGenerateOvr)
1076
0
        {
1077
            // If generating only .msk.ovr, use the exact overview size as
1078
            // the overviews of the imagery.
1079
0
            int nIters = poFirstBand->GetOverviewCount();
1080
0
            if (nOverviewCount >= 0 && nOverviewCount < nIters)
1081
0
                nIters = nOverviewCount;
1082
0
            for (int i = 0; i < nIters; i++)
1083
0
            {
1084
0
                auto poOvrBand = poFirstBand->GetOverview(i);
1085
0
                asOverviewDims.emplace_back(
1086
0
                    std::pair(poOvrBand->GetXSize(), poOvrBand->GetYSize()));
1087
0
            }
1088
0
        }
1089
0
        else
1090
0
        {
1091
0
            while (true)
1092
0
            {
1093
0
                if (nOverviewCount < 0)
1094
0
                {
1095
0
                    if (nTmpXSize <= nOvrThresholdSize &&
1096
0
                        nTmpYSize <= nOvrThresholdSize)
1097
0
                        break;
1098
0
                }
1099
0
                else if (static_cast<int>(asOverviewDims.size()) ==
1100
0
                             nOverviewCount ||
1101
0
                         (nTmpXSize == 1 && nTmpYSize == 1))
1102
0
                {
1103
0
                    break;
1104
0
                }
1105
0
                nTmpXSize /= 2;
1106
0
                nTmpYSize /= 2;
1107
0
                if (nTmpXSize == 0)
1108
0
                    nTmpXSize = 1;
1109
0
                if (nTmpYSize == 0)
1110
0
                    nTmpYSize = 1;
1111
0
                asOverviewDims.emplace_back(std::pair(nTmpXSize, nTmpYSize));
1112
0
            }
1113
0
        }
1114
0
    }
1115
1116
0
    if (dfTotalPixelsToProcess == 0.0)
1117
0
    {
1118
0
        dfTotalPixelsToProcess =
1119
0
            (bGenerateMskOvr ? double(nXSize) * nYSize / 3 : 0) +
1120
0
            (bGenerateOvr ? double(nXSize) * nYSize * nBands / 3 : 0) +
1121
0
            double(nXSize) * nYSize * (nBands + (bHasMask ? 1 : 0)) * 4. / 3;
1122
0
    }
1123
1124
0
    CPLStringList aosOverviewOptions;
1125
0
    aosOverviewOptions.SetNameValue(
1126
0
        "COMPRESS",
1127
0
        CPLGetConfigOption("COG_TMP_COMPRESSION",  // only for debug purposes
1128
0
                           HasZSTDCompression() ? "ZSTD" : "LZW"));
1129
0
    aosOverviewOptions.SetNameValue(
1130
0
        "NUM_THREADS", CSLFetchNameValue(papszOptions, "NUM_THREADS"));
1131
0
    aosOverviewOptions.SetNameValue("BIGTIFF", "YES");
1132
0
    aosOverviewOptions.SetNameValue("SPARSE_OK", "YES");
1133
1134
0
    if (bGenerateMskOvr)
1135
0
    {
1136
0
        CPLDebug("COG", "Generating overviews of the mask: start");
1137
0
        m_osTmpMskOverviewFilename = GetTmpFilename(pszFilename, "msk.ovr.tmp");
1138
0
        GDALRasterBand *poSrcMask = poFirstBand->GetMaskBand();
1139
0
        const char *pszResampling = CSLFetchNameValueDef(
1140
0
            papszOptions, "OVERVIEW_RESAMPLING",
1141
0
            CSLFetchNameValueDef(papszOptions, "RESAMPLING",
1142
0
                                 GetResampling(poSrcDS)));
1143
1144
0
        double dfNextPixels = dfCurPixels + double(nXSize) * nYSize / 3;
1145
0
        void *pScaledProgress = GDALCreateScaledProgress(
1146
0
            dfCurPixels / dfTotalPixelsToProcess,
1147
0
            dfNextPixels / dfTotalPixelsToProcess, pfnProgress, pProgressData);
1148
0
        dfCurPixels = dfNextPixels;
1149
1150
0
        CPLErr eErr = GTIFFBuildOverviewsEx(
1151
0
            m_osTmpMskOverviewFilename, 1, &poSrcMask,
1152
0
            static_cast<int>(asOverviewDims.size()), nullptr,
1153
0
            asOverviewDims.data(), pszResampling, aosOverviewOptions.List(),
1154
0
            GDALScaledProgress, pScaledProgress);
1155
0
        CPLDebug("COG", "Generating overviews of the mask: end");
1156
1157
0
        GDALDestroyScaledProgress(pScaledProgress);
1158
0
        if (eErr != CE_None)
1159
0
        {
1160
0
            return nullptr;
1161
0
        }
1162
0
    }
1163
1164
0
    std::string osOverviewResampling;
1165
0
    if (bGenerateOvr)
1166
0
    {
1167
0
        CPLDebug("COG", "Generating overviews of the imagery: start");
1168
0
        m_osTmpOverviewFilename = GetTmpFilename(pszFilename, "ovr.tmp");
1169
0
        std::vector<GDALRasterBand *> apoSrcBands;
1170
0
        for (int i = 0; i < nBands; i++)
1171
0
            apoSrcBands.push_back(poCurDS->GetRasterBand(i + 1));
1172
0
        osOverviewResampling = CSLFetchNameValueDef(
1173
0
            papszOptions, "OVERVIEW_RESAMPLING",
1174
0
            CSLFetchNameValueDef(papszOptions, "RESAMPLING",
1175
0
                                 GetResampling(poSrcDS)));
1176
1177
0
        double dfNextPixels =
1178
0
            dfCurPixels + double(nXSize) * nYSize * nBands / 3;
1179
0
        void *pScaledProgress = GDALCreateScaledProgress(
1180
0
            dfCurPixels / dfTotalPixelsToProcess,
1181
0
            dfNextPixels / dfTotalPixelsToProcess, pfnProgress, pProgressData);
1182
0
        dfCurPixels = dfNextPixels;
1183
1184
0
        if (nBands > 1)
1185
0
        {
1186
0
            aosOverviewOptions.SetNameValue(GDALMD_INTERLEAVE, "PIXEL");
1187
0
        }
1188
0
        if (!m_osTmpMskOverviewFilename.empty())
1189
0
        {
1190
0
            aosOverviewOptions.SetNameValue("MASK_OVERVIEW_DATASET",
1191
0
                                            m_osTmpMskOverviewFilename);
1192
0
        }
1193
0
        CPLErr eErr = GTIFFBuildOverviewsEx(
1194
0
            m_osTmpOverviewFilename, nBands, &apoSrcBands[0],
1195
0
            static_cast<int>(asOverviewDims.size()), nullptr,
1196
0
            asOverviewDims.data(), osOverviewResampling.c_str(),
1197
0
            aosOverviewOptions.List(), GDALScaledProgress, pScaledProgress);
1198
0
        CPLDebug("COG", "Generating overviews of the imagery: end");
1199
1200
0
        GDALDestroyScaledProgress(pScaledProgress);
1201
0
        if (eErr != CE_None)
1202
0
        {
1203
0
            return nullptr;
1204
0
        }
1205
0
    }
1206
0
    else if (poSrcDS->GetRasterBand(1)->GetOverviewCount() > 0)
1207
0
    {
1208
0
        const char *pszResampling =
1209
0
            poSrcDS->GetRasterBand(1)->GetOverview(0)->GetMetadataItem(
1210
0
                "RESAMPLING");
1211
0
        if (pszResampling)
1212
0
            osOverviewResampling = pszResampling;
1213
0
    }
1214
1215
0
    CPLStringList aosOptions;
1216
0
    aosOptions.SetNameValue("COPY_SRC_OVERVIEWS", "YES");
1217
0
    aosOptions.SetNameValue("COMPRESS", osCompress);
1218
0
    aosOptions.SetNameValue("TILED", "YES");
1219
0
    aosOptions.SetNameValue("BLOCKXSIZE", osBlockSize);
1220
0
    aosOptions.SetNameValue("BLOCKYSIZE", osBlockSize);
1221
0
    const char *pszPredictor =
1222
0
        CSLFetchNameValueDef(papszOptions, "PREDICTOR", "FALSE");
1223
0
    const char *pszPredictorValue = GetPredictor(poSrcDS, pszPredictor);
1224
0
    if (pszPredictorValue != nullptr)
1225
0
    {
1226
0
        aosOptions.SetNameValue("PREDICTOR", pszPredictorValue);
1227
0
    }
1228
1229
0
    const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY");
1230
0
    if (EQUAL(osCompress, "JPEG"))
1231
0
    {
1232
0
        aosOptions.SetNameValue("JPEG_QUALITY", pszQuality);
1233
0
        if (nBands == 3 && EQUAL(pszInterleave, "PIXEL"))
1234
0
            aosOptions.SetNameValue("PHOTOMETRIC", "YCBCR");
1235
0
    }
1236
0
    else if (EQUAL(osCompress, "WEBP"))
1237
0
    {
1238
0
        if (pszQuality && atoi(pszQuality) == 100)
1239
0
            aosOptions.SetNameValue("WEBP_LOSSLESS", "YES");
1240
0
        aosOptions.SetNameValue("WEBP_LEVEL", pszQuality);
1241
0
    }
1242
0
    else if (EQUAL(osCompress, "DEFLATE") || EQUAL(osCompress, "LERC_DEFLATE"))
1243
0
    {
1244
0
        aosOptions.SetNameValue("ZLEVEL",
1245
0
                                CSLFetchNameValue(papszOptions, "LEVEL"));
1246
0
    }
1247
0
    else if (EQUAL(osCompress, "ZSTD") || EQUAL(osCompress, "LERC_ZSTD"))
1248
0
    {
1249
0
        aosOptions.SetNameValue("ZSTD_LEVEL",
1250
0
                                CSLFetchNameValue(papszOptions, "LEVEL"));
1251
0
    }
1252
0
    else if (EQUAL(osCompress, "LZMA"))
1253
0
    {
1254
0
        aosOptions.SetNameValue("LZMA_PRESET",
1255
0
                                CSLFetchNameValue(papszOptions, "LEVEL"));
1256
0
    }
1257
1258
0
    if (STARTS_WITH_CI(osCompress, "LERC"))
1259
0
    {
1260
0
        aosOptions.SetNameValue("MAX_Z_ERROR",
1261
0
                                CSLFetchNameValue(papszOptions, "MAX_Z_ERROR"));
1262
0
        aosOptions.SetNameValue(
1263
0
            "MAX_Z_ERROR_OVERVIEW",
1264
0
            CSLFetchNameValue(papszOptions, "MAX_Z_ERROR_OVERVIEW"));
1265
0
    }
1266
1267
0
    if (STARTS_WITH_CI(osCompress, "JXL"))
1268
0
    {
1269
0
        for (const char *pszKey : {"JXL_LOSSLESS", "JXL_EFFORT", "JXL_DISTANCE",
1270
0
                                   "JXL_ALPHA_DISTANCE"})
1271
0
        {
1272
0
            const char *pszValue = CSLFetchNameValue(papszOptions, pszKey);
1273
0
            if (pszValue)
1274
0
                aosOptions.SetNameValue(pszKey, pszValue);
1275
0
        }
1276
0
    }
1277
1278
0
    aosOptions.SetNameValue("BIGTIFF",
1279
0
                            CSLFetchNameValue(papszOptions, "BIGTIFF"));
1280
0
    aosOptions.SetNameValue("NUM_THREADS",
1281
0
                            CSLFetchNameValue(papszOptions, "NUM_THREADS"));
1282
0
    aosOptions.SetNameValue("GEOTIFF_VERSION",
1283
0
                            CSLFetchNameValue(papszOptions, "GEOTIFF_VERSION"));
1284
0
    aosOptions.SetNameValue("SPARSE_OK",
1285
0
                            CSLFetchNameValue(papszOptions, "SPARSE_OK"));
1286
0
    aosOptions.SetNameValue(GDALMD_NBITS,
1287
0
                            CSLFetchNameValue(papszOptions, GDALMD_NBITS));
1288
1289
0
    if (EQUAL(osOverviews, "NONE"))
1290
0
    {
1291
0
        aosOptions.SetNameValue("@OVERVIEW_DATASET", "");
1292
0
    }
1293
0
    else
1294
0
    {
1295
0
        if (!osOverviewResampling.empty())
1296
0
        {
1297
0
            aosOptions.SetNameValue("@OVERVIEW_RESAMPLING",
1298
0
                                    osOverviewResampling.c_str());
1299
0
        }
1300
0
        if (!m_osTmpOverviewFilename.empty())
1301
0
        {
1302
0
            aosOptions.SetNameValue("@OVERVIEW_DATASET",
1303
0
                                    m_osTmpOverviewFilename);
1304
0
        }
1305
0
        if (!m_osTmpMskOverviewFilename.empty())
1306
0
        {
1307
0
            aosOptions.SetNameValue("@MASK_OVERVIEW_DATASET",
1308
0
                                    m_osTmpMskOverviewFilename);
1309
0
        }
1310
0
        aosOptions.SetNameValue(
1311
0
            "@OVERVIEW_COUNT",
1312
0
            CSLFetchNameValue(papszOptions, "OVERVIEW_COUNT"));
1313
0
    }
1314
1315
0
    const CPLString osTilingScheme(
1316
0
        CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM"));
1317
0
    if (osTilingScheme != "CUSTOM")
1318
0
    {
1319
0
        aosOptions.SetNameValue("@TILING_SCHEME_NAME", osTilingScheme);
1320
0
        aosOptions.SetNameValue("@TILING_SCHEME_ZOOM_LEVEL",
1321
0
                                CPLSPrintf("%d", nZoomLevel));
1322
0
        if (nAlignedLevels > 0)
1323
0
        {
1324
0
            aosOptions.SetNameValue("@TILING_SCHEME_ALIGNED_LEVELS",
1325
0
                                    CPLSPrintf("%d", nAlignedLevels));
1326
0
        }
1327
0
    }
1328
0
    const char *pszOverviewCompress = CSLFetchNameValueDef(
1329
0
        papszOptions, "OVERVIEW_COMPRESS", osCompress.c_str());
1330
1331
0
    CPLConfigOptionSetter ovrCompressSetter("COMPRESS_OVERVIEW",
1332
0
                                            pszOverviewCompress, true);
1333
0
    const char *pszOverviewQuality =
1334
0
        CSLFetchNameValue(papszOptions, "OVERVIEW_QUALITY");
1335
0
    CPLConfigOptionSetter ovrQualityJpegSetter("JPEG_QUALITY_OVERVIEW",
1336
0
                                               pszOverviewQuality, true);
1337
1338
0
    std::unique_ptr<CPLConfigOptionSetter> poWebpLosslessSetter;
1339
0
    std::unique_ptr<CPLConfigOptionSetter> poWebpLevelSetter;
1340
0
    if (EQUAL(pszOverviewCompress, "WEBP"))
1341
0
    {
1342
0
        if (pszOverviewQuality && CPLAtof(pszOverviewQuality) == 100.0)
1343
0
        {
1344
0
            poWebpLosslessSetter.reset(new CPLConfigOptionSetter(
1345
0
                "WEBP_LOSSLESS_OVERVIEW", "TRUE", true));
1346
0
        }
1347
0
        else
1348
0
        {
1349
0
            poWebpLosslessSetter.reset(new CPLConfigOptionSetter(
1350
0
                "WEBP_LOSSLESS_OVERVIEW", "FALSE", true));
1351
0
            poWebpLevelSetter.reset(new CPLConfigOptionSetter(
1352
0
                "WEBP_LEVEL_OVERVIEW", pszOverviewQuality, true));
1353
0
        }
1354
0
    }
1355
1356
0
    std::unique_ptr<CPLConfigOptionSetter> poPhotometricSetter;
1357
0
    if (nBands == 3 && EQUAL(pszOverviewCompress, "JPEG") &&
1358
0
        EQUAL(pszInterleave, "PIXEL"))
1359
0
    {
1360
0
        poPhotometricSetter.reset(
1361
0
            new CPLConfigOptionSetter("PHOTOMETRIC_OVERVIEW", "YCBCR", true));
1362
0
    }
1363
1364
0
    const char *osOvrPredictor =
1365
0
        CSLFetchNameValueDef(papszOptions, "OVERVIEW_PREDICTOR", "FALSE");
1366
0
    const char *pszOvrPredictorValue = GetPredictor(poSrcDS, osOvrPredictor);
1367
0
    CPLConfigOptionSetter ovrPredictorSetter("PREDICTOR_OVERVIEW",
1368
0
                                             pszOvrPredictorValue, true);
1369
1370
0
    GDALDriver *poGTiffDrv =
1371
0
        GDALDriver::FromHandle(GDALGetDriverByName("GTiff"));
1372
0
    if (!poGTiffDrv)
1373
0
        return nullptr;
1374
0
    void *pScaledProgress = GDALCreateScaledProgress(
1375
0
        dfCurPixels / dfTotalPixelsToProcess, 1.0, pfnProgress, pProgressData);
1376
1377
0
    CPLConfigOptionSetter oSetterInternalMask("GDAL_TIFF_INTERNAL_MASK", "YES",
1378
0
                                              false);
1379
1380
0
    const char *pszCopySrcMDD = CSLFetchNameValue(papszOptions, "COPY_SRC_MDD");
1381
0
    if (pszCopySrcMDD)
1382
0
        aosOptions.SetNameValue("COPY_SRC_MDD", pszCopySrcMDD);
1383
0
    char **papszSrcMDD = CSLFetchNameValueMultiple(papszOptions, "SRC_MDD");
1384
0
    for (CSLConstList papszSrcMDDIter = papszSrcMDD;
1385
0
         papszSrcMDDIter && *papszSrcMDDIter; ++papszSrcMDDIter)
1386
0
        aosOptions.AddNameValue("SRC_MDD", *papszSrcMDDIter);
1387
0
    CSLDestroy(papszSrcMDD);
1388
1389
0
    if (EQUAL(pszInterleave, "TILE"))
1390
0
    {
1391
0
        aosOptions.SetNameValue(GDALMD_INTERLEAVE, "BAND");
1392
0
        aosOptions.SetNameValue("@TILE_INTERLEAVE", "YES");
1393
0
    }
1394
0
    else
1395
0
    {
1396
0
        aosOptions.SetNameValue(GDALMD_INTERLEAVE, pszInterleave);
1397
0
    }
1398
1399
0
    aosOptions.SetNameValue("@FLUSHCACHE", "YES");
1400
0
    aosOptions.SetNameValue("@SUPPRESS_ASAP",
1401
0
                            CSLFetchNameValue(papszOptions, "@SUPPRESS_ASAP"));
1402
1403
0
    CPLDebug("COG", "Generating final product: start");
1404
0
    auto poRet = std::unique_ptr<GDALDataset>(
1405
0
        poGTiffDrv->CreateCopy(pszFilename, poCurDS, false, aosOptions.List(),
1406
0
                               GDALScaledProgress, pScaledProgress));
1407
1408
0
    GDALDestroyScaledProgress(pScaledProgress);
1409
1410
0
    CPLDebug("COG", "Generating final product: end");
1411
0
    return poRet;
1412
0
}
1413
1414
/************************************************************************/
1415
/*                           COGCreateCopy()                            */
1416
/************************************************************************/
1417
1418
static GDALDataset *COGCreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
1419
                                  int /*bStrict*/, CSLConstList papszOptions,
1420
                                  GDALProgressFunc pfnProgress,
1421
                                  void *pProgressData)
1422
0
{
1423
0
    return GDALCOGCreator()
1424
0
        .Create(pszFilename, poSrcDS, papszOptions, pfnProgress, pProgressData)
1425
0
        .release();
1426
0
}
1427
1428
/************************************************************************/
1429
/*                          COGProxyRasterBand                          */
1430
/************************************************************************/
1431
1432
class COGProxyRasterBand final : public GDALProxyRasterBand
1433
{
1434
  public:
1435
    explicit COGProxyRasterBand(GDALRasterBand *poUnderlyingRasterBand)
1436
0
        : m_poUnderlyingBand(poUnderlyingRasterBand)
1437
0
    {
1438
0
        eDataType = poUnderlyingRasterBand->GetRasterDataType();
1439
0
        poUnderlyingRasterBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
1440
0
    }
1441
1442
  protected:
1443
    GDALRasterBand *RefUnderlyingRasterBand(bool /*bForceOpen*/) const override;
1444
1445
  private:
1446
    GDALRasterBand *m_poUnderlyingBand = nullptr;
1447
1448
    CPL_DISALLOW_COPY_ASSIGN(COGProxyRasterBand)
1449
};
1450
1451
GDALRasterBand *
1452
COGProxyRasterBand::RefUnderlyingRasterBand(bool /*bForceOpen*/) const
1453
0
{
1454
0
    return m_poUnderlyingBand;
1455
0
}
1456
1457
/************************************************************************/
1458
/*                           COGProxyDataset                            */
1459
/************************************************************************/
1460
1461
class COGProxyDataset final : public GDALProxyDataset
1462
{
1463
  public:
1464
    COGProxyDataset(GDALDriver *poCOGDriver, const char *pszFilename,
1465
                    CSLConstList papszOptions,
1466
                    std::unique_ptr<GDALDataset> poGTiffTmpDS)
1467
0
        : m_poCOGDriver(poCOGDriver), m_osFilename(pszFilename),
1468
0
          m_aosOptions(papszOptions), m_poGTiffTmpDS(std::move(poGTiffTmpDS))
1469
0
    {
1470
0
        eAccess = GA_Update;
1471
0
        nRasterXSize = m_poGTiffTmpDS->GetRasterXSize();
1472
0
        nRasterYSize = m_poGTiffTmpDS->GetRasterYSize();
1473
0
        const int l_nBands = m_poGTiffTmpDS->GetRasterCount();
1474
0
        for (int i = 0; i < l_nBands; ++i)
1475
0
            SetBand(i + 1, std::make_unique<COGProxyRasterBand>(
1476
0
                               m_poGTiffTmpDS->GetRasterBand(i + 1)));
1477
0
    }
1478
1479
    ~COGProxyDataset() override;
1480
1481
    CPLErr Close(GDALProgressFunc pfnProgress = nullptr,
1482
                 void *pProgressData = nullptr) override;
1483
1484
    bool GetCloseReportsProgress() const override
1485
0
    {
1486
0
        return true;
1487
0
    }
1488
1489
    GDALDriver *GetDriver() const override
1490
0
    {
1491
0
        return m_poCOGDriver;
1492
0
    }
1493
1494
  protected:
1495
    GDALDataset *RefUnderlyingDataset() const override
1496
0
    {
1497
0
        return m_poGTiffTmpDS.get();
1498
0
    }
1499
1500
  private:
1501
    GDALDriver *const m_poCOGDriver;
1502
    const std::string m_osFilename;
1503
    const CPLStringList m_aosOptions;
1504
    std::unique_ptr<GDALDataset> m_poGTiffTmpDS;
1505
1506
    CPL_DISALLOW_COPY_ASSIGN(COGProxyDataset)
1507
};
1508
1509
/************************************************************************/
1510
/*                 COGProxyDataset::~COGProxyDataset()                  */
1511
/************************************************************************/
1512
1513
COGProxyDataset::~COGProxyDataset()
1514
0
{
1515
0
    COGProxyDataset::Close();
1516
0
}
1517
1518
/************************************************************************/
1519
/*                               Close()                                */
1520
/************************************************************************/
1521
1522
CPLErr COGProxyDataset::Close(GDALProgressFunc pfnProgress, void *pProgressData)
1523
0
{
1524
0
    CPLErr eErr = CE_None;
1525
0
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
1526
0
    {
1527
0
        if (IsMarkedSuppressOnClose())
1528
0
        {
1529
0
            m_poGTiffTmpDS->MarkSuppressOnClose();
1530
0
        }
1531
0
        else if (!GDALCOGCreator().Create(
1532
0
                     m_osFilename.c_str(), m_poGTiffTmpDS.get(),
1533
0
                     m_aosOptions.List(), pfnProgress, pProgressData))
1534
1535
0
        {
1536
0
            eErr = CE_Failure;
1537
0
        }
1538
1539
0
        eErr = GDAL::Combine(eErr, m_poGTiffTmpDS->Close());
1540
0
        m_poGTiffTmpDS.reset();
1541
1542
0
        eErr = GDAL::Combine(eErr, GDALDataset::Close());
1543
0
    }
1544
0
    return eErr;
1545
0
}
1546
1547
/************************************************************************/
1548
/*                             COGCreate()                              */
1549
/************************************************************************/
1550
1551
static GDALDataset *COGCreate(const char *pszFilename, int nXSize, int nYSize,
1552
                              int nBands, GDALDataType eType,
1553
                              CSLConstList papszOptions)
1554
0
{
1555
0
    const std::string osTmpFile(GetTmpFilename(pszFilename, "create.tif"));
1556
0
    CPLStringList aosOptions;
1557
0
    aosOptions.SetNameValue("@CREATE_ONLY_VISIBLE_AT_CLOSE_TIME", "YES");
1558
0
    aosOptions.SetNameValue("@SUPPRESS_ASAP", "YES");
1559
0
    aosOptions.SetNameValue("TILED", "YES");
1560
0
    const char *pszBlockSize =
1561
0
        CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "512");
1562
0
    aosOptions.SetNameValue("BLOCKXSIZE", pszBlockSize);
1563
0
    aosOptions.SetNameValue("BLOCKYSIZE", pszBlockSize);
1564
1565
    // doesn't hurt on the temporary file even if final file doesn't need it
1566
0
    aosOptions.SetNameValue("BIGTIFF", "YES");
1567
1568
0
    bool bHasLZW = false;
1569
0
    bool bHasDEFLATE = false;
1570
0
    bool bHasLZMA = false;
1571
0
    bool bHasZSTD = false;
1572
0
    bool bHasJPEG = false;
1573
0
    bool bHasWebP = false;
1574
0
    bool bHasLERC = false;
1575
0
    CPL_IGNORE_RET_VAL(GTiffGetCompressValues(bHasLZW, bHasDEFLATE, bHasLZMA,
1576
0
                                              bHasZSTD, bHasJPEG, bHasWebP,
1577
0
                                              bHasLERC, true /* bForCOG */));
1578
0
    aosOptions.SetNameValue("COMPRESS", bHasZSTD ? "ZSTD" : "LZW");
1579
1580
0
    auto poGTiffTmpDS = std::unique_ptr<GDALDataset>(GTiffDataset::Create(
1581
0
        osTmpFile.c_str(), nXSize, nYSize, nBands, eType, aosOptions.List()));
1582
0
    if (!poGTiffTmpDS)
1583
0
        return nullptr;
1584
1585
0
    auto poCOGDriver = GetGDALDriverManager()->GetDriverByName("COG");
1586
0
    return std::make_unique<COGProxyDataset>(
1587
0
               poCOGDriver, pszFilename, papszOptions, std::move(poGTiffTmpDS))
1588
0
        .release();
1589
0
}
1590
1591
/************************************************************************/
1592
/*                          GDALRegister_COG()                          */
1593
/************************************************************************/
1594
1595
class GDALCOGDriver final : public GDALDriver
1596
{
1597
    std::recursive_mutex m_oMutex{};
1598
    bool m_bInitialized = false;
1599
1600
    bool bHasLZW = false;
1601
    bool bHasDEFLATE = false;
1602
    bool bHasLZMA = false;
1603
    bool bHasZSTD = false;
1604
    bool bHasJPEG = false;
1605
    bool bHasWebP = false;
1606
    bool bHasLERC = false;
1607
    std::string osCompressValues{};
1608
1609
    void InitializeCreationOptionList();
1610
1611
  public:
1612
    GDALCOGDriver();
1613
1614
    const char *GetMetadataItem(const char *pszName,
1615
                                const char *pszDomain) override;
1616
1617
    CSLConstList GetMetadata(const char *pszDomain) override
1618
0
    {
1619
0
        std::lock_guard oLock(m_oMutex);
1620
0
        InitializeCreationOptionList();
1621
0
        return GDALDriver::GetMetadata(pszDomain);
1622
0
    }
1623
};
1624
1625
GDALCOGDriver::GDALCOGDriver()
1626
1
{
1627
    // We could defer this in InitializeCreationOptionList() but with currently
1628
    // released libtiff versions where there was a bug (now fixed) in
1629
    // TIFFGetConfiguredCODECs(), this wouldn't work properly if the LERC codec
1630
    // had been registered in between
1631
1
    osCompressValues = GTiffGetCompressValues(bHasLZW, bHasDEFLATE, bHasLZMA,
1632
1
                                              bHasZSTD, bHasJPEG, bHasWebP,
1633
1
                                              bHasLERC, true /* bForCOG */);
1634
1
    gbHasLZW = bHasLZW;
1635
1
}
1636
1637
const char *GDALCOGDriver::GetMetadataItem(const char *pszName,
1638
                                           const char *pszDomain)
1639
2
{
1640
2
    std::lock_guard oLock(m_oMutex);
1641
2
    if (EQUAL(pszName, GDAL_DMD_CREATIONOPTIONLIST))
1642
0
    {
1643
0
        InitializeCreationOptionList();
1644
0
    }
1645
2
    return GDALDriver::GetMetadataItem(pszName, pszDomain);
1646
2
}
1647
1648
void GDALCOGDriver::InitializeCreationOptionList()
1649
0
{
1650
0
    if (m_bInitialized)
1651
0
        return;
1652
0
    m_bInitialized = true;
1653
1654
0
    CPLString osOptions;
1655
0
    osOptions = "<CreationOptionList>"
1656
0
                "   <Option name='COMPRESS' type='string-select' default='";
1657
0
    osOptions += bHasLZW ? "LZW" : "NONE";
1658
0
    osOptions += "'>";
1659
0
    osOptions += osCompressValues;
1660
0
    osOptions += "   </Option>";
1661
1662
0
    osOptions +=
1663
0
        "   <Option name='OVERVIEW_COMPRESS' type='string-select' default='";
1664
0
    osOptions += bHasLZW ? "LZW" : "NONE";
1665
0
    osOptions += "'>";
1666
0
    osOptions += osCompressValues;
1667
0
    osOptions += "   </Option>";
1668
1669
0
    if (bHasLZW || bHasDEFLATE || bHasZSTD || bHasLZMA)
1670
0
    {
1671
0
        const char *osPredictorOptions =
1672
0
            "     <Value>YES</Value>"
1673
0
            "     <Value>NO</Value>"
1674
0
            "     <Value alias='2'>STANDARD</Value>"
1675
0
            "     <Value alias='3'>FLOATING_POINT</Value>";
1676
1677
0
        osOptions +=
1678
0
            "   <Option name='LEVEL' type='int' "
1679
0
            "description='DEFLATE/ZSTD/LZMA compression level: 1 (fastest)'/>";
1680
1681
0
        osOptions +=
1682
0
            "   <Option name='PREDICTOR' type='string-select' default='FALSE'>";
1683
0
        osOptions += osPredictorOptions;
1684
0
        osOptions += "   </Option>"
1685
0
                     "   <Option name='OVERVIEW_PREDICTOR' "
1686
0
                     "type='string-select' default='FALSE'>";
1687
0
        osOptions += osPredictorOptions;
1688
0
        osOptions += "   </Option>";
1689
0
    }
1690
0
    if (bHasJPEG || bHasWebP)
1691
0
    {
1692
0
        std::string osJPEG_WEBP;
1693
0
        if (bHasJPEG)
1694
0
            osJPEG_WEBP = "JPEG";
1695
0
        if (bHasWebP)
1696
0
        {
1697
0
            if (!osJPEG_WEBP.empty())
1698
0
                osJPEG_WEBP += '/';
1699
0
            osJPEG_WEBP += "WEBP";
1700
0
        }
1701
0
        osOptions += "   <Option name='QUALITY' type='int' "
1702
0
                     "description='" +
1703
0
                     osJPEG_WEBP +
1704
0
                     " quality 1-100' min='1' max='100' default='75'/>"
1705
0
                     "   <Option name='OVERVIEW_QUALITY' type='int' "
1706
0
                     "description='Overview " +
1707
0
                     osJPEG_WEBP +
1708
0
                     " quality 1-100' min='1' max='100' "
1709
0
                     "default='75'/>";
1710
0
    }
1711
0
    if (bHasLERC)
1712
0
    {
1713
0
        osOptions +=
1714
0
            ""
1715
0
            "   <Option name='MAX_Z_ERROR' type='float' description='Maximum "
1716
0
            "error for LERC compression' default='0'/>"
1717
0
            "   <Option name='MAX_Z_ERROR_OVERVIEW' type='float' "
1718
0
            "description='Maximum error for LERC compression in overviews' "
1719
0
            "default='0'/>";
1720
0
    }
1721
#ifdef HAVE_JXL
1722
    osOptions +=
1723
        ""
1724
        "   <Option name='JXL_LOSSLESS' type='boolean' description='Whether "
1725
        "JPEGXL compression should be lossless' default='YES'/>"
1726
        "   <Option name='JXL_EFFORT' type='int' description='Level of effort "
1727
        "1(fast)-9(slow)' min='1' max='9' default='5'/>"
1728
        "   <Option name='JXL_DISTANCE' type='float' description='Distance "
1729
        "level for lossy compression (0=mathematically lossless, 1.0=visually "
1730
        "lossless, usual range [0.5,3])' default='1.0' min='0.01' max='25.0'/>";
1731
#ifdef HAVE_JxlEncoderSetExtraChannelDistance
1732
    osOptions += "   <Option name='JXL_ALPHA_DISTANCE' type='float' "
1733
                 "description='Distance level for alpha channel "
1734
                 "(-1=same as non-alpha channels, "
1735
                 "0=mathematically lossless, 1.0=visually lossless, "
1736
                 "usual range [0.5,3])' default='-1' min='-1' max='25.0'/>";
1737
#endif
1738
#endif
1739
0
    osOptions +=
1740
0
        "   <Option name='NUM_THREADS' type='string' "
1741
0
        "description='Number of worker threads for compression. "
1742
0
        "Can be set to ALL_CPUS' default='1'/>"
1743
0
        "   <Option name='NBITS' type='int' description='BITS for sub-byte "
1744
0
        "files (1-7), sub-uint16_t (9-15), sub-uint32_t (17-31), or float32 "
1745
0
        "(16)'/>"
1746
0
        "   <Option name='BLOCKSIZE' type='int' "
1747
0
        "description='Tile size in pixels' min='128' default='512'/>"
1748
0
        "   <Option name='INTERLEAVE' type='string-select' default='PIXEL'>"
1749
0
        "       <Value>BAND</Value>"
1750
0
        "       <Value>PIXEL</Value>"
1751
0
        "       <Value>TILE</Value>"
1752
0
        "   </Option>"
1753
0
        "   <Option name='BIGTIFF' type='string-select' description='"
1754
0
        "Force creation of BigTIFF file'>"
1755
0
        "     <Value>YES</Value>"
1756
0
        "     <Value>NO</Value>"
1757
0
        "     <Value>IF_NEEDED</Value>"
1758
0
        "     <Value>IF_SAFER</Value>"
1759
0
        "   </Option>"
1760
0
        "   <Option name='RESAMPLING' type='string' "
1761
0
        "description='Resampling method for overviews or warping'/>"
1762
0
        "   <Option name='OVERVIEW_RESAMPLING' type='string' "
1763
0
        "description='Resampling method for overviews'/>"
1764
0
        "   <Option name='WARP_RESAMPLING' type='string' "
1765
0
        "description='Resampling method for warping'/>"
1766
0
        "   <Option name='OVERVIEWS' type='string-select' description='"
1767
0
        "Behavior regarding overviews'>"
1768
0
        "     <Value>AUTO</Value>"
1769
0
        "     <Value>IGNORE_EXISTING</Value>"
1770
0
        "     <Value>FORCE_USE_EXISTING</Value>"
1771
0
        "     <Value>NONE</Value>"
1772
0
        "   </Option>"
1773
0
        "  <Option name='OVERVIEW_COUNT' type='int' min='0' "
1774
0
        "description='Number of overviews'/>"
1775
0
        "  <Option name='TILING_SCHEME' type='string-select' description='"
1776
0
        "Which tiling scheme to use pre-defined value or custom inline/outline "
1777
0
        "JSON definition' default='CUSTOM'>"
1778
0
        "    <Value>CUSTOM</Value>";
1779
1780
0
    const auto tmsList = gdal::TileMatrixSet::listPredefinedTileMatrixSets();
1781
0
    for (const auto &tmsName : tmsList)
1782
0
    {
1783
0
        const auto poTM = gdal::TileMatrixSet::parse(tmsName.c_str());
1784
0
        if (poTM && poTM->haveAllLevelsSameTopLeft() &&
1785
0
            poTM->haveAllLevelsSameTileSize() &&
1786
0
            !poTM->hasVariableMatrixWidth())
1787
0
        {
1788
0
            osOptions += "    <Value>";
1789
0
            osOptions += tmsName;
1790
0
            osOptions += "</Value>";
1791
0
        }
1792
0
    }
1793
1794
0
    osOptions +=
1795
0
        "  </Option>"
1796
0
        "  <Option name='ZOOM_LEVEL' type='int' description='Target zoom "
1797
0
        "level. "
1798
0
        "Only used for TILING_SCHEME != CUSTOM'/>"
1799
0
        "  <Option name='ZOOM_LEVEL_STRATEGY' type='string-select' "
1800
0
        "description='Strategy to determine zoom level. "
1801
0
        "Only used for TILING_SCHEME != CUSTOM' default='AUTO'>"
1802
0
        "    <Value>AUTO</Value>"
1803
0
        "    <Value>LOWER</Value>"
1804
0
        "    <Value>UPPER</Value>"
1805
0
        "  </Option>"
1806
0
        "   <Option name='TARGET_SRS' type='string' "
1807
0
        "description='Target SRS as EPSG:XXXX, WKT or PROJ string for "
1808
0
        "reprojection'/>"
1809
0
        "  <Option name='RES' type='float' description='"
1810
0
        "Target resolution for reprojection'/>"
1811
0
        "  <Option name='EXTENT' type='string' description='"
1812
0
        "Target extent as minx,miny,maxx,maxy for reprojection'/>"
1813
0
        "  <Option name='ALIGNED_LEVELS' type='int' description='"
1814
0
        "Number of resolution levels for which the tiles from GeoTIFF and the "
1815
0
        "specified tiling scheme match'/>"
1816
0
        "  <Option name='ADD_ALPHA' type='boolean' description='Can be set to "
1817
0
        "NO to "
1818
0
        "disable the addition of an alpha band in case of reprojection' "
1819
0
        "default='YES'/>"
1820
0
#if LIBGEOTIFF_VERSION >= 1600
1821
0
        "   <Option name='GEOTIFF_VERSION' type='string-select' default='AUTO' "
1822
0
        "description='Which version of GeoTIFF must be used'>"
1823
0
        "       <Value>AUTO</Value>"
1824
0
        "       <Value>1.0</Value>"
1825
0
        "       <Value>1.1</Value>"
1826
0
        "   </Option>"
1827
0
#endif
1828
0
        "   <Option name='SPARSE_OK' type='boolean' description='Should empty "
1829
0
        "blocks be omitted on disk?' default='FALSE'/>"
1830
0
        "   <Option name='STATISTICS' type='string-select' default='AUTO' "
1831
0
        "description='Which to add statistics to the output file'>"
1832
0
        "       <Value>AUTO</Value>"
1833
0
        "       <Value>YES</Value>"
1834
0
        "       <Value>NO</Value>"
1835
0
        "   </Option>"
1836
0
        "</CreationOptionList>";
1837
1838
0
    SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST, osOptions.c_str());
1839
0
}
1840
1841
/************************************************************************/
1842
/*                         COGValidateAlgorithm                         */
1843
/************************************************************************/
1844
1845
#ifndef _
1846
0
#define _(x) x
1847
#endif
1848
1849
class COGValidateAlgorithm final : public GDALAlgorithm
1850
{
1851
  public:
1852
    COGValidateAlgorithm()
1853
0
        : GDALAlgorithm("validate",
1854
0
                        "Validate if a TIFF file is a Cloud Optimized GeoTIFF",
1855
0
                        "/programs/gdal_driver_cog_validate.html")
1856
0
    {
1857
0
        constexpr int type = GDAL_OF_RASTER;
1858
0
        auto &arg = AddArg("dataset", 0, _("COG dataset"), &m_dataset, type)
1859
0
                        .AddAlias("input")
1860
0
                        .SetPositional()
1861
0
                        .SetRequired();
1862
0
        SetAutoCompleteFunctionForFilename(arg, type);
1863
1864
0
        AddArg("full-check", 0, _("Whether to perform full check"),
1865
0
               &m_fullCheck)
1866
0
            .SetChoices("auto", "yes", "no")
1867
0
            .SetDefault(m_fullCheck);
1868
1869
0
        AddOutputStringArg(&m_output);
1870
0
        AddProgressArg();
1871
0
    }
1872
1873
  protected:
1874
    bool RunImpl(GDALProgressFunc, void *) override;
1875
1876
  private:
1877
    GDALArgDatasetValue m_dataset{};
1878
    std::string m_output{};
1879
    std::string m_fullCheck{"auto"};
1880
};
1881
1882
/************************************************************************/
1883
/*                              RunImpl()                               */
1884
/************************************************************************/
1885
1886
bool COGValidateAlgorithm::RunImpl(GDALProgressFunc, void *)
1887
0
{
1888
0
    auto poDS = m_dataset.GetDatasetRef();
1889
0
    CPLAssert(poDS);
1890
0
    auto poDriver = poDS->GetDriver();
1891
0
    if (!poDriver || !EQUAL(poDriver->GetDescription(), "GTIFF"))
1892
0
    {
1893
0
        ReportError(CE_Failure, CPLE_AppDefined, "%s is not a TIFF file",
1894
0
                    m_dataset.GetName().c_str());
1895
0
        return false;
1896
0
    }
1897
1898
0
    if (!GDALPythonInitialize())
1899
0
        return false;
1900
1901
0
    GIL_Holder oHolder(false);
1902
1903
0
    const CPLString osModuleName(CPLSPrintf("cog_validate_module_%p", this));
1904
0
    PyObject *poCompiledString = Py_CompileString(
1905
0
        "from osgeo_utils.samples.validate_cloud_optimized_geotiff import "
1906
0
        "main, get_output_string\n",
1907
0
        osModuleName, Py_file_input);
1908
0
    if (poCompiledString == nullptr || PyErr_Occurred())
1909
0
    {
1910
0
        CPLError(CE_Failure, CPLE_AppDefined, "Couldn't compile code:\n%s",
1911
0
                 GetPyExceptionString().c_str());
1912
0
        return false;
1913
0
    }
1914
0
    PyObject *poModule =
1915
0
        PyImport_ExecCodeModule(osModuleName, poCompiledString);
1916
0
    Py_DecRef(poCompiledString);
1917
1918
0
    if (poModule == nullptr || PyErr_Occurred())
1919
0
    {
1920
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s",
1921
0
                 GetPyExceptionString().c_str());
1922
0
        return false;
1923
0
    }
1924
1925
0
    PyObject *poMain = PyObject_GetAttrString(poModule, "main");
1926
0
    CPLAssert(poMain);
1927
0
    PyObject *poGetOutput =
1928
0
        PyObject_GetAttrString(poModule, "get_output_string");
1929
0
    CPLAssert(poGetOutput);
1930
1931
0
    Py_DecRef(poModule);
1932
1933
0
    PyObject *pyArgv = PyTuple_New(3);
1934
0
    PyTuple_SetItem(pyArgv, 0, PyUnicode_FromString("dummy"));
1935
0
    PyTuple_SetItem(pyArgv, 1,
1936
0
                    PyUnicode_FromString(m_dataset.GetName().c_str()));
1937
0
    PyTuple_SetItem(
1938
0
        pyArgv, 2,
1939
0
        PyUnicode_FromString(("--full-check=" + m_fullCheck).c_str()));
1940
0
    PyObject *pyKwargs = PyDict_New();
1941
0
    PyDict_SetItemString(pyKwargs, "argv", pyArgv);
1942
0
    PyDict_SetItemString(pyKwargs, "output_in_string", PyBool_FromLong(true));
1943
0
    PyObject *pyArgs = PyTuple_New(0);
1944
0
    PyObject *pRetValue = PyObject_Call(poMain, pyArgs, pyKwargs);
1945
0
    const auto nRetValue = pRetValue ? PyLong_AsLong(pRetValue) : -1;
1946
0
    Py_DecRef(pyArgs);
1947
0
    Py_DecRef(pyKwargs);
1948
0
    Py_DecRef(pRetValue);
1949
1950
0
    if (!m_quiet || nRetValue)
1951
0
    {
1952
0
        pyArgs = PyTuple_New(0);
1953
0
        pyKwargs = PyDict_New();
1954
0
        PyObject *poMsg = PyObject_Call(poGetOutput, pyArgs, pyKwargs);
1955
0
        Py_DecRef(pyArgs);
1956
0
        Py_DecRef(pyKwargs);
1957
0
        if (poMsg)
1958
0
            m_output = GetString(poMsg);
1959
0
        Py_DecRef(poMsg);
1960
0
    }
1961
1962
0
    Py_DecRef(poMain);
1963
0
    Py_DecRef(poGetOutput);
1964
1965
0
    if (nRetValue && !IsCalledFromCommandLine())
1966
0
    {
1967
0
        ReportError(CE_Failure, CPLE_AppDefined, "%s", m_output.c_str());
1968
0
    }
1969
1970
0
    return nRetValue == 0;
1971
0
}
1972
1973
/************************************************************************/
1974
/*                   COGDriverInstantiateAlgorithm()                    */
1975
/************************************************************************/
1976
1977
static GDALAlgorithm *
1978
COGDriverInstantiateAlgorithm(const std::vector<std::string> &aosPath)
1979
0
{
1980
0
    if (aosPath.size() == 1 && aosPath[0] == "validate")
1981
0
    {
1982
0
        return std::make_unique<COGValidateAlgorithm>().release();
1983
0
    }
1984
0
    else
1985
0
    {
1986
0
        return nullptr;
1987
0
    }
1988
0
}
1989
1990
void GDALRegister_COG()
1991
1992
1
{
1993
1
    if (GDALGetDriverByName("COG") != nullptr)
1994
0
        return;
1995
1996
1
    auto poDriver = new GDALCOGDriver();
1997
1
    poDriver->SetDescription("COG");
1998
1
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1999
1
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
2000
1
                              "Cloud optimized GeoTIFF generator");
2001
1
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/cog.html");
2002
1
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "tif tiff");
2003
1
    poDriver->SetMetadataItem(GDAL_DMD_OVERVIEW_CREATIONOPTIONLIST,
2004
1
                              "<OverviewCreationOptionList>"
2005
1
                              "</OverviewCreationOptionList>");
2006
2007
1
    poDriver->SetMetadataItem(
2008
1
        GDAL_DMD_CREATIONDATATYPES,
2009
1
        "Byte Int8 UInt16 Int16 UInt32 Int32 UInt64 Int64 Float32 "
2010
1
        "Float64 CInt16 CInt32 CFloat32 CFloat64");
2011
2012
1
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
2013
1
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE_ONLY_VISIBLE_AT_CLOSE_TIME,
2014
1
                              "YES");
2015
1
    poDriver->SetMetadataItem(GDAL_DCAP_CAN_READ_AFTER_DELETE, "YES");
2016
2017
1
    poDriver->SetMetadataItem(GDAL_DCAP_COORDINATE_EPOCH, "YES");
2018
2019
1
    poDriver->pfnCreateCopy = COGCreateCopy;
2020
1
    poDriver->pfnCreate = COGCreate;
2021
2022
1
    poDriver->pfnInstantiateAlgorithm = COGDriverInstantiateAlgorithm;
2023
1
    poDriver->DeclareAlgorithm({"validate"});
2024
2025
1
    GetGDALDriverManager()->RegisterDriver(poDriver);
2026
1
}