Coverage Report

Created: 2026-07-25 06:50

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