Coverage Report

Created: 2026-10-28 07:09

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
        if (PassesFilters(poSrcFeature.get()))
206
0
            apoOutFeatures.push_back(std::move(poSrcFeature));
207
208
0
        return true;
209
0
    }
210
211
    bool TestCapability(const char *pszCap) const override
212
0
    {
213
0
        if (EQUAL(pszCap, OLCStringsAsUTF8) ||
214
0
            EQUAL(pszCap, OLCCurveGeometries) || EQUAL(pszCap, OLCZGeometries))
215
0
            return m_srcLayer.TestCapability(pszCap);
216
0
        return false;
217
0
    }
218
219
  private:
220
    const bool m_bOverrideCrs;
221
    const bool m_unsetFID;
222
    const OGRFeatureDefnRefCountedPtr m_poFeatureDefn;
223
    OGRSpatialReferenceRefCountedPtr m_poSRS{};
224
225
    CPL_DISALLOW_COPY_ASSIGN(GDALVectorEditAlgorithmLayer)
226
};
227
228
/************************************************************************/
229
/*                     GDALVectorEditOutputDataset                      */
230
/************************************************************************/
231
232
class GDALVectorEditOutputDataset final : public GDALVectorPipelineOutputDataset
233
{
234
  public:
235
    explicit GDALVectorEditOutputDataset(GDALDataset &oSrcDS)
236
0
        : GDALVectorPipelineOutputDataset(oSrcDS)
237
0
    {
238
0
    }
239
240
    CSLConstList GetMetadata(const char *pszDomain) override;
241
242
    const char *GetMetadataItem(const char *pszName,
243
                                const char *pszDomain) override
244
0
    {
245
0
        if (!pszDomain || pszDomain[0] == 0)
246
0
            return GDALDataset::GetMetadataItem(pszName, pszDomain);
247
0
        return m_srcDS.GetMetadataItem(pszName, pszDomain);
248
0
    }
249
};
250
251
CSLConstList GDALVectorEditOutputDataset::GetMetadata(const char *pszDomain)
252
0
{
253
0
    if (!pszDomain || pszDomain[0] == 0)
254
0
        return GDALDataset::GetMetadata(pszDomain);
255
0
    return m_srcDS.GetMetadata(pszDomain);
256
0
}
257
258
}  // namespace
259
260
/************************************************************************/
261
/*                  GDALVectorEditAlgorithm::RunStep()                  */
262
/************************************************************************/
263
264
bool GDALVectorEditAlgorithm::RunStep(GDALPipelineStepRunContext &)
265
0
{
266
0
    auto poSrcDS = m_inputDataset[0].GetDatasetRef();
267
0
    CPLAssert(poSrcDS);
268
269
0
    CPLAssert(m_outputDataset.GetName().empty());
270
0
    CPLAssert(!m_outputDataset.GetDatasetRef());
271
272
0
    const int nLayerCount = poSrcDS->GetLayerCount();
273
274
0
    bool bChangeGeomType = false;
275
0
    OGRwkbGeometryType eType = wkbUnknown;
276
0
    if (!m_geometryType.empty())
277
0
    {
278
0
        eType = OGRFromOGCGeomType(m_geometryType.c_str());
279
0
        bChangeGeomType = true;
280
0
    }
281
282
0
    auto outDS = std::make_unique<GDALVectorEditOutputDataset>(*poSrcDS);
283
284
0
    outDS->SetMetadata(poSrcDS->GetMetadata());
285
286
0
    const CPLStringList aosMD(m_metadata);
287
0
    for (const auto &[key, value] : cpl::IterateNameValue(aosMD))
288
0
    {
289
0
        if (outDS->SetMetadataItem(key, value) != CE_None)
290
0
        {
291
0
            ReportError(CE_Failure, CPLE_AppDefined,
292
0
                        "SetMetadataItem('%s', '%s') failed", key, value);
293
0
            return false;
294
0
        }
295
0
    }
296
297
0
    for (const std::string &key : m_unsetMetadata)
298
0
    {
299
0
        if (outDS->SetMetadataItem(key.c_str(), nullptr) != CE_None)
300
0
        {
301
0
            ReportError(CE_Failure, CPLE_AppDefined,
302
0
                        "SetMetadataItem('%s', NULL) failed", key.c_str());
303
0
            return false;
304
0
        }
305
0
    }
306
307
0
    bool ret = true;
308
0
    for (int i = 0; ret && i < nLayerCount; ++i)
309
0
    {
310
0
        auto poSrcLayer = poSrcDS->GetLayer(i);
311
0
        ret = (poSrcLayer != nullptr);
312
0
        if (ret)
313
0
        {
314
            // Capture errors from VRT layers such as WFS
315
            // (see issue GH # https://github.com/OSGeo/gdal/issues/14826)
316
0
            const auto errorCount{CPLGetErrorCounter()};
317
            // Force layer definition to be read to trigger errors
318
0
            poSrcLayer->GetLayerDefn();
319
0
            if (CPLGetErrorCounter() != errorCount)
320
0
            {
321
0
                ret = false;
322
0
            }
323
0
            else
324
0
            {
325
0
                outDS->AddLayer(*poSrcLayer,
326
0
                                std::make_unique<GDALVectorEditAlgorithmLayer>(
327
0
                                    *poSrcLayer, m_activeLayer,
328
0
                                    m_outputLayerName, bChangeGeomType, eType,
329
0
                                    m_overrideCrs, m_layerMetadata,
330
0
                                    m_unsetLayerMetadata, m_unsetFID));
331
0
            }
332
0
        }
333
0
    }
334
335
0
    if (ret)
336
0
        m_outputDataset.Set(std::move(outDS));
337
338
0
    return ret;
339
0
}
340
341
0
GDALVectorEditAlgorithmStandalone::~GDALVectorEditAlgorithmStandalone() =
342
    default;
343
344
//! @endcond