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

Generated by: LCOV version 1.14