LCOV - code coverage report
Current view: top level - frmts/gtiff - cogdriver.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 837 886 94.5 %
Date: 2025-12-24 19:12:58 Functions: 29 29 100.0 %

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

Generated by: LCOV version 1.14