Coverage Report

Created: 2026-04-10 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalalg_vector_combine.cpp
Line
Count
Source
1
/******************************************************************************
2
*
3
 * Project:  GDAL
4
 * Purpose:  "gdal vector combine" subcommand
5
 * Author:   Daniel Baston
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2025-2026, ISciences LLC
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdalalg_vector_combine.h"
14
15
#include "cpl_enumerate.h"
16
#include "cpl_error.h"
17
#include "gdal_priv.h"
18
#include "gdalalg_vector_geom.h"
19
#include "ogr_geometry.h"
20
21
#include <algorithm>
22
#include <cinttypes>
23
#include <optional>
24
25
#ifndef _
26
0
#define _(x) (x)
27
#endif
28
29
//! @cond Doxygen_Suppress
30
31
GDALVectorCombineAlgorithm::GDALVectorCombineAlgorithm(bool standaloneStep)
32
0
    : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
33
0
                                      standaloneStep)
34
0
{
35
0
    AddArg("group-by", 0,
36
0
           _("Names of field(s) by which inputs should be grouped"), &m_groupBy)
37
0
        .SetDuplicateValuesAllowed(false);
38
39
0
    AddArg("keep-nested", 0,
40
0
           _("Avoid combining the components of multipart geometries"),
41
0
           &m_keepNested);
42
43
0
    AddArg("add-extra-fields", 0,
44
0
           _("Whether to add extra fields, depending on if they have identical "
45
0
             "values within each group"),
46
0
           &m_addExtraFields)
47
0
        .SetChoices(NO, SOMETIMES_IDENTICAL, ALWAYS_IDENTICAL)
48
0
        .SetDefault(m_addExtraFields)
49
0
        .AddValidationAction(
50
0
            [this]()
51
0
            {
52
                // We check the SQLITE driver availability, because we need to
53
                // issue a SQL request using the SQLITE dialect, but that works
54
                // on any source dataset.
55
0
                if (m_addExtraFields != NO &&
56
0
                    GetGDALDriverManager()->GetDriverByName("SQLITE") ==
57
0
                        nullptr)
58
0
                {
59
0
                    ReportError(CE_Failure, CPLE_NotSupported,
60
0
                                "The SQLITE driver must be available for "
61
0
                                "add-extra-fields=%s",
62
0
                                m_addExtraFields.c_str());
63
0
                    return false;
64
0
                }
65
0
                return true;
66
0
            });
67
0
}
68
69
namespace
70
{
71
class GDALVectorCombineOutputLayer final
72
    : public GDALVectorNonStreamingAlgorithmLayer
73
{
74
    /** Identify which fields have, at least for one group, the same
75
     * value within the rows of the group, and add them to the destination
76
     * feature definition, after the group-by fields.
77
     */
78
    void IdentifySrcFieldsThatCanBeCopied(GDALDataset &srcDS,
79
                                          const std::string &addExtraFields)
80
0
    {
81
0
        const OGRFeatureDefn *srcDefn = m_srcLayer.GetLayerDefn();
82
0
        if (srcDefn->GetFieldCount() > static_cast<int>(m_groupBy.size()))
83
0
        {
84
0
            std::vector<std::pair<std::string, int>> extraFieldCandidates;
85
86
0
            const auto itSrcFields = srcDefn->GetFields();
87
0
            for (const auto [iSrcField, srcFieldDefn] :
88
0
                 cpl::enumerate(itSrcFields))
89
0
            {
90
0
                const char *fieldName = srcFieldDefn->GetNameRef();
91
0
                if (std::find(m_groupBy.begin(), m_groupBy.end(), fieldName) ==
92
0
                    m_groupBy.end())
93
0
                {
94
0
                    extraFieldCandidates.emplace_back(
95
0
                        fieldName, static_cast<int>(iSrcField));
96
0
                }
97
0
            }
98
99
0
            std::string sql("SELECT ");
100
0
            bool addComma = false;
101
0
            for (const auto &[fieldName, _] : extraFieldCandidates)
102
0
            {
103
0
                if (addComma)
104
0
                    sql += ", ";
105
0
                addComma = true;
106
0
                if (addExtraFields ==
107
0
                    GDALVectorCombineAlgorithm::ALWAYS_IDENTICAL)
108
0
                    sql += "MIN(";
109
0
                else
110
0
                    sql += "MAX(";
111
0
                sql += CPLQuotedSQLIdentifier(fieldName.c_str());
112
0
                sql += ')';
113
0
            }
114
0
            sql += " FROM (SELECT ";
115
0
            addComma = false;
116
0
            for (const auto &[fieldName, _] : extraFieldCandidates)
117
0
            {
118
0
                if (addComma)
119
0
                    sql += ", ";
120
0
                addComma = true;
121
0
                sql += "(COUNT(DISTINCT COALESCE(";
122
0
                sql += CPLQuotedSQLIdentifier(fieldName.c_str());
123
0
                sql += ", '__NULL__')) == 1) AS ";
124
0
                sql += CPLQuotedSQLIdentifier(fieldName.c_str());
125
0
            }
126
0
            sql += " FROM ";
127
0
            sql += CPLQuotedSQLIdentifier(GetLayerDefn()->GetName());
128
0
            if (!m_groupBy.empty())
129
0
            {
130
0
                sql += " GROUP BY ";
131
0
                addComma = false;
132
0
                for (const auto &fieldName : m_groupBy)
133
0
                {
134
0
                    if (addComma)
135
0
                        sql += ", ";
136
0
                    addComma = true;
137
0
                    sql += CPLQuotedSQLIdentifier(fieldName.c_str());
138
0
                }
139
0
            }
140
0
            sql += ") dummy_table_name";
141
142
0
            auto poSQLyr = srcDS.ExecuteSQL(sql.c_str(), nullptr, "SQLite");
143
0
            if (poSQLyr)
144
0
            {
145
0
                auto poResultFeature =
146
0
                    std::unique_ptr<OGRFeature>(poSQLyr->GetNextFeature());
147
0
                if (poResultFeature)
148
0
                {
149
0
                    CPLAssert(poResultFeature->GetFieldCount() ==
150
0
                              static_cast<int>(extraFieldCandidates.size()));
151
0
                    for (const auto &[iSqlCol, srcFieldInfo] :
152
0
                         cpl::enumerate(extraFieldCandidates))
153
0
                    {
154
0
                        const int iSrcField = srcFieldInfo.second;
155
0
                        if (poResultFeature->GetFieldAsInteger(
156
0
                                static_cast<int>(iSqlCol)) == 1)
157
0
                        {
158
0
                            m_defn->AddFieldDefn(
159
0
                                srcDefn->GetFieldDefn(iSrcField));
160
0
                            m_srcExtraFieldIndices.push_back(iSrcField);
161
0
                        }
162
0
                        else
163
0
                        {
164
0
                            CPLDebugOnly(
165
0
                                "gdalalg_vector_combine",
166
0
                                "Field %s has the same values within a group",
167
0
                                srcFieldInfo.first.c_str());
168
0
                        }
169
0
                    }
170
0
                }
171
0
                srcDS.ReleaseResultSet(poSQLyr);
172
0
            }
173
0
        }
174
0
    }
175
176
  public:
177
    explicit GDALVectorCombineOutputLayer(
178
        GDALDataset &srcDS, OGRLayer &srcLayer, int geomFieldIndex,
179
        const std::vector<std::string> &groupBy, bool keepNested,
180
        const std::string &addExtraFields)
181
0
        : GDALVectorNonStreamingAlgorithmLayer(srcLayer, geomFieldIndex),
182
0
          m_groupBy(groupBy), m_defn(OGRFeatureDefnRefCountedPtr::makeInstance(
183
0
                                  srcLayer.GetLayerDefn()->GetName())),
184
0
          m_keepNested(keepNested)
185
0
    {
186
0
        const OGRFeatureDefn *srcDefn = m_srcLayer.GetLayerDefn();
187
188
        // Copy field definitions for attribute fields used in
189
        // --group-by. All other attributes are discarded.
190
0
        for (const auto &fieldName : m_groupBy)
191
0
        {
192
            // RunStep already checked that the field exists
193
0
            const auto iField = srcDefn->GetFieldIndex(fieldName.c_str());
194
0
            CPLAssert(iField >= 0);
195
196
0
            m_srcGroupByFieldIndices.push_back(iField);
197
0
            m_defn->AddFieldDefn(srcDefn->GetFieldDefn(iField));
198
0
        }
199
200
0
        if (addExtraFields != GDALVectorCombineAlgorithm::NO)
201
0
            IdentifySrcFieldsThatCanBeCopied(srcDS, addExtraFields);
202
203
        // Create a new geometry field corresponding to each input geometry
204
        // field. An appropriate type is worked out below.
205
0
        m_defn->SetGeomType(wkbNone);  // Remove default geometry field
206
0
        for (const OGRGeomFieldDefn *srcGeomDefn : srcDefn->GetGeomFields())
207
0
        {
208
0
            const auto eSrcGeomType = srcGeomDefn->GetType();
209
0
            const bool bHasZ = OGR_GT_HasZ(eSrcGeomType);
210
0
            const bool bHasM = OGR_GT_HasM(eSrcGeomType);
211
212
0
            OGRwkbGeometryType eDstGeomType =
213
0
                OGR_GT_SetModifier(wkbGeometryCollection, bHasZ, bHasM);
214
215
            // If the layer claims to have single-part geometries, choose a more
216
            // specific output type like "MultiPoint" rather than "GeometryCollection"
217
0
            if (wkbFlatten(eSrcGeomType) != wkbUnknown &&
218
0
                !OGR_GT_IsSubClassOf(wkbFlatten(eSrcGeomType),
219
0
                                     wkbGeometryCollection))
220
0
            {
221
0
                eDstGeomType = OGR_GT_GetCollection(eSrcGeomType);
222
0
            }
223
224
0
            auto dstGeomDefn = std::make_unique<OGRGeomFieldDefn>(
225
0
                srcGeomDefn->GetNameRef(), eDstGeomType);
226
0
            dstGeomDefn->SetSpatialRef(srcGeomDefn->GetSpatialRef());
227
0
            m_defn->AddGeomFieldDefn(std::move(dstGeomDefn));
228
0
        }
229
0
    }
230
231
    GIntBig GetFeatureCount(int bForce) override
232
0
    {
233
0
        if (m_poAttrQuery == nullptr && m_poFilterGeom == nullptr)
234
0
        {
235
0
            return static_cast<GIntBig>(m_features.size());
236
0
        }
237
238
0
        return OGRLayer::GetFeatureCount(bForce);
239
0
    }
240
241
    const OGRFeatureDefn *GetLayerDefn() const override
242
0
    {
243
0
        return m_defn.get();
244
0
    }
245
246
    OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent,
247
                      bool bForce) override
248
0
    {
249
0
        return m_srcLayer.GetExtent(iGeomField, psExtent, bForce);
250
0
    }
251
252
    OGRErr IGetExtent3D(int iGeomField, OGREnvelope3D *psExtent,
253
                        bool bForce) override
254
0
    {
255
0
        return m_srcLayer.GetExtent3D(iGeomField, psExtent, bForce);
256
0
    }
257
258
    std::unique_ptr<OGRFeature> GetNextProcessedFeature() override
259
0
    {
260
0
        if (!m_itFeature)
261
0
        {
262
0
            m_itFeature = m_features.begin();
263
0
        }
264
265
0
        if (m_itFeature.value() == m_features.end())
266
0
        {
267
0
            return nullptr;
268
0
        }
269
270
0
        std::unique_ptr<OGRFeature> feature(
271
0
            m_itFeature.value()->second->Clone());
272
0
        feature->SetFID(m_nProcessedFeaturesRead++);
273
0
        ++m_itFeature.value();
274
0
        return feature;
275
0
    }
276
277
    bool Process(GDALProgressFunc pfnProgress, void *pProgressData) override
278
0
    {
279
0
        const int nGeomFields = m_srcLayer.GetLayerDefn()->GetGeomFieldCount();
280
281
0
        const GIntBig nLayerFeatures =
282
0
            m_srcLayer.TestCapability(OLCFastFeatureCount)
283
0
                ? m_srcLayer.GetFeatureCount(false)
284
0
                : -1;
285
0
        const double dfInvLayerFeatures =
286
0
            1.0 / std::max(1.0, static_cast<double>(nLayerFeatures));
287
288
0
        GIntBig nFeaturesRead = 0;
289
290
0
        struct PairSourceFeatureUniqueValues
291
0
        {
292
0
            std::unique_ptr<OGRFeature> poSrcFeature{};
293
0
            std::vector<std::optional<std::string>> srcUniqueValues{};
294
0
        };
295
296
0
        std::map<OGRFeature *, PairSourceFeatureUniqueValues>
297
0
            mapDstFeatureToOtherFields;
298
299
0
        std::vector<std::string> fieldValues(m_srcGroupByFieldIndices.size());
300
0
        std::vector<std::string> extraFieldValues(
301
0
            m_srcExtraFieldIndices.size());
302
303
0
        std::vector<int> srcDstFieldMap(
304
0
            m_srcLayer.GetLayerDefn()->GetFieldCount(), -1);
305
0
        for (const auto [iDstField, iSrcField] :
306
0
             cpl::enumerate(m_srcGroupByFieldIndices))
307
0
        {
308
0
            srcDstFieldMap[iSrcField] = static_cast<int>(iDstField);
309
0
        }
310
311
0
        for (const auto &srcFeature : m_srcLayer)
312
0
        {
313
0
            for (const auto [iDstField, iSrcField] :
314
0
                 cpl::enumerate(m_srcGroupByFieldIndices))
315
0
            {
316
0
                fieldValues[iDstField] =
317
0
                    srcFeature->GetFieldAsString(iSrcField);
318
0
            }
319
320
0
            for (const auto [iExtraField, iSrcField] :
321
0
                 cpl::enumerate(m_srcExtraFieldIndices))
322
0
            {
323
0
                extraFieldValues[iExtraField] =
324
0
                    srcFeature->GetFieldAsString(iSrcField);
325
0
            }
326
327
0
            OGRFeature *dstFeature;
328
329
0
            if (auto it = m_features.find(fieldValues); it == m_features.end())
330
0
            {
331
0
                it = m_features
332
0
                         .insert(std::pair(
333
0
                             fieldValues,
334
0
                             std::make_unique<OGRFeature>(m_defn.get())))
335
0
                         .first;
336
0
                dstFeature = it->second.get();
337
338
0
                dstFeature->SetFrom(srcFeature.get(), srcDstFieldMap.data(),
339
0
                                    false);
340
341
0
                for (int iGeomField = 0; iGeomField < nGeomFields; iGeomField++)
342
0
                {
343
0
                    OGRGeomFieldDefn *poGeomDefn =
344
0
                        m_defn->GetGeomFieldDefn(iGeomField);
345
0
                    const auto eGeomType = poGeomDefn->GetType();
346
347
0
                    std::unique_ptr<OGRGeometry> poGeom(
348
0
                        OGRGeometryFactory::createGeometry(eGeomType));
349
0
                    poGeom->assignSpatialReference(poGeomDefn->GetSpatialRef());
350
351
0
                    dstFeature->SetGeomField(iGeomField, std::move(poGeom));
352
0
                }
353
354
0
                if (!m_srcExtraFieldIndices.empty())
355
0
                {
356
0
                    PairSourceFeatureUniqueValues pair;
357
0
                    pair.poSrcFeature.reset(srcFeature->Clone());
358
0
                    for (const std::string &s : extraFieldValues)
359
0
                        pair.srcUniqueValues.push_back(s);
360
0
                    mapDstFeatureToOtherFields[dstFeature] = std::move(pair);
361
0
                }
362
0
            }
363
0
            else
364
0
            {
365
0
                dstFeature = it->second.get();
366
367
                // Check that the extra field values for that source feature
368
                // are the same as for other source features of the same group.
369
                // If not the case, cancel the extra field value for that group.
370
0
                if (!m_srcExtraFieldIndices.empty())
371
0
                {
372
0
                    auto iterOtherFields =
373
0
                        mapDstFeatureToOtherFields.find(dstFeature);
374
0
                    CPLAssert(iterOtherFields !=
375
0
                              mapDstFeatureToOtherFields.end());
376
0
                    auto &srcUniqueValues =
377
0
                        iterOtherFields->second.srcUniqueValues;
378
0
                    CPLAssert(srcUniqueValues.size() ==
379
0
                              extraFieldValues.size());
380
0
                    for (const auto &[i, sVal] :
381
0
                         cpl::enumerate(extraFieldValues))
382
0
                    {
383
0
                        if (srcUniqueValues[i].has_value() &&
384
0
                            *(srcUniqueValues[i]) != sVal)
385
0
                        {
386
0
                            srcUniqueValues[i].reset();
387
0
                        }
388
0
                    }
389
0
                }
390
0
            }
391
392
0
            for (int iGeomField = 0; iGeomField < nGeomFields; iGeomField++)
393
0
            {
394
0
                OGRGeomFieldDefn *poGeomFieldDefn =
395
0
                    m_defn->GetGeomFieldDefn(iGeomField);
396
397
0
                std::unique_ptr<OGRGeometry> poSrcGeom(
398
0
                    srcFeature->StealGeometry(iGeomField));
399
0
                if (poSrcGeom != nullptr && !poSrcGeom->IsEmpty())
400
0
                {
401
0
                    const auto eSrcType = poSrcGeom->getGeometryType();
402
0
                    const auto bSrcIsCollection = OGR_GT_IsSubClassOf(
403
0
                        wkbFlatten(eSrcType), wkbGeometryCollection);
404
0
                    const auto bDstIsUntypedCollection =
405
0
                        wkbFlatten(poGeomFieldDefn->GetType()) ==
406
0
                        wkbGeometryCollection;
407
408
                    // Did this geometry unexpectedly have Z?
409
0
                    if (OGR_GT_HasZ(eSrcType) !=
410
0
                        OGR_GT_HasZ(poGeomFieldDefn->GetType()))
411
0
                    {
412
0
                        AddZ(iGeomField);
413
0
                    }
414
415
                    // Did this geometry unexpectedly have M?
416
0
                    if (OGR_GT_HasM(eSrcType) !=
417
0
                        OGR_GT_HasM(poGeomFieldDefn->GetType()))
418
0
                    {
419
0
                        AddM(iGeomField);
420
0
                    }
421
422
                    // Do we need to change the output from a typed collection
423
                    // like MultiPolygon to a generic GeometryCollection?
424
0
                    if (m_keepNested && bSrcIsCollection &&
425
0
                        !bDstIsUntypedCollection)
426
0
                    {
427
0
                        SetTypeGeometryCollection(iGeomField);
428
0
                    }
429
430
0
                    OGRGeometryCollection *poDstGeom =
431
0
                        cpl::down_cast<OGRGeometryCollection *>(
432
0
                            dstFeature->GetGeomFieldRef(iGeomField));
433
434
0
                    if (m_keepNested || !bSrcIsCollection)
435
0
                    {
436
0
                        if (poDstGeom->addGeometry(std::move(poSrcGeom)) !=
437
0
                            OGRERR_NONE)
438
0
                        {
439
0
                            CPLError(
440
0
                                CE_Failure, CPLE_AppDefined,
441
0
                                "Failed to add geometry of type %s to output "
442
0
                                "feature of type %s",
443
0
                                OGRGeometryTypeToName(eSrcType),
444
0
                                OGRGeometryTypeToName(
445
0
                                    poDstGeom->getGeometryType()));
446
0
                            return false;
447
0
                        }
448
0
                    }
449
0
                    else
450
0
                    {
451
0
                        std::unique_ptr<OGRGeometryCollection>
452
0
                            poSrcGeomCollection(
453
0
                                poSrcGeom.release()->toGeometryCollection());
454
0
                        if (poDstGeom->addGeometryComponents(
455
0
                                std::move(poSrcGeomCollection)) != OGRERR_NONE)
456
0
                        {
457
0
                            CPLError(CE_Failure, CPLE_AppDefined,
458
0
                                     "Failed to add components from geometry "
459
0
                                     "of type %s to output "
460
0
                                     "feature of type %s",
461
0
                                     OGRGeometryTypeToName(eSrcType),
462
0
                                     OGRGeometryTypeToName(
463
0
                                         poDstGeom->getGeometryType()));
464
0
                            return false;
465
0
                        }
466
0
                    }
467
0
                }
468
0
            }
469
470
0
            if (pfnProgress && nLayerFeatures > 0 &&
471
0
                !pfnProgress(static_cast<double>(++nFeaturesRead) *
472
0
                                 dfInvLayerFeatures,
473
0
                             "", pProgressData))
474
0
            {
475
0
                CPLError(CE_Failure, CPLE_UserInterrupt, "Interrupted by user");
476
0
                return false;
477
0
            }
478
0
        }
479
480
        // Copy extra fields from source features that have a same value
481
        // among each groups
482
0
        for (const auto &[poDstFeature, pairSourceFeatureUniqueValues] :
483
0
             mapDstFeatureToOtherFields)
484
0
        {
485
0
            for (const auto [iExtraField, iSrcField] :
486
0
                 cpl::enumerate(m_srcExtraFieldIndices))
487
0
            {
488
0
                const int iDstField = static_cast<int>(
489
0
                    m_srcGroupByFieldIndices.size() + iExtraField);
490
0
                if (pairSourceFeatureUniqueValues.srcUniqueValues[iExtraField])
491
0
                {
492
0
                    const auto poRawField =
493
0
                        pairSourceFeatureUniqueValues.poSrcFeature
494
0
                            ->GetRawFieldRef(iSrcField);
495
0
                    poDstFeature->SetField(iDstField, poRawField);
496
0
                }
497
0
            }
498
0
        }
499
500
0
        if (pfnProgress)
501
0
        {
502
0
            pfnProgress(1.0, "", pProgressData);
503
0
        }
504
505
0
        return true;
506
0
    }
507
508
    int TestCapability(const char *pszCap) const override
509
0
    {
510
0
        if (EQUAL(pszCap, OLCFastFeatureCount))
511
0
        {
512
0
            return true;
513
0
        }
514
515
0
        if (EQUAL(pszCap, OLCStringsAsUTF8) ||
516
0
            EQUAL(pszCap, OLCFastGetExtent) ||
517
0
            EQUAL(pszCap, OLCFastGetExtent3D) ||
518
0
            EQUAL(pszCap, OLCCurveGeometries) ||
519
0
            EQUAL(pszCap, OLCMeasuredGeometries) ||
520
0
            EQUAL(pszCap, OLCZGeometries))
521
0
        {
522
0
            return m_srcLayer.TestCapability(pszCap);
523
0
        }
524
525
0
        return false;
526
0
    }
527
528
    void ResetReading() override
529
0
    {
530
0
        m_itFeature.reset();
531
0
        m_nProcessedFeaturesRead = 0;
532
0
    }
533
534
    CPL_DISALLOW_COPY_ASSIGN(GDALVectorCombineOutputLayer)
535
536
  private:
537
    void AddM(int iGeomField)
538
0
    {
539
0
        OGRGeomFieldDefn *poGeomFieldDefn =
540
0
            m_defn->GetGeomFieldDefn(iGeomField);
541
0
        whileUnsealing(poGeomFieldDefn)
542
0
            ->SetType(OGR_GT_SetM(poGeomFieldDefn->GetType()));
543
544
0
        for (auto &[_, poFeature] : m_features)
545
0
        {
546
0
            poFeature->GetGeomFieldRef(iGeomField)->setMeasured(true);
547
0
        }
548
0
    }
549
550
    void AddZ(int iGeomField)
551
0
    {
552
0
        OGRGeomFieldDefn *poGeomFieldDefn =
553
0
            m_defn->GetGeomFieldDefn(iGeomField);
554
0
        whileUnsealing(poGeomFieldDefn)
555
0
            ->SetType(OGR_GT_SetZ(poGeomFieldDefn->GetType()));
556
557
0
        for (auto &[_, poFeature] : m_features)
558
0
        {
559
0
            poFeature->GetGeomFieldRef(iGeomField)->set3D(true);
560
0
        }
561
0
    }
562
563
    void SetTypeGeometryCollection(int iGeomField)
564
0
    {
565
0
        OGRGeomFieldDefn *poGeomFieldDefn =
566
0
            m_defn->GetGeomFieldDefn(iGeomField);
567
0
        const bool hasZ = OGR_GT_HasZ(poGeomFieldDefn->GetType());
568
0
        const bool hasM = OGR_GT_HasM(poGeomFieldDefn->GetType());
569
570
0
        whileUnsealing(poGeomFieldDefn)
571
0
            ->SetType(OGR_GT_SetModifier(wkbGeometryCollection, hasZ, hasM));
572
573
0
        for (auto &[_, poFeature] : m_features)
574
0
        {
575
0
            std::unique_ptr<OGRGeometry> poTmpGeom(
576
0
                poFeature->StealGeometry(iGeomField));
577
0
            poTmpGeom = OGRGeometryFactory::forceTo(std::move(poTmpGeom),
578
0
                                                    poGeomFieldDefn->GetType());
579
0
            CPLAssert(poTmpGeom);
580
0
            poFeature->SetGeomField(iGeomField, std::move(poTmpGeom));
581
0
        }
582
0
    }
583
584
    const std::vector<std::string> m_groupBy{};
585
    std::vector<int> m_srcGroupByFieldIndices{};
586
    std::vector<int> m_srcExtraFieldIndices{};
587
    std::map<std::vector<std::string>, std::unique_ptr<OGRFeature>>
588
        m_features{};
589
    std::optional<decltype(m_features)::const_iterator> m_itFeature{};
590
    const OGRFeatureDefnRefCountedPtr m_defn;
591
    GIntBig m_nProcessedFeaturesRead = 0;
592
    const bool m_keepNested;
593
};
594
}  // namespace
595
596
bool GDALVectorCombineAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
597
0
{
598
0
    auto poSrcDS = m_inputDataset[0].GetDatasetRef();
599
0
    auto poDstDS =
600
0
        std::make_unique<GDALVectorNonStreamingAlgorithmDataset>(*poSrcDS);
601
602
0
    GDALVectorAlgorithmLayerProgressHelper progressHelper(ctxt);
603
604
0
    for (auto &&poSrcLayer : poSrcDS->GetLayers())
605
0
    {
606
0
        if (m_inputLayerNames.empty() ||
607
0
            std::find(m_inputLayerNames.begin(), m_inputLayerNames.end(),
608
0
                      poSrcLayer->GetDescription()) != m_inputLayerNames.end())
609
0
        {
610
0
            const auto poSrcLayerDefn = poSrcLayer->GetLayerDefn();
611
0
            if (poSrcLayerDefn->GetGeomFieldCount() == 0)
612
0
            {
613
0
                if (m_inputLayerNames.empty())
614
0
                    continue;
615
0
                ReportError(CE_Failure, CPLE_AppDefined,
616
0
                            "Specified layer '%s' has no geometry field",
617
0
                            poSrcLayer->GetDescription());
618
0
                return false;
619
0
            }
620
621
            // Check that all attributes exist
622
0
            for (const auto &fieldName : m_groupBy)
623
0
            {
624
0
                const int iSrcFieldIndex =
625
0
                    poSrcLayerDefn->GetFieldIndex(fieldName.c_str());
626
0
                if (iSrcFieldIndex == -1)
627
0
                {
628
0
                    ReportError(CE_Failure, CPLE_AppDefined,
629
0
                                "Specified attribute field '%s' does not exist "
630
0
                                "in layer '%s'",
631
0
                                fieldName.c_str(),
632
0
                                poSrcLayer->GetDescription());
633
0
                    return false;
634
0
                }
635
0
            }
636
637
0
            progressHelper.AddProcessedLayer(*poSrcLayer);
638
0
        }
639
0
    }
640
641
0
    for ([[maybe_unused]] auto [poSrcLayer, bProcessed, layerProgressFunc,
642
0
                                layerProgressData] : progressHelper)
643
0
    {
644
0
        auto poLayer = std::make_unique<GDALVectorCombineOutputLayer>(
645
0
            *poSrcDS, *poSrcLayer, -1, m_groupBy, m_keepNested,
646
0
            m_addExtraFields);
647
648
0
        if (!poDstDS->AddProcessedLayer(std::move(poLayer), layerProgressFunc,
649
0
                                        layerProgressData.get()))
650
0
        {
651
0
            return false;
652
0
        }
653
0
    }
654
655
0
    m_outputDataset.Set(std::move(poDstDS));
656
657
0
    return true;
658
0
}
659
660
0
GDALVectorCombineAlgorithmStandalone::~GDALVectorCombineAlgorithmStandalone() =
661
    default;
662
663
//! @endcond