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