Line data Source code
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_json.h"
17 : #include "cpl_mem_cache.h"
18 : #include "cpl_spawn.h"
19 : #include "cpl_time.h"
20 : #include "cpl_worker_thread_pool.h"
21 : #include "gdal_alg_priv.h"
22 : #include "gdal_priv.h"
23 : #include "gdalgetgdalpath.h"
24 : #include "gdalwarper.h"
25 : #include "gdal_utils.h"
26 : #include "ogr_spatialref.h"
27 : #include "memdataset.h"
28 : #include "tilematrixset.hpp"
29 : #include "ogr_p.h"
30 :
31 : #include <algorithm>
32 : #include <array>
33 : #include <atomic>
34 : #include <cinttypes>
35 : #include <cmath>
36 : #include <mutex>
37 : #include <utility>
38 : #include <thread>
39 :
40 : #ifndef _WIN32
41 : #define FORK_ALLOWED
42 : #endif
43 :
44 : //! @cond Doxygen_Suppress
45 :
46 : #ifndef _
47 : #define _(x) (x)
48 : #endif
49 :
50 : // Unlikely substring to appear in stdout. We do that in case some GDAL
51 : // driver would output on stdout.
52 : constexpr const char PROGRESS_MARKER[] = {'!', '.', 'x'};
53 : constexpr const char END_MARKER[] = {'?', 'E', '?', 'N', '?', 'D', '?'};
54 :
55 : constexpr const char *STOP_MARKER = "STOP\n";
56 :
57 : namespace
58 : {
59 : struct BandMetadata
60 : {
61 : std::string osDescription{};
62 : GDALDataType eDT{};
63 : GDALColorInterp eColorInterp{};
64 : std::string osCenterWaveLength{};
65 : std::string osFWHM{};
66 : };
67 : } // namespace
68 :
69 : /************************************************************************/
70 : /* GetThresholdMinTilesPerJob() */
71 : /************************************************************************/
72 :
73 13 : static int GetThresholdMinThreadsForSpawn()
74 : {
75 : // Minimum number of threads for automatic switch to spawning
76 13 : constexpr int THRESHOLD_MIN_THREADS_FOR_SPAWN = 8;
77 :
78 : // Config option for test only
79 13 : return std::max(1, atoi(CPLGetConfigOption(
80 : "GDAL_THRESHOLD_MIN_THREADS_FOR_SPAWN",
81 13 : CPLSPrintf("%d", THRESHOLD_MIN_THREADS_FOR_SPAWN))));
82 : }
83 :
84 : /************************************************************************/
85 : /* GetThresholdMinTilesPerJob() */
86 : /************************************************************************/
87 :
88 178 : static int GetThresholdMinTilesPerJob()
89 : {
90 : // Minimum number of tiles per job to decide for automatic switch to spawning
91 178 : constexpr int THRESHOLD_TILES_PER_JOB = 100;
92 :
93 : // Config option for test only
94 : return std::max(
95 178 : 1, atoi(CPLGetConfigOption("GDAL_THRESHOLD_MIN_TILES_PER_JOB",
96 178 : CPLSPrintf("%d", THRESHOLD_TILES_PER_JOB))));
97 : }
98 :
99 : /************************************************************************/
100 : /* GDALRasterTileAlgorithm::GDALRasterTileAlgorithm() */
101 : /************************************************************************/
102 :
103 140 : GDALRasterTileAlgorithm::GDALRasterTileAlgorithm()
104 140 : : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
105 : {
106 140 : AddProgressArg();
107 : AddArg("spawned", 0, _("Whether this is a spawned worker"),
108 280 : &m_spawned)
109 140 : .SetHidden(); // Used in spawn mode
110 : #ifdef FORK_ALLOWED
111 : AddArg("forked", 0, _("Whether this is a forked worker"),
112 280 : &m_forked)
113 140 : .SetHidden(); // Used in forked mode
114 : #else
115 : CPL_IGNORE_RET_VAL(m_forked);
116 : #endif
117 280 : AddArg("config-options-in-stdin", 0, _(""), &m_dummy)
118 140 : .SetHidden(); // Used in spawn mode
119 : AddArg("ovr-zoom-level", 0, _("Overview zoom level to compute"),
120 280 : &m_ovrZoomLevel)
121 140 : .SetMinValueIncluded(0)
122 140 : .SetHidden(); // Used in spawn mode
123 280 : AddArg("ovr-min-x", 0, _("Minimum tile X coordinate"), &m_minOvrTileX)
124 140 : .SetMinValueIncluded(0)
125 140 : .SetHidden(); // Used in spawn mode
126 280 : AddArg("ovr-max-x", 0, _("Maximum tile X coordinate"), &m_maxOvrTileX)
127 140 : .SetMinValueIncluded(0)
128 140 : .SetHidden(); // Used in spawn mode
129 280 : AddArg("ovr-min-y", 0, _("Minimum tile Y coordinate"), &m_minOvrTileY)
130 140 : .SetMinValueIncluded(0)
131 140 : .SetHidden(); // Used in spawn mode
132 280 : AddArg("ovr-max-y", 0, _("Maximum tile Y coordinate"), &m_maxOvrTileY)
133 140 : .SetMinValueIncluded(0)
134 140 : .SetHidden(); // Used in spawn mode
135 :
136 140 : AddOpenOptionsArg(&m_openOptions);
137 140 : AddInputFormatsArg(&m_inputFormats)
138 280 : .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_RASTER});
139 140 : AddInputDatasetArg(&m_dataset, GDAL_OF_RASTER);
140 140 : AddOutputFormatArg(&m_outputFormat)
141 140 : .SetDefault(m_outputFormat)
142 : .AddMetadataItem(
143 : GAAMDI_REQUIRED_CAPABILITIES,
144 700 : {GDAL_DCAP_RASTER, GDAL_DCAP_CREATECOPY, GDAL_DMD_EXTENSIONS})
145 280 : .AddMetadataItem(GAAMDI_VRT_COMPATIBLE, {"false"});
146 140 : AddCreationOptionsArg(&m_creationOptions);
147 :
148 280 : AddArg("output", 'o', _("Output directory"), &m_outputDirectory)
149 140 : .SetRequired()
150 140 : .SetMinCharCount(1)
151 140 : .SetPositional();
152 :
153 560 : std::vector<std::string> tilingSchemes{"raster"};
154 1260 : for (const std::string &scheme :
155 2660 : gdal::TileMatrixSet::listPredefinedTileMatrixSets())
156 : {
157 2520 : auto poTMS = gdal::TileMatrixSet::parse(scheme.c_str());
158 2520 : OGRSpatialReference oSRS_TMS;
159 2520 : if (poTMS && !poTMS->hasVariableMatrixWidth() &&
160 1260 : oSRS_TMS.SetFromUserInput(poTMS->crs().c_str()) == OGRERR_NONE)
161 : {
162 1260 : std::string identifier = scheme == "GoogleMapsCompatible"
163 : ? "WebMercatorQuad"
164 2520 : : poTMS->identifier();
165 1260 : m_mapTileMatrixIdentifierToScheme[identifier] = scheme;
166 1260 : tilingSchemes.push_back(std::move(identifier));
167 : }
168 : }
169 280 : AddArg("tiling-scheme", 0, _("Tiling scheme"), &m_tilingScheme)
170 140 : .SetDefault("WebMercatorQuad")
171 140 : .SetChoices(tilingSchemes)
172 : .SetHiddenChoices(
173 : "GoogleMapsCompatible", // equivalent of WebMercatorQuad
174 : "mercator", // gdal2tiles equivalent of WebMercatorQuad
175 : "geodetic" // gdal2tiles (not totally) equivalent of WorldCRS84Quad
176 140 : );
177 :
178 280 : AddArg("min-zoom", 0, _("Minimum zoom level"), &m_minZoomLevel)
179 140 : .SetMinValueIncluded(0);
180 280 : AddArg("max-zoom", 0, _("Maximum zoom level"), &m_maxZoomLevel)
181 140 : .SetMinValueIncluded(0);
182 :
183 280 : AddArg("min-x", 0, _("Minimum tile X coordinate"), &m_minTileX)
184 140 : .SetMinValueIncluded(0);
185 280 : AddArg("max-x", 0, _("Maximum tile X coordinate"), &m_maxTileX)
186 140 : .SetMinValueIncluded(0);
187 280 : AddArg("min-y", 0, _("Minimum tile Y coordinate"), &m_minTileY)
188 140 : .SetMinValueIncluded(0);
189 280 : AddArg("max-y", 0, _("Maximum tile Y coordinate"), &m_maxTileY)
190 140 : .SetMinValueIncluded(0);
191 : AddArg("no-intersection-ok", 0,
192 : _("Whether dataset extent not intersecting tile matrix is only a "
193 : "warning"),
194 140 : &m_noIntersectionIsOK);
195 :
196 : AddArg("resampling", 'r', _("Resampling method for max zoom"),
197 280 : &m_resampling)
198 : .SetChoices("nearest", "bilinear", "cubic", "cubicspline", "lanczos",
199 : "average", "rms", "mode", "min", "max", "med", "q1", "q3",
200 140 : "sum")
201 140 : .SetDefault("cubic")
202 140 : .SetHiddenChoices("near");
203 : AddArg("overview-resampling", 0, _("Resampling method for overviews"),
204 280 : &m_overviewResampling)
205 : .SetChoices("nearest", "bilinear", "cubic", "cubicspline", "lanczos",
206 : "average", "rms", "mode", "min", "max", "med", "q1", "q3",
207 140 : "sum")
208 140 : .SetHiddenChoices("near");
209 :
210 : AddArg("convention", 0,
211 : _("Tile numbering convention: xyz (from top) or tms (from bottom)"),
212 280 : &m_convention)
213 140 : .SetDefault(m_convention)
214 140 : .SetChoices("xyz", "tms");
215 280 : AddArg("tile-size", 0, _("Override default tile size"), &m_tileSize)
216 140 : .SetMinValueIncluded(64)
217 140 : .SetMaxValueIncluded(32768);
218 : AddArg("add-alpha", 0, _("Whether to force adding an alpha channel"),
219 280 : &m_addalpha)
220 140 : .SetMutualExclusionGroup("alpha");
221 : AddArg("no-alpha", 0, _("Whether to disable adding an alpha channel"),
222 280 : &m_noalpha)
223 140 : .SetMutualExclusionGroup("alpha");
224 : auto &dstNoDataArg =
225 140 : AddArg("dst-nodata", 0, _("Destination nodata value"), &m_dstNoData);
226 140 : AddArg("skip-blank", 0, _("Do not generate blank tiles"), &m_skipBlank);
227 :
228 : {
229 : auto &arg = AddArg("metadata", 0,
230 280 : _("Add metadata item to output tiles"), &m_metadata)
231 280 : .SetMetaVar("<KEY>=<VALUE>")
232 140 : .SetPackedValuesAllowed(false);
233 17 : arg.AddValidationAction([this, &arg]()
234 157 : { return ParseAndValidateKeyValue(arg); });
235 140 : arg.AddHiddenAlias("mo");
236 : }
237 : AddArg("copy-src-metadata", 0,
238 : _("Whether to copy metadata from source dataset"),
239 140 : &m_copySrcMetadata);
240 :
241 : AddArg("aux-xml", 0, _("Generate .aux.xml sidecar files when needed"),
242 140 : &m_auxXML);
243 140 : AddArg("kml", 0, _("Generate KML files"), &m_kml);
244 140 : AddArg("resume", 0, _("Generate only missing files"), &m_resume);
245 :
246 140 : AddNumThreadsArg(&m_numThreads, &m_numThreadsStr);
247 : AddArg("parallel-method", 0,
248 : #ifdef FORK_ALLOWED
249 : _("Parallelization method (thread, spawn, fork)")
250 : #else
251 : _("Parallelization method (thread / spawn)")
252 : #endif
253 : ,
254 280 : &m_parallelMethod)
255 : .SetChoices("thread", "spawn"
256 : #ifdef FORK_ALLOWED
257 : ,
258 : "fork"
259 : #endif
260 140 : );
261 :
262 140 : constexpr const char *ADVANCED_RESAMPLING_CATEGORY = "Advanced Resampling";
263 : auto &excludedValuesArg =
264 : AddArg("excluded-values", 0,
265 : _("Tuples of values (e.g. <R>,<G>,<B> or (<R1>,<G1>,<B1>),"
266 : "(<R2>,<G2>,<B2>)) that must beignored as contributing source "
267 : "pixels during (average) resampling"),
268 280 : &m_excludedValues)
269 140 : .SetCategory(ADVANCED_RESAMPLING_CATEGORY);
270 : auto &excludedValuesPctThresholdArg =
271 : AddArg(
272 : "excluded-values-pct-threshold", 0,
273 : _("Minimum percentage of source pixels that must be set at one of "
274 : "the --excluded-values to cause the excluded value to be used as "
275 : "the target pixel value"),
276 280 : &m_excludedValuesPctThreshold)
277 140 : .SetDefault(m_excludedValuesPctThreshold)
278 140 : .SetMinValueIncluded(0)
279 140 : .SetMaxValueIncluded(100)
280 140 : .SetCategory(ADVANCED_RESAMPLING_CATEGORY);
281 : auto &nodataValuesPctThresholdArg =
282 : AddArg(
283 : "nodata-values-pct-threshold", 0,
284 : _("Minimum percentage of source pixels that must be set at one of "
285 : "nodata (or alpha=0 or any other way to express transparent pixel"
286 : "to cause the target pixel value to be transparent"),
287 280 : &m_nodataValuesPctThreshold)
288 140 : .SetDefault(m_nodataValuesPctThreshold)
289 140 : .SetMinValueIncluded(0)
290 140 : .SetMaxValueIncluded(100)
291 140 : .SetCategory(ADVANCED_RESAMPLING_CATEGORY);
292 :
293 140 : constexpr const char *PUBLICATION_CATEGORY = "Publication";
294 280 : AddArg("webviewer", 0, _("Web viewer to generate"), &m_webviewers)
295 140 : .SetDefault("all")
296 140 : .SetChoices("none", "all", "leaflet", "openlayers", "mapml", "stac")
297 140 : .SetCategory(PUBLICATION_CATEGORY);
298 : AddArg("url", 0,
299 : _("URL address where the generated tiles are going to be published"),
300 280 : &m_url)
301 140 : .SetCategory(PUBLICATION_CATEGORY);
302 280 : AddArg("title", 0, _("Title of the map"), &m_title)
303 140 : .SetCategory(PUBLICATION_CATEGORY);
304 280 : AddArg("copyright", 0, _("Copyright for the map"), &m_copyright)
305 140 : .SetCategory(PUBLICATION_CATEGORY);
306 : AddArg("mapml-template", 0,
307 : _("Filename of a template mapml file where variables will be "
308 : "substituted"),
309 280 : &m_mapmlTemplate)
310 140 : .SetMinCharCount(1)
311 140 : .SetCategory(PUBLICATION_CATEGORY);
312 :
313 140 : AddValidationAction(
314 161 : [this, &dstNoDataArg, &excludedValuesArg,
315 1064 : &excludedValuesPctThresholdArg, &nodataValuesPctThresholdArg]()
316 : {
317 161 : if (m_minTileX >= 0 && m_maxTileX >= 0 && m_minTileX > m_maxTileX)
318 : {
319 1 : ReportError(CE_Failure, CPLE_IllegalArg,
320 : "'min-x' must be lesser or equal to 'max-x'");
321 1 : return false;
322 : }
323 :
324 160 : if (m_minTileY >= 0 && m_maxTileY >= 0 && m_minTileY > m_maxTileY)
325 : {
326 1 : ReportError(CE_Failure, CPLE_IllegalArg,
327 : "'min-y' must be lesser or equal to 'max-y'");
328 1 : return false;
329 : }
330 :
331 159 : if (m_minZoomLevel >= 0 && m_maxZoomLevel >= 0 &&
332 55 : m_minZoomLevel > m_maxZoomLevel)
333 : {
334 1 : ReportError(CE_Failure, CPLE_IllegalArg,
335 : "'min-zoom' must be lesser or equal to 'max-zoom'");
336 1 : return false;
337 : }
338 :
339 158 : if (m_addalpha && dstNoDataArg.IsExplicitlySet())
340 : {
341 1 : ReportError(
342 : CE_Failure, CPLE_IllegalArg,
343 : "'add-alpha' and 'dst-nodata' are mutually exclusive");
344 1 : return false;
345 : }
346 :
347 465 : for (const auto *arg :
348 : {&excludedValuesArg, &excludedValuesPctThresholdArg,
349 622 : &nodataValuesPctThresholdArg})
350 : {
351 467 : if (arg->IsExplicitlySet() && m_resampling != "average")
352 : {
353 1 : ReportError(CE_Failure, CPLE_AppDefined,
354 : "'%s' can only be specified if 'resampling' is "
355 : "set to 'average'",
356 1 : arg->GetName().c_str());
357 2 : return false;
358 : }
359 467 : if (arg->IsExplicitlySet() && !m_overviewResampling.empty() &&
360 1 : m_overviewResampling != "average")
361 : {
362 1 : ReportError(CE_Failure, CPLE_AppDefined,
363 : "'%s' can only be specified if "
364 : "'overview-resampling' is set to 'average'",
365 1 : arg->GetName().c_str());
366 1 : return false;
367 : }
368 : }
369 :
370 155 : return true;
371 : });
372 140 : }
373 :
374 : /************************************************************************/
375 : /* GetTileIndices() */
376 : /************************************************************************/
377 :
378 427 : static bool GetTileIndices(gdal::TileMatrixSet::TileMatrix &tileMatrix,
379 : bool bInvertAxisTMS, int tileSize,
380 : const double adfExtent[4], int &nMinTileX,
381 : int &nMinTileY, int &nMaxTileX, int &nMaxTileY,
382 : bool noIntersectionIsOK, bool &bIntersects,
383 : bool checkRasterOverflow = true)
384 : {
385 427 : if (tileSize > 0)
386 : {
387 32 : tileMatrix.mResX *=
388 32 : static_cast<double>(tileMatrix.mTileWidth) / tileSize;
389 32 : tileMatrix.mResY *=
390 32 : static_cast<double>(tileMatrix.mTileHeight) / tileSize;
391 32 : tileMatrix.mTileWidth = tileSize;
392 32 : tileMatrix.mTileHeight = tileSize;
393 : }
394 :
395 427 : if (bInvertAxisTMS)
396 2 : std::swap(tileMatrix.mTopLeftX, tileMatrix.mTopLeftY);
397 :
398 427 : const double dfTileWidth = tileMatrix.mResX * tileMatrix.mTileWidth;
399 427 : const double dfTileHeight = tileMatrix.mResY * tileMatrix.mTileHeight;
400 :
401 427 : constexpr double EPSILON = 1e-3;
402 427 : const double dfMinTileX =
403 427 : (adfExtent[0] - tileMatrix.mTopLeftX) / dfTileWidth;
404 427 : nMinTileX = static_cast<int>(
405 854 : std::clamp(std::floor(dfMinTileX + EPSILON), 0.0,
406 427 : static_cast<double>(tileMatrix.mMatrixWidth - 1)));
407 427 : const double dfMinTileY =
408 427 : (tileMatrix.mTopLeftY - adfExtent[3]) / dfTileHeight;
409 427 : nMinTileY = static_cast<int>(
410 854 : std::clamp(std::floor(dfMinTileY + EPSILON), 0.0,
411 427 : static_cast<double>(tileMatrix.mMatrixHeight - 1)));
412 427 : const double dfMaxTileX =
413 427 : (adfExtent[2] - tileMatrix.mTopLeftX) / dfTileWidth;
414 427 : nMaxTileX = static_cast<int>(
415 854 : std::clamp(std::floor(dfMaxTileX + EPSILON), 0.0,
416 427 : static_cast<double>(tileMatrix.mMatrixWidth - 1)));
417 427 : const double dfMaxTileY =
418 427 : (tileMatrix.mTopLeftY - adfExtent[1]) / dfTileHeight;
419 427 : nMaxTileY = static_cast<int>(
420 854 : std::clamp(std::floor(dfMaxTileY + EPSILON), 0.0,
421 427 : static_cast<double>(tileMatrix.mMatrixHeight - 1)));
422 :
423 427 : bIntersects = (dfMinTileX <= tileMatrix.mMatrixWidth && dfMaxTileX >= 0 &&
424 854 : dfMinTileY <= tileMatrix.mMatrixHeight && dfMaxTileY >= 0);
425 427 : if (!bIntersects)
426 : {
427 2 : CPLDebug("gdal_raster_tile",
428 : "dfMinTileX=%g dfMinTileY=%g dfMaxTileX=%g dfMaxTileY=%g",
429 : dfMinTileX, dfMinTileY, dfMaxTileX, dfMaxTileY);
430 2 : CPLError(noIntersectionIsOK ? CE_Warning : CE_Failure, CPLE_AppDefined,
431 : "Extent of source dataset is not compatible with extent of "
432 : "tile matrix %s",
433 : tileMatrix.mId.c_str());
434 2 : return noIntersectionIsOK;
435 : }
436 425 : if (checkRasterOverflow)
437 : {
438 320 : if (nMaxTileX - nMinTileX + 1 > INT_MAX / tileMatrix.mTileWidth ||
439 320 : nMaxTileY - nMinTileY + 1 > INT_MAX / tileMatrix.mTileHeight)
440 : {
441 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large zoom level");
442 0 : return false;
443 : }
444 : }
445 425 : return true;
446 : }
447 :
448 : /************************************************************************/
449 : /* GetFileY() */
450 : /************************************************************************/
451 :
452 4253 : static int GetFileY(int iY, const gdal::TileMatrixSet::TileMatrix &tileMatrix,
453 : const std::string &convention)
454 : {
455 4253 : return convention == "xyz" ? iY : tileMatrix.mMatrixHeight - 1 - iY;
456 : }
457 :
458 : /************************************************************************/
459 : /* GenerateTile() */
460 : /************************************************************************/
461 :
462 522 : static bool GenerateTile(
463 : GDALDataset *poSrcDS, GDALDriver *m_poDstDriver, const char *pszExtension,
464 : CSLConstList creationOptions, GDALWarpOperation &oWO,
465 : const OGRSpatialReference &oSRS_TMS, GDALDataType eWorkingDataType,
466 : const gdal::TileMatrixSet::TileMatrix &tileMatrix,
467 : const std::string &outputDirectory, int nBands, const double *pdfDstNoData,
468 : int nZoomLevel, int iX, int iY, const std::string &convention,
469 : int nMinTileX, int nMinTileY, bool bSkipBlank, bool bUserAskedForAlpha,
470 : bool bAuxXML, bool bResume, const std::vector<std::string> &metadata,
471 : const GDALColorTable *poColorTable, std::vector<GByte> &dstBuffer)
472 : {
473 : const std::string osDirZ = CPLFormFilenameSafe(
474 1044 : outputDirectory.c_str(), CPLSPrintf("%d", nZoomLevel), nullptr);
475 : const std::string osDirX =
476 1044 : CPLFormFilenameSafe(osDirZ.c_str(), CPLSPrintf("%d", iX), nullptr);
477 522 : const int iFileY = GetFileY(iY, tileMatrix, convention);
478 : const std::string osFilename = CPLFormFilenameSafe(
479 1044 : osDirX.c_str(), CPLSPrintf("%d", iFileY), pszExtension);
480 :
481 522 : if (bResume)
482 : {
483 : VSIStatBufL sStat;
484 5 : if (VSIStatL(osFilename.c_str(), &sStat) == 0)
485 5 : return true;
486 : }
487 :
488 517 : const int nDstXOff = (iX - nMinTileX) * tileMatrix.mTileWidth;
489 517 : const int nDstYOff = (iY - nMinTileY) * tileMatrix.mTileHeight;
490 517 : memset(dstBuffer.data(), 0, dstBuffer.size());
491 1034 : const CPLErr eErr = oWO.WarpRegionToBuffer(
492 517 : nDstXOff, nDstYOff, tileMatrix.mTileWidth, tileMatrix.mTileHeight,
493 517 : dstBuffer.data(), eWorkingDataType);
494 517 : if (eErr != CE_None)
495 2 : return false;
496 :
497 : const bool bDstHasAlpha =
498 544 : nBands > poSrcDS->GetRasterCount() ||
499 29 : (nBands == poSrcDS->GetRasterCount() &&
500 28 : poSrcDS->GetRasterBand(nBands)->GetColorInterpretation() ==
501 515 : GCI_AlphaBand);
502 515 : const size_t nBytesPerBand = static_cast<size_t>(tileMatrix.mTileWidth) *
503 515 : tileMatrix.mTileHeight *
504 515 : GDALGetDataTypeSizeBytes(eWorkingDataType);
505 515 : if (bDstHasAlpha && bSkipBlank)
506 : {
507 9 : bool bBlank = true;
508 327706 : for (size_t i = 0; i < nBytesPerBand && bBlank; ++i)
509 : {
510 327697 : bBlank = (dstBuffer[(nBands - 1) * nBytesPerBand + i] == 0);
511 : }
512 9 : if (bBlank)
513 5 : return true;
514 : }
515 510 : if (bDstHasAlpha && !bUserAskedForAlpha)
516 : {
517 495 : bool bAllOpaque = true;
518 27927700 : for (size_t i = 0; i < nBytesPerBand && bAllOpaque; ++i)
519 : {
520 27949200 : bAllOpaque = (dstBuffer[(nBands - 1) * nBytesPerBand + i] == 255);
521 : }
522 0 : if (bAllOpaque)
523 420 : nBands--;
524 : }
525 :
526 0 : VSIMkdir(osDirZ.c_str(), 0755);
527 510 : VSIMkdir(osDirX.c_str(), 0755);
528 :
529 : auto memDS = std::unique_ptr<GDALDataset>(
530 510 : MEMDataset::Create("", tileMatrix.mTileWidth, tileMatrix.mTileHeight, 0,
531 1020 : eWorkingDataType, nullptr));
532 2051 : for (int i = 0; i < nBands; ++i)
533 : {
534 1541 : char szBuffer[32] = {'\0'};
535 3082 : int nRet = CPLPrintPointer(
536 1541 : szBuffer, dstBuffer.data() + i * nBytesPerBand, sizeof(szBuffer));
537 1541 : szBuffer[nRet] = 0;
538 :
539 1541 : char szOption[64] = {'\0'};
540 1541 : snprintf(szOption, sizeof(szOption), "DATAPOINTER=%s", szBuffer);
541 :
542 1541 : char *apszOptions[] = {szOption, nullptr};
543 :
544 1541 : memDS->AddBand(eWorkingDataType, apszOptions);
545 1541 : auto poDstBand = memDS->GetRasterBand(i + 1);
546 1541 : if (i + 1 <= poSrcDS->GetRasterCount())
547 1466 : poDstBand->SetColorInterpretation(
548 1466 : poSrcDS->GetRasterBand(i + 1)->GetColorInterpretation());
549 : else
550 75 : poDstBand->SetColorInterpretation(GCI_AlphaBand);
551 1541 : if (pdfDstNoData)
552 9 : poDstBand->SetNoDataValue(*pdfDstNoData);
553 1541 : if (i == 0 && poColorTable)
554 1 : poDstBand->SetColorTable(
555 1 : const_cast<GDALColorTable *>(poColorTable));
556 : }
557 1020 : const CPLStringList aosMD(metadata);
558 582 : for (const auto [key, value] : cpl::IterateNameValue(aosMD))
559 : {
560 72 : memDS->SetMetadataItem(key, value);
561 : }
562 :
563 510 : GDALGeoTransform gt;
564 1020 : gt[0] =
565 510 : tileMatrix.mTopLeftX + iX * tileMatrix.mResX * tileMatrix.mTileWidth;
566 510 : gt[1] = tileMatrix.mResX;
567 510 : gt[2] = 0;
568 1020 : gt[3] =
569 510 : tileMatrix.mTopLeftY - iY * tileMatrix.mResY * tileMatrix.mTileHeight;
570 510 : gt[4] = 0;
571 510 : gt[5] = -tileMatrix.mResY;
572 510 : memDS->SetGeoTransform(gt);
573 :
574 510 : memDS->SetSpatialRef(&oSRS_TMS);
575 :
576 : CPLConfigOptionSetter oSetter("GDAL_PAM_ENABLED", bAuxXML ? "YES" : "NO",
577 1020 : false);
578 : CPLConfigOptionSetter oSetter2("GDAL_DISABLE_READDIR_ON_OPEN", "YES",
579 1020 : false);
580 :
581 510 : std::unique_ptr<CPLConfigOptionSetter> poSetter;
582 : // No need to reopen the dataset at end of CreateCopy() (for PNG
583 : // and JPEG) if we don't need to generate .aux.xml
584 510 : if (!bAuxXML)
585 506 : poSetter = std::make_unique<CPLConfigOptionSetter>(
586 1012 : "GDAL_OPEN_AFTER_COPY", "NO", false);
587 510 : CPL_IGNORE_RET_VAL(poSetter);
588 :
589 : const bool bSupportsCreateOnlyVisibleAtCloseTime =
590 1020 : m_poDstDriver->GetMetadataItem(
591 510 : GDAL_DCAP_CREATE_ONLY_VISIBLE_AT_CLOSE_TIME) != nullptr;
592 :
593 : const std::string osTmpFilename = bSupportsCreateOnlyVisibleAtCloseTime
594 : ? osFilename
595 1020 : : osFilename + ".tmp." + pszExtension;
596 :
597 1020 : CPLStringList aosCreationOptions(creationOptions);
598 510 : if (bSupportsCreateOnlyVisibleAtCloseTime)
599 : aosCreationOptions.SetNameValue("@CREATE_ONLY_VISIBLE_AT_CLOSE_TIME",
600 508 : "YES");
601 : std::unique_ptr<GDALDataset> poOutDS(
602 : m_poDstDriver->CreateCopy(osTmpFilename.c_str(), memDS.get(), false,
603 510 : aosCreationOptions.List(), nullptr, nullptr));
604 510 : bool bRet = poOutDS && poOutDS->Close() == CE_None;
605 510 : poOutDS.reset();
606 510 : if (bRet)
607 : {
608 508 : if (!bSupportsCreateOnlyVisibleAtCloseTime)
609 : {
610 0 : bRet = VSIRename(osTmpFilename.c_str(), osFilename.c_str()) == 0;
611 0 : if (bAuxXML)
612 : {
613 0 : VSIRename((osTmpFilename + ".aux.xml").c_str(),
614 0 : (osFilename + ".aux.xml").c_str());
615 : }
616 : }
617 : }
618 : else
619 : {
620 2 : VSIUnlink(osTmpFilename.c_str());
621 : }
622 510 : return bRet;
623 : }
624 :
625 : /************************************************************************/
626 : /* GenerateOverviewTile() */
627 : /************************************************************************/
628 :
629 : static bool
630 157 : GenerateOverviewTile(GDALDataset &oSrcDS, GDALDriver *m_poDstDriver,
631 : const std::string &outputFormat, const char *pszExtension,
632 : CSLConstList creationOptions,
633 : CSLConstList papszWarpOptions,
634 : const std::string &resampling,
635 : const gdal::TileMatrixSet::TileMatrix &tileMatrix,
636 : const std::string &outputDirectory, int nZoomLevel, int iX,
637 : int iY, const std::string &convention, bool bSkipBlank,
638 : bool bUserAskedForAlpha, bool bAuxXML, bool bResume)
639 : {
640 : const std::string osDirZ = CPLFormFilenameSafe(
641 314 : outputDirectory.c_str(), CPLSPrintf("%d", nZoomLevel), nullptr);
642 : const std::string osDirX =
643 314 : CPLFormFilenameSafe(osDirZ.c_str(), CPLSPrintf("%d", iX), nullptr);
644 :
645 157 : const int iFileY = GetFileY(iY, tileMatrix, convention);
646 : const std::string osFilename = CPLFormFilenameSafe(
647 314 : osDirX.c_str(), CPLSPrintf("%d", iFileY), pszExtension);
648 :
649 157 : if (bResume)
650 : {
651 : VSIStatBufL sStat;
652 2 : if (VSIStatL(osFilename.c_str(), &sStat) == 0)
653 1 : return true;
654 : }
655 :
656 156 : VSIMkdir(osDirZ.c_str(), 0755);
657 156 : VSIMkdir(osDirX.c_str(), 0755);
658 :
659 : const bool bSupportsCreateOnlyVisibleAtCloseTime =
660 312 : m_poDstDriver->GetMetadataItem(
661 156 : GDAL_DCAP_CREATE_ONLY_VISIBLE_AT_CLOSE_TIME) != nullptr;
662 :
663 312 : CPLStringList aosOptions;
664 :
665 156 : aosOptions.AddString("-of");
666 156 : aosOptions.AddString(outputFormat.c_str());
667 :
668 180 : for (const char *pszCO : cpl::Iterate(creationOptions))
669 : {
670 24 : aosOptions.AddString("-co");
671 24 : aosOptions.AddString(pszCO);
672 : }
673 156 : if (bSupportsCreateOnlyVisibleAtCloseTime)
674 : {
675 156 : aosOptions.AddString("-co");
676 156 : aosOptions.AddString("@CREATE_ONLY_VISIBLE_AT_CLOSE_TIME=YES");
677 : }
678 :
679 : CPLConfigOptionSetter oSetter("GDAL_PAM_ENABLED", bAuxXML ? "YES" : "NO",
680 312 : false);
681 : CPLConfigOptionSetter oSetter2("GDAL_DISABLE_READDIR_ON_OPEN", "YES",
682 312 : false);
683 :
684 156 : aosOptions.AddString("-r");
685 156 : aosOptions.AddString(resampling.c_str());
686 :
687 156 : std::unique_ptr<GDALDataset> poOutDS;
688 156 : const double dfMinX =
689 156 : tileMatrix.mTopLeftX + iX * tileMatrix.mResX * tileMatrix.mTileWidth;
690 156 : const double dfMaxY =
691 156 : tileMatrix.mTopLeftY - iY * tileMatrix.mResY * tileMatrix.mTileHeight;
692 156 : const double dfMaxX = dfMinX + tileMatrix.mResX * tileMatrix.mTileWidth;
693 156 : const double dfMinY = dfMaxY - tileMatrix.mResY * tileMatrix.mTileHeight;
694 :
695 : const bool resamplingCompatibleOfTranslate =
696 457 : papszWarpOptions == nullptr &&
697 448 : (resampling == "nearest" || resampling == "average" ||
698 297 : resampling == "bilinear" || resampling == "cubic" ||
699 9 : resampling == "cubicspline" || resampling == "lanczos" ||
700 3 : resampling == "mode");
701 :
702 : const std::string osTmpFilename = bSupportsCreateOnlyVisibleAtCloseTime
703 : ? osFilename
704 312 : : osFilename + ".tmp." + pszExtension;
705 :
706 156 : if (resamplingCompatibleOfTranslate)
707 : {
708 150 : GDALGeoTransform upperGT;
709 150 : oSrcDS.GetGeoTransform(upperGT);
710 150 : const double dfMinXUpper = upperGT[0];
711 : const double dfMaxXUpper =
712 150 : dfMinXUpper + upperGT[1] * oSrcDS.GetRasterXSize();
713 150 : const double dfMaxYUpper = upperGT[3];
714 : const double dfMinYUpper =
715 150 : dfMaxYUpper + upperGT[5] * oSrcDS.GetRasterYSize();
716 150 : if (dfMinX >= dfMinXUpper && dfMaxX <= dfMaxXUpper &&
717 134 : dfMinY >= dfMinYUpper && dfMaxY <= dfMaxYUpper)
718 : {
719 : // If the overview tile is fully within the extent of the
720 : // upper zoom level, we can use GDALDataset::RasterIO() directly.
721 :
722 134 : const auto eDT = oSrcDS.GetRasterBand(1)->GetRasterDataType();
723 : const size_t nBytesPerBand =
724 134 : static_cast<size_t>(tileMatrix.mTileWidth) *
725 134 : tileMatrix.mTileHeight * GDALGetDataTypeSizeBytes(eDT);
726 : std::vector<GByte> dstBuffer(nBytesPerBand *
727 134 : oSrcDS.GetRasterCount());
728 :
729 134 : const double dfXOff = (dfMinX - dfMinXUpper) / upperGT[1];
730 134 : const double dfYOff = (dfMaxYUpper - dfMaxY) / -upperGT[5];
731 134 : const double dfXSize = (dfMaxX - dfMinX) / upperGT[1];
732 134 : const double dfYSize = (dfMaxY - dfMinY) / -upperGT[5];
733 : GDALRasterIOExtraArg sExtraArg;
734 133 : INIT_RASTERIO_EXTRA_ARG(sExtraArg);
735 133 : CPL_IGNORE_RET_VAL(sExtraArg.eResampleAlg);
736 133 : sExtraArg.eResampleAlg =
737 133 : GDALRasterIOGetResampleAlg(resampling.c_str());
738 133 : sExtraArg.dfXOff = dfXOff;
739 133 : sExtraArg.dfYOff = dfYOff;
740 133 : sExtraArg.dfXSize = dfXSize;
741 133 : sExtraArg.dfYSize = dfYSize;
742 133 : sExtraArg.bFloatingPointWindowValidity =
743 133 : sExtraArg.eResampleAlg != GRIORA_NearestNeighbour;
744 133 : constexpr double EPSILON = 1e-3;
745 133 : if (oSrcDS.RasterIO(GF_Read, static_cast<int>(dfXOff + EPSILON),
746 133 : static_cast<int>(dfYOff + EPSILON),
747 133 : static_cast<int>(dfXSize + 0.5),
748 133 : static_cast<int>(dfYSize + 0.5),
749 133 : dstBuffer.data(), tileMatrix.mTileWidth,
750 133 : tileMatrix.mTileHeight, eDT,
751 : oSrcDS.GetRasterCount(), nullptr, 0, 0, 0,
752 134 : &sExtraArg) == CE_None)
753 : {
754 133 : int nDstBands = oSrcDS.GetRasterCount();
755 : const bool bDstHasAlpha =
756 133 : oSrcDS.GetRasterBand(nDstBands)->GetColorInterpretation() ==
757 133 : GCI_AlphaBand;
758 133 : if (bDstHasAlpha && bSkipBlank)
759 : {
760 2 : bool bBlank = true;
761 65545 : for (size_t i = 0; i < nBytesPerBand && bBlank; ++i)
762 : {
763 65543 : bBlank =
764 65543 : (dstBuffer[(nDstBands - 1) * nBytesPerBand + i] ==
765 : 0);
766 : }
767 2 : if (bBlank)
768 1 : return true;
769 1 : bSkipBlank = false;
770 : }
771 132 : if (bDstHasAlpha && !bUserAskedForAlpha)
772 : {
773 132 : bool bAllOpaque = true;
774 8454280 : for (size_t i = 0; i < nBytesPerBand && bAllOpaque; ++i)
775 : {
776 8454150 : bAllOpaque =
777 8454150 : (dstBuffer[(nDstBands - 1) * nBytesPerBand + i] ==
778 : 255);
779 : }
780 132 : if (bAllOpaque)
781 129 : nDstBands--;
782 : }
783 :
784 264 : auto memDS = std::unique_ptr<GDALDataset>(MEMDataset::Create(
785 132 : "", tileMatrix.mTileWidth, tileMatrix.mTileHeight, 0, eDT,
786 264 : nullptr));
787 531 : for (int i = 0; i < nDstBands; ++i)
788 : {
789 399 : char szBuffer[32] = {'\0'};
790 798 : int nRet = CPLPrintPointer(
791 399 : szBuffer, dstBuffer.data() + i * nBytesPerBand,
792 : sizeof(szBuffer));
793 399 : szBuffer[nRet] = 0;
794 :
795 399 : char szOption[64] = {'\0'};
796 399 : snprintf(szOption, sizeof(szOption), "DATAPOINTER=%s",
797 : szBuffer);
798 :
799 399 : char *apszOptions[] = {szOption, nullptr};
800 :
801 399 : memDS->AddBand(eDT, apszOptions);
802 399 : auto poSrcBand = oSrcDS.GetRasterBand(i + 1);
803 399 : auto poDstBand = memDS->GetRasterBand(i + 1);
804 399 : poDstBand->SetColorInterpretation(
805 399 : poSrcBand->GetColorInterpretation());
806 399 : int bHasNoData = false;
807 : const double dfNoData =
808 399 : poSrcBand->GetNoDataValue(&bHasNoData);
809 399 : if (bHasNoData)
810 0 : poDstBand->SetNoDataValue(dfNoData);
811 399 : if (auto poCT = poSrcBand->GetColorTable())
812 0 : poDstBand->SetColorTable(poCT);
813 : }
814 132 : memDS->SetMetadata(oSrcDS.GetMetadata());
815 264 : memDS->SetGeoTransform(GDALGeoTransform(
816 132 : dfMinX, tileMatrix.mResX, 0, dfMaxY, 0, -tileMatrix.mResY));
817 :
818 132 : memDS->SetSpatialRef(oSrcDS.GetSpatialRef());
819 :
820 132 : std::unique_ptr<CPLConfigOptionSetter> poSetter;
821 : // No need to reopen the dataset at end of CreateCopy() (for PNG
822 : // and JPEG) if we don't need to generate .aux.xml
823 132 : if (!bAuxXML)
824 132 : poSetter = std::make_unique<CPLConfigOptionSetter>(
825 264 : "GDAL_OPEN_AFTER_COPY", "NO", false);
826 132 : CPL_IGNORE_RET_VAL(poSetter);
827 :
828 264 : CPLStringList aosCreationOptions(creationOptions);
829 132 : if (bSupportsCreateOnlyVisibleAtCloseTime)
830 : aosCreationOptions.SetNameValue(
831 132 : "@CREATE_ONLY_VISIBLE_AT_CLOSE_TIME", "YES");
832 132 : poOutDS.reset(m_poDstDriver->CreateCopy(
833 : osTmpFilename.c_str(), memDS.get(), false,
834 132 : aosCreationOptions.List(), nullptr, nullptr));
835 133 : }
836 : }
837 : else
838 : {
839 : // If the overview tile is not fully within the extent of the
840 : // upper zoom level, use GDALTranslate() to use VRT padding
841 :
842 16 : aosOptions.AddString("-q");
843 :
844 16 : aosOptions.AddString("-projwin");
845 16 : aosOptions.AddString(CPLSPrintf("%.17g", dfMinX));
846 16 : aosOptions.AddString(CPLSPrintf("%.17g", dfMaxY));
847 16 : aosOptions.AddString(CPLSPrintf("%.17g", dfMaxX));
848 16 : aosOptions.AddString(CPLSPrintf("%.17g", dfMinY));
849 :
850 16 : aosOptions.AddString("-outsize");
851 16 : aosOptions.AddString(CPLSPrintf("%d", tileMatrix.mTileWidth));
852 16 : aosOptions.AddString(CPLSPrintf("%d", tileMatrix.mTileHeight));
853 :
854 : GDALTranslateOptions *psOptions =
855 16 : GDALTranslateOptionsNew(aosOptions.List(), nullptr);
856 16 : poOutDS.reset(GDALDataset::FromHandle(GDALTranslate(
857 : osTmpFilename.c_str(), GDALDataset::ToHandle(&oSrcDS),
858 : psOptions, nullptr)));
859 16 : GDALTranslateOptionsFree(psOptions);
860 : }
861 : }
862 : else
863 : {
864 6 : aosOptions.AddString("-te");
865 6 : aosOptions.AddString(CPLSPrintf("%.17g", dfMinX));
866 6 : aosOptions.AddString(CPLSPrintf("%.17g", dfMinY));
867 6 : aosOptions.AddString(CPLSPrintf("%.17g", dfMaxX));
868 6 : aosOptions.AddString(CPLSPrintf("%.17g", dfMaxY));
869 :
870 6 : aosOptions.AddString("-ts");
871 6 : aosOptions.AddString(CPLSPrintf("%d", tileMatrix.mTileWidth));
872 6 : aosOptions.AddString(CPLSPrintf("%d", tileMatrix.mTileHeight));
873 :
874 11 : for (int i = 0; papszWarpOptions && papszWarpOptions[i]; ++i)
875 : {
876 5 : aosOptions.AddString("-wo");
877 5 : aosOptions.AddString(papszWarpOptions[i]);
878 : }
879 :
880 : GDALWarpAppOptions *psOptions =
881 6 : GDALWarpAppOptionsNew(aosOptions.List(), nullptr);
882 6 : GDALDatasetH hSrcDS = GDALDataset::ToHandle(&oSrcDS);
883 6 : poOutDS.reset(GDALDataset::FromHandle(GDALWarp(
884 : osTmpFilename.c_str(), nullptr, 1, &hSrcDS, psOptions, nullptr)));
885 6 : GDALWarpAppOptionsFree(psOptions);
886 : }
887 :
888 155 : bool bRet = poOutDS != nullptr;
889 155 : if (bRet && bSkipBlank)
890 : {
891 10 : auto poLastBand = poOutDS->GetRasterBand(poOutDS->GetRasterCount());
892 10 : if (poLastBand->GetColorInterpretation() == GCI_AlphaBand)
893 : {
894 : std::vector<GByte> buffer(
895 1 : static_cast<size_t>(tileMatrix.mTileWidth) *
896 1 : tileMatrix.mTileHeight *
897 1 : GDALGetDataTypeSizeBytes(poLastBand->GetRasterDataType()));
898 2 : CPL_IGNORE_RET_VAL(poLastBand->RasterIO(
899 1 : GF_Read, 0, 0, tileMatrix.mTileWidth, tileMatrix.mTileHeight,
900 1 : buffer.data(), tileMatrix.mTileWidth, tileMatrix.mTileHeight,
901 : poLastBand->GetRasterDataType(), 0, 0, nullptr));
902 1 : bool bBlank = true;
903 65537 : for (size_t i = 0; i < buffer.size() && bBlank; ++i)
904 : {
905 65536 : bBlank = (buffer[i] == 0);
906 : }
907 1 : if (bBlank)
908 : {
909 1 : poOutDS.reset();
910 1 : VSIUnlink(osTmpFilename.c_str());
911 1 : if (bAuxXML)
912 0 : VSIUnlink((osTmpFilename + ".aux.xml").c_str());
913 1 : return true;
914 : }
915 : }
916 : }
917 154 : bRet = bRet && poOutDS->Close() == CE_None;
918 154 : poOutDS.reset();
919 154 : if (bRet)
920 : {
921 153 : if (!bSupportsCreateOnlyVisibleAtCloseTime)
922 : {
923 0 : bRet = VSIRename(osTmpFilename.c_str(), osFilename.c_str()) == 0;
924 0 : if (bAuxXML)
925 : {
926 0 : VSIRename((osTmpFilename + ".aux.xml").c_str(),
927 0 : (osFilename + ".aux.xml").c_str());
928 : }
929 : }
930 : }
931 : else
932 : {
933 1 : VSIUnlink(osTmpFilename.c_str());
934 : }
935 154 : return bRet;
936 : }
937 :
938 : namespace
939 : {
940 :
941 : /************************************************************************/
942 : /* FakeMaxZoomRasterBand */
943 : /************************************************************************/
944 :
945 : class FakeMaxZoomRasterBand : public GDALRasterBand
946 : {
947 : void *m_pDstBuffer = nullptr;
948 : CPL_DISALLOW_COPY_ASSIGN(FakeMaxZoomRasterBand)
949 :
950 : public:
951 413 : FakeMaxZoomRasterBand(int nBandIn, int nWidth, int nHeight,
952 : int nBlockXSizeIn, int nBlockYSizeIn,
953 : GDALDataType eDT, void *pDstBuffer)
954 413 : : m_pDstBuffer(pDstBuffer)
955 : {
956 413 : nBand = nBandIn;
957 413 : nRasterXSize = nWidth;
958 413 : nRasterYSize = nHeight;
959 413 : nBlockXSize = nBlockXSizeIn;
960 413 : nBlockYSize = nBlockYSizeIn;
961 413 : eDataType = eDT;
962 413 : }
963 :
964 0 : CPLErr IReadBlock(int, int, void *) override
965 : {
966 0 : CPLAssert(false);
967 : return CE_Failure;
968 : }
969 :
970 : #ifdef DEBUG
971 0 : CPLErr IWriteBlock(int, int, void *) override
972 : {
973 0 : CPLAssert(false);
974 : return CE_Failure;
975 : }
976 : #endif
977 :
978 1004 : CPLErr IRasterIO(GDALRWFlag eRWFlag, [[maybe_unused]] int nXOff,
979 : [[maybe_unused]] int nYOff, [[maybe_unused]] int nXSize,
980 : [[maybe_unused]] int nYSize, void *pData,
981 : [[maybe_unused]] int nBufXSize,
982 : [[maybe_unused]] int nBufYSize, GDALDataType eBufType,
983 : GSpacing nPixelSpace, [[maybe_unused]] GSpacing nLineSpace,
984 : GDALRasterIOExtraArg *) override
985 : {
986 : // For sake of implementation simplicity, check various assumptions of
987 : // how GDALAlphaMask code does I/O
988 1004 : CPLAssert((nXOff % nBlockXSize) == 0);
989 1004 : CPLAssert((nYOff % nBlockYSize) == 0);
990 1004 : CPLAssert(nXSize == nBufXSize);
991 1004 : CPLAssert(nXSize == nBlockXSize);
992 1004 : CPLAssert(nYSize == nBufYSize);
993 1004 : CPLAssert(nYSize == nBlockYSize);
994 1004 : CPLAssert(nLineSpace == nBlockXSize * nPixelSpace);
995 1004 : CPLAssert(
996 : nBand ==
997 : poDS->GetRasterCount()); // only alpha band is accessed this way
998 1004 : if (eRWFlag == GF_Read)
999 : {
1000 502 : double dfZero = 0;
1001 502 : GDALCopyWords64(&dfZero, GDT_Float64, 0, pData, eBufType,
1002 : static_cast<int>(nPixelSpace),
1003 502 : static_cast<size_t>(nBlockXSize) * nBlockYSize);
1004 : }
1005 : else
1006 : {
1007 502 : GDALCopyWords64(pData, eBufType, static_cast<int>(nPixelSpace),
1008 : m_pDstBuffer, eDataType,
1009 : GDALGetDataTypeSizeBytes(eDataType),
1010 502 : static_cast<size_t>(nBlockXSize) * nBlockYSize);
1011 : }
1012 1004 : return CE_None;
1013 : }
1014 : };
1015 :
1016 : /************************************************************************/
1017 : /* FakeMaxZoomDataset */
1018 : /************************************************************************/
1019 :
1020 : // This class is used to create a fake output dataset for GDALWarpOperation.
1021 : // In particular we need to implement GDALRasterBand::IRasterIO(GF_Write, ...)
1022 : // to catch writes (of one single tile) to the alpha band and redirect them
1023 : // to the dstBuffer passed to FakeMaxZoomDataset constructor.
1024 :
1025 : class FakeMaxZoomDataset : public GDALDataset
1026 : {
1027 : const int m_nBlockXSize;
1028 : const int m_nBlockYSize;
1029 : const OGRSpatialReference m_oSRS;
1030 : const GDALGeoTransform m_gt{};
1031 :
1032 : public:
1033 121 : FakeMaxZoomDataset(int nWidth, int nHeight, int nBandsIn, int nBlockXSize,
1034 : int nBlockYSize, GDALDataType eDT,
1035 : const GDALGeoTransform >,
1036 : const OGRSpatialReference &oSRS,
1037 : std::vector<GByte> &dstBuffer)
1038 121 : : m_nBlockXSize(nBlockXSize), m_nBlockYSize(nBlockYSize), m_oSRS(oSRS),
1039 121 : m_gt(gt)
1040 : {
1041 121 : eAccess = GA_Update;
1042 121 : nRasterXSize = nWidth;
1043 121 : nRasterYSize = nHeight;
1044 534 : for (int i = 1; i <= nBandsIn; ++i)
1045 : {
1046 413 : SetBand(i,
1047 : new FakeMaxZoomRasterBand(
1048 : i, nWidth, nHeight, nBlockXSize, nBlockYSize, eDT,
1049 413 : dstBuffer.data() + static_cast<size_t>(i - 1) *
1050 826 : nBlockXSize * nBlockYSize *
1051 413 : GDALGetDataTypeSizeBytes(eDT)));
1052 : }
1053 121 : }
1054 :
1055 308 : const OGRSpatialReference *GetSpatialRef() const override
1056 : {
1057 308 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
1058 : }
1059 :
1060 97 : CPLErr GetGeoTransform(GDALGeoTransform >) const override
1061 : {
1062 97 : gt = m_gt;
1063 97 : return CE_None;
1064 : }
1065 :
1066 : using GDALDataset::Clone;
1067 :
1068 : std::unique_ptr<FakeMaxZoomDataset>
1069 24 : Clone(std::vector<GByte> &dstBuffer) const
1070 : {
1071 : return std::make_unique<FakeMaxZoomDataset>(
1072 24 : nRasterXSize, nRasterYSize, nBands, m_nBlockXSize, m_nBlockYSize,
1073 48 : GetRasterBand(1)->GetRasterDataType(), m_gt, m_oSRS, dstBuffer);
1074 : }
1075 : };
1076 :
1077 : /************************************************************************/
1078 : /* MosaicRasterBand */
1079 : /************************************************************************/
1080 :
1081 : class MosaicRasterBand : public GDALRasterBand
1082 : {
1083 : const int m_tileMinX;
1084 : const int m_tileMinY;
1085 : const GDALColorInterp m_eColorInterp;
1086 : const gdal::TileMatrixSet::TileMatrix m_oTM;
1087 : const std::string m_convention;
1088 : const std::string m_directory;
1089 : const std::string m_extension;
1090 : const bool m_hasNoData;
1091 : const double m_noData;
1092 : std::unique_ptr<GDALColorTable> m_poColorTable{};
1093 :
1094 : public:
1095 294 : MosaicRasterBand(GDALDataset *poDSIn, int nBandIn, int nWidth, int nHeight,
1096 : int nBlockXSizeIn, int nBlockYSizeIn, GDALDataType eDT,
1097 : GDALColorInterp eColorInterp, int nTileMinX, int nTileMinY,
1098 : const gdal::TileMatrixSet::TileMatrix &oTM,
1099 : const std::string &convention,
1100 : const std::string &directory, const std::string &extension,
1101 : const double *pdfDstNoData,
1102 : const GDALColorTable *poColorTable)
1103 294 : : m_tileMinX(nTileMinX), m_tileMinY(nTileMinY),
1104 : m_eColorInterp(eColorInterp), m_oTM(oTM), m_convention(convention),
1105 : m_directory(directory), m_extension(extension),
1106 294 : m_hasNoData(pdfDstNoData != nullptr),
1107 294 : m_noData(pdfDstNoData ? *pdfDstNoData : 0),
1108 588 : m_poColorTable(poColorTable ? poColorTable->Clone() : nullptr)
1109 : {
1110 294 : poDS = poDSIn;
1111 294 : nBand = nBandIn;
1112 294 : nRasterXSize = nWidth;
1113 294 : nRasterYSize = nHeight;
1114 294 : nBlockXSize = nBlockXSizeIn;
1115 294 : nBlockYSize = nBlockYSizeIn;
1116 294 : eDataType = eDT;
1117 294 : }
1118 :
1119 : CPLErr IReadBlock(int nXBlock, int nYBlock, void *pData) override;
1120 :
1121 2798 : GDALColorTable *GetColorTable() override
1122 : {
1123 2798 : return m_poColorTable.get();
1124 : }
1125 :
1126 765 : GDALColorInterp GetColorInterpretation() override
1127 : {
1128 765 : return m_eColorInterp;
1129 : }
1130 :
1131 2277 : double GetNoDataValue(int *pbHasNoData) override
1132 : {
1133 2277 : if (pbHasNoData)
1134 2268 : *pbHasNoData = m_hasNoData;
1135 2277 : return m_noData;
1136 : }
1137 : };
1138 :
1139 : /************************************************************************/
1140 : /* MosaicDataset */
1141 : /************************************************************************/
1142 :
1143 : // This class is to expose the tiles of a given level as a mosaic that
1144 : // can be used as a source to generate the immediately below zoom level.
1145 :
1146 : class MosaicDataset : public GDALDataset
1147 : {
1148 : friend class MosaicRasterBand;
1149 :
1150 : const std::string m_directory;
1151 : const std::string m_extension;
1152 : const std::string m_format;
1153 : const std::vector<GDALColorInterp> m_aeColorInterp;
1154 : const gdal::TileMatrixSet::TileMatrix &m_oTM;
1155 : const OGRSpatialReference m_oSRS;
1156 : const int m_nTileMinX;
1157 : const int m_nTileMinY;
1158 : const int m_nTileMaxX;
1159 : const int m_nTileMaxY;
1160 : const std::string m_convention;
1161 : const GDALDataType m_eDT;
1162 : const double *const m_pdfDstNoData;
1163 : const std::vector<std::string> &m_metadata;
1164 : const GDALColorTable *const m_poCT;
1165 :
1166 : GDALGeoTransform m_gt{};
1167 : const int m_nMaxCacheTileSize;
1168 : lru11::Cache<std::string, std::shared_ptr<GDALDataset>> m_oCacheTile;
1169 :
1170 : CPL_DISALLOW_COPY_ASSIGN(MosaicDataset)
1171 :
1172 : public:
1173 85 : MosaicDataset(const std::string &directory, const std::string &extension,
1174 : const std::string &format,
1175 : const std::vector<GDALColorInterp> &aeColorInterp,
1176 : const gdal::TileMatrixSet::TileMatrix &oTM,
1177 : const OGRSpatialReference &oSRS, int nTileMinX, int nTileMinY,
1178 : int nTileMaxX, int nTileMaxY, const std::string &convention,
1179 : int nBandsIn, GDALDataType eDT, const double *pdfDstNoData,
1180 : const std::vector<std::string> &metadata,
1181 : const GDALColorTable *poCT, int maxCacheTileSize)
1182 85 : : m_directory(directory), m_extension(extension), m_format(format),
1183 : m_aeColorInterp(aeColorInterp), m_oTM(oTM), m_oSRS(oSRS),
1184 : m_nTileMinX(nTileMinX), m_nTileMinY(nTileMinY),
1185 : m_nTileMaxX(nTileMaxX), m_nTileMaxY(nTileMaxY),
1186 : m_convention(convention), m_eDT(eDT), m_pdfDstNoData(pdfDstNoData),
1187 : m_metadata(metadata), m_poCT(poCT),
1188 : m_nMaxCacheTileSize(maxCacheTileSize),
1189 85 : m_oCacheTile(/* max_size = */ maxCacheTileSize, /* elasticity = */ 0)
1190 : {
1191 85 : nRasterXSize = (nTileMaxX - nTileMinX + 1) * oTM.mTileWidth;
1192 85 : nRasterYSize = (nTileMaxY - nTileMinY + 1) * oTM.mTileHeight;
1193 85 : m_gt[0] = oTM.mTopLeftX + nTileMinX * oTM.mResX * oTM.mTileWidth;
1194 85 : m_gt[1] = oTM.mResX;
1195 85 : m_gt[2] = 0;
1196 85 : m_gt[3] = oTM.mTopLeftY - nTileMinY * oTM.mResY * oTM.mTileHeight;
1197 85 : m_gt[4] = 0;
1198 85 : m_gt[5] = -oTM.mResY;
1199 379 : for (int i = 1; i <= nBandsIn; ++i)
1200 : {
1201 : const GDALColorInterp eColorInterp =
1202 294 : (i <= static_cast<int>(m_aeColorInterp.size()))
1203 294 : ? m_aeColorInterp[i - 1]
1204 294 : : GCI_AlphaBand;
1205 294 : SetBand(i, new MosaicRasterBand(
1206 294 : this, i, nRasterXSize, nRasterYSize, oTM.mTileWidth,
1207 294 : oTM.mTileHeight, eDT, eColorInterp, nTileMinX,
1208 : nTileMinY, oTM, convention, directory, extension,
1209 294 : pdfDstNoData, poCT));
1210 : }
1211 85 : SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
1212 170 : const CPLStringList aosMD(metadata);
1213 102 : for (const auto [key, value] : cpl::IterateNameValue(aosMD))
1214 : {
1215 17 : SetMetadataItem(key, value);
1216 : }
1217 85 : }
1218 :
1219 178 : const OGRSpatialReference *GetSpatialRef() const override
1220 : {
1221 178 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
1222 : }
1223 :
1224 188 : CPLErr GetGeoTransform(GDALGeoTransform >) const override
1225 : {
1226 188 : gt = m_gt;
1227 188 : return CE_None;
1228 : }
1229 :
1230 : using GDALDataset::Clone;
1231 :
1232 24 : std::unique_ptr<MosaicDataset> Clone() const
1233 : {
1234 : return std::make_unique<MosaicDataset>(
1235 24 : m_directory, m_extension, m_format, m_aeColorInterp, m_oTM, m_oSRS,
1236 24 : m_nTileMinX, m_nTileMinY, m_nTileMaxX, m_nTileMaxY, m_convention,
1237 24 : nBands, m_eDT, m_pdfDstNoData, m_metadata, m_poCT,
1238 24 : m_nMaxCacheTileSize);
1239 : }
1240 : };
1241 :
1242 : /************************************************************************/
1243 : /* MosaicRasterBand::IReadBlock() */
1244 : /************************************************************************/
1245 :
1246 3559 : CPLErr MosaicRasterBand::IReadBlock(int nXBlock, int nYBlock, void *pData)
1247 : {
1248 3559 : auto poThisDS = cpl::down_cast<MosaicDataset *>(poDS);
1249 : std::string filename = CPLFormFilenameSafe(
1250 7111 : m_directory.c_str(), CPLSPrintf("%d", m_tileMinX + nXBlock), nullptr);
1251 3556 : const int iFileY = GetFileY(m_tileMinY + nYBlock, m_oTM, m_convention);
1252 7115 : filename = CPLFormFilenameSafe(filename.c_str(), CPLSPrintf("%d", iFileY),
1253 3560 : m_extension.c_str());
1254 :
1255 3559 : std::shared_ptr<GDALDataset> poTileDS;
1256 3560 : if (!poThisDS->m_oCacheTile.tryGet(filename, poTileDS))
1257 : {
1258 1025 : const char *const apszAllowedDrivers[] = {poThisDS->m_format.c_str(),
1259 1024 : nullptr};
1260 1024 : const char *const apszAllowedDriversForCOG[] = {"GTiff", "LIBERTIFF",
1261 : nullptr};
1262 : // CPLDebugOnly("gdal_raster_tile", "Opening %s", filename.c_str());
1263 1024 : poTileDS.reset(GDALDataset::Open(
1264 : filename.c_str(), GDAL_OF_RASTER | GDAL_OF_INTERNAL,
1265 1024 : EQUAL(poThisDS->m_format.c_str(), "COG") ? apszAllowedDriversForCOG
1266 : : apszAllowedDrivers));
1267 1021 : if (!poTileDS)
1268 : {
1269 : VSIStatBufL sStat;
1270 6 : if (VSIStatL(filename.c_str(), &sStat) == 0)
1271 : {
1272 1 : CPLError(CE_Failure, CPLE_AppDefined,
1273 : "File %s exists but cannot be opened with %s driver",
1274 : filename.c_str(), poThisDS->m_format.c_str());
1275 1 : return CE_Failure;
1276 : }
1277 : }
1278 1016 : poThisDS->m_oCacheTile.insert(filename, poTileDS);
1279 : }
1280 3558 : if (!poTileDS || nBand > poTileDS->GetRasterCount())
1281 : {
1282 1726 : memset(pData,
1283 859 : (poTileDS && (nBand == poTileDS->GetRasterCount() + 1)) ? 255
1284 : : 0,
1285 862 : static_cast<size_t>(nBlockXSize) * nBlockYSize *
1286 862 : GDALGetDataTypeSizeBytes(eDataType));
1287 867 : return CE_None;
1288 : }
1289 : else
1290 : {
1291 2691 : return poTileDS->GetRasterBand(nBand)->RasterIO(
1292 : GF_Read, 0, 0, nBlockXSize, nBlockYSize, pData, nBlockXSize,
1293 2691 : nBlockYSize, eDataType, 0, 0, nullptr);
1294 : }
1295 : }
1296 :
1297 : } // namespace
1298 :
1299 : /************************************************************************/
1300 : /* ApplySubstitutions() */
1301 : /************************************************************************/
1302 :
1303 77 : static void ApplySubstitutions(CPLString &s,
1304 : const std::map<std::string, std::string> &substs)
1305 : {
1306 1208 : for (const auto &[key, value] : substs)
1307 : {
1308 1131 : s.replaceAll("%(" + key + ")s", value);
1309 1131 : s.replaceAll("%(" + key + ")d", value);
1310 1131 : s.replaceAll("%(" + key + ")f", value);
1311 1131 : s.replaceAll("${" + key + "}", value);
1312 : }
1313 77 : }
1314 :
1315 : /************************************************************************/
1316 : /* GenerateLeaflet() */
1317 : /************************************************************************/
1318 :
1319 19 : static void GenerateLeaflet(const std::string &osDirectory,
1320 : const std::string &osTitle, double dfSouthLat,
1321 : double dfWestLon, double dfNorthLat,
1322 : double dfEastLon, int nMinZoom, int nMaxZoom,
1323 : int nTileSize, const std::string &osExtension,
1324 : const std::string &osURL,
1325 : const std::string &osCopyright, bool bXYZ)
1326 : {
1327 19 : if (const char *pszTemplate = CPLFindFile("gdal", "leaflet_template.html"))
1328 : {
1329 38 : const std::string osFilename(pszTemplate);
1330 38 : std::map<std::string, std::string> substs;
1331 :
1332 : // For tests
1333 : const char *pszFmt =
1334 19 : atoi(CPLGetConfigOption("GDAL_RASTER_TILE_HTML_PREC", "17")) == 10
1335 : ? "%.10g"
1336 19 : : "%.17g";
1337 :
1338 38 : substs["double_quote_escaped_title"] =
1339 57 : CPLString(osTitle).replaceAll('"', "\\\"");
1340 19 : char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
1341 19 : substs["xml_escaped_title"] = pszStr;
1342 19 : CPLFree(pszStr);
1343 19 : substs["south"] = CPLSPrintf(pszFmt, dfSouthLat);
1344 19 : substs["west"] = CPLSPrintf(pszFmt, dfWestLon);
1345 19 : substs["north"] = CPLSPrintf(pszFmt, dfNorthLat);
1346 19 : substs["east"] = CPLSPrintf(pszFmt, dfEastLon);
1347 19 : substs["centerlon"] = CPLSPrintf(pszFmt, (dfNorthLat + dfSouthLat) / 2);
1348 19 : substs["centerlat"] = CPLSPrintf(pszFmt, (dfWestLon + dfEastLon) / 2);
1349 19 : substs["minzoom"] = CPLSPrintf("%d", nMinZoom);
1350 19 : substs["maxzoom"] = CPLSPrintf("%d", nMaxZoom);
1351 19 : substs["beginzoom"] = CPLSPrintf("%d", nMaxZoom);
1352 19 : substs["tile_size"] = CPLSPrintf("%d", nTileSize); // not used
1353 19 : substs["tileformat"] = osExtension;
1354 19 : substs["publishurl"] = osURL; // not used
1355 19 : substs["copyright"] = CPLString(osCopyright).replaceAll('"', "\\\"");
1356 19 : substs["tms"] = bXYZ ? "0" : "1";
1357 :
1358 19 : GByte *pabyRet = nullptr;
1359 19 : CPL_IGNORE_RET_VAL(VSIIngestFile(nullptr, osFilename.c_str(), &pabyRet,
1360 : nullptr, 10 * 1024 * 1024));
1361 19 : if (pabyRet)
1362 : {
1363 38 : CPLString osHTML(reinterpret_cast<char *>(pabyRet));
1364 19 : CPLFree(pabyRet);
1365 :
1366 19 : ApplySubstitutions(osHTML, substs);
1367 :
1368 19 : VSILFILE *f = VSIFOpenL(CPLFormFilenameSafe(osDirectory.c_str(),
1369 : "leaflet.html", nullptr)
1370 : .c_str(),
1371 : "wb");
1372 19 : if (f)
1373 : {
1374 19 : VSIFWriteL(osHTML.data(), 1, osHTML.size(), f);
1375 19 : VSIFCloseL(f);
1376 : }
1377 : }
1378 : }
1379 19 : }
1380 :
1381 : /************************************************************************/
1382 : /* GenerateMapML() */
1383 : /************************************************************************/
1384 :
1385 : static void
1386 20 : GenerateMapML(const std::string &osDirectory, const std::string &mapmlTemplate,
1387 : const std::string &osTitle, int nMinTileX, int nMinTileY,
1388 : int nMaxTileX, int nMaxTileY, int nMinZoom, int nMaxZoom,
1389 : const std::string &osExtension, const std::string &osURL,
1390 : const std::string &osCopyright, const gdal::TileMatrixSet &tms)
1391 : {
1392 20 : if (const char *pszTemplate =
1393 20 : (mapmlTemplate.empty() ? CPLFindFile("gdal", "template_tiles.mapml")
1394 20 : : mapmlTemplate.c_str()))
1395 : {
1396 40 : const std::string osFilename(pszTemplate);
1397 40 : std::map<std::string, std::string> substs;
1398 :
1399 20 : if (tms.identifier() == "GoogleMapsCompatible")
1400 19 : substs["TILING_SCHEME"] = "OSMTILE";
1401 1 : else if (tms.identifier() == "WorldCRS84Quad")
1402 1 : substs["TILING_SCHEME"] = "WGS84";
1403 : else
1404 0 : substs["TILING_SCHEME"] = tms.identifier();
1405 :
1406 20 : substs["URL"] = osURL.empty() ? "./" : osURL;
1407 20 : substs["MINTILEX"] = CPLSPrintf("%d", nMinTileX);
1408 20 : substs["MINTILEY"] = CPLSPrintf("%d", nMinTileY);
1409 20 : substs["MAXTILEX"] = CPLSPrintf("%d", nMaxTileX);
1410 20 : substs["MAXTILEY"] = CPLSPrintf("%d", nMaxTileY);
1411 20 : substs["CURZOOM"] = CPLSPrintf("%d", nMaxZoom);
1412 20 : substs["MINZOOM"] = CPLSPrintf("%d", nMinZoom);
1413 20 : substs["MAXZOOM"] = CPLSPrintf("%d", nMaxZoom);
1414 20 : substs["TILEEXT"] = osExtension;
1415 20 : char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
1416 20 : substs["TITLE"] = pszStr;
1417 20 : CPLFree(pszStr);
1418 20 : substs["COPYRIGHT"] = osCopyright;
1419 :
1420 20 : GByte *pabyRet = nullptr;
1421 20 : CPL_IGNORE_RET_VAL(VSIIngestFile(nullptr, osFilename.c_str(), &pabyRet,
1422 : nullptr, 10 * 1024 * 1024));
1423 20 : if (pabyRet)
1424 : {
1425 40 : CPLString osMAPML(reinterpret_cast<char *>(pabyRet));
1426 20 : CPLFree(pabyRet);
1427 :
1428 20 : ApplySubstitutions(osMAPML, substs);
1429 :
1430 20 : VSILFILE *f = VSIFOpenL(
1431 40 : CPLFormFilenameSafe(osDirectory.c_str(), "mapml.mapml", nullptr)
1432 : .c_str(),
1433 : "wb");
1434 20 : if (f)
1435 : {
1436 20 : VSIFWriteL(osMAPML.data(), 1, osMAPML.size(), f);
1437 20 : VSIFCloseL(f);
1438 : }
1439 : }
1440 : }
1441 20 : }
1442 :
1443 : /************************************************************************/
1444 : /* GenerateSTAC() */
1445 : /************************************************************************/
1446 :
1447 : static void
1448 24 : GenerateSTAC(const std::string &osDirectory, const std::string &osTitle,
1449 : double dfWestLon, double dfSouthLat, double dfEastLon,
1450 : double dfNorthLat, const std::vector<std::string> &metadata,
1451 : const std::vector<BandMetadata> &aoBandMetadata, int nMinZoom,
1452 : int nMaxZoom, const std::string &osExtension,
1453 : const std::string &osFormat, const std::string &osURL,
1454 : const std::string &osCopyright, const OGRSpatialReference &oSRS,
1455 : const gdal::TileMatrixSet &tms, bool bInvertAxisTMS, int tileSize,
1456 : const double adfExtent[4], const GDALArgDatasetValue &dataset)
1457 : {
1458 48 : CPLJSONObject oRoot;
1459 24 : oRoot["stac_version"] = "1.1.0";
1460 48 : CPLJSONArray oExtensions;
1461 24 : oRoot["stac_extensions"] = oExtensions;
1462 24 : oRoot["id"] = osTitle;
1463 24 : oRoot["type"] = "Feature";
1464 24 : oRoot["bbox"] = {dfWestLon, dfSouthLat, dfEastLon, dfNorthLat};
1465 48 : CPLJSONObject oGeometry;
1466 :
1467 24 : const auto BuildPolygon = [](double x1, double y1, double x2, double y2)
1468 : {
1469 : return CPLJSONArray::Build({CPLJSONArray::Build(
1470 : {CPLJSONArray::Build({x1, y1}), CPLJSONArray::Build({x1, y2}),
1471 : CPLJSONArray::Build({x2, y2}), CPLJSONArray::Build({x2, y1}),
1472 168 : CPLJSONArray::Build({x1, y1})})});
1473 : };
1474 :
1475 24 : if (dfWestLon <= dfEastLon)
1476 : {
1477 24 : oGeometry["type"] = "Polygon";
1478 : oGeometry["coordinates"] =
1479 24 : BuildPolygon(dfWestLon, dfSouthLat, dfEastLon, dfNorthLat);
1480 : }
1481 : else
1482 : {
1483 0 : oGeometry["type"] = "MultiPolygon";
1484 0 : oGeometry["coordinates"] = {
1485 : BuildPolygon(dfWestLon, dfSouthLat, 180.0, dfNorthLat),
1486 0 : BuildPolygon(-180.0, dfSouthLat, dfEastLon, dfNorthLat)};
1487 : }
1488 24 : oRoot["geometry"] = std::move(oGeometry);
1489 :
1490 48 : CPLJSONObject oProperties;
1491 24 : oRoot["properties"] = oProperties;
1492 48 : const CPLStringList aosMD(metadata);
1493 48 : std::string osDateTime = "1970-01-01T00:00:00.000Z";
1494 24 : if (!dataset.GetName().empty())
1495 : {
1496 : VSIStatBufL sStat;
1497 24 : if (VSIStatL(dataset.GetName().c_str(), &sStat) == 0 &&
1498 12 : sStat.st_mtime != 0)
1499 : {
1500 : struct tm tm;
1501 12 : CPLUnixTimeToYMDHMS(sStat.st_mtime, &tm);
1502 : osDateTime = CPLSPrintf(
1503 12 : "%04d-%02d-%02dT%02d:%02d:%02dZ", tm.tm_year + 1900,
1504 12 : tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
1505 : }
1506 : }
1507 48 : std::string osStartDateTime = "0001-01-01T00:00:00.000Z";
1508 48 : std::string osEndDateTime = "9999-12-31T23:59:59.999Z";
1509 :
1510 0 : const auto GetDateTimeAsISO8211 = [](const char *pszInput)
1511 : {
1512 0 : std::string osRet;
1513 : OGRField sField;
1514 0 : if (OGRParseDate(pszInput, &sField, 0))
1515 : {
1516 0 : char *pszDT = OGRGetXMLDateTime(&sField);
1517 0 : if (pszDT)
1518 0 : osRet = pszDT;
1519 0 : CPLFree(pszDT);
1520 : }
1521 0 : return osRet;
1522 : };
1523 :
1524 25 : for (const auto &[key, value] : cpl::IterateNameValue(aosMD))
1525 : {
1526 1 : if (EQUAL(key, "datetime"))
1527 : {
1528 0 : std::string osTmp = GetDateTimeAsISO8211(value);
1529 0 : if (!osTmp.empty())
1530 : {
1531 0 : osDateTime = std::move(osTmp);
1532 0 : continue;
1533 : }
1534 : }
1535 1 : else if (EQUAL(key, "start_datetime"))
1536 : {
1537 0 : std::string osTmp = GetDateTimeAsISO8211(value);
1538 0 : if (!osTmp.empty())
1539 : {
1540 0 : osStartDateTime = std::move(osTmp);
1541 0 : continue;
1542 : }
1543 : }
1544 1 : else if (EQUAL(key, "end_datetime"))
1545 : {
1546 0 : std::string osTmp = GetDateTimeAsISO8211(value);
1547 0 : if (!osTmp.empty())
1548 : {
1549 0 : osEndDateTime = std::move(osTmp);
1550 0 : continue;
1551 : }
1552 : }
1553 1 : else if (EQUAL(key, "TIFFTAG_DATETIME"))
1554 : {
1555 : int nYear, nMonth, nDay, nHour, nMin, nSec;
1556 0 : if (sscanf(value, "%04d:%02d:%02d %02d:%02d:%02d", &nYear, &nMonth,
1557 0 : &nDay, &nHour, &nMin, &nSec) == 6)
1558 : {
1559 : osDateTime = CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%02dZ", nYear,
1560 0 : nMonth, nDay, nHour, nMin, nSec);
1561 0 : continue;
1562 : }
1563 : }
1564 :
1565 1 : oProperties[key] = value;
1566 : }
1567 24 : oProperties["datetime"] = osDateTime;
1568 24 : oProperties["start_datetime"] = osStartDateTime;
1569 24 : oProperties["end_datetime"] = osEndDateTime;
1570 24 : if (!osCopyright.empty())
1571 0 : oProperties["copyright"] = osCopyright;
1572 :
1573 : // Just keep the tile matrix zoom levels we use
1574 48 : gdal::TileMatrixSet tmsLimitedToZoomLevelUsed(tms);
1575 24 : auto &tileMatrixList = tmsLimitedToZoomLevelUsed.tileMatrixList();
1576 24 : tileMatrixList.erase(tileMatrixList.begin() + nMaxZoom + 1,
1577 48 : tileMatrixList.end());
1578 24 : tileMatrixList.erase(tileMatrixList.begin(),
1579 48 : tileMatrixList.begin() + nMinZoom);
1580 :
1581 48 : CPLJSONObject oLimits;
1582 : // Patch their definition with the potentially overridden tileSize.
1583 81 : for (auto &tm : tileMatrixList)
1584 : {
1585 57 : int nOvrMinTileX = 0;
1586 57 : int nOvrMinTileY = 0;
1587 57 : int nOvrMaxTileX = 0;
1588 57 : int nOvrMaxTileY = 0;
1589 57 : bool bIntersects = false;
1590 57 : CPL_IGNORE_RET_VAL(GetTileIndices(
1591 : tm, bInvertAxisTMS, tileSize, adfExtent, nOvrMinTileX, nOvrMinTileY,
1592 : nOvrMaxTileX, nOvrMaxTileY, /* noIntersectionIsOK = */ true,
1593 : bIntersects));
1594 :
1595 57 : CPLJSONObject oLimit;
1596 57 : oLimit["min_tile_col"] = nOvrMinTileX;
1597 57 : oLimit["max_tile_col"] = nOvrMaxTileX;
1598 57 : oLimit["min_tile_row"] = nOvrMinTileY;
1599 57 : oLimit["max_tile_row"] = nOvrMaxTileY;
1600 57 : oLimits[tm.mId] = std::move(oLimit);
1601 : }
1602 :
1603 48 : CPLJSONObject oTilesTileMatrixSets;
1604 : {
1605 24 : CPLJSONDocument oDoc;
1606 24 : CPL_IGNORE_RET_VAL(
1607 24 : oDoc.LoadMemory(tmsLimitedToZoomLevelUsed.exportToTMSJsonV1()));
1608 : oTilesTileMatrixSets[tmsLimitedToZoomLevelUsed.identifier()] =
1609 24 : oDoc.GetRoot();
1610 : }
1611 24 : oProperties["tiles:tile_matrix_sets"] = std::move(oTilesTileMatrixSets);
1612 :
1613 48 : CPLJSONObject oTilesTileMatrixLinks;
1614 48 : CPLJSONObject oTilesTileMatrixLink;
1615 : oTilesTileMatrixLink["url"] =
1616 24 : std::string("#").append(tmsLimitedToZoomLevelUsed.identifier());
1617 24 : oTilesTileMatrixLink["limits"] = std::move(oLimits);
1618 : oTilesTileMatrixLinks[tmsLimitedToZoomLevelUsed.identifier()] =
1619 24 : std::move(oTilesTileMatrixLink);
1620 24 : oProperties["tiles:tile_matrix_links"] = std::move(oTilesTileMatrixLinks);
1621 :
1622 24 : const char *pszAuthName = oSRS.GetAuthorityName(nullptr);
1623 24 : const char *pszAuthCode = oSRS.GetAuthorityCode(nullptr);
1624 24 : if (pszAuthName && pszAuthCode)
1625 : {
1626 23 : oProperties["proj:code"] =
1627 23 : std::string(pszAuthName).append(":").append(pszAuthCode);
1628 : }
1629 : else
1630 : {
1631 1 : char *pszPROJJSON = nullptr;
1632 1 : CPL_IGNORE_RET_VAL(oSRS.exportToPROJJSON(&pszPROJJSON, nullptr));
1633 1 : if (pszPROJJSON)
1634 : {
1635 0 : CPLJSONDocument oDoc;
1636 0 : CPL_IGNORE_RET_VAL(oDoc.LoadMemory(pszPROJJSON));
1637 0 : CPLFree(pszPROJJSON);
1638 0 : oProperties["proj:projjson"] = oDoc.GetRoot();
1639 : }
1640 : }
1641 : {
1642 24 : auto ovrTileMatrix = tms.tileMatrixList()[nMaxZoom];
1643 24 : int nOvrMinTileX = 0;
1644 24 : int nOvrMinTileY = 0;
1645 24 : int nOvrMaxTileX = 0;
1646 24 : int nOvrMaxTileY = 0;
1647 24 : bool bIntersects = false;
1648 24 : CPL_IGNORE_RET_VAL(GetTileIndices(
1649 : ovrTileMatrix, bInvertAxisTMS, tileSize, adfExtent, nOvrMinTileX,
1650 : nOvrMinTileY, nOvrMaxTileX, nOvrMaxTileY,
1651 : /* noIntersectionIsOK = */ true, bIntersects));
1652 0 : oProperties["proj:shape"] = {
1653 24 : (nOvrMaxTileY - nOvrMinTileY + 1) * ovrTileMatrix.mTileHeight,
1654 24 : (nOvrMaxTileX - nOvrMinTileX + 1) * ovrTileMatrix.mTileWidth};
1655 :
1656 0 : oProperties["proj:transform"] = {
1657 24 : ovrTileMatrix.mResX,
1658 : 0.0,
1659 24 : ovrTileMatrix.mTopLeftX +
1660 24 : nOvrMinTileX * ovrTileMatrix.mTileWidth * ovrTileMatrix.mResX,
1661 : 0.0,
1662 24 : -ovrTileMatrix.mResY,
1663 24 : ovrTileMatrix.mTopLeftY +
1664 24 : nOvrMinTileY * ovrTileMatrix.mTileHeight * ovrTileMatrix.mResY,
1665 : 0.0,
1666 : 0.0,
1667 24 : 0.0};
1668 : }
1669 :
1670 24 : constexpr const char *ASSET_NAME = "bands";
1671 :
1672 48 : CPLJSONObject oAssetTemplates;
1673 24 : oRoot["asset_templates"] = oAssetTemplates;
1674 :
1675 48 : CPLJSONObject oAssetTemplate;
1676 24 : oAssetTemplates[ASSET_NAME] = oAssetTemplate;
1677 :
1678 48 : std::string osHref = (osURL.empty() ? std::string(".") : std::string(osURL))
1679 24 : .append("/{TileMatrix}/{TileCol}/{TileRow}.")
1680 48 : .append(osExtension);
1681 :
1682 : const std::map<std::string, std::string> oMapVSIToURIPrefix = {
1683 : {"vsis3", "s3://"},
1684 : {"vsigs", "gs://"},
1685 : {"vsiaz", "az://"}, // Not universally recognized
1686 144 : };
1687 :
1688 : const CPLStringList aosSplitHref(
1689 48 : CSLTokenizeString2(osHref.c_str(), "/", 0));
1690 24 : if (!aosSplitHref.empty())
1691 : {
1692 24 : const auto oIter = oMapVSIToURIPrefix.find(aosSplitHref[0]);
1693 24 : if (oIter != oMapVSIToURIPrefix.end())
1694 : {
1695 : // +2 because of 2 slash characters
1696 0 : osHref = std::string(oIter->second)
1697 0 : .append(osHref.c_str() + strlen(aosSplitHref[0]) + 2);
1698 : }
1699 : }
1700 24 : oAssetTemplate["href"] = osHref;
1701 :
1702 24 : if (EQUAL(osFormat.c_str(), "COG"))
1703 : oAssetTemplate["type"] =
1704 0 : "image/tiff; application=geotiff; profile=cloud-optimized";
1705 24 : else if (osExtension == "tif")
1706 2 : oAssetTemplate["type"] = "image/tiff; application=geotiff";
1707 22 : else if (osExtension == "png")
1708 22 : oAssetTemplate["type"] = "image/png";
1709 0 : else if (osExtension == "jpg")
1710 0 : oAssetTemplate["type"] = "image/jpeg";
1711 0 : else if (osExtension == "webp")
1712 0 : oAssetTemplate["type"] = "image/webp";
1713 :
1714 : const std::map<GDALDataType, const char *> oMapDTToStac = {
1715 : {GDT_Int8, "int8"},
1716 : {GDT_Int16, "int16"},
1717 : {GDT_Int32, "int32"},
1718 : {GDT_Int64, "int64"},
1719 : {GDT_Byte, "uint8"},
1720 : {GDT_UInt16, "uint16"},
1721 : {GDT_UInt32, "uint32"},
1722 : {GDT_UInt64, "uint64"},
1723 : // float16: 16-bit float; unhandled
1724 : {GDT_Float32, "float32"},
1725 : {GDT_Float64, "float64"},
1726 : {GDT_CInt16, "cint16"},
1727 : {GDT_CInt32, "cint32"},
1728 : // cfloat16: complex 16-bit float; unhandled
1729 : {GDT_CFloat32, "cfloat32"},
1730 : {GDT_CFloat64, "cfloat64"},
1731 48 : };
1732 :
1733 48 : CPLJSONArray oBands;
1734 24 : int iBand = 1;
1735 24 : bool bEOExtensionUsed = false;
1736 78 : for (const auto &bandMD : aoBandMetadata)
1737 : {
1738 108 : CPLJSONObject oBand;
1739 54 : oBand["name"] = bandMD.osDescription.empty()
1740 108 : ? std::string(CPLSPrintf("Band%d", iBand))
1741 54 : : bandMD.osDescription;
1742 :
1743 54 : const auto oIter = oMapDTToStac.find(bandMD.eDT);
1744 54 : if (oIter != oMapDTToStac.end())
1745 54 : oBand["data_type"] = oIter->second;
1746 :
1747 54 : if (const char *pszCommonName =
1748 54 : GDALGetSTACCommonNameFromColorInterp(bandMD.eColorInterp))
1749 : {
1750 33 : bEOExtensionUsed = true;
1751 33 : oBand["eo:common_name"] = pszCommonName;
1752 : }
1753 54 : if (!bandMD.osCenterWaveLength.empty() && !bandMD.osFWHM.empty())
1754 : {
1755 0 : bEOExtensionUsed = true;
1756 : oBand["eo:center_wavelength"] =
1757 0 : CPLAtof(bandMD.osCenterWaveLength.c_str());
1758 0 : oBand["eo:full_width_half_max"] = CPLAtof(bandMD.osFWHM.c_str());
1759 : }
1760 54 : ++iBand;
1761 54 : oBands.Add(oBand);
1762 : }
1763 24 : oAssetTemplate["bands"] = oBands;
1764 :
1765 24 : oRoot.Add("assets", CPLJSONObject());
1766 24 : oRoot.Add("links", CPLJSONArray());
1767 :
1768 24 : oExtensions.Add(
1769 : "https://stac-extensions.github.io/tiled-assets/v1.0.0/schema.json");
1770 24 : oExtensions.Add(
1771 : "https://stac-extensions.github.io/projection/v2.0.0/schema.json");
1772 24 : if (bEOExtensionUsed)
1773 11 : oExtensions.Add(
1774 : "https://stac-extensions.github.io/eo/v2.0.0/schema.json");
1775 :
1776 : // Serialize JSON document to file
1777 : const std::string osJSON =
1778 48 : CPLString(oRoot.Format(CPLJSONObject::PrettyFormat::Pretty))
1779 72 : .replaceAll("\\/", '/');
1780 24 : VSILFILE *f = VSIFOpenL(
1781 48 : CPLFormFilenameSafe(osDirectory.c_str(), "stacta.json", nullptr)
1782 : .c_str(),
1783 : "wb");
1784 24 : if (f)
1785 : {
1786 24 : VSIFWriteL(osJSON.data(), 1, osJSON.size(), f);
1787 24 : VSIFCloseL(f);
1788 : }
1789 24 : }
1790 :
1791 : /************************************************************************/
1792 : /* GenerateOpenLayers() */
1793 : /************************************************************************/
1794 :
1795 27 : static void GenerateOpenLayers(
1796 : const std::string &osDirectory, const std::string &osTitle, double dfMinX,
1797 : double dfMinY, double dfMaxX, double dfMaxY, int nMinZoom, int nMaxZoom,
1798 : int nTileSize, const std::string &osExtension, const std::string &osURL,
1799 : const std::string &osCopyright, const gdal::TileMatrixSet &tms,
1800 : bool bInvertAxisTMS, const OGRSpatialReference &oSRS_TMS, bool bXYZ)
1801 : {
1802 54 : std::map<std::string, std::string> substs;
1803 :
1804 : // For tests
1805 : const char *pszFmt =
1806 27 : atoi(CPLGetConfigOption("GDAL_RASTER_TILE_HTML_PREC", "17")) == 10
1807 : ? "%.10g"
1808 27 : : "%.17g";
1809 :
1810 27 : char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
1811 27 : substs["xml_escaped_title"] = pszStr;
1812 27 : CPLFree(pszStr);
1813 27 : substs["ominx"] = CPLSPrintf(pszFmt, dfMinX);
1814 27 : substs["ominy"] = CPLSPrintf(pszFmt, dfMinY);
1815 27 : substs["omaxx"] = CPLSPrintf(pszFmt, dfMaxX);
1816 27 : substs["omaxy"] = CPLSPrintf(pszFmt, dfMaxY);
1817 27 : substs["center_x"] = CPLSPrintf(pszFmt, (dfMinX + dfMaxX) / 2);
1818 27 : substs["center_y"] = CPLSPrintf(pszFmt, (dfMinY + dfMaxY) / 2);
1819 27 : substs["minzoom"] = CPLSPrintf("%d", nMinZoom);
1820 27 : substs["maxzoom"] = CPLSPrintf("%d", nMaxZoom);
1821 27 : substs["tile_size"] = CPLSPrintf("%d", nTileSize);
1822 27 : substs["tileformat"] = osExtension;
1823 27 : substs["publishurl"] = osURL;
1824 27 : substs["copyright"] = osCopyright;
1825 27 : substs["sign_y"] = bXYZ ? "" : "-";
1826 :
1827 : CPLString s(R"raw(<!DOCTYPE html>
1828 : <html>
1829 : <head>
1830 : <title>%(xml_escaped_title)s</title>
1831 : <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
1832 : <meta http-equiv='imagetoolbar' content='no'/>
1833 : <style type="text/css"> v\:* {behavior:url(#default#VML);}
1834 : html, body { overflow: hidden; padding: 0; height: 100%; width: 100%; font-family: 'Lucida Grande',Geneva,Arial,Verdana,sans-serif; }
1835 : body { margin: 10px; background: #fff; }
1836 : h1 { margin: 0; padding: 6px; border:0; font-size: 20pt; }
1837 : #header { height: 43px; padding: 0; background-color: #eee; border: 1px solid #888; }
1838 : #subheader { height: 12px; text-align: right; font-size: 10px; color: #555;}
1839 : #map { height: 90%; border: 1px solid #888; }
1840 : </style>
1841 : <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">
1842 : <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@main/dist/en/v7.0.0/legacy/ol.js"></script>
1843 : <script src="https://unpkg.com/ol-layerswitcher@4.1.1"></script>
1844 : <link rel="stylesheet" href="https://unpkg.com/ol-layerswitcher@4.1.1/src/ol-layerswitcher.css" />
1845 : </head>
1846 : <body>
1847 : <div id="header"><h1>%(xml_escaped_title)s</h1></div>
1848 : <div id="subheader">Generated by <a href="https://gdal.org/programs/gdal_raster_tile.html">gdal raster tile</a> </div>
1849 : <div id="map" class="map"></div>
1850 : <div id="mouse-position"></div>
1851 : <script type="text/javascript">
1852 : var mousePositionControl = new ol.control.MousePosition({
1853 : className: 'custom-mouse-position',
1854 : target: document.getElementById('mouse-position'),
1855 : undefinedHTML: ' '
1856 : });
1857 : var map = new ol.Map({
1858 : controls: ol.control.defaults.defaults().extend([mousePositionControl]),
1859 54 : target: 'map',)raw");
1860 :
1861 35 : if (tms.identifier() == "GoogleMapsCompatible" ||
1862 8 : tms.identifier() == "WorldCRS84Quad")
1863 : {
1864 21 : s += R"raw(
1865 : layers: [
1866 : new ol.layer.Group({
1867 : title: 'Base maps',
1868 : layers: [
1869 : new ol.layer.Tile({
1870 : title: 'OpenStreetMap',
1871 : type: 'base',
1872 : visible: true,
1873 : source: new ol.source.OSM()
1874 : }),
1875 : ]
1876 : }),)raw";
1877 : }
1878 :
1879 27 : if (tms.identifier() == "GoogleMapsCompatible")
1880 : {
1881 19 : s += R"raw(new ol.layer.Group({
1882 : title: 'Overlay',
1883 : layers: [
1884 : new ol.layer.Tile({
1885 : title: 'Overlay',
1886 : // opacity: 0.7,
1887 : extent: [%(ominx)f, %(ominy)f,%(omaxx)f, %(omaxy)f],
1888 : source: new ol.source.XYZ({
1889 : attributions: '%(copyright)s',
1890 : minZoom: %(minzoom)d,
1891 : maxZoom: %(maxzoom)d,
1892 : url: './{z}/{x}/{%(sign_y)sy}.%(tileformat)s',
1893 : tileSize: [%(tile_size)d, %(tile_size)d]
1894 : })
1895 : }),
1896 : ]
1897 : }),)raw";
1898 : }
1899 8 : else if (tms.identifier() == "WorldCRS84Quad")
1900 : {
1901 2 : const double base_res = 180.0 / nTileSize;
1902 4 : std::string resolutions = "[";
1903 4 : for (int i = 0; i <= nMaxZoom; ++i)
1904 : {
1905 2 : if (i > 0)
1906 0 : resolutions += ",";
1907 2 : resolutions += CPLSPrintf(pszFmt, base_res / (1 << i));
1908 : }
1909 2 : resolutions += "]";
1910 2 : substs["resolutions"] = std::move(resolutions);
1911 :
1912 2 : if (bXYZ)
1913 : {
1914 1 : substs["origin"] = "[-180,90]";
1915 1 : substs["y_formula"] = "tileCoord[2]";
1916 : }
1917 : else
1918 : {
1919 1 : substs["origin"] = "[-180,-90]";
1920 1 : substs["y_formula"] = "- 1 - tileCoord[2]";
1921 : }
1922 :
1923 2 : s += R"raw(
1924 : new ol.layer.Group({
1925 : title: 'Overlay',
1926 : layers: [
1927 : new ol.layer.Tile({
1928 : title: 'Overlay',
1929 : // opacity: 0.7,
1930 : extent: [%(ominx)f, %(ominy)f,%(omaxx)f, %(omaxy)f],
1931 : source: new ol.source.TileImage({
1932 : attributions: '%(copyright)s',
1933 : projection: 'EPSG:4326',
1934 : minZoom: %(minzoom)d,
1935 : maxZoom: %(maxzoom)d,
1936 : tileGrid: new ol.tilegrid.TileGrid({
1937 : extent: [-180,-90,180,90],
1938 : origin: %(origin)s,
1939 : resolutions: %(resolutions)s,
1940 : tileSize: [%(tile_size)d, %(tile_size)d]
1941 : }),
1942 : tileUrlFunction: function(tileCoord) {
1943 : return ('./{z}/{x}/{y}.%(tileformat)s'
1944 : .replace('{z}', String(tileCoord[0]))
1945 : .replace('{x}', String(tileCoord[1]))
1946 : .replace('{y}', String(%(y_formula)s)));
1947 : },
1948 : })
1949 : }),
1950 : ]
1951 : }),)raw";
1952 : }
1953 : else
1954 : {
1955 12 : substs["maxres"] =
1956 12 : CPLSPrintf(pszFmt, tms.tileMatrixList()[nMinZoom].mResX);
1957 12 : std::string resolutions = "[";
1958 16 : for (int i = 0; i <= nMaxZoom; ++i)
1959 : {
1960 10 : if (i > 0)
1961 4 : resolutions += ",";
1962 10 : resolutions += CPLSPrintf(pszFmt, tms.tileMatrixList()[i].mResX);
1963 : }
1964 6 : resolutions += "]";
1965 6 : substs["resolutions"] = std::move(resolutions);
1966 :
1967 12 : std::string matrixsizes = "[";
1968 16 : for (int i = 0; i <= nMaxZoom; ++i)
1969 : {
1970 10 : if (i > 0)
1971 4 : matrixsizes += ",";
1972 : matrixsizes +=
1973 10 : CPLSPrintf("[%d,%d]", tms.tileMatrixList()[i].mMatrixWidth,
1974 20 : tms.tileMatrixList()[i].mMatrixHeight);
1975 : }
1976 6 : matrixsizes += "]";
1977 6 : substs["matrixsizes"] = std::move(matrixsizes);
1978 :
1979 6 : double dfTopLeftX = tms.tileMatrixList()[0].mTopLeftX;
1980 6 : double dfTopLeftY = tms.tileMatrixList()[0].mTopLeftY;
1981 6 : if (bInvertAxisTMS)
1982 0 : std::swap(dfTopLeftX, dfTopLeftY);
1983 :
1984 6 : if (bXYZ)
1985 : {
1986 10 : substs["origin"] =
1987 10 : CPLSPrintf("[%.17g,%.17g]", dfTopLeftX, dfTopLeftY);
1988 5 : substs["y_formula"] = "tileCoord[2]";
1989 : }
1990 : else
1991 : {
1992 2 : substs["origin"] = CPLSPrintf(
1993 : "[%.17g,%.17g]", dfTopLeftX,
1994 1 : dfTopLeftY - tms.tileMatrixList()[0].mResY *
1995 3 : tms.tileMatrixList()[0].mTileHeight);
1996 1 : substs["y_formula"] = "- 1 - tileCoord[2]";
1997 : }
1998 :
1999 12 : substs["tilegrid_extent"] =
2000 : CPLSPrintf("[%.17g,%.17g,%.17g,%.17g]", dfTopLeftX,
2001 6 : dfTopLeftY - tms.tileMatrixList()[0].mMatrixHeight *
2002 6 : tms.tileMatrixList()[0].mResY *
2003 6 : tms.tileMatrixList()[0].mTileHeight,
2004 6 : dfTopLeftX + tms.tileMatrixList()[0].mMatrixWidth *
2005 6 : tms.tileMatrixList()[0].mResX *
2006 6 : tms.tileMatrixList()[0].mTileWidth,
2007 24 : dfTopLeftY);
2008 :
2009 6 : s += R"raw(
2010 : layers: [
2011 : new ol.layer.Group({
2012 : title: 'Overlay',
2013 : layers: [
2014 : new ol.layer.Tile({
2015 : title: 'Overlay',
2016 : // opacity: 0.7,
2017 : extent: [%(ominx)f, %(ominy)f,%(omaxx)f, %(omaxy)f],
2018 : source: new ol.source.TileImage({
2019 : attributions: '%(copyright)s',
2020 : minZoom: %(minzoom)d,
2021 : maxZoom: %(maxzoom)d,
2022 : tileGrid: new ol.tilegrid.TileGrid({
2023 : extent: %(tilegrid_extent)s,
2024 : origin: %(origin)s,
2025 : resolutions: %(resolutions)s,
2026 : sizes: %(matrixsizes)s,
2027 : tileSize: [%(tile_size)d, %(tile_size)d]
2028 : }),
2029 : tileUrlFunction: function(tileCoord) {
2030 : return ('./{z}/{x}/{y}.%(tileformat)s'
2031 : .replace('{z}', String(tileCoord[0]))
2032 : .replace('{x}', String(tileCoord[1]))
2033 : .replace('{y}', String(%(y_formula)s)));
2034 : },
2035 : })
2036 : }),
2037 : ]
2038 : }),)raw";
2039 : }
2040 :
2041 27 : s += R"raw(
2042 : ],
2043 : view: new ol.View({
2044 : center: [%(center_x)f, %(center_y)f],)raw";
2045 :
2046 35 : if (tms.identifier() == "GoogleMapsCompatible" ||
2047 8 : tms.identifier() == "WorldCRS84Quad")
2048 : {
2049 21 : substs["view_zoom"] = substs["minzoom"];
2050 21 : if (tms.identifier() == "WorldCRS84Quad")
2051 : {
2052 2 : substs["view_zoom"] = CPLSPrintf("%d", nMinZoom + 1);
2053 : }
2054 :
2055 21 : s += R"raw(
2056 : zoom: %(view_zoom)d,)raw";
2057 : }
2058 : else
2059 : {
2060 6 : s += R"raw(
2061 : resolution: %(maxres)f,)raw";
2062 : }
2063 :
2064 27 : if (tms.identifier() == "WorldCRS84Quad")
2065 : {
2066 2 : s += R"raw(
2067 : projection: 'EPSG:4326',)raw";
2068 : }
2069 25 : else if (!oSRS_TMS.IsEmpty() && tms.identifier() != "GoogleMapsCompatible")
2070 : {
2071 5 : const char *pszAuthName = oSRS_TMS.GetAuthorityName(nullptr);
2072 5 : const char *pszAuthCode = oSRS_TMS.GetAuthorityCode(nullptr);
2073 5 : if (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "EPSG"))
2074 : {
2075 3 : substs["epsg_code"] = pszAuthCode;
2076 3 : if (oSRS_TMS.IsGeographic())
2077 : {
2078 1 : substs["units"] = "deg";
2079 : }
2080 : else
2081 : {
2082 2 : const char *pszUnits = "";
2083 2 : if (oSRS_TMS.GetLinearUnits(&pszUnits) == 1.0)
2084 2 : substs["units"] = "m";
2085 : else
2086 0 : substs["units"] = pszUnits;
2087 : }
2088 3 : s += R"raw(
2089 : projection: new ol.proj.Projection({code: 'EPSG:%(epsg_code)s', units:'%(units)s'}),)raw";
2090 : }
2091 : }
2092 :
2093 27 : s += R"raw(
2094 : })
2095 : });)raw";
2096 :
2097 35 : if (tms.identifier() == "GoogleMapsCompatible" ||
2098 8 : tms.identifier() == "WorldCRS84Quad")
2099 : {
2100 21 : s += R"raw(
2101 : map.addControl(new ol.control.LayerSwitcher());)raw";
2102 : }
2103 :
2104 27 : s += R"raw(
2105 : </script>
2106 : </body>
2107 : </html>)raw";
2108 :
2109 27 : ApplySubstitutions(s, substs);
2110 :
2111 27 : VSILFILE *f = VSIFOpenL(
2112 54 : CPLFormFilenameSafe(osDirectory.c_str(), "openlayers.html", nullptr)
2113 : .c_str(),
2114 : "wb");
2115 27 : if (f)
2116 : {
2117 27 : VSIFWriteL(s.data(), 1, s.size(), f);
2118 27 : VSIFCloseL(f);
2119 : }
2120 27 : }
2121 :
2122 : /************************************************************************/
2123 : /* GetTileBoundingBox() */
2124 : /************************************************************************/
2125 :
2126 6 : static void GetTileBoundingBox(int nTileX, int nTileY, int nTileZ,
2127 : const gdal::TileMatrixSet *poTMS,
2128 : bool bInvertAxisTMS,
2129 : OGRCoordinateTransformation *poCTToWGS84,
2130 : double &dfTLX, double &dfTLY, double &dfTRX,
2131 : double &dfTRY, double &dfLLX, double &dfLLY,
2132 : double &dfLRX, double &dfLRY)
2133 : {
2134 : gdal::TileMatrixSet::TileMatrix tileMatrix =
2135 12 : poTMS->tileMatrixList()[nTileZ];
2136 6 : if (bInvertAxisTMS)
2137 0 : std::swap(tileMatrix.mTopLeftX, tileMatrix.mTopLeftY);
2138 :
2139 6 : dfTLX = tileMatrix.mTopLeftX +
2140 6 : nTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
2141 6 : dfTLY = tileMatrix.mTopLeftY -
2142 6 : nTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
2143 6 : poCTToWGS84->Transform(1, &dfTLX, &dfTLY);
2144 :
2145 6 : dfTRX = tileMatrix.mTopLeftX +
2146 6 : (nTileX + 1) * tileMatrix.mResX * tileMatrix.mTileWidth;
2147 6 : dfTRY = tileMatrix.mTopLeftY -
2148 6 : nTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
2149 6 : poCTToWGS84->Transform(1, &dfTRX, &dfTRY);
2150 :
2151 6 : dfLLX = tileMatrix.mTopLeftX +
2152 6 : nTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
2153 6 : dfLLY = tileMatrix.mTopLeftY -
2154 6 : (nTileY + 1) * tileMatrix.mResY * tileMatrix.mTileHeight;
2155 6 : poCTToWGS84->Transform(1, &dfLLX, &dfLLY);
2156 :
2157 6 : dfLRX = tileMatrix.mTopLeftX +
2158 6 : (nTileX + 1) * tileMatrix.mResX * tileMatrix.mTileWidth;
2159 6 : dfLRY = tileMatrix.mTopLeftY -
2160 6 : (nTileY + 1) * tileMatrix.mResY * tileMatrix.mTileHeight;
2161 6 : poCTToWGS84->Transform(1, &dfLRX, &dfLRY);
2162 6 : }
2163 :
2164 : /************************************************************************/
2165 : /* GenerateKML() */
2166 : /************************************************************************/
2167 :
2168 : namespace
2169 : {
2170 : struct TileCoordinates
2171 : {
2172 : int nTileX = 0;
2173 : int nTileY = 0;
2174 : int nTileZ = 0;
2175 : };
2176 : } // namespace
2177 :
2178 5 : static void GenerateKML(const std::string &osDirectory,
2179 : const std::string &osTitle, int nTileX, int nTileY,
2180 : int nTileZ, int nTileSize,
2181 : const std::string &osExtension,
2182 : const std::string &osURL,
2183 : const gdal::TileMatrixSet *poTMS, bool bInvertAxisTMS,
2184 : const std::string &convention,
2185 : OGRCoordinateTransformation *poCTToWGS84,
2186 : const std::vector<TileCoordinates> &children)
2187 : {
2188 10 : std::map<std::string, std::string> substs;
2189 :
2190 5 : const bool bIsTileKML = nTileX >= 0;
2191 :
2192 : // For tests
2193 : const char *pszFmt =
2194 5 : atoi(CPLGetConfigOption("GDAL_RASTER_TILE_KML_PREC", "14")) == 10
2195 : ? "%.10f"
2196 5 : : "%.14f";
2197 :
2198 5 : substs["tx"] = CPLSPrintf("%d", nTileX);
2199 5 : substs["tz"] = CPLSPrintf("%d", nTileZ);
2200 5 : substs["tileformat"] = osExtension;
2201 5 : substs["minlodpixels"] = CPLSPrintf("%d", nTileSize / 2);
2202 10 : substs["maxlodpixels"] =
2203 10 : children.empty() ? "-1" : CPLSPrintf("%d", nTileSize * 8);
2204 :
2205 5 : double dfTLX = 0;
2206 5 : double dfTLY = 0;
2207 5 : double dfTRX = 0;
2208 5 : double dfTRY = 0;
2209 5 : double dfLLX = 0;
2210 5 : double dfLLY = 0;
2211 5 : double dfLRX = 0;
2212 5 : double dfLRY = 0;
2213 :
2214 5 : int nFileY = -1;
2215 5 : if (!bIsTileKML)
2216 : {
2217 2 : char *pszStr = CPLEscapeString(osTitle.c_str(), -1, CPLES_XML);
2218 2 : substs["xml_escaped_title"] = pszStr;
2219 2 : CPLFree(pszStr);
2220 : }
2221 : else
2222 : {
2223 3 : nFileY = GetFileY(nTileY, poTMS->tileMatrixList()[nTileZ], convention);
2224 3 : substs["realtiley"] = CPLSPrintf("%d", nFileY);
2225 6 : substs["xml_escaped_title"] =
2226 6 : CPLSPrintf("%d/%d/%d.kml", nTileZ, nTileX, nFileY);
2227 :
2228 3 : GetTileBoundingBox(nTileX, nTileY, nTileZ, poTMS, bInvertAxisTMS,
2229 : poCTToWGS84, dfTLX, dfTLY, dfTRX, dfTRY, dfLLX,
2230 : dfLLY, dfLRX, dfLRY);
2231 : }
2232 :
2233 10 : substs["drawOrder"] = CPLSPrintf("%d", nTileX == 0 ? 2 * nTileZ + 1
2234 4 : : nTileX > 0 ? 2 * nTileZ
2235 14 : : 0);
2236 :
2237 5 : substs["url"] = osURL.empty() && bIsTileKML ? "../../" : "";
2238 :
2239 5 : const bool bIsRectangle =
2240 5 : (dfTLX == dfLLX && dfTRX == dfLRX && dfTLY == dfTRY && dfLLY == dfLRY);
2241 5 : const bool bUseGXNamespace = bIsTileKML && !bIsRectangle;
2242 :
2243 10 : substs["xmlns_gx"] = bUseGXNamespace
2244 : ? " xmlns:gx=\"http://www.google.com/kml/ext/2.2\""
2245 10 : : "";
2246 :
2247 : CPLString s(R"raw(<?xml version="1.0" encoding="utf-8"?>
2248 : <kml xmlns="http://www.opengis.net/kml/2.2"%(xmlns_gx)s>
2249 : <Document>
2250 : <name>%(xml_escaped_title)s</name>
2251 : <description></description>
2252 : <Style>
2253 : <ListStyle id="hideChildren">
2254 : <listItemType>checkHideChildren</listItemType>
2255 : </ListStyle>
2256 : </Style>
2257 10 : )raw");
2258 5 : ApplySubstitutions(s, substs);
2259 :
2260 5 : if (bIsTileKML)
2261 : {
2262 : CPLString s2(R"raw( <Region>
2263 : <LatLonAltBox>
2264 : <north>%(north)f</north>
2265 : <south>%(south)f</south>
2266 : <east>%(east)f</east>
2267 : <west>%(west)f</west>
2268 : </LatLonAltBox>
2269 : <Lod>
2270 : <minLodPixels>%(minlodpixels)d</minLodPixels>
2271 : <maxLodPixels>%(maxlodpixels)d</maxLodPixels>
2272 : </Lod>
2273 : </Region>
2274 : <GroundOverlay>
2275 : <drawOrder>%(drawOrder)d</drawOrder>
2276 : <Icon>
2277 : <href>%(realtiley)d.%(tileformat)s</href>
2278 : </Icon>
2279 : <LatLonBox>
2280 : <north>%(north)f</north>
2281 : <south>%(south)f</south>
2282 : <east>%(east)f</east>
2283 : <west>%(west)f</west>
2284 : </LatLonBox>
2285 6 : )raw");
2286 :
2287 3 : if (!bIsRectangle)
2288 : {
2289 : s2 +=
2290 1 : R"raw( <gx:LatLonQuad><coordinates>%(LLX)f,%(LLY)f %(LRX)f,%(LRY)f %(TRX)f,%(TRY)f %(TLX)f,%(TLY)f</coordinates></gx:LatLonQuad>
2291 : )raw";
2292 : }
2293 :
2294 3 : s2 += R"raw( </GroundOverlay>
2295 : )raw";
2296 3 : substs["north"] = CPLSPrintf(pszFmt, std::max(dfTLY, dfTRY));
2297 3 : substs["south"] = CPLSPrintf(pszFmt, std::min(dfLLY, dfLRY));
2298 3 : substs["east"] = CPLSPrintf(pszFmt, std::max(dfTRX, dfLRX));
2299 3 : substs["west"] = CPLSPrintf(pszFmt, std::min(dfLLX, dfTLX));
2300 :
2301 3 : if (!bIsRectangle)
2302 : {
2303 1 : substs["TLX"] = CPLSPrintf(pszFmt, dfTLX);
2304 1 : substs["TLY"] = CPLSPrintf(pszFmt, dfTLY);
2305 1 : substs["TRX"] = CPLSPrintf(pszFmt, dfTRX);
2306 1 : substs["TRY"] = CPLSPrintf(pszFmt, dfTRY);
2307 1 : substs["LRX"] = CPLSPrintf(pszFmt, dfLRX);
2308 1 : substs["LRY"] = CPLSPrintf(pszFmt, dfLRY);
2309 1 : substs["LLX"] = CPLSPrintf(pszFmt, dfLLX);
2310 1 : substs["LLY"] = CPLSPrintf(pszFmt, dfLLY);
2311 : }
2312 :
2313 3 : ApplySubstitutions(s2, substs);
2314 3 : s += s2;
2315 : }
2316 :
2317 8 : for (const auto &child : children)
2318 : {
2319 3 : substs["tx"] = CPLSPrintf("%d", child.nTileX);
2320 3 : substs["tz"] = CPLSPrintf("%d", child.nTileZ);
2321 6 : substs["realtiley"] = CPLSPrintf(
2322 3 : "%d", GetFileY(child.nTileY, poTMS->tileMatrixList()[child.nTileZ],
2323 6 : convention));
2324 :
2325 3 : GetTileBoundingBox(child.nTileX, child.nTileY, child.nTileZ, poTMS,
2326 : bInvertAxisTMS, poCTToWGS84, dfTLX, dfTLY, dfTRX,
2327 : dfTRY, dfLLX, dfLLY, dfLRX, dfLRY);
2328 :
2329 : CPLString s2(R"raw( <NetworkLink>
2330 : <name>%(tz)d/%(tx)d/%(realtiley)d.%(tileformat)s</name>
2331 : <Region>
2332 : <LatLonAltBox>
2333 : <north>%(north)f</north>
2334 : <south>%(south)f</south>
2335 : <east>%(east)f</east>
2336 : <west>%(west)f</west>
2337 : </LatLonAltBox>
2338 : <Lod>
2339 : <minLodPixels>%(minlodpixels)d</minLodPixels>
2340 : <maxLodPixels>-1</maxLodPixels>
2341 : </Lod>
2342 : </Region>
2343 : <Link>
2344 : <href>%(url)s%(tz)d/%(tx)d/%(realtiley)d.kml</href>
2345 : <viewRefreshMode>onRegion</viewRefreshMode>
2346 : <viewFormat/>
2347 : </Link>
2348 : </NetworkLink>
2349 6 : )raw");
2350 3 : substs["north"] = CPLSPrintf(pszFmt, std::max(dfTLY, dfTRY));
2351 3 : substs["south"] = CPLSPrintf(pszFmt, std::min(dfLLY, dfLRY));
2352 3 : substs["east"] = CPLSPrintf(pszFmt, std::max(dfTRX, dfLRX));
2353 3 : substs["west"] = CPLSPrintf(pszFmt, std::min(dfLLX, dfTLX));
2354 3 : ApplySubstitutions(s2, substs);
2355 3 : s += s2;
2356 : }
2357 :
2358 5 : s += R"raw(</Document>
2359 : </kml>)raw";
2360 :
2361 10 : std::string osFilename(osDirectory);
2362 5 : if (!bIsTileKML)
2363 : {
2364 : osFilename =
2365 2 : CPLFormFilenameSafe(osFilename.c_str(), "doc.kml", nullptr);
2366 : }
2367 : else
2368 : {
2369 6 : osFilename = CPLFormFilenameSafe(osFilename.c_str(),
2370 3 : CPLSPrintf("%d", nTileZ), nullptr);
2371 6 : osFilename = CPLFormFilenameSafe(osFilename.c_str(),
2372 3 : CPLSPrintf("%d", nTileX), nullptr);
2373 6 : osFilename = CPLFormFilenameSafe(osFilename.c_str(),
2374 3 : CPLSPrintf("%d.kml", nFileY), nullptr);
2375 : }
2376 :
2377 5 : VSILFILE *f = VSIFOpenL(osFilename.c_str(), "wb");
2378 5 : if (f)
2379 : {
2380 5 : VSIFWriteL(s.data(), 1, s.size(), f);
2381 5 : VSIFCloseL(f);
2382 : }
2383 5 : }
2384 :
2385 : namespace
2386 : {
2387 :
2388 : /************************************************************************/
2389 : /* ResourceManager */
2390 : /************************************************************************/
2391 :
2392 : // Generic cache managing resources
2393 : template <class Resource> class ResourceManager /* non final */
2394 : {
2395 : public:
2396 136 : virtual ~ResourceManager() = default;
2397 :
2398 48 : std::unique_ptr<Resource> AcquireResources()
2399 : {
2400 96 : std::lock_guard oLock(m_oMutex);
2401 48 : if (!m_oResources.empty())
2402 : {
2403 0 : auto ret = std::move(m_oResources.back());
2404 0 : m_oResources.pop_back();
2405 0 : return ret;
2406 : }
2407 :
2408 48 : return CreateResources();
2409 : }
2410 :
2411 48 : void ReleaseResources(std::unique_ptr<Resource> resources)
2412 : {
2413 96 : std::lock_guard oLock(m_oMutex);
2414 48 : m_oResources.push_back(std::move(resources));
2415 48 : }
2416 :
2417 0 : void SetError()
2418 : {
2419 0 : std::lock_guard oLock(m_oMutex);
2420 0 : if (m_errorMsg.empty())
2421 0 : m_errorMsg = CPLGetLastErrorMsg();
2422 0 : }
2423 :
2424 12 : const std::string &GetErrorMsg() const
2425 : {
2426 12 : std::lock_guard oLock(m_oMutex);
2427 24 : return m_errorMsg;
2428 : }
2429 :
2430 : protected:
2431 : virtual std::unique_ptr<Resource> CreateResources() = 0;
2432 :
2433 : private:
2434 : mutable std::mutex m_oMutex{};
2435 : std::vector<std::unique_ptr<Resource>> m_oResources{};
2436 : std::string m_errorMsg{};
2437 : };
2438 :
2439 : /************************************************************************/
2440 : /* PerThreadMaxZoomResources */
2441 : /************************************************************************/
2442 :
2443 : // Per-thread resources for generation of tiles at full resolution
2444 : struct PerThreadMaxZoomResources
2445 : {
2446 : struct GDALDatasetReleaser
2447 : {
2448 24 : void operator()(GDALDataset *poDS)
2449 : {
2450 24 : if (poDS)
2451 24 : poDS->ReleaseRef();
2452 24 : }
2453 : };
2454 :
2455 : std::unique_ptr<GDALDataset, GDALDatasetReleaser> poSrcDS{};
2456 : std::vector<GByte> dstBuffer{};
2457 : std::unique_ptr<FakeMaxZoomDataset> poFakeMaxZoomDS{};
2458 : std::unique_ptr<void, decltype(&GDALDestroyTransformer)> poTransformer{
2459 : nullptr, GDALDestroyTransformer};
2460 : std::unique_ptr<GDALWarpOperation> poWO{};
2461 : };
2462 :
2463 : /************************************************************************/
2464 : /* PerThreadMaxZoomResourceManager */
2465 : /************************************************************************/
2466 :
2467 : // Manage a cache of PerThreadMaxZoomResources instances
2468 : class PerThreadMaxZoomResourceManager final
2469 : : public ResourceManager<PerThreadMaxZoomResources>
2470 : {
2471 : public:
2472 75 : PerThreadMaxZoomResourceManager(GDALDataset *poSrcDS,
2473 : const GDALWarpOptions *psWO,
2474 : void *pTransformerArg,
2475 : const FakeMaxZoomDataset &oFakeMaxZoomDS,
2476 : size_t nBufferSize)
2477 75 : : m_poSrcDS(poSrcDS), m_psWOSource(psWO),
2478 : m_pTransformerArg(pTransformerArg), m_oFakeMaxZoomDS(oFakeMaxZoomDS),
2479 75 : m_nBufferSize(nBufferSize)
2480 : {
2481 75 : }
2482 :
2483 : protected:
2484 24 : std::unique_ptr<PerThreadMaxZoomResources> CreateResources() override
2485 : {
2486 48 : auto ret = std::make_unique<PerThreadMaxZoomResources>();
2487 :
2488 24 : ret->poSrcDS.reset(GDALGetThreadSafeDataset(m_poSrcDS, GDAL_OF_RASTER));
2489 24 : if (!ret->poSrcDS)
2490 0 : return nullptr;
2491 :
2492 : try
2493 : {
2494 24 : ret->dstBuffer.resize(m_nBufferSize);
2495 : }
2496 0 : catch (const std::exception &)
2497 : {
2498 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
2499 : "Out of memory allocating temporary buffer");
2500 0 : return nullptr;
2501 : }
2502 :
2503 24 : ret->poFakeMaxZoomDS = m_oFakeMaxZoomDS.Clone(ret->dstBuffer);
2504 :
2505 24 : ret->poTransformer.reset(GDALCloneTransformer(m_pTransformerArg));
2506 24 : if (!ret->poTransformer)
2507 0 : return nullptr;
2508 :
2509 : auto psWO =
2510 : std::unique_ptr<GDALWarpOptions, decltype(&GDALDestroyWarpOptions)>(
2511 48 : GDALCloneWarpOptions(m_psWOSource), GDALDestroyWarpOptions);
2512 24 : if (!psWO)
2513 0 : return nullptr;
2514 :
2515 24 : psWO->hSrcDS = GDALDataset::ToHandle(ret->poSrcDS.get());
2516 24 : psWO->hDstDS = GDALDataset::ToHandle(ret->poFakeMaxZoomDS.get());
2517 24 : psWO->pTransformerArg = ret->poTransformer.get();
2518 24 : psWO->pfnTransformer = m_psWOSource->pfnTransformer;
2519 :
2520 24 : ret->poWO = std::make_unique<GDALWarpOperation>();
2521 24 : if (ret->poWO->Initialize(psWO.get()) != CE_None)
2522 0 : return nullptr;
2523 :
2524 24 : return ret;
2525 : }
2526 :
2527 : private:
2528 : GDALDataset *const m_poSrcDS;
2529 : const GDALWarpOptions *const m_psWOSource;
2530 : void *const m_pTransformerArg;
2531 : const FakeMaxZoomDataset &m_oFakeMaxZoomDS;
2532 : const size_t m_nBufferSize;
2533 :
2534 : CPL_DISALLOW_COPY_ASSIGN(PerThreadMaxZoomResourceManager)
2535 : };
2536 :
2537 : /************************************************************************/
2538 : /* PerThreadLowerZoomResources */
2539 : /************************************************************************/
2540 :
2541 : // Per-thread resources for generation of tiles at zoom level < max
2542 : struct PerThreadLowerZoomResources
2543 : {
2544 : std::unique_ptr<GDALDataset> poSrcDS{};
2545 : };
2546 :
2547 : /************************************************************************/
2548 : /* PerThreadLowerZoomResourceManager */
2549 : /************************************************************************/
2550 :
2551 : // Manage a cache of PerThreadLowerZoomResources instances
2552 : class PerThreadLowerZoomResourceManager final
2553 : : public ResourceManager<PerThreadLowerZoomResources>
2554 : {
2555 : public:
2556 61 : explicit PerThreadLowerZoomResourceManager(const MosaicDataset &oSrcDS)
2557 61 : : m_oSrcDS(oSrcDS)
2558 : {
2559 61 : }
2560 :
2561 : protected:
2562 24 : std::unique_ptr<PerThreadLowerZoomResources> CreateResources() override
2563 : {
2564 24 : auto ret = std::make_unique<PerThreadLowerZoomResources>();
2565 24 : ret->poSrcDS = m_oSrcDS.Clone();
2566 24 : return ret;
2567 : }
2568 :
2569 : private:
2570 : const MosaicDataset &m_oSrcDS;
2571 : };
2572 :
2573 : } // namespace
2574 :
2575 : /************************************************************************/
2576 : /* GDALRasterTileAlgorithm::ValidateOutputFormat() */
2577 : /************************************************************************/
2578 :
2579 121 : bool GDALRasterTileAlgorithm::ValidateOutputFormat(GDALDataType eSrcDT) const
2580 : {
2581 121 : if (m_outputFormat == "PNG")
2582 : {
2583 97 : if (m_poSrcDS->GetRasterCount() > 4)
2584 : {
2585 1 : ReportError(CE_Failure, CPLE_NotSupported,
2586 : "Only up to 4 bands supported for PNG.");
2587 1 : return false;
2588 : }
2589 96 : if (eSrcDT != GDT_Byte && eSrcDT != GDT_UInt16)
2590 : {
2591 1 : ReportError(CE_Failure, CPLE_NotSupported,
2592 : "Only Byte and UInt16 data types supported for PNG.");
2593 1 : return false;
2594 : }
2595 : }
2596 24 : else if (m_outputFormat == "JPEG")
2597 : {
2598 5 : if (m_poSrcDS->GetRasterCount() > 4)
2599 : {
2600 1 : ReportError(
2601 : CE_Failure, CPLE_NotSupported,
2602 : "Only up to 4 bands supported for JPEG (with alpha ignored).");
2603 1 : return false;
2604 : }
2605 : const bool bUInt16Supported =
2606 4 : strstr(m_poDstDriver->GetMetadataItem(GDAL_DMD_CREATIONDATATYPES),
2607 4 : "UInt16");
2608 4 : if (eSrcDT != GDT_Byte && !(eSrcDT == GDT_UInt16 && bUInt16Supported))
2609 : {
2610 1 : ReportError(
2611 : CE_Failure, CPLE_NotSupported,
2612 : bUInt16Supported
2613 : ? "Only Byte and UInt16 data types supported for JPEG."
2614 : : "Only Byte data type supported for JPEG.");
2615 1 : return false;
2616 : }
2617 3 : if (eSrcDT == GDT_UInt16)
2618 : {
2619 3 : if (const char *pszNBITS =
2620 6 : m_poSrcDS->GetRasterBand(1)->GetMetadataItem(
2621 3 : "NBITS", "IMAGE_STRUCTURE"))
2622 : {
2623 1 : if (atoi(pszNBITS) > 12)
2624 : {
2625 1 : ReportError(CE_Failure, CPLE_NotSupported,
2626 : "JPEG output only supported up to 12 bits");
2627 1 : return false;
2628 : }
2629 : }
2630 : else
2631 : {
2632 2 : double adfMinMax[2] = {0, 0};
2633 2 : m_poSrcDS->GetRasterBand(1)->ComputeRasterMinMax(
2634 2 : /* bApproxOK = */ true, adfMinMax);
2635 2 : if (adfMinMax[1] >= (1 << 12))
2636 : {
2637 1 : ReportError(CE_Failure, CPLE_NotSupported,
2638 : "JPEG output only supported up to 12 bits");
2639 1 : return false;
2640 : }
2641 : }
2642 : }
2643 : }
2644 19 : else if (m_outputFormat == "WEBP")
2645 : {
2646 3 : if (m_poSrcDS->GetRasterCount() != 3 &&
2647 1 : m_poSrcDS->GetRasterCount() != 4)
2648 : {
2649 1 : ReportError(CE_Failure, CPLE_NotSupported,
2650 : "Only 3 or 4 bands supported for WEBP.");
2651 1 : return false;
2652 : }
2653 1 : if (eSrcDT != GDT_Byte)
2654 : {
2655 1 : ReportError(CE_Failure, CPLE_NotSupported,
2656 : "Only Byte data type supported for WEBP.");
2657 1 : return false;
2658 : }
2659 : }
2660 113 : return true;
2661 : }
2662 :
2663 : /************************************************************************/
2664 : /* GDALRasterTileAlgorithm::ComputeJobChunkSize() */
2665 : /************************************************************************/
2666 :
2667 : // Given a number of tiles in the Y dimension being nTilesPerCol and
2668 : // in the X dimension being nTilesPerRow, compute the (upper bound of)
2669 : // number of jobs needed to be nYOuterIterations x nXOuterIterations,
2670 : // with each job processing in average dfTilesYPerJob x dfTilesXPerJob
2671 : // tiles.
2672 : /* static */
2673 25 : void GDALRasterTileAlgorithm::ComputeJobChunkSize(
2674 : int nMaxJobCount, int nTilesPerCol, int nTilesPerRow,
2675 : double &dfTilesYPerJob, int &nYOuterIterations, double &dfTilesXPerJob,
2676 : int &nXOuterIterations)
2677 : {
2678 25 : CPLAssert(nMaxJobCount >= 1);
2679 25 : dfTilesYPerJob = static_cast<double>(nTilesPerCol) / nMaxJobCount;
2680 25 : nYOuterIterations = dfTilesYPerJob >= 1 ? nMaxJobCount : 1;
2681 :
2682 50 : dfTilesXPerJob = dfTilesYPerJob >= 1
2683 25 : ? nTilesPerRow
2684 7 : : static_cast<double>(nTilesPerRow) / nMaxJobCount;
2685 25 : nXOuterIterations = dfTilesYPerJob >= 1 ? 1 : nMaxJobCount;
2686 :
2687 25 : if (dfTilesYPerJob < 1 && dfTilesXPerJob < 1 &&
2688 7 : nTilesPerCol <= nMaxJobCount / nTilesPerRow)
2689 : {
2690 7 : dfTilesYPerJob = 1;
2691 7 : dfTilesXPerJob = 1;
2692 7 : nYOuterIterations = nTilesPerCol;
2693 7 : nXOuterIterations = nTilesPerRow;
2694 : }
2695 25 : }
2696 :
2697 : /************************************************************************/
2698 : /* GDALRasterTileAlgorithm::AddArgToArgv() */
2699 : /************************************************************************/
2700 :
2701 144 : bool GDALRasterTileAlgorithm::AddArgToArgv(const GDALAlgorithmArg *arg,
2702 : CPLStringList &aosArgv) const
2703 : {
2704 144 : aosArgv.push_back(CPLSPrintf("--%s", arg->GetName().c_str()));
2705 144 : if (arg->GetType() == GAAT_STRING)
2706 : {
2707 50 : aosArgv.push_back(arg->Get<std::string>().c_str());
2708 : }
2709 94 : else if (arg->GetType() == GAAT_STRING_LIST)
2710 : {
2711 12 : bool bFirst = true;
2712 24 : for (const std::string &s : arg->Get<std::vector<std::string>>())
2713 : {
2714 12 : if (!bFirst)
2715 : {
2716 0 : aosArgv.push_back(CPLSPrintf("--%s", arg->GetName().c_str()));
2717 : }
2718 12 : bFirst = false;
2719 12 : aosArgv.push_back(s.c_str());
2720 : }
2721 : }
2722 82 : else if (arg->GetType() == GAAT_REAL)
2723 : {
2724 0 : aosArgv.push_back(CPLSPrintf("%.17g", arg->Get<double>()));
2725 : }
2726 82 : else if (arg->GetType() == GAAT_INTEGER)
2727 : {
2728 82 : aosArgv.push_back(CPLSPrintf("%d", arg->Get<int>()));
2729 : }
2730 0 : else if (arg->GetType() != GAAT_BOOLEAN)
2731 : {
2732 0 : ReportError(CE_Failure, CPLE_AppDefined,
2733 : "Bug: argument of type %d not handled "
2734 : "by gdal raster tile!",
2735 0 : static_cast<int>(arg->GetType()));
2736 0 : return false;
2737 : }
2738 144 : return true;
2739 : }
2740 :
2741 : /************************************************************************/
2742 : /* GDALRasterTileAlgorithm::IsCompatibleOfSpawn() */
2743 : /************************************************************************/
2744 :
2745 13 : bool GDALRasterTileAlgorithm::IsCompatibleOfSpawn(const char *&pszErrorMsg)
2746 : {
2747 13 : pszErrorMsg = "";
2748 13 : if (!m_bIsNamedNonMemSrcDS)
2749 : {
2750 1 : pszErrorMsg = "Unnamed or memory dataset sources are not supported "
2751 : "with spawn parallelization method";
2752 1 : return false;
2753 : }
2754 12 : if (cpl::starts_with(m_outputDirectory, "/vsimem/"))
2755 : {
2756 4 : pszErrorMsg = "/vsimem/ output directory not supported with spawn "
2757 : "parallelization method";
2758 4 : return false;
2759 : }
2760 :
2761 8 : if (m_osGDALPath.empty())
2762 8 : m_osGDALPath = GDALGetGDALPath();
2763 8 : return !(m_osGDALPath.empty());
2764 : }
2765 :
2766 : /************************************************************************/
2767 : /* GetProgressForChildProcesses() */
2768 : /************************************************************************/
2769 :
2770 13 : static void GetProgressForChildProcesses(
2771 : bool &bRet, std::vector<CPLSpawnedProcess *> &ahSpawnedProcesses,
2772 : std::vector<uint64_t> &anRemainingTilesForProcess, uint64_t &nCurTile,
2773 : uint64_t nTotalTiles, GDALProgressFunc pfnProgress, void *pProgressData)
2774 : {
2775 13 : std::vector<unsigned int> anProgressState(ahSpawnedProcesses.size(), 0);
2776 13 : std::vector<unsigned int> anEndState(ahSpawnedProcesses.size(), 0);
2777 13 : std::vector<bool> abFinished(ahSpawnedProcesses.size(), false);
2778 :
2779 367 : while (bRet)
2780 : {
2781 367 : size_t iProcess = 0;
2782 367 : size_t nFinished = 0;
2783 1815 : for (CPLSpawnedProcess *hSpawnedProcess : ahSpawnedProcesses)
2784 : {
2785 1448 : char ch = 0;
2786 4344 : if (abFinished[iProcess] ||
2787 1448 : !CPLPipeRead(CPLSpawnAsyncGetInputFileHandle(hSpawnedProcess),
2788 1448 : &ch, 1))
2789 : {
2790 0 : ++nFinished;
2791 : }
2792 1448 : else if (ch == PROGRESS_MARKER[anProgressState[iProcess]])
2793 : {
2794 1014 : ++anProgressState[iProcess];
2795 1014 : if (anProgressState[iProcess] == sizeof(PROGRESS_MARKER))
2796 : {
2797 338 : anProgressState[iProcess] = 0;
2798 338 : --anRemainingTilesForProcess[iProcess];
2799 338 : ++nCurTile;
2800 338 : if (bRet && pfnProgress)
2801 : {
2802 84 : if (!pfnProgress(static_cast<double>(nCurTile) /
2803 84 : static_cast<double>(nTotalTiles),
2804 : "", pProgressData))
2805 : {
2806 0 : CPLError(CE_Failure, CPLE_UserInterrupt,
2807 : "Process interrupted by user");
2808 0 : bRet = false;
2809 0 : return;
2810 : }
2811 : }
2812 : }
2813 : }
2814 434 : else if (ch == END_MARKER[anEndState[iProcess]])
2815 : {
2816 350 : ++anEndState[iProcess];
2817 350 : if (anEndState[iProcess] == sizeof(END_MARKER))
2818 : {
2819 50 : anEndState[iProcess] = 0;
2820 50 : abFinished[iProcess] = true;
2821 50 : ++nFinished;
2822 : }
2823 : }
2824 : else
2825 : {
2826 84 : CPLErrorOnce(
2827 : CE_Warning, CPLE_AppDefined,
2828 : "Spurious character detected on stdout of child process");
2829 84 : anProgressState[iProcess] = 0;
2830 84 : if (ch == PROGRESS_MARKER[anProgressState[iProcess]])
2831 : {
2832 84 : ++anProgressState[iProcess];
2833 : }
2834 : }
2835 1448 : ++iProcess;
2836 : }
2837 367 : if (!bRet || nFinished == ahSpawnedProcesses.size())
2838 13 : break;
2839 : }
2840 : }
2841 :
2842 : /************************************************************************/
2843 : /* WaitForSpawnedProcesses() */
2844 : /************************************************************************/
2845 :
2846 13 : void GDALRasterTileAlgorithm::WaitForSpawnedProcesses(
2847 : bool &bRet, const std::vector<std::string> &asCommandLines,
2848 : std::vector<CPLSpawnedProcess *> &ahSpawnedProcesses) const
2849 : {
2850 13 : size_t iProcess = 0;
2851 63 : for (CPLSpawnedProcess *hSpawnedProcess : ahSpawnedProcesses)
2852 : {
2853 50 : CPL_IGNORE_RET_VAL(
2854 50 : CPLPipeWrite(CPLSpawnAsyncGetOutputFileHandle(hSpawnedProcess),
2855 : STOP_MARKER, static_cast<int>(strlen(STOP_MARKER))));
2856 :
2857 50 : char ch = 0;
2858 50 : std::string errorMsg;
2859 519 : while (CPLPipeRead(CPLSpawnAsyncGetErrorFileHandle(hSpawnedProcess),
2860 519 : &ch, 1))
2861 : {
2862 469 : if (ch == '\n')
2863 : {
2864 6 : if (!errorMsg.empty())
2865 : {
2866 6 : if (cpl::starts_with(errorMsg, "ERROR "))
2867 : {
2868 6 : const auto nPos = errorMsg.find(": ");
2869 6 : if (nPos != std::string::npos)
2870 6 : errorMsg = errorMsg.substr(nPos + 1);
2871 6 : ReportError(CE_Failure, CPLE_AppDefined, "%s",
2872 : errorMsg.c_str());
2873 : }
2874 : else
2875 : {
2876 0 : std::string osComp = "GDAL";
2877 0 : const auto nPos = errorMsg.find(": ");
2878 0 : if (nPos != std::string::npos)
2879 : {
2880 0 : osComp = errorMsg.substr(0, nPos);
2881 0 : errorMsg = errorMsg.substr(nPos + 1);
2882 : }
2883 0 : CPLDebug(osComp.c_str(), "%s", errorMsg.c_str());
2884 : }
2885 6 : errorMsg.clear();
2886 : }
2887 : }
2888 : else
2889 : {
2890 463 : errorMsg += ch;
2891 : }
2892 : }
2893 :
2894 50 : if (CPLSpawnAsyncFinish(hSpawnedProcess, /* bWait = */ true,
2895 50 : /* bKill = */ false) != 0)
2896 : {
2897 2 : bRet = false;
2898 2 : ReportError(CE_Failure, CPLE_AppDefined,
2899 : "Child process '%s' failed",
2900 2 : asCommandLines[iProcess].c_str());
2901 : }
2902 50 : ++iProcess;
2903 : }
2904 13 : }
2905 :
2906 : /************************************************************************/
2907 : /* GDALRasterTileAlgorithm::GetMaxChildCount() */
2908 : /**********************************f**************************************/
2909 :
2910 13 : int GDALRasterTileAlgorithm::GetMaxChildCount(int nMaxJobCount) const
2911 : {
2912 : #ifndef _WIN32
2913 : // Limit the number of jobs compared to how many file descriptors we have
2914 : // left
2915 : const int remainingFileDescriptorCount =
2916 13 : CPLGetRemainingFileDescriptorCount();
2917 13 : constexpr int SOME_MARGIN = 3;
2918 13 : constexpr int FD_PER_CHILD = 3; /* stdin, stdout and stderr */
2919 13 : if (FD_PER_CHILD * nMaxJobCount + SOME_MARGIN >
2920 : remainingFileDescriptorCount)
2921 : {
2922 0 : nMaxJobCount = std::max(
2923 0 : 1, (remainingFileDescriptorCount - SOME_MARGIN) / FD_PER_CHILD);
2924 0 : ReportError(
2925 : CE_Warning, CPLE_AppDefined,
2926 : "Limiting the number of child workers to %d (instead of %d), "
2927 : "because there are not enough file descriptors left (%d)",
2928 0 : nMaxJobCount, m_numThreads, remainingFileDescriptorCount);
2929 : }
2930 : #endif
2931 13 : return nMaxJobCount;
2932 : }
2933 :
2934 : /************************************************************************/
2935 : /* SendConfigOptions() */
2936 : /************************************************************************/
2937 :
2938 26 : static void SendConfigOptions(CPLSpawnedProcess *hSpawnedProcess, bool &bRet)
2939 : {
2940 : // Send most config options through pipe, to avoid leaking
2941 : // secrets when listing processes
2942 26 : auto handle = CPLSpawnAsyncGetOutputFileHandle(hSpawnedProcess);
2943 78 : for (auto pfnFunc : {&CPLGetConfigOptions, &CPLGetThreadLocalConfigOptions})
2944 : {
2945 104 : CPLStringList aosConfigOptions((*pfnFunc)());
2946 126 : for (const char *pszNameValue : aosConfigOptions)
2947 : {
2948 74 : if (!STARTS_WITH(pszNameValue, "GDAL_CACHEMAX") &&
2949 74 : !STARTS_WITH(pszNameValue, "GDAL_NUM_THREADS"))
2950 : {
2951 74 : constexpr const char *CONFIG_MARKER = "--config\n";
2952 74 : bRet &= CPL_TO_BOOL(
2953 : CPLPipeWrite(handle, CONFIG_MARKER,
2954 74 : static_cast<int>(strlen(CONFIG_MARKER))));
2955 74 : char *pszEscaped = CPLEscapeString(pszNameValue, -1, CPLES_URL);
2956 74 : bRet &= CPL_TO_BOOL(CPLPipeWrite(
2957 74 : handle, pszEscaped, static_cast<int>(strlen(pszEscaped))));
2958 74 : CPLFree(pszEscaped);
2959 74 : bRet &= CPL_TO_BOOL(CPLPipeWrite(handle, "\n", 1));
2960 : }
2961 : }
2962 : }
2963 26 : constexpr const char *END_CONFIG_MARKER = "END_CONFIG\n";
2964 26 : bRet &=
2965 26 : CPL_TO_BOOL(CPLPipeWrite(handle, END_CONFIG_MARKER,
2966 26 : static_cast<int>(strlen(END_CONFIG_MARKER))));
2967 26 : }
2968 :
2969 : /************************************************************************/
2970 : /* GenerateTilesForkMethod() */
2971 : /************************************************************************/
2972 :
2973 : #ifdef FORK_ALLOWED
2974 :
2975 : namespace
2976 : {
2977 : struct ForkWorkStructure
2978 : {
2979 : uint64_t nCacheMaxPerProcess = 0;
2980 : CPLStringList aosArgv{};
2981 : GDALDataset *poMemSrcDS{};
2982 : };
2983 : } // namespace
2984 :
2985 : static CPL_FILE_HANDLE pipeIn = CPL_FILE_INVALID_HANDLE;
2986 : static CPL_FILE_HANDLE pipeOut = CPL_FILE_INVALID_HANDLE;
2987 :
2988 0 : static int GenerateTilesForkMethod(CPL_FILE_HANDLE in, CPL_FILE_HANDLE out)
2989 : {
2990 0 : pipeIn = in;
2991 0 : pipeOut = out;
2992 :
2993 0 : const ForkWorkStructure *pWorkStructure = nullptr;
2994 0 : CPLPipeRead(in, &pWorkStructure, sizeof(pWorkStructure));
2995 :
2996 0 : CPLSetConfigOption("GDAL_NUM_THREADS", "1");
2997 0 : GDALSetCacheMax64(pWorkStructure->nCacheMaxPerProcess);
2998 :
2999 0 : GDALRasterTileAlgorithm alg;
3000 0 : if (pWorkStructure->poMemSrcDS)
3001 0 : alg.GetArg(GDAL_ARG_NAME_INPUT)->Set(pWorkStructure->poMemSrcDS);
3002 0 : return alg.ParseCommandLineArguments(pWorkStructure->aosArgv) && alg.Run()
3003 0 : ? 0
3004 0 : : 1;
3005 : }
3006 :
3007 : #endif // FORK_ALLOWED
3008 :
3009 : /************************************************************************/
3010 : /* GDALRasterTileAlgorithm::GenerateBaseTilesSpawnMethod() */
3011 : /************************************************************************/
3012 :
3013 5 : bool GDALRasterTileAlgorithm::GenerateBaseTilesSpawnMethod(
3014 : int nBaseTilesPerCol, int nBaseTilesPerRow, int nMinTileX, int nMinTileY,
3015 : int nMaxTileX, int nMaxTileY, uint64_t nTotalTiles, uint64_t nBaseTiles,
3016 : GDALProgressFunc pfnProgress, void *pProgressData)
3017 : {
3018 5 : if (m_parallelMethod == "spawn")
3019 : {
3020 3 : CPLAssert(!m_osGDALPath.empty());
3021 : }
3022 :
3023 5 : const int nMaxJobCount = GetMaxChildCount(std::max(
3024 0 : 1, static_cast<int>(std::min<uint64_t>(
3025 5 : m_numThreads, nBaseTiles / GetThresholdMinTilesPerJob()))));
3026 :
3027 : double dfTilesYPerJob;
3028 : int nYOuterIterations;
3029 : double dfTilesXPerJob;
3030 : int nXOuterIterations;
3031 5 : ComputeJobChunkSize(nMaxJobCount, nBaseTilesPerCol, nBaseTilesPerRow,
3032 : dfTilesYPerJob, nYOuterIterations, dfTilesXPerJob,
3033 : nXOuterIterations);
3034 :
3035 5 : CPLDebugOnly("gdal_raster_tile",
3036 : "nYOuterIterations=%d, dfTilesYPerJob=%g, "
3037 : "nXOuterIterations=%d, dfTilesXPerJob=%g",
3038 : nYOuterIterations, dfTilesYPerJob, nXOuterIterations,
3039 : dfTilesXPerJob);
3040 :
3041 10 : std::vector<std::string> asCommandLines;
3042 10 : std::vector<CPLSpawnedProcess *> ahSpawnedProcesses;
3043 10 : std::vector<uint64_t> anRemainingTilesForProcess;
3044 :
3045 5 : const uint64_t nCacheMaxPerProcess = GDALGetCacheMax64() / nMaxJobCount;
3046 :
3047 5 : const auto poSrcDriver = m_poSrcDS->GetDriver();
3048 : const bool bIsMEMSource =
3049 5 : poSrcDriver && EQUAL(poSrcDriver->GetDescription(), "MEM");
3050 :
3051 5 : int nLastYEndIncluded = nMinTileY - 1;
3052 :
3053 : #ifdef FORK_ALLOWED
3054 10 : std::vector<std::unique_ptr<ForkWorkStructure>> forkWorkStructures;
3055 : #endif
3056 :
3057 5 : bool bRet = true;
3058 23 : for (int iYOuterIter = 0; bRet && iYOuterIter < nYOuterIterations &&
3059 18 : nLastYEndIncluded < nMaxTileY;
3060 : ++iYOuterIter)
3061 : {
3062 18 : const int iYStart = nLastYEndIncluded + 1;
3063 : const int iYEndIncluded =
3064 18 : iYOuterIter + 1 == nYOuterIterations
3065 31 : ? nMaxTileY
3066 : : std::max(
3067 : iYStart,
3068 31 : static_cast<int>(std::floor(
3069 13 : nMinTileY + (iYOuterIter + 1) * dfTilesYPerJob - 1)));
3070 :
3071 18 : nLastYEndIncluded = iYEndIncluded;
3072 :
3073 18 : int nLastXEndIncluded = nMinTileX - 1;
3074 36 : for (int iXOuterIter = 0; bRet && iXOuterIter < nXOuterIterations &&
3075 18 : nLastXEndIncluded < nMaxTileX;
3076 : ++iXOuterIter)
3077 : {
3078 18 : const int iXStart = nLastXEndIncluded + 1;
3079 : const int iXEndIncluded =
3080 18 : iXOuterIter + 1 == nXOuterIterations
3081 18 : ? nMaxTileX
3082 : : std::max(iXStart,
3083 18 : static_cast<int>(std::floor(
3084 0 : nMinTileX +
3085 0 : (iXOuterIter + 1) * dfTilesXPerJob - 1)));
3086 :
3087 18 : nLastXEndIncluded = iXEndIncluded;
3088 :
3089 18 : anRemainingTilesForProcess.push_back(
3090 0 : static_cast<uint64_t>(iYEndIncluded - iYStart + 1) *
3091 18 : (iXEndIncluded - iXStart + 1));
3092 :
3093 18 : CPLStringList aosArgv;
3094 18 : if (m_parallelMethod == "spawn")
3095 : {
3096 10 : aosArgv.push_back(m_osGDALPath.c_str());
3097 10 : aosArgv.push_back("raster");
3098 10 : aosArgv.push_back("tile");
3099 10 : aosArgv.push_back("--config-options-in-stdin");
3100 10 : aosArgv.push_back("--config");
3101 10 : aosArgv.push_back("GDAL_NUM_THREADS=1");
3102 10 : aosArgv.push_back("--config");
3103 10 : aosArgv.push_back(
3104 : CPLSPrintf("GDAL_CACHEMAX=%" PRIu64, nCacheMaxPerProcess));
3105 : }
3106 18 : aosArgv.push_back("--num-threads");
3107 18 : aosArgv.push_back("1");
3108 18 : aosArgv.push_back("--min-x");
3109 18 : aosArgv.push_back(CPLSPrintf("%d", iXStart));
3110 18 : aosArgv.push_back("--max-x");
3111 18 : aosArgv.push_back(CPLSPrintf("%d", iXEndIncluded));
3112 18 : aosArgv.push_back("--min-y");
3113 18 : aosArgv.push_back(CPLSPrintf("%d", iYStart));
3114 18 : aosArgv.push_back("--max-y");
3115 18 : aosArgv.push_back(CPLSPrintf("%d", iYEndIncluded));
3116 18 : aosArgv.push_back("--webviewer");
3117 18 : aosArgv.push_back("none");
3118 18 : aosArgv.push_back(m_parallelMethod == "spawn" ? "--spawned"
3119 : : "--forked");
3120 18 : if (!bIsMEMSource)
3121 : {
3122 14 : aosArgv.push_back("--input");
3123 14 : aosArgv.push_back(m_poSrcDS->GetDescription());
3124 : }
3125 936 : for (const auto &arg : GetArgs())
3126 : {
3127 1090 : if (arg->IsExplicitlySet() && arg->GetName() != "min-x" &&
3128 258 : arg->GetName() != "min-y" && arg->GetName() != "max-x" &&
3129 242 : arg->GetName() != "max-y" && arg->GetName() != "min-zoom" &&
3130 140 : arg->GetName() != "progress" &&
3131 140 : arg->GetName() != "progress-forked" &&
3132 122 : arg->GetName() != "input" &&
3133 102 : arg->GetName() != "num-threads" &&
3134 1054 : arg->GetName() != "webviewer" &&
3135 50 : arg->GetName() != "parallel-method")
3136 : {
3137 40 : if (!AddArgToArgv(arg.get(), aosArgv))
3138 0 : return false;
3139 : }
3140 : }
3141 :
3142 18 : std::string cmdLine;
3143 440 : for (const char *arg : aosArgv)
3144 : {
3145 422 : if (!cmdLine.empty())
3146 404 : cmdLine += ' ';
3147 422 : cmdLine += arg;
3148 : }
3149 18 : CPLDebugOnly("gdal_raster_tile", "%s %s",
3150 : m_parallelMethod == "spawn" ? "Spawning" : "Forking",
3151 : cmdLine.c_str());
3152 18 : asCommandLines.push_back(std::move(cmdLine));
3153 :
3154 : #ifdef FORK_ALLOWED
3155 18 : if (m_parallelMethod == "fork")
3156 : {
3157 8 : forkWorkStructures.push_back(
3158 16 : std::make_unique<ForkWorkStructure>());
3159 8 : ForkWorkStructure *pData = forkWorkStructures.back().get();
3160 8 : pData->nCacheMaxPerProcess = nCacheMaxPerProcess;
3161 8 : pData->aosArgv = aosArgv;
3162 8 : if (bIsMEMSource)
3163 4 : pData->poMemSrcDS = m_poSrcDS;
3164 : }
3165 18 : CPL_IGNORE_RET_VAL(aosArgv);
3166 : #endif
3167 :
3168 36 : CPLSpawnedProcess *hSpawnedProcess = CPLSpawnAsync(
3169 : #ifdef FORK_ALLOWED
3170 18 : m_parallelMethod == "fork" ? GenerateTilesForkMethod :
3171 : #endif
3172 : nullptr,
3173 28 : m_parallelMethod == "fork" ? nullptr : aosArgv.List(),
3174 : /* bCreateInputPipe = */ true,
3175 : /* bCreateOutputPipe = */ true,
3176 18 : /* bCreateErrorPipe = */ true, nullptr);
3177 18 : if (!hSpawnedProcess)
3178 : {
3179 0 : ReportError(CE_Failure, CPLE_AppDefined,
3180 : "Spawning child gdal process '%s' failed",
3181 0 : asCommandLines.back().c_str());
3182 0 : bRet = false;
3183 0 : break;
3184 : }
3185 :
3186 18 : CPLDebugOnly("gdal_raster_tile",
3187 : "Job for y in [%d,%d] and x in [%d,%d], "
3188 : "run by process %" PRIu64,
3189 : iYStart, iYEndIncluded, iXStart, iXEndIncluded,
3190 : static_cast<uint64_t>(
3191 : CPLSpawnAsyncGetChildProcessId(hSpawnedProcess)));
3192 :
3193 18 : ahSpawnedProcesses.push_back(hSpawnedProcess);
3194 :
3195 18 : if (m_parallelMethod == "spawn")
3196 : {
3197 10 : SendConfigOptions(hSpawnedProcess, bRet);
3198 : }
3199 :
3200 : #ifdef FORK_ALLOWED
3201 : else
3202 : {
3203 8 : ForkWorkStructure *pData = forkWorkStructures.back().get();
3204 8 : auto handle = CPLSpawnAsyncGetOutputFileHandle(hSpawnedProcess);
3205 8 : bRet &= CPL_TO_BOOL(CPLPipeWrite(
3206 8 : handle, &pData, static_cast<int>(sizeof(pData))));
3207 : }
3208 : #endif
3209 :
3210 18 : if (!bRet)
3211 : {
3212 0 : ReportError(CE_Failure, CPLE_AppDefined,
3213 : "Could not transmit config options to child gdal "
3214 : "process '%s'",
3215 0 : asCommandLines.back().c_str());
3216 0 : break;
3217 : }
3218 : }
3219 : }
3220 :
3221 5 : uint64_t nCurTile = 0;
3222 5 : GetProgressForChildProcesses(bRet, ahSpawnedProcesses,
3223 : anRemainingTilesForProcess, nCurTile,
3224 : nTotalTiles, pfnProgress, pProgressData);
3225 :
3226 5 : WaitForSpawnedProcesses(bRet, asCommandLines, ahSpawnedProcesses);
3227 :
3228 5 : if (bRet && nCurTile != nBaseTiles)
3229 : {
3230 0 : bRet = false;
3231 0 : ReportError(CE_Failure, CPLE_AppDefined,
3232 : "Not all tiles at max zoom level have been "
3233 : "generated. Got %" PRIu64 ", expected %" PRIu64,
3234 : nCurTile, nBaseTiles);
3235 : }
3236 :
3237 5 : return bRet;
3238 : }
3239 :
3240 : /************************************************************************/
3241 : /* GDALRasterTileAlgorithm::GenerateOverviewTilesSpawnMethod() */
3242 : /************************************************************************/
3243 :
3244 8 : bool GDALRasterTileAlgorithm::GenerateOverviewTilesSpawnMethod(
3245 : int iZ, int nOvrMinTileX, int nOvrMinTileY, int nOvrMaxTileX,
3246 : int nOvrMaxTileY, std::atomic<uint64_t> &nCurTile, uint64_t nTotalTiles,
3247 : GDALProgressFunc pfnProgress, void *pProgressData)
3248 : {
3249 8 : if (m_parallelMethod == "spawn")
3250 : {
3251 4 : CPLAssert(!m_osGDALPath.empty());
3252 : }
3253 :
3254 8 : const int nOvrTilesPerCol = nOvrMaxTileY - nOvrMinTileY + 1;
3255 8 : const int nOvrTilesPerRow = nOvrMaxTileX - nOvrMinTileX + 1;
3256 8 : const uint64_t nExpectedOvrTileCount =
3257 8 : static_cast<uint64_t>(nOvrTilesPerCol) * nOvrTilesPerRow;
3258 :
3259 8 : const int nMaxJobCount = GetMaxChildCount(
3260 0 : std::max(1, static_cast<int>(std::min<uint64_t>(
3261 0 : m_numThreads, nExpectedOvrTileCount /
3262 8 : GetThresholdMinTilesPerJob()))));
3263 :
3264 : double dfTilesYPerJob;
3265 : int nYOuterIterations;
3266 : double dfTilesXPerJob;
3267 : int nXOuterIterations;
3268 8 : ComputeJobChunkSize(nMaxJobCount, nOvrTilesPerCol, nOvrTilesPerRow,
3269 : dfTilesYPerJob, nYOuterIterations, dfTilesXPerJob,
3270 : nXOuterIterations);
3271 :
3272 8 : CPLDebugOnly("gdal_raster_tile",
3273 : "z=%d, nYOuterIterations=%d, dfTilesYPerJob=%g, "
3274 : "nXOuterIterations=%d, dfTilesXPerJob=%g",
3275 : iZ, nYOuterIterations, dfTilesYPerJob, nXOuterIterations,
3276 : dfTilesXPerJob);
3277 :
3278 16 : std::vector<std::string> asCommandLines;
3279 16 : std::vector<CPLSpawnedProcess *> ahSpawnedProcesses;
3280 16 : std::vector<uint64_t> anRemainingTilesForProcess;
3281 :
3282 : #ifdef FORK_ALLOWED
3283 16 : std::vector<std::unique_ptr<ForkWorkStructure>> forkWorkStructures;
3284 : #endif
3285 :
3286 8 : const uint64_t nCacheMaxPerProcess = GDALGetCacheMax64() / nMaxJobCount;
3287 :
3288 8 : const auto poSrcDriver = m_poSrcDS ? m_poSrcDS->GetDriver() : nullptr;
3289 : const bool bIsMEMSource =
3290 8 : poSrcDriver && EQUAL(poSrcDriver->GetDescription(), "MEM");
3291 :
3292 8 : int nLastYEndIncluded = nOvrMinTileY - 1;
3293 8 : bool bRet = true;
3294 32 : for (int iYOuterIter = 0; bRet && iYOuterIter < nYOuterIterations &&
3295 24 : nLastYEndIncluded < nOvrMaxTileY;
3296 : ++iYOuterIter)
3297 : {
3298 24 : const int iYStart = nLastYEndIncluded + 1;
3299 : const int iYEndIncluded =
3300 24 : iYOuterIter + 1 == nYOuterIterations
3301 40 : ? nOvrMaxTileY
3302 : : std::max(iYStart,
3303 40 : static_cast<int>(std::floor(
3304 16 : nOvrMinTileY +
3305 16 : (iYOuterIter + 1) * dfTilesYPerJob - 1)));
3306 :
3307 24 : nLastYEndIncluded = iYEndIncluded;
3308 :
3309 24 : int nLastXEndIncluded = nOvrMinTileX - 1;
3310 56 : for (int iXOuterIter = 0; bRet && iXOuterIter < nXOuterIterations &&
3311 32 : nLastXEndIncluded < nOvrMaxTileX;
3312 : ++iXOuterIter)
3313 : {
3314 32 : const int iXStart = nLastXEndIncluded + 1;
3315 : const int iXEndIncluded =
3316 32 : iXOuterIter + 1 == nXOuterIterations
3317 40 : ? nOvrMaxTileX
3318 : : std::max(iXStart,
3319 40 : static_cast<int>(std::floor(
3320 8 : nOvrMinTileX +
3321 8 : (iXOuterIter + 1) * dfTilesXPerJob - 1)));
3322 :
3323 32 : nLastXEndIncluded = iXEndIncluded;
3324 :
3325 32 : anRemainingTilesForProcess.push_back(
3326 0 : static_cast<uint64_t>(iYEndIncluded - iYStart + 1) *
3327 32 : (iXEndIncluded - iXStart + 1));
3328 :
3329 32 : CPLStringList aosArgv;
3330 32 : if (m_parallelMethod == "spawn")
3331 : {
3332 16 : aosArgv.push_back(m_osGDALPath.c_str());
3333 16 : aosArgv.push_back("raster");
3334 16 : aosArgv.push_back("tile");
3335 16 : aosArgv.push_back("--config-options-in-stdin");
3336 16 : aosArgv.push_back("--config");
3337 16 : aosArgv.push_back("GDAL_NUM_THREADS=1");
3338 16 : aosArgv.push_back("--config");
3339 16 : aosArgv.push_back(
3340 : CPLSPrintf("GDAL_CACHEMAX=%" PRIu64, nCacheMaxPerProcess));
3341 : }
3342 32 : aosArgv.push_back("--num-threads");
3343 32 : aosArgv.push_back("1");
3344 32 : aosArgv.push_back("--ovr-zoom-level");
3345 32 : aosArgv.push_back(CPLSPrintf("%d", iZ));
3346 32 : aosArgv.push_back("--ovr-min-x");
3347 32 : aosArgv.push_back(CPLSPrintf("%d", iXStart));
3348 32 : aosArgv.push_back("--ovr-max-x");
3349 32 : aosArgv.push_back(CPLSPrintf("%d", iXEndIncluded));
3350 32 : aosArgv.push_back("--ovr-min-y");
3351 32 : aosArgv.push_back(CPLSPrintf("%d", iYStart));
3352 32 : aosArgv.push_back("--ovr-max-y");
3353 32 : aosArgv.push_back(CPLSPrintf("%d", iYEndIncluded));
3354 32 : aosArgv.push_back("--webviewer");
3355 32 : aosArgv.push_back("none");
3356 32 : aosArgv.push_back(m_parallelMethod == "spawn" ? "--spawned"
3357 : : "--forked");
3358 32 : if (!bIsMEMSource)
3359 : {
3360 24 : aosArgv.push_back("--input");
3361 24 : aosArgv.push_back(m_dataset.GetName().c_str());
3362 : }
3363 1664 : for (const auto &arg : GetArgs())
3364 : {
3365 1936 : if (arg->IsExplicitlySet() && arg->GetName() != "progress" &&
3366 304 : arg->GetName() != "progress-forked" &&
3367 272 : arg->GetName() != "input" &&
3368 240 : arg->GetName() != "num-threads" &&
3369 1904 : arg->GetName() != "webviewer" &&
3370 120 : arg->GetName() != "parallel-method")
3371 : {
3372 104 : if (!AddArgToArgv(arg.get(), aosArgv))
3373 0 : return false;
3374 : }
3375 : }
3376 :
3377 32 : std::string cmdLine;
3378 896 : for (const char *arg : aosArgv)
3379 : {
3380 864 : if (!cmdLine.empty())
3381 832 : cmdLine += ' ';
3382 864 : cmdLine += arg;
3383 : }
3384 32 : CPLDebugOnly("gdal_raster_tile", "%s %s",
3385 : m_parallelMethod == "spawn" ? "Spawning" : "Forking",
3386 : cmdLine.c_str());
3387 32 : asCommandLines.push_back(std::move(cmdLine));
3388 :
3389 : #ifdef FORK_ALLOWED
3390 32 : if (m_parallelMethod == "fork")
3391 : {
3392 16 : forkWorkStructures.push_back(
3393 32 : std::make_unique<ForkWorkStructure>());
3394 16 : ForkWorkStructure *pData = forkWorkStructures.back().get();
3395 16 : pData->nCacheMaxPerProcess = nCacheMaxPerProcess;
3396 16 : pData->aosArgv = aosArgv;
3397 16 : if (bIsMEMSource)
3398 8 : pData->poMemSrcDS = m_poSrcDS;
3399 : }
3400 32 : CPL_IGNORE_RET_VAL(aosArgv);
3401 : #endif
3402 :
3403 64 : CPLSpawnedProcess *hSpawnedProcess = CPLSpawnAsync(
3404 : #ifdef FORK_ALLOWED
3405 32 : m_parallelMethod == "fork" ? GenerateTilesForkMethod :
3406 : #endif
3407 : nullptr,
3408 48 : m_parallelMethod == "fork" ? nullptr : aosArgv.List(),
3409 : /* bCreateInputPipe = */ true,
3410 : /* bCreateOutputPipe = */ true,
3411 32 : /* bCreateErrorPipe = */ true, nullptr);
3412 32 : if (!hSpawnedProcess)
3413 : {
3414 0 : ReportError(CE_Failure, CPLE_AppDefined,
3415 : "Spawning child gdal process '%s' failed",
3416 0 : asCommandLines.back().c_str());
3417 0 : bRet = false;
3418 0 : break;
3419 : }
3420 :
3421 32 : CPLDebugOnly("gdal_raster_tile",
3422 : "Job for z = %d, y in [%d,%d] and x in [%d,%d], "
3423 : "run by process %" PRIu64,
3424 : iZ, iYStart, iYEndIncluded, iXStart, iXEndIncluded,
3425 : static_cast<uint64_t>(
3426 : CPLSpawnAsyncGetChildProcessId(hSpawnedProcess)));
3427 :
3428 32 : ahSpawnedProcesses.push_back(hSpawnedProcess);
3429 :
3430 32 : if (m_parallelMethod == "spawn")
3431 : {
3432 16 : SendConfigOptions(hSpawnedProcess, bRet);
3433 : }
3434 :
3435 : #ifdef FORK_ALLOWED
3436 : else
3437 : {
3438 16 : ForkWorkStructure *pData = forkWorkStructures.back().get();
3439 16 : auto handle = CPLSpawnAsyncGetOutputFileHandle(hSpawnedProcess);
3440 16 : bRet &= CPL_TO_BOOL(CPLPipeWrite(
3441 16 : handle, &pData, static_cast<int>(sizeof(pData))));
3442 : }
3443 : #endif
3444 32 : if (!bRet)
3445 : {
3446 0 : ReportError(CE_Failure, CPLE_AppDefined,
3447 : "Could not transmit config options to child gdal "
3448 : "process '%s'",
3449 0 : asCommandLines.back().c_str());
3450 0 : break;
3451 : }
3452 : }
3453 : }
3454 :
3455 8 : uint64_t nCurTileLocal = nCurTile;
3456 8 : GetProgressForChildProcesses(bRet, ahSpawnedProcesses,
3457 : anRemainingTilesForProcess, nCurTileLocal,
3458 : nTotalTiles, pfnProgress, pProgressData);
3459 :
3460 8 : WaitForSpawnedProcesses(bRet, asCommandLines, ahSpawnedProcesses);
3461 :
3462 8 : if (bRet && nCurTileLocal - nCurTile != nExpectedOvrTileCount)
3463 : {
3464 0 : bRet = false;
3465 0 : ReportError(CE_Failure, CPLE_AppDefined,
3466 : "Not all tiles at zoom level %d have been "
3467 : "generated. Got %" PRIu64 ", expected %" PRIu64,
3468 0 : iZ, nCurTileLocal - nCurTile, nExpectedOvrTileCount);
3469 : }
3470 :
3471 8 : nCurTile = nCurTileLocal;
3472 :
3473 8 : return bRet;
3474 : }
3475 :
3476 : /************************************************************************/
3477 : /* GDALRasterTileAlgorithm::RunImpl() */
3478 : /************************************************************************/
3479 :
3480 129 : bool GDALRasterTileAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
3481 : void *pProgressData)
3482 : {
3483 129 : m_poSrcDS = m_dataset.GetDatasetRef();
3484 129 : CPLAssert(m_poSrcDS);
3485 129 : const int nSrcWidth = m_poSrcDS->GetRasterXSize();
3486 129 : const int nSrcHeight = m_poSrcDS->GetRasterYSize();
3487 129 : if (m_poSrcDS->GetRasterCount() == 0 || nSrcWidth == 0 || nSrcHeight == 0)
3488 : {
3489 1 : ReportError(CE_Failure, CPLE_AppDefined, "Invalid source dataset");
3490 1 : return false;
3491 : }
3492 :
3493 128 : const bool bIsNamedSource = m_poSrcDS->GetDescription()[0] != 0;
3494 128 : auto poSrcDriver = m_poSrcDS->GetDriver();
3495 : const bool bIsMEMSource =
3496 128 : poSrcDriver && EQUAL(poSrcDriver->GetDescription(), "MEM");
3497 128 : m_bIsNamedNonMemSrcDS = bIsNamedSource && !bIsMEMSource;
3498 128 : const bool bSrcIsFineForFork = bIsNamedSource || bIsMEMSource;
3499 :
3500 128 : if (m_parallelMethod == "spawn")
3501 : {
3502 5 : const char *pszErrorMsg = "";
3503 5 : if (!IsCompatibleOfSpawn(pszErrorMsg))
3504 : {
3505 3 : if (pszErrorMsg[0])
3506 2 : ReportError(CE_Failure, CPLE_AppDefined, "%s", pszErrorMsg);
3507 3 : return false;
3508 : }
3509 : }
3510 : #ifdef FORK_ALLOWED
3511 123 : else if (m_parallelMethod == "fork")
3512 : {
3513 3 : if (!bSrcIsFineForFork)
3514 : {
3515 1 : ReportError(CE_Failure, CPLE_AppDefined,
3516 : "Unnamed non-MEM source are not supported "
3517 : "with fork parallelization method");
3518 1 : return false;
3519 : }
3520 2 : if (cpl::starts_with(m_outputDirectory, "/vsimem/"))
3521 : {
3522 1 : ReportError(CE_Failure, CPLE_AppDefined,
3523 : "/vsimem/ output directory not supported with fork "
3524 : "parallelization method");
3525 1 : return false;
3526 : }
3527 : }
3528 : #endif
3529 :
3530 123 : if (m_resampling == "near")
3531 1 : m_resampling = "nearest";
3532 123 : if (m_overviewResampling == "near")
3533 1 : m_overviewResampling = "nearest";
3534 122 : else if (m_overviewResampling.empty())
3535 117 : m_overviewResampling = m_resampling;
3536 :
3537 246 : CPLStringList aosWarpOptions;
3538 123 : if (!m_excludedValues.empty() || m_nodataValuesPctThreshold < 100)
3539 : {
3540 : aosWarpOptions.SetNameValue(
3541 : "NODATA_VALUES_PCT_THRESHOLD",
3542 3 : CPLSPrintf("%g", m_nodataValuesPctThreshold));
3543 3 : if (!m_excludedValues.empty())
3544 : {
3545 : aosWarpOptions.SetNameValue("EXCLUDED_VALUES",
3546 1 : m_excludedValues.c_str());
3547 : aosWarpOptions.SetNameValue(
3548 : "EXCLUDED_VALUES_PCT_THRESHOLD",
3549 1 : CPLSPrintf("%g", m_excludedValuesPctThreshold));
3550 : }
3551 : }
3552 :
3553 123 : if (m_poSrcDS->GetRasterBand(1)->GetColorInterpretation() ==
3554 127 : GCI_PaletteIndex &&
3555 4 : ((m_resampling != "nearest" && m_resampling != "mode") ||
3556 1 : (m_overviewResampling != "nearest" && m_overviewResampling != "mode")))
3557 : {
3558 1 : ReportError(CE_Failure, CPLE_NotSupported,
3559 : "Datasets with color table not supported with non-nearest "
3560 : "or non-mode resampling. Run 'gdal raster "
3561 : "color-map' before or set the 'resampling' argument to "
3562 : "'nearest' or 'mode'.");
3563 1 : return false;
3564 : }
3565 :
3566 122 : const auto eSrcDT = m_poSrcDS->GetRasterBand(1)->GetRasterDataType();
3567 122 : m_poDstDriver =
3568 122 : GetGDALDriverManager()->GetDriverByName(m_outputFormat.c_str());
3569 122 : if (!m_poDstDriver)
3570 : {
3571 1 : ReportError(CE_Failure, CPLE_AppDefined,
3572 : "Invalid value for argument 'output-format'. Driver '%s' "
3573 : "does not exist",
3574 : m_outputFormat.c_str());
3575 1 : return false;
3576 : }
3577 :
3578 121 : if (!ValidateOutputFormat(eSrcDT))
3579 8 : return false;
3580 :
3581 : const char *pszExtensions =
3582 113 : m_poDstDriver->GetMetadataItem(GDAL_DMD_EXTENSIONS);
3583 113 : CPLAssert(pszExtensions && pszExtensions[0] != 0);
3584 : const CPLStringList aosExtensions(
3585 226 : CSLTokenizeString2(pszExtensions, " ", 0));
3586 113 : const char *pszExtension = aosExtensions[0];
3587 113 : GDALGeoTransform srcGT;
3588 113 : const bool bHasSrcGT = m_poSrcDS->GetGeoTransform(srcGT) == CE_None;
3589 : const bool bHasNorthUpSrcGT =
3590 113 : bHasSrcGT && srcGT[2] == 0 && srcGT[4] == 0 && srcGT[5] < 0;
3591 226 : OGRSpatialReference oSRS_TMS;
3592 :
3593 113 : if (m_tilingScheme == "raster")
3594 : {
3595 4 : if (const auto poSRS = m_poSrcDS->GetSpatialRef())
3596 3 : oSRS_TMS = *poSRS;
3597 : }
3598 : else
3599 : {
3600 1 : if (!bHasSrcGT && m_poSrcDS->GetGCPCount() == 0 &&
3601 111 : m_poSrcDS->GetMetadata("GEOLOCATION") == nullptr &&
3602 1 : m_poSrcDS->GetMetadata("RPC") == nullptr)
3603 : {
3604 1 : ReportError(CE_Failure, CPLE_NotSupported,
3605 : "Ungeoreferenced datasets are not supported, unless "
3606 : "'tiling-scheme' is set to 'raster'");
3607 1 : return false;
3608 : }
3609 :
3610 108 : if (m_poSrcDS->GetMetadata("GEOLOCATION") == nullptr &&
3611 108 : m_poSrcDS->GetMetadata("RPC") == nullptr &&
3612 217 : m_poSrcDS->GetSpatialRef() == nullptr &&
3613 1 : m_poSrcDS->GetGCPSpatialRef() == nullptr)
3614 : {
3615 1 : ReportError(CE_Failure, CPLE_NotSupported,
3616 : "Ungeoreferenced datasets are not supported, unless "
3617 : "'tiling-scheme' is set to 'raster'");
3618 1 : return false;
3619 : }
3620 : }
3621 :
3622 111 : if (m_copySrcMetadata)
3623 : {
3624 8 : CPLStringList aosMD(CSLDuplicate(m_poSrcDS->GetMetadata()));
3625 4 : const CPLStringList aosNewMD(m_metadata);
3626 8 : for (const auto [key, value] : cpl::IterateNameValue(aosNewMD))
3627 : {
3628 4 : aosMD.SetNameValue(key, value);
3629 : }
3630 4 : m_metadata = aosMD;
3631 : }
3632 :
3633 222 : std::vector<BandMetadata> aoBandMetadata;
3634 65914 : for (int i = 1; i <= m_poSrcDS->GetRasterCount(); ++i)
3635 : {
3636 65803 : auto poBand = m_poSrcDS->GetRasterBand(i);
3637 131606 : BandMetadata bm;
3638 65803 : bm.osDescription = poBand->GetDescription();
3639 65803 : bm.eDT = poBand->GetRasterDataType();
3640 65803 : bm.eColorInterp = poBand->GetColorInterpretation();
3641 65803 : if (const char *pszCenterWavelength =
3642 65803 : poBand->GetMetadataItem("CENTRAL_WAVELENGTH_UM", "IMAGERY"))
3643 0 : bm.osCenterWaveLength = pszCenterWavelength;
3644 65803 : if (const char *pszFWHM = poBand->GetMetadataItem("FWHM_UM", "IMAGERY"))
3645 0 : bm.osFWHM = pszFWHM;
3646 65803 : aoBandMetadata.emplace_back(std::move(bm));
3647 : }
3648 :
3649 111 : GDALGeoTransform srcGTModif{0, 1, 0, 0, 0, -1};
3650 :
3651 111 : if (m_tilingScheme == "mercator")
3652 1 : m_tilingScheme = "WebMercatorQuad";
3653 110 : else if (m_tilingScheme == "geodetic")
3654 1 : m_tilingScheme = "WorldCRS84Quad";
3655 109 : else if (m_tilingScheme == "raster")
3656 : {
3657 4 : if (m_tileSize == 0)
3658 4 : m_tileSize = 256;
3659 4 : if (m_maxZoomLevel < 0)
3660 : {
3661 3 : m_maxZoomLevel = static_cast<int>(std::ceil(std::log2(
3662 6 : std::max(1, std::max(nSrcWidth, nSrcHeight) / m_tileSize))));
3663 : }
3664 4 : if (bHasNorthUpSrcGT)
3665 : {
3666 3 : srcGTModif = srcGT;
3667 : }
3668 : }
3669 :
3670 : auto poTMS =
3671 111 : m_tilingScheme == "raster"
3672 : ? gdal::TileMatrixSet::createRaster(
3673 4 : nSrcWidth, nSrcHeight, m_tileSize, 1 + m_maxZoomLevel,
3674 16 : srcGTModif[0], srcGTModif[3], srcGTModif[1], -srcGTModif[5],
3675 115 : oSRS_TMS.IsEmpty() ? std::string() : oSRS_TMS.exportToWkt())
3676 : : gdal::TileMatrixSet::parse(
3677 226 : m_mapTileMatrixIdentifierToScheme[m_tilingScheme].c_str());
3678 : // Enforced by SetChoices() on the m_tilingScheme argument
3679 111 : CPLAssert(poTMS && !poTMS->hasVariableMatrixWidth());
3680 :
3681 222 : CPLStringList aosTO;
3682 111 : if (m_tilingScheme == "raster")
3683 : {
3684 4 : aosTO.SetNameValue("SRC_METHOD", "GEOTRANSFORM");
3685 : }
3686 : else
3687 : {
3688 107 : CPL_IGNORE_RET_VAL(oSRS_TMS.SetFromUserInput(poTMS->crs().c_str()));
3689 107 : aosTO.SetNameValue("DST_SRS", oSRS_TMS.exportToWkt().c_str());
3690 : }
3691 :
3692 111 : const char *pszAuthName = oSRS_TMS.GetAuthorityName(nullptr);
3693 111 : const char *pszAuthCode = oSRS_TMS.GetAuthorityCode(nullptr);
3694 111 : const int nEPSGCode =
3695 110 : (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "EPSG"))
3696 221 : ? atoi(pszAuthCode)
3697 : : 0;
3698 :
3699 : const bool bInvertAxisTMS =
3700 218 : m_tilingScheme != "raster" &&
3701 107 : (oSRS_TMS.EPSGTreatsAsLatLong() != FALSE ||
3702 107 : oSRS_TMS.EPSGTreatsAsNorthingEasting() != FALSE);
3703 :
3704 111 : oSRS_TMS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3705 :
3706 : std::unique_ptr<void, decltype(&GDALDestroyTransformer)> hTransformArg(
3707 222 : nullptr, GDALDestroyTransformer);
3708 :
3709 : // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
3710 : // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
3711 : // EPSG:3857.
3712 111 : std::unique_ptr<GDALDataset> poTmpDS;
3713 111 : bool bEPSG3857Adjust = false;
3714 111 : if (nEPSGCode == 3857 && bHasNorthUpSrcGT)
3715 : {
3716 85 : const auto poSrcSRS = m_poSrcDS->GetSpatialRef();
3717 85 : if (poSrcSRS && poSrcSRS->IsGeographic())
3718 : {
3719 54 : double maxLat = srcGT[3];
3720 54 : double minLat = srcGT[3] + nSrcHeight * srcGT[5];
3721 : // Corresponds to the latitude of below MAX_GM
3722 54 : constexpr double MAX_LAT = 85.0511287798066;
3723 54 : bool bModified = false;
3724 54 : if (maxLat > MAX_LAT)
3725 : {
3726 53 : maxLat = MAX_LAT;
3727 53 : bModified = true;
3728 : }
3729 54 : if (minLat < -MAX_LAT)
3730 : {
3731 53 : minLat = -MAX_LAT;
3732 53 : bModified = true;
3733 : }
3734 54 : if (bModified)
3735 : {
3736 106 : CPLStringList aosOptions;
3737 53 : aosOptions.AddString("-of");
3738 53 : aosOptions.AddString("VRT");
3739 53 : aosOptions.AddString("-projwin");
3740 53 : aosOptions.AddString(CPLSPrintf("%.17g", srcGT[0]));
3741 53 : aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
3742 : aosOptions.AddString(
3743 53 : CPLSPrintf("%.17g", srcGT[0] + nSrcWidth * srcGT[1]));
3744 53 : aosOptions.AddString(CPLSPrintf("%.17g", minLat));
3745 : auto psOptions =
3746 53 : GDALTranslateOptionsNew(aosOptions.List(), nullptr);
3747 53 : poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
3748 : "", GDALDataset::ToHandle(m_poSrcDS), psOptions, nullptr)));
3749 53 : GDALTranslateOptionsFree(psOptions);
3750 53 : if (poTmpDS)
3751 : {
3752 53 : bEPSG3857Adjust = true;
3753 53 : hTransformArg.reset(GDALCreateGenImgProjTransformer2(
3754 53 : GDALDataset::FromHandle(poTmpDS.get()), nullptr,
3755 53 : aosTO.List()));
3756 : }
3757 : }
3758 : }
3759 : }
3760 :
3761 111 : GDALGeoTransform dstGT;
3762 : double adfExtent[4];
3763 : int nXSize, nYSize;
3764 :
3765 : bool bSuggestOK;
3766 111 : if (m_tilingScheme == "raster")
3767 : {
3768 4 : bSuggestOK = true;
3769 4 : nXSize = nSrcWidth;
3770 4 : nYSize = nSrcHeight;
3771 4 : dstGT = srcGTModif;
3772 4 : adfExtent[0] = dstGT[0];
3773 4 : adfExtent[1] = dstGT[3] + nSrcHeight * dstGT[5];
3774 4 : adfExtent[2] = dstGT[0] + nSrcWidth * dstGT[1];
3775 4 : adfExtent[3] = dstGT[3];
3776 : }
3777 : else
3778 : {
3779 107 : if (!hTransformArg)
3780 : {
3781 54 : hTransformArg.reset(GDALCreateGenImgProjTransformer2(
3782 54 : m_poSrcDS, nullptr, aosTO.List()));
3783 : }
3784 107 : if (!hTransformArg)
3785 : {
3786 1 : return false;
3787 : }
3788 106 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
3789 106 : bSuggestOK =
3790 212 : (GDALSuggestedWarpOutput2(
3791 106 : m_poSrcDS,
3792 106 : static_cast<GDALTransformerInfo *>(hTransformArg.get())
3793 : ->pfnTransform,
3794 : hTransformArg.get(), dstGT.data(), &nXSize, &nYSize, adfExtent,
3795 : 0) == CE_None);
3796 : }
3797 110 : if (!bSuggestOK)
3798 : {
3799 1 : ReportError(CE_Failure, CPLE_AppDefined,
3800 : "Cannot determine extent of raster in target CRS");
3801 1 : return false;
3802 : }
3803 :
3804 109 : poTmpDS.reset();
3805 :
3806 109 : if (bEPSG3857Adjust)
3807 : {
3808 53 : constexpr double SPHERICAL_RADIUS = 6378137.0;
3809 53 : constexpr double MAX_GM =
3810 : SPHERICAL_RADIUS * M_PI; // 20037508.342789244
3811 53 : double maxNorthing = dstGT[3];
3812 53 : double minNorthing = dstGT[3] + dstGT[5] * nYSize;
3813 53 : bool bChanged = false;
3814 53 : if (maxNorthing > MAX_GM)
3815 : {
3816 50 : bChanged = true;
3817 50 : maxNorthing = MAX_GM;
3818 : }
3819 53 : if (minNorthing < -MAX_GM)
3820 : {
3821 50 : bChanged = true;
3822 50 : minNorthing = -MAX_GM;
3823 : }
3824 53 : if (bChanged)
3825 : {
3826 50 : dstGT[3] = maxNorthing;
3827 50 : nYSize = int((maxNorthing - minNorthing) / (-dstGT[5]) + 0.5);
3828 50 : adfExtent[1] = maxNorthing + nYSize * dstGT[5];
3829 50 : adfExtent[3] = maxNorthing;
3830 : }
3831 : }
3832 :
3833 109 : const auto &tileMatrixList = poTMS->tileMatrixList();
3834 109 : if (m_maxZoomLevel >= 0)
3835 : {
3836 60 : if (m_maxZoomLevel >= static_cast<int>(tileMatrixList.size()))
3837 : {
3838 1 : ReportError(CE_Failure, CPLE_AppDefined,
3839 : "max-zoom = %d is invalid. It must be in [0,%d] range",
3840 : m_maxZoomLevel,
3841 1 : static_cast<int>(tileMatrixList.size()) - 1);
3842 1 : return false;
3843 : }
3844 : }
3845 : else
3846 : {
3847 49 : const double dfComputedRes = dstGT[1];
3848 49 : double dfPrevRes = 0.0;
3849 49 : double dfRes = 0.0;
3850 49 : constexpr double EPSILON = 1e-8;
3851 :
3852 49 : if (m_minZoomLevel >= 0)
3853 17 : m_maxZoomLevel = m_minZoomLevel;
3854 : else
3855 32 : m_maxZoomLevel = 0;
3856 :
3857 164 : for (; m_maxZoomLevel < static_cast<int>(tileMatrixList.size());
3858 115 : m_maxZoomLevel++)
3859 : {
3860 163 : dfRes = tileMatrixList[m_maxZoomLevel].mResX;
3861 163 : if (dfComputedRes > dfRes ||
3862 115 : fabs(dfComputedRes - dfRes) / dfRes <= EPSILON)
3863 : break;
3864 115 : dfPrevRes = dfRes;
3865 : }
3866 49 : if (m_maxZoomLevel >= static_cast<int>(tileMatrixList.size()))
3867 : {
3868 1 : ReportError(CE_Failure, CPLE_AppDefined,
3869 : "Could not find an appropriate zoom level. Perhaps "
3870 : "min-zoom is too large?");
3871 1 : return false;
3872 : }
3873 :
3874 48 : if (m_maxZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > EPSILON)
3875 : {
3876 : // Round to closest resolution
3877 43 : if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
3878 23 : m_maxZoomLevel--;
3879 : }
3880 : }
3881 107 : if (m_minZoomLevel < 0)
3882 53 : m_minZoomLevel = m_maxZoomLevel;
3883 :
3884 214 : auto tileMatrix = tileMatrixList[m_maxZoomLevel];
3885 107 : int nMinTileX = 0;
3886 107 : int nMinTileY = 0;
3887 107 : int nMaxTileX = 0;
3888 107 : int nMaxTileY = 0;
3889 107 : bool bIntersects = false;
3890 107 : if (!GetTileIndices(tileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
3891 : nMinTileX, nMinTileY, nMaxTileX, nMaxTileY,
3892 107 : m_noIntersectionIsOK, bIntersects,
3893 : /* checkRasterOverflow = */ false))
3894 : {
3895 1 : return false;
3896 : }
3897 106 : if (!bIntersects)
3898 1 : return true;
3899 :
3900 : // Potentially restrict tiling to user specified coordinates
3901 105 : if (m_minTileX >= tileMatrix.mMatrixWidth)
3902 : {
3903 1 : ReportError(CE_Failure, CPLE_IllegalArg,
3904 : "'min-x' value must be in [0,%d] range",
3905 1 : tileMatrix.mMatrixWidth - 1);
3906 1 : return false;
3907 : }
3908 104 : if (m_maxTileX >= tileMatrix.mMatrixWidth)
3909 : {
3910 1 : ReportError(CE_Failure, CPLE_IllegalArg,
3911 : "'max-x' value must be in [0,%d] range",
3912 1 : tileMatrix.mMatrixWidth - 1);
3913 1 : return false;
3914 : }
3915 103 : if (m_minTileY >= tileMatrix.mMatrixHeight)
3916 : {
3917 1 : ReportError(CE_Failure, CPLE_IllegalArg,
3918 : "'min-y' value must be in [0,%d] range",
3919 1 : tileMatrix.mMatrixHeight - 1);
3920 1 : return false;
3921 : }
3922 102 : if (m_maxTileY >= tileMatrix.mMatrixHeight)
3923 : {
3924 1 : ReportError(CE_Failure, CPLE_IllegalArg,
3925 : "'max-y' value must be in [0,%d] range",
3926 1 : tileMatrix.mMatrixHeight - 1);
3927 1 : return false;
3928 : }
3929 :
3930 101 : if ((m_minTileX >= 0 && m_minTileX > nMaxTileX) ||
3931 99 : (m_minTileY >= 0 && m_minTileY > nMaxTileY) ||
3932 99 : (m_maxTileX >= 0 && m_maxTileX < nMinTileX) ||
3933 99 : (m_maxTileY >= 0 && m_maxTileY < nMinTileY))
3934 : {
3935 2 : ReportError(
3936 2 : m_noIntersectionIsOK ? CE_Warning : CE_Failure, CPLE_AppDefined,
3937 : "Dataset extent not intersecting specified min/max X/Y tile "
3938 : "coordinates");
3939 2 : return m_noIntersectionIsOK;
3940 : }
3941 99 : if (m_minTileX >= 0 && m_minTileX > nMinTileX)
3942 : {
3943 2 : nMinTileX = m_minTileX;
3944 2 : adfExtent[0] = tileMatrix.mTopLeftX +
3945 2 : nMinTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
3946 : }
3947 99 : if (m_minTileY >= 0 && m_minTileY > nMinTileY)
3948 : {
3949 8 : nMinTileY = m_minTileY;
3950 8 : adfExtent[3] = tileMatrix.mTopLeftY -
3951 8 : nMinTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
3952 : }
3953 99 : if (m_maxTileX >= 0 && m_maxTileX < nMaxTileX)
3954 : {
3955 2 : nMaxTileX = m_maxTileX;
3956 2 : adfExtent[2] = tileMatrix.mTopLeftX + (nMaxTileX + 1) *
3957 2 : tileMatrix.mResX *
3958 2 : tileMatrix.mTileWidth;
3959 : }
3960 99 : if (m_maxTileY >= 0 && m_maxTileY < nMaxTileY)
3961 : {
3962 9 : nMaxTileY = m_maxTileY;
3963 9 : adfExtent[1] = tileMatrix.mTopLeftY - (nMaxTileY + 1) *
3964 9 : tileMatrix.mResY *
3965 9 : tileMatrix.mTileHeight;
3966 : }
3967 :
3968 99 : if (nMaxTileX - nMinTileX + 1 > INT_MAX / tileMatrix.mTileWidth ||
3969 98 : nMaxTileY - nMinTileY + 1 > INT_MAX / tileMatrix.mTileHeight)
3970 : {
3971 1 : ReportError(CE_Failure, CPLE_AppDefined, "Too large zoom level");
3972 1 : return false;
3973 : }
3974 :
3975 196 : dstGT[0] = tileMatrix.mTopLeftX +
3976 98 : nMinTileX * tileMatrix.mResX * tileMatrix.mTileWidth;
3977 98 : dstGT[1] = tileMatrix.mResX;
3978 98 : dstGT[2] = 0;
3979 196 : dstGT[3] = tileMatrix.mTopLeftY -
3980 98 : nMinTileY * tileMatrix.mResY * tileMatrix.mTileHeight;
3981 98 : dstGT[4] = 0;
3982 98 : dstGT[5] = -tileMatrix.mResY;
3983 :
3984 : /* -------------------------------------------------------------------- */
3985 : /* Setup warp options. */
3986 : /* -------------------------------------------------------------------- */
3987 : std::unique_ptr<GDALWarpOptions, decltype(&GDALDestroyWarpOptions)> psWO(
3988 196 : GDALCreateWarpOptions(), GDALDestroyWarpOptions);
3989 :
3990 98 : psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
3991 196 : psWO->papszWarpOptions =
3992 98 : CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
3993 196 : psWO->papszWarpOptions =
3994 98 : CSLMerge(psWO->papszWarpOptions, aosWarpOptions.List());
3995 :
3996 98 : int bHasSrcNoData = false;
3997 : const double dfSrcNoDataValue =
3998 98 : m_poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasSrcNoData);
3999 :
4000 : const bool bLastSrcBandIsAlpha =
4001 167 : (m_poSrcDS->GetRasterCount() > 1 &&
4002 69 : m_poSrcDS->GetRasterBand(m_poSrcDS->GetRasterCount())
4003 69 : ->GetColorInterpretation() == GCI_AlphaBand);
4004 :
4005 98 : const bool bOutputSupportsAlpha = !EQUAL(m_outputFormat.c_str(), "JPEG");
4006 98 : const bool bOutputSupportsNoData = EQUAL(m_outputFormat.c_str(), "GTiff");
4007 98 : const bool bDstNoDataSpecified = GetArg("dst-nodata")->IsExplicitlySet();
4008 : auto poColorTable = std::unique_ptr<GDALColorTable>(
4009 98 : [this]()
4010 : {
4011 98 : auto poCT = m_poSrcDS->GetRasterBand(1)->GetColorTable();
4012 98 : return poCT ? poCT->Clone() : nullptr;
4013 196 : }());
4014 :
4015 98 : const bool bUserAskedForAlpha = m_addalpha;
4016 98 : if (!m_noalpha && !m_addalpha)
4017 : {
4018 108 : m_addalpha = !(bHasSrcNoData && bOutputSupportsNoData) &&
4019 108 : !bDstNoDataSpecified && poColorTable == nullptr;
4020 : }
4021 98 : m_addalpha &= bOutputSupportsAlpha;
4022 :
4023 98 : psWO->nBandCount = m_poSrcDS->GetRasterCount();
4024 98 : if (bLastSrcBandIsAlpha)
4025 : {
4026 5 : --psWO->nBandCount;
4027 5 : psWO->nSrcAlphaBand = m_poSrcDS->GetRasterCount();
4028 : }
4029 :
4030 98 : if (bHasSrcNoData)
4031 : {
4032 26 : psWO->padfSrcNoDataReal =
4033 13 : static_cast<double *>(CPLCalloc(psWO->nBandCount, sizeof(double)));
4034 32 : for (int i = 0; i < psWO->nBandCount; ++i)
4035 : {
4036 19 : psWO->padfSrcNoDataReal[i] = dfSrcNoDataValue;
4037 : }
4038 : }
4039 :
4040 98 : if ((bHasSrcNoData && !m_addalpha && bOutputSupportsNoData) ||
4041 : bDstNoDataSpecified)
4042 : {
4043 18 : psWO->padfDstNoDataReal =
4044 9 : static_cast<double *>(CPLCalloc(psWO->nBandCount, sizeof(double)));
4045 18 : for (int i = 0; i < psWO->nBandCount; ++i)
4046 : {
4047 9 : psWO->padfDstNoDataReal[i] =
4048 9 : bDstNoDataSpecified ? m_dstNoData : dfSrcNoDataValue;
4049 : }
4050 : }
4051 :
4052 98 : psWO->eWorkingDataType = eSrcDT;
4053 :
4054 98 : GDALGetWarpResampleAlg(m_resampling.c_str(), psWO->eResampleAlg);
4055 :
4056 : /* -------------------------------------------------------------------- */
4057 : /* Setup band mapping. */
4058 : /* -------------------------------------------------------------------- */
4059 :
4060 196 : psWO->panSrcBands =
4061 98 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
4062 196 : psWO->panDstBands =
4063 98 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
4064 :
4065 65867 : for (int i = 0; i < psWO->nBandCount; i++)
4066 : {
4067 65769 : psWO->panSrcBands[i] = i + 1;
4068 65769 : psWO->panDstBands[i] = i + 1;
4069 : }
4070 :
4071 98 : if (m_addalpha)
4072 85 : psWO->nDstAlphaBand = psWO->nBandCount + 1;
4073 :
4074 : const int nDstBands =
4075 98 : psWO->nDstAlphaBand ? psWO->nDstAlphaBand : psWO->nBandCount;
4076 :
4077 196 : std::vector<GByte> dstBuffer;
4078 : const uint64_t dstBufferSize =
4079 98 : static_cast<uint64_t>(tileMatrix.mTileWidth) * tileMatrix.mTileHeight *
4080 98 : nDstBands * GDALGetDataTypeSizeBytes(psWO->eWorkingDataType);
4081 : const uint64_t nUsableRAM =
4082 98 : std::min<uint64_t>(INT_MAX, CPLGetUsablePhysicalRAM() / 4);
4083 98 : if (dstBufferSize <=
4084 98 : (nUsableRAM ? nUsableRAM : static_cast<uint64_t>(INT_MAX)))
4085 : {
4086 : try
4087 : {
4088 97 : dstBuffer.resize(static_cast<size_t>(dstBufferSize));
4089 : }
4090 0 : catch (const std::exception &)
4091 : {
4092 : }
4093 : }
4094 98 : if (dstBuffer.size() < dstBufferSize)
4095 : {
4096 1 : ReportError(CE_Failure, CPLE_AppDefined,
4097 : "Tile size and/or number of bands too large compared to "
4098 : "available RAM");
4099 1 : return false;
4100 : }
4101 :
4102 : FakeMaxZoomDataset oFakeMaxZoomDS(
4103 97 : (nMaxTileX - nMinTileX + 1) * tileMatrix.mTileWidth,
4104 97 : (nMaxTileY - nMinTileY + 1) * tileMatrix.mTileHeight, nDstBands,
4105 97 : tileMatrix.mTileWidth, tileMatrix.mTileHeight, psWO->eWorkingDataType,
4106 194 : dstGT, oSRS_TMS, dstBuffer);
4107 97 : CPL_IGNORE_RET_VAL(oFakeMaxZoomDS.GetSpatialRef());
4108 :
4109 97 : psWO->hSrcDS = GDALDataset::ToHandle(m_poSrcDS);
4110 97 : psWO->hDstDS = GDALDataset::ToHandle(&oFakeMaxZoomDS);
4111 :
4112 97 : std::unique_ptr<GDALDataset> tmpSrcDS;
4113 97 : if (m_tilingScheme == "raster" && !bHasNorthUpSrcGT)
4114 : {
4115 1 : CPLStringList aosOptions;
4116 1 : aosOptions.AddString("-of");
4117 1 : aosOptions.AddString("VRT");
4118 1 : aosOptions.AddString("-a_ullr");
4119 1 : aosOptions.AddString(CPLSPrintf("%.17g", srcGTModif[0]));
4120 1 : aosOptions.AddString(CPLSPrintf("%.17g", srcGTModif[3]));
4121 : aosOptions.AddString(
4122 1 : CPLSPrintf("%.17g", srcGTModif[0] + nSrcWidth * srcGTModif[1]));
4123 : aosOptions.AddString(
4124 1 : CPLSPrintf("%.17g", srcGTModif[3] + nSrcHeight * srcGTModif[5]));
4125 1 : if (oSRS_TMS.IsEmpty())
4126 : {
4127 1 : aosOptions.AddString("-a_srs");
4128 1 : aosOptions.AddString("none");
4129 : }
4130 :
4131 : GDALTranslateOptions *psOptions =
4132 1 : GDALTranslateOptionsNew(aosOptions.List(), nullptr);
4133 :
4134 1 : tmpSrcDS.reset(GDALDataset::FromHandle(GDALTranslate(
4135 : "", GDALDataset::ToHandle(m_poSrcDS), psOptions, nullptr)));
4136 1 : GDALTranslateOptionsFree(psOptions);
4137 1 : if (!tmpSrcDS)
4138 0 : return false;
4139 : }
4140 98 : hTransformArg.reset(GDALCreateGenImgProjTransformer2(
4141 98 : tmpSrcDS ? tmpSrcDS.get() : m_poSrcDS, &oFakeMaxZoomDS, aosTO.List()));
4142 97 : CPLAssert(hTransformArg);
4143 :
4144 : /* -------------------------------------------------------------------- */
4145 : /* Warp the transformer with a linear approximator */
4146 : /* -------------------------------------------------------------------- */
4147 97 : hTransformArg.reset(GDALCreateApproxTransformer(
4148 : GDALGenImgProjTransform, hTransformArg.release(), 0.125));
4149 97 : GDALApproxTransformerOwnsSubtransformer(hTransformArg.get(), TRUE);
4150 :
4151 97 : psWO->pfnTransformer = GDALApproxTransform;
4152 97 : psWO->pTransformerArg = hTransformArg.get();
4153 :
4154 : /* -------------------------------------------------------------------- */
4155 : /* Determine total number of tiles */
4156 : /* -------------------------------------------------------------------- */
4157 97 : const int nBaseTilesPerRow = nMaxTileX - nMinTileX + 1;
4158 97 : const int nBaseTilesPerCol = nMaxTileY - nMinTileY + 1;
4159 97 : const uint64_t nBaseTiles =
4160 97 : static_cast<uint64_t>(nBaseTilesPerCol) * nBaseTilesPerRow;
4161 97 : uint64_t nTotalTiles = nBaseTiles;
4162 97 : std::atomic<uint64_t> nCurTile = 0;
4163 97 : bool bRet = true;
4164 :
4165 204 : for (int iZ = m_maxZoomLevel - 1;
4166 204 : bRet && bIntersects && iZ >= m_minZoomLevel; --iZ)
4167 : {
4168 214 : auto ovrTileMatrix = tileMatrixList[iZ];
4169 107 : int nOvrMinTileX = 0;
4170 107 : int nOvrMinTileY = 0;
4171 107 : int nOvrMaxTileX = 0;
4172 107 : int nOvrMaxTileY = 0;
4173 107 : bRet =
4174 214 : GetTileIndices(ovrTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
4175 : nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX,
4176 107 : nOvrMaxTileY, m_noIntersectionIsOK, bIntersects);
4177 107 : if (bIntersects)
4178 : {
4179 107 : nTotalTiles +=
4180 107 : static_cast<uint64_t>(nOvrMaxTileY - nOvrMinTileY + 1) *
4181 107 : (nOvrMaxTileX - nOvrMinTileX + 1);
4182 : }
4183 : }
4184 :
4185 : /* -------------------------------------------------------------------- */
4186 : /* Generate tiles at max zoom level */
4187 : /* -------------------------------------------------------------------- */
4188 194 : GDALWarpOperation oWO;
4189 :
4190 97 : bRet = oWO.Initialize(psWO.get()) == CE_None && bRet;
4191 :
4192 : const auto GetUpdatedCreationOptions =
4193 386 : [this](const gdal::TileMatrixSet::TileMatrix &oTM)
4194 : {
4195 136 : CPLStringList aosCreationOptions(m_creationOptions);
4196 136 : if (m_outputFormat == "GTiff")
4197 : {
4198 48 : if (aosCreationOptions.FetchNameValue("TILED") == nullptr &&
4199 24 : aosCreationOptions.FetchNameValue("BLOCKYSIZE") == nullptr)
4200 : {
4201 24 : if (oTM.mTileWidth <= 512 && oTM.mTileHeight <= 512)
4202 : {
4203 22 : aosCreationOptions.SetNameValue(
4204 22 : "BLOCKYSIZE", CPLSPrintf("%d", oTM.mTileHeight));
4205 : }
4206 : else
4207 : {
4208 2 : aosCreationOptions.SetNameValue("TILED", "YES");
4209 : }
4210 : }
4211 24 : if (aosCreationOptions.FetchNameValue("COMPRESS") == nullptr)
4212 24 : aosCreationOptions.SetNameValue("COMPRESS", "LZW");
4213 : }
4214 112 : else if (m_outputFormat == "COG")
4215 : {
4216 2 : if (aosCreationOptions.FetchNameValue("OVERVIEW_RESAMPLING") ==
4217 : nullptr)
4218 : {
4219 : aosCreationOptions.SetNameValue("OVERVIEW_RESAMPLING",
4220 2 : m_overviewResampling.c_str());
4221 : }
4222 2 : if (aosCreationOptions.FetchNameValue("BLOCKSIZE") == nullptr &&
4223 2 : oTM.mTileWidth <= 512 && oTM.mTileWidth == oTM.mTileHeight)
4224 : {
4225 : aosCreationOptions.SetNameValue(
4226 2 : "BLOCKSIZE", CPLSPrintf("%d", oTM.mTileWidth));
4227 : }
4228 : }
4229 136 : return aosCreationOptions;
4230 97 : };
4231 :
4232 97 : VSIMkdir(m_outputDirectory.c_str(), 0755);
4233 : VSIStatBufL sStat;
4234 193 : if (VSIStatL(m_outputDirectory.c_str(), &sStat) != 0 ||
4235 96 : !VSI_ISDIR(sStat.st_mode))
4236 : {
4237 1 : ReportError(CE_Failure, CPLE_FileIO,
4238 : "Cannot create output directory %s",
4239 : m_outputDirectory.c_str());
4240 1 : return false;
4241 : }
4242 :
4243 192 : OGRSpatialReference oWGS84;
4244 96 : oWGS84.importFromEPSG(4326);
4245 96 : oWGS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
4246 :
4247 96 : std::unique_ptr<OGRCoordinateTransformation> poCTToWGS84;
4248 96 : if (!oSRS_TMS.IsEmpty())
4249 : {
4250 190 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
4251 95 : poCTToWGS84.reset(
4252 : OGRCreateCoordinateTransformation(&oSRS_TMS, &oWGS84));
4253 : }
4254 :
4255 98 : const bool kmlCompatible = m_kml &&
4256 17 : [this, &poTMS, &poCTToWGS84, bInvertAxisTMS]()
4257 : {
4258 2 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
4259 2 : double dfX = poTMS->tileMatrixList()[0].mTopLeftX;
4260 2 : double dfY = poTMS->tileMatrixList()[0].mTopLeftY;
4261 2 : if (bInvertAxisTMS)
4262 0 : std::swap(dfX, dfY);
4263 3 : return (m_minZoomLevel == m_maxZoomLevel ||
4264 1 : (poTMS->haveAllLevelsSameTopLeft() &&
4265 1 : poTMS->haveAllLevelsSameTileSize() &&
4266 3 : poTMS->hasOnlyPowerOfTwoVaryingScales())) &&
4267 7 : poCTToWGS84 && poCTToWGS84->Transform(1, &dfX, &dfY);
4268 2 : }();
4269 : const int kmlTileSize =
4270 96 : m_tileSize > 0 ? m_tileSize : poTMS->tileMatrixList()[0].mTileWidth;
4271 96 : if (m_kml && !kmlCompatible)
4272 : {
4273 0 : ReportError(CE_Failure, CPLE_NotSupported,
4274 : "Tiling scheme not compatible with KML output");
4275 0 : return false;
4276 : }
4277 :
4278 96 : if (m_title.empty())
4279 94 : m_title = CPLGetFilename(m_dataset.GetName().c_str());
4280 :
4281 96 : if (!m_url.empty())
4282 : {
4283 2 : if (m_url.back() != '/')
4284 2 : m_url += '/';
4285 4 : std::string out_path = m_outputDirectory;
4286 2 : if (m_outputDirectory.back() == '/')
4287 0 : out_path.pop_back();
4288 2 : m_url += CPLGetFilename(out_path.c_str());
4289 : }
4290 :
4291 192 : CPLWorkerThreadPool oThreadPool;
4292 :
4293 96 : bool bThreadPoolInitialized = false;
4294 : const auto InitThreadPool =
4295 474 : [this, &oThreadPool, &bRet, &bThreadPoolInitialized]()
4296 : {
4297 136 : if (!bThreadPoolInitialized)
4298 : {
4299 95 : bThreadPoolInitialized = true;
4300 :
4301 95 : if (bRet && m_numThreads > 1)
4302 : {
4303 6 : CPLDebug("gdal_raster_tile", "Using %d threads", m_numThreads);
4304 6 : bRet = oThreadPool.Setup(m_numThreads, nullptr, nullptr);
4305 : }
4306 : }
4307 :
4308 136 : return bRet;
4309 96 : };
4310 :
4311 : // Just for unit test purposes
4312 96 : const bool bEmitSpuriousCharsOnStdout = CPLTestBool(
4313 : CPLGetConfigOption("GDAL_RASTER_TILE_EMIT_SPURIOUS_CHARS", "NO"));
4314 :
4315 32 : const auto IsCompatibleOfSpawnSilent = [bSrcIsFineForFork, this]()
4316 : {
4317 8 : const char *pszErrorMsg = "";
4318 : {
4319 8 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
4320 8 : if (IsCompatibleOfSpawn(pszErrorMsg))
4321 : {
4322 1 : m_parallelMethod = "spawn";
4323 1 : return true;
4324 : }
4325 : }
4326 : (void)bSrcIsFineForFork;
4327 : #ifdef FORK_ALLOWED
4328 14 : if (bSrcIsFineForFork &&
4329 7 : !cpl::starts_with(m_outputDirectory, "/vsimem/"))
4330 : {
4331 4 : if (CPLGetCurrentThreadCount() == 1)
4332 : {
4333 1 : CPLDebugOnce(
4334 : "gdal_raster_tile",
4335 : "'gdal' binary not found. Using instead "
4336 : "parallel-method=fork. If causing instability issues, set "
4337 : "parallel-method to 'thread' or 'spawn'");
4338 1 : m_parallelMethod = "fork";
4339 1 : return true;
4340 : }
4341 : }
4342 : #endif
4343 6 : return false;
4344 96 : };
4345 :
4346 96 : m_numThreads = std::max(
4347 192 : 1, static_cast<int>(std::min<uint64_t>(
4348 96 : m_numThreads, nBaseTiles / GetThresholdMinTilesPerJob())));
4349 :
4350 96 : std::atomic<bool> bParentAskedForStop = false;
4351 192 : std::thread threadWaitForParentStop;
4352 96 : if (m_spawned)
4353 : {
4354 52 : threadWaitForParentStop = std::thread(
4355 52 : [&bParentAskedForStop]()
4356 : {
4357 26 : char szBuffer[81] = {0};
4358 26 : while (fgets(szBuffer, 80, stdin))
4359 : {
4360 26 : if (strcmp(szBuffer, STOP_MARKER) == 0)
4361 : {
4362 26 : bParentAskedForStop = true;
4363 26 : break;
4364 : }
4365 : else
4366 : {
4367 0 : CPLError(CE_Failure, CPLE_AppDefined,
4368 : "Got unexpected input from parent '%s'",
4369 : szBuffer);
4370 : }
4371 : }
4372 52 : });
4373 : }
4374 : #ifdef FORK_ALLOWED
4375 70 : else if (m_forked)
4376 : {
4377 0 : threadWaitForParentStop = std::thread(
4378 0 : [&bParentAskedForStop]()
4379 : {
4380 0 : std::string buffer;
4381 0 : buffer.resize(strlen(STOP_MARKER));
4382 0 : if (CPLPipeRead(pipeIn, buffer.data(),
4383 0 : static_cast<int>(strlen(STOP_MARKER))) &&
4384 0 : buffer == STOP_MARKER)
4385 : {
4386 0 : bParentAskedForStop = true;
4387 : }
4388 : else
4389 : {
4390 0 : CPLError(CE_Failure, CPLE_AppDefined,
4391 : "Got unexpected input from parent '%s'",
4392 : buffer.c_str());
4393 : }
4394 0 : });
4395 : }
4396 : #endif
4397 :
4398 96 : if (m_ovrZoomLevel >= 0)
4399 : {
4400 : // do not generate base tiles if called as a child process with
4401 : // --ovr-zoom-level
4402 : }
4403 102 : else if (m_numThreads > 1 && nBaseTiles > 1 &&
4404 11 : ((m_parallelMethod.empty() &&
4405 7 : m_numThreads >= GetThresholdMinThreadsForSpawn() &&
4406 13 : IsCompatibleOfSpawnSilent()) ||
4407 16 : (m_parallelMethod == "spawn" || m_parallelMethod == "fork")))
4408 : {
4409 5 : if (!GenerateBaseTilesSpawnMethod(nBaseTilesPerCol, nBaseTilesPerRow,
4410 : nMinTileX, nMinTileY, nMaxTileX,
4411 : nMaxTileY, nTotalTiles, nBaseTiles,
4412 : pfnProgress, pProgressData))
4413 : {
4414 1 : return false;
4415 : }
4416 4 : nCurTile = nBaseTiles;
4417 : }
4418 : else
4419 : {
4420 : // Branch for multi-threaded or single-threaded max zoom level tile
4421 : // generation
4422 :
4423 : PerThreadMaxZoomResourceManager oResourceManager(
4424 75 : m_poSrcDS, psWO.get(), hTransformArg.get(), oFakeMaxZoomDS,
4425 225 : dstBuffer.size());
4426 :
4427 : const CPLStringList aosCreationOptions(
4428 150 : GetUpdatedCreationOptions(tileMatrix));
4429 :
4430 75 : CPLDebug("gdal_raster_tile",
4431 : "Generating tiles z=%d, y=%d...%d, x=%d...%d", m_maxZoomLevel,
4432 : nMinTileY, nMaxTileY, nMinTileX, nMaxTileX);
4433 :
4434 75 : bRet &= InitThreadPool();
4435 :
4436 75 : if (bRet && m_numThreads > 1)
4437 : {
4438 6 : std::atomic<bool> bFailure = false;
4439 6 : std::atomic<int> nQueuedJobs = 0;
4440 :
4441 : double dfTilesYPerJob;
4442 : int nYOuterIterations;
4443 : double dfTilesXPerJob;
4444 : int nXOuterIterations;
4445 6 : ComputeJobChunkSize(m_numThreads, nBaseTilesPerCol,
4446 : nBaseTilesPerRow, dfTilesYPerJob,
4447 : nYOuterIterations, dfTilesXPerJob,
4448 : nXOuterIterations);
4449 :
4450 6 : CPLDebugOnly("gdal_raster_tile",
4451 : "nYOuterIterations=%d, dfTilesYPerJob=%g, "
4452 : "nXOuterIterations=%d, dfTilesXPerJob=%g",
4453 : nYOuterIterations, dfTilesYPerJob, nXOuterIterations,
4454 : dfTilesXPerJob);
4455 :
4456 6 : int nLastYEndIncluded = nMinTileY - 1;
4457 30 : for (int iYOuterIter = 0; bRet && iYOuterIter < nYOuterIterations &&
4458 24 : nLastYEndIncluded < nMaxTileY;
4459 : ++iYOuterIter)
4460 : {
4461 24 : const int iYStart = nLastYEndIncluded + 1;
4462 : const int iYEndIncluded =
4463 24 : iYOuterIter + 1 == nYOuterIterations
4464 42 : ? nMaxTileY
4465 : : std::max(
4466 : iYStart,
4467 42 : static_cast<int>(std::floor(
4468 18 : nMinTileY +
4469 18 : (iYOuterIter + 1) * dfTilesYPerJob - 1)));
4470 :
4471 24 : nLastYEndIncluded = iYEndIncluded;
4472 :
4473 24 : int nLastXEndIncluded = nMinTileX - 1;
4474 24 : for (int iXOuterIter = 0;
4475 48 : bRet && iXOuterIter < nXOuterIterations &&
4476 24 : nLastXEndIncluded < nMaxTileX;
4477 : ++iXOuterIter)
4478 : {
4479 24 : const int iXStart = nLastXEndIncluded + 1;
4480 : const int iXEndIncluded =
4481 24 : iXOuterIter + 1 == nXOuterIterations
4482 24 : ? nMaxTileX
4483 : : std::max(
4484 : iXStart,
4485 24 : static_cast<int>(std::floor(
4486 0 : nMinTileX +
4487 0 : (iXOuterIter + 1) * dfTilesXPerJob - 1)));
4488 :
4489 24 : nLastXEndIncluded = iXEndIncluded;
4490 :
4491 24 : CPLDebugOnly("gdal_raster_tile",
4492 : "Job for y in [%d,%d] and x in [%d,%d]",
4493 : iYStart, iYEndIncluded, iXStart,
4494 : iXEndIncluded);
4495 :
4496 24 : auto job = [this, &oThreadPool, &oResourceManager,
4497 : &bFailure, &bParentAskedForStop, &nCurTile,
4498 : &nQueuedJobs, pszExtension, &aosCreationOptions,
4499 : &psWO, &tileMatrix, nDstBands, iXStart,
4500 : iXEndIncluded, iYStart, iYEndIncluded,
4501 : nMinTileX, nMinTileY, &poColorTable,
4502 2090 : bUserAskedForAlpha]()
4503 : {
4504 24 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
4505 :
4506 23 : auto resources = oResourceManager.AcquireResources();
4507 24 : if (resources)
4508 : {
4509 60 : for (int iY = iYStart;
4510 60 : iY <= iYEndIncluded && !bParentAskedForStop;
4511 : ++iY)
4512 : {
4513 462 : for (int iX = iXStart; iX <= iXEndIncluded &&
4514 219 : !bParentAskedForStop;
4515 : ++iX)
4516 : {
4517 621 : if (!GenerateTile(
4518 207 : resources->poSrcDS.get(),
4519 : m_poDstDriver, pszExtension,
4520 : aosCreationOptions.List(),
4521 207 : *(resources->poWO.get()),
4522 207 : *(resources->poFakeMaxZoomDS
4523 207 : ->GetSpatialRef()),
4524 207 : psWO->eWorkingDataType, tileMatrix,
4525 207 : m_outputDirectory, nDstBands,
4526 207 : psWO->padfDstNoDataReal
4527 0 : ? &(psWO->padfDstNoDataReal[0])
4528 : : nullptr,
4529 : m_maxZoomLevel, iX, iY,
4530 207 : m_convention, nMinTileX, nMinTileY,
4531 207 : m_skipBlank, bUserAskedForAlpha,
4532 207 : m_auxXML, m_resume, m_metadata,
4533 207 : poColorTable.get(),
4534 207 : resources->dstBuffer))
4535 : {
4536 0 : oResourceManager.SetError();
4537 0 : bFailure = true;
4538 0 : --nQueuedJobs;
4539 0 : return;
4540 : }
4541 207 : ++nCurTile;
4542 207 : oThreadPool.WakeUpWaitEvent();
4543 : }
4544 : }
4545 24 : oResourceManager.ReleaseResources(
4546 24 : std::move(resources));
4547 : }
4548 : else
4549 : {
4550 0 : oResourceManager.SetError();
4551 0 : bFailure = true;
4552 : }
4553 :
4554 24 : --nQueuedJobs;
4555 24 : };
4556 :
4557 24 : ++nQueuedJobs;
4558 24 : oThreadPool.SubmitJob(std::move(job));
4559 : }
4560 : }
4561 :
4562 : // Wait for completion of all jobs
4563 210 : while (bRet && nQueuedJobs > 0)
4564 : {
4565 204 : oThreadPool.WaitEvent();
4566 204 : bRet &= !bFailure;
4567 273 : if (bRet && pfnProgress &&
4568 138 : !pfnProgress(static_cast<double>(nCurTile) /
4569 69 : static_cast<double>(nTotalTiles),
4570 : "", pProgressData))
4571 : {
4572 3 : bParentAskedForStop = true;
4573 3 : bRet = false;
4574 3 : CPLError(CE_Failure, CPLE_UserInterrupt,
4575 : "Process interrupted by user");
4576 : }
4577 : }
4578 6 : oThreadPool.WaitCompletion();
4579 6 : bRet &=
4580 10 : !bFailure && (!pfnProgress ||
4581 8 : pfnProgress(static_cast<double>(nCurTile) /
4582 4 : static_cast<double>(nTotalTiles),
4583 6 : "", pProgressData));
4584 :
4585 6 : if (!oResourceManager.GetErrorMsg().empty())
4586 : {
4587 : // Re-emit error message from worker thread to main thread
4588 0 : ReportError(CE_Failure, CPLE_AppDefined, "%s",
4589 0 : oResourceManager.GetErrorMsg().c_str());
4590 6 : }
4591 : }
4592 : else
4593 : {
4594 : // Branch for single-thread max zoom level tile generation
4595 174 : for (int iY = nMinTileY;
4596 174 : bRet && !bParentAskedForStop && iY <= nMaxTileY; ++iY)
4597 : {
4598 420 : for (int iX = nMinTileX;
4599 420 : bRet && !bParentAskedForStop && iX <= nMaxTileX; ++iX)
4600 : {
4601 630 : bRet = GenerateTile(
4602 : m_poSrcDS, m_poDstDriver, pszExtension,
4603 : aosCreationOptions.List(), oWO, oSRS_TMS,
4604 315 : psWO->eWorkingDataType, tileMatrix, m_outputDirectory,
4605 : nDstBands,
4606 315 : psWO->padfDstNoDataReal ? &(psWO->padfDstNoDataReal[0])
4607 : : nullptr,
4608 315 : m_maxZoomLevel, iX, iY, m_convention, nMinTileX,
4609 315 : nMinTileY, m_skipBlank, bUserAskedForAlpha, m_auxXML,
4610 315 : m_resume, m_metadata, poColorTable.get(), dstBuffer);
4611 :
4612 315 : if (m_spawned)
4613 : {
4614 130 : if (bEmitSpuriousCharsOnStdout)
4615 64 : fwrite(&PROGRESS_MARKER[0], 1, 1, stdout);
4616 130 : fwrite(PROGRESS_MARKER, sizeof(PROGRESS_MARKER), 1,
4617 : stdout);
4618 130 : fflush(stdout);
4619 : }
4620 : #ifdef FORK_ALLOWED
4621 185 : else if (m_forked)
4622 : {
4623 0 : CPLPipeWrite(pipeOut, PROGRESS_MARKER,
4624 : sizeof(PROGRESS_MARKER));
4625 : }
4626 : #endif
4627 : else
4628 : {
4629 185 : ++nCurTile;
4630 188 : if (bRet && pfnProgress &&
4631 6 : !pfnProgress(static_cast<double>(nCurTile) /
4632 3 : static_cast<double>(nTotalTiles),
4633 : "", pProgressData))
4634 : {
4635 1 : bRet = false;
4636 1 : CPLError(CE_Failure, CPLE_UserInterrupt,
4637 : "Process interrupted by user");
4638 : }
4639 : }
4640 : }
4641 : }
4642 : }
4643 :
4644 75 : if (m_kml && bRet)
4645 : {
4646 4 : for (int iY = nMinTileY; iY <= nMaxTileY; ++iY)
4647 : {
4648 4 : for (int iX = nMinTileX; iX <= nMaxTileX; ++iX)
4649 : {
4650 : const int nFileY =
4651 2 : GetFileY(iY, poTMS->tileMatrixList()[m_maxZoomLevel],
4652 2 : m_convention);
4653 : std::string osFilename = CPLFormFilenameSafe(
4654 : m_outputDirectory.c_str(),
4655 4 : CPLSPrintf("%d", m_maxZoomLevel), nullptr);
4656 4 : osFilename = CPLFormFilenameSafe(
4657 2 : osFilename.c_str(), CPLSPrintf("%d", iX), nullptr);
4658 4 : osFilename = CPLFormFilenameSafe(
4659 : osFilename.c_str(),
4660 2 : CPLSPrintf("%d.%s", nFileY, pszExtension), nullptr);
4661 2 : if (VSIStatL(osFilename.c_str(), &sStat) == 0)
4662 : {
4663 4 : GenerateKML(m_outputDirectory, m_title, iX, iY,
4664 : m_maxZoomLevel, kmlTileSize, pszExtension,
4665 2 : m_url, poTMS.get(), bInvertAxisTMS,
4666 2 : m_convention, poCTToWGS84.get(), {});
4667 : }
4668 : }
4669 : }
4670 : }
4671 : }
4672 :
4673 : // Close source dataset if we have opened it (in GDALAlgorithm core code),
4674 : // to free file descriptors, particularly if it is a VRT file.
4675 95 : std::vector<GDALColorInterp> aeColorInterp;
4676 327 : for (int i = 1; i <= m_poSrcDS->GetRasterCount(); ++i)
4677 232 : aeColorInterp.push_back(
4678 232 : m_poSrcDS->GetRasterBand(i)->GetColorInterpretation());
4679 95 : if (m_dataset.HasDatasetBeenOpenedByAlgorithm())
4680 : {
4681 56 : m_dataset.Close();
4682 56 : m_poSrcDS = nullptr;
4683 : }
4684 :
4685 : /* -------------------------------------------------------------------- */
4686 : /* Generate tiles at lower zoom levels */
4687 : /* -------------------------------------------------------------------- */
4688 95 : const int iZStart =
4689 95 : m_ovrZoomLevel >= 0 ? m_ovrZoomLevel : m_maxZoomLevel - 1;
4690 95 : const int iZEnd = m_ovrZoomLevel >= 0 ? m_ovrZoomLevel : m_minZoomLevel;
4691 164 : for (int iZ = iZStart; bRet && iZ >= iZEnd; --iZ)
4692 : {
4693 69 : int nOvrMinTileX = 0;
4694 69 : int nOvrMinTileY = 0;
4695 69 : int nOvrMaxTileX = 0;
4696 69 : int nOvrMaxTileY = 0;
4697 :
4698 138 : auto ovrTileMatrix = tileMatrixList[iZ];
4699 69 : CPL_IGNORE_RET_VAL(
4700 69 : GetTileIndices(ovrTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
4701 : nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX,
4702 69 : nOvrMaxTileY, m_noIntersectionIsOK, bIntersects));
4703 :
4704 69 : bRet = bIntersects;
4705 :
4706 69 : if (m_minOvrTileX >= 0)
4707 : {
4708 16 : bRet = true;
4709 16 : nOvrMinTileX = m_minOvrTileX;
4710 16 : nOvrMinTileY = m_minOvrTileY;
4711 16 : nOvrMaxTileX = m_maxOvrTileX;
4712 16 : nOvrMaxTileY = m_maxOvrTileY;
4713 : }
4714 :
4715 69 : if (bRet)
4716 : {
4717 69 : CPLDebug("gdal_raster_tile",
4718 : "Generating overview tiles z=%d, y=%d...%d, x=%d...%d", iZ,
4719 : nOvrMinTileY, nOvrMaxTileY, nOvrMinTileX, nOvrMaxTileX);
4720 : }
4721 :
4722 69 : const int nOvrTilesPerCol = nOvrMaxTileY - nOvrMinTileY + 1;
4723 69 : const int nOvrTilesPerRow = nOvrMaxTileX - nOvrMinTileX + 1;
4724 69 : const uint64_t nOvrTileCount =
4725 69 : static_cast<uint64_t>(nOvrTilesPerCol) * nOvrTilesPerRow;
4726 :
4727 69 : m_numThreads = std::max(
4728 138 : 1,
4729 138 : static_cast<int>(std::min<uint64_t>(
4730 69 : m_numThreads, nOvrTileCount / GetThresholdMinTilesPerJob())));
4731 :
4732 97 : if (m_numThreads > 1 && nOvrTileCount > 1 &&
4733 14 : ((m_parallelMethod.empty() &&
4734 6 : m_numThreads >= GetThresholdMinThreadsForSpawn() &&
4735 18 : IsCompatibleOfSpawnSilent()) ||
4736 24 : (m_parallelMethod == "spawn" || m_parallelMethod == "fork")))
4737 : {
4738 8 : bRet &= GenerateOverviewTilesSpawnMethod(
4739 : iZ, nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX, nOvrMaxTileY,
4740 8 : nCurTile, nTotalTiles, pfnProgress, pProgressData);
4741 : }
4742 : else
4743 : {
4744 61 : bRet &= InitThreadPool();
4745 :
4746 122 : auto srcTileMatrix = tileMatrixList[iZ + 1];
4747 61 : int nSrcMinTileX = 0;
4748 61 : int nSrcMinTileY = 0;
4749 61 : int nSrcMaxTileX = 0;
4750 61 : int nSrcMaxTileY = 0;
4751 :
4752 61 : CPL_IGNORE_RET_VAL(GetTileIndices(
4753 : srcTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
4754 : nSrcMinTileX, nSrcMinTileY, nSrcMaxTileX, nSrcMaxTileY,
4755 61 : m_noIntersectionIsOK, bIntersects));
4756 :
4757 61 : constexpr double EPSILON = 1e-3;
4758 61 : int maxCacheTileSizePerThread = static_cast<int>(
4759 61 : (1 + std::ceil(
4760 61 : (ovrTileMatrix.mResY * ovrTileMatrix.mTileHeight) /
4761 61 : (srcTileMatrix.mResY * srcTileMatrix.mTileHeight) -
4762 61 : EPSILON)) *
4763 61 : (1 + std::ceil(
4764 61 : (ovrTileMatrix.mResX * ovrTileMatrix.mTileWidth) /
4765 61 : (srcTileMatrix.mResX * srcTileMatrix.mTileWidth) -
4766 : EPSILON)));
4767 :
4768 61 : CPLDebugOnly("gdal_raster_tile",
4769 : "Ideal maxCacheTileSizePerThread = %d",
4770 : maxCacheTileSizePerThread);
4771 :
4772 : #ifndef _WIN32
4773 : const int remainingFileDescriptorCount =
4774 61 : CPLGetRemainingFileDescriptorCount();
4775 61 : CPLDebugOnly("gdal_raster_tile",
4776 : "remainingFileDescriptorCount = %d",
4777 : remainingFileDescriptorCount);
4778 61 : if (remainingFileDescriptorCount >= 0 &&
4779 : remainingFileDescriptorCount <
4780 61 : (1 + maxCacheTileSizePerThread) * m_numThreads)
4781 : {
4782 : const int newNumThreads =
4783 0 : std::max(1, remainingFileDescriptorCount /
4784 0 : (1 + maxCacheTileSizePerThread));
4785 0 : if (newNumThreads < m_numThreads)
4786 : {
4787 0 : CPLError(CE_Warning, CPLE_AppDefined,
4788 : "Not enough file descriptors available given the "
4789 : "number of "
4790 : "threads. Reducing the number of threads %d to %d",
4791 : m_numThreads, newNumThreads);
4792 0 : m_numThreads = newNumThreads;
4793 : }
4794 : }
4795 : #endif
4796 :
4797 : MosaicDataset oSrcDS(
4798 122 : CPLFormFilenameSafe(m_outputDirectory.c_str(),
4799 : CPLSPrintf("%d", iZ + 1), nullptr),
4800 61 : pszExtension, m_outputFormat, aeColorInterp, srcTileMatrix,
4801 : oSRS_TMS, nSrcMinTileX, nSrcMinTileY, nSrcMaxTileX,
4802 61 : nSrcMaxTileY, m_convention, nDstBands, psWO->eWorkingDataType,
4803 9 : psWO->padfDstNoDataReal ? &(psWO->padfDstNoDataReal[0])
4804 : : nullptr,
4805 314 : m_metadata, poColorTable.get(), maxCacheTileSizePerThread);
4806 :
4807 : const CPLStringList aosCreationOptions(
4808 122 : GetUpdatedCreationOptions(ovrTileMatrix));
4809 :
4810 122 : PerThreadLowerZoomResourceManager oResourceManager(oSrcDS);
4811 61 : std::atomic<bool> bFailure = false;
4812 61 : std::atomic<int> nQueuedJobs = 0;
4813 :
4814 61 : const bool bUseThreads = m_numThreads > 1 && nOvrTileCount > 1;
4815 :
4816 61 : if (bUseThreads)
4817 : {
4818 : double dfTilesYPerJob;
4819 : int nYOuterIterations;
4820 : double dfTilesXPerJob;
4821 : int nXOuterIterations;
4822 6 : ComputeJobChunkSize(m_numThreads, nOvrTilesPerCol,
4823 : nOvrTilesPerRow, dfTilesYPerJob,
4824 : nYOuterIterations, dfTilesXPerJob,
4825 : nXOuterIterations);
4826 :
4827 6 : CPLDebugOnly("gdal_raster_tile",
4828 : "z=%d, nYOuterIterations=%d, dfTilesYPerJob=%g, "
4829 : "nXOuterIterations=%d, dfTilesXPerJob=%g",
4830 : iZ, nYOuterIterations, dfTilesYPerJob,
4831 : nXOuterIterations, dfTilesXPerJob);
4832 :
4833 6 : int nLastYEndIncluded = nOvrMinTileY - 1;
4834 24 : for (int iYOuterIter = 0;
4835 24 : bRet && iYOuterIter < nYOuterIterations &&
4836 18 : nLastYEndIncluded < nOvrMaxTileY;
4837 : ++iYOuterIter)
4838 : {
4839 18 : const int iYStart = nLastYEndIncluded + 1;
4840 : const int iYEndIncluded =
4841 18 : iYOuterIter + 1 == nYOuterIterations
4842 30 : ? nOvrMaxTileY
4843 : : std::max(
4844 : iYStart,
4845 30 : static_cast<int>(std::floor(
4846 12 : nOvrMinTileY +
4847 12 : (iYOuterIter + 1) * dfTilesYPerJob - 1)));
4848 :
4849 18 : nLastYEndIncluded = iYEndIncluded;
4850 :
4851 18 : int nLastXEndIncluded = nOvrMinTileX - 1;
4852 18 : for (int iXOuterIter = 0;
4853 42 : bRet && iXOuterIter < nXOuterIterations &&
4854 24 : nLastXEndIncluded < nOvrMaxTileX;
4855 : ++iXOuterIter)
4856 : {
4857 24 : const int iXStart = nLastXEndIncluded + 1;
4858 : const int iXEndIncluded =
4859 24 : iXOuterIter + 1 == nXOuterIterations
4860 30 : ? nOvrMaxTileX
4861 30 : : std::max(iXStart, static_cast<int>(std::floor(
4862 6 : nOvrMinTileX +
4863 6 : (iXOuterIter + 1) *
4864 : dfTilesXPerJob -
4865 6 : 1)));
4866 :
4867 24 : nLastXEndIncluded = iXEndIncluded;
4868 :
4869 24 : CPLDebugOnly(
4870 : "gdal_raster_tile",
4871 : "Job for z=%d, y in [%d,%d] and x in [%d,%d]", iZ,
4872 : iYStart, iYEndIncluded, iXStart, iXEndIncluded);
4873 : auto job =
4874 24 : [this, &oThreadPool, &oResourceManager, &bFailure,
4875 : &bParentAskedForStop, &nCurTile, &nQueuedJobs,
4876 : pszExtension, &aosCreationOptions, &aosWarpOptions,
4877 : &ovrTileMatrix, iZ, iXStart, iXEndIncluded,
4878 588 : iYStart, iYEndIncluded, bUserAskedForAlpha]()
4879 : {
4880 : CPLErrorStateBackuper oBackuper(
4881 24 : CPLQuietErrorHandler);
4882 :
4883 : auto resources =
4884 24 : oResourceManager.AcquireResources();
4885 24 : if (resources)
4886 : {
4887 72 : for (int iY = iYStart; iY <= iYEndIncluded &&
4888 24 : !bParentAskedForStop;
4889 : ++iY)
4890 : {
4891 84 : for (int iX = iXStart;
4892 144 : iX <= iXEndIncluded &&
4893 60 : !bParentAskedForStop;
4894 : ++iX)
4895 : {
4896 120 : if (!GenerateOverviewTile(
4897 60 : *(resources->poSrcDS.get()),
4898 60 : m_poDstDriver, m_outputFormat,
4899 : pszExtension,
4900 : aosCreationOptions.List(),
4901 60 : aosWarpOptions.List(),
4902 60 : m_overviewResampling,
4903 : ovrTileMatrix,
4904 60 : m_outputDirectory, iZ, iX, iY,
4905 60 : m_convention, m_skipBlank,
4906 60 : bUserAskedForAlpha, m_auxXML,
4907 60 : m_resume))
4908 : {
4909 0 : oResourceManager.SetError();
4910 0 : bFailure = true;
4911 0 : --nQueuedJobs;
4912 0 : return;
4913 : }
4914 :
4915 60 : ++nCurTile;
4916 60 : oThreadPool.WakeUpWaitEvent();
4917 : }
4918 : }
4919 24 : oResourceManager.ReleaseResources(
4920 24 : std::move(resources));
4921 : }
4922 : else
4923 : {
4924 0 : oResourceManager.SetError();
4925 0 : bFailure = true;
4926 : }
4927 24 : --nQueuedJobs;
4928 24 : };
4929 :
4930 24 : ++nQueuedJobs;
4931 24 : oThreadPool.SubmitJob(std::move(job));
4932 : }
4933 : }
4934 :
4935 : // Wait for completion of all jobs
4936 84 : while (bRet && nQueuedJobs > 0)
4937 : {
4938 78 : oThreadPool.WaitEvent();
4939 78 : bRet &= !bFailure;
4940 103 : if (bRet && pfnProgress &&
4941 50 : !pfnProgress(static_cast<double>(nCurTile) /
4942 25 : static_cast<double>(nTotalTiles),
4943 : "", pProgressData))
4944 : {
4945 0 : bParentAskedForStop = true;
4946 0 : bRet = false;
4947 0 : CPLError(CE_Failure, CPLE_UserInterrupt,
4948 : "Process interrupted by user");
4949 : }
4950 : }
4951 6 : oThreadPool.WaitCompletion();
4952 8 : bRet &= !bFailure &&
4953 2 : (!pfnProgress ||
4954 4 : pfnProgress(static_cast<double>(nCurTile) /
4955 2 : static_cast<double>(nTotalTiles),
4956 6 : "", pProgressData));
4957 :
4958 6 : if (!oResourceManager.GetErrorMsg().empty())
4959 : {
4960 : // Re-emit error message from worker thread to main thread
4961 0 : ReportError(CE_Failure, CPLE_AppDefined, "%s",
4962 0 : oResourceManager.GetErrorMsg().c_str());
4963 : }
4964 : }
4965 : else
4966 : {
4967 : // Branch for single-thread overview generation
4968 :
4969 114 : for (int iY = nOvrMinTileY;
4970 114 : bRet && !bParentAskedForStop && iY <= nOvrMaxTileY; ++iY)
4971 : {
4972 156 : for (int iX = nOvrMinTileX;
4973 156 : bRet && !bParentAskedForStop && iX <= nOvrMaxTileX;
4974 : ++iX)
4975 : {
4976 97 : bRet = GenerateOverviewTile(
4977 97 : oSrcDS, m_poDstDriver, m_outputFormat, pszExtension,
4978 97 : aosCreationOptions.List(), aosWarpOptions.List(),
4979 97 : m_overviewResampling, ovrTileMatrix,
4980 97 : m_outputDirectory, iZ, iX, iY, m_convention,
4981 97 : m_skipBlank, bUserAskedForAlpha, m_auxXML,
4982 97 : m_resume);
4983 :
4984 97 : if (m_spawned)
4985 : {
4986 40 : if (bEmitSpuriousCharsOnStdout)
4987 20 : fwrite(&PROGRESS_MARKER[0], 1, 1, stdout);
4988 40 : fwrite(PROGRESS_MARKER, sizeof(PROGRESS_MARKER), 1,
4989 : stdout);
4990 40 : fflush(stdout);
4991 : }
4992 : #ifdef FORK_ALLOWED
4993 57 : else if (m_forked)
4994 : {
4995 0 : CPLPipeWrite(pipeOut, PROGRESS_MARKER,
4996 : sizeof(PROGRESS_MARKER));
4997 : }
4998 : #endif
4999 : else
5000 : {
5001 57 : ++nCurTile;
5002 59 : if (bRet && pfnProgress &&
5003 2 : !pfnProgress(
5004 2 : static_cast<double>(nCurTile) /
5005 2 : static_cast<double>(nTotalTiles),
5006 : "", pProgressData))
5007 : {
5008 0 : bRet = false;
5009 0 : CPLError(CE_Failure, CPLE_UserInterrupt,
5010 : "Process interrupted by user");
5011 : }
5012 : }
5013 : }
5014 : }
5015 : }
5016 : }
5017 :
5018 69 : if (m_kml && bRet)
5019 : {
5020 2 : for (int iY = nOvrMinTileY; bRet && iY <= nOvrMaxTileY; ++iY)
5021 : {
5022 2 : for (int iX = nOvrMinTileX; bRet && iX <= nOvrMaxTileX; ++iX)
5023 : {
5024 : int nFileY =
5025 1 : GetFileY(iY, poTMS->tileMatrixList()[iZ], m_convention);
5026 : std::string osFilename =
5027 : CPLFormFilenameSafe(m_outputDirectory.c_str(),
5028 2 : CPLSPrintf("%d", iZ), nullptr);
5029 2 : osFilename = CPLFormFilenameSafe(
5030 1 : osFilename.c_str(), CPLSPrintf("%d", iX), nullptr);
5031 2 : osFilename = CPLFormFilenameSafe(
5032 : osFilename.c_str(),
5033 1 : CPLSPrintf("%d.%s", nFileY, pszExtension), nullptr);
5034 1 : if (VSIStatL(osFilename.c_str(), &sStat) == 0)
5035 : {
5036 1 : std::vector<TileCoordinates> children;
5037 :
5038 3 : for (int iChildY = 0; iChildY <= 1; ++iChildY)
5039 : {
5040 6 : for (int iChildX = 0; iChildX <= 1; ++iChildX)
5041 : {
5042 : nFileY =
5043 4 : GetFileY(iY * 2 + iChildY,
5044 4 : poTMS->tileMatrixList()[iZ + 1],
5045 4 : m_convention);
5046 8 : osFilename = CPLFormFilenameSafe(
5047 : m_outputDirectory.c_str(),
5048 4 : CPLSPrintf("%d", iZ + 1), nullptr);
5049 8 : osFilename = CPLFormFilenameSafe(
5050 : osFilename.c_str(),
5051 4 : CPLSPrintf("%d", iX * 2 + iChildX),
5052 4 : nullptr);
5053 8 : osFilename = CPLFormFilenameSafe(
5054 : osFilename.c_str(),
5055 : CPLSPrintf("%d.%s", nFileY, pszExtension),
5056 4 : nullptr);
5057 4 : if (VSIStatL(osFilename.c_str(), &sStat) == 0)
5058 : {
5059 1 : TileCoordinates tc;
5060 1 : tc.nTileX = iX * 2 + iChildX;
5061 1 : tc.nTileY = iY * 2 + iChildY;
5062 1 : tc.nTileZ = iZ + 1;
5063 1 : children.push_back(std::move(tc));
5064 : }
5065 : }
5066 : }
5067 :
5068 2 : GenerateKML(m_outputDirectory, m_title, iX, iY, iZ,
5069 1 : kmlTileSize, pszExtension, m_url,
5070 1 : poTMS.get(), bInvertAxisTMS, m_convention,
5071 : poCTToWGS84.get(), children);
5072 : }
5073 : }
5074 : }
5075 : }
5076 : }
5077 :
5078 786 : const auto IsWebViewerEnabled = [this](const char *name)
5079 : {
5080 262 : return std::find_if(m_webviewers.begin(), m_webviewers.end(),
5081 438 : [name](const std::string &s) {
5082 266 : return s == "all" || s == name;
5083 262 : }) != m_webviewers.end();
5084 95 : };
5085 :
5086 149 : if (m_ovrZoomLevel < 0 && bRet &&
5087 244 : poTMS->identifier() == "GoogleMapsCompatible" &&
5088 52 : IsWebViewerEnabled("leaflet"))
5089 : {
5090 19 : double dfSouthLat = -90;
5091 19 : double dfWestLon = -180;
5092 19 : double dfNorthLat = 90;
5093 19 : double dfEastLon = 180;
5094 :
5095 19 : if (poCTToWGS84)
5096 : {
5097 19 : poCTToWGS84->TransformBounds(
5098 : adfExtent[0], adfExtent[1], adfExtent[2], adfExtent[3],
5099 19 : &dfWestLon, &dfSouthLat, &dfEastLon, &dfNorthLat, 21);
5100 : }
5101 :
5102 19 : GenerateLeaflet(m_outputDirectory, m_title, dfSouthLat, dfWestLon,
5103 : dfNorthLat, dfEastLon, m_minZoomLevel, m_maxZoomLevel,
5104 19 : tileMatrix.mTileWidth, pszExtension, m_url, m_copyright,
5105 19 : m_convention == "xyz");
5106 : }
5107 :
5108 95 : if (m_ovrZoomLevel < 0 && bRet && IsWebViewerEnabled("openlayers"))
5109 : {
5110 54 : GenerateOpenLayers(
5111 27 : m_outputDirectory, m_title, adfExtent[0], adfExtent[1],
5112 : adfExtent[2], adfExtent[3], m_minZoomLevel, m_maxZoomLevel,
5113 27 : tileMatrix.mTileWidth, pszExtension, m_url, m_copyright,
5114 27 : *(poTMS.get()), bInvertAxisTMS, oSRS_TMS, m_convention == "xyz");
5115 : }
5116 :
5117 104 : if (m_ovrZoomLevel < 0 && bRet && IsWebViewerEnabled("mapml") &&
5118 199 : poTMS->identifier() != "raster" && m_convention == "xyz")
5119 : {
5120 20 : GenerateMapML(m_outputDirectory, m_mapmlTemplate, m_title, nMinTileX,
5121 : nMinTileY, nMaxTileX, nMaxTileY, m_minZoomLevel,
5122 20 : m_maxZoomLevel, pszExtension, m_url, m_copyright,
5123 20 : *(poTMS.get()));
5124 : }
5125 :
5126 120 : if (m_ovrZoomLevel < 0 && bRet && IsWebViewerEnabled("stac") &&
5127 25 : m_convention == "xyz")
5128 : {
5129 24 : OGRCoordinateTransformation *poCT = poCTToWGS84.get();
5130 0 : std::unique_ptr<OGRCoordinateTransformation> poCTToLongLat;
5131 24 : if (!poCTToWGS84)
5132 : {
5133 2 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
5134 2 : OGRSpatialReference oLongLat;
5135 1 : oLongLat.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
5136 1 : oLongLat.CopyGeogCSFrom(&oSRS_TMS);
5137 1 : poCTToLongLat.reset(
5138 : OGRCreateCoordinateTransformation(&oSRS_TMS, &oLongLat));
5139 1 : poCT = poCTToLongLat.get();
5140 : }
5141 :
5142 24 : double dfSouthLat = -90;
5143 24 : double dfWestLon = -180;
5144 24 : double dfNorthLat = 90;
5145 24 : double dfEastLon = 180;
5146 24 : if (poCT)
5147 : {
5148 23 : poCT->TransformBounds(adfExtent[0], adfExtent[1], adfExtent[2],
5149 : adfExtent[3], &dfWestLon, &dfSouthLat,
5150 23 : &dfEastLon, &dfNorthLat, 21);
5151 : }
5152 :
5153 48 : GenerateSTAC(m_outputDirectory, m_title, dfWestLon, dfSouthLat,
5154 24 : dfEastLon, dfNorthLat, m_metadata, aoBandMetadata,
5155 : m_minZoomLevel, m_maxZoomLevel, pszExtension,
5156 24 : m_outputFormat, m_url, m_copyright, oSRS_TMS,
5157 24 : *(poTMS.get()), bInvertAxisTMS, m_tileSize, adfExtent,
5158 24 : m_dataset);
5159 : }
5160 :
5161 95 : if (m_ovrZoomLevel < 0 && bRet && m_kml)
5162 : {
5163 4 : std::vector<TileCoordinates> children;
5164 :
5165 2 : auto ovrTileMatrix = tileMatrixList[m_minZoomLevel];
5166 2 : int nOvrMinTileX = 0;
5167 2 : int nOvrMinTileY = 0;
5168 2 : int nOvrMaxTileX = 0;
5169 2 : int nOvrMaxTileY = 0;
5170 2 : CPL_IGNORE_RET_VAL(
5171 2 : GetTileIndices(ovrTileMatrix, bInvertAxisTMS, m_tileSize, adfExtent,
5172 : nOvrMinTileX, nOvrMinTileY, nOvrMaxTileX,
5173 2 : nOvrMaxTileY, m_noIntersectionIsOK, bIntersects));
5174 :
5175 4 : for (int iY = nOvrMinTileY; bRet && iY <= nOvrMaxTileY; ++iY)
5176 : {
5177 4 : for (int iX = nOvrMinTileX; bRet && iX <= nOvrMaxTileX; ++iX)
5178 : {
5179 2 : int nFileY = GetFileY(
5180 2 : iY, poTMS->tileMatrixList()[m_minZoomLevel], m_convention);
5181 : std::string osFilename = CPLFormFilenameSafe(
5182 : m_outputDirectory.c_str(), CPLSPrintf("%d", m_minZoomLevel),
5183 4 : nullptr);
5184 4 : osFilename = CPLFormFilenameSafe(osFilename.c_str(),
5185 2 : CPLSPrintf("%d", iX), nullptr);
5186 4 : osFilename = CPLFormFilenameSafe(
5187 : osFilename.c_str(),
5188 2 : CPLSPrintf("%d.%s", nFileY, pszExtension), nullptr);
5189 2 : if (VSIStatL(osFilename.c_str(), &sStat) == 0)
5190 : {
5191 2 : TileCoordinates tc;
5192 2 : tc.nTileX = iX;
5193 2 : tc.nTileY = iY;
5194 2 : tc.nTileZ = m_minZoomLevel;
5195 2 : children.push_back(std::move(tc));
5196 : }
5197 : }
5198 : }
5199 4 : GenerateKML(m_outputDirectory, m_title, -1, -1, -1, kmlTileSize,
5200 2 : pszExtension, m_url, poTMS.get(), bInvertAxisTMS,
5201 2 : m_convention, poCTToWGS84.get(), children);
5202 : }
5203 :
5204 95 : if (!bRet && CPLGetLastErrorType() == CE_None)
5205 : {
5206 : // If that happens, this is a programming error
5207 0 : ReportError(CE_Failure, CPLE_AppDefined,
5208 : "Bug: process failed without returning an error message");
5209 : }
5210 :
5211 95 : if (m_spawned)
5212 : {
5213 26 : fwrite(END_MARKER, sizeof(END_MARKER), 1, stdout);
5214 26 : fflush(stdout);
5215 26 : fclose(stdout);
5216 26 : threadWaitForParentStop.join();
5217 : }
5218 : #ifdef FORK_ALLOWED
5219 69 : else if (m_forked)
5220 : {
5221 0 : CPLPipeWrite(pipeOut, END_MARKER, sizeof(END_MARKER));
5222 0 : threadWaitForParentStop.join();
5223 : }
5224 : #endif
5225 :
5226 95 : return bRet;
5227 : }
5228 :
5229 : //! @endcond
|