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