LCOV - code coverage report
Current view: top level - frmts/gtiff - cogdriver.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 747 795 94.0 %
Date: 2024-11-25 23:50:41 Functions: 21 21 100.0 %

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

Generated by: LCOV version 1.14