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_edit.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  "edit" step of "vector pipeline"
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_edit.h"
14
15
#include "gdal_priv.h"
16
#include "gdal_utils.h"
17
18
//! @cond Doxygen_Suppress
19
20
#ifndef _
21
0
#define _(x) (x)
22
#endif
23
24
/************************************************************************/
25
/*          GDALVectorEditAlgorithm::GDALVectorEditAlgorithm()          */
26
/************************************************************************/
27
28
GDALVectorEditAlgorithm::GDALVectorEditAlgorithm(bool standaloneStep)
29
0
    : GDALVectorPipelineStepAlgorithm(
30
0
          NAME, DESCRIPTION, HELP_URL,
31
0
          ConstructorOptions()
32
0
              .SetStandaloneStep(standaloneStep)
33
0
              .SetOutputLayerNameAvailableInPipelineStep(true))
34
0
{
35
0
    AddActiveLayerArg(&m_activeLayer);
36
0
    if (!standaloneStep)
37
0
    {
38
0
        AddOutputLayerNameArg(/* hiddenForCLI = */ false,
39
0
                              /* shortNameOutputLayerAllowed = */ false);
40
0
    }
41
42
0
    AddGeometryTypeArg(&m_geometryType, _("Layer geometry type"));
43
44
0
    AddArg("crs", 0, _("Override CRS (without reprojection)"), &m_overrideCrs)
45
0
        .AddHiddenAlias("a_srs")
46
0
        .SetIsCRSArg(/*noneAllowed=*/true);
47
48
0
    {
49
0
        auto &arg = AddArg("metadata", 0, _("Add/update dataset metadata item"),
50
0
                           &m_metadata)
51
0
                        .SetMetaVar("<KEY>=<VALUE>")
52
0
                        .SetPackedValuesAllowed(false);
53
0
        arg.AddValidationAction([this, &arg]()
54
0
                                { return ParseAndValidateKeyValue(arg); });
55
0
        arg.AddHiddenAlias("mo");
56
0
    }
57
58
0
    AddArg("unset-metadata", 0, _("Remove dataset metadata item"),
59
0
           &m_unsetMetadata)
60
0
        .SetMetaVar("<KEY>");
61
62
0
    {
63
0
        auto &arg =
64
0
            AddArg("layer-metadata", 0, _("Add/update layer metadata item"),
65
0
                   &m_layerMetadata)
66
0
                .SetMetaVar("<KEY>=<VALUE>")
67
0
                .SetPackedValuesAllowed(false);
68
0
        arg.AddValidationAction([this, &arg]()
69
0
                                { return ParseAndValidateKeyValue(arg); });
70
0
    }
71
72
0
    AddArg("unset-layer-metadata", 0, _("Remove layer metadata item"),
73
0
           &m_unsetLayerMetadata)
74
0
        .SetMetaVar("<KEY>");
75
76
0
    AddArg("unset-fid", 0,
77
0
           _("Unset the identifier of each feature and the FID column name"),
78
0
           &m_unsetFID);
79
80
0
    AddValidationAction(
81
0
        [this]()
82
0
        {
83
0
            if (!m_outputLayerName.empty() && m_activeLayer.empty() &&
84
0
                m_inputDataset.size() == 1)
85
0
            {
86
0
                auto poSrcDS = m_inputDataset[0].GetDatasetRef();
87
0
                if (poSrcDS && poSrcDS->GetLayerCount() > 1)
88
0
                {
89
0
                    ReportError(CE_Failure, CPLE_IllegalArg,
90
0
                                "Argument 'output-layer' cannot be used when "
91
0
                                "the input dataset has multiple layers, unless "
92
0
                                "argument 'active-layer' is specified");
93
0
                    return false;
94
0
                }
95
0
            }
96
0
            return true;
97
0
        });
98
0
}
99
100
namespace
101
{
102
103
/************************************************************************/
104
/*                     GDALVectorEditAlgorithmLayer                     */
105
/************************************************************************/
106
107
class GDALVectorEditAlgorithmLayer final : public GDALVectorPipelineOutputLayer
108
{
109
  public:
110
    GDALVectorEditAlgorithmLayer(
111
        OGRLayer &oSrcLayer, const std::string &activeLayer,
112
        const std::string &outputLayerName, bool bChangeGeomType,
113
        OGRwkbGeometryType eType, const std::string &overrideCrs,
114
        const std::vector<std::string> &layerMetadata,
115
        const std::vector<std::string> &unsetLayerMetadata, bool unsetFID)
116
0
        : GDALVectorPipelineOutputLayer(oSrcLayer),
117
0
          m_bOverrideCrs(!overrideCrs.empty()), m_unsetFID(unsetFID),
118
0
          m_poFeatureDefn(oSrcLayer.GetLayerDefn()->Clone())
119
0
    {
120
0
        SetMetadata(oSrcLayer.GetMetadata());
121
122
0
        if (activeLayer.empty() || activeLayer == oSrcLayer.GetDescription())
123
0
        {
124
0
            if (!outputLayerName.empty())
125
0
            {
126
0
                m_poFeatureDefn->SetName(outputLayerName.c_str());
127
0
            }
128
129
0
            const CPLStringList aosMD(layerMetadata);
130
0
            for (const auto &[key, value] : cpl::IterateNameValue(aosMD))
131
0
            {
132
0
                if (SetMetadataItem(key, value) != CE_None)
133
0
                {
134
0
                    CPLError(CE_Warning, CPLE_AppDefined,
135
0
                             "SetMetadataItem('%s', '%s') failed", key, value);
136
0
                }
137
0
            }
138
139
0
            for (const std::string &key : unsetLayerMetadata)
140
0
            {
141
0
                if (SetMetadataItem(key.c_str(), nullptr) != CE_None)
142
0
                {
143
0
                    CPLError(CE_Warning, CPLE_AppDefined,
144
0
                             "SetMetadataItem('%s', NULL) failed", key.c_str());
145
0
                }
146
0
            }
147
148
0
            if (bChangeGeomType)
149
0
            {
150
0
                for (auto *poGeomFieldDefns : m_poFeatureDefn->GetGeomFields())
151
0
                {
152
0
                    poGeomFieldDefns->SetType(eType);
153
0
                }
154
0
            }
155
156
0
            if (!overrideCrs.empty())
157
0
            {
158
0
                if (!EQUAL(overrideCrs.c_str(), "null") &&
159
0
                    !EQUAL(overrideCrs.c_str(), "none"))
160
0
                {
161
0
                    m_poSRS = OGRSpatialReferenceRefCountedPtr::makeInstance();
162
0
                    m_poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
163
                    // already checked by GDALAlgorithmArg framework
164
0
                    CPL_IGNORE_RET_VAL(
165
0
                        m_poSRS->SetFromUserInput(overrideCrs.c_str()));
166
0
                }
167
0
                for (auto *poGeomFieldDefns : m_poFeatureDefn->GetGeomFields())
168
0
                {
169
0
                    poGeomFieldDefns->SetSpatialRef(m_poSRS.get());
170
0
                }
171
0
            }
172
0
        }
173
174
0
        SetDescription(m_poFeatureDefn->GetName());
175
0
    }
176
177
    const char *GetFIDColumn() const override
178
0
    {
179
0
        if (m_unsetFID)
180
0
            return "";
181
0
        return m_srcLayer.GetFIDColumn();
182
0
    }
183
184
    const OGRFeatureDefn *GetLayerDefn() const override
185
0
    {
186
0
        return m_poFeatureDefn.get();
187
0
    }
188
189
    bool TranslateFeature(
190
        std::unique_ptr<OGRFeature> poSrcFeature,
191
        std::vector<std::unique_ptr<OGRFeature>> &apoOutFeatures) override
192
0
    {
193
0
        poSrcFeature->SetFDefnUnsafe(m_poFeatureDefn.get());
194
0
        if (m_bOverrideCrs)
195
0
        {
196
0
            for (int i = 0; i < m_poFeatureDefn->GetGeomFieldCount(); ++i)
197
0
            {
198
0
                auto poGeom = poSrcFeature->GetGeomFieldRef(i);
199
0
                if (poGeom)
200
0
                    poGeom->assignSpatialReference(m_poSRS.get());
201
0
            }
202
0
        }
203
0
        if (m_unsetFID)
204
0
            poSrcFeature->SetFID(OGRNullFID);
205
0
        apoOutFeatures.push_back(std::move(poSrcFeature));
206
207
0
        return true;
208
0
    }
209
210
    int TestCapability(const char *pszCap) const override
211
0
    {
212
0
        if (EQUAL(pszCap, OLCStringsAsUTF8) ||
213
0
            EQUAL(pszCap, OLCCurveGeometries) || EQUAL(pszCap, OLCZGeometries))
214
0
            return m_srcLayer.TestCapability(pszCap);
215
0
        return false;
216
0
    }
217
218
  private:
219
    const bool m_bOverrideCrs;
220
    const bool m_unsetFID;
221
    const OGRFeatureDefnRefCountedPtr m_poFeatureDefn;
222
    OGRSpatialReferenceRefCountedPtr m_poSRS{};
223
224
    CPL_DISALLOW_COPY_ASSIGN(GDALVectorEditAlgorithmLayer)
225
};
226
227
/************************************************************************/
228
/*                     GDALVectorEditOutputDataset                      */
229
/************************************************************************/
230
231
class GDALVectorEditOutputDataset final : public GDALVectorPipelineOutputDataset
232
{
233
  public:
234
    explicit GDALVectorEditOutputDataset(GDALDataset &oSrcDS)
235
0
        : GDALVectorPipelineOutputDataset(oSrcDS)
236
0
    {
237
0
    }
238
239
    CSLConstList GetMetadata(const char *pszDomain) override;
240
241
    const char *GetMetadataItem(const char *pszName,
242
                                const char *pszDomain) override
243
0
    {
244
0
        if (!pszDomain || pszDomain[0] == 0)
245
0
            return GDALDataset::GetMetadataItem(pszName, pszDomain);
246
0
        return m_srcDS.GetMetadataItem(pszName, pszDomain);
247
0
    }
248
};
249
250
CSLConstList GDALVectorEditOutputDataset::GetMetadata(const char *pszDomain)
251
0
{
252
0
    if (!pszDomain || pszDomain[0] == 0)
253
0
        return GDALDataset::GetMetadata(pszDomain);
254
0
    return m_srcDS.GetMetadata(pszDomain);
255
0
}
256
257
}  // namespace
258
259
/************************************************************************/
260
/*                  GDALVectorEditAlgorithm::RunStep()                  */
261
/************************************************************************/
262
263
bool GDALVectorEditAlgorithm::RunStep(GDALPipelineStepRunContext &)
264
0
{
265
0
    auto poSrcDS = m_inputDataset[0].GetDatasetRef();
266
0
    CPLAssert(poSrcDS);
267
268
0
    CPLAssert(m_outputDataset.GetName().empty());
269
0
    CPLAssert(!m_outputDataset.GetDatasetRef());
270
271
0
    const int nLayerCount = poSrcDS->GetLayerCount();
272
273
0
    bool bChangeGeomType = false;
274
0
    OGRwkbGeometryType eType = wkbUnknown;
275
0
    if (!m_geometryType.empty())
276
0
    {
277
0
        eType = OGRFromOGCGeomType(m_geometryType.c_str());
278
0
        bChangeGeomType = true;
279
0
    }
280
281
0
    auto outDS = std::make_unique<GDALVectorEditOutputDataset>(*poSrcDS);
282
283
0
    outDS->SetMetadata(poSrcDS->GetMetadata());
284
285
0
    const CPLStringList aosMD(m_metadata);
286
0
    for (const auto &[key, value] : cpl::IterateNameValue(aosMD))
287
0
    {
288
0
        if (outDS->SetMetadataItem(key, value) != CE_None)
289
0
        {
290
0
            ReportError(CE_Failure, CPLE_AppDefined,
291
0
                        "SetMetadataItem('%s', '%s') failed", key, value);
292
0
            return false;
293
0
        }
294
0
    }
295
296
0
    for (const std::string &key : m_unsetMetadata)
297
0
    {
298
0
        if (outDS->SetMetadataItem(key.c_str(), nullptr) != CE_None)
299
0
        {
300
0
            ReportError(CE_Failure, CPLE_AppDefined,
301
0
                        "SetMetadataItem('%s', NULL) failed", key.c_str());
302
0
            return false;
303
0
        }
304
0
    }
305
306
0
    bool ret = true;
307
0
    for (int i = 0; ret && i < nLayerCount; ++i)
308
0
    {
309
0
        auto poSrcLayer = poSrcDS->GetLayer(i);
310
0
        ret = (poSrcLayer != nullptr);
311
0
        if (ret)
312
0
        {
313
            // Capture errors from VRT layers such as WFS
314
            // (see issue GH # https://github.com/OSGeo/gdal/issues/14826)
315
0
            const auto errorCount{CPLGetErrorCounter()};
316
            // Force layer definition to be read to trigger errors
317
0
            poSrcLayer->GetLayerDefn();
318
0
            if (CPLGetErrorCounter() != errorCount)
319
0
            {
320
0
                ret = false;
321
0
            }
322
0
            else
323
0
            {
324
0
                outDS->AddLayer(*poSrcLayer,
325
0
                                std::make_unique<GDALVectorEditAlgorithmLayer>(
326
0
                                    *poSrcLayer, m_activeLayer,
327
0
                                    m_outputLayerName, bChangeGeomType, eType,
328
0
                                    m_overrideCrs, m_layerMetadata,
329
0
                                    m_unsetLayerMetadata, m_unsetFID));
330
0
            }
331
0
        }
332
0
    }
333
334
0
    if (ret)
335
0
        m_outputDataset.Set(std::move(outDS));
336
337
0
    return ret;
338
0
}
339
340
0
GDALVectorEditAlgorithmStandalone::~GDALVectorEditAlgorithmStandalone() =
341
    default;
342
343
//! @endcond