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_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
    const OGRFeatureDefnRefCountedPtr m_poFeatureDefn;
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
    const OGRFeatureDefn *GetLayerDefn() const override
96
0
    {
97
0
        return m_poFeatureDefn.get();
98
0
    }
99
100
    void ResetReading() override
101
0
    {
102
0
        m_nextFID = 1;
103
0
        GDALVectorPipelineOutputLayer::ResetReading();
104
0
    }
105
106
    OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent,
107
                      bool bForce) override
108
0
    {
109
0
        return m_srcLayer.GetExtent(iGeomField, psExtent, bForce);
110
0
    }
111
112
    int TestCapability(const char *pszCap) const override
113
0
    {
114
0
        if (EQUAL(pszCap, OLCCurveGeometries) ||
115
0
            EQUAL(pszCap, OLCMeasuredGeometries) ||
116
0
            EQUAL(pszCap, OLCZGeometries) || EQUAL(pszCap, OLCFastGetExtent) ||
117
0
            EQUAL(pszCap, OLCStringsAsUTF8))
118
0
        {
119
0
            return m_srcLayer.TestCapability(pszCap);
120
0
        }
121
0
        return false;
122
0
    }
123
};
124
125
/************************************************************************/
126
/*             GDALVectorExplodeCollectionsAlgorithmLayer()             */
127
/************************************************************************/
128
129
GDALVectorExplodeCollectionsAlgorithmLayer::
130
    GDALVectorExplodeCollectionsAlgorithmLayer(
131
        OGRLayer &oSrcLayer,
132
        const GDALVectorExplodeCollectionsAlgorithm::Options &opts)
133
0
    : GDALVectorPipelineOutputLayer(oSrcLayer), m_opts(opts),
134
0
      m_poFeatureDefn(oSrcLayer.GetLayerDefn()->Clone())
135
0
{
136
0
    SetDescription(oSrcLayer.GetDescription());
137
0
    SetMetadata(oSrcLayer.GetMetadata());
138
139
0
    if (!m_opts.m_geomField.empty())
140
0
    {
141
0
        const int nIdx = oSrcLayer.GetLayerDefn()->GetGeomFieldIndex(
142
0
            m_opts.m_geomField.c_str());
143
0
        if (nIdx >= 0)
144
0
            m_iGeomIdx = nIdx;
145
0
        else
146
0
            m_iGeomIdx = INT_MAX;
147
0
    }
148
149
0
    for (int i = 0; i < m_poFeatureDefn->GetGeomFieldCount(); ++i)
150
0
    {
151
0
        if (IsSelectedGeomField(i))
152
0
        {
153
0
            const auto poGeomFieldDefn = m_poFeatureDefn->GetGeomFieldDefn(i);
154
0
            poGeomFieldDefn->SetType(
155
0
                !m_opts.m_type.empty()
156
0
                    ? m_opts.m_eType
157
0
                    : OGR_GT_GetSingle(poGeomFieldDefn->GetType()));
158
0
        }
159
0
    }
160
0
}
161
162
/************************************************************************/
163
/*                          TranslateFeature()                          */
164
/************************************************************************/
165
166
void GDALVectorExplodeCollectionsAlgorithmLayer::TranslateFeature(
167
    std::unique_ptr<OGRFeature> poSrcFeature,
168
    std::vector<std::unique_ptr<OGRFeature>> &apoOutFeatures)
169
0
{
170
0
    std::list<std::pair<std::unique_ptr<OGRFeature>, int>> apoTmpFeatures;
171
0
    apoTmpFeatures.emplace_back(std::move(poSrcFeature), 0);
172
0
    const int nGeomFieldCount = m_poFeatureDefn->GetGeomFieldCount();
173
0
    while (!apoTmpFeatures.empty())
174
0
    {
175
0
        auto [poCurFeature, nextGeomIndex] = std::move(apoTmpFeatures.front());
176
0
        auto insertionPoint = apoTmpFeatures.erase(apoTmpFeatures.begin());
177
0
        bool bInsertionDone = false;
178
0
        for (int i = nextGeomIndex; i < nGeomFieldCount; ++i)
179
0
        {
180
0
            auto poGeom = poCurFeature->GetGeomFieldRef(i);
181
0
            if (poGeom && !poGeom->IsEmpty() &&
182
0
                OGR_GT_IsSubClassOf(poGeom->getGeometryType(),
183
0
                                    wkbGeometryCollection) &&
184
0
                IsSelectedGeomField(i))
185
0
            {
186
0
                const auto poGeomFieldDefn =
187
0
                    m_poFeatureDefn->GetGeomFieldDefn(i);
188
0
                bInsertionDone = true;
189
0
                const auto eTargetType =
190
0
                    !m_opts.m_type.empty()
191
0
                        ? m_opts.m_eType
192
0
                        : OGR_GT_GetSingle(poGeomFieldDefn->GetType());
193
0
                auto poColl = std::unique_ptr<OGRGeometryCollection>(
194
0
                    poCurFeature->StealGeometry(i)->toGeometryCollection());
195
0
                bool bTmpFeaturesInserted = false;
196
0
                for (const auto *poSubGeomRef : poColl.get())
197
0
                {
198
0
                    auto poNewFeature =
199
0
                        std::unique_ptr<OGRFeature>(poCurFeature->Clone());
200
0
                    auto poNewGeom =
201
0
                        std::unique_ptr<OGRGeometry>(poSubGeomRef->clone());
202
0
                    if (poNewGeom->getGeometryType() != eTargetType)
203
0
                        poNewGeom = OGRGeometryFactory::forceTo(
204
0
                            std::move(poNewGeom), eTargetType);
205
0
                    if (m_opts.m_skip && !m_opts.m_type.empty() &&
206
0
                        (!poNewGeom ||
207
0
                         (wkbFlatten(eTargetType) != wkbUnknown &&
208
0
                          poNewGeom->getGeometryType() != eTargetType)))
209
0
                    {
210
                        // skip
211
0
                    }
212
0
                    else
213
0
                    {
214
0
                        poNewGeom->assignSpatialReference(
215
0
                            poGeomFieldDefn->GetSpatialRef());
216
0
                        poNewFeature->SetGeomFieldDirectly(i,
217
0
                                                           poNewGeom.release());
218
219
0
                        if (!m_opts.m_geomField.empty() ||
220
0
                            i == nGeomFieldCount - 1)
221
0
                        {
222
0
                            poNewFeature->SetFDefnUnsafe(m_poFeatureDefn.get());
223
0
                            poNewFeature->SetFID(m_nextFID);
224
0
                            ++m_nextFID;
225
0
                            apoOutFeatures.push_back(std::move(poNewFeature));
226
0
                        }
227
0
                        else
228
0
                        {
229
0
                            bTmpFeaturesInserted = true;
230
0
                            apoTmpFeatures.insert(
231
0
                                insertionPoint,
232
0
                                std::pair<std::unique_ptr<OGRFeature>, int>(
233
0
                                    std::move(poNewFeature),
234
0
                                    nextGeomIndex + 1));
235
0
                        }
236
0
                    }
237
0
                }
238
239
0
                if (bTmpFeaturesInserted)
240
0
                    break;
241
0
            }
242
0
            else if (poGeom)
243
0
            {
244
0
                const auto poGeomFieldDefn =
245
0
                    m_poFeatureDefn->GetGeomFieldDefn(i);
246
0
                poGeom->assignSpatialReference(
247
0
                    poGeomFieldDefn->GetSpatialRef());
248
0
            }
249
0
        }
250
0
        if (!bInsertionDone)
251
0
        {
252
0
            poCurFeature->SetFDefnUnsafe(m_poFeatureDefn.get());
253
0
            poCurFeature->SetFID(m_nextFID);
254
0
            ++m_nextFID;
255
0
            apoOutFeatures.push_back(std::move(poCurFeature));
256
0
        }
257
0
    }
258
0
}
259
260
}  // namespace
261
262
/************************************************************************/
263
/*       GDALVectorExplodeCollectionsAlgorithm::CreateAlgLayer()        */
264
/************************************************************************/
265
266
std::unique_ptr<OGRLayerWithTranslateFeature>
267
GDALVectorExplodeCollectionsAlgorithm::CreateAlgLayer(OGRLayer &srcLayer)
268
0
{
269
0
    return std::make_unique<GDALVectorExplodeCollectionsAlgorithmLayer>(
270
0
        srcLayer, m_opts);
271
0
}
272
273
/************************************************************************/
274
/*           GDALVectorExplodeCollectionsAlgorithm::RunStep()           */
275
/************************************************************************/
276
277
bool GDALVectorExplodeCollectionsAlgorithm::RunStep(
278
    GDALPipelineStepRunContext &ctxt)
279
0
{
280
0
    if (!m_opts.m_type.empty())
281
0
    {
282
0
        m_opts.m_eType = OGRFromOGCGeomType(m_opts.m_type.c_str());
283
0
        if (wkbFlatten(m_opts.m_eType) == wkbUnknown &&
284
0
            !STARTS_WITH_CI(m_opts.m_type.c_str(), "GEOMETRY"))
285
0
        {
286
0
            ReportError(CE_Failure, CPLE_AppDefined,
287
0
                        "Invalid geometry type '%s'", m_opts.m_type.c_str());
288
0
            return false;
289
0
        }
290
0
    }
291
292
0
    return GDALVectorGeomAbstractAlgorithm::RunStep(ctxt);
293
0
}
294
295
GDALVectorExplodeCollectionsAlgorithmStandalone::
296
0
    ~GDALVectorExplodeCollectionsAlgorithmStandalone() = default;
297
298
//! @endcond