Coverage Report

Created: 2025-11-16 06:25

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