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