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