Coverage Report

Created: 2026-07-14 06:21

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