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_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
    bool 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
bool 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
                            if (PassesFilters(poNewFeature.get()))
226
0
                                apoOutFeatures.push_back(
227
0
                                    std::move(poNewFeature));
228
0
                        }
229
0
                        else
230
0
                        {
231
0
                            bTmpFeaturesInserted = true;
232
0
                            apoTmpFeatures.insert(
233
0
                                insertionPoint,
234
0
                                std::pair<std::unique_ptr<OGRFeature>, int>(
235
0
                                    std::move(poNewFeature),
236
0
                                    nextGeomIndex + 1));
237
0
                        }
238
0
                    }
239
0
                }
240
241
0
                if (bTmpFeaturesInserted)
242
0
                    break;
243
0
            }
244
0
            else if (poGeom)
245
0
            {
246
0
                const auto poGeomFieldDefn =
247
0
                    m_poFeatureDefn->GetGeomFieldDefn(i);
248
0
                poGeom->assignSpatialReference(
249
0
                    poGeomFieldDefn->GetSpatialRef());
250
0
            }
251
0
        }
252
0
        if (!bInsertionDone)
253
0
        {
254
0
            poCurFeature->SetFDefnUnsafe(m_poFeatureDefn.get());
255
0
            poCurFeature->SetFID(m_nextFID);
256
0
            ++m_nextFID;
257
0
            if (PassesFilters(poCurFeature.get()))
258
0
                apoOutFeatures.push_back(std::move(poCurFeature));
259
0
        }
260
0
    }
261
262
0
    return true;
263
0
}
264
265
}  // namespace
266
267
/************************************************************************/
268
/*       GDALVectorExplodeCollectionsAlgorithm::CreateAlgLayer()        */
269
/************************************************************************/
270
271
std::unique_ptr<OGRLayerWithTranslateFeature>
272
GDALVectorExplodeCollectionsAlgorithm::CreateAlgLayer(OGRLayer &srcLayer)
273
0
{
274
0
    return std::make_unique<GDALVectorExplodeCollectionsAlgorithmLayer>(
275
0
        srcLayer, m_opts);
276
0
}
277
278
/************************************************************************/
279
/*           GDALVectorExplodeCollectionsAlgorithm::RunStep()           */
280
/************************************************************************/
281
282
bool GDALVectorExplodeCollectionsAlgorithm::RunStep(
283
    GDALPipelineStepRunContext &ctxt)
284
0
{
285
0
    if (!m_opts.m_type.empty())
286
0
    {
287
0
        m_opts.m_eType = OGRFromOGCGeomType(m_opts.m_type.c_str());
288
0
        if (wkbFlatten(m_opts.m_eType) == wkbUnknown &&
289
0
            !STARTS_WITH_CI(m_opts.m_type.c_str(), "GEOMETRY"))
290
0
        {
291
0
            ReportError(CE_Failure, CPLE_AppDefined,
292
0
                        "Invalid geometry type '%s'", m_opts.m_type.c_str());
293
0
            return false;
294
0
        }
295
0
    }
296
297
0
    return GDALVectorGeomAbstractAlgorithm::RunStep(ctxt);
298
0
}
299
300
GDALVectorExplodeCollectionsAlgorithmStandalone::
301
0
    ~GDALVectorExplodeCollectionsAlgorithmStandalone() = default;
302
303
//! @endcond