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_concat.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  gdal "vector concat" 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_vector_concat.h"
14
#include "gdalalg_vector_write.h"
15
16
#include "cpl_conv.h"
17
#include "cpl_enumerate.h"
18
#include "gdal_priv.h"
19
#include "gdal_utils.h"
20
#include "ogrsf_frmts.h"
21
22
#include "ogrlayerdecorator.h"
23
#include "ogrlayerpool.h"
24
#include "ogrunionlayer.h"
25
#include "ogrwarpedlayer.h"
26
27
#include <algorithm>
28
#include <set>
29
30
//! @cond Doxygen_Suppress
31
32
#ifndef _
33
0
#define _(x) (x)
34
#endif
35
36
/************************************************************************/
37
/*        GDALVectorConcatAlgorithm::GDALVectorConcatAlgorithm()        */
38
/************************************************************************/
39
40
GDALVectorConcatAlgorithm::GDALVectorConcatAlgorithm(bool bStandalone)
41
0
    : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
42
0
                                      ConstructorOptions()
43
0
                                          .SetStandaloneStep(bStandalone)
44
0
                                          .SetAddDefaultArguments(bStandalone)
45
0
                                          .SetInputDatasetMetaVar("INPUTS")
46
0
                                          .SetInputDatasetMaxCount(INT_MAX)
47
0
                                          .SetAddOutputLayerNameArgument(false)
48
0
                                          .SetAutoOpenInputDatasets(false))
49
0
{
50
0
    if (!bStandalone)
51
0
    {
52
0
        AddVectorInputArgs(/* hiddenForCLI = */ false);
53
0
    }
54
55
0
    AddArg(
56
0
        "mode", 0,
57
0
        _("Determine the strategy to create output layers from source layers "),
58
0
        &m_mode)
59
0
        .SetChoices("merge-per-layer-name", "stack", "single")
60
0
        .SetDefault(m_mode);
61
0
    AddArg(GDAL_ARG_NAME_OUTPUT_LAYER, 0,
62
0
           _("Name of the output vector layer (single mode), or template to "
63
0
             "name the output vector layers (stack mode)"),
64
0
           &m_layerNameTemplate);
65
0
    AddArg("source-layer-field-name", 0,
66
0
           _("Name of the new field to add to contain identification of the "
67
0
             "source layer, with value determined from "
68
0
             "'source-layer-field-content'"),
69
0
           &m_sourceLayerFieldName);
70
0
    AddArg("source-layer-field-content", 0,
71
0
           _("A string, possibly using {AUTO_NAME}, {DS_NAME}, {DS_BASENAME}, "
72
0
             "{DS_INDEX}, {LAYER_NAME}, {LAYER_INDEX}"),
73
0
           &m_sourceLayerFieldContent);
74
0
    AddArg("field-strategy", 0,
75
0
           _("How to determine target fields from source fields"),
76
0
           &m_fieldStrategy)
77
0
        .SetChoices("union", "intersection")
78
0
        .SetDefault(m_fieldStrategy);
79
0
    AddArg("input-crs", 's', _("Input CRS"), &m_srsCrs)
80
0
        .SetIsCRSArg()
81
0
        .AddHiddenAlias("s_srs")
82
0
        .AddHiddenAlias("src-crs");
83
0
    AddArg("output-crs", 'd', _("Output CRS"), &m_dstCrs)
84
0
        .SetIsCRSArg()
85
0
        .AddHiddenAlias("t_srs")
86
0
        .AddHiddenAlias("dst-crs");
87
0
}
88
89
0
GDALVectorConcatAlgorithm::~GDALVectorConcatAlgorithm() = default;
90
91
/************************************************************************/
92
/*                    GDALVectorConcatOutputDataset                     */
93
/************************************************************************/
94
95
class GDALVectorConcatOutputDataset final : public GDALDataset
96
{
97
    // The layers read lazily from the objects declared before them, and
98
    // members are destroyed in reverse declaration order. Do not reorder.
99
    std::vector<std::unique_ptr<GDALDataset, GDALDatasetUniquePtrReleaser>>
100
        m_srcDatasets{};
101
    std::unique_ptr<OGRLayerPool> m_poLayerPool{};
102
    std::vector<std::unique_ptr<OGRLayer>> m_tempLayers{};
103
    std::vector<std::unique_ptr<OGRLayer>> m_layers{};
104
105
  public:
106
0
    GDALVectorConcatOutputDataset() = default;
107
108
    /** Add a dataset the layers read from, taking over a reference on it. */
109
    void AddSrcDatasetRef(GDALDataset *poSrcDS)
110
0
    {
111
0
        m_srcDatasets.emplace_back(poSrcDS);
112
0
    }
113
114
    /** Create the pool used by proxied layers, and return a borrowed pointer. */
115
    OGRLayerPool *CreateLayerPool(int nMaxSimultaneouslyOpened)
116
0
    {
117
0
        m_poLayerPool =
118
0
            std::make_unique<OGRLayerPool>(nMaxSimultaneouslyOpened);
119
0
        return m_poLayerPool.get();
120
0
    }
121
122
    /** Add an intermediate layer, and return a borrowed pointer to it. */
123
    OGRLayer *AddTempLayer(std::unique_ptr<OGRLayer> layer)
124
0
    {
125
0
        m_tempLayers.push_back(std::move(layer));
126
0
        return m_tempLayers.back().get();
127
0
    }
128
129
    void AddLayer(std::unique_ptr<OGRLayer> layer)
130
0
    {
131
0
        m_layers.push_back(std::move(layer));
132
0
    }
133
134
    int GetLayerCount() const override;
135
136
    OGRLayer *GetLayer(int idx) const override
137
0
    {
138
0
        return idx >= 0 && idx < GetLayerCount() ? m_layers[idx].get()
139
0
                                                 : nullptr;
140
0
    }
141
142
    bool TestCapability(const char *pszCap) const override
143
0
    {
144
0
        if (EQUAL(pszCap, ODsCCurveGeometries) ||
145
0
            EQUAL(pszCap, ODsCMeasuredGeometries) ||
146
0
            EQUAL(pszCap, ODsCZGeometries))
147
0
        {
148
0
            return true;
149
0
        }
150
0
        return false;
151
0
    }
152
};
153
154
int GDALVectorConcatOutputDataset::GetLayerCount() const
155
0
{
156
0
    return static_cast<int>(m_layers.size());
157
0
}
158
159
/************************************************************************/
160
/*                     GDALVectorConcatRenamedLayer                     */
161
/************************************************************************/
162
163
class GDALVectorConcatRenamedLayer final : public OGRLayerDecorator
164
{
165
  public:
166
    GDALVectorConcatRenamedLayer(OGRLayer *poSrcLayer,
167
                                 const std::string &newName)
168
0
        : OGRLayerDecorator(poSrcLayer, false), m_newName(newName)
169
0
    {
170
0
    }
171
172
    const char *GetName() const override;
173
174
  private:
175
    const std::string m_newName;
176
};
177
178
const char *GDALVectorConcatRenamedLayer::GetName() const
179
0
{
180
0
    return m_newName.c_str();
181
0
}
182
183
/************************************************************************/
184
/*                           BuildLayerName()                           */
185
/************************************************************************/
186
187
static std::string BuildLayerName(const std::string &layerNameTemplate,
188
                                  int dsIdx, const std::string &dsName,
189
                                  int lyrIdx, const std::string &lyrName)
190
0
{
191
0
    CPLString ret = layerNameTemplate;
192
0
    std::string baseName;
193
0
    VSIStatBufL sStat;
194
0
    if (VSIStatL(dsName.c_str(), &sStat) == 0)
195
0
        baseName = CPLGetBasenameSafe(dsName.c_str());
196
197
0
    if (baseName == lyrName)
198
0
    {
199
0
        ret = ret.replaceAll("{AUTO_NAME}", baseName);
200
0
    }
201
0
    else
202
0
    {
203
0
        ret = ret.replaceAll("{AUTO_NAME}",
204
0
                             std::string(baseName.empty() ? dsName : baseName)
205
0
                                 .append("_")
206
0
                                 .append(lyrName));
207
0
    }
208
209
0
    ret =
210
0
        ret.replaceAll("{DS_BASENAME}", !baseName.empty() ? baseName : dsName);
211
0
    ret = ret.replaceAll("{DS_NAME}", dsName);
212
0
    ret = ret.replaceAll("{DS_INDEX}", std::to_string(dsIdx).c_str());
213
0
    ret = ret.replaceAll("{LAYER_NAME}", lyrName);
214
0
    ret = ret.replaceAll("{LAYER_INDEX}", std::to_string(lyrIdx).c_str());
215
216
0
    return std::string(std::move(ret));
217
0
}
218
219
namespace
220
{
221
222
/************************************************************************/
223
/*                          OpenProxiedLayer()                          */
224
/************************************************************************/
225
226
struct PooledInitData
227
{
228
    std::unique_ptr<GDALDataset> poDS{};
229
    std::string osDatasetName{};
230
    // Copies, and not pointers to the algorithm members, as a layer may be
231
    // (re)opened after the algorithm has been destroyed.
232
    CPLStringList aosInputFormats{};
233
    CPLStringList aosOpenOptions{};
234
    int iLayer = 0;
235
};
236
237
static OGRLayer *OpenProxiedLayer(void *pUserData)
238
0
{
239
0
    PooledInitData *pData = static_cast<PooledInitData *>(pUserData);
240
0
    pData->poDS.reset(GDALDataset::Open(
241
0
        pData->osDatasetName.c_str(), GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR,
242
0
        pData->aosInputFormats.List(), pData->aosOpenOptions.List(), nullptr));
243
0
    if (!pData->poDS)
244
0
        return nullptr;
245
0
    return pData->poDS->GetLayer(pData->iLayer);
246
0
}
247
248
/************************************************************************/
249
/*                        ReleaseProxiedLayer()                         */
250
/************************************************************************/
251
252
static void ReleaseProxiedLayer(OGRLayer *, void *pUserData)
253
0
{
254
0
    PooledInitData *pData = static_cast<PooledInitData *>(pUserData);
255
0
    pData->poDS.reset();
256
0
}
257
258
/************************************************************************/
259
/*                      FreeProxiedLayerUserData()                      */
260
/************************************************************************/
261
262
static void FreeProxiedLayerUserData(void *pUserData)
263
0
{
264
0
    delete static_cast<PooledInitData *>(pUserData);
265
0
}
266
267
}  // namespace
268
269
/************************************************************************/
270
/*                 GDALVectorConcatAlgorithm::RunStep()                 */
271
/************************************************************************/
272
273
bool GDALVectorConcatAlgorithm::RunStep(GDALPipelineStepRunContext &)
274
0
{
275
0
    std::unique_ptr<OGRSpatialReference> poSrcCRS;
276
0
    if (!m_srsCrs.empty())
277
0
    {
278
0
        poSrcCRS = std::make_unique<OGRSpatialReference>();
279
0
        poSrcCRS->SetFromUserInput(m_srsCrs.c_str());
280
0
        poSrcCRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
281
0
    }
282
283
0
    OGRSpatialReference oDstCRS;
284
0
    if (!m_dstCrs.empty())
285
0
    {
286
0
        oDstCRS.SetFromUserInput(m_dstCrs.c_str());
287
0
        oDstCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
288
0
    }
289
290
0
    struct LayerDesc
291
0
    {
292
0
        int iDS = 0;
293
0
        int iLayer = 0;
294
0
        std::string osDatasetName{};
295
0
    };
296
297
0
    if (m_layerNameTemplate.empty())
298
0
    {
299
0
        if (m_mode == "single")
300
0
            m_layerNameTemplate = "merged";
301
0
        else if (m_mode == "stack")
302
0
            m_layerNameTemplate = "{AUTO_NAME}";
303
0
    }
304
0
    else if (m_mode == "merge-per-layer-name")
305
0
    {
306
0
        ReportError(CE_Failure, CPLE_IllegalArg,
307
0
                    "'layer-name' name argument cannot be specified in "
308
0
                    "mode=merge-per-layer-name");
309
0
        return false;
310
0
    }
311
312
0
    if (m_sourceLayerFieldContent.empty())
313
0
        m_sourceLayerFieldContent = "{AUTO_NAME}";
314
0
    else if (m_sourceLayerFieldName.empty())
315
0
        m_sourceLayerFieldName = "source_ds_lyr";
316
317
0
    const int nMaxSimultaneouslyOpened =
318
0
        std::max(atoi(CPLGetConfigOption(
319
0
                     "GDAL_VECTOR_CONCAT_MAX_OPENED_DATASETS", "100")),
320
0
                 1);
321
322
    // First pass on input layers
323
0
    std::map<std::string, std::vector<LayerDesc>> allLayerNames;
324
0
    int iDS = 0;
325
0
    int nonOpenedDSCount = 0;
326
0
    for (auto &srcDS : m_inputDataset)
327
0
    {
328
0
        GDALDataset *poSrcDS = srcDS.GetDatasetRef();
329
0
        std::unique_ptr<GDALDataset> poTmpDS;
330
0
        if (!poSrcDS)
331
0
        {
332
0
            poTmpDS.reset(GDALDataset::Open(
333
0
                srcDS.GetName().c_str(), GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR,
334
0
                CPLStringList(m_inputFormats).List(),
335
0
                CPLStringList(m_openOptions).List(), nullptr));
336
0
            poSrcDS = poTmpDS.get();
337
0
            if (!poSrcDS)
338
0
                return false;
339
0
            if (static_cast<int>(m_inputDataset.size()) <=
340
0
                nMaxSimultaneouslyOpened)
341
0
            {
342
0
                srcDS.Set(std::move(poTmpDS));
343
0
                poSrcDS = srcDS.GetDatasetRef();
344
0
            }
345
0
            else
346
0
            {
347
0
                ++nonOpenedDSCount;
348
0
            }
349
0
        }
350
351
0
        int iLayer = 0;
352
0
        for (const auto &poLayer : poSrcDS->GetLayers())
353
0
        {
354
0
            if (m_inputLayerNames.empty() ||
355
0
                std::find(m_inputLayerNames.begin(), m_inputLayerNames.end(),
356
0
                          poLayer->GetName()) != m_inputLayerNames.end())
357
0
            {
358
0
                if (!m_dstCrs.empty() && m_srsCrs.empty() &&
359
0
                    poLayer->GetSpatialRef() == nullptr)
360
0
                {
361
0
                    ReportError(
362
0
                        CE_Failure, CPLE_AppDefined,
363
0
                        "Layer '%s' of '%s' has no spatial reference system",
364
0
                        poLayer->GetName(), poSrcDS->GetDescription());
365
0
                    return false;
366
0
                }
367
0
                LayerDesc layerDesc;
368
0
                layerDesc.iDS = iDS;
369
0
                layerDesc.iLayer = iLayer;
370
0
                layerDesc.osDatasetName = poSrcDS->GetDescription();
371
0
                const std::string outLayerName =
372
0
                    m_mode == "single" ? m_layerNameTemplate
373
0
                    : m_mode == "merge-per-layer-name"
374
0
                        ? std::string(poLayer->GetName())
375
0
                        : BuildLayerName(m_layerNameTemplate, iDS,
376
0
                                         poSrcDS->GetDescription(), iLayer,
377
0
                                         poLayer->GetName());
378
0
                CPLDebugOnly("gdal_vector_concat", "%s,%s->%s",
379
0
                             poSrcDS->GetDescription(), poLayer->GetName(),
380
0
                             outLayerName.c_str());
381
0
                allLayerNames[outLayerName].push_back(std::move(layerDesc));
382
0
            }
383
0
            ++iLayer;
384
0
        }
385
0
        ++iDS;
386
0
    }
387
388
0
    auto poUnionDS = std::make_unique<GDALVectorConcatOutputDataset>();
389
390
0
    OGRLayerPool *poLayerPool = nullptr;
391
0
    if (nonOpenedDSCount > nMaxSimultaneouslyOpened)
392
0
        poLayerPool = poUnionDS->CreateLayerPool(nMaxSimultaneouslyOpened);
393
394
0
    bool ret = true;
395
0
    for (const auto &[outLayerName, listOfLayers] : allLayerNames)
396
0
    {
397
0
        const int nLayerCount = static_cast<int>(listOfLayers.size());
398
0
        std::unique_ptr<OGRLayer *, VSIFreeReleaser> papoSrcLayers(
399
0
            static_cast<OGRLayer **>(
400
0
                CPLCalloc(nLayerCount, sizeof(OGRLayer *))));
401
0
        for (const auto [i, layer] : cpl::enumerate(listOfLayers))
402
0
        {
403
0
            auto &srcDS = m_inputDataset[layer.iDS];
404
0
            GDALDataset *poSrcDS = srcDS.GetDatasetRef();
405
0
            std::unique_ptr<GDALDataset> poTmpDS;
406
0
            if (!poSrcDS)
407
0
            {
408
0
                poTmpDS.reset(GDALDataset::Open(
409
0
                    layer.osDatasetName.c_str(),
410
0
                    GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR,
411
0
                    CPLStringList(m_inputFormats).List(),
412
0
                    CPLStringList(m_openOptions).List(), nullptr));
413
0
                poSrcDS = poTmpDS.get();
414
0
                if (!poSrcDS)
415
0
                    return false;
416
0
            }
417
0
            OGRLayer *poSrcLayer = poSrcDS->GetLayer(layer.iLayer);
418
419
0
            if (poLayerPool)
420
0
            {
421
0
                auto pData = std::make_unique<PooledInitData>();
422
0
                pData->osDatasetName = layer.osDatasetName;
423
0
                pData->aosInputFormats = CPLStringList(m_inputFormats);
424
0
                pData->aosOpenOptions = CPLStringList(m_openOptions);
425
0
                pData->iLayer = layer.iLayer;
426
0
                auto proxiedLayer = std::make_unique<OGRProxiedLayer>(
427
0
                    poLayerPool, OpenProxiedLayer, ReleaseProxiedLayer,
428
0
                    FreeProxiedLayerUserData, pData.release());
429
0
                proxiedLayer->SetDescription(poSrcLayer->GetDescription());
430
0
                poSrcLayer = poUnionDS->AddTempLayer(std::move(proxiedLayer));
431
0
            }
432
0
            else if (poTmpDS)
433
0
            {
434
0
                srcDS.Set(std::move(poTmpDS));
435
0
            }
436
437
0
            if (m_sourceLayerFieldName.empty())
438
0
            {
439
0
                papoSrcLayers.get()[i] = poSrcLayer;
440
0
            }
441
0
            else
442
0
            {
443
0
                const std::string newSrcLayerName = BuildLayerName(
444
0
                    m_sourceLayerFieldContent, listOfLayers[i].iDS,
445
0
                    listOfLayers[i].osDatasetName.c_str(),
446
0
                    listOfLayers[i].iLayer, poSrcLayer->GetName());
447
0
                ret = !newSrcLayerName.empty() && ret;
448
0
                papoSrcLayers.get()[i] = poUnionDS->AddTempLayer(
449
0
                    std::make_unique<GDALVectorConcatRenamedLayer>(
450
0
                        poSrcLayer, newSrcLayerName));
451
0
            }
452
0
        }
453
454
        // Auto-wrap source layers if needed
455
0
        if (!m_dstCrs.empty())
456
0
        {
457
0
            for (int i = 0; ret && i < nLayerCount; ++i)
458
0
            {
459
0
                const OGRSpatialReference *poSrcLayerCRS;
460
0
                if (poSrcCRS)
461
0
                    poSrcLayerCRS = poSrcCRS.get();
462
0
                else
463
0
                    poSrcLayerCRS = papoSrcLayers.get()[i]->GetSpatialRef();
464
0
                if (poSrcLayerCRS && !poSrcLayerCRS->IsSame(&oDstCRS))
465
0
                {
466
0
                    auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
467
0
                        OGRCreateCoordinateTransformation(poSrcLayerCRS,
468
0
                                                          &oDstCRS));
469
0
                    auto poReversedCT =
470
0
                        std::unique_ptr<OGRCoordinateTransformation>(
471
0
                            OGRCreateCoordinateTransformation(&oDstCRS,
472
0
                                                              poSrcLayerCRS));
473
0
                    ret = (poCT != nullptr) && (poReversedCT != nullptr);
474
0
                    if (ret)
475
0
                    {
476
0
                        papoSrcLayers.get()[i] = poUnionDS->AddTempLayer(
477
0
                            std::make_unique<OGRWarpedLayer>(
478
0
                                papoSrcLayers.get()[i], /* iGeomField = */ 0,
479
0
                                /*bTakeOwnership = */ false, std::move(poCT),
480
0
                                std::move(poReversedCT)));
481
0
                    }
482
0
                }
483
0
            }
484
0
        }
485
486
0
        auto poUnionLayer = std::make_unique<OGRUnionLayer>(
487
0
            outLayerName.c_str(), nLayerCount, papoSrcLayers.release(),
488
0
            /* bTakeLayerOwnership = */ false);
489
490
0
        if (!m_sourceLayerFieldName.empty())
491
0
        {
492
0
            poUnionLayer->SetSourceLayerFieldName(
493
0
                m_sourceLayerFieldName.c_str());
494
0
        }
495
496
0
        const FieldUnionStrategy eStrategy =
497
0
            m_fieldStrategy == "union" ? FIELD_UNION_ALL_LAYERS
498
0
                                       : FIELD_INTERSECTION_ALL_LAYERS;
499
0
        poUnionLayer->SetFields(eStrategy, 0, nullptr, 0, nullptr);
500
501
0
        poUnionDS->AddLayer(std::move(poUnionLayer));
502
0
    }
503
504
0
    if (ret)
505
0
    {
506
0
        for (auto &srcDS : m_inputDataset)
507
0
        {
508
0
            if (GDALDataset *poSrcDS = srcDS.GetDatasetIncreaseRefCount())
509
0
                poUnionDS->AddSrcDatasetRef(poSrcDS);
510
0
        }
511
512
0
        m_outputDataset.Set(std::move(poUnionDS));
513
0
    }
514
0
    return ret;
515
0
}
516
517
/************************************************************************/
518
/*                 GDALVectorConcatAlgorithm::RunImpl()                 */
519
/************************************************************************/
520
521
bool GDALVectorConcatAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
522
                                        void *pProgressData)
523
0
{
524
0
    if (m_standaloneStep)
525
0
    {
526
0
        GDALVectorWriteAlgorithm writeAlg;
527
0
        for (auto &arg : writeAlg.GetArgs())
528
0
        {
529
0
            if (!arg->IsHidden() &&
530
0
                arg->GetName() != GDAL_ARG_NAME_OUTPUT_LAYER)
531
0
            {
532
0
                auto stepArg = GetArg(arg->GetName());
533
0
                if (stepArg && stepArg->IsExplicitlySet())
534
0
                {
535
0
                    arg->SetSkipIfAlreadySet(true);
536
0
                    arg->SetFrom(*stepArg);
537
0
                }
538
0
            }
539
0
        }
540
541
        // Already checked by GDALAlgorithm::Run()
542
0
        CPLAssert(!m_executionForStreamOutput ||
543
0
                  EQUAL(m_format.c_str(), "stream"));
544
545
0
        m_standaloneStep = false;
546
0
        m_alreadyRun = false;
547
0
        bool ret = Run(pfnProgress, pProgressData);
548
0
        m_standaloneStep = true;
549
0
        if (ret)
550
0
        {
551
0
            if (m_format == "stream")
552
0
            {
553
0
                ret = true;
554
0
            }
555
0
            else
556
0
            {
557
0
                writeAlg.m_inputDataset.clear();
558
0
                writeAlg.m_inputDataset.resize(1);
559
0
                writeAlg.m_inputDataset[0].Set(m_outputDataset.GetDatasetRef());
560
0
                if (writeAlg.Run(pfnProgress, pProgressData))
561
0
                {
562
0
                    m_outputDataset.Set(
563
0
                        writeAlg.m_outputDataset.GetDatasetRef());
564
0
                    ret = true;
565
0
                }
566
0
            }
567
0
        }
568
569
0
        return ret;
570
0
    }
571
0
    else
572
0
    {
573
0
        GDALPipelineStepRunContext stepCtxt;
574
0
        stepCtxt.m_pfnProgress = pfnProgress;
575
0
        stepCtxt.m_pProgressData = pProgressData;
576
0
        return RunStep(stepCtxt);
577
0
    }
578
0
}
579
580
GDALVectorConcatAlgorithmStandalone::~GDALVectorConcatAlgorithmStandalone() =
581
    default;
582
583
//! @endcond