Coverage Report

Created: 2026-09-14 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalalg_vector_explode.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  "explode" step of "vector pipeline"
5
 * Author:   Daniel Baston
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2026, ISciences LLC
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdalalg_vector_explode.h"
14
15
#include "cpl_conv.h"
16
#include "cpl_string.h"
17
#include "gdal_priv.h"
18
#include "ogr_p.h"
19
#include "ogrsf_frmts.h"
20
21
#include <algorithm>
22
#include <cinttypes>
23
#include <list>
24
#include <memory>
25
#include <numeric>
26
#include <vector>
27
28
//! @cond Doxygen_Suppress
29
30
#ifndef _
31
0
#define _(x) (x)
32
#endif
33
34
/************************************************************************/
35
/*       GDALVectorExplodeAlgorithm::GDALVectorExplodeAlgorithm()       */
36
/************************************************************************/
37
38
GDALVectorExplodeAlgorithm::GDALVectorExplodeAlgorithm(bool standaloneStep)
39
0
    : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
40
0
                                      standaloneStep)
41
0
{
42
0
    AddActiveLayerArg(&m_activeLayer);
43
44
0
    {
45
0
        auto &arg =
46
0
            AddArg("field", 0, _("Attribute fields(s) to explode"), &m_fields)
47
0
                .SetDuplicateValuesAllowed(false)
48
0
                .SetMetaVar("FIELD");
49
50
0
        SetAutoCompleteFunctionForFieldName(
51
0
            arg, nullptr, true, false, m_inputDataset, {"ALL"},
52
0
            [](const OGRFieldDefn *defn)
53
0
            { return OGR_GetFieldTypeIsList(defn->GetType()); });
54
0
    }
55
56
0
    AddArg("geometry", 0, _("Explode default geometry field"), &m_defaultGeom);
57
58
0
    {
59
0
        auto &arg = AddArg("geometry-field", 0,
60
0
                           _("Geometry field(s) to explode"), &m_geomFields)
61
0
                        .SetDuplicateValuesAllowed(false)
62
0
                        .SetMetaVar("GEOMETRY-FIELD");
63
0
        SetAutoCompleteFunctionForFieldName(arg, nullptr, false, true,
64
0
                                            m_inputDataset, {"ALL"});
65
0
    }
66
67
0
    AddArg("index-field", 0, _("Name of the output index field"),
68
0
           &m_indexFieldName)
69
0
        .SetDefault(m_indexFieldName);
70
0
}
71
72
0
GDALVectorExplodeAlgorithmStandalone::~GDALVectorExplodeAlgorithmStandalone() =
73
    default;
74
75
namespace
76
{
77
78
class GDALVectorExplodeLayer final : public GDALVectorPipelineOutputLayer
79
{
80
  public:
81
    GDALVectorExplodeLayer(OGRLayer &srcLayer,
82
                           const std::vector<std::string> &fieldsToExplode,
83
                           const std::vector<std::string> &geomFieldsToExplode,
84
                           const std::string &indexFieldName)
85
0
        : GDALVectorPipelineOutputLayer(srcLayer),
86
0
          m_fieldsToExplode(fieldsToExplode),
87
0
          m_geomFieldsToExplode(geomFieldsToExplode),
88
0
          m_indexFieldName(indexFieldName)
89
0
    {
90
0
        if (!PrepareFeatureDefn())
91
0
        {
92
0
            m_setupError = true;
93
0
        }
94
0
    }
95
96
    bool PrepareFeatureDefn()
97
0
    {
98
0
        m_poFeatureDefn.reset(
99
0
            OGRFeatureDefn::CreateFeatureDefn(m_srcLayer.GetName()));
100
101
        // Avoid creating geometry field with null SRS
102
        // We'll copy it in later from the source layer
103
0
        m_poFeatureDefn->DeleteGeomFieldDefn(0);
104
105
0
        const bool addIndexField = !m_indexFieldName.empty();
106
107
0
        if (addIndexField)
108
0
        {
109
0
            auto poIdxField = std::make_unique<OGRFieldDefn>(
110
0
                m_indexFieldName.c_str(), OFTInteger);
111
0
            m_poFeatureDefn->AddFieldDefn(std::move(poIdxField));
112
0
        }
113
114
0
        const OGRFeatureDefn *poSrcDefn = m_srcLayer.GetLayerDefn();
115
116
        // By default, all fields copied as-is.
117
0
        m_unnestedFieldSrcToDstMap.resize(poSrcDefn->GetFieldCount(), -1);
118
0
        m_passThroughFieldSrcToDstMap.resize(poSrcDefn->GetFieldCount());
119
0
        std::iota(m_passThroughFieldSrcToDstMap.begin(),
120
0
                  m_passThroughFieldSrcToDstMap.end(), addIndexField ? 1 : 0);
121
122
0
        m_geomFieldExploded.resize(poSrcDefn->GetGeomFieldCount(), false);
123
124
0
        for (const auto &fieldName : m_fieldsToExplode)
125
0
        {
126
0
            const int iSrcField = poSrcDefn->GetFieldIndex(fieldName.c_str());
127
0
            if (iSrcField < 0)
128
0
            {
129
0
                CPLError(CE_Failure, CPLE_AppDefined,
130
0
                         "Field '%s' not found in source layer.",
131
0
                         fieldName.c_str());
132
0
                return false;
133
0
            }
134
135
0
            const OGRFieldDefn *poSrcFieldDefn =
136
0
                poSrcDefn->GetFieldDefn(iSrcField);
137
0
            const auto eSrcType = poSrcFieldDefn->GetType();
138
0
            if (OGR_GetFieldTypeIsList(eSrcType))
139
0
            {
140
0
                m_passThroughFieldSrcToDstMap[iSrcField] = -1;
141
0
                m_unnestedFieldSrcToDstMap[iSrcField] =
142
0
                    iSrcField + addIndexField;
143
0
            }
144
0
        }
145
146
0
        for (const auto &fieldName : m_geomFieldsToExplode)
147
0
        {
148
            // Is it a geometry field?
149
0
            int iSrcGeomField = poSrcDefn->GetGeomFieldIndex(fieldName.c_str());
150
151
            // Interpret --geometry-field _OGR_GEOMETRY_ as the first geometry
152
            // field, regardless of what it is actually named
153
0
            if (iSrcGeomField < 0)
154
0
            {
155
0
                if (poSrcDefn->GetGeomFieldCount() > 0 &&
156
0
                    EQUAL(fieldName.c_str(),
157
0
                          OGR_GEOMETRY_DEFAULT_NON_EMPTY_NAME))
158
0
                {
159
0
                    iSrcGeomField = 0;
160
0
                }
161
0
            }
162
163
            // Didn't find anything by name. Check by index.
164
0
            if (iSrcGeomField < 0 &&
165
0
                std::all_of(
166
0
                    fieldName.begin(), fieldName.end(), [](char c)
167
0
                    { return std::isdigit(static_cast<unsigned char>(c)); }))
168
0
            {
169
0
                const int iGeomField = std::atoi(fieldName.c_str());
170
171
0
                if (iGeomField < poSrcDefn->GetGeomFieldCount())
172
0
                {
173
0
                    iSrcGeomField = iGeomField;
174
0
                }
175
0
            }
176
177
0
            if (iSrcGeomField < 0)
178
0
            {
179
0
                CPLError(
180
0
                    CE_Failure, CPLE_AppDefined,
181
0
                    "Could not find geometry field '%s' in source layer '%s'",
182
0
                    fieldName.c_str(), m_srcLayer.GetName());
183
0
                return false;
184
0
            }
185
186
0
            m_geomFieldExploded[iSrcGeomField] = true;
187
0
        }
188
189
        // Create attribute fields
190
0
        for (int iSrcField = 0; iSrcField < poSrcDefn->GetFieldCount();
191
0
             iSrcField++)
192
0
        {
193
0
            const auto *poSrcFieldDefn = poSrcDefn->GetFieldDefn(iSrcField);
194
0
            std::unique_ptr<OGRFieldDefn> poDstFieldDefn;
195
196
0
            if (m_passThroughFieldSrcToDstMap[iSrcField] != -1)
197
0
            {
198
0
                poDstFieldDefn =
199
0
                    std::make_unique<OGRFieldDefn>(*poSrcFieldDefn);
200
0
            }
201
0
            else
202
0
            {
203
0
                const auto eScalarType =
204
0
                    OGR_GetFieldTypeAsScalar(poSrcFieldDefn->GetType());
205
0
                poDstFieldDefn = std::make_unique<OGRFieldDefn>(
206
0
                    poSrcFieldDefn->GetNameRef(), eScalarType);
207
0
            }
208
209
0
            m_poFeatureDefn->AddFieldDefn(std::move(poDstFieldDefn));
210
0
        }
211
212
        // Create geometry fields
213
0
        for (int iSrcGeomField = 0;
214
0
             iSrcGeomField < poSrcDefn->GetGeomFieldCount(); iSrcGeomField++)
215
0
        {
216
0
            const OGRGeomFieldDefn *poSrcGeomFieldDefn =
217
0
                poSrcDefn->GetGeomFieldDefn(iSrcGeomField);
218
0
            std::unique_ptr<OGRGeomFieldDefn> poDstGeomFieldDefn;
219
220
0
            if (m_geomFieldExploded[iSrcGeomField])
221
0
            {
222
0
                const auto eDstType =
223
0
                    OGR_GT_GetSingle(poSrcGeomFieldDefn->GetType());
224
0
                poDstGeomFieldDefn = std::make_unique<OGRGeomFieldDefn>(
225
0
                    poSrcGeomFieldDefn->GetNameRef(), eDstType);
226
0
                poDstGeomFieldDefn->SetSpatialRef(
227
0
                    poSrcGeomFieldDefn->GetSpatialRef());
228
0
            }
229
0
            else
230
0
            {
231
0
                poDstGeomFieldDefn =
232
0
                    std::make_unique<OGRGeomFieldDefn>(*poSrcGeomFieldDefn);
233
0
            }
234
235
0
            m_poFeatureDefn->AddGeomFieldDefn(std::move(poDstGeomFieldDefn));
236
0
        }
237
238
0
        return true;
239
0
    }
240
241
    const char *GetDescription() const override
242
0
    {
243
0
        return m_poFeatureDefn->GetName();
244
0
    }
245
246
    const OGRFeatureDefn *GetLayerDefn() const override
247
0
    {
248
0
        return m_poFeatureDefn.get();
249
0
    }
250
251
    void ResetReading() override
252
0
    {
253
0
        m_nextFID = 1;
254
0
        GDALVectorPipelineOutputLayer::ResetReading();
255
0
    }
256
257
    bool TestCapability(const char *pszCap) const override
258
0
    {
259
0
        if (EQUAL(pszCap, OLCFastGetExtent) ||
260
0
            EQUAL(pszCap, OLCFastGetExtent3D) ||
261
0
            EQUAL(pszCap, OLCStringsAsUTF8) ||
262
0
            EQUAL(pszCap, OLCCurveGeometries) ||
263
0
            EQUAL(pszCap, OLCMeasuredGeometries) ||
264
0
            EQUAL(pszCap, OLCZGeometries))
265
0
        {
266
0
            return m_srcLayer.TestCapability(pszCap);
267
0
        }
268
269
0
        return false;
270
0
    }
271
272
    bool TranslateFeature(
273
        std::unique_ptr<OGRFeature> poSrcFeature,
274
        std::vector<std::unique_ptr<OGRFeature>> &apoOutFeatures) override
275
0
    {
276
0
        if (m_setupError)
277
0
        {
278
0
            CPLError(CE_Failure, CPLE_AppDefined,
279
0
                     "Failed to prepare output layer.");
280
0
            return false;
281
0
        }
282
283
0
        int nDstFeatures = 1;
284
285
0
        for (int iDstFeature = 0; iDstFeature < nDstFeatures; iDstFeature++)
286
0
        {
287
0
            auto poDstFeature =
288
0
                std::make_unique<OGRFeature>(m_poFeatureDefn.get());
289
0
            if (!m_indexFieldName.empty())
290
0
            {
291
0
                poDstFeature->SetField(0, iDstFeature);
292
0
            }
293
294
0
            if (poDstFeature->SetFieldsFrom(
295
0
                    poSrcFeature.get(), m_passThroughFieldSrcToDstMap.data(),
296
0
                    true) != OGRERR_NONE)
297
0
            {
298
0
                CPLError(CE_Failure, CPLE_AppDefined,
299
0
                         "Failed to set fields of output feature");
300
0
                return false;
301
0
            }
302
303
0
            for (int iSrcArrayField = 0;
304
0
                 iSrcArrayField <
305
0
                 static_cast<int>(m_unnestedFieldSrcToDstMap.size());
306
0
                 iSrcArrayField++)
307
0
            {
308
0
                const int iDstField =
309
0
                    m_unnestedFieldSrcToDstMap[iSrcArrayField];
310
0
                if (iDstField < 0)
311
0
                {
312
0
                    continue;
313
0
                }
314
315
0
                const auto poSrcFieldDefn =
316
0
                    poSrcFeature->GetFieldDefnRef(iSrcArrayField);
317
0
                const auto eSrcType = poSrcFieldDefn->GetType();
318
0
                int nArrayLength = -1;
319
0
                if (eSrcType == OFTIntegerList)
320
0
                {
321
0
                    const int *pnArray = poSrcFeature->GetFieldAsIntegerList(
322
0
                        iSrcArrayField, &nArrayLength);
323
0
                    if (iDstFeature >= nArrayLength)
324
0
                    {
325
0
                        CPLError(CE_Failure, CPLE_AppDefined,
326
0
                                 "Field '%s' of source feature %" PRId64
327
0
                                 " does not have enough elements.",
328
0
                                 poSrcFieldDefn->GetNameRef(),
329
0
                                 static_cast<int64_t>(poSrcFeature->GetFID()));
330
0
                        return false;
331
0
                    }
332
0
                    poDstFeature->SetField(iDstField, pnArray[iDstFeature]);
333
0
                }
334
0
                else if (eSrcType == OFTInteger64List)
335
0
                {
336
0
                    const GIntBig *pnArray =
337
0
                        poSrcFeature->GetFieldAsInteger64List(iSrcArrayField,
338
0
                                                              &nArrayLength);
339
0
                    if (iDstFeature >= nArrayLength)
340
0
                    {
341
0
                        CPLError(CE_Failure, CPLE_AppDefined,
342
0
                                 "Field '%s' of source feature %" PRId64
343
0
                                 " does not have enough elements.",
344
0
                                 poSrcFieldDefn->GetNameRef(),
345
0
                                 static_cast<int64_t>(poSrcFeature->GetFID()));
346
0
                        return false;
347
0
                    }
348
0
                    poDstFeature->SetField(iDstField, pnArray[iDstFeature]);
349
0
                }
350
0
                else if (eSrcType == OFTRealList)
351
0
                {
352
0
                    const double *padfArray =
353
0
                        poSrcFeature->GetFieldAsDoubleList(iSrcArrayField,
354
0
                                                           &nArrayLength);
355
0
                    if (iDstFeature >= nArrayLength)
356
0
                    {
357
0
                        CPLError(CE_Failure, CPLE_AppDefined,
358
0
                                 "Field '%s' of source feature %" PRId64
359
0
                                 " does not have enough elements.",
360
0
                                 poSrcFieldDefn->GetNameRef(),
361
0
                                 static_cast<int64_t>(poSrcFeature->GetFID()));
362
0
                        return false;
363
0
                    }
364
0
                    poDstFeature->SetField(iDstField, padfArray[iDstFeature]);
365
0
                }
366
0
                else if (eSrcType == OFTStringList)
367
0
                {
368
0
                    CSLConstList papszArray =
369
0
                        poSrcFeature->GetFieldAsStringList(iSrcArrayField);
370
0
                    nArrayLength = CSLCount(papszArray);
371
0
                    if (iDstFeature >= nArrayLength)
372
0
                    {
373
0
                        CPLError(CE_Failure, CPLE_AppDefined,
374
0
                                 "Field '%s' of source feature %" PRId64
375
0
                                 " does not have enough elements.",
376
0
                                 poSrcFieldDefn->GetNameRef(),
377
0
                                 static_cast<int64_t>(poSrcFeature->GetFID()));
378
0
                        return false;
379
0
                    }
380
0
                    poDstFeature->SetField(iDstField, papszArray[iDstFeature]);
381
0
                }
382
0
                nDstFeatures = std::max(nDstFeatures, nArrayLength);
383
0
            }
384
385
0
            for (int iGeomField = 0;
386
0
                 iGeomField < poSrcFeature->GetGeomFieldCount(); iGeomField++)
387
0
            {
388
0
                if (m_geomFieldExploded[iGeomField])
389
0
                {
390
0
                    std::unique_ptr<OGRGeometry> poDstGeom;
391
392
0
                    OGRGeometry *poSrcGeom(
393
0
                        poSrcFeature->GetGeomFieldRef(iGeomField));
394
395
0
                    const bool bSrcIsCollection =
396
0
                        poSrcGeom != nullptr &&
397
0
                        OGR_GT_IsSubClassOf(
398
0
                            wkbFlatten(poSrcGeom->getGeometryType()),
399
0
                            wkbGeometryCollection);
400
401
0
                    if (bSrcIsCollection)
402
0
                    {
403
0
                        OGRGeometryCollection *poColl =
404
0
                            poSrcGeom->toGeometryCollection();
405
406
0
                        auto nGeoms = poColl->getNumGeometries();
407
0
                        nDstFeatures = std::max(nDstFeatures, nGeoms);
408
409
0
                        if (nGeoms == 0)
410
0
                        {
411
0
                            CPLError(
412
0
                                CE_Failure, CPLE_AppDefined,
413
0
                                "Geometry field '%s' of source feature %" PRId64
414
0
                                " has %d elements (expected %d)",
415
0
                                poSrcFeature->GetDefnRef()
416
0
                                    ->GetGeomFieldDefn(iGeomField)
417
0
                                    ->GetNameRef(),
418
0
                                static_cast<int64_t>(poSrcFeature->GetFID()),
419
0
                                nGeoms + iDstFeature, nDstFeatures);
420
0
                            return false;
421
0
                        }
422
423
0
                        poDstGeom = poColl->stealGeometry(0);
424
0
                    }
425
0
                    else
426
0
                    {
427
0
                        if (iDstFeature > 1 &&
428
0
                            apoOutFeatures.front()->GetGeomFieldRef(
429
0
                                iGeomField) != nullptr)
430
0
                        {
431
0
                            CPLError(
432
0
                                CE_Failure, CPLE_AppDefined,
433
0
                                "Geometry field '%s' of source feature %" PRId64
434
0
                                " is not a collection.",
435
0
                                poSrcFeature->GetDefnRef()
436
0
                                    ->GetGeomFieldDefn(iGeomField)
437
0
                                    ->GetNameRef(),
438
0
                                static_cast<int64_t>(poSrcFeature->GetFID()));
439
0
                            return false;
440
0
                        }
441
442
0
                        poDstGeom.reset(
443
0
                            poSrcFeature->StealGeometry(iGeomField));
444
0
                    }
445
446
0
                    poDstFeature->SetGeomField(iGeomField,
447
0
                                               std::move(poDstGeom));
448
0
                }
449
0
                else
450
0
                {
451
0
                    std::unique_ptr<OGRGeometry> poSrcGeom;
452
453
0
                    if (apoOutFeatures.empty())
454
0
                    {
455
0
                        poSrcGeom.reset(
456
0
                            poSrcFeature->StealGeometry(iGeomField));
457
0
                    }
458
0
                    else if (const OGRGeometry *poFirstGeom =
459
0
                                 apoOutFeatures.front()->GetGeomFieldRef(
460
0
                                     iGeomField))
461
0
                    {
462
0
                        poSrcGeom.reset(poFirstGeom->clone());
463
0
                    }
464
465
0
                    poDstFeature->SetGeomField(iGeomField,
466
0
                                               std::move(poSrcGeom));
467
0
                }
468
0
            }
469
470
0
            poDstFeature->SetFID(m_nextFID++);
471
0
            if (PassesFilters(poDstFeature.get()))
472
0
                apoOutFeatures.push_back(std::move(poDstFeature));
473
0
        }
474
475
0
        return true;
476
0
    }
477
478
  protected:
479
    OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent,
480
                      bool bForce) override
481
0
    {
482
0
        return m_srcLayer.GetExtent(iGeomField, psExtent, bForce);
483
0
    }
484
485
    OGRErr IGetExtent3D(int iGeomField, OGREnvelope3D *psExtent3D,
486
                        bool bForce) override
487
0
    {
488
0
        return m_srcLayer.GetExtent3D(iGeomField, psExtent3D, bForce);
489
0
    }
490
491
  private:
492
    std::vector<int> m_passThroughFieldSrcToDstMap{};
493
    std::vector<int> m_unnestedFieldSrcToDstMap{};
494
    std::vector<bool> m_geomFieldExploded{};
495
    std::vector<std::string> m_fieldsToExplode{};
496
    std::vector<std::string> m_geomFieldsToExplode{};
497
    std::string m_indexFieldName{};
498
    bool m_setupError{false};
499
    OGRFeatureDefnRefCountedPtr m_poFeatureDefn{nullptr};
500
    GIntBig m_nextFID{1};
501
502
    CPL_DISALLOW_COPY_ASSIGN(GDALVectorExplodeLayer)
503
};
504
505
}  // namespace
506
507
/************************************************************************/
508
/*                GDALVectorExplodeAlgorithm::RunStep()                 */
509
/************************************************************************/
510
511
bool GDALVectorExplodeAlgorithm::RunStep(GDALPipelineStepRunContext &)
512
0
{
513
0
    auto poSrcDS = m_inputDataset[0].GetDatasetRef();
514
0
    CPLAssert(poSrcDS);
515
516
0
    auto poOutDS = std::make_unique<GDALVectorPipelineOutputDataset>(*poSrcDS);
517
518
0
    if (m_defaultGeom)
519
0
    {
520
0
        m_geomFields.emplace_back(OGR_GEOMETRY_DEFAULT_NON_EMPTY_NAME);
521
0
    }
522
523
0
    if (m_fields.empty() && m_geomFields.empty())
524
0
    {
525
0
        ReportError(CE_Failure, CPLE_IllegalArg,
526
0
                    "At least one field or geometry field must be specified");
527
0
        return false;
528
0
    }
529
530
0
    for (OGRLayer *poSrcLayer : poSrcDS->GetLayers())
531
0
    {
532
0
        if (!poSrcLayer)
533
0
            continue;
534
535
0
        if (!m_activeLayer.empty() &&
536
0
            poSrcLayer->GetDescription() != m_activeLayer)
537
0
        {
538
0
            poOutDS->AddLayer(
539
0
                *poSrcLayer,
540
0
                std::make_unique<GDALVectorPipelinePassthroughLayer>(
541
0
                    *poSrcLayer));
542
0
            continue;
543
0
        }
544
545
0
        const auto *poLayerDefn = poSrcLayer->GetLayerDefn();
546
547
0
        auto fieldsForLayer = m_fields;
548
0
        auto geomFieldsForLayer = m_geomFields;
549
550
0
        if (geomFieldsForLayer.size() == 1 && geomFieldsForLayer[0] == "ALL")
551
0
        {
552
0
            geomFieldsForLayer.clear();
553
0
            for (int iGeomField = 0;
554
0
                 iGeomField < poLayerDefn->GetGeomFieldCount(); iGeomField++)
555
0
            {
556
0
                geomFieldsForLayer.emplace_back(
557
0
                    poLayerDefn->GetGeomFieldDefn(iGeomField)->GetNameRef());
558
0
            }
559
0
        }
560
561
0
        if (fieldsForLayer.size() == 1 && fieldsForLayer[0] == "ALL")
562
0
        {
563
0
            fieldsForLayer.clear();
564
0
            for (int iField = 0; iField < poLayerDefn->GetFieldCount();
565
0
                 iField++)
566
0
            {
567
0
                fieldsForLayer.emplace_back(
568
0
                    poLayerDefn->GetFieldDefn(iField)->GetNameRef());
569
0
            }
570
0
        }
571
572
0
        auto poOutLayer = std::make_unique<GDALVectorExplodeLayer>(
573
0
            *poSrcLayer, fieldsForLayer, geomFieldsForLayer, m_indexFieldName);
574
0
        poOutDS->AddLayer(*poSrcLayer, std::move(poOutLayer));
575
0
    }
576
577
0
    m_outputDataset.Set(std::move(poOutDS));
578
0
    return true;
579
0
}
580
581
//! @endcond