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

Generated by: LCOV version 1.14