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_layer_algebra.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  gdal "vector layer-algebra" subcommand
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_layer_algebra.h"
14
#include "gdalalg_vector_write.h"
15
16
#include "cpl_conv.h"
17
#include "gdal_priv.h"
18
#include "gdal_utils.h"
19
#include "ogr_api.h"
20
#include "ogrsf_frmts.h"
21
22
#include <algorithm>
23
24
//! @cond Doxygen_Suppress
25
26
#ifndef _
27
0
#define _(x) (x)
28
#endif
29
30
/************************************************************************/
31
/*                  GDALVectorLayerAlgebraAlgorithm()                   */
32
/************************************************************************/
33
34
GDALVectorLayerAlgebraAlgorithm::GDALVectorLayerAlgebraAlgorithm(
35
    bool standaloneStep)
36
0
    : GDALVectorPipelineStepAlgorithm(
37
0
          NAME, DESCRIPTION, HELP_URL,
38
0
          ConstructorOptions()
39
0
              .SetStandaloneStep(standaloneStep)
40
0
              .SetInputDatasetMaxCount(1)
41
0
              .SetAddInputLayerNameArgument(false)
42
0
              .SetAddDefaultArguments(false)
43
0
              .SetAddUpsertArgument(false)
44
0
              .SetAddSkipErrorsArgument(false)
45
0
              .SetOutputLayerNameAvailableInPipelineStep(true)
46
0
              .SetOutputFormatCreateCapability(GDAL_DCAP_CREATE))
47
0
{
48
0
    if (standaloneStep)
49
0
    {
50
0
        AddProgressArg();
51
0
    }
52
53
0
    auto &opArg =
54
0
        AddArg("operation", 0, _("Operation to perform"), &m_operation)
55
0
            .SetChoices("union", "intersection", "sym-difference", "identity",
56
0
                        "update", "clip", "erase")
57
0
            .SetRequired();
58
0
    if (standaloneStep)
59
0
        opArg.SetPositional();
60
61
0
    if (standaloneStep)
62
0
    {
63
0
        AddVectorInputArgs(false);
64
0
    }
65
0
    else
66
0
    {
67
0
        AddVectorHiddenInputDatasetArg();
68
0
    }
69
70
0
    {
71
0
        auto &arg = AddArg("method", 0, _("Method vector dataset"),
72
0
                           &m_methodDataset, GDAL_OF_VECTOR)
73
0
                        .SetRequired();
74
0
        if (standaloneStep)
75
0
            arg.SetPositional();
76
77
0
        SetAutoCompleteFunctionForFilename(arg, GDAL_OF_VECTOR);
78
0
    }
79
80
0
    if (standaloneStep)
81
0
    {
82
0
        AddVectorOutputArgs(false, false);
83
0
    }
84
0
    else
85
0
    {
86
0
        AddOutputLayerNameArg(/* hiddenForCLI = */ false,
87
0
                              /* shortNameOutputLayerAllowed = */ false);
88
0
    }
89
90
0
    AddArg(GDAL_ARG_NAME_INPUT_LAYER, 0, _("Input layer name"),
91
0
           &m_inputLayerName);
92
93
0
    AddArg("method-layer", 0, _("Method layer name"), &m_methodLayerName);
94
95
0
    AddGeometryTypeArg(&m_geometryType);
96
97
0
    AddArg("input-prefix", 0,
98
0
           _("Prefix for fields corresponding to input layer"), &m_inputPrefix)
99
0
        .SetCategory(GAAC_ADVANCED);
100
0
    AddArg("input-field", 0, _("Input field(s) to add to output layer"),
101
0
           &m_inputFields)
102
0
        .SetCategory(GAAC_ADVANCED)
103
0
        .SetMutualExclusionGroup("input-field");
104
0
    AddArg("no-input-field", 0, _("Do not add any input field to output layer"),
105
0
           &m_noInputFields)
106
0
        .SetCategory(GAAC_ADVANCED)
107
0
        .SetMutualExclusionGroup("input-field");
108
0
    AddArg("all-input-field", 0, _("Add all input fields to output layer"),
109
0
           &m_allInputFields)
110
0
        .SetCategory(GAAC_ADVANCED)
111
0
        .SetMutualExclusionGroup("input-field");
112
113
0
    AddArg("method-prefix", 0,
114
0
           _("Prefix for fields corresponding to method layer"),
115
0
           &m_methodPrefix)
116
0
        .SetCategory(GAAC_ADVANCED);
117
0
    AddArg("method-field", 0, _("Method field(s) to add to output layer"),
118
0
           &m_methodFields)
119
0
        .SetCategory(GAAC_ADVANCED)
120
0
        .SetMutualExclusionGroup("method-field");
121
0
    AddArg("no-method-field", 0,
122
0
           _("Do not add any method field to output layer"), &m_noMethodFields)
123
0
        .SetCategory(GAAC_ADVANCED)
124
0
        .SetMutualExclusionGroup("method-field");
125
0
    AddArg("all-method-field", 0, _("Add all method fields to output layer"),
126
0
           &m_allMethodFields)
127
0
        .SetCategory(GAAC_ADVANCED)
128
0
        .SetMutualExclusionGroup("method-field");
129
0
}
130
131
/************************************************************************/
132
/*         GDALVectorLayerAlgebraAlgorithm::CanHandleNextStep()         */
133
/************************************************************************/
134
135
bool GDALVectorLayerAlgebraAlgorithm::CanHandleNextStep(
136
    GDALPipelineStepAlgorithm *poNextStep) const
137
0
{
138
0
    return poNextStep->GetName() == GDALVectorWriteAlgorithm::NAME &&
139
0
           poNextStep->GetOutputFormat() != "stream";
140
0
}
141
142
/************************************************************************/
143
/*               GDALRasterPolygonizeAlgorithm::RunImpl()               */
144
/************************************************************************/
145
146
bool GDALVectorLayerAlgebraAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
147
                                              void *pProgressData)
148
0
{
149
0
    GDALPipelineStepRunContext stepCtxt;
150
0
    stepCtxt.m_pfnProgress = pfnProgress;
151
0
    stepCtxt.m_pProgressData = pProgressData;
152
0
    return RunPreStepPipelineValidations() && RunStep(stepCtxt);
153
0
}
154
155
/************************************************************************/
156
/*              GDALVectorLayerAlgebraAlgorithm::RunImpl()              */
157
/************************************************************************/
158
159
bool GDALVectorLayerAlgebraAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
160
0
{
161
#ifdef HAVE_GEOS
162
    auto poSrcDS = m_inputDataset[0].GetDatasetRef();
163
    CPLAssert(poSrcDS);
164
    auto poMethodDS = m_methodDataset.GetDatasetRef();
165
    CPLAssert(poMethodDS);
166
167
    if (poSrcDS == poMethodDS)
168
    {
169
        ReportError(CE_Failure, CPLE_NotSupported,
170
                    "Input and method datasets must be different");
171
        return false;
172
    }
173
174
    auto poWriteStep = ctxt.m_poNextUsableStep ? ctxt.m_poNextUsableStep : this;
175
176
    GDALDataset *poDstDS = nullptr;
177
    bool bTemporaryFile = false;
178
    std::unique_ptr<GDALDataset> poNewRetDS;
179
    std::string outputLayerName;
180
    OGRLayer *poDstLayer = nullptr;
181
    if (!CreateDatasetSingleOutputLayerIfNeeded(ctxt, "output", poDstDS,
182
                                                bTemporaryFile, poNewRetDS,
183
                                                outputLayerName, poDstLayer))
184
    {
185
        return false;
186
    }
187
188
    OGRLayer *poInputLayer;
189
    if (m_inputLayerName.empty() && poSrcDS->GetLayerCount() == 1)
190
        poInputLayer = poSrcDS->GetLayer(0);
191
    else
192
        poInputLayer = poSrcDS->GetLayerByName(m_inputLayerName.c_str());
193
    if (!poInputLayer)
194
    {
195
        ReportError(CE_Failure, CPLE_AppDefined, "Cannot get input layer '%s'",
196
                    m_inputLayerName.c_str());
197
        return false;
198
    }
199
200
    OGRLayer *poMethodLayer;
201
    if (m_methodLayerName.empty() && poMethodDS->GetLayerCount() == 1)
202
        poMethodLayer = poMethodDS->GetLayer(0);
203
    else
204
        poMethodLayer = poMethodDS->GetLayerByName(m_methodLayerName.c_str());
205
    if (!poMethodLayer)
206
    {
207
        ReportError(CE_Failure, CPLE_AppDefined, "Cannot get method layer '%s'",
208
                    m_methodLayerName.c_str());
209
        return false;
210
    }
211
212
    if (!poDstLayer)
213
    {
214
        const CPLStringList aosLayerCreationOptions(
215
            poWriteStep->GetLayerCreationOptions());
216
217
        const OGRwkbGeometryType eType =
218
            !m_geometryType.empty() ? OGRFromOGCGeomType(m_geometryType.c_str())
219
                                    : poInputLayer->GetGeomType();
220
        poDstLayer = poDstDS->CreateLayer(outputLayerName.c_str(),
221
                                          poInputLayer->GetSpatialRef(), eType,
222
                                          aosLayerCreationOptions.List());
223
        if (!poDstLayer)
224
            return false;
225
    }
226
227
    CPLStringList aosOptions;
228
229
    if (m_inputFields.empty() && !m_noInputFields)
230
        m_allInputFields = true;
231
232
    if (m_methodFields.empty() && !m_noMethodFields && !m_allMethodFields)
233
    {
234
        if (m_operation == "update" || m_operation == "clip" ||
235
            m_operation == "erase")
236
            m_noMethodFields = true;
237
        else
238
            m_allMethodFields = true;
239
    }
240
241
    if (m_noInputFields && m_noMethodFields)
242
    {
243
        aosOptions.SetNameValue("ADD_INPUT_FIELDS", "NO");
244
        aosOptions.SetNameValue("ADD_METHOD_FIELDS", "NO");
245
    }
246
    else
247
    {
248
        // Copy fields from input or method layer to output layer
249
        const auto CopyFields =
250
            [poDstLayer](OGRLayer *poSrcLayer, const std::string &prefix,
251
                         const std::vector<std::string> &srcFields)
252
        {
253
            const auto contains =
254
                [](const std::vector<std::string> &v, const std::string &s)
255
            { return std::find(v.begin(), v.end(), s) != v.end(); };
256
257
            const auto poOutFDefn = poDstLayer->GetLayerDefn();
258
            const auto poFDefn = poSrcLayer->GetLayerDefn();
259
            const int nCount = poFDefn->GetFieldCount();
260
            for (int i = 0; i < nCount; ++i)
261
            {
262
                const auto poSrcFieldDefn = poFDefn->GetFieldDefn(i);
263
                const char *pszName = poSrcFieldDefn->GetNameRef();
264
                if (srcFields.empty() || contains(srcFields, pszName))
265
                {
266
                    OGRFieldDefn oField(*poSrcFieldDefn);
267
                    const std::string outName = prefix + pszName;
268
                    whileUnsealing(&oField)->SetName(outName.c_str());
269
                    if (poOutFDefn->GetFieldIndex(outName.c_str()) < 0 &&
270
                        poDstLayer->CreateField(&oField) != OGRERR_NONE)
271
                    {
272
                        return false;
273
                    }
274
                }
275
            }
276
            return true;
277
        };
278
279
        if (!m_noInputFields)
280
        {
281
            if (!GetArg("input-prefix")->IsExplicitlySet() &&
282
                m_inputPrefix.empty() && !m_noMethodFields)
283
            {
284
                m_inputPrefix = "input_";
285
            }
286
            if (!m_inputPrefix.empty())
287
            {
288
                aosOptions.SetNameValue("INPUT_PREFIX", m_inputPrefix.c_str());
289
            }
290
            if (!CopyFields(poInputLayer, m_inputPrefix, m_inputFields))
291
                return false;
292
        }
293
294
        if (!m_noMethodFields)
295
        {
296
            if (!GetArg("method-prefix")->IsExplicitlySet() &&
297
                m_methodPrefix.empty() && !m_noInputFields)
298
            {
299
                m_methodPrefix = "method_";
300
            }
301
            if (!m_methodPrefix.empty())
302
            {
303
                aosOptions.SetNameValue("METHOD_PREFIX",
304
                                        m_methodPrefix.c_str());
305
            }
306
            if (!CopyFields(poMethodLayer, m_methodPrefix, m_methodFields))
307
                return false;
308
        }
309
    }
310
311
    if (OGR_GT_IsSubClassOf(poDstLayer->GetGeomType(), wkbGeometryCollection))
312
    {
313
        aosOptions.SetNameValue("PROMOTE_TO_MULTI", "YES");
314
    }
315
316
    const std::map<std::string, decltype(&OGRLayer::Union)>
317
        mapOperationToMethod = {
318
            {"union", &OGRLayer::Union},
319
            {"intersection", &OGRLayer::Intersection},
320
            {"sym-difference", &OGRLayer::SymDifference},
321
            {"identity", &OGRLayer::Identity},
322
            {"update", &OGRLayer::Update},
323
            {"clip", &OGRLayer::Clip},
324
            {"erase", &OGRLayer::Erase},
325
        };
326
327
    const auto oIter = mapOperationToMethod.find(m_operation);
328
    CPLAssert(oIter != mapOperationToMethod.end());
329
    const auto pFunc = oIter->second;
330
    bool bOK = (poInputLayer->*pFunc)(poMethodLayer, poDstLayer,
331
                                      aosOptions.List(), ctxt.m_pfnProgress,
332
                                      ctxt.m_pProgressData) == OGRERR_NONE;
333
    if (bOK && poNewRetDS)
334
    {
335
        if (bTemporaryFile)
336
        {
337
            bOK = poNewRetDS->FlushCache() == CE_None;
338
#if !defined(__APPLE__)
339
            // For some unknown reason, unlinking the file on MacOSX
340
            // leads to later "disk I/O error". See https://github.com/OSGeo/gdal/issues/13794
341
            VSIUnlink(poNewRetDS->GetDescription());
342
#endif
343
        }
344
345
        m_outputDataset.Set(std::move(poNewRetDS));
346
    }
347
348
    return bOK;
349
#else
350
0
    (void)ctxt;
351
0
    ReportError(CE_Failure, CPLE_NotSupported,
352
0
                "This algorithm is only supported for builds against GEOS");
353
0
    return false;
354
0
#endif
355
0
}
356
357
GDALVectorLayerAlgebraAlgorithmStandalone::
358
0
    ~GDALVectorLayerAlgebraAlgorithmStandalone() = default;
359
360
//! @endcond