• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 15899162844

26 Jun 2025 10:14AM UTC coverage: 71.088% (+0.004%) from 71.084%
15899162844

Pull #12623

github

web-flow
Merge c704a8392 into f5cb024d4
Pull Request #12623: gdal raster overview add: add a --overview-src option

209 of 244 new or added lines in 5 files covered. (85.66%)

96 existing lines in 44 files now uncovered.

574014 of 807474 relevant lines covered (71.09%)

250815.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.28
/apps/gdalalg_raster_tile.cpp
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  gdal "raster tile" subcommand
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12

13
#include "gdalalg_raster_tile.h"
14

15
#include "cpl_conv.h"
16
#include "cpl_mem_cache.h"
17
#include "cpl_worker_thread_pool.h"
18
#include "gdal_alg_priv.h"
19
#include "gdal_priv.h"
20
#include "gdalwarper.h"
21
#include "gdal_utils.h"
22
#include "ogr_spatialref.h"
23
#include "memdataset.h"
24
#include "tilematrixset.hpp"
25

26
#include <algorithm>
27
#include <array>
28
#include <atomic>
29
#include <cmath>
30
#include <mutex>
31

32
//! @cond Doxygen_Suppress
33

34
#ifndef _
35
#define _(x) (x)
36
#endif
37

38
/************************************************************************/
39
/*           GDALRasterTileAlgorithm::GDALRasterTileAlgorithm()         */
40
/************************************************************************/
41

42
GDALRasterTileAlgorithm::GDALRasterTileAlgorithm()
98✔
43
    : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
98✔
44
{
45
    AddProgressArg();
98✔
46
    AddOpenOptionsArg(&m_openOptions);
98✔
47
    AddInputFormatsArg(&m_inputFormats)
98✔
48
        .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_RASTER});
196✔
49
    AddInputDatasetArg(&m_dataset, GDAL_OF_RASTER);
98✔
50
    AddOutputFormatArg(&m_outputFormat)
98✔
51
        .SetDefault(m_outputFormat)
98✔
52
        .AddMetadataItem(
53
            GAAMDI_REQUIRED_CAPABILITIES,
54
            {GDAL_DCAP_RASTER, GDAL_DCAP_CREATECOPY, GDAL_DMD_EXTENSIONS})
490✔
55
        .AddMetadataItem(GAAMDI_VRT_COMPATIBLE, {"false"});
196✔
56
    AddCreationOptionsArg(&m_creationOptions);
98✔
57

58
    AddArg("output", 'o', _("Output directory"), &m_outputDirectory)
196✔
59
        .SetRequired()
98✔
60
        .SetMinCharCount(1)
98✔
61
        .SetPositional();
98✔
62

63
    std::vector<std::string> tilingSchemes{"raster"};
392✔
64
    for (const std::string &scheme :
882✔
65
         gdal::TileMatrixSet::listPredefinedTileMatrixSets())
1,862✔
66
    {
67
        auto poTMS = gdal::TileMatrixSet::parse(scheme.c_str());
1,764✔
68
        OGRSpatialReference oSRS_TMS;
1,764✔
69
        if (poTMS && !poTMS->hasVariableMatrixWidth() &&
1,764✔
70
            oSRS_TMS.SetFromUserInput(poTMS->crs().c_str()) == OGRERR_NONE)
882✔
71
        {
72
            std::string identifier = scheme == "GoogleMapsCompatible"
882✔
73
                                         ? "WebMercatorQuad"
74
                                         : poTMS->identifier();
1,764✔
75
            m_mapTileMatrixIdentifierToScheme[identifier] = scheme;
882✔
76
            tilingSchemes.push_back(std::move(identifier));
882✔
77
        }
78
    }
79
    AddArg("tiling-scheme", 0, _("Tiling scheme"), &m_tilingScheme)
196✔
80
        .SetDefault("WebMercatorQuad")
98✔
81
        .SetChoices(tilingSchemes)
98✔
82
        .SetHiddenChoices(
83
            "GoogleMapsCompatible",  // equivalent of WebMercatorQuad
84
            "mercator",              // gdal2tiles equivalent of WebMercatorQuad
85
            "geodetic"  // gdal2tiles (not totally) equivalent of WorldCRS84Quad
86
        );
98✔
87

88
    AddArg("min-zoom", 0, _("Minimum zoom level"), &m_minZoomLevel)
196✔
89
        .SetMinValueIncluded(0);
98✔
90
    AddArg("max-zoom", 0, _("Maximum zoom level"), &m_maxZoomLevel)
196✔
91
        .SetMinValueIncluded(0);
98✔
92

93
    AddArg("min-x", 0, _("Minimum tile X coordinate"), &m_minTileX)
196✔
94
        .SetMinValueIncluded(0);
98✔
95
    AddArg("max-x", 0, _("Maximum tile X coordinate"), &m_maxTileX)
196✔
96
        .SetMinValueIncluded(0);
98✔
97
    AddArg("min-y", 0, _("Minimum tile Y coordinate"), &m_minTileY)
196✔
98
        .SetMinValueIncluded(0);
98✔
99
    AddArg("max-y", 0, _("Maximum tile Y coordinate"), &m_maxTileY)
196✔
100
        .SetMinValueIncluded(0);
98✔
101
    AddArg("no-intersection-ok", 0,
102
           _("Whether dataset extent not intersecting tile matrix is only a "
103
             "warning"),
104
           &m_noIntersectionIsOK);
98✔
105

106
    AddArg("resampling", 'r', _("Resampling method for max zoom"),
107
           &m_resampling)
196✔
108
        .SetChoices("nearest", "bilinear", "cubic", "cubicspline", "lanczos",
109
                    "average", "rms", "mode", "min", "max", "med", "q1", "q3",
110
                    "sum")
98✔
111
        .SetDefault("cubic")
98✔
112
        .SetHiddenChoices("near");
98✔
113
    AddArg("overview-resampling", 0, _("Resampling method for overviews"),
114
           &m_overviewResampling)
196✔
115
        .SetChoices("nearest", "bilinear", "cubic", "cubicspline", "lanczos",
116
                    "average", "rms", "mode", "min", "max", "med", "q1", "q3",
117
                    "sum")
98✔
118
        .SetHiddenChoices("near");
98✔
119

120
    AddArg("convention", 0,
121
           _("Tile numbering convention: xyz (from top) or tms (from bottom)"),
122
           &m_convention)
196✔
123
        .SetDefault(m_convention)
98✔
124
        .SetChoices("xyz", "tms");
98✔
125
    AddArg("tile-size", 0, _("Override default tile size"), &m_tileSize)
196✔
126
        .SetMinValueIncluded(64)
98✔
127
        .SetMaxValueIncluded(32768);
98✔
128
    AddArg("add-alpha", 0, _("Whether to force adding an alpha channel"),
129
           &m_addalpha)
196✔
130
        .SetMutualExclusionGroup("alpha");
98✔
131
    AddArg("no-alpha", 0, _("Whether to disable adding an alpha channel"),
132
           &m_noalpha)
196✔
133
        .SetMutualExclusionGroup("alpha");
98✔
134
    auto &dstNoDataArg =
135
        AddArg("dst-nodata", 0, _("Destination nodata value"), &m_dstNoData);
98✔
136
    AddArg("skip-blank", 0, _("Do not generate blank tiles"), &m_skipBlank);
98✔
137

138
    {
139
        auto &arg = AddArg("metadata", 0,
140
                           _("Add metadata item to output tiles"), &m_metadata)
196✔
141
                        .SetMetaVar("<KEY>=<VALUE>")
196✔
142
                        .SetPackedValuesAllowed(false);
98✔
143
        arg.AddValidationAction([this, &arg]()
4✔
144
                                { return ParseAndValidateKeyValue(arg); });
102✔
145
        arg.AddHiddenAlias("mo");
98✔
146
    }
147
    AddArg("copy-src-metadata", 0,
148
           _("Whether to copy metadata from source dataset"),
149
           &m_copySrcMetadata);
98✔
150

151
    AddArg("aux-xml", 0, _("Generate .aux.xml sidecar files when needed"),
152
           &m_auxXML);
98✔
153
    AddArg("kml", 0, _("Generate KML files"), &m_kml);
98✔
154
    AddArg("resume", 0, _("Generate only missing files"), &m_resume);
98✔
155

156
    AddNumThreadsArg(&m_numThreads, &m_numThreadsStr);
98✔
157

158
    constexpr const char *ADVANCED_RESAMPLING_CATEGORY = "Advanced Resampling";
98✔
159
    auto &excludedValuesArg =
160
        AddArg("excluded-values", 0,
161
               _("Tuples of values (e.g. <R>,<G>,<B> or (<R1>,<G1>,<B1>),"
162
                 "(<R2>,<G2>,<B2>)) that must beignored as contributing source "
163
                 "pixels during (average) resampling"),
164
               &m_excludedValues)
196✔
165
            .SetCategory(ADVANCED_RESAMPLING_CATEGORY);
98✔
166
    auto &excludedValuesPctThresholdArg =
167
        AddArg(
168
            "excluded-values-pct-threshold", 0,
169
            _("Minimum percentage of source pixels that must be set at one of "
170
              "the --excluded-values to cause the excluded value to be used as "
171
              "the target pixel value"),
172
            &m_excludedValuesPctThreshold)
196✔
173
            .SetDefault(m_excludedValuesPctThreshold)
98✔
174
            .SetMinValueIncluded(0)
98✔
175
            .SetMaxValueIncluded(100)
98✔
176
            .SetCategory(ADVANCED_RESAMPLING_CATEGORY);
98✔
177
    auto &nodataValuesPctThresholdArg =
178
        AddArg(
179
            "nodata-values-pct-threshold", 0,
180
            _("Minimum percentage of source pixels that must be set at one of "
181
              "nodata (or alpha=0 or any other way to express transparent pixel"
182
              "to cause the target pixel value to be transparent"),
183
            &m_nodataValuesPctThreshold)
196✔
184
            .SetDefault(m_nodataValuesPctThreshold)
98✔
185
            .SetMinValueIncluded(0)
98✔
186
            .SetMaxValueIncluded(100)
98✔
187
            .SetCategory(ADVANCED_RESAMPLING_CATEGORY);
98✔
188

189
    constexpr const char *PUBLICATION_CATEGORY = "Publication";
98✔
190
    AddArg("webviewer", 0, _("Web viewer to generate"), &m_webviewers)
196✔
191
        .SetDefault("all")
98✔
192
        .SetChoices("none", "all", "leaflet", "openlayers", "mapml")
98✔
193
        .SetCategory(PUBLICATION_CATEGORY);
98✔
194
    AddArg("url", 0,
195
           _("URL address where the generated tiles are going to be published"),
196
           &m_url)
196✔
197
        .SetCategory(PUBLICATION_CATEGORY);
98✔
198
    AddArg("title", 0, _("Title of the map"), &m_title)
196✔
199
        .SetCategory(PUBLICATION_CATEGORY);
98✔
200
    AddArg("copyright", 0, _("Copyright for the map"), &m_copyright)
196✔
201
        .SetCategory(PUBLICATION_CATEGORY);
98✔
202
    AddArg("mapml-template", 0,
203
           _("Filename of a template mapml file where variables will be "
204
             "substituted"),
205
           &m_mapmlTemplate)
196✔
206
        .SetMinCharCount(1)
98✔
207
        .SetCategory(PUBLICATION_CATEGORY);
98✔
208

209
    AddValidationAction(
98✔
210
        [this, &dstNoDataArg, &excludedValuesArg,
93✔
211
         &excludedValuesPctThresholdArg, &nodataValuesPctThresholdArg]()
564✔
212
        {
213
            if (m_minTileX >= 0 && m_maxTileX >= 0 && m_minTileX > m_maxTileX)
93✔
214
            {
215
                ReportError(CE_Failure, CPLE_IllegalArg,
1✔
216
                            "'min-x' must be lesser or equal to 'max-x'");
217
                return false;
1✔
218
            }
219

220
            if (m_minTileY >= 0 && m_maxTileY >= 0 && m_minTileY > m_maxTileY)
92✔
221
            {
222
                ReportError(CE_Failure, CPLE_IllegalArg,
1✔
223
                            "'min-y' must be lesser or equal to 'max-y'");
224
                return false;
1✔
225
            }
226

227
            if (m_minZoomLevel >= 0 && m_maxZoomLevel >= 0 &&
91✔
228
                m_minZoomLevel > m_maxZoomLevel)
15✔
229
            {
230
                ReportError(CE_Failure, CPLE_IllegalArg,
1✔
231
                            "'min-zoom' must be lesser or equal to 'max-zoom'");
232
                return false;
1✔
233
            }
234

235
            if (m_addalpha && dstNoDataArg.IsExplicitlySet())
90✔
236
            {
237
                ReportError(
1✔
238
                    CE_Failure, CPLE_IllegalArg,
239
                    "'add-alpha' and 'dst-nodata' are mutually exclusive");
240
                return false;
1✔
241
            }
242

243
            for (const auto *arg :
261✔
244
                 {&excludedValuesArg, &excludedValuesPctThresholdArg,
245
                  &nodataValuesPctThresholdArg})
350✔
246
            {
247
                if (arg->IsExplicitlySet() && m_resampling != "average")
263✔
248
                {
249
                    ReportError(CE_Failure, CPLE_AppDefined,
1✔
250
                                "'%s' can only be specified if 'resampling' is "
251
                                "set to 'average'",
252
                                arg->GetName().c_str());
1✔
253
                    return false;
2✔
254
                }
255
                if (arg->IsExplicitlySet() && !m_overviewResampling.empty() &&
263✔
256
                    m_overviewResampling != "average")
1✔
257
                {
258
                    ReportError(CE_Failure, CPLE_AppDefined,
1✔
259
                                "'%s' can only be specified if "
260
                                "'overview-resampling' is set to 'average'",
261
                                arg->GetName().c_str());
1✔
262
                    return false;
1✔
263
                }
264
            }
265

266
            return true;
87✔
267
        });
268
}
98✔
269

270
/************************************************************************/
271
/*                          GetTileIndices()                            */
272
/************************************************************************/
273

274
static bool GetTileIndices(gdal::TileMatrixSet::TileMatrix &tileMatrix,
177✔
275
                           bool bInvertAxisTMS, int tileSize,
276
                           const double adfExtent[4], int &nMinTileX,
277
                           int &nMinTileY, int &nMaxTileX, int &nMaxTileY,
278
                           bool noIntersectionIsOK, bool &bIntersects,
279
                           bool checkRasterOverflow = true)
280
{
281
    if (tileSize > 0)
177✔
282
    {
283
        tileMatrix.mResX *=
18✔
284
            static_cast<double>(tileMatrix.mTileWidth) / tileSize;
18✔
285
        tileMatrix.mResY *=
18✔
286
            static_cast<double>(tileMatrix.mTileHeight) / tileSize;
18✔
287
        tileMatrix.mTileWidth = tileSize;
18✔
288
        tileMatrix.mTileHeight = tileSize;
18✔
289
    }
290

291
    if (bInvertAxisTMS)
177✔
292
        std::swap(tileMatrix.mTopLeftX, tileMatrix.mTopLeftY);
2✔
293

294
    const double dfTileWidth = tileMatrix.mResX * tileMatrix.mTileWidth;
177✔
295
    const double dfTileHeight = tileMatrix.mResY * tileMatrix.mTileHeight;
177✔
296

297
    constexpr double EPSILON = 1e-3;
177✔
298
    const double dfMinTileX =
177✔
299
        (adfExtent[0] - tileMatrix.mTopLeftX) / dfTileWidth;
177✔
300
    nMinTileX = static_cast<int>(
177✔
301
        std::clamp(std::floor(dfMinTileX + EPSILON), 0.0,
354✔
302
                   static_cast<double>(tileMatrix.mMatrixWidth - 1)));
177✔
303
    const double dfMinTileY =
177✔
304
        (tileMatrix.mTopLeftY - adfExtent[3]) / dfTileHeight;
177✔
305
    nMinTileY = static_cast<int>(
177✔
306
        std::clamp(std::floor(dfMinTileY + EPSILON), 0.0,
354✔
307
                   static_cast<double>(tileMatrix.mMatrixHeight - 1)));
177✔
308
    const double dfMaxTileX =
177✔
309
        (adfExtent[2] - tileMatrix.mTopLeftX) / dfTileWidth;
177✔
310
    nMaxTileX = static_cast<int>(
177✔
311
        std::clamp(std::floor(dfMaxTileX + EPSILON), 0.0,
354✔
312
                   static_cast<double>(tileMatrix.mMatrixWidth - 1)));
177✔
313
    const double dfMaxTileY =
177✔
314
        (tileMatrix.mTopLeftY - adfExtent[1]) / dfTileHeight;
177✔
315
    nMaxTileY = static_cast<int>(
177✔
316
        std::clamp(std::floor(dfMaxTileY + EPSILON), 0.0,
354✔
317
                   static_cast<double>(tileMatrix.mMatrixHeight - 1)));
177✔
318

319
    bIntersects = (dfMinTileX <= tileMatrix.mMatrixWidth && dfMaxTileX >= 0 &&
177✔
320
                   dfMinTileY <= tileMatrix.mMatrixHeight && dfMaxTileY >= 0);
354✔
321
    if (!bIntersects)
177✔
322
    {
323
        CPLDebug("gdal_raster_tile",
2✔
324
                 "dfMinTileX=%g dfMinTileY=%g dfMaxTileX=%g dfMaxTileY=%g",
325
                 dfMinTileX, dfMinTileY, dfMaxTileX, dfMaxTileY);
326
        CPLError(noIntersectionIsOK ? CE_Warning : CE_Failure, CPLE_AppDefined,
2✔
327
                 "Extent of source dataset is not compatible with extent of "
328
                 "tile matrix %s",
329
                 tileMatrix.mId.c_str());
330
        return noIntersectionIsOK;
2✔
331
    }
332
    if (checkRasterOverflow)
175✔
333
    {
334
        if (nMaxTileX - nMinTileX + 1 > INT_MAX / tileMatrix.mTileWidth ||
107✔
335
            nMaxTileY - nMinTileY + 1 > INT_MAX / tileMatrix.mTileHeight)
107✔
336
        {
337
            CPLError(CE_Failure, CPLE_AppDefined, "Too large zoom level");
×
338
            return false;
×
339
        }
340
    }
341
    return true;
175✔
342
}
343

344
/************************************************************************/
345
/*                           GetFileY()                                 */
346
/************************************************************************/
347

348
static int GetFileY(int iY, const gdal::TileMatrixSet::TileMatrix &tileMatrix,
1,949✔
349
                    const std::string &convention)
350
{
351
    return convention == "xyz" ? iY : tileMatrix.mMatrixHeight - 1 - iY;
1,949✔
352
}
353

354
/************************************************************************/
355
/*                          GenerateTile()                              */
356
/************************************************************************/
357

358
static bool GenerateTile(
263✔
359
    GDALDataset *poSrcDS, GDALDriver *poDstDriver, const char *pszExtension,
360
    CSLConstList creationOptions, GDALWarpOperation &oWO,
361
    const OGRSpatialReference &oSRS_TMS, GDALDataType eWorkingDataType,
362
    const gdal::TileMatrixSet::TileMatrix &tileMatrix,
363
    const std::string &outputDirectory, int nBands, const double *pdfDstNoData,
364
    int nZoomLevel, int iX, int iY, const std::string &convention,
365
    int nMinTileX, int nMinTileY, bool bSkipBlank, bool bUserAskedForAlpha,
366
    bool bAuxXML, bool bResume, const std::vector<std::string> &metadata,
367
    const GDALColorTable *poColorTable, std::vector<GByte> &dstBuffer)
368
{
369
    const std::string osDirZ = CPLFormFilenameSafe(
370
        outputDirectory.c_str(), CPLSPrintf("%d", nZoomLevel), nullptr);
526✔
371
    const std::string osDirX =
372
        CPLFormFilenameSafe(osDirZ.c_str(), CPLSPrintf("%d", iX), nullptr);
526✔
373
    const int iFileY = GetFileY(iY, tileMatrix, convention);
263✔
374
    const std::string osFilename = CPLFormFilenameSafe(
375
        osDirX.c_str(), CPLSPrintf("%d", iFileY), pszExtension);
526✔
376

377
    if (bResume)
263✔
378
    {
379
        VSIStatBufL sStat;
380
        if (VSIStatL(osFilename.c_str(), &sStat) == 0)
5✔
381
            return true;
5✔
382
    }
383

384
    const int nDstXOff = (iX - nMinTileX) * tileMatrix.mTileWidth;
258✔
385
    const int nDstYOff = (iY - nMinTileY) * tileMatrix.mTileHeight;
258✔
386
    memset(dstBuffer.data(), 0, dstBuffer.size());
258✔
387
    const CPLErr eErr = oWO.WarpRegionToBuffer(
516✔
388
        nDstXOff, nDstYOff, tileMatrix.mTileWidth, tileMatrix.mTileHeight,
258✔
389
        dstBuffer.data(), eWorkingDataType);
258✔
390
    if (eErr != CE_None)
258✔
391
        return false;
×
392

393
    const bool bDstHasAlpha =
394
        nBands > poSrcDS->GetRasterCount() ||
287✔
395
        (nBands == poSrcDS->GetRasterCount() &&
29✔
396
         poSrcDS->GetRasterBand(nBands)->GetColorInterpretation() ==
28✔
397
             GCI_AlphaBand);
258✔
398
    const size_t nBytesPerBand = static_cast<size_t>(tileMatrix.mTileWidth) *
258✔
399
                                 tileMatrix.mTileHeight *
258✔
400
                                 GDALGetDataTypeSizeBytes(eWorkingDataType);
258✔
401
    if (bDstHasAlpha && bSkipBlank)
258✔
402
    {
403
        bool bBlank = true;
9✔
404
        for (size_t i = 0; i < nBytesPerBand && bBlank; ++i)
283,545✔
405
        {
406
            bBlank = (dstBuffer[(nBands - 1) * nBytesPerBand + i] == 0);
283,301✔
407
        }
408
        if (bBlank)
244✔
409
            return true;
5✔
410
    }
411
    if (bDstHasAlpha && !bUserAskedForAlpha)
488✔
412
    {
413
        bool bAllOpaque = true;
238✔
414
        for (size_t i = 0; i < nBytesPerBand && bAllOpaque; ++i)
11,066,300✔
415
        {
416
            bAllOpaque = (dstBuffer[(nBands - 1) * nBytesPerBand + i] == 255);
11,280,600✔
417
        }
418
        if (bAllOpaque)
×
419
            nBands--;
163✔
420
    }
421

422
    VSIMkdir(osDirZ.c_str(), 0755);
×
423
    VSIMkdir(osDirX.c_str(), 0755);
253✔
424

425
    auto memDS = std::unique_ptr<GDALDataset>(
426
        MEMDataset::Create("", tileMatrix.mTileWidth, tileMatrix.mTileHeight, 0,
253✔
427
                           eWorkingDataType, nullptr));
506✔
428
    for (int i = 0; i < nBands; ++i)
1,023✔
429
    {
430
        char szBuffer[32] = {'\0'};
770✔
431
        int nRet = CPLPrintPointer(
1,540✔
432
            szBuffer, dstBuffer.data() + i * nBytesPerBand, sizeof(szBuffer));
770✔
433
        szBuffer[nRet] = 0;
770✔
434

435
        char szOption[64] = {'\0'};
770✔
436
        snprintf(szOption, sizeof(szOption), "DATAPOINTER=%s", szBuffer);
770✔
437

438
        char *apszOptions[] = {szOption, nullptr};
770✔
439

440
        memDS->AddBand(eWorkingDataType, apszOptions);
770✔
441
        auto poDstBand = memDS->GetRasterBand(i + 1);
770✔
442
        if (i + 1 <= poSrcDS->GetRasterCount())
770✔
443
            poDstBand->SetColorInterpretation(
695✔
444
                poSrcDS->GetRasterBand(i + 1)->GetColorInterpretation());
695✔
445
        else
446
            poDstBand->SetColorInterpretation(GCI_AlphaBand);
75✔
447
        if (pdfDstNoData)
770✔
448
            poDstBand->SetNoDataValue(*pdfDstNoData);
9✔
449
        if (i == 0 && poColorTable)
770✔
450
            poDstBand->SetColorTable(
1✔
451
                const_cast<GDALColorTable *>(poColorTable));
1✔
452
    }
453
    const CPLStringList aosMD(metadata);
506✔
454
    for (const auto [key, value] : cpl::IterateNameValue(aosMD))
261✔
455
    {
456
        memDS->SetMetadataItem(key, value);
8✔
457
    }
458

459
    GDALGeoTransform gt;
253✔
460
    gt[0] =
506✔
461
        tileMatrix.mTopLeftX + iX * tileMatrix.mResX * tileMatrix.mTileWidth;
253✔
462
    gt[1] = tileMatrix.mResX;
253✔
463
    gt[2] = 0;
253✔
464
    gt[3] =
506✔
465
        tileMatrix.mTopLeftY - iY * tileMatrix.mResY * tileMatrix.mTileHeight;
253✔
466
    gt[4] = 0;
253✔
467
    gt[5] = -tileMatrix.mResY;
253✔
468
    memDS->SetGeoTransform(gt);
253✔
469

470
    memDS->SetSpatialRef(&oSRS_TMS);
253✔
471

472
    CPLConfigOptionSetter oSetter("GDAL_PAM_ENABLED", bAuxXML ? "YES" : "NO",
473
                                  false);
506✔
474

475
    const std::string osTmpFilename = osFilename + ".tmp." + pszExtension;
506✔
476

477
    std::unique_ptr<GDALDataset> poOutDS(
478
        poDstDriver->CreateCopy(osTmpFilename.c_str(), memDS.get(), false,
479
                                creationOptions, nullptr, nullptr));
253✔
480
    bool bRet = poOutDS && poOutDS->Close() == CE_None;
253✔
481
    poOutDS.reset();
253✔
482
    if (bRet)
253✔
483
    {
484
        bRet = VSIRename(osTmpFilename.c_str(), osFilename.c_str()) == 0;
236✔
485
        if (bAuxXML)
236✔
486
        {
487
            VSIRename((osTmpFilename + ".aux.xml").c_str(),
4✔
488
                      (osFilename + ".aux.xml").c_str());
8✔
489
        }
490
    }
491
    else
492
    {
493
        VSIUnlink(osTmpFilename.c_str());
17✔
494
    }
495
    return bRet;
253✔
496
}
497

498
/************************************************************************/
499
/*                    GenerateOverviewTile()                            */
500
/************************************************************************/
501

502
static bool
503
GenerateOverviewTile(GDALDataset &oSrcDS, GDALDriver *poDstDriver,
71✔
504
                     const std::string &outputFormat, const char *pszExtension,
505
                     CSLConstList creationOptions,
506
                     CSLConstList papszWarpOptions,
507
                     const std::string &resampling,
508
                     const gdal::TileMatrixSet::TileMatrix &tileMatrix,
509
                     const std::string &outputDirectory, int nZoomLevel, int iX,
510
                     int iY, const std::string &convention, bool bSkipBlank,
511
                     bool bUserAskedForAlpha, bool bAuxXML, bool bResume)
512
{
513
    const std::string osDirZ = CPLFormFilenameSafe(
514
        outputDirectory.c_str(), CPLSPrintf("%d", nZoomLevel), nullptr);
142✔
515
    const std::string osDirX =
516
        CPLFormFilenameSafe(osDirZ.c_str(), CPLSPrintf("%d", iX), nullptr);
142✔
517

518
    const int iFileY = GetFileY(iY, tileMatrix, convention);
71✔
519
    const std::string osFilename = CPLFormFilenameSafe(
520
        osDirX.c_str(), CPLSPrintf("%d", iFileY), pszExtension);
142✔
521

522
    if (bResume)
71✔
523
    {
524
        VSIStatBufL sStat;
525
        if (VSIStatL(osFilename.c_str(), &sStat) == 0)
2✔
526
            return true;
1✔
527
    }
528

529
    VSIMkdir(osDirZ.c_str(), 0755);
70✔
530
    VSIMkdir(osDirX.c_str(), 0755);
70✔
531

532
    CPLStringList aosOptions;
140✔
533

534
    aosOptions.AddString("-of");
70✔
535
    aosOptions.AddString(outputFormat.c_str());
70✔
536

537
    for (const char *pszCO : cpl::Iterate(creationOptions))
94✔
538
    {
539
        aosOptions.AddString("-co");
24✔
540
        aosOptions.AddString(pszCO);
24✔
541
    }
542
    CPLConfigOptionSetter oSetter("GDAL_PAM_ENABLED", bAuxXML ? "YES" : "NO",
543
                                  false);
140✔
544

545
    aosOptions.AddString("-r");
70✔
546
    aosOptions.AddString(resampling.c_str());
70✔
547

548
    std::unique_ptr<GDALDataset> poOutDS;
70✔
549
    const double dfMinX =
70✔
550
        tileMatrix.mTopLeftX + iX * tileMatrix.mResX * tileMatrix.mTileWidth;
70✔
551
    const double dfMaxY =
70✔
552
        tileMatrix.mTopLeftY - iY * tileMatrix.mResY * tileMatrix.mTileHeight;
70✔
553
    const double dfMaxX = dfMinX + tileMatrix.mResX * tileMatrix.mTileWidth;
70✔
554
    const double dfMinY = dfMaxY - tileMatrix.mResY * tileMatrix.mTileHeight;
70✔
555

556
    const bool resamplingCompatibleOfTranslate =
557
        papszWarpOptions == nullptr &&
199✔
558
        (resampling == "nearest" || resampling == "average" ||
190✔
559
         resampling == "bilinear" || resampling == "cubic" ||
125✔
560
         resampling == "cubicspline" || resampling == "lanczos" ||
9✔
561
         resampling == "mode");
3✔
562

563
    const std::string osTmpFilename = osFilename + ".tmp." + pszExtension;
140✔
564

565
    if (resamplingCompatibleOfTranslate)
70✔
566
    {
567
        GDALGeoTransform upperGT;
64✔
568
        oSrcDS.GetGeoTransform(upperGT);
64✔
569
        const double dfMinXUpper = upperGT[0];
64✔
570
        const double dfMaxXUpper =
571
            dfMinXUpper + upperGT[1] * oSrcDS.GetRasterXSize();
64✔
572
        const double dfMaxYUpper = upperGT[3];
64✔
573
        const double dfMinYUpper =
574
            dfMaxYUpper + upperGT[5] * oSrcDS.GetRasterYSize();
64✔
575
        if (dfMinX >= dfMinXUpper && dfMaxX <= dfMaxXUpper &&
64✔
576
            dfMinY >= dfMinYUpper && dfMaxY <= dfMaxYUpper)
48✔
577
        {
578
            // If the overview tile is fully within the extent of the
579
            // upper zoom level, we can use GDALDataset::RasterIO() directly.
580

581
            const auto eDT = oSrcDS.GetRasterBand(1)->GetRasterDataType();
48✔
582
            const size_t nBytesPerBand =
583
                static_cast<size_t>(tileMatrix.mTileWidth) *
48✔
584
                tileMatrix.mTileHeight * GDALGetDataTypeSizeBytes(eDT);
48✔
585
            std::vector<GByte> dstBuffer(nBytesPerBand *
586
                                         oSrcDS.GetRasterCount());
48✔
587

588
            const double dfXOff = (dfMinX - dfMinXUpper) / upperGT[1];
48✔
589
            const double dfYOff = (dfMaxYUpper - dfMaxY) / -upperGT[5];
48✔
590
            const double dfXSize = (dfMaxX - dfMinX) / upperGT[1];
48✔
591
            const double dfYSize = (dfMaxY - dfMinY) / -upperGT[5];
48✔
592
            GDALRasterIOExtraArg sExtraArg;
593
            INIT_RASTERIO_EXTRA_ARG(sExtraArg);
48✔
594
            CPL_IGNORE_RET_VAL(sExtraArg.eResampleAlg);
48✔
595
            sExtraArg.eResampleAlg =
48✔
596
                GDALRasterIOGetResampleAlg(resampling.c_str());
48✔
597
            sExtraArg.dfXOff = dfXOff;
48✔
598
            sExtraArg.dfYOff = dfYOff;
48✔
599
            sExtraArg.dfXSize = dfXSize;
48✔
600
            sExtraArg.dfYSize = dfYSize;
48✔
601
            sExtraArg.bFloatingPointWindowValidity =
48✔
602
                sExtraArg.eResampleAlg != GRIORA_NearestNeighbour;
48✔
603
            constexpr double EPSILON = 1e-3;
48✔
604
            if (oSrcDS.RasterIO(GF_Read, static_cast<int>(dfXOff + EPSILON),
48✔
605
                                static_cast<int>(dfYOff + EPSILON),
48✔
606
                                static_cast<int>(dfXSize + 0.5),
48✔
607
                                static_cast<int>(dfYSize + 0.5),
48✔
608
                                dstBuffer.data(), tileMatrix.mTileWidth,
48✔
609
                                tileMatrix.mTileHeight, eDT,
48✔
610
                                oSrcDS.GetRasterCount(), nullptr, 0, 0, 0,
611
                                &sExtraArg) == CE_None)
48✔
612
            {
613
                int nDstBands = oSrcDS.GetRasterCount();
47✔
614
                const bool bDstHasAlpha =
615
                    oSrcDS.GetRasterBand(nDstBands)->GetColorInterpretation() ==
47✔
616
                    GCI_AlphaBand;
47✔
617
                if (bDstHasAlpha && bSkipBlank)
47✔
618
                {
619
                    bool bBlank = true;
2✔
620
                    for (size_t i = 0; i < nBytesPerBand && bBlank; ++i)
65,545✔
621
                    {
622
                        bBlank =
65,543✔
623
                            (dstBuffer[(nDstBands - 1) * nBytesPerBand + i] ==
65,543✔
624
                             0);
625
                    }
626
                    if (bBlank)
2✔
627
                        return true;
1✔
628
                    bSkipBlank = false;
1✔
629
                }
630
                if (bDstHasAlpha && !bUserAskedForAlpha)
46✔
631
                {
632
                    bool bAllOpaque = true;
46✔
633
                    for (size_t i = 0; i < nBytesPerBand && bAllOpaque; ++i)
2,790,200✔
634
                    {
635
                        bAllOpaque =
2,790,150✔
636
                            (dstBuffer[(nDstBands - 1) * nBytesPerBand + i] ==
2,792,660✔
637
                             255);
638
                    }
UNCOV
639
                    if (bAllOpaque)
×
640
                        nDstBands--;
43✔
641
                }
642

UNCOV
643
                auto memDS = std::unique_ptr<GDALDataset>(MEMDataset::Create(
×
UNCOV
644
                    "", tileMatrix.mTileWidth, tileMatrix.mTileHeight, 0, eDT,
×
645
                    nullptr));
92✔
646
                for (int i = 0; i < nDstBands; ++i)
187✔
647
                {
648
                    char szBuffer[32] = {'\0'};
141✔
649
                    int nRet = CPLPrintPointer(
282✔
650
                        szBuffer, dstBuffer.data() + i * nBytesPerBand,
141✔
651
                        sizeof(szBuffer));
652
                    szBuffer[nRet] = 0;
141✔
653

654
                    char szOption[64] = {'\0'};
141✔
655
                    snprintf(szOption, sizeof(szOption), "DATAPOINTER=%s",
141✔
656
                             szBuffer);
657

658
                    char *apszOptions[] = {szOption, nullptr};
141✔
659

660
                    memDS->AddBand(eDT, apszOptions);
141✔
661
                    auto poSrcBand = oSrcDS.GetRasterBand(i + 1);
141✔
662
                    auto poDstBand = memDS->GetRasterBand(i + 1);
141✔
663
                    poDstBand->SetColorInterpretation(
141✔
664
                        poSrcBand->GetColorInterpretation());
141✔
665
                    int bHasNoData = false;
141✔
666
                    const double dfNoData =
667
                        poSrcBand->GetNoDataValue(&bHasNoData);
141✔
668
                    if (bHasNoData)
141✔
669
                        poDstBand->SetNoDataValue(dfNoData);
×
670
                    if (auto poCT = poSrcBand->GetColorTable())
141✔
671
                        poDstBand->SetColorTable(poCT);
×
672
                }
673
                memDS->SetMetadata(oSrcDS.GetMetadata());
46✔
674
                memDS->SetGeoTransform(GDALGeoTransform(
92✔
675
                    dfMinX, tileMatrix.mResX, 0, dfMaxY, 0, -tileMatrix.mResY));
46✔
676

677
                memDS->SetSpatialRef(oSrcDS.GetSpatialRef());
46✔
678

679
                poOutDS.reset(poDstDriver->CreateCopy(
46✔
680
                    osTmpFilename.c_str(), memDS.get(), false, creationOptions,
681
                    nullptr, nullptr));
682
            }
47✔
683
        }
684
        else
685
        {
686
            // If the overview tile is not fully within the extent of the
687
            // upper zoom level, use GDALTranslate() to use VRT padding
688

689
            aosOptions.AddString("-q");
16✔
690

691
            aosOptions.AddString("-projwin");
16✔
692
            aosOptions.AddString(CPLSPrintf("%.17g", dfMinX));
16✔
693
            aosOptions.AddString(CPLSPrintf("%.17g", dfMaxY));
16✔
694
            aosOptions.AddString(CPLSPrintf("%.17g", dfMaxX));
16✔
695
            aosOptions.AddString(CPLSPrintf("%.17g", dfMinY));
16✔
696

697
            aosOptions.AddString("-outsize");
16✔
698
            aosOptions.AddString(CPLSPrintf("%d", tileMatrix.mTileWidth));
16✔
699
            aosOptions.AddString(CPLSPrintf("%d", tileMatrix.mTileHeight));
16✔
700

701
            GDALTranslateOptions *psOptions =
702
                GDALTranslateOptionsNew(aosOptions.List(), nullptr);
16✔
703
            poOutDS.reset(GDALDataset::FromHandle(GDALTranslate(
16✔
704
                osTmpFilename.c_str(), GDALDataset::ToHandle(&oSrcDS),
705
                psOptions, nullptr)));
706
            GDALTranslateOptionsFree(psOptions);
16✔
707
        }
708
    }
709
    else
710
    {
711
        aosOptions.AddString("-te");
6✔
712
        aosOptions.AddString(CPLSPrintf("%.17g", dfMinX));
6✔
713
        aosOptions.AddString(CPLSPrintf("%.17g", dfMinY));
6✔
714
        aosOptions.AddString(CPLSPrintf("%.17g", dfMaxX));
6✔
715
        aosOptions.AddString(CPLSPrintf("%.17g", dfMaxY));
6✔
716

717
        aosOptions.AddString("-ts");
6✔
718
        aosOptions.AddString(CPLSPrintf("%d", tileMatrix.mTileWidth));
6✔
719
        aosOptions.AddString(CPLSPrintf("%d", tileMatrix.mTileHeight));
6✔
720

721
        for (int i = 0; papszWarpOptions && papszWarpOptions[i]; ++i)
11✔
722
        {
723
            aosOptions.AddString("-wo");
5✔
724
            aosOptions.AddString(papszWarpOptions[i]);
5✔
725
        }
726

727
        GDALWarpAppOptions *psOptions =
728
            GDALWarpAppOptionsNew(aosOptions.List(), nullptr);
6✔
729
        GDALDatasetH hSrcDS = GDALDataset::ToHandle(&oSrcDS);
6✔
730
        poOutDS.reset(GDALDataset::FromHandle(GDALWarp(
6✔
731
            osTmpFilename.c_str(), nullptr, 1, &hSrcDS, psOptions, nullptr)));
732
        GDALWarpAppOptionsFree(psOptions);
6✔
733
    }
734

735
    bool bRet = poOutDS != nullptr;
69✔
736
    if (bRet && bSkipBlank)
69✔
737
    {
738
        auto poLastBand = poOutDS->GetRasterBand(poOutDS->GetRasterCount());
10✔
739
        if (poLastBand->GetColorInterpretation() == GCI_AlphaBand)
10✔
740
        {
741
            std::vector<GByte> buffer(
742
                static_cast<size_t>(tileMatrix.mTileWidth) *
1✔
743
                tileMatrix.mTileHeight *
1✔
744
                GDALGetDataTypeSizeBytes(poLastBand->GetRasterDataType()));
1✔
745
            CPL_IGNORE_RET_VAL(poLastBand->RasterIO(
2✔
746
                GF_Read, 0, 0, tileMatrix.mTileWidth, tileMatrix.mTileHeight,
1✔
747
                buffer.data(), tileMatrix.mTileWidth, tileMatrix.mTileHeight,
1✔
748
                poLastBand->GetRasterDataType(), 0, 0, nullptr));
749
            bool bBlank = true;
1✔
750
            for (size_t i = 0; i < buffer.size() && bBlank; ++i)
65,537✔
751
            {
752
                bBlank = (buffer[i] == 0);
65,536✔
753
            }
754
            if (bBlank)
1✔
755
            {
756
                poOutDS.reset();
1✔
757
                VSIUnlink(osTmpFilename.c_str());
1✔
758
                if (bAuxXML)
1✔
759
                    VSIUnlink((osTmpFilename + ".aux.xml").c_str());
×
760
                return true;
1✔
761
            }
762
        }
763
    }
764
    bRet = bRet && poOutDS->Close() == CE_None;
68✔
765
    poOutDS.reset();
68✔
766
    if (bRet)
68✔
767
    {
768
        bRet = VSIRename(osTmpFilename.c_str(), osFilename.c_str()) == 0;
67✔
769
        if (bAuxXML)
67✔
770
        {
771
            VSIRename((osTmpFilename + ".aux.xml").c_str(),
4✔
772
                      (osFilename + ".aux.xml").c_str());
8✔
773
        }
774
    }
775
    else
776
    {
777
        VSIUnlink(osTmpFilename.c_str());
1✔
778
    }
779
    return bRet;
68✔
780
}
781

782
namespace
783
{
784

785
/************************************************************************/
786
/*                     FakeMaxZoomRasterBand                            */
787
/************************************************************************/
788

789
class FakeMaxZoomRasterBand : public GDALRasterBand
790
{
791
    void *m_pDstBuffer = nullptr;
792
    CPL_DISALLOW_COPY_ASSIGN(FakeMaxZoomRasterBand)
793

794
  public:
795
    FakeMaxZoomRasterBand(int nBandIn, int nWidth, int nHeight,
597✔
796
                          int nBlockXSizeIn, int nBlockYSizeIn,
797
                          GDALDataType eDT, void *pDstBuffer)
798
        : m_pDstBuffer(pDstBuffer)
597✔
799
    {
800
        nBand = nBandIn;
597✔
801
        nRasterXSize = nWidth;
597✔
802
        nRasterYSize = nHeight;
597✔
803
        nBlockXSize = nBlockXSizeIn;
597✔
804
        nBlockYSize = nBlockYSizeIn;
597✔
805
        eDataType = eDT;
597✔
806
    }
597✔
807

808
    CPLErr IReadBlock(int, int, void *) override
×
809
    {
810
        CPLAssert(false);
×
811
        return CE_Failure;
812
    }
813

814
#ifdef DEBUG
815
    CPLErr IWriteBlock(int, int, void *) override
×
816
    {
817
        CPLAssert(false);
×
818
        return CE_Failure;
819
    }
820
#endif
821

822
    CPLErr IRasterIO(GDALRWFlag eRWFlag, [[maybe_unused]] int nXOff,
490✔
823
                     [[maybe_unused]] int nYOff, [[maybe_unused]] int nXSize,
824
                     [[maybe_unused]] int nYSize, void *pData,
825
                     [[maybe_unused]] int nBufXSize,
826
                     [[maybe_unused]] int nBufYSize, GDALDataType eBufType,
827
                     GSpacing nPixelSpace, [[maybe_unused]] GSpacing nLineSpace,
828
                     GDALRasterIOExtraArg *) override
829
    {
830
        // For sake of implementation simplicity, check various assumptions of
831
        // how GDALAlphaMask code does I/O
832
        CPLAssert((nXOff % nBlockXSize) == 0);
490✔
833
        CPLAssert((nYOff % nBlockYSize) == 0);
490✔
834
        CPLAssert(nXSize == nBufXSize);
490✔
835
        CPLAssert(nXSize == nBlockXSize);
490✔
836
        CPLAssert(nYSize == nBufYSize);
490✔
837
        CPLAssert(nYSize == nBlockYSize);
490✔
838
        CPLAssert(nLineSpace == nBlockXSize * nPixelSpace);
490✔
839
        CPLAssert(
490✔
840
            nBand ==
841
            poDS->GetRasterCount());  // only alpha band is accessed this way
842
        if (eRWFlag == GF_Read)
490✔
843
        {
844
            double dfZero = 0;
245✔
845
            GDALCopyWords64(&dfZero, GDT_Float64, 0, pData, eBufType,
245✔
846
                            static_cast<int>(nPixelSpace),
847
                            static_cast<size_t>(nBlockXSize) * nBlockYSize);
245✔
848
        }
849
        else
850
        {
851
            GDALCopyWords64(pData, eBufType, static_cast<int>(nPixelSpace),
245✔
852
                            m_pDstBuffer, eDataType,
853
                            GDALGetDataTypeSizeBytes(eDataType),
854
                            static_cast<size_t>(nBlockXSize) * nBlockYSize);
245✔
855
        }
856
        return CE_None;
490✔
857
    }
858
};
859

860
/************************************************************************/
861
/*                       FakeMaxZoomDataset                             */
862
/************************************************************************/
863

864
// This class is used to create a fake output dataset for GDALWarpOperation.
865
// In particular we need to implement GDALRasterBand::IRasterIO(GF_Write, ...)
866
// to catch writes (of one single tile) to the alpha band and redirect them
867
// to the dstBuffer passed to FakeMaxZoomDataset constructor.
868

869
class FakeMaxZoomDataset : public GDALDataset
870
{
871
    const int m_nBlockXSize;
872
    const int m_nBlockYSize;
873
    const OGRSpatialReference m_oSRS;
874
    const GDALGeoTransform m_gt{};
875

876
  public:
877
    FakeMaxZoomDataset(int nWidth, int nHeight, int nBandsIn, int nBlockXSize,
171✔
878
                       int nBlockYSize, GDALDataType eDT,
879
                       const GDALGeoTransform &gt,
880
                       const OGRSpatialReference &oSRS,
881
                       std::vector<GByte> &dstBuffer)
882
        : m_nBlockXSize(nBlockXSize), m_nBlockYSize(nBlockYSize), m_oSRS(oSRS),
171✔
883
          m_gt(gt)
171✔
884
    {
885
        eAccess = GA_Update;
171✔
886
        nRasterXSize = nWidth;
171✔
887
        nRasterYSize = nHeight;
171✔
888
        for (int i = 1; i <= nBandsIn; ++i)
768✔
889
        {
890
            SetBand(i,
597✔
891
                    new FakeMaxZoomRasterBand(
892
                        i, nWidth, nHeight, nBlockXSize, nBlockYSize, eDT,
893
                        dstBuffer.data() + static_cast<size_t>(i - 1) *
597✔
894
                                               nBlockXSize * nBlockYSize *
1,194✔
895
                                               GDALGetDataTypeSizeBytes(eDT)));
597✔
896
        }
897
    }
171✔
898

899
    const OGRSpatialReference *GetSpatialRef() const override
296✔
900
    {
901
        return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
296✔
902
    }
903

904
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override
60✔
905
    {
906
        gt = m_gt;
60✔
907
        return CE_None;
60✔
908
    }
909

910
    using GDALDataset::Clone;
911

912
    std::unique_ptr<FakeMaxZoomDataset>
913
    Clone(std::vector<GByte> &dstBuffer) const
111✔
914
    {
915
        return std::make_unique<FakeMaxZoomDataset>(
916
            nRasterXSize, nRasterYSize, nBands, m_nBlockXSize, m_nBlockYSize,
111✔
917
            GetRasterBand(1)->GetRasterDataType(), m_gt, m_oSRS, dstBuffer);
222✔
918
    }
919
};
920

921
/************************************************************************/
922
/*                          MosaicRasterBand                            */
923
/************************************************************************/
924

925
class MosaicRasterBand : public GDALRasterBand
926
{
927
    const int m_tileMinX;
928
    const int m_tileMinY;
929
    const GDALColorInterp m_eColorInterp;
930
    const gdal::TileMatrixSet::TileMatrix m_oTM;
931
    const std::string m_convention;
932
    const std::string m_directory;
933
    const std::string m_extension;
934
    const bool m_hasNoData;
935
    const double m_noData;
936
    std::unique_ptr<GDALColorTable> m_poColorTable{};
937

938
  public:
939
    MosaicRasterBand(GDALDataset *poDSIn, int nBandIn, int nWidth, int nHeight,
158✔
940
                     int nBlockXSizeIn, int nBlockYSizeIn, GDALDataType eDT,
941
                     GDALColorInterp eColorInterp, int nTileMinX, int nTileMinY,
942
                     const gdal::TileMatrixSet::TileMatrix &oTM,
943
                     const std::string &convention,
944
                     const std::string &directory, const std::string &extension,
945
                     const double *pdfDstNoData,
946
                     const GDALColorTable *poColorTable)
947
        : m_tileMinX(nTileMinX), m_tileMinY(nTileMinY),
158✔
948
          m_eColorInterp(eColorInterp), m_oTM(oTM), m_convention(convention),
949
          m_directory(directory), m_extension(extension),
950
          m_hasNoData(pdfDstNoData != nullptr),
158✔
951
          m_noData(pdfDstNoData ? *pdfDstNoData : 0),
158✔
952
          m_poColorTable(poColorTable ? poColorTable->Clone() : nullptr)
316✔
953
    {
954
        poDS = poDSIn;
158✔
955
        nBand = nBandIn;
158✔
956
        nRasterXSize = nWidth;
158✔
957
        nRasterYSize = nHeight;
158✔
958
        nBlockXSize = nBlockXSizeIn;
158✔
959
        nBlockYSize = nBlockYSizeIn;
158✔
960
        eDataType = eDT;
158✔
961
    }
158✔
962

963
    CPLErr IReadBlock(int nXBlock, int nYBlock, void *pData) override;
964

965
    GDALColorTable *GetColorTable() override
2,024✔
966
    {
967
        return m_poColorTable.get();
2,024✔
968
    }
969

970
    GDALColorInterp GetColorInterpretation() override
325✔
971
    {
972
        return m_eColorInterp;
325✔
973
    }
974

975
    double GetNoDataValue(int *pbHasNoData) override
1,635✔
976
    {
977
        if (pbHasNoData)
1,635✔
978
            *pbHasNoData = m_hasNoData;
1,626✔
979
        return m_noData;
1,635✔
980
    }
981
};
982

983
/************************************************************************/
984
/*                         MosaicDataset                                */
985
/************************************************************************/
986

987
// This class is to expose the tiles of a given level as a mosaic that
988
// can be used as a source to generate the immediately below zoom level.
989

990
class MosaicDataset : public GDALDataset
991
{
992
    friend class MosaicRasterBand;
993

994
    const std::string m_directory;
995
    const std::string m_extension;
996
    const std::string m_format;
997
    GDALDataset *const m_poSrcDS;
998
    const gdal::TileMatrixSet::TileMatrix &m_oTM;
999
    const OGRSpatialReference m_oSRS;
1000
    const int m_nTileMinX;
1001
    const int m_nTileMinY;
1002
    const int m_nTileMaxX;
1003
    const int m_nTileMaxY;
1004
    const std::string m_convention;
1005
    const GDALDataType m_eDT;
1006
    const double *const m_pdfDstNoData;
1007
    const std::vector<std::string> &m_metadata;
1008
    const GDALColorTable *const m_poCT;
1009

1010
    GDALGeoTransform m_gt{};
1011
    lru11::Cache<std::string, std::shared_ptr<GDALDataset>> m_oCacheTile{};
1012

1013
    CPL_DISALLOW_COPY_ASSIGN(MosaicDataset)
1014

1015
  public:
1016
    MosaicDataset(const std::string &directory, const std::string &extension,
51✔
1017
                  const std::string &format, GDALDataset *poSrcDS,
1018
                  const gdal::TileMatrixSet::TileMatrix &oTM,
1019
                  const OGRSpatialReference &oSRS, int nTileMinX, int nTileMinY,
1020
                  int nTileMaxX, int nTileMaxY, const std::string &convention,
1021
                  int nBandsIn, GDALDataType eDT, const double *pdfDstNoData,
1022
                  const std::vector<std::string> &metadata,
1023
                  const GDALColorTable *poCT)
1024
        : m_directory(directory), m_extension(extension), m_format(format),
51✔
1025
          m_poSrcDS(poSrcDS), m_oTM(oTM), m_oSRS(oSRS), m_nTileMinX(nTileMinX),
1026
          m_nTileMinY(nTileMinY), m_nTileMaxX(nTileMaxX),
1027
          m_nTileMaxY(nTileMaxY), m_convention(convention), m_eDT(eDT),
1028
          m_pdfDstNoData(pdfDstNoData), m_metadata(metadata), m_poCT(poCT)
51✔
1029
    {
1030
        nRasterXSize = (nTileMaxX - nTileMinX + 1) * oTM.mTileWidth;
51✔
1031
        nRasterYSize = (nTileMaxY - nTileMinY + 1) * oTM.mTileHeight;
51✔
1032
        m_gt[0] = oTM.mTopLeftX + nTileMinX * oTM.mResX * oTM.mTileWidth;
51✔
1033
        m_gt[1] = oTM.mResX;
51✔
1034
        m_gt[2] = 0;
51✔
1035
        m_gt[3] = oTM.mTopLeftY - nTileMinY * oTM.mResY * oTM.mTileHeight;
51✔
1036
        m_gt[4] = 0;
51✔
1037
        m_gt[5] = -oTM.mResY;
51✔
1038
        for (int i = 1; i <= nBandsIn; ++i)
209✔
1039
        {
1040
            const GDALColorInterp eColorInterp =
1041
                (i <= poSrcDS->GetRasterCount())
158✔
1042
                    ? poSrcDS->GetRasterBand(i)->GetColorInterpretation()
158✔
1043
                    : GCI_AlphaBand;
158✔
1044
            SetBand(i, new MosaicRasterBand(
158✔
1045
                           this, i, nRasterXSize, nRasterYSize, oTM.mTileWidth,
158✔
1046
                           oTM.mTileHeight, eDT, eColorInterp, nTileMinX,
158✔
1047
                           nTileMinY, oTM, convention, directory, extension,
1048
                           pdfDstNoData, poCT));
158✔
1049
        }
1050
        SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
51✔
1051
        const CPLStringList aosMD(metadata);
102✔
1052
        for (const auto [key, value] : cpl::IterateNameValue(aosMD))
59✔
1053
        {
1054
            SetMetadataItem(key, value);
8✔
1055
        }
1056
    }
51✔
1057

1058
    const OGRSpatialReference *GetSpatialRef() const override
92✔
1059
    {
1060
        return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
92✔
1061
    }
1062

1063
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override
102✔
1064
    {
1065
        gt = m_gt;
102✔
1066
        return CE_None;
102✔
1067
    }
1068

1069
    using GDALDataset::Clone;
1070

1071
    std::unique_ptr<MosaicDataset> Clone() const
16✔
1072
    {
1073
        return std::make_unique<MosaicDataset>(
1074
            m_directory, m_extension, m_format, m_poSrcDS, m_oTM, m_oSRS,
16✔
1075
            m_nTileMinX, m_nTileMinY, m_nTileMaxX, m_nTileMaxY, m_convention,
16✔
1076
            nBands, m_eDT, m_pdfDstNoData, m_metadata, m_poCT);
16✔
1077
    }
1078
};
1079

1080
/************************************************************************/
1081
/*                   MosaicRasterBand::IReadBlock()                     */
1082
/************************************************************************/
1083

1084
CPLErr MosaicRasterBand::IReadBlock(int nXBlock, int nYBlock, void *pData)
1,600✔
1085
{
1086
    auto poThisDS = cpl::down_cast<MosaicDataset *>(poDS);
1,600✔
1087
    std::string filename = CPLFormFilenameSafe(
1088
        m_directory.c_str(), CPLSPrintf("%d", m_tileMinX + nXBlock), nullptr);
3,200✔
1089
    const int iFileY = GetFileY(m_tileMinY + nYBlock, m_oTM, m_convention);
1,600✔
1090
    filename = CPLFormFilenameSafe(filename.c_str(), CPLSPrintf("%d", iFileY),
3,200✔
1091
                                   m_extension.c_str());
1,600✔
1092

1093
    std::shared_ptr<GDALDataset> poTileDS;
1,600✔
1094
    if (!poThisDS->m_oCacheTile.tryGet(filename, poTileDS))
1,600✔
1095
    {
1096
        const char *const apszAllowedDrivers[] = {poThisDS->m_format.c_str(),
415✔
1097
                                                  nullptr};
415✔
1098
        const char *const apszAllowedDriversForCOG[] = {"GTiff", "LIBERTIFF",
415✔
1099
                                                        nullptr};
1100
        poTileDS.reset(GDALDataset::Open(
415✔
1101
            filename.c_str(), GDAL_OF_RASTER | GDAL_OF_INTERNAL,
1102
            EQUAL(poThisDS->m_format.c_str(), "COG") ? apszAllowedDriversForCOG
415✔
1103
                                                     : apszAllowedDrivers));
1104
        if (!poTileDS)
415✔
1105
        {
1106
            VSIStatBufL sStat;
1107
            if (VSIStatL(filename.c_str(), &sStat) == 0)
6✔
1108
            {
1109
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
1110
                         "File %s exists but cannot be opened with %s driver",
1111
                         filename.c_str(), poThisDS->m_format.c_str());
1112
                return CE_Failure;
1✔
1113
            }
1114
        }
1115
        poThisDS->m_oCacheTile.insert(filename, poTileDS);
414✔
1116
    }
1117
    if (!poTileDS || nBand > poTileDS->GetRasterCount())
1,599✔
1118
    {
1119
        memset(pData,
751✔
1120
               (poTileDS && (nBand == poTileDS->GetRasterCount() + 1)) ? 255
373✔
1121
                                                                       : 0,
1122
               static_cast<size_t>(nBlockXSize) * nBlockYSize *
378✔
1123
                   GDALGetDataTypeSizeBytes(eDataType));
378✔
1124
        return CE_None;
378✔
1125
    }
1126
    else
1127
    {
1128
        return poTileDS->GetRasterBand(nBand)->RasterIO(
1,221✔
1129
            GF_Read, 0, 0, nBlockXSize, nBlockYSize, pData, nBlockXSize,
1130
            nBlockYSize, eDataType, 0, 0, nullptr);
1,221✔
1131
    }
1132
}
1133

1134
}  // namespace
1135

1136
/************************************************************************/
1137
/*                         ApplySubstitutions()                         */
1138
/************************************************************************/
1139

1140
static void ApplySubstitutions(CPLString &s,
59✔
1141
                               const std::map<std::string, std::string> &substs)
1142
{
1143
    for (const auto &[key, value] : substs)
932✔
1144
    {
1145
        s.replaceAll("%(" + key + ")s", value);
873✔
1146
        s.replaceAll("%(" + key + ")d", value);
873✔
1147
        s.replaceAll("%(" + key + ")f", value);
873✔
1148
        s.replaceAll("${" + key + "}", value);
873✔
1149
    }
1150
}
59✔
1151

1152
/************************************************************************/
1153
/*                           GenerateLeaflet()                          */
1154
/************************************************************************/
1155

1156
static void GenerateLeaflet(const std::string &osDirectory,
13✔
1157
                            const std::string &osTitle, double dfSouthLat,
1158
                            double dfWestLon, double dfNorthLat,
1159
                            double dfEastLon, int nMinZoom, int nMaxZoom,
1160
                            int nTileSize, const std::string &osExtension,
1161
                            const std::string &osURL,
1162
                            const std::string &osCopyright, bool bXYZ)
1163
{
1164
    if (const char *pszTemplate = CPLFindFile("gdal", "leaflet_template.html"))
13✔
1165
    {
1166
        const std::string osFilename(pszTemplate);
26✔
1167
        std::map<std::string, std::string> substs;
26✔
1168

1169
        // For tests
1170
        const char *pszFmt =
1171
            atoi(CPLGetConfigOption("GDAL_RASTER_TILE_HTML_PREC", "17")) == 10
13✔
1172
                ? "%.10g"
1173
                : "%.17g";
13✔
1174

1175
        substs["double_quote_escaped_title"] =
26✔
1176
            CPLString(osTitle).replaceAll('"', "\\\"");
39✔
1177
        char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
13✔
1178
        substs["xml_escaped_title"] = pszStr;
13✔
1179
        CPLFree(pszStr);
13✔
1180
        substs["south"] = CPLSPrintf(pszFmt, dfSouthLat);
13✔
1181
        substs["west"] = CPLSPrintf(pszFmt, dfWestLon);
13✔
1182
        substs["north"] = CPLSPrintf(pszFmt, dfNorthLat);
13✔
1183
        substs["east"] = CPLSPrintf(pszFmt, dfEastLon);
13✔
1184
        substs["centerlon"] = CPLSPrintf(pszFmt, (dfNorthLat + dfSouthLat) / 2);
13✔
1185
        substs["centerlat"] = CPLSPrintf(pszFmt, (dfWestLon + dfEastLon) / 2);
13✔
1186
        substs["minzoom"] = CPLSPrintf("%d", nMinZoom);
13✔
1187
        substs["maxzoom"] = CPLSPrintf("%d", nMaxZoom);
13✔
1188
        substs["beginzoom"] = CPLSPrintf("%d", nMaxZoom);
13✔
1189
        substs["tile_size"] = CPLSPrintf("%d", nTileSize);  // not used
13✔
1190
        substs["tileformat"] = osExtension;
13✔
1191
        substs["publishurl"] = osURL;  // not used
13✔
1192
        substs["copyright"] = CPLString(osCopyright).replaceAll('"', "\\\"");
13✔
1193
        substs["tms"] = bXYZ ? "0" : "1";
13✔
1194

1195
        GByte *pabyRet = nullptr;
13✔
1196
        CPL_IGNORE_RET_VAL(VSIIngestFile(nullptr, osFilename.c_str(), &pabyRet,
13✔
1197
                                         nullptr, 10 * 1024 * 1024));
1198
        if (pabyRet)
13✔
1199
        {
1200
            CPLString osHTML(reinterpret_cast<char *>(pabyRet));
26✔
1201
            CPLFree(pabyRet);
13✔
1202

1203
            ApplySubstitutions(osHTML, substs);
13✔
1204

1205
            VSILFILE *f = VSIFOpenL(CPLFormFilenameSafe(osDirectory.c_str(),
13✔
1206
                                                        "leaflet.html", nullptr)
1207
                                        .c_str(),
1208
                                    "wb");
1209
            if (f)
13✔
1210
            {
1211
                VSIFWriteL(osHTML.data(), 1, osHTML.size(), f);
13✔
1212
                VSIFCloseL(f);
13✔
1213
            }
1214
        }
1215
    }
1216
}
13✔
1217

1218
/************************************************************************/
1219
/*                           GenerateMapML()                            */
1220
/************************************************************************/
1221

1222
static void
1223
GenerateMapML(const std::string &osDirectory, const std::string &mapmlTemplate,
14✔
1224
              const std::string &osTitle, int nMinTileX, int nMinTileY,
1225
              int nMaxTileX, int nMaxTileY, int nMinZoom, int nMaxZoom,
1226
              const std::string &osExtension, const std::string &osURL,
1227
              const std::string &osCopyright, const gdal::TileMatrixSet &tms)
1228
{
1229
    if (const char *pszTemplate =
14✔
1230
            (mapmlTemplate.empty() ? CPLFindFile("gdal", "template_tiles.mapml")
14✔
1231
                                   : mapmlTemplate.c_str()))
14✔
1232
    {
1233
        const std::string osFilename(pszTemplate);
28✔
1234
        std::map<std::string, std::string> substs;
28✔
1235

1236
        if (tms.identifier() == "GoogleMapsCompatible")
14✔
1237
            substs["TILING_SCHEME"] = "OSMTILE";
13✔
1238
        else if (tms.identifier() == "WorldCRS84Quad")
1✔
1239
            substs["TILING_SCHEME"] = "WGS84";
1✔
1240
        else
1241
            substs["TILING_SCHEME"] = tms.identifier();
×
1242

1243
        substs["URL"] = osURL.empty() ? "./" : osURL;
14✔
1244
        substs["MINTILEX"] = CPLSPrintf("%d", nMinTileX);
14✔
1245
        substs["MINTILEY"] = CPLSPrintf("%d", nMinTileY);
14✔
1246
        substs["MAXTILEX"] = CPLSPrintf("%d", nMaxTileX);
14✔
1247
        substs["MAXTILEY"] = CPLSPrintf("%d", nMaxTileY);
14✔
1248
        substs["CURZOOM"] = CPLSPrintf("%d", nMaxZoom);
14✔
1249
        substs["MINZOOM"] = CPLSPrintf("%d", nMinZoom);
14✔
1250
        substs["MAXZOOM"] = CPLSPrintf("%d", nMaxZoom);
14✔
1251
        substs["TILEEXT"] = osExtension;
14✔
1252
        char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
14✔
1253
        substs["TITLE"] = pszStr;
14✔
1254
        CPLFree(pszStr);
14✔
1255
        substs["COPYRIGHT"] = osCopyright;
14✔
1256

1257
        GByte *pabyRet = nullptr;
14✔
1258
        CPL_IGNORE_RET_VAL(VSIIngestFile(nullptr, osFilename.c_str(), &pabyRet,
14✔
1259
                                         nullptr, 10 * 1024 * 1024));
1260
        if (pabyRet)
14✔
1261
        {
1262
            CPLString osMAPML(reinterpret_cast<char *>(pabyRet));
28✔
1263
            CPLFree(pabyRet);
14✔
1264

1265
            ApplySubstitutions(osMAPML, substs);
14✔
1266

1267
            VSILFILE *f = VSIFOpenL(
14✔
1268
                CPLFormFilenameSafe(osDirectory.c_str(), "mapml.mapml", nullptr)
28✔
1269
                    .c_str(),
1270
                "wb");
1271
            if (f)
14✔
1272
            {
1273
                VSIFWriteL(osMAPML.data(), 1, osMAPML.size(), f);
14✔
1274
                VSIFCloseL(f);
14✔
1275
            }
1276
        }
1277
    }
1278
}
14✔
1279

1280
/************************************************************************/
1281
/*                           GenerateOpenLayers()                       */
1282
/************************************************************************/
1283

1284
static void GenerateOpenLayers(
21✔
1285
    const std::string &osDirectory, const std::string &osTitle, double dfMinX,
1286
    double dfMinY, double dfMaxX, double dfMaxY, int nMinZoom, int nMaxZoom,
1287
    int nTileSize, const std::string &osExtension, const std::string &osURL,
1288
    const std::string &osCopyright, const gdal::TileMatrixSet &tms,
1289
    bool bInvertAxisTMS, const OGRSpatialReference &oSRS_TMS, bool bXYZ)
1290
{
1291
    std::map<std::string, std::string> substs;
42✔
1292

1293
    // For tests
1294
    const char *pszFmt =
1295
        atoi(CPLGetConfigOption("GDAL_RASTER_TILE_HTML_PREC", "17")) == 10
21✔
1296
            ? "%.10g"
1297
            : "%.17g";
21✔
1298

1299
    char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
21✔
1300
    substs["xml_escaped_title"] = pszStr;
21✔
1301
    CPLFree(pszStr);
21✔
1302
    substs["ominx"] = CPLSPrintf(pszFmt, dfMinX);
21✔
1303
    substs["ominy"] = CPLSPrintf(pszFmt, dfMinY);
21✔
1304
    substs["omaxx"] = CPLSPrintf(pszFmt, dfMaxX);
21✔
1305
    substs["omaxy"] = CPLSPrintf(pszFmt, dfMaxY);
21✔
1306
    substs["center_x"] = CPLSPrintf(pszFmt, (dfMinX + dfMaxX) / 2);
21✔
1307
    substs["center_y"] = CPLSPrintf(pszFmt, (dfMinY + dfMaxY) / 2);
21✔
1308
    substs["minzoom"] = CPLSPrintf("%d", nMinZoom);
21✔
1309
    substs["maxzoom"] = CPLSPrintf("%d", nMaxZoom);
21✔
1310
    substs["tile_size"] = CPLSPrintf("%d", nTileSize);
21✔
1311
    substs["tileformat"] = osExtension;
21✔
1312
    substs["publishurl"] = osURL;
21✔
1313
    substs["copyright"] = osCopyright;
21✔
1314
    substs["sign_y"] = bXYZ ? "" : "-";
21✔
1315

1316
    CPLString s(R"raw(<!DOCTYPE html>
1317
<html>
1318
<head>
1319
    <title>%(xml_escaped_title)s</title>
1320
    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
1321
    <meta http-equiv='imagetoolbar' content='no'/>
1322
    <style type="text/css"> v\:* {behavior:url(#default#VML);}
1323
        html, body { overflow: hidden; padding: 0; height: 100%; width: 100%; font-family: 'Lucida Grande',Geneva,Arial,Verdana,sans-serif; }
1324
        body { margin: 10px; background: #fff; }
1325
        h1 { margin: 0; padding: 6px; border:0; font-size: 20pt; }
1326
        #header { height: 43px; padding: 0; background-color: #eee; border: 1px solid #888; }
1327
        #subheader { height: 12px; text-align: right; font-size: 10px; color: #555;}
1328
        #map { height: 90%; border: 1px solid #888; }
1329
    </style>
1330
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@main/dist/en/v7.0.0/legacy/ol.css" type="text/css">
1331
    <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@main/dist/en/v7.0.0/legacy/ol.js"></script>
1332
    <script src="https://unpkg.com/ol-layerswitcher@4.1.1"></script>
1333
    <link rel="stylesheet" href="https://unpkg.com/ol-layerswitcher@4.1.1/src/ol-layerswitcher.css" />
1334
</head>
1335
<body>
1336
    <div id="header"><h1>%(xml_escaped_title)s</h1></div>
1337
    <div id="subheader">Generated by <a href="https://gdal.org/programs/gdal_raster_tile.html">gdal raster tile</a>&nbsp;&nbsp;&nbsp;&nbsp;</div>
1338
    <div id="map" class="map"></div>
1339
    <div id="mouse-position"></div>
1340
    <script type="text/javascript">
1341
        var mousePositionControl = new ol.control.MousePosition({
1342
            className: 'custom-mouse-position',
1343
            target: document.getElementById('mouse-position'),
1344
            undefinedHTML: '&nbsp;'
1345
        });
1346
        var map = new ol.Map({
1347
            controls: ol.control.defaults.defaults().extend([mousePositionControl]),
1348
            target: 'map',)raw");
42✔
1349

1350
    if (tms.identifier() == "GoogleMapsCompatible" ||
29✔
1351
        tms.identifier() == "WorldCRS84Quad")
8✔
1352
    {
1353
        s += R"raw(
15✔
1354
            layers: [
1355
                new ol.layer.Group({
1356
                        title: 'Base maps',
1357
                        layers: [
1358
                            new ol.layer.Tile({
1359
                                title: 'OpenStreetMap',
1360
                                type: 'base',
1361
                                visible: true,
1362
                                source: new ol.source.OSM()
1363
                            }),
1364
                        ]
1365
                }),)raw";
1366
    }
1367

1368
    if (tms.identifier() == "GoogleMapsCompatible")
21✔
1369
    {
1370
        s += R"raw(new ol.layer.Group({
13✔
1371
                    title: 'Overlay',
1372
                    layers: [
1373
                        new ol.layer.Tile({
1374
                            title: 'Overlay',
1375
                            // opacity: 0.7,
1376
                            extent: [%(ominx)f, %(ominy)f,%(omaxx)f, %(omaxy)f],
1377
                            source: new ol.source.XYZ({
1378
                                attributions: '%(copyright)s',
1379
                                minZoom: %(minzoom)d,
1380
                                maxZoom: %(maxzoom)d,
1381
                                url: './{z}/{x}/{%(sign_y)sy}.%(tileformat)s',
1382
                                tileSize: [%(tile_size)d, %(tile_size)d]
1383
                            })
1384
                        }),
1385
                    ]
1386
                }),)raw";
1387
    }
1388
    else if (tms.identifier() == "WorldCRS84Quad")
8✔
1389
    {
1390
        const double base_res = 180.0 / nTileSize;
2✔
1391
        std::string resolutions = "[";
4✔
1392
        for (int i = 0; i <= nMaxZoom; ++i)
4✔
1393
        {
1394
            if (i > 0)
2✔
1395
                resolutions += ",";
×
1396
            resolutions += CPLSPrintf(pszFmt, base_res / (1 << i));
2✔
1397
        }
1398
        resolutions += "]";
2✔
1399
        substs["resolutions"] = std::move(resolutions);
2✔
1400

1401
        if (bXYZ)
2✔
1402
        {
1403
            substs["origin"] = "[-180,90]";
1✔
1404
            substs["y_formula"] = "tileCoord[2]";
1✔
1405
        }
1406
        else
1407
        {
1408
            substs["origin"] = "[-180,-90]";
1✔
1409
            substs["y_formula"] = "- 1 - tileCoord[2]";
1✔
1410
        }
1411

1412
        s += R"raw(
2✔
1413
                new ol.layer.Group({
1414
                    title: 'Overlay',
1415
                    layers: [
1416
                        new ol.layer.Tile({
1417
                            title: 'Overlay',
1418
                            // opacity: 0.7,
1419
                            extent: [%(ominx)f, %(ominy)f,%(omaxx)f, %(omaxy)f],
1420
                            source: new ol.source.TileImage({
1421
                                attributions: '%(copyright)s',
1422
                                projection: 'EPSG:4326',
1423
                                minZoom: %(minzoom)d,
1424
                                maxZoom: %(maxzoom)d,
1425
                                tileGrid: new ol.tilegrid.TileGrid({
1426
                                    extent: [-180,-90,180,90],
1427
                                    origin: %(origin)s,
1428
                                    resolutions: %(resolutions)s,
1429
                                    tileSize: [%(tile_size)d, %(tile_size)d]
1430
                                }),
1431
                                tileUrlFunction: function(tileCoord) {
1432
                                    return ('./{z}/{x}/{y}.%(tileformat)s'
1433
                                        .replace('{z}', String(tileCoord[0]))
1434
                                        .replace('{x}', String(tileCoord[1]))
1435
                                        .replace('{y}', String(%(y_formula)s)));
1436
                                },
1437
                            })
1438
                        }),
1439
                    ]
1440
                }),)raw";
1441
    }
1442
    else
1443
    {
1444
        substs["maxres"] =
12✔
1445
            CPLSPrintf(pszFmt, tms.tileMatrixList()[nMinZoom].mResX);
12✔
1446
        std::string resolutions = "[";
12✔
1447
        for (int i = 0; i <= nMaxZoom; ++i)
16✔
1448
        {
1449
            if (i > 0)
10✔
1450
                resolutions += ",";
4✔
1451
            resolutions += CPLSPrintf(pszFmt, tms.tileMatrixList()[i].mResX);
10✔
1452
        }
1453
        resolutions += "]";
6✔
1454
        substs["resolutions"] = std::move(resolutions);
6✔
1455

1456
        std::string matrixsizes = "[";
12✔
1457
        for (int i = 0; i <= nMaxZoom; ++i)
16✔
1458
        {
1459
            if (i > 0)
10✔
1460
                matrixsizes += ",";
4✔
1461
            matrixsizes +=
1462
                CPLSPrintf("[%d,%d]", tms.tileMatrixList()[i].mMatrixWidth,
10✔
1463
                           tms.tileMatrixList()[i].mMatrixHeight);
20✔
1464
        }
1465
        matrixsizes += "]";
6✔
1466
        substs["matrixsizes"] = std::move(matrixsizes);
6✔
1467

1468
        double dfTopLeftX = tms.tileMatrixList()[0].mTopLeftX;
6✔
1469
        double dfTopLeftY = tms.tileMatrixList()[0].mTopLeftY;
6✔
1470
        if (bInvertAxisTMS)
6✔
1471
            std::swap(dfTopLeftX, dfTopLeftY);
×
1472

1473
        if (bXYZ)
6✔
1474
        {
1475
            substs["origin"] =
10✔
1476
                CPLSPrintf("[%.17g,%.17g]", dfTopLeftX, dfTopLeftY);
10✔
1477
            substs["y_formula"] = "tileCoord[2]";
5✔
1478
        }
1479
        else
1480
        {
1481
            substs["origin"] = CPLSPrintf(
2✔
1482
                "[%.17g,%.17g]", dfTopLeftX,
1483
                dfTopLeftY - tms.tileMatrixList()[0].mResY *
1✔
1484
                                 tms.tileMatrixList()[0].mTileHeight);
3✔
1485
            substs["y_formula"] = "- 1 - tileCoord[2]";
1✔
1486
        }
1487

1488
        substs["tilegrid_extent"] =
12✔
1489
            CPLSPrintf("[%.17g,%.17g,%.17g,%.17g]", dfTopLeftX,
1490
                       dfTopLeftY - tms.tileMatrixList()[0].mMatrixHeight *
6✔
1491
                                        tms.tileMatrixList()[0].mResY *
6✔
1492
                                        tms.tileMatrixList()[0].mTileHeight,
6✔
1493
                       dfTopLeftX + tms.tileMatrixList()[0].mMatrixWidth *
6✔
1494
                                        tms.tileMatrixList()[0].mResX *
6✔
1495
                                        tms.tileMatrixList()[0].mTileWidth,
6✔
1496
                       dfTopLeftY);
24✔
1497

1498
        s += R"raw(
6✔
1499
            layers: [
1500
                new ol.layer.Group({
1501
                    title: 'Overlay',
1502
                    layers: [
1503
                        new ol.layer.Tile({
1504
                            title: 'Overlay',
1505
                            // opacity: 0.7,
1506
                            extent: [%(ominx)f, %(ominy)f,%(omaxx)f, %(omaxy)f],
1507
                            source: new ol.source.TileImage({
1508
                                attributions: '%(copyright)s',
1509
                                minZoom: %(minzoom)d,
1510
                                maxZoom: %(maxzoom)d,
1511
                                tileGrid: new ol.tilegrid.TileGrid({
1512
                                    extent: %(tilegrid_extent)s,
1513
                                    origin: %(origin)s,
1514
                                    resolutions: %(resolutions)s,
1515
                                    sizes: %(matrixsizes)s,
1516
                                    tileSize: [%(tile_size)d, %(tile_size)d]
1517
                                }),
1518
                                tileUrlFunction: function(tileCoord) {
1519
                                    return ('./{z}/{x}/{y}.%(tileformat)s'
1520
                                        .replace('{z}', String(tileCoord[0]))
1521
                                        .replace('{x}', String(tileCoord[1]))
1522
                                        .replace('{y}', String(%(y_formula)s)));
1523
                                },
1524
                            })
1525
                        }),
1526
                    ]
1527
                }),)raw";
1528
    }
1529

1530
    s += R"raw(
21✔
1531
            ],
1532
            view: new ol.View({
1533
                center: [%(center_x)f, %(center_y)f],)raw";
1534

1535
    if (tms.identifier() == "GoogleMapsCompatible" ||
29✔
1536
        tms.identifier() == "WorldCRS84Quad")
8✔
1537
    {
1538
        substs["view_zoom"] = substs["minzoom"];
15✔
1539
        if (tms.identifier() == "WorldCRS84Quad")
15✔
1540
        {
1541
            substs["view_zoom"] = CPLSPrintf("%d", nMinZoom + 1);
2✔
1542
        }
1543

1544
        s += R"raw(
15✔
1545
                zoom: %(view_zoom)d,)raw";
1546
    }
1547
    else
1548
    {
1549
        s += R"raw(
6✔
1550
                resolution: %(maxres)f,)raw";
1551
    }
1552

1553
    if (tms.identifier() == "WorldCRS84Quad")
21✔
1554
    {
1555
        s += R"raw(
2✔
1556
                projection: 'EPSG:4326',)raw";
1557
    }
1558
    else if (!oSRS_TMS.IsEmpty() && tms.identifier() != "GoogleMapsCompatible")
19✔
1559
    {
1560
        const char *pszAuthName = oSRS_TMS.GetAuthorityName(nullptr);
5✔
1561
        const char *pszAuthCode = oSRS_TMS.GetAuthorityCode(nullptr);
5✔
1562
        if (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "EPSG"))
5✔
1563
        {
1564
            substs["epsg_code"] = pszAuthCode;
3✔
1565
            if (oSRS_TMS.IsGeographic())
3✔
1566
            {
1567
                substs["units"] = "deg";
1✔
1568
            }
1569
            else
1570
            {
1571
                const char *pszUnits = "";
2✔
1572
                if (oSRS_TMS.GetLinearUnits(&pszUnits) == 1.0)
2✔
1573
                    substs["units"] = "m";
2✔
1574
                else
1575
                    substs["units"] = pszUnits;
×
1576
            }
1577
            s += R"raw(
3✔
1578
                projection: new ol.proj.Projection({code: 'EPSG:%(epsg_code)s', units:'%(units)s'}),)raw";
1579
        }
1580
    }
1581

1582
    s += R"raw(
21✔
1583
            })
1584
        });)raw";
1585

1586
    if (tms.identifier() == "GoogleMapsCompatible" ||
29✔
1587
        tms.identifier() == "WorldCRS84Quad")
8✔
1588
    {
1589
        s += R"raw(
15✔
1590
        map.addControl(new ol.control.LayerSwitcher());)raw";
1591
    }
1592

1593
    s += R"raw(
21✔
1594
    </script>
1595
</body>
1596
</html>)raw";
1597

1598
    ApplySubstitutions(s, substs);
21✔
1599

1600
    VSILFILE *f = VSIFOpenL(
21✔
1601
        CPLFormFilenameSafe(osDirectory.c_str(), "openlayers.html", nullptr)
42✔
1602
            .c_str(),
1603
        "wb");
1604
    if (f)
21✔
1605
    {
1606
        VSIFWriteL(s.data(), 1, s.size(), f);
21✔
1607
        VSIFCloseL(f);
21✔
1608
    }
1609
}
21✔
1610

1611
/************************************************************************/
1612
/*                           GetTileBoundingBox()                       */
1613
/************************************************************************/
1614

1615
static void GetTileBoundingBox(int nTileX, int nTileY, int nTileZ,
6✔
1616
                               const gdal::TileMatrixSet *poTMS,
1617
                               bool bInvertAxisTMS,
1618
                               OGRCoordinateTransformation *poCTToWGS84,
1619
                               double &dfTLX, double &dfTLY, double &dfTRX,
1620
                               double &dfTRY, double &dfLLX, double &dfLLY,
1621
                               double &dfLRX, double &dfLRY)
1622
{
1623
    gdal::TileMatrixSet::TileMatrix tileMatrix =
1624
        poTMS->tileMatrixList()[nTileZ];
12✔
1625
    if (bInvertAxisTMS)
6✔
1626
        std::swap(tileMatrix.mTopLeftX, tileMatrix.mTopLeftY);
×
1627

1628
    dfTLX = tileMatrix.mTopLeftX +
6✔
1629
            nTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
6✔
1630
    dfTLY = tileMatrix.mTopLeftY -
6✔
1631
            nTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
6✔
1632
    poCTToWGS84->Transform(1, &dfTLX, &dfTLY);
6✔
1633

1634
    dfTRX = tileMatrix.mTopLeftX +
6✔
1635
            (nTileX + 1) * tileMatrix.mResX * tileMatrix.mTileWidth;
6✔
1636
    dfTRY = tileMatrix.mTopLeftY -
6✔
1637
            nTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
6✔
1638
    poCTToWGS84->Transform(1, &dfTRX, &dfTRY);
6✔
1639

1640
    dfLLX = tileMatrix.mTopLeftX +
6✔
1641
            nTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
6✔
1642
    dfLLY = tileMatrix.mTopLeftY -
6✔
1643
            (nTileY + 1) * tileMatrix.mResY * tileMatrix.mTileHeight;
6✔
1644
    poCTToWGS84->Transform(1, &dfLLX, &dfLLY);
6✔
1645

1646
    dfLRX = tileMatrix.mTopLeftX +
6✔
1647
            (nTileX + 1) * tileMatrix.mResX * tileMatrix.mTileWidth;
6✔
1648
    dfLRY = tileMatrix.mTopLeftY -
6✔
1649
            (nTileY + 1) * tileMatrix.mResY * tileMatrix.mTileHeight;
6✔
1650
    poCTToWGS84->Transform(1, &dfLRX, &dfLRY);
6✔
1651
}
6✔
1652

1653
/************************************************************************/
1654
/*                           GenerateKML()                              */
1655
/************************************************************************/
1656

1657
namespace
1658
{
1659
struct TileCoordinates
1660
{
1661
    int nTileX = 0;
1662
    int nTileY = 0;
1663
    int nTileZ = 0;
1664
};
1665
}  // namespace
1666

1667
static void GenerateKML(const std::string &osDirectory,
5✔
1668
                        const std::string &osTitle, int nTileX, int nTileY,
1669
                        int nTileZ, int nTileSize,
1670
                        const std::string &osExtension,
1671
                        const std::string &osURL,
1672
                        const gdal::TileMatrixSet *poTMS, bool bInvertAxisTMS,
1673
                        const std::string &convention,
1674
                        OGRCoordinateTransformation *poCTToWGS84,
1675
                        const std::vector<TileCoordinates> &children)
1676
{
1677
    std::map<std::string, std::string> substs;
10✔
1678

1679
    const bool bIsTileKML = nTileX >= 0;
5✔
1680

1681
    // For tests
1682
    const char *pszFmt =
1683
        atoi(CPLGetConfigOption("GDAL_RASTER_TILE_KML_PREC", "14")) == 10
5✔
1684
            ? "%.10f"
1685
            : "%.14f";
5✔
1686

1687
    substs["tx"] = CPLSPrintf("%d", nTileX);
5✔
1688
    substs["tz"] = CPLSPrintf("%d", nTileZ);
5✔
1689
    substs["tileformat"] = osExtension;
5✔
1690
    substs["minlodpixels"] = CPLSPrintf("%d", nTileSize / 2);
5✔
1691
    substs["maxlodpixels"] =
10✔
1692
        children.empty() ? "-1" : CPLSPrintf("%d", nTileSize * 8);
10✔
1693

1694
    double dfTLX = 0;
5✔
1695
    double dfTLY = 0;
5✔
1696
    double dfTRX = 0;
5✔
1697
    double dfTRY = 0;
5✔
1698
    double dfLLX = 0;
5✔
1699
    double dfLLY = 0;
5✔
1700
    double dfLRX = 0;
5✔
1701
    double dfLRY = 0;
5✔
1702

1703
    int nFileY = -1;
5✔
1704
    if (!bIsTileKML)
5✔
1705
    {
1706
        char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
2✔
1707
        substs["xml_escaped_title"] = pszStr;
2✔
1708
        CPLFree(pszStr);
2✔
1709
    }
1710
    else
1711
    {
1712
        nFileY = GetFileY(nTileY, poTMS->tileMatrixList()[nTileZ], convention);
3✔
1713
        substs["realtiley"] = CPLSPrintf("%d", nFileY);
3✔
1714
        substs["xml_escaped_title"] =
6✔
1715
            CPLSPrintf("%d/%d/%d.kml", nTileZ, nTileX, nFileY);
6✔
1716

1717
        GetTileBoundingBox(nTileX, nTileY, nTileZ, poTMS, bInvertAxisTMS,
3✔
1718
                           poCTToWGS84, dfTLX, dfTLY, dfTRX, dfTRY, dfLLX,
1719
                           dfLLY, dfLRX, dfLRY);
1720
    }
1721

1722
    substs["drawOrder"] = CPLSPrintf("%d", nTileX == 0  ? 2 * nTileZ + 1
10✔
1723
                                           : nTileX > 0 ? 2 * nTileZ
4✔
1724
                                                        : 0);
14✔
1725

1726
    substs["url"] = osURL.empty() && bIsTileKML ? "../../" : "";
5✔
1727

1728
    const bool bIsRectangle =
5✔
1729
        (dfTLX == dfLLX && dfTRX == dfLRX && dfTLY == dfTRY && dfLLY == dfLRY);
5✔
1730
    const bool bUseGXNamespace = bIsTileKML && !bIsRectangle;
5✔
1731

1732
    substs["xmlns_gx"] = bUseGXNamespace
10✔
1733
                             ? " xmlns:gx=\"http://www.google.com/kml/ext/2.2\""
1734
                             : "";
10✔
1735

1736
    CPLString s(R"raw(<?xml version="1.0" encoding="utf-8"?>
1737
<kml xmlns="http://www.opengis.net/kml/2.2"%(xmlns_gx)s>
1738
  <Document>
1739
    <name>%(xml_escaped_title)s</name>
1740
    <description></description>
1741
    <Style>
1742
      <ListStyle id="hideChildren">
1743
        <listItemType>checkHideChildren</listItemType>
1744
      </ListStyle>
1745
    </Style>
1746
)raw");
10✔
1747
    ApplySubstitutions(s, substs);
5✔
1748

1749
    if (bIsTileKML)
5✔
1750
    {
1751
        CPLString s2(R"raw(    <Region>
1752
      <LatLonAltBox>
1753
        <north>%(north)f</north>
1754
        <south>%(south)f</south>
1755
        <east>%(east)f</east>
1756
        <west>%(west)f</west>
1757
      </LatLonAltBox>
1758
      <Lod>
1759
        <minLodPixels>%(minlodpixels)d</minLodPixels>
1760
        <maxLodPixels>%(maxlodpixels)d</maxLodPixels>
1761
      </Lod>
1762
    </Region>
1763
    <GroundOverlay>
1764
      <drawOrder>%(drawOrder)d</drawOrder>
1765
      <Icon>
1766
        <href>%(realtiley)d.%(tileformat)s</href>
1767
      </Icon>
1768
      <LatLonBox>
1769
        <north>%(north)f</north>
1770
        <south>%(south)f</south>
1771
        <east>%(east)f</east>
1772
        <west>%(west)f</west>
1773
      </LatLonBox>
1774
)raw");
6✔
1775

1776
        if (!bIsRectangle)
3✔
1777
        {
1778
            s2 +=
1779
                R"raw(      <gx:LatLonQuad><coordinates>%(LLX)f,%(LLY)f %(LRX)f,%(LRY)f %(TRX)f,%(TRY)f %(TLX)f,%(TLY)f</coordinates></gx:LatLonQuad>
1✔
1780
)raw";
1781
        }
1782

1783
        s2 += R"raw(    </GroundOverlay>
3✔
1784
)raw";
1785
        substs["north"] = CPLSPrintf(pszFmt, std::max(dfTLY, dfTRY));
3✔
1786
        substs["south"] = CPLSPrintf(pszFmt, std::min(dfLLY, dfLRY));
3✔
1787
        substs["east"] = CPLSPrintf(pszFmt, std::max(dfTRX, dfLRX));
3✔
1788
        substs["west"] = CPLSPrintf(pszFmt, std::min(dfLLX, dfTLX));
3✔
1789

1790
        if (!bIsRectangle)
3✔
1791
        {
1792
            substs["TLX"] = CPLSPrintf(pszFmt, dfTLX);
1✔
1793
            substs["TLY"] = CPLSPrintf(pszFmt, dfTLY);
1✔
1794
            substs["TRX"] = CPLSPrintf(pszFmt, dfTRX);
1✔
1795
            substs["TRY"] = CPLSPrintf(pszFmt, dfTRY);
1✔
1796
            substs["LRX"] = CPLSPrintf(pszFmt, dfLRX);
1✔
1797
            substs["LRY"] = CPLSPrintf(pszFmt, dfLRY);
1✔
1798
            substs["LLX"] = CPLSPrintf(pszFmt, dfLLX);
1✔
1799
            substs["LLY"] = CPLSPrintf(pszFmt, dfLLY);
1✔
1800
        }
1801

1802
        ApplySubstitutions(s2, substs);
3✔
1803
        s += s2;
3✔
1804
    }
1805

1806
    for (const auto &child : children)
8✔
1807
    {
1808
        substs["tx"] = CPLSPrintf("%d", child.nTileX);
3✔
1809
        substs["tz"] = CPLSPrintf("%d", child.nTileZ);
3✔
1810
        substs["realtiley"] = CPLSPrintf(
6✔
1811
            "%d", GetFileY(child.nTileY, poTMS->tileMatrixList()[child.nTileZ],
3✔
1812
                           convention));
6✔
1813

1814
        GetTileBoundingBox(child.nTileX, child.nTileY, child.nTileZ, poTMS,
3✔
1815
                           bInvertAxisTMS, poCTToWGS84, dfTLX, dfTLY, dfTRX,
1816
                           dfTRY, dfLLX, dfLLY, dfLRX, dfLRY);
1817

1818
        CPLString s2(R"raw(    <NetworkLink>
1819
      <name>%(tz)d/%(tx)d/%(realtiley)d.%(tileformat)s</name>
1820
      <Region>
1821
        <LatLonAltBox>
1822
          <north>%(north)f</north>
1823
          <south>%(south)f</south>
1824
          <east>%(east)f</east>
1825
          <west>%(west)f</west>
1826
        </LatLonAltBox>
1827
        <Lod>
1828
          <minLodPixels>%(minlodpixels)d</minLodPixels>
1829
          <maxLodPixels>-1</maxLodPixels>
1830
        </Lod>
1831
      </Region>
1832
      <Link>
1833
        <href>%(url)s%(tz)d/%(tx)d/%(realtiley)d.kml</href>
1834
        <viewRefreshMode>onRegion</viewRefreshMode>
1835
        <viewFormat/>
1836
      </Link>
1837
    </NetworkLink>
1838
)raw");
6✔
1839
        substs["north"] = CPLSPrintf(pszFmt, std::max(dfTLY, dfTRY));
3✔
1840
        substs["south"] = CPLSPrintf(pszFmt, std::min(dfLLY, dfLRY));
3✔
1841
        substs["east"] = CPLSPrintf(pszFmt, std::max(dfTRX, dfLRX));
3✔
1842
        substs["west"] = CPLSPrintf(pszFmt, std::min(dfLLX, dfTLX));
3✔
1843
        ApplySubstitutions(s2, substs);
3✔
1844
        s += s2;
3✔
1845
    }
1846

1847
    s += R"raw(</Document>
5✔
1848
</kml>)raw";
1849

1850
    std::string osFilename(osDirectory);
10✔
1851
    if (!bIsTileKML)
5✔
1852
    {
1853
        osFilename =
1854
            CPLFormFilenameSafe(osFilename.c_str(), "doc.kml", nullptr);
2✔
1855
    }
1856
    else
1857
    {
1858
        osFilename = CPLFormFilenameSafe(osFilename.c_str(),
6✔
1859
                                         CPLSPrintf("%d", nTileZ), nullptr);
3✔
1860
        osFilename = CPLFormFilenameSafe(osFilename.c_str(),
6✔
1861
                                         CPLSPrintf("%d", nTileX), nullptr);
3✔
1862
        osFilename = CPLFormFilenameSafe(osFilename.c_str(),
6✔
1863
                                         CPLSPrintf("%d.kml", nFileY), nullptr);
3✔
1864
    }
1865

1866
    VSILFILE *f = VSIFOpenL(osFilename.c_str(), "wb");
5✔
1867
    if (f)
5✔
1868
    {
1869
        VSIFWriteL(s.data(), 1, s.size(), f);
5✔
1870
        VSIFCloseL(f);
5✔
1871
    }
1872
}
5✔
1873

1874
namespace
1875
{
1876

1877
/************************************************************************/
1878
/*                            ResourceManager                           */
1879
/************************************************************************/
1880

1881
// Generic cache managing resources
1882
template <class Resource> class ResourceManager /* non final */
1883
{
1884
  public:
1885
    virtual ~ResourceManager() = default;
94✔
1886

1887
    std::unique_ptr<Resource> AcquireResources()
272✔
1888
    {
1889
        std::lock_guard oLock(m_oMutex);
544✔
1890
        if (!m_oResources.empty())
272✔
1891
        {
1892
            auto ret = std::move(m_oResources.back());
290✔
1893
            m_oResources.pop_back();
145✔
1894
            return ret;
145✔
1895
        }
1896

1897
        return CreateResources();
127✔
1898
    }
1899

1900
    void ReleaseResources(std::unique_ptr<Resource> resources)
256✔
1901
    {
1902
        std::lock_guard oLock(m_oMutex);
512✔
1903
        m_oResources.push_back(std::move(resources));
256✔
1904
    }
256✔
1905

1906
    void SetError()
16✔
1907
    {
1908
        std::lock_guard oLock(m_oMutex);
32✔
1909
        if (m_errorMsg.empty())
16✔
1910
            m_errorMsg = CPLGetLastErrorMsg();
1✔
1911
    }
16✔
1912

1913
    const std::string &GetErrorMsg() const
33✔
1914
    {
1915
        std::lock_guard oLock(m_oMutex);
33✔
1916
        return m_errorMsg;
66✔
1917
    }
1918

1919
  protected:
1920
    virtual std::unique_ptr<Resource> CreateResources() = 0;
1921

1922
  private:
1923
    mutable std::mutex m_oMutex{};
1924
    std::vector<std::unique_ptr<Resource>> m_oResources{};
1925
    std::string m_errorMsg{};
1926
};
1927

1928
/************************************************************************/
1929
/*                         PerThreadMaxZoomResources                    */
1930
/************************************************************************/
1931

1932
// Per-thread resources for generation of tiles at full resolution
1933
struct PerThreadMaxZoomResources
1934
{
1935
    struct GDALDatasetReleaser
1936
    {
1937
        void operator()(GDALDataset *poDS)
111✔
1938
        {
1939
            if (poDS)
111✔
1940
                poDS->ReleaseRef();
111✔
1941
        }
111✔
1942
    };
1943

1944
    std::unique_ptr<GDALDataset, GDALDatasetReleaser> poSrcDS{};
1945
    std::vector<GByte> dstBuffer{};
1946
    std::unique_ptr<FakeMaxZoomDataset> poFakeMaxZoomDS{};
1947
    std::unique_ptr<void, decltype(&GDALDestroyTransformer)> poTransformer{
1948
        nullptr, GDALDestroyTransformer};
1949
    std::unique_ptr<GDALWarpOperation> poWO{};
1950
};
1951

1952
/************************************************************************/
1953
/*                      PerThreadMaxZoomResourceManager                 */
1954
/************************************************************************/
1955

1956
// Manage a cache of PerThreadMaxZoomResources instances
1957
class PerThreadMaxZoomResourceManager final
1958
    : public ResourceManager<PerThreadMaxZoomResources>
1959
{
1960
  public:
1961
    PerThreadMaxZoomResourceManager(GDALDataset *poSrcDS,
59✔
1962
                                    const GDALWarpOptions *psWO,
1963
                                    void *pTransformerArg,
1964
                                    const FakeMaxZoomDataset &oFakeMaxZoomDS,
1965
                                    size_t nBufferSize)
1966
        : m_poSrcDS(poSrcDS), m_psWOSource(psWO),
59✔
1967
          m_pTransformerArg(pTransformerArg), m_oFakeMaxZoomDS(oFakeMaxZoomDS),
1968
          m_nBufferSize(nBufferSize)
59✔
1969
    {
1970
    }
59✔
1971

1972
  protected:
1973
    std::unique_ptr<PerThreadMaxZoomResources> CreateResources() override
111✔
1974
    {
1975
        auto ret = std::make_unique<PerThreadMaxZoomResources>();
222✔
1976

1977
        ret->poSrcDS.reset(GDALGetThreadSafeDataset(m_poSrcDS, GDAL_OF_RASTER));
111✔
1978
        if (!ret->poSrcDS)
111✔
1979
            return nullptr;
×
1980

1981
        try
1982
        {
1983
            ret->dstBuffer.resize(m_nBufferSize);
111✔
1984
        }
1985
        catch (const std::exception &)
×
1986
        {
1987
            CPLError(CE_Failure, CPLE_OutOfMemory,
×
1988
                     "Out of memory allocating temporary buffer");
1989
            return nullptr;
×
1990
        }
1991

1992
        ret->poFakeMaxZoomDS = m_oFakeMaxZoomDS.Clone(ret->dstBuffer);
111✔
1993

1994
        ret->poTransformer.reset(GDALCloneTransformer(m_pTransformerArg));
111✔
1995
        if (!ret->poTransformer)
111✔
1996
            return nullptr;
×
1997

1998
        auto psWO =
1999
            std::unique_ptr<GDALWarpOptions, decltype(&GDALDestroyWarpOptions)>(
2000
                GDALCloneWarpOptions(m_psWOSource), GDALDestroyWarpOptions);
222✔
2001
        if (!psWO)
111✔
2002
            return nullptr;
×
2003

2004
        psWO->hSrcDS = GDALDataset::ToHandle(ret->poSrcDS.get());
111✔
2005
        psWO->hDstDS = GDALDataset::ToHandle(ret->poFakeMaxZoomDS.get());
111✔
2006
        psWO->pTransformerArg = ret->poTransformer.get();
111✔
2007
        psWO->pfnTransformer = m_psWOSource->pfnTransformer;
111✔
2008

2009
        ret->poWO = std::make_unique<GDALWarpOperation>();
111✔
2010
        if (ret->poWO->Initialize(psWO.get()) != CE_None)
111✔
2011
            return nullptr;
×
2012

2013
        return ret;
111✔
2014
    }
2015

2016
  private:
2017
    GDALDataset *const m_poSrcDS;
2018
    const GDALWarpOptions *const m_psWOSource;
2019
    void *const m_pTransformerArg;
2020
    const FakeMaxZoomDataset &m_oFakeMaxZoomDS;
2021
    const size_t m_nBufferSize;
2022

2023
    CPL_DISALLOW_COPY_ASSIGN(PerThreadMaxZoomResourceManager)
2024
};
2025

2026
/************************************************************************/
2027
/*                       PerThreadLowerZoomResources                    */
2028
/************************************************************************/
2029

2030
// Per-thread resources for generation of tiles at zoom level < max
2031
struct PerThreadLowerZoomResources
2032
{
2033
    std::unique_ptr<GDALDataset> poSrcDS{};
2034
};
2035

2036
/************************************************************************/
2037
/*                   PerThreadLowerZoomResourceManager                  */
2038
/************************************************************************/
2039

2040
// Manage a cache of PerThreadLowerZoomResources instances
2041
class PerThreadLowerZoomResourceManager final
2042
    : public ResourceManager<PerThreadLowerZoomResources>
2043
{
2044
  public:
2045
    explicit PerThreadLowerZoomResourceManager(const MosaicDataset &oSrcDS)
35✔
2046
        : m_oSrcDS(oSrcDS)
35✔
2047
    {
2048
    }
35✔
2049

2050
  protected:
2051
    std::unique_ptr<PerThreadLowerZoomResources> CreateResources() override
16✔
2052
    {
2053
        auto ret = std::make_unique<PerThreadLowerZoomResources>();
16✔
2054
        ret->poSrcDS = m_oSrcDS.Clone();
16✔
2055
        return ret;
16✔
2056
    }
2057

2058
  private:
2059
    const MosaicDataset &m_oSrcDS;
2060
};
2061

2062
}  // namespace
2063

2064
/************************************************************************/
2065
/*                  GDALRasterTileAlgorithm::RunImpl()                  */
2066
/************************************************************************/
2067

2068
bool GDALRasterTileAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
87✔
2069
                                      void *pProgressData)
2070
{
2071
    auto poSrcDS = m_dataset.GetDatasetRef();
87✔
2072
    CPLAssert(poSrcDS);
87✔
2073
    const int nSrcWidth = poSrcDS->GetRasterXSize();
87✔
2074
    const int nSrcHeight = poSrcDS->GetRasterYSize();
87✔
2075
    if (poSrcDS->GetRasterCount() == 0 || nSrcWidth == 0 || nSrcHeight == 0)
87✔
2076
    {
2077
        ReportError(CE_Failure, CPLE_AppDefined, "Invalid source dataset");
1✔
2078
        return false;
1✔
2079
    }
2080

2081
    if (m_resampling == "near")
86✔
2082
        m_resampling = "nearest";
1✔
2083
    if (m_overviewResampling == "near")
86✔
2084
        m_overviewResampling = "nearest";
1✔
2085
    else if (m_overviewResampling.empty())
85✔
2086
        m_overviewResampling = m_resampling;
80✔
2087

2088
    CPLStringList aosWarpOptions;
172✔
2089
    if (!m_excludedValues.empty() || m_nodataValuesPctThreshold < 100)
86✔
2090
    {
2091
        aosWarpOptions.SetNameValue(
2092
            "NODATA_VALUES_PCT_THRESHOLD",
2093
            CPLSPrintf("%g", m_nodataValuesPctThreshold));
3✔
2094
        if (!m_excludedValues.empty())
3✔
2095
        {
2096
            aosWarpOptions.SetNameValue("EXCLUDED_VALUES",
2097
                                        m_excludedValues.c_str());
1✔
2098
            aosWarpOptions.SetNameValue(
2099
                "EXCLUDED_VALUES_PCT_THRESHOLD",
2100
                CPLSPrintf("%g", m_excludedValuesPctThreshold));
1✔
2101
        }
2102
    }
2103

2104
    if (poSrcDS->GetRasterBand(1)->GetColorInterpretation() ==
86✔
2105
            GCI_PaletteIndex &&
90✔
2106
        ((m_resampling != "nearest" && m_resampling != "mode") ||
4✔
2107
         (m_overviewResampling != "nearest" && m_overviewResampling != "mode")))
1✔
2108
    {
2109
        ReportError(CE_Failure, CPLE_NotSupported,
1✔
2110
                    "Datasets with color table not supported with non-nearest "
2111
                    "or non-mode resampling. Run 'gdal raster "
2112
                    "color-map' before or set the 'resampling' argument to "
2113
                    "'nearest' or 'mode'.");
2114
        return false;
1✔
2115
    }
2116

2117
    const auto eSrcDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
85✔
2118
    auto poDstDriver =
2119
        GetGDALDriverManager()->GetDriverByName(m_outputFormat.c_str());
85✔
2120
    if (!poDstDriver)
85✔
2121
    {
2122
        ReportError(CE_Failure, CPLE_AppDefined,
1✔
2123
                    "Invalid value for argument 'output-format'. Driver '%s' "
2124
                    "does not exist",
2125
                    m_outputFormat.c_str());
2126
        return false;
1✔
2127
    }
2128

2129
    if (m_outputFormat == "PNG")
84✔
2130
    {
2131
        if (poSrcDS->GetRasterCount() > 4)
60✔
2132
        {
2133
            ReportError(CE_Failure, CPLE_NotSupported,
1✔
2134
                        "Only up to 4 bands supported for PNG.");
2135
            return false;
1✔
2136
        }
2137
        if (eSrcDT != GDT_Byte && eSrcDT != GDT_UInt16)
59✔
2138
        {
2139
            ReportError(CE_Failure, CPLE_NotSupported,
1✔
2140
                        "Only Byte and UInt16 data types supported for PNG.");
2141
            return false;
1✔
2142
        }
2143
    }
2144
    else if (m_outputFormat == "JPEG")
24✔
2145
    {
2146
        if (poSrcDS->GetRasterCount() > 4)
5✔
2147
        {
2148
            ReportError(
1✔
2149
                CE_Failure, CPLE_NotSupported,
2150
                "Only up to 4 bands supported for JPEG (with alpha ignored).");
2151
            return false;
1✔
2152
        }
2153
        const bool bUInt16Supported = strstr(
8✔
2154
            poDstDriver->GetMetadataItem(GDAL_DMD_CREATIONDATATYPES), "UInt16");
4✔
2155
        if (eSrcDT != GDT_Byte && !(eSrcDT == GDT_UInt16 && bUInt16Supported))
4✔
2156
        {
2157
            ReportError(
1✔
2158
                CE_Failure, CPLE_NotSupported,
2159
                bUInt16Supported
2160
                    ? "Only Byte and UInt16 data types supported for JPEG."
2161
                    : "Only Byte data type supported for JPEG.");
2162
            return false;
1✔
2163
        }
2164
        if (eSrcDT == GDT_UInt16)
3✔
2165
        {
2166
            if (const char *pszNBITS =
3✔
2167
                    poSrcDS->GetRasterBand(1)->GetMetadataItem(
6✔
2168
                        "NBITS", "IMAGE_STRUCTURE"))
3✔
2169
            {
2170
                if (atoi(pszNBITS) > 12)
1✔
2171
                {
2172
                    ReportError(CE_Failure, CPLE_NotSupported,
1✔
2173
                                "JPEG output only supported up to 12 bits");
2174
                    return false;
1✔
2175
                }
2176
            }
2177
            else
2178
            {
2179
                double adfMinMax[2] = {0, 0};
2✔
2180
                poSrcDS->GetRasterBand(1)->ComputeRasterMinMax(
2✔
2181
                    /* bApproxOK = */ true, adfMinMax);
2✔
2182
                if (adfMinMax[1] >= (1 << 12))
2✔
2183
                {
2184
                    ReportError(CE_Failure, CPLE_NotSupported,
1✔
2185
                                "JPEG output only supported up to 12 bits");
2186
                    return false;
1✔
2187
                }
2188
            }
2189
        }
2190
    }
2191
    else if (m_outputFormat == "WEBP")
19✔
2192
    {
2193
        if (poSrcDS->GetRasterCount() != 3 && poSrcDS->GetRasterCount() != 4)
2✔
2194
        {
2195
            ReportError(CE_Failure, CPLE_NotSupported,
1✔
2196
                        "Only 3 or 4 bands supported for WEBP.");
2197
            return false;
1✔
2198
        }
2199
        if (eSrcDT != GDT_Byte)
1✔
2200
        {
2201
            ReportError(CE_Failure, CPLE_NotSupported,
1✔
2202
                        "Only Byte data type supported for WEBP.");
2203
            return false;
1✔
2204
        }
2205
    }
2206

2207
    const char *pszExtensions =
2208
        poDstDriver->GetMetadataItem(GDAL_DMD_EXTENSIONS);
76✔
2209
    CPLAssert(pszExtensions && pszExtensions[0] != 0);
76✔
2210
    const CPLStringList aosExtensions(
2211
        CSLTokenizeString2(pszExtensions, " ", 0));
152✔
2212
    const char *pszExtension = aosExtensions[0];
76✔
2213
    GDALGeoTransform srcGT;
76✔
2214
    const bool bHasSrcGT = poSrcDS->GetGeoTransform(srcGT) == CE_None;
76✔
2215
    const bool bHasNorthUpSrcGT =
2216
        bHasSrcGT && srcGT[2] == 0 && srcGT[4] == 0 && srcGT[5] < 0;
76✔
2217
    OGRSpatialReference oSRS_TMS;
152✔
2218

2219
    if (m_tilingScheme == "raster")
76✔
2220
    {
2221
        if (const auto poSRS = poSrcDS->GetSpatialRef())
4✔
2222
            oSRS_TMS = *poSRS;
3✔
2223
    }
2224
    else
2225
    {
2226
        if (!bHasSrcGT && poSrcDS->GetGCPCount() == 0 &&
1✔
2227
            poSrcDS->GetMetadata("GEOLOCATION") == nullptr &&
74✔
2228
            poSrcDS->GetMetadata("RPC") == nullptr)
1✔
2229
        {
2230
            ReportError(CE_Failure, CPLE_NotSupported,
1✔
2231
                        "Ungeoreferenced datasets are not supported, unless "
2232
                        "'tiling-scheme' is set to 'raster'");
2233
            return false;
1✔
2234
        }
2235

2236
        if (poSrcDS->GetMetadata("GEOLOCATION") == nullptr &&
71✔
2237
            poSrcDS->GetMetadata("RPC") == nullptr &&
71✔
2238
            poSrcDS->GetSpatialRef() == nullptr &&
143✔
2239
            poSrcDS->GetGCPSpatialRef() == nullptr)
1✔
2240
        {
2241
            ReportError(CE_Failure, CPLE_NotSupported,
1✔
2242
                        "Ungeoreferenced datasets are not supported, unless "
2243
                        "'tiling-scheme' is set to 'raster'");
2244
            return false;
1✔
2245
        }
2246
    }
2247

2248
    if (m_copySrcMetadata)
74✔
2249
    {
2250
        CPLStringList aosMD(CSLDuplicate(poSrcDS->GetMetadata()));
8✔
2251
        const CPLStringList aosNewMD(m_metadata);
4✔
2252
        for (const auto [key, value] : cpl::IterateNameValue(aosNewMD))
8✔
2253
        {
2254
            aosMD.SetNameValue(key, value);
4✔
2255
        }
2256
        m_metadata = aosMD;
4✔
2257
    }
2258

2259
    GDALGeoTransform srcGTModif{0, 1, 0, 0, 0, -1};
74✔
2260

2261
    if (m_tilingScheme == "mercator")
74✔
2262
        m_tilingScheme = "WebMercatorQuad";
1✔
2263
    else if (m_tilingScheme == "geodetic")
73✔
2264
        m_tilingScheme = "WorldCRS84Quad";
1✔
2265
    else if (m_tilingScheme == "raster")
72✔
2266
    {
2267
        if (m_tileSize == 0)
4✔
2268
            m_tileSize = 256;
4✔
2269
        if (m_maxZoomLevel < 0)
4✔
2270
        {
2271
            m_maxZoomLevel = static_cast<int>(std::ceil(std::log2(
3✔
2272
                std::max(1, std::max(nSrcWidth, nSrcHeight) / m_tileSize))));
6✔
2273
        }
2274
        if (bHasNorthUpSrcGT)
4✔
2275
        {
2276
            srcGTModif = srcGT;
3✔
2277
        }
2278
    }
2279

2280
    auto poTMS =
2281
        m_tilingScheme == "raster"
74✔
2282
            ? gdal::TileMatrixSet::createRaster(
2283
                  nSrcWidth, nSrcHeight, m_tileSize, 1 + m_maxZoomLevel,
4✔
2284
                  srcGTModif[0], srcGTModif[3], srcGTModif[1], -srcGTModif[5],
16✔
2285
                  oSRS_TMS.IsEmpty() ? std::string() : oSRS_TMS.exportToWkt())
78✔
2286
            : gdal::TileMatrixSet::parse(
2287
                  m_mapTileMatrixIdentifierToScheme[m_tilingScheme].c_str());
152✔
2288
    // Enforced by SetChoices() on the m_tilingScheme argument
2289
    CPLAssert(poTMS && !poTMS->hasVariableMatrixWidth());
74✔
2290

2291
    CPLStringList aosTO;
148✔
2292
    if (m_tilingScheme == "raster")
74✔
2293
    {
2294
        aosTO.SetNameValue("SRC_METHOD", "GEOTRANSFORM");
4✔
2295
    }
2296
    else
2297
    {
2298
        CPL_IGNORE_RET_VAL(oSRS_TMS.SetFromUserInput(poTMS->crs().c_str()));
70✔
2299
        aosTO.SetNameValue("DST_SRS", oSRS_TMS.exportToWkt().c_str());
70✔
2300
    }
2301

2302
    const char *pszAuthName = oSRS_TMS.GetAuthorityName(nullptr);
74✔
2303
    const char *pszAuthCode = oSRS_TMS.GetAuthorityCode(nullptr);
74✔
2304
    const int nEPSGCode =
74✔
2305
        (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "EPSG"))
73✔
2306
            ? atoi(pszAuthCode)
147✔
2307
            : 0;
2308

2309
    const bool bInvertAxisTMS =
2310
        m_tilingScheme != "raster" &&
144✔
2311
        (oSRS_TMS.EPSGTreatsAsLatLong() != FALSE ||
70✔
2312
         oSRS_TMS.EPSGTreatsAsNorthingEasting() != FALSE);
70✔
2313

2314
    oSRS_TMS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
74✔
2315

2316
    std::unique_ptr<void, decltype(&GDALDestroyTransformer)> hTransformArg(
2317
        nullptr, GDALDestroyTransformer);
148✔
2318

2319
    // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
2320
    // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
2321
    // EPSG:3857.
2322
    std::unique_ptr<GDALDataset> poTmpDS;
74✔
2323
    bool bEPSG3857Adjust = false;
74✔
2324
    if (nEPSGCode == 3857 && bHasNorthUpSrcGT)
74✔
2325
    {
2326
        const auto poSrcSRS = poSrcDS->GetSpatialRef();
48✔
2327
        if (poSrcSRS && poSrcSRS->IsGeographic())
48✔
2328
        {
2329
            double maxLat = srcGT[3];
17✔
2330
            double minLat = srcGT[3] + nSrcHeight * srcGT[5];
17✔
2331
            // Corresponds to the latitude of below MAX_GM
2332
            constexpr double MAX_LAT = 85.0511287798066;
17✔
2333
            bool bModified = false;
17✔
2334
            if (maxLat > MAX_LAT)
17✔
2335
            {
2336
                maxLat = MAX_LAT;
16✔
2337
                bModified = true;
16✔
2338
            }
2339
            if (minLat < -MAX_LAT)
17✔
2340
            {
2341
                minLat = -MAX_LAT;
16✔
2342
                bModified = true;
16✔
2343
            }
2344
            if (bModified)
17✔
2345
            {
2346
                CPLStringList aosOptions;
32✔
2347
                aosOptions.AddString("-of");
16✔
2348
                aosOptions.AddString("VRT");
16✔
2349
                aosOptions.AddString("-projwin");
16✔
2350
                aosOptions.AddString(CPLSPrintf("%.17g", srcGT[0]));
16✔
2351
                aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
16✔
2352
                aosOptions.AddString(
2353
                    CPLSPrintf("%.17g", srcGT[0] + nSrcWidth * srcGT[1]));
16✔
2354
                aosOptions.AddString(CPLSPrintf("%.17g", minLat));
16✔
2355
                auto psOptions =
2356
                    GDALTranslateOptionsNew(aosOptions.List(), nullptr);
16✔
2357
                poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
16✔
2358
                    "", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
2359
                GDALTranslateOptionsFree(psOptions);
16✔
2360
                if (poTmpDS)
16✔
2361
                {
2362
                    bEPSG3857Adjust = true;
16✔
2363
                    hTransformArg.reset(GDALCreateGenImgProjTransformer2(
16✔
2364
                        GDALDataset::FromHandle(poTmpDS.get()), nullptr,
16✔
2365
                        aosTO.List()));
16✔
2366
                }
2367
            }
2368
        }
2369
    }
2370

2371
    GDALGeoTransform dstGT;
74✔
2372
    double adfExtent[4];
2373
    int nXSize, nYSize;
2374

2375
    bool bSuggestOK;
2376
    if (m_tilingScheme == "raster")
74✔
2377
    {
2378
        bSuggestOK = true;
4✔
2379
        nXSize = nSrcWidth;
4✔
2380
        nYSize = nSrcHeight;
4✔
2381
        dstGT = srcGTModif;
4✔
2382
        adfExtent[0] = dstGT[0];
4✔
2383
        adfExtent[1] = dstGT[3] + nSrcHeight * dstGT[5];
4✔
2384
        adfExtent[2] = dstGT[0] + nSrcWidth * dstGT[1];
4✔
2385
        adfExtent[3] = dstGT[3];
4✔
2386
    }
2387
    else
2388
    {
2389
        if (!hTransformArg)
70✔
2390
        {
2391
            hTransformArg.reset(GDALCreateGenImgProjTransformer2(
54✔
2392
                poSrcDS, nullptr, aosTO.List()));
54✔
2393
        }
2394
        if (!hTransformArg)
70✔
2395
        {
2396
            return false;
1✔
2397
        }
2398
        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
69✔
2399
        bSuggestOK =
69✔
2400
            (GDALSuggestedWarpOutput2(
138✔
2401
                 poSrcDS,
2402
                 static_cast<GDALTransformerInfo *>(hTransformArg.get())
69✔
2403
                     ->pfnTransform,
2404
                 hTransformArg.get(), dstGT.data(), &nXSize, &nYSize, adfExtent,
2405
                 0) == CE_None);
2406
    }
2407
    if (!bSuggestOK)
73✔
2408
    {
2409
        ReportError(CE_Failure, CPLE_AppDefined,
1✔
2410
                    "Cannot determine extent of raster in target CRS");
2411
        return false;
1✔
2412
    }
2413

2414
    poTmpDS.reset();
72✔
2415

2416
    if (bEPSG3857Adjust)
72✔
2417
    {
2418
        constexpr double SPHERICAL_RADIUS = 6378137.0;
16✔
2419
        constexpr double MAX_GM =
16✔
2420
            SPHERICAL_RADIUS * M_PI;  // 20037508.342789244
2421
        double maxNorthing = dstGT[3];
16✔
2422
        double minNorthing = dstGT[3] + dstGT[5] * nYSize;
16✔
2423
        bool bChanged = false;
16✔
2424
        if (maxNorthing > MAX_GM)
16✔
2425
        {
2426
            bChanged = true;
13✔
2427
            maxNorthing = MAX_GM;
13✔
2428
        }
2429
        if (minNorthing < -MAX_GM)
16✔
2430
        {
2431
            bChanged = true;
13✔
2432
            minNorthing = -MAX_GM;
13✔
2433
        }
2434
        if (bChanged)
16✔
2435
        {
2436
            dstGT[3] = maxNorthing;
13✔
2437
            nYSize = int((maxNorthing - minNorthing) / (-dstGT[5]) + 0.5);
13✔
2438
            adfExtent[1] = maxNorthing + nYSize * dstGT[5];
13✔
2439
            adfExtent[3] = maxNorthing;
13✔
2440
        }
2441
    }
2442

2443
    const auto &tileMatrixList = poTMS->tileMatrixList();
72✔
2444
    if (m_maxZoomLevel >= 0)
72✔
2445
    {
2446
        if (m_maxZoomLevel >= static_cast<int>(tileMatrixList.size()))
23✔
2447
        {
2448
            ReportError(CE_Failure, CPLE_AppDefined,
1✔
2449
                        "max-zoom = %d is invalid. It must be in [0,%d] range",
2450
                        m_maxZoomLevel,
2451
                        static_cast<int>(tileMatrixList.size()) - 1);
1✔
2452
            return false;
1✔
2453
        }
2454
    }
2455
    else
2456
    {
2457
        const double dfComputedRes = dstGT[1];
49✔
2458
        double dfPrevRes = 0.0;
49✔
2459
        double dfRes = 0.0;
49✔
2460
        constexpr double EPSILON = 1e-8;
49✔
2461

2462
        if (m_minZoomLevel >= 0)
49✔
2463
            m_maxZoomLevel = m_minZoomLevel;
17✔
2464
        else
2465
            m_maxZoomLevel = 0;
32✔
2466

2467
        for (; m_maxZoomLevel < static_cast<int>(tileMatrixList.size());
164✔
2468
             m_maxZoomLevel++)
115✔
2469
        {
2470
            dfRes = tileMatrixList[m_maxZoomLevel].mResX;
163✔
2471
            if (dfComputedRes > dfRes ||
163✔
2472
                fabs(dfComputedRes - dfRes) / dfRes <= EPSILON)
115✔
2473
                break;
2474
            dfPrevRes = dfRes;
115✔
2475
        }
2476
        if (m_maxZoomLevel >= static_cast<int>(tileMatrixList.size()))
49✔
2477
        {
2478
            ReportError(CE_Failure, CPLE_AppDefined,
1✔
2479
                        "Could not find an appropriate zoom level. Perhaps "
2480
                        "min-zoom is too large?");
2481
            return false;
1✔
2482
        }
2483

2484
        if (m_maxZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > EPSILON)
48✔
2485
        {
2486
            // Round to closest resolution
2487
            if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
43✔
2488
                m_maxZoomLevel--;
23✔
2489
        }
2490
    }
2491
    if (m_minZoomLevel < 0)
70✔
2492
        m_minZoomLevel = m_maxZoomLevel;
40✔
2493

2494
    auto tileMatrix = tileMatrixList[m_maxZoomLevel];
140✔
2495
    int nMinTileX = 0;
70✔
2496
    int nMinTileY = 0;
70✔
2497
    int nMaxTileX = 0;
70✔
2498
    int nMaxTileY = 0;
70✔
2499
    bool bIntersects = false;
70✔
2500
    if (!GetTileIndices(tileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
70✔
2501
                        nMinTileX, nMinTileY, nMaxTileX, nMaxTileY,
2502
                        m_noIntersectionIsOK, bIntersects,
70✔
2503
                        /* checkRasterOverflow = */ false))
2504
    {
2505
        return false;
1✔
2506
    }
2507
    if (!bIntersects)
69✔
2508
        return true;
1✔
2509

2510
    // Potentially restrict tiling to user specified coordinates
2511
    if (m_minTileX >= tileMatrix.mMatrixWidth)
68✔
2512
    {
2513
        ReportError(CE_Failure, CPLE_IllegalArg,
1✔
2514
                    "'min-x' value must be in [0,%d] range",
2515
                    tileMatrix.mMatrixWidth - 1);
1✔
2516
        return false;
1✔
2517
    }
2518
    if (m_maxTileX >= tileMatrix.mMatrixWidth)
67✔
2519
    {
2520
        ReportError(CE_Failure, CPLE_IllegalArg,
1✔
2521
                    "'max-x' value must be in [0,%d] range",
2522
                    tileMatrix.mMatrixWidth - 1);
1✔
2523
        return false;
1✔
2524
    }
2525
    if (m_minTileY >= tileMatrix.mMatrixHeight)
66✔
2526
    {
2527
        ReportError(CE_Failure, CPLE_IllegalArg,
1✔
2528
                    "'min-y' value must be in [0,%d] range",
2529
                    tileMatrix.mMatrixHeight - 1);
1✔
2530
        return false;
1✔
2531
    }
2532
    if (m_maxTileY >= tileMatrix.mMatrixHeight)
65✔
2533
    {
2534
        ReportError(CE_Failure, CPLE_IllegalArg,
1✔
2535
                    "'max-y' value must be in [0,%d] range",
2536
                    tileMatrix.mMatrixHeight - 1);
1✔
2537
        return false;
1✔
2538
    }
2539

2540
    if ((m_minTileX >= 0 && m_minTileX > nMaxTileX) ||
64✔
2541
        (m_minTileY >= 0 && m_minTileY > nMaxTileY) ||
62✔
2542
        (m_maxTileX >= 0 && m_maxTileX < nMinTileX) ||
62✔
2543
        (m_maxTileY >= 0 && m_maxTileY < nMinTileY))
62✔
2544
    {
2545
        ReportError(
2✔
2546
            m_noIntersectionIsOK ? CE_Warning : CE_Failure, CPLE_AppDefined,
2✔
2547
            "Dataset extent not intersecting specified min/max X/Y tile "
2548
            "coordinates");
2549
        return m_noIntersectionIsOK;
2✔
2550
    }
2551
    if (m_minTileX >= 0 && m_minTileX > nMinTileX)
62✔
2552
    {
2553
        nMinTileX = m_minTileX;
2✔
2554
        adfExtent[0] = tileMatrix.mTopLeftX +
2✔
2555
                       nMinTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
2✔
2556
    }
2557
    if (m_minTileY >= 0 && m_minTileY > nMinTileY)
62✔
2558
    {
2559
        nMinTileY = m_minTileY;
1✔
2560
        adfExtent[3] = tileMatrix.mTopLeftY -
1✔
2561
                       nMinTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
1✔
2562
    }
2563
    if (m_maxTileX >= 0 && m_maxTileX < nMaxTileX)
62✔
2564
    {
2565
        nMaxTileX = m_maxTileX;
2✔
2566
        adfExtent[2] = tileMatrix.mTopLeftX + (nMaxTileX + 1) *
2✔
2567
                                                  tileMatrix.mResX *
2✔
2568
                                                  tileMatrix.mTileWidth;
2✔
2569
    }
2570
    if (m_maxTileY >= 0 && m_maxTileY < nMaxTileY)
62✔
2571
    {
2572
        nMaxTileY = m_maxTileY;
2✔
2573
        adfExtent[1] = tileMatrix.mTopLeftY - (nMaxTileY + 1) *
2✔
2574
                                                  tileMatrix.mResY *
2✔
2575
                                                  tileMatrix.mTileHeight;
2✔
2576
    }
2577

2578
    if (nMaxTileX - nMinTileX + 1 > INT_MAX / tileMatrix.mTileWidth ||
62✔
2579
        nMaxTileY - nMinTileY + 1 > INT_MAX / tileMatrix.mTileHeight)
61✔
2580
    {
2581
        ReportError(CE_Failure, CPLE_AppDefined, "Too large zoom level");
1✔
2582
        return false;
1✔
2583
    }
2584

2585
    dstGT[0] = tileMatrix.mTopLeftX +
122✔
2586
               nMinTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
61✔
2587
    dstGT[1] = tileMatrix.mResX;
61✔
2588
    dstGT[2] = 0;
61✔
2589
    dstGT[3] = tileMatrix.mTopLeftY -
122✔
2590
               nMinTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
61✔
2591
    dstGT[4] = 0;
61✔
2592
    dstGT[5] = -tileMatrix.mResY;
61✔
2593

2594
    /* -------------------------------------------------------------------- */
2595
    /*      Setup warp options.                                             */
2596
    /* -------------------------------------------------------------------- */
2597
    std::unique_ptr<GDALWarpOptions, decltype(&GDALDestroyWarpOptions)> psWO(
2598
        GDALCreateWarpOptions(), GDALDestroyWarpOptions);
122✔
2599

2600
    psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
61✔
2601
    psWO->papszWarpOptions =
122✔
2602
        CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
61✔
2603
    psWO->papszWarpOptions =
122✔
2604
        CSLMerge(psWO->papszWarpOptions, aosWarpOptions.List());
61✔
2605

2606
    int bHasSrcNoData = false;
61✔
2607
    const double dfSrcNoDataValue =
2608
        poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasSrcNoData);
61✔
2609

2610
    const bool bLastSrcBandIsAlpha =
2611
        (poSrcDS->GetRasterCount() > 1 &&
93✔
2612
         poSrcDS->GetRasterBand(poSrcDS->GetRasterCount())
32✔
2613
                 ->GetColorInterpretation() == GCI_AlphaBand);
32✔
2614

2615
    const bool bOutputSupportsAlpha = !EQUAL(m_outputFormat.c_str(), "JPEG");
61✔
2616
    const bool bOutputSupportsNoData = EQUAL(m_outputFormat.c_str(), "GTiff");
61✔
2617
    const bool bDstNoDataSpecified = GetArg("dst-nodata")->IsExplicitlySet();
61✔
2618
    const GDALColorTable *poColorTable =
2619
        poSrcDS->GetRasterBand(1)->GetColorTable();
61✔
2620

2621
    const bool bUserAskedForAlpha = m_addalpha;
61✔
2622
    if (!m_noalpha && !m_addalpha)
61✔
2623
    {
2624
        m_addalpha = !(bHasSrcNoData && bOutputSupportsNoData) &&
71✔
2625
                     !bDstNoDataSpecified && poColorTable == nullptr;
71✔
2626
    }
2627
    m_addalpha &= bOutputSupportsAlpha;
61✔
2628

2629
    psWO->nBandCount = poSrcDS->GetRasterCount();
61✔
2630
    if (bLastSrcBandIsAlpha)
61✔
2631
    {
2632
        --psWO->nBandCount;
5✔
2633
        psWO->nSrcAlphaBand = poSrcDS->GetRasterCount();
5✔
2634
    }
2635

2636
    if (bHasSrcNoData)
61✔
2637
    {
2638
        psWO->padfSrcNoDataReal =
26✔
2639
            static_cast<double *>(CPLCalloc(psWO->nBandCount, sizeof(double)));
13✔
2640
        for (int i = 0; i < psWO->nBandCount; ++i)
32✔
2641
        {
2642
            psWO->padfSrcNoDataReal[i] = dfSrcNoDataValue;
19✔
2643
        }
2644
    }
2645

2646
    if ((bHasSrcNoData && !m_addalpha && bOutputSupportsNoData) ||
61✔
2647
        bDstNoDataSpecified)
2648
    {
2649
        psWO->padfDstNoDataReal =
18✔
2650
            static_cast<double *>(CPLCalloc(psWO->nBandCount, sizeof(double)));
9✔
2651
        for (int i = 0; i < psWO->nBandCount; ++i)
18✔
2652
        {
2653
            psWO->padfDstNoDataReal[i] =
9✔
2654
                bDstNoDataSpecified ? m_dstNoData : dfSrcNoDataValue;
9✔
2655
        }
2656
    }
2657

2658
    psWO->eWorkingDataType = eSrcDT;
61✔
2659

2660
    GDALGetWarpResampleAlg(m_resampling.c_str(), psWO->eResampleAlg);
61✔
2661

2662
    /* -------------------------------------------------------------------- */
2663
    /*      Setup band mapping.                                             */
2664
    /* -------------------------------------------------------------------- */
2665

2666
    psWO->panSrcBands =
122✔
2667
        static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
61✔
2668
    psWO->panDstBands =
122✔
2669
        static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
61✔
2670

2671
    for (int i = 0; i < psWO->nBandCount; i++)
65,719✔
2672
    {
2673
        psWO->panSrcBands[i] = i + 1;
65,658✔
2674
        psWO->panDstBands[i] = i + 1;
65,658✔
2675
    }
2676

2677
    if (m_addalpha)
61✔
2678
        psWO->nDstAlphaBand = psWO->nBandCount + 1;
48✔
2679

2680
    const int nDstBands =
2681
        psWO->nDstAlphaBand ? psWO->nDstAlphaBand : psWO->nBandCount;
61✔
2682

2683
    std::vector<GByte> dstBuffer;
122✔
2684
    const uint64_t dstBufferSize =
2685
        static_cast<uint64_t>(tileMatrix.mTileWidth) * tileMatrix.mTileHeight *
61✔
2686
        nDstBands * GDALGetDataTypeSizeBytes(psWO->eWorkingDataType);
61✔
2687
    const uint64_t nUsableRAM =
2688
        std::min<uint64_t>(INT_MAX, CPLGetUsablePhysicalRAM() / 4);
61✔
2689
    if (dstBufferSize <=
61✔
2690
        (nUsableRAM ? nUsableRAM : static_cast<uint64_t>(INT_MAX)))
61✔
2691
    {
2692
        try
2693
        {
2694
            dstBuffer.resize(static_cast<size_t>(dstBufferSize));
60✔
2695
        }
2696
        catch (const std::exception &)
×
2697
        {
2698
        }
2699
    }
2700
    if (dstBuffer.size() < dstBufferSize)
61✔
2701
    {
2702
        ReportError(CE_Failure, CPLE_AppDefined,
1✔
2703
                    "Tile size and/or number of bands too large compared to "
2704
                    "available RAM");
2705
        return false;
1✔
2706
    }
2707

2708
    FakeMaxZoomDataset oFakeMaxZoomDS(
2709
        (nMaxTileX - nMinTileX + 1) * tileMatrix.mTileWidth,
60✔
2710
        (nMaxTileY - nMinTileY + 1) * tileMatrix.mTileHeight, nDstBands,
60✔
2711
        tileMatrix.mTileWidth, tileMatrix.mTileHeight, psWO->eWorkingDataType,
60✔
2712
        dstGT, oSRS_TMS, dstBuffer);
120✔
2713
    CPL_IGNORE_RET_VAL(oFakeMaxZoomDS.GetSpatialRef());
60✔
2714

2715
    psWO->hSrcDS = GDALDataset::ToHandle(poSrcDS);
60✔
2716
    psWO->hDstDS = GDALDataset::ToHandle(&oFakeMaxZoomDS);
60✔
2717

2718
    std::unique_ptr<GDALDataset> tmpSrcDS;
60✔
2719
    if (m_tilingScheme == "raster" && !bHasNorthUpSrcGT)
60✔
2720
    {
2721
        CPLStringList aosOptions;
1✔
2722
        aosOptions.AddString("-of");
1✔
2723
        aosOptions.AddString("VRT");
1✔
2724
        aosOptions.AddString("-a_ullr");
1✔
2725
        aosOptions.AddString(CPLSPrintf("%.17g", srcGTModif[0]));
1✔
2726
        aosOptions.AddString(CPLSPrintf("%.17g", srcGTModif[3]));
1✔
2727
        aosOptions.AddString(
2728
            CPLSPrintf("%.17g", srcGTModif[0] + nSrcWidth * srcGTModif[1]));
1✔
2729
        aosOptions.AddString(
2730
            CPLSPrintf("%.17g", srcGTModif[3] + nSrcHeight * srcGTModif[5]));
1✔
2731
        if (oSRS_TMS.IsEmpty())
1✔
2732
        {
2733
            aosOptions.AddString("-a_srs");
1✔
2734
            aosOptions.AddString("none");
1✔
2735
        }
2736

2737
        GDALTranslateOptions *psOptions =
2738
            GDALTranslateOptionsNew(aosOptions.List(), nullptr);
1✔
2739

2740
        tmpSrcDS.reset(GDALDataset::FromHandle(GDALTranslate(
1✔
2741
            "", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
2742
        GDALTranslateOptionsFree(psOptions);
1✔
2743
        if (!tmpSrcDS)
1✔
2744
            return false;
×
2745
    }
2746
    hTransformArg.reset(GDALCreateGenImgProjTransformer2(
61✔
2747
        tmpSrcDS ? tmpSrcDS.get() : poSrcDS, &oFakeMaxZoomDS, aosTO.List()));
61✔
2748
    CPLAssert(hTransformArg);
60✔
2749

2750
    /* -------------------------------------------------------------------- */
2751
    /*      Warp the transformer with a linear approximator                 */
2752
    /* -------------------------------------------------------------------- */
2753
    hTransformArg.reset(GDALCreateApproxTransformer(
60✔
2754
        GDALGenImgProjTransform, hTransformArg.release(), 0.125));
2755
    GDALApproxTransformerOwnsSubtransformer(hTransformArg.get(), TRUE);
60✔
2756

2757
    psWO->pfnTransformer = GDALApproxTransform;
60✔
2758
    psWO->pTransformerArg = hTransformArg.get();
60✔
2759

2760
    /* -------------------------------------------------------------------- */
2761
    /*      Determine total number of tiles                                 */
2762
    /* -------------------------------------------------------------------- */
2763
    uint64_t nTotalTiles = static_cast<uint64_t>(nMaxTileY - nMinTileY + 1) *
60✔
2764
                           (nMaxTileX - nMinTileX + 1);
60✔
2765
    const uint64_t nBaseTiles = nTotalTiles;
60✔
2766
    std::atomic<uint64_t> nCurTile = 0;
60✔
2767
    bool bRet = true;
60✔
2768

2769
    for (int iZ = m_maxZoomLevel - 1;
95✔
2770
         bRet && bIntersects && iZ >= m_minZoomLevel; --iZ)
95✔
2771
    {
2772
        auto ovrTileMatrix = tileMatrixList[iZ];
70✔
2773
        int nOvrMinTileX = 0;
35✔
2774
        int nOvrMinTileY = 0;
35✔
2775
        int nOvrMaxTileX = 0;
35✔
2776
        int nOvrMaxTileY = 0;
35✔
2777
        bRet =
2778
            GetTileIndices(ovrTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
70✔
2779
                           nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX,
2780
                           nOvrMaxTileY, m_noIntersectionIsOK, bIntersects);
35✔
2781
        if (bIntersects)
35✔
2782
        {
2783
            nTotalTiles +=
35✔
2784
                static_cast<uint64_t>(nOvrMaxTileY - nOvrMinTileY + 1) *
35✔
2785
                (nOvrMaxTileX - nOvrMinTileX + 1);
35✔
2786
        }
2787
    }
2788

2789
    /* -------------------------------------------------------------------- */
2790
    /*      Generate tiles at max zoom level                                */
2791
    /* -------------------------------------------------------------------- */
2792
    GDALWarpOperation oWO;
120✔
2793

2794
    bRet = oWO.Initialize(psWO.get()) == CE_None && bRet;
60✔
2795

2796
    const auto GetUpdatedCreationOptions =
2797
        [this](const gdal::TileMatrixSet::TileMatrix &oTM)
260✔
2798
    {
2799
        CPLStringList aosCreationOptions(m_creationOptions);
94✔
2800
        if (m_outputFormat == "GTiff")
94✔
2801
        {
2802
            if (aosCreationOptions.FetchNameValue("TILED") == nullptr &&
48✔
2803
                aosCreationOptions.FetchNameValue("BLOCKYSIZE") == nullptr)
24✔
2804
            {
2805
                if (oTM.mTileWidth <= 512 && oTM.mTileHeight <= 512)
24✔
2806
                {
2807
                    aosCreationOptions.SetNameValue(
22✔
2808
                        "BLOCKYSIZE", CPLSPrintf("%d", oTM.mTileHeight));
22✔
2809
                }
2810
                else
2811
                {
2812
                    aosCreationOptions.SetNameValue("TILED", "YES");
2✔
2813
                }
2814
            }
2815
            if (aosCreationOptions.FetchNameValue("COMPRESS") == nullptr)
24✔
2816
                aosCreationOptions.SetNameValue("COMPRESS", "LZW");
24✔
2817
        }
2818
        else if (m_outputFormat == "COG")
70✔
2819
        {
2820
            if (aosCreationOptions.FetchNameValue("OVERVIEW_RESAMPLING") ==
2✔
2821
                nullptr)
2822
            {
2823
                aosCreationOptions.SetNameValue("OVERVIEW_RESAMPLING",
2824
                                                m_overviewResampling.c_str());
2✔
2825
            }
2826
            if (aosCreationOptions.FetchNameValue("BLOCKSIZE") == nullptr &&
2✔
2827
                oTM.mTileWidth <= 512 && oTM.mTileWidth == oTM.mTileHeight)
2✔
2828
            {
2829
                aosCreationOptions.SetNameValue(
2830
                    "BLOCKSIZE", CPLSPrintf("%d", oTM.mTileWidth));
2✔
2831
            }
2832
        }
2833
        return aosCreationOptions;
94✔
2834
    };
60✔
2835

2836
    VSIMkdir(m_outputDirectory.c_str(), 0755);
60✔
2837
    VSIStatBufL sStat;
2838
    if (VSIStatL(m_outputDirectory.c_str(), &sStat) != 0 ||
119✔
2839
        !VSI_ISDIR(sStat.st_mode))
59✔
2840
    {
2841
        ReportError(CE_Failure, CPLE_FileIO,
1✔
2842
                    "Cannot create output directory %s",
2843
                    m_outputDirectory.c_str());
2844
        return false;
1✔
2845
    }
2846

2847
    OGRSpatialReference oWGS84;
118✔
2848
    oWGS84.importFromEPSG(4326);
59✔
2849
    oWGS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
59✔
2850

2851
    std::unique_ptr<OGRCoordinateTransformation> poCTToWGS84;
59✔
2852
    if (!oSRS_TMS.IsEmpty())
59✔
2853
    {
2854
        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
116✔
2855
        poCTToWGS84.reset(
58✔
2856
            OGRCreateCoordinateTransformation(&oSRS_TMS, &oWGS84));
2857
    }
2858

2859
    const bool kmlCompatible = m_kml &&
61✔
2860
                               [this, &poTMS, &poCTToWGS84, bInvertAxisTMS]()
17✔
2861
    {
2862
        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
2✔
2863
        double dfX = poTMS->tileMatrixList()[0].mTopLeftX;
2✔
2864
        double dfY = poTMS->tileMatrixList()[0].mTopLeftY;
2✔
2865
        if (bInvertAxisTMS)
2✔
2866
            std::swap(dfX, dfY);
×
2867
        return (m_minZoomLevel == m_maxZoomLevel ||
3✔
2868
                (poTMS->haveAllLevelsSameTopLeft() &&
1✔
2869
                 poTMS->haveAllLevelsSameTileSize() &&
1✔
2870
                 poTMS->hasOnlyPowerOfTwoVaryingScales())) &&
3✔
2871
               poCTToWGS84 && poCTToWGS84->Transform(1, &dfX, &dfY);
7✔
2872
    }();
2✔
2873
    const int kmlTileSize =
2874
        m_tileSize > 0 ? m_tileSize : poTMS->tileMatrixList()[0].mTileWidth;
59✔
2875
    if (m_kml && !kmlCompatible)
59✔
2876
    {
2877
        ReportError(CE_Failure, CPLE_NotSupported,
×
2878
                    "Tiling scheme not compatible with KML output");
2879
        return false;
×
2880
    }
2881

2882
    if (m_title.empty())
59✔
2883
        m_title = CPLGetFilename(m_dataset.GetName().c_str());
57✔
2884

2885
    if (!m_url.empty())
59✔
2886
    {
2887
        if (m_url.back() != '/')
2✔
2888
            m_url += '/';
2✔
2889
        std::string out_path = m_outputDirectory;
4✔
2890
        if (m_outputDirectory.back() == '/')
2✔
2891
            out_path.pop_back();
×
2892
        m_url += CPLGetFilename(out_path.c_str());
2✔
2893
    }
2894

2895
    CPLWorkerThreadPool oThreadPool;
59✔
2896

2897
    {
2898
        PerThreadMaxZoomResourceManager oResourceManager(
2899
            poSrcDS, psWO.get(), hTransformArg.get(), oFakeMaxZoomDS,
59✔
2900
            dstBuffer.size());
177✔
2901

2902
        const CPLStringList aosCreationOptions(
2903
            GetUpdatedCreationOptions(tileMatrix));
118✔
2904

2905
        CPLDebug("gdal_raster_tile",
59✔
2906
                 "Generating tiles z=%d, y=%d...%d, x=%d...%d", m_maxZoomLevel,
2907
                 nMinTileY, nMaxTileY, nMinTileX, nMaxTileX);
2908

2909
        if (static_cast<uint64_t>(m_numThreads) > nBaseTiles)
59✔
2910
            m_numThreads = static_cast<int>(nBaseTiles);
37✔
2911

2912
        if (bRet && m_numThreads > 1)
59✔
2913
        {
2914
            CPLDebug("gdal_raster_tile", "Using %d threads", m_numThreads);
28✔
2915
            bRet = oThreadPool.Setup(m_numThreads, nullptr, nullptr);
28✔
2916
        }
2917

2918
        std::atomic<bool> bFailure = false;
59✔
2919
        std::atomic<int> nQueuedJobs = 0;
59✔
2920

2921
        for (int iY = nMinTileY; bRet && iY <= nMaxTileY; ++iY)
156✔
2922
        {
2923
            for (int iX = nMinTileX; bRet && iX <= nMaxTileX; ++iX)
360✔
2924
            {
2925
                if (m_numThreads > 1)
263✔
2926
                {
2927
                    auto job = [this, &oResourceManager, &bFailure, &nCurTile,
232✔
2928
                                &nQueuedJobs, poDstDriver, pszExtension,
2929
                                &aosCreationOptions, &psWO, &tileMatrix,
2930
                                nDstBands, iX, iY, nMinTileX, nMinTileY,
2931
                                poColorTable, bUserAskedForAlpha]()
1,870✔
2932
                    {
2933
                        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
464✔
2934

2935
                        --nQueuedJobs;
231✔
2936
                        auto resources = oResourceManager.AcquireResources();
463✔
2937
                        if (resources &&
464✔
2938
                            GenerateTile(
696✔
2939
                                resources->poSrcDS.get(), poDstDriver,
232✔
2940
                                pszExtension, aosCreationOptions.List(),
2941
                                *(resources->poWO.get()),
232✔
2942
                                *(resources->poFakeMaxZoomDS->GetSpatialRef()),
232✔
2943
                                psWO->eWorkingDataType, tileMatrix,
232✔
2944
                                m_outputDirectory, nDstBands,
232✔
2945
                                psWO->padfDstNoDataReal
232✔
2946
                                    ? &(psWO->padfDstNoDataReal[0])
×
2947
                                    : nullptr,
2948
                                m_maxZoomLevel, iX, iY, m_convention, nMinTileX,
232✔
2949
                                nMinTileY, m_skipBlank, bUserAskedForAlpha,
232✔
2950
                                m_auxXML, m_resume, m_metadata, poColorTable,
232✔
2951
                                resources->dstBuffer))
464✔
2952
                        {
2953
                            oResourceManager.ReleaseResources(
216✔
2954
                                std::move(resources));
216✔
2955
                        }
2956
                        else
2957
                        {
2958
                            oResourceManager.SetError();
16✔
2959
                            bFailure = true;
16✔
2960
                        }
2961
                        ++nCurTile;
232✔
2962
                    };
464✔
2963

2964
                    // Avoid queueing too many jobs at once
2965
                    while (bRet && nQueuedJobs / 10 > m_numThreads)
254✔
2966
                    {
2967
                        oThreadPool.WaitEvent();
22✔
2968

2969
                        bRet &=
22✔
2970
                            !bFailure &&
33✔
2971
                            (!pfnProgress ||
11✔
2972
                             pfnProgress(static_cast<double>(nCurTile) /
22✔
2973
                                             static_cast<double>(nTotalTiles),
11✔
2974
                                         "", pProgressData));
22✔
2975
                    }
2976

2977
                    ++nQueuedJobs;
232✔
2978
                    oThreadPool.SubmitJob(std::move(job));
232✔
2979
                }
2980
                else
2981
                {
2982
                    bRet = GenerateTile(
62✔
2983
                        poSrcDS, poDstDriver, pszExtension,
2984
                        aosCreationOptions.List(), oWO, oSRS_TMS,
2985
                        psWO->eWorkingDataType, tileMatrix, m_outputDirectory,
31✔
2986
                        nDstBands,
2987
                        psWO->padfDstNoDataReal ? &(psWO->padfDstNoDataReal[0])
31✔
2988
                                                : nullptr,
2989
                        m_maxZoomLevel, iX, iY, m_convention, nMinTileX,
31✔
2990
                        nMinTileY, m_skipBlank, bUserAskedForAlpha, m_auxXML,
31✔
2991
                        m_resume, m_metadata, poColorTable, dstBuffer);
31✔
2992

2993
                    ++nCurTile;
31✔
2994
                    bRet &= (!pfnProgress ||
33✔
2995
                             pfnProgress(static_cast<double>(nCurTile) /
4✔
2996
                                             static_cast<double>(nTotalTiles),
2✔
2997
                                         "", pProgressData));
31✔
2998
                }
2999
            }
3000
        }
3001

3002
        if (m_numThreads > 1)
59✔
3003
        {
3004
            // Wait for completion of all jobs
3005
            while (bRet && nQueuedJobs > 0)
153✔
3006
            {
3007
                oThreadPool.WaitEvent();
125✔
3008
                bRet &= !bFailure &&
175✔
3009
                        (!pfnProgress ||
50✔
3010
                         pfnProgress(static_cast<double>(nCurTile) /
100✔
3011
                                         static_cast<double>(nTotalTiles),
50✔
3012
                                     "", pProgressData));
125✔
3013
            }
3014
            oThreadPool.WaitCompletion();
28✔
3015
            bRet &=
28✔
3016
                !bFailure && (!pfnProgress ||
29✔
3017
                              pfnProgress(static_cast<double>(nCurTile) /
2✔
3018
                                              static_cast<double>(nTotalTiles),
1✔
3019
                                          "", pProgressData));
28✔
3020

3021
            if (!oResourceManager.GetErrorMsg().empty())
28✔
3022
            {
3023
                // Re-emit error message from worker thread to main thread
3024
                ReportError(CE_Failure, CPLE_AppDefined, "%s",
1✔
3025
                            oResourceManager.GetErrorMsg().c_str());
1✔
3026
            }
3027
        }
3028

3029
        if (m_kml && bRet)
59✔
3030
        {
3031
            for (int iY = nMinTileY; iY <= nMaxTileY; ++iY)
4✔
3032
            {
3033
                for (int iX = nMinTileX; iX <= nMaxTileX; ++iX)
4✔
3034
                {
3035
                    const int nFileY =
3036
                        GetFileY(iY, poTMS->tileMatrixList()[m_maxZoomLevel],
2✔
3037
                                 m_convention);
2✔
3038
                    std::string osFilename = CPLFormFilenameSafe(
3039
                        m_outputDirectory.c_str(),
3040
                        CPLSPrintf("%d", m_maxZoomLevel), nullptr);
4✔
3041
                    osFilename = CPLFormFilenameSafe(
4✔
3042
                        osFilename.c_str(), CPLSPrintf("%d", iX), nullptr);
2✔
3043
                    osFilename = CPLFormFilenameSafe(
4✔
3044
                        osFilename.c_str(),
3045
                        CPLSPrintf("%d.%s", nFileY, pszExtension), nullptr);
2✔
3046
                    if (VSIStatL(osFilename.c_str(), &sStat) == 0)
2✔
3047
                    {
3048
                        GenerateKML(m_outputDirectory, m_title, iX, iY,
4✔
3049
                                    m_maxZoomLevel, kmlTileSize, pszExtension,
3050
                                    m_url, poTMS.get(), bInvertAxisTMS,
2✔
3051
                                    m_convention, poCTToWGS84.get(), {});
2✔
3052
                    }
3053
                }
3054
            }
3055
        }
3056
    }
3057

3058
    /* -------------------------------------------------------------------- */
3059
    /*      Generate tiles at lower zoom levels                             */
3060
    /* -------------------------------------------------------------------- */
3061
    for (int iZ = m_maxZoomLevel - 1; bRet && iZ >= m_minZoomLevel; --iZ)
94✔
3062
    {
3063
        auto srcTileMatrix = tileMatrixList[iZ + 1];
70✔
3064
        int nSrcMinTileX = 0;
35✔
3065
        int nSrcMinTileY = 0;
35✔
3066
        int nSrcMaxTileX = 0;
35✔
3067
        int nSrcMaxTileY = 0;
35✔
3068

3069
        CPL_IGNORE_RET_VAL(
35✔
3070
            GetTileIndices(srcTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
35✔
3071
                           nSrcMinTileX, nSrcMinTileY, nSrcMaxTileX,
3072
                           nSrcMaxTileY, m_noIntersectionIsOK, bIntersects));
35✔
3073

3074
        MosaicDataset oSrcDS(
3075
            CPLFormFilenameSafe(m_outputDirectory.c_str(),
70✔
3076
                                CPLSPrintf("%d", iZ + 1), nullptr),
3077
            pszExtension, m_outputFormat, poSrcDS, srcTileMatrix, oSRS_TMS,
35✔
3078
            nSrcMinTileX, nSrcMinTileY, nSrcMaxTileX, nSrcMaxTileY,
3079
            m_convention, nDstBands, psWO->eWorkingDataType,
35✔
3080
            psWO->padfDstNoDataReal ? &(psWO->padfDstNoDataReal[0]) : nullptr,
9✔
3081
            m_metadata, poColorTable);
184✔
3082

3083
        auto ovrTileMatrix = tileMatrixList[iZ];
70✔
3084
        int nOvrMinTileX = 0;
35✔
3085
        int nOvrMinTileY = 0;
35✔
3086
        int nOvrMaxTileX = 0;
35✔
3087
        int nOvrMaxTileY = 0;
35✔
3088
        CPL_IGNORE_RET_VAL(
35✔
3089
            GetTileIndices(ovrTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
35✔
3090
                           nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX,
3091
                           nOvrMaxTileY, m_noIntersectionIsOK, bIntersects));
35✔
3092
        bRet = bIntersects;
35✔
3093

3094
        if (bRet)
35✔
3095
        {
3096
            CPLDebug("gdal_raster_tile",
35✔
3097
                     "Generating overview tiles z=%d, y=%d...%d, x=%d...%d", iZ,
3098
                     nOvrMinTileY, nOvrMaxTileY, nOvrMinTileX, nOvrMaxTileX);
3099
        }
3100

3101
        const CPLStringList aosCreationOptions(
3102
            GetUpdatedCreationOptions(ovrTileMatrix));
70✔
3103

3104
        PerThreadLowerZoomResourceManager oResourceManager(oSrcDS);
70✔
3105
        std::atomic<bool> bFailure = false;
35✔
3106
        std::atomic<int> nQueuedJobs = 0;
35✔
3107

3108
        const bool bUseThreads =
35✔
3109
            m_numThreads > 1 &&
52✔
3110
            (nOvrMaxTileY > nOvrMinTileY || nOvrMaxTileX > nOvrMinTileX);
17✔
3111

3112
        for (int iY = nOvrMinTileY; bRet && iY <= nOvrMaxTileY; ++iY)
78✔
3113
        {
3114
            for (int iX = nOvrMinTileX; bRet && iX <= nOvrMaxTileX; ++iX)
114✔
3115
            {
3116
                if (bUseThreads)
71✔
3117
                {
3118
                    auto job = [this, &oResourceManager, poDstDriver, &bFailure,
40✔
3119
                                &nCurTile, &nQueuedJobs, pszExtension,
3120
                                &aosCreationOptions, &aosWarpOptions,
3121
                                &ovrTileMatrix, iZ, iX, iY,
3122
                                bUserAskedForAlpha]()
280✔
3123
                    {
3124
                        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
80✔
3125

3126
                        --nQueuedJobs;
40✔
3127
                        auto resources = oResourceManager.AcquireResources();
80✔
3128
                        if (resources &&
80✔
3129
                            GenerateOverviewTile(
80✔
3130
                                *(resources->poSrcDS.get()), poDstDriver,
40✔
3131
                                m_outputFormat, pszExtension,
40✔
3132
                                aosCreationOptions.List(),
3133
                                aosWarpOptions.List(), m_overviewResampling,
40✔
3134
                                ovrTileMatrix, m_outputDirectory, iZ, iX, iY,
40✔
3135
                                m_convention, m_skipBlank, bUserAskedForAlpha,
40✔
3136
                                m_auxXML, m_resume))
80✔
3137
                        {
3138
                            oResourceManager.ReleaseResources(
40✔
3139
                                std::move(resources));
40✔
3140
                        }
3141
                        else
3142
                        {
3143
                            oResourceManager.SetError();
×
3144
                            bFailure = true;
×
3145
                        }
3146
                        ++nCurTile;
40✔
3147
                    };
80✔
3148

3149
                    // Avoid queueing too many jobs at once
3150
                    while (bRet && nQueuedJobs / 10 > m_numThreads)
40✔
3151
                    {
3152
                        oThreadPool.WaitEvent();
×
3153

3154
                        bRet &=
×
3155
                            !bFailure &&
×
3156
                            (!pfnProgress ||
×
3157
                             pfnProgress(static_cast<double>(nCurTile) /
×
3158
                                             static_cast<double>(nTotalTiles),
×
3159
                                         "", pProgressData));
×
3160
                    }
3161

3162
                    ++nQueuedJobs;
40✔
3163
                    oThreadPool.SubmitJob(std::move(job));
40✔
3164
                }
3165
                else
3166
                {
3167
                    bRet = GenerateOverviewTile(
31✔
3168
                        oSrcDS, poDstDriver, m_outputFormat, pszExtension,
31✔
3169
                        aosCreationOptions.List(), aosWarpOptions.List(),
31✔
3170
                        m_overviewResampling, ovrTileMatrix, m_outputDirectory,
31✔
3171
                        iZ, iX, iY, m_convention, m_skipBlank,
31✔
3172
                        bUserAskedForAlpha, m_auxXML, m_resume);
31✔
3173

3174
                    ++nCurTile;
31✔
3175
                    bRet &= (!pfnProgress ||
32✔
3176
                             pfnProgress(static_cast<double>(nCurTile) /
2✔
3177
                                             static_cast<double>(nTotalTiles),
1✔
3178
                                         "", pProgressData));
31✔
3179
                }
3180
            }
3181
        }
3182

3183
        if (bUseThreads)
35✔
3184
        {
3185
            // Wait for completion of all jobs
3186
            while (bRet && nQueuedJobs > 0)
30✔
3187
            {
3188
                oThreadPool.WaitEvent();
26✔
3189
                bRet &= !bFailure &&
39✔
3190
                        (!pfnProgress ||
13✔
3191
                         pfnProgress(static_cast<double>(nCurTile) /
26✔
3192
                                         static_cast<double>(nTotalTiles),
13✔
3193
                                     "", pProgressData));
26✔
3194
            }
3195
            oThreadPool.WaitCompletion();
4✔
3196
            bRet &=
4✔
3197
                !bFailure && (!pfnProgress ||
6✔
3198
                              pfnProgress(static_cast<double>(nCurTile) /
4✔
3199
                                              static_cast<double>(nTotalTiles),
2✔
3200
                                          "", pProgressData));
4✔
3201

3202
            if (!oResourceManager.GetErrorMsg().empty())
4✔
3203
            {
3204
                // Re-emit error message from worker thread to main thread
3205
                ReportError(CE_Failure, CPLE_AppDefined, "%s",
×
3206
                            oResourceManager.GetErrorMsg().c_str());
×
3207
            }
3208
        }
3209

3210
        if (m_kml && bRet)
35✔
3211
        {
3212
            for (int iY = nOvrMinTileY; bRet && iY <= nOvrMaxTileY; ++iY)
2✔
3213
            {
3214
                for (int iX = nOvrMinTileX; bRet && iX <= nOvrMaxTileX; ++iX)
2✔
3215
                {
3216
                    int nFileY =
3217
                        GetFileY(iY, poTMS->tileMatrixList()[iZ], m_convention);
1✔
3218
                    std::string osFilename =
3219
                        CPLFormFilenameSafe(m_outputDirectory.c_str(),
3220
                                            CPLSPrintf("%d", iZ), nullptr);
2✔
3221
                    osFilename = CPLFormFilenameSafe(
2✔
3222
                        osFilename.c_str(), CPLSPrintf("%d", iX), nullptr);
1✔
3223
                    osFilename = CPLFormFilenameSafe(
2✔
3224
                        osFilename.c_str(),
3225
                        CPLSPrintf("%d.%s", nFileY, pszExtension), nullptr);
1✔
3226
                    if (VSIStatL(osFilename.c_str(), &sStat) == 0)
1✔
3227
                    {
3228
                        std::vector<TileCoordinates> children;
1✔
3229

3230
                        for (int iChildY = 0; iChildY <= 1; ++iChildY)
3✔
3231
                        {
3232
                            for (int iChildX = 0; iChildX <= 1; ++iChildX)
6✔
3233
                            {
3234
                                nFileY =
3235
                                    GetFileY(iY * 2 + iChildY,
4✔
3236
                                             poTMS->tileMatrixList()[iZ + 1],
4✔
3237
                                             m_convention);
4✔
3238
                                osFilename = CPLFormFilenameSafe(
8✔
3239
                                    m_outputDirectory.c_str(),
3240
                                    CPLSPrintf("%d", iZ + 1), nullptr);
4✔
3241
                                osFilename = CPLFormFilenameSafe(
8✔
3242
                                    osFilename.c_str(),
3243
                                    CPLSPrintf("%d", iX * 2 + iChildX),
4✔
3244
                                    nullptr);
4✔
3245
                                osFilename = CPLFormFilenameSafe(
8✔
3246
                                    osFilename.c_str(),
3247
                                    CPLSPrintf("%d.%s", nFileY, pszExtension),
3248
                                    nullptr);
4✔
3249
                                if (VSIStatL(osFilename.c_str(), &sStat) == 0)
4✔
3250
                                {
3251
                                    TileCoordinates tc;
1✔
3252
                                    tc.nTileX = iX * 2 + iChildX;
1✔
3253
                                    tc.nTileY = iY * 2 + iChildY;
1✔
3254
                                    tc.nTileZ = iZ + 1;
1✔
3255
                                    children.push_back(std::move(tc));
1✔
3256
                                }
3257
                            }
3258
                        }
3259

3260
                        GenerateKML(m_outputDirectory, m_title, iX, iY, iZ,
2✔
3261
                                    kmlTileSize, pszExtension, m_url,
1✔
3262
                                    poTMS.get(), bInvertAxisTMS, m_convention,
1✔
3263
                                    poCTToWGS84.get(), children);
3264
                    }
3265
                }
3266
            }
3267
        }
3268
    }
3269

3270
    const auto IsWebViewerEnabled = [this](const char *name)
450✔
3271
    {
3272
        return std::find_if(m_webviewers.begin(), m_webviewers.end(),
150✔
3273
                            [name](const std::string &s) {
253✔
3274
                                return s == "all" || s == name;
152✔
3275
                            }) != m_webviewers.end();
150✔
3276
    };
59✔
3277

3278
    if (bRet && poTMS->identifier() == "GoogleMapsCompatible" &&
97✔
3279
        IsWebViewerEnabled("leaflet"))
38✔
3280
    {
3281
        double dfSouthLat = -90;
13✔
3282
        double dfWestLon = -180;
13✔
3283
        double dfNorthLat = 90;
13✔
3284
        double dfEastLon = 180;
13✔
3285

3286
        if (poCTToWGS84)
13✔
3287
        {
3288
            poCTToWGS84->TransformBounds(
13✔
3289
                adfExtent[0], adfExtent[1], adfExtent[2], adfExtent[3],
3290
                &dfWestLon, &dfSouthLat, &dfEastLon, &dfNorthLat, 21);
13✔
3291
        }
3292

3293
        GenerateLeaflet(m_outputDirectory, m_title, dfSouthLat, dfWestLon,
13✔
3294
                        dfNorthLat, dfEastLon, m_minZoomLevel, m_maxZoomLevel,
3295
                        tileMatrix.mTileWidth, pszExtension, m_url, m_copyright,
13✔
3296
                        m_convention == "xyz");
13✔
3297
    }
3298

3299
    if (bRet && IsWebViewerEnabled("openlayers"))
59✔
3300
    {
3301
        GenerateOpenLayers(
42✔
3302
            m_outputDirectory, m_title, adfExtent[0], adfExtent[1],
21✔
3303
            adfExtent[2], adfExtent[3], m_minZoomLevel, m_maxZoomLevel,
3304
            tileMatrix.mTileWidth, pszExtension, m_url, m_copyright,
21✔
3305
            *(poTMS.get()), bInvertAxisTMS, oSRS_TMS, m_convention == "xyz");
21✔
3306
    }
3307

3308
    if (bRet && IsWebViewerEnabled("mapml") &&
75✔
3309
        poTMS->identifier() != "raster" && m_convention == "xyz")
134✔
3310
    {
3311
        GenerateMapML(m_outputDirectory, m_mapmlTemplate, m_title, nMinTileX,
14✔
3312
                      nMinTileY, nMaxTileX, nMaxTileY, m_minZoomLevel,
3313
                      m_maxZoomLevel, pszExtension, m_url, m_copyright,
14✔
3314
                      *(poTMS.get()));
14✔
3315
    }
3316

3317
    if (bRet && m_kml)
59✔
3318
    {
3319
        std::vector<TileCoordinates> children;
4✔
3320

3321
        auto ovrTileMatrix = tileMatrixList[m_minZoomLevel];
2✔
3322
        int nOvrMinTileX = 0;
2✔
3323
        int nOvrMinTileY = 0;
2✔
3324
        int nOvrMaxTileX = 0;
2✔
3325
        int nOvrMaxTileY = 0;
2✔
3326
        CPL_IGNORE_RET_VAL(
2✔
3327
            GetTileIndices(ovrTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
2✔
3328
                           nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX,
3329
                           nOvrMaxTileY, m_noIntersectionIsOK, bIntersects));
2✔
3330

3331
        for (int iY = nOvrMinTileY; bRet && iY <= nOvrMaxTileY; ++iY)
4✔
3332
        {
3333
            for (int iX = nOvrMinTileX; bRet && iX <= nOvrMaxTileX; ++iX)
4✔
3334
            {
3335
                int nFileY = GetFileY(
2✔
3336
                    iY, poTMS->tileMatrixList()[m_minZoomLevel], m_convention);
2✔
3337
                std::string osFilename = CPLFormFilenameSafe(
3338
                    m_outputDirectory.c_str(), CPLSPrintf("%d", m_minZoomLevel),
3339
                    nullptr);
4✔
3340
                osFilename = CPLFormFilenameSafe(osFilename.c_str(),
4✔
3341
                                                 CPLSPrintf("%d", iX), nullptr);
2✔
3342
                osFilename = CPLFormFilenameSafe(
4✔
3343
                    osFilename.c_str(),
3344
                    CPLSPrintf("%d.%s", nFileY, pszExtension), nullptr);
2✔
3345
                if (VSIStatL(osFilename.c_str(), &sStat) == 0)
2✔
3346
                {
3347
                    TileCoordinates tc;
2✔
3348
                    tc.nTileX = iX;
2✔
3349
                    tc.nTileY = iY;
2✔
3350
                    tc.nTileZ = m_minZoomLevel;
2✔
3351
                    children.push_back(std::move(tc));
2✔
3352
                }
3353
            }
3354
        }
3355
        GenerateKML(m_outputDirectory, m_title, -1, -1, -1, kmlTileSize,
4✔
3356
                    pszExtension, m_url, poTMS.get(), bInvertAxisTMS,
2✔
3357
                    m_convention, poCTToWGS84.get(), children);
2✔
3358
    }
3359

3360
    return bRet;
59✔
3361
}
3362

3363
//! @endcond
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc