Coverage Report

Created: 2026-02-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalalg_vector_explode_collections.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  "gdal vector explode-collections"
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_explode_collections.h"
14
15
#include "gdal_priv.h"
16
#include "ogrsf_frmts.h"
17
18
#include <list>
19
#include <utility>
20
21
//! @cond Doxygen_Suppress
22
23
#ifndef _
24
0
#define _(x) (x)
25
#endif
26
27
/************************************************************************/
28
/*               GDALVectorExplodeCollectionsAlgorithm()                */
29
/************************************************************************/
30
31
GDALVectorExplodeCollectionsAlgorithm::GDALVectorExplodeCollectionsAlgorithm(
32
    bool standaloneStep)
33
0
    : GDALVectorGeomAbstractAlgorithm(NAME, DESCRIPTION, HELP_URL,
34
0
                                      standaloneStep, m_opts)
35
0
{
36
0
    AddArg("geometry-type", 0, _("Geometry type"), &m_opts.m_type)
37
0
        .SetAutoCompleteFunction(
38
0
            [](const std::string &currentValue)
39
0
            {
40
0
                std::vector<std::string> oRet;
41
0
                for (const char *type :
42
0
                     {"GEOMETRY", "POINT", "LINESTRING", "POLYGON",
43
0
                      "CIRCULARSTRING", "COMPOUNDCURVE", "CURVEPOLYGON",
44
0
                      "POLYHEDRALSURFACE", "TIN"})
45
0
                {
46
0
                    if (currentValue.empty() ||
47
0
                        STARTS_WITH(type, currentValue.c_str()))
48
0
                    {
49
0
                        oRet.push_back(type);
50
0
                        oRet.push_back(std::string(type).append("Z"));
51
0
                        oRet.push_back(std::string(type).append("M"));
52
0
                        oRet.push_back(std::string(type).append("ZM"));
53
0
                    }
54
0
                }
55
0
                return oRet;
56
0
            });
57
58
0
    AddArg("skip-on-type-mismatch", 0,
59
0
           _("Skip feature when change of feature geometry type failed"),
60
0
           &m_opts.m_skip);
61
0
}
62
63
namespace
64
{
65
66
/************************************************************************/
67
/*              GDALVectorExplodeCollectionsAlgorithmLayer              */
68
/************************************************************************/
69
70
class GDALVectorExplodeCollectionsAlgorithmLayer final
71
    : public GDALVectorPipelineOutputLayer
72
{
73
  private:
74
    const GDALVectorExplodeCollectionsAlgorithm::Options m_opts;
75
    int m_iGeomIdx = -1;
76
    OGRFeatureDefn *const m_poFeatureDefn = nullptr;
77
    GIntBig m_nextFID = 1;
78
79
    CPL_DISALLOW_COPY_ASSIGN(GDALVectorExplodeCollectionsAlgorithmLayer)
80
81
    void TranslateFeature(
82
        std::unique_ptr<OGRFeature> poSrcFeature,
83
        std::vector<std::unique_ptr<OGRFeature>> &apoOutFeatures) override;
84
85
    bool IsSelectedGeomField(int idx) const
86
0
    {
87
0
        return m_iGeomIdx < 0 || idx == m_iGeomIdx;
88
0
    }
89
90
  public:
91
    GDALVectorExplodeCollectionsAlgorithmLayer(
92
        OGRLayer &oSrcLayer,
93
        const GDALVectorExplodeCollectionsAlgorithm::Options &opts);
94
95
    ~GDALVectorExplodeCollectionsAlgorithmLayer() override
96
0
    {
97
0
        m_poFeatureDefn->Release();
98
0
    }
99
100
    const OGRFeatureDefn *GetLayerDefn() const override
101
0
    {
102
0
        return m_poFeatureDefn;
103
0
    }
104
105
    void ResetReading() override
106
0
    {
107
0
        m_nextFID = 1;
108
0
        GDALVectorPipelineOutputLayer::ResetReading();
109
0
    }
110
111
    OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent,
112
                      bool bForce) override
113
0
    {
114
0
        return m_srcLayer.GetExtent(iGeomField, psExtent, bForce);
115
0
    }
116
117
    int TestCapability(const char *pszCap) const override
118
0
    {
119
0
        if (EQUAL(pszCap, OLCCurveGeometries) ||
120
0
            EQUAL(pszCap, OLCMeasuredGeometries) ||
121
0
            EQUAL(pszCap, OLCZGeometries) || EQUAL(pszCap, OLCFastGetExtent) ||
122
0
            EQUAL(pszCap, OLCStringsAsUTF8))
123
0
        {
124
0
            return m_srcLayer.TestCapability(pszCap);
125
0
        }
126
0
        return false;
127
0
    }
128
};
129
130
/************************************************************************/
131
/*             GDALVectorExplodeCollectionsAlgorithmLayer()             */
132
/************************************************************************/
133
134
GDALVectorExplodeCollectionsAlgorithmLayer::
135
    GDALVectorExplodeCollectionsAlgorithmLayer(
136
        OGRLayer &oSrcLayer,
137
        const GDALVectorExplodeCollectionsAlgorithm::Options &opts)
138
0
    : GDALVectorPipelineOutputLayer(oSrcLayer), m_opts(opts),
139
0
      m_poFeatureDefn(oSrcLayer.GetLayerDefn()->Clone())
140
0
{
141
0
    SetDescription(oSrcLayer.GetDescription());
142
0
    SetMetadata(oSrcLayer.GetMetadata());
143
0
    m_poFeatureDefn->Reference();
144
145
0
    if (!m_opts.m_geomField.empty())
146
0
    {
147
0
        const int nIdx = oSrcLayer.GetLayerDefn()->GetGeomFieldIndex(
148
0
            m_opts.m_geomField.c_str());
149
0
        if (nIdx >= 0)
150
0
            m_iGeomIdx = nIdx;
151
0
        else
152
0
            m_iGeomIdx = INT_MAX;
153
0
    }
154
155
0
    for (int i = 0; i < m_poFeatureDefn->GetGeomFieldCount(); ++i)
156
0
    {
157
0
        if (IsSelectedGeomField(i))
158
0
        {
159
0
            const auto poGeomFieldDefn = m_poFeatureDefn->GetGeomFieldDefn(i);
160
0
            poGeomFieldDefn->SetType(
161
0
                !m_opts.m_type.empty()
162
0
                    ? m_opts.m_eType
163
0
                    : OGR_GT_GetSingle(poGeomFieldDefn->GetType()));
164
0
        }
165
0
    }
166
0
}
167
168
/************************************************************************/
169
/*                          TranslateFeature()                          */
170
/************************************************************************/
171
172
void GDALVectorExplodeCollectionsAlgorithmLayer::TranslateFeature(
173
    std::unique_ptr<OGRFeature> poSrcFeature,
174
    std::vector<std::unique_ptr<OGRFeature>> &apoOutFeatures)
175
0
{
176
0
    std::list<std::pair<std::unique_ptr<OGRFeature>, int>> apoTmpFeatures;
177
0
    apoTmpFeatures.emplace_back(std::move(poSrcFeature), 0);
178
0
    const int nGeomFieldCount = m_poFeatureDefn->GetGeomFieldCount();
179
0
    while (!apoTmpFeatures.empty())
180
0
    {
181
0
        auto [poCurFeature, nextGeomIndex] = std::move(apoTmpFeatures.front());
182
0
        auto insertionPoint = apoTmpFeatures.erase(apoTmpFeatures.begin());
183
0
        bool bInsertionDone = false;
184
0
        for (int i = nextGeomIndex; i < nGeomFieldCount; ++i)
185
0
        {
186
0
            auto poGeom = poCurFeature->GetGeomFieldRef(i);
187
0
            if (poGeom && !poGeom->IsEmpty() &&
188
0
                OGR_GT_IsSubClassOf(poGeom->getGeometryType(),
189
0
                                    wkbGeometryCollection) &&
190
0
                IsSelectedGeomField(i))
191
0
            {
192
0
                const auto poGeomFieldDefn =
193
0
                    m_poFeatureDefn->GetGeomFieldDefn(i);
194
0
                bInsertionDone = true;
195
0
                const auto eTargetType =
196
0
                    !m_opts.m_type.empty()
197
0
                        ? m_opts.m_eType
198
0
                        : OGR_GT_GetSingle(poGeomFieldDefn->GetType());
199
0
                auto poColl = std::unique_ptr<OGRGeometryCollection>(
200
0
                    poCurFeature->StealGeometry(i)->toGeometryCollection());
201
0
                bool bTmpFeaturesInserted = false;
202
0
                for (const auto *poSubGeomRef : poColl.get())
203
0
                {
204
0
                    auto poNewFeature =
205
0
                        std::unique_ptr<OGRFeature>(poCurFeature->Clone());
206
0
                    auto poNewGeom =
207
0
                        std::unique_ptr<OGRGeometry>(poSubGeomRef->clone());
208
0
                    if (poNewGeom->getGeometryType() != eTargetType)
209
0
                        poNewGeom = OGRGeometryFactory::forceTo(
210
0
                            std::move(poNewGeom), eTargetType);
211
0
                    if (m_opts.m_skip && !m_opts.m_type.empty() &&
212
0
                        (!poNewGeom ||
213
0
                         (wkbFlatten(eTargetType) != wkbUnknown &&
214
0
                          poNewGeom->getGeometryType() != eTargetType)))
215
0
                    {
216
                        // skip
217
0
                    }
218
0
                    else
219
0
                    {
220
0
                        poNewGeom->assignSpatialReference(
221
0
                            poGeomFieldDefn->GetSpatialRef());
222
0
                        poNewFeature->SetGeomFieldDirectly(i,
223
0
                                                           poNewGeom.release());
224
225
0
                        if (!m_opts.m_geomField.empty() ||
226
0
                            i == nGeomFieldCount - 1)
227
0
                        {
228
0
                            poNewFeature->SetFDefnUnsafe(m_poFeatureDefn);
229
0
                            poNewFeature->SetFID(m_nextFID);
230
0
                            ++m_nextFID;
231
0
                            apoOutFeatures.push_back(std::move(poNewFeature));
232
0
                        }
233
0
                        else
234
0
                        {
235
0
                            bTmpFeaturesInserted = true;
236
0
                            apoTmpFeatures.insert(
237
0
                                insertionPoint,
238
0
                                std::pair<std::unique_ptr<OGRFeature>, int>(
239
0
                                    std::move(poNewFeature),
240
0
                                    nextGeomIndex + 1));
241
0
                        }
242
0
                    }
243
0
                }
244
245
0
                if (bTmpFeaturesInserted)
246
0
                    break;
247
0
            }
248
0
            else if (poGeom)
249
0
            {
250
0
                const auto poGeomFieldDefn =
251
0
                    m_poFeatureDefn->GetGeomFieldDefn(i);
252
0
                poGeom->assignSpatialReference(
253
0
                    poGeomFieldDefn->GetSpatialRef());
254
0
            }
255
0
        }
256
0
        if (!bInsertionDone)
257
0
        {
258
0
            poCurFeature->SetFDefnUnsafe(m_poFeatureDefn);
259
0
            poCurFeature->SetFID(m_nextFID);
260
0
            ++m_nextFID;
261
0
            apoOutFeatures.push_back(std::move(poCurFeature));
262
0
        }
263
0
    }
264
0
}
265
266
}  // namespace
267
268
/************************************************************************/
269
/*       GDALVectorExplodeCollectionsAlgorithm::CreateAlgLayer()        */
270
/************************************************************************/
271
272
std::unique_ptr<OGRLayerWithTranslateFeature>
273
GDALVectorExplodeCollectionsAlgorithm::CreateAlgLayer(OGRLayer &srcLayer)
274
0
{
275
0
    return std::make_unique<GDALVectorExplodeCollectionsAlgorithmLayer>(
276
0
        srcLayer, m_opts);
277
0
}
278
279
/************************************************************************/
280
/*           GDALVectorExplodeCollectionsAlgorithm::RunStep()           */
281
/************************************************************************/
282
283
bool GDALVectorExplodeCollectionsAlgorithm::RunStep(
284
    GDALPipelineStepRunContext &ctxt)
285
0
{
286
0
    if (!m_opts.m_type.empty())
287
0
    {
288
0
        m_opts.m_eType = OGRFromOGCGeomType(m_opts.m_type.c_str());
289
0
        if (wkbFlatten(m_opts.m_eType) == wkbUnknown &&
290
0
            !STARTS_WITH_CI(m_opts.m_type.c_str(), "GEOMETRY"))
291
0
        {
292
0
            ReportError(CE_Failure, CPLE_AppDefined,
293
0
                        "Invalid geometry type '%s'", m_opts.m_type.c_str());
294
0
            return false;
295
0
        }
296
0
    }
297
298
0
    return GDALVectorGeomAbstractAlgorithm::RunStep(ctxt);
299
0
}
300
301
GDALVectorExplodeCollectionsAlgorithmStandalone::
302
0
    ~GDALVectorExplodeCollectionsAlgorithmStandalone() = default;
303
304
//! @endcond