Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: gdal "raster pipeline" subcommand
5 : * Author: Even Rouault <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "gdalalg_raster_pipeline.h"
14 : #include "gdalalg_materialize.h"
15 : #include "gdalalg_raster_read.h"
16 : #include "gdalalg_raster_calc.h"
17 : #include "gdalalg_raster_aspect.h"
18 : #include "gdalalg_raster_blend.h"
19 : #include "gdalalg_raster_clip.h"
20 : #include "gdalalg_raster_color_map.h"
21 : #include "gdalalg_raster_compare.h"
22 : #include "gdalalg_raster_edit.h"
23 : #include "gdalalg_raster_fill_nodata.h"
24 : #include "gdalalg_raster_hillshade.h"
25 : #include "gdalalg_raster_info.h"
26 : #include "gdalalg_raster_mosaic.h"
27 : #include "gdalalg_raster_neighbors.h"
28 : #include "gdalalg_raster_nodata_to_alpha.h"
29 : #include "gdalalg_raster_overview.h"
30 : #include "gdalalg_raster_pansharpen.h"
31 : #include "gdalalg_raster_proximity.h"
32 : #include "gdalalg_raster_reclassify.h"
33 : #include "gdalalg_raster_reproject.h"
34 : #include "gdalalg_raster_resize.h"
35 : #include "gdalalg_raster_rgb_to_palette.h"
36 : #include "gdalalg_raster_roughness.h"
37 : #include "gdalalg_raster_scale.h"
38 : #include "gdalalg_raster_select.h"
39 : #include "gdalalg_raster_set_type.h"
40 : #include "gdalalg_raster_sieve.h"
41 : #include "gdalalg_raster_slope.h"
42 : #include "gdalalg_raster_stack.h"
43 : #include "gdalalg_raster_tile.h"
44 : #include "gdalalg_raster_write.h"
45 : #include "gdalalg_raster_tpi.h"
46 : #include "gdalalg_raster_tri.h"
47 : #include "gdalalg_raster_unscale.h"
48 : #include "gdalalg_raster_viewshed.h"
49 : #include "gdalalg_tee.h"
50 :
51 : #include "cpl_conv.h"
52 : #include "cpl_progress.h"
53 : #include "cpl_string.h"
54 : #include "cpl_vsi.h"
55 : #include "gdal_priv.h"
56 : #include "gdal_utils.h"
57 :
58 : #include <algorithm>
59 : #include <array>
60 : #include <cassert>
61 :
62 : //! @cond Doxygen_Suppress
63 :
64 : #ifndef _
65 : #define _(x) (x)
66 : #endif
67 :
68 : GDALRasterAlgorithmStepRegistry::~GDALRasterAlgorithmStepRegistry() = default;
69 :
70 : /************************************************************************/
71 : /* GDALRasterPipelineStepAlgorithm::GDALRasterPipelineStepAlgorithm() */
72 : /************************************************************************/
73 :
74 1682 : GDALRasterPipelineStepAlgorithm::GDALRasterPipelineStepAlgorithm(
75 : const std::string &name, const std::string &description,
76 1682 : const std::string &helpURL, bool standaloneStep)
77 : : GDALRasterPipelineStepAlgorithm(
78 : name, description, helpURL,
79 1682 : ConstructorOptions().SetStandaloneStep(standaloneStep))
80 : {
81 1682 : }
82 :
83 : /************************************************************************/
84 : /* GDALRasterPipelineStepAlgorithm::GDALRasterPipelineStepAlgorithm() */
85 : /************************************************************************/
86 :
87 4105 : GDALRasterPipelineStepAlgorithm::GDALRasterPipelineStepAlgorithm(
88 : const std::string &name, const std::string &description,
89 4105 : const std::string &helpURL, const ConstructorOptions &options)
90 4105 : : GDALPipelineStepAlgorithm(name, description, helpURL, options)
91 : {
92 4105 : if (m_standaloneStep)
93 : {
94 1134 : m_supportsStreamedOutput = true;
95 :
96 1134 : if (m_constructorOptions.addDefaultArguments)
97 : {
98 377 : AddRasterInputArgs(false, false);
99 377 : AddProgressArg();
100 377 : AddRasterOutputArgs(false);
101 : }
102 : }
103 2971 : else if (m_constructorOptions.addDefaultArguments)
104 : {
105 1381 : AddRasterHiddenInputDatasetArg();
106 : }
107 4105 : }
108 :
109 : GDALRasterPipelineStepAlgorithm::~GDALRasterPipelineStepAlgorithm() = default;
110 :
111 : /************************************************************************/
112 : /* GDALRasterPipelineStepAlgorithm::SetOutputVRTCompatible() */
113 : /************************************************************************/
114 :
115 320 : void GDALRasterPipelineStepAlgorithm::SetOutputVRTCompatible(bool b)
116 : {
117 320 : m_outputVRTCompatible = b;
118 320 : if (m_outputFormatArg)
119 : {
120 92 : m_outputFormatArg->AddMetadataItem(GAAMDI_VRT_COMPATIBLE,
121 184 : {b ? "true" : "false"});
122 : }
123 320 : }
124 :
125 : /************************************************************************/
126 : /* GDALRasterPipelineAlgorithm::GDALRasterPipelineAlgorithm() */
127 : /************************************************************************/
128 :
129 128 : GDALRasterPipelineAlgorithm::GDALRasterPipelineAlgorithm(
130 128 : bool openForMixedRasterVector)
131 : : GDALAbstractPipelineAlgorithm(NAME, DESCRIPTION, HELP_URL,
132 0 : ConstructorOptions()
133 128 : .SetAddDefaultArguments(false)
134 256 : .SetInputDatasetMaxCount(INT_MAX))
135 : {
136 128 : m_supportsStreamedOutput = true;
137 :
138 128 : AddRasterInputArgs(openForMixedRasterVector, /* hiddenForCLI = */ true);
139 128 : AddProgressArg();
140 256 : AddArg("pipeline", 0, _("Pipeline string"), &m_pipeline)
141 128 : .SetHiddenForCLI()
142 128 : .SetPositional();
143 128 : AddRasterOutputArgs(/* hiddenForCLI = */ true);
144 :
145 128 : AddOutputStringArg(&m_output).SetHiddenForCLI();
146 128 : AddStdoutArg(&m_stdout);
147 :
148 128 : RegisterAlgorithms(m_stepRegistry, false);
149 128 : }
150 :
151 : /************************************************************************/
152 : /* GDALRasterPipelineAlgorithm::RegisterAlgorithms() */
153 : /************************************************************************/
154 :
155 : /* static */
156 319 : void GDALRasterPipelineAlgorithm::RegisterAlgorithms(
157 : GDALRasterAlgorithmStepRegistry ®istry, bool forMixedPipeline)
158 : {
159 319 : GDALAlgorithmRegistry::AlgInfo algInfo;
160 :
161 : const auto addSuffixIfNeeded =
162 2871 : [forMixedPipeline](const char *name) -> std::string
163 : {
164 4590 : return forMixedPipeline ? std::string(name).append(RASTER_SUFFIX)
165 7461 : : std::string(name);
166 319 : };
167 :
168 319 : registry.Register<GDALRasterReadAlgorithm>(
169 638 : addSuffixIfNeeded(GDALRasterReadAlgorithm::NAME));
170 :
171 319 : registry.Register<GDALRasterCalcAlgorithm>();
172 :
173 319 : registry.Register<GDALRasterNeighborsAlgorithm>();
174 :
175 319 : registry.Register<GDALRasterWriteAlgorithm>(
176 638 : addSuffixIfNeeded(GDALRasterWriteAlgorithm::NAME));
177 :
178 319 : registry.Register<GDALRasterInfoAlgorithm>(
179 638 : addSuffixIfNeeded(GDALRasterInfoAlgorithm::NAME));
180 :
181 319 : registry.Register<GDALRasterAspectAlgorithm>();
182 319 : registry.Register<GDALRasterBlendAlgorithm>();
183 :
184 319 : registry.Register<GDALRasterClipAlgorithm>(
185 638 : addSuffixIfNeeded(GDALRasterClipAlgorithm::NAME));
186 :
187 319 : registry.Register<GDALRasterColorMapAlgorithm>();
188 319 : registry.Register<GDALRasterCompareAlgorithm>();
189 :
190 319 : registry.Register<GDALRasterEditAlgorithm>(
191 638 : addSuffixIfNeeded(GDALRasterEditAlgorithm::NAME));
192 :
193 319 : registry.Register<GDALRasterNoDataToAlphaAlgorithm>();
194 319 : registry.Register<GDALRasterFillNodataAlgorithm>();
195 319 : registry.Register<GDALRasterHillshadeAlgorithm>();
196 :
197 319 : registry.Register<GDALMaterializeRasterAlgorithm>(
198 638 : addSuffixIfNeeded(GDALMaterializeRasterAlgorithm::NAME));
199 :
200 319 : registry.Register<GDALRasterMosaicAlgorithm>();
201 319 : registry.Register<GDALRasterOverviewAlgorithm>();
202 319 : registry.Register<GDALRasterPansharpenAlgorithm>();
203 319 : registry.Register<GDALRasterProximityAlgorithm>();
204 319 : registry.Register<GDALRasterReclassifyAlgorithm>();
205 :
206 319 : registry.Register<GDALRasterReprojectAlgorithm>(
207 638 : addSuffixIfNeeded(GDALRasterReprojectAlgorithm::NAME));
208 :
209 319 : registry.Register<GDALRasterResizeAlgorithm>();
210 319 : registry.Register<GDALRasterRGBToPaletteAlgorithm>();
211 319 : registry.Register<GDALRasterRoughnessAlgorithm>();
212 319 : registry.Register<GDALRasterScaleAlgorithm>();
213 :
214 319 : registry.Register<GDALRasterSelectAlgorithm>(
215 638 : addSuffixIfNeeded(GDALRasterSelectAlgorithm::NAME));
216 :
217 319 : registry.Register<GDALRasterSetTypeAlgorithm>();
218 319 : registry.Register<GDALRasterSieveAlgorithm>();
219 319 : registry.Register<GDALRasterSlopeAlgorithm>();
220 319 : registry.Register<GDALRasterStackAlgorithm>();
221 319 : registry.Register<GDALRasterTileAlgorithm>();
222 319 : registry.Register<GDALRasterTPIAlgorithm>();
223 319 : registry.Register<GDALRasterTRIAlgorithm>();
224 319 : registry.Register<GDALRasterUnscaleAlgorithm>();
225 319 : registry.Register<GDALRasterViewshedAlgorithm>();
226 319 : registry.Register<GDALTeeRasterAlgorithm>(
227 638 : addSuffixIfNeeded(GDALTeeRasterAlgorithm::NAME));
228 319 : }
229 :
230 : /************************************************************************/
231 : /* GDALRasterPipelineAlgorithm::GetUsageForCLI() */
232 : /************************************************************************/
233 :
234 8 : std::string GDALRasterPipelineAlgorithm::GetUsageForCLI(
235 : bool shortUsage, const UsageOptions &usageOptions) const
236 : {
237 8 : UsageOptions stepUsageOptions;
238 8 : stepUsageOptions.isPipelineStep = true;
239 :
240 8 : if (!m_helpDocCategory.empty() && m_helpDocCategory != "main")
241 : {
242 4 : auto alg = GetStepAlg(m_helpDocCategory);
243 2 : if (alg)
244 : {
245 2 : alg->SetCallPath({m_helpDocCategory});
246 1 : alg->GetArg("help-doc")->Set(true);
247 1 : return alg->GetUsageForCLI(shortUsage, stepUsageOptions);
248 : }
249 : else
250 : {
251 1 : fprintf(stderr, "ERROR: unknown pipeline step '%s'\n",
252 : m_helpDocCategory.c_str());
253 : return CPLSPrintf("ERROR: unknown pipeline step '%s'\n",
254 1 : m_helpDocCategory.c_str());
255 : }
256 : }
257 :
258 6 : UsageOptions usageOptionsMain(usageOptions);
259 6 : usageOptionsMain.isPipelineMain = true;
260 : std::string ret =
261 12 : GDALAlgorithm::GetUsageForCLI(shortUsage, usageOptionsMain);
262 6 : if (shortUsage)
263 2 : return ret;
264 :
265 : ret += "\n<PIPELINE> is of the form: read|mosaic|stack [READ-OPTIONS] "
266 : "( ! <STEP-NAME> [STEP-OPTIONS] )* ! info|compare|tile|write "
267 4 : "[WRITE-OPTIONS]\n";
268 :
269 4 : if (m_helpDocCategory == "main")
270 : {
271 1 : return ret;
272 : }
273 :
274 3 : ret += '\n';
275 3 : ret += "Example: 'gdal raster pipeline --progress ! read in.tif ! \\\n";
276 3 : ret += " reproject --dst-crs=EPSG:32632 ! ";
277 3 : ret += "write out.tif --overwrite'\n";
278 3 : ret += '\n';
279 3 : ret += "Potential steps are:\n";
280 :
281 111 : for (const std::string &name : m_stepRegistry.GetNames())
282 : {
283 216 : auto alg = GetStepAlg(name);
284 108 : auto [options, maxOptLen] = alg->GetArgNamesForCLI();
285 108 : stepUsageOptions.maxOptLen =
286 108 : std::max(stepUsageOptions.maxOptLen, maxOptLen);
287 : }
288 :
289 : {
290 3 : const auto name = GDALRasterReadAlgorithm::NAME;
291 3 : ret += '\n';
292 6 : auto alg = GetStepAlg(name);
293 6 : alg->SetCallPath({name});
294 3 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
295 : }
296 111 : for (const std::string &name : m_stepRegistry.GetNames())
297 : {
298 216 : auto alg = GetStepAlg(name);
299 108 : assert(alg);
300 120 : if (alg->CanBeFirstStep() && !alg->CanBeMiddleStep() &&
301 120 : !alg->IsHidden() && name != GDALRasterReadAlgorithm::NAME)
302 : {
303 9 : ret += '\n';
304 18 : alg->SetCallPath({name});
305 9 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
306 : }
307 : }
308 111 : for (const std::string &name : m_stepRegistry.GetNames())
309 : {
310 216 : auto alg = GetStepAlg(name);
311 108 : assert(alg);
312 108 : if (alg->CanBeMiddleStep() && !alg->IsHidden())
313 : {
314 84 : ret += '\n';
315 168 : alg->SetCallPath({name});
316 84 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
317 : }
318 : }
319 111 : for (const std::string &name : m_stepRegistry.GetNames())
320 : {
321 216 : auto alg = GetStepAlg(name);
322 108 : assert(alg);
323 123 : if (alg->CanBeLastStep() && !alg->CanBeMiddleStep() &&
324 123 : !alg->IsHidden() && name != GDALRasterWriteAlgorithm::NAME)
325 : {
326 9 : ret += '\n';
327 18 : alg->SetCallPath({name});
328 9 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
329 : }
330 : }
331 : {
332 3 : const auto name = GDALRasterWriteAlgorithm::NAME;
333 3 : ret += '\n';
334 6 : auto alg = GetStepAlg(name);
335 6 : alg->SetCallPath({name});
336 3 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
337 : }
338 3 : ret += GetUsageForCLIEnd();
339 :
340 3 : return ret;
341 : }
342 :
343 : /************************************************************************/
344 : /* GDALRasterPipelineNonNativelyStreamingAlgorithm() */
345 : /************************************************************************/
346 :
347 268 : GDALRasterPipelineNonNativelyStreamingAlgorithm::
348 : GDALRasterPipelineNonNativelyStreamingAlgorithm(
349 : const std::string &name, const std::string &description,
350 268 : const std::string &helpURL, bool standaloneStep)
351 : : GDALRasterPipelineStepAlgorithm(name, description, helpURL,
352 268 : standaloneStep)
353 : {
354 268 : }
355 :
356 : /************************************************************************/
357 : /* IsNativelyStreamingCompatible() */
358 : /************************************************************************/
359 :
360 46 : bool GDALRasterPipelineNonNativelyStreamingAlgorithm::
361 : IsNativelyStreamingCompatible() const
362 : {
363 46 : return false;
364 : }
365 :
366 : /************************************************************************/
367 : /* MustCreateOnDiskTempDataset() */
368 : /************************************************************************/
369 :
370 55 : static bool MustCreateOnDiskTempDataset(int nWidth, int nHeight, int nBands,
371 : GDALDataType eDT)
372 : {
373 : // Config option mostly for autotest purposes
374 55 : if (CPLTestBool(CPLGetConfigOption(
375 : "GDAL_RASTER_PIPELINE_USE_GTIFF_FOR_TEMP_DATASET", "NO")))
376 5 : return true;
377 :
378 : // Allow up to 10% of RAM usage for temporary dataset
379 50 : const auto nRAM = CPLGetUsablePhysicalRAM() / 10;
380 50 : const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
381 50 : const bool bOnDisk =
382 100 : nBands > 0 && nDTSize > 0 && nRAM > 0 &&
383 50 : static_cast<int64_t>(nWidth) * nHeight > nRAM / (nBands * nDTSize);
384 50 : return bOnDisk;
385 : }
386 :
387 : /************************************************************************/
388 : /* CreateTemporaryDataset() */
389 : /************************************************************************/
390 :
391 : std::unique_ptr<GDALDataset>
392 30 : GDALRasterPipelineNonNativelyStreamingAlgorithm::CreateTemporaryDataset(
393 : int nWidth, int nHeight, int nBands, GDALDataType eDT,
394 : bool bTiledIfPossible, GDALDataset *poSrcDSForMetadata, bool bCopyMetadata)
395 : {
396 : const bool bOnDisk =
397 30 : MustCreateOnDiskTempDataset(nWidth, nHeight, nBands, eDT);
398 30 : const char *pszDriverName = bOnDisk ? "GTIFF" : "MEM";
399 : GDALDriver *poDriver =
400 30 : GetGDALDriverManager()->GetDriverByName(pszDriverName);
401 60 : CPLStringList aosOptions;
402 60 : std::string osTmpFilename;
403 30 : if (bOnDisk)
404 : {
405 : osTmpFilename =
406 4 : CPLGenerateTempFilenameSafe(
407 : poSrcDSForMetadata
408 4 : ? CPLGetBasenameSafe(poSrcDSForMetadata->GetDescription())
409 2 : .c_str()
410 4 : : "") +
411 2 : ".tif";
412 2 : if (bTiledIfPossible)
413 2 : aosOptions.SetNameValue("TILED", "YES");
414 : const char *pszCOList =
415 2 : poDriver->GetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST);
416 : aosOptions.SetNameValue("COMPRESS",
417 2 : pszCOList && strstr(pszCOList, "ZSTD") ? "ZSTD"
418 4 : : "LZW");
419 2 : aosOptions.SetNameValue("SPARSE_OK", "YES");
420 : }
421 : std::unique_ptr<GDALDataset> poOutDS(
422 30 : poDriver ? poDriver->Create(osTmpFilename.c_str(), nWidth, nHeight,
423 30 : nBands, eDT, aosOptions.List())
424 60 : : nullptr);
425 30 : if (poOutDS && bOnDisk)
426 : {
427 : // In file systems that allow it (all but Windows...), we want to
428 : // delete the temporary file as soon as soon as possible after
429 : // having open it, so that if someone kills the process there are
430 : // no temp files left over. If that unlink() doesn't succeed
431 : // (on Windows), then the file will eventually be deleted when
432 : // poTmpDS is cleaned due to MarkSuppressOnClose().
433 0 : VSIUnlink(osTmpFilename.c_str());
434 0 : poOutDS->MarkSuppressOnClose();
435 : }
436 :
437 30 : if (poOutDS && poSrcDSForMetadata)
438 : {
439 28 : poOutDS->SetSpatialRef(poSrcDSForMetadata->GetSpatialRef());
440 28 : GDALGeoTransform gt;
441 28 : if (poSrcDSForMetadata->GetGeoTransform(gt) == CE_None)
442 27 : poOutDS->SetGeoTransform(gt);
443 28 : if (const int nGCPCount = poSrcDSForMetadata->GetGCPCount())
444 : {
445 0 : const auto apsGCPs = poSrcDSForMetadata->GetGCPs();
446 0 : if (apsGCPs)
447 : {
448 0 : poOutDS->SetGCPs(nGCPCount, apsGCPs,
449 0 : poSrcDSForMetadata->GetGCPSpatialRef());
450 : }
451 : }
452 28 : if (bCopyMetadata)
453 : {
454 14 : poOutDS->SetMetadata(poSrcDSForMetadata->GetMetadata());
455 : }
456 : }
457 :
458 60 : return poOutDS;
459 : }
460 :
461 : /************************************************************************/
462 : /* CreateTemporaryCopy() */
463 : /************************************************************************/
464 :
465 : std::unique_ptr<GDALDataset>
466 25 : GDALRasterPipelineNonNativelyStreamingAlgorithm::CreateTemporaryCopy(
467 : GDALAlgorithm *poAlg, GDALDataset *poSrcDS, int nSingleBand,
468 : bool bTiledIfPossible, GDALProgressFunc pfnProgress, void *pProgressData)
469 : {
470 25 : const int nBands = nSingleBand > 0 ? 1 : poSrcDS->GetRasterCount();
471 : const auto eDT =
472 25 : nBands ? poSrcDS->GetRasterBand(1)->GetRasterDataType() : GDT_Unknown;
473 25 : const bool bOnDisk = MustCreateOnDiskTempDataset(
474 : poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(), nBands, eDT);
475 25 : const char *pszDriverName = bOnDisk ? "GTIFF" : "MEM";
476 :
477 50 : CPLStringList options;
478 25 : if (nSingleBand > 0)
479 : {
480 25 : options.AddString("-b");
481 25 : options.AddString(CPLSPrintf("%d", nSingleBand));
482 : }
483 :
484 25 : options.AddString("-of");
485 25 : options.AddString(pszDriverName);
486 :
487 50 : std::string osTmpFilename;
488 25 : if (bOnDisk)
489 : {
490 : osTmpFilename =
491 3 : CPLGenerateTempFilenameSafe(
492 9 : CPLGetBasenameSafe(poSrcDS->GetDescription()).c_str()) +
493 3 : ".tif";
494 3 : if (bTiledIfPossible)
495 : {
496 3 : options.AddString("-co");
497 3 : options.AddString("TILED=YES");
498 : }
499 :
500 : GDALDriver *poDriver =
501 3 : GetGDALDriverManager()->GetDriverByName(pszDriverName);
502 : const char *pszCOList =
503 3 : poDriver ? poDriver->GetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST)
504 3 : : nullptr;
505 3 : options.AddString("-co");
506 3 : options.AddString(pszCOList && strstr(pszCOList, "ZSTD")
507 : ? "COMPRESS=ZSTD"
508 6 : : "COMPRESS=LZW");
509 : }
510 :
511 : GDALTranslateOptions *translateOptions =
512 25 : GDALTranslateOptionsNew(options.List(), nullptr);
513 :
514 25 : if (pfnProgress)
515 12 : GDALTranslateOptionsSetProgress(translateOptions, pfnProgress,
516 : pProgressData);
517 :
518 : std::unique_ptr<GDALDataset> poOutDS(GDALDataset::FromHandle(
519 : GDALTranslate(osTmpFilename.c_str(), GDALDataset::ToHandle(poSrcDS),
520 25 : translateOptions, nullptr)));
521 25 : GDALTranslateOptionsFree(translateOptions);
522 :
523 25 : if (!poOutDS)
524 : {
525 2 : poAlg->ReportError(CE_Failure, CPLE_AppDefined,
526 : "Failed to create temporary dataset");
527 : }
528 23 : else if (bOnDisk)
529 : {
530 : // In file systems that allow it (all but Windows...), we want to
531 : // delete the temporary file as soon as soon as possible after
532 : // having open it, so that if someone kills the process there are
533 : // no temp files left over. If that unlink() doesn't succeed
534 : // (on Windows), then the file will eventually be deleted when
535 : // poTmpDS is cleaned due to MarkSuppressOnClose().
536 1 : VSIUnlink(osTmpFilename.c_str());
537 1 : poOutDS->MarkSuppressOnClose();
538 : }
539 50 : return poOutDS;
540 : }
541 :
542 : //! @endcond
|