Coverage Report

Created: 2026-09-14 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
    const auto poInputSRS = poInputLayer->GetSpatialRef();
213
    const auto poMethodSRS = poMethodLayer->GetSpatialRef();
214
    if (poInputSRS && !poMethodSRS)
215
    {
216
        ReportError(
217
            CE_Warning, CPLE_AppDefined,
218
            "Input layer has a CRS, but method layer has none. Assuming "
219
            "geometries of method layer to be expressed in input layer CRS.");
220
    }
221
    else if (!poInputSRS && poMethodSRS)
222
    {
223
        ReportError(
224
            CE_Warning, CPLE_AppDefined,
225
            "Method layer has a CRS, but input layer has none. Assuming "
226
            "geometries of input layer to be expressed in method layer CRS.");
227
    }
228
    else if (poInputSRS && poMethodSRS && !poInputSRS->IsSame(poMethodSRS))
229
    {
230
        ReportError(
231
            CE_Warning, CPLE_AppDefined,
232
            "Input and method layer have non-equivalent CRS. No on-the-fly "
233
            "reprojection is performed, and thus results may be incorrect.");
234
    }
235
236
    if (!poDstLayer)
237
    {
238
        const CPLStringList aosLayerCreationOptions(
239
            poWriteStep->GetLayerCreationOptions());
240
241
        const OGRwkbGeometryType eType =
242
            !m_geometryType.empty() ? OGRFromOGCGeomType(m_geometryType.c_str())
243
                                    : poInputLayer->GetGeomType();
244
        poDstLayer = poDstDS->CreateLayer(outputLayerName.c_str(),
245
                                          poInputLayer->GetSpatialRef(), eType,
246
                                          aosLayerCreationOptions.List());
247
        if (!poDstLayer)
248
            return false;
249
    }
250
251
    CPLStringList aosOptions;
252
253
    if (m_inputFields.empty() && !m_noInputFields)
254
        m_allInputFields = true;
255
256
    if (m_methodFields.empty() && !m_noMethodFields && !m_allMethodFields)
257
    {
258
        if (m_operation == "update" || m_operation == "clip" ||
259
            m_operation == "erase")
260
            m_noMethodFields = true;
261
        else
262
            m_allMethodFields = true;
263
    }
264
265
    if (m_noInputFields && m_noMethodFields)
266
    {
267
        aosOptions.SetNameValue("ADD_INPUT_FIELDS", "NO");
268
        aosOptions.SetNameValue("ADD_METHOD_FIELDS", "NO");
269
    }
270
    else
271
    {
272
        // Copy fields from input or method layer to output layer
273
        const auto CopyFields =
274
            [poDstLayer](OGRLayer *poSrcLayer, const std::string &prefix,
275
                         const std::vector<std::string> &srcFields)
276
        {
277
            const auto contains =
278
                [](const std::vector<std::string> &v, const std::string &s)
279
            { return std::find(v.begin(), v.end(), s) != v.end(); };
280
281
            const auto poOutFDefn = poDstLayer->GetLayerDefn();
282
            const auto poFDefn = poSrcLayer->GetLayerDefn();
283
            const int nCount = poFDefn->GetFieldCount();
284
            for (int i = 0; i < nCount; ++i)
285
            {
286
                const auto poSrcFieldDefn = poFDefn->GetFieldDefn(i);
287
                const char *pszName = poSrcFieldDefn->GetNameRef();
288
                if (srcFields.empty() || contains(srcFields, pszName))
289
                {
290
                    OGRFieldDefn oField(*poSrcFieldDefn);
291
                    const std::string outName = prefix + pszName;
292
                    whileUnsealing(&oField)->SetName(outName.c_str());
293
                    if (poOutFDefn->GetFieldIndex(outName.c_str()) < 0 &&
294
                        poDstLayer->CreateField(&oField) != OGRERR_NONE)
295
                    {
296
                        return false;
297
                    }
298
                }
299
            }
300
            for (const auto &name : srcFields)
301
            {
302
                if (poFDefn->GetFieldIndex(name.c_str()) < 0)
303
                {
304
                    CPLError(CE_Warning, CPLE_AppDefined, "Unknown field '%s'",
305
                             name.c_str());
306
                }
307
            }
308
            return true;
309
        };
310
311
        if (!m_noInputFields)
312
        {
313
            if (!GetArg("input-prefix")->IsExplicitlySet() &&
314
                m_inputPrefix.empty() && !m_noMethodFields)
315
            {
316
                m_inputPrefix = "input_";
317
            }
318
            if (!m_inputPrefix.empty())
319
            {
320
                aosOptions.SetNameValue("INPUT_PREFIX", m_inputPrefix.c_str());
321
            }
322
            if (!CopyFields(poInputLayer, m_inputPrefix, m_inputFields))
323
                return false;
324
        }
325
326
        if (!m_noMethodFields)
327
        {
328
            if (!GetArg("method-prefix")->IsExplicitlySet() &&
329
                m_methodPrefix.empty() && !m_noInputFields)
330
            {
331
                m_methodPrefix = "method_";
332
            }
333
            if (!m_methodPrefix.empty())
334
            {
335
                aosOptions.SetNameValue("METHOD_PREFIX",
336
                                        m_methodPrefix.c_str());
337
            }
338
            if (!CopyFields(poMethodLayer, m_methodPrefix, m_methodFields))
339
                return false;
340
        }
341
    }
342
343
    aosOptions.SetNameValue(
344
        "OUTPUT_GEOMETRY_TYPE",
345
        OGRToOGCGeomType(poDstLayer->GetGeomType(), false, true));
346
347
    const std::map<std::string, decltype(&OGRLayer::Union)>
348
        mapOperationToMethod = {
349
            {"union", &OGRLayer::Union},
350
            {"intersection", &OGRLayer::Intersection},
351
            {"sym-difference", &OGRLayer::SymDifference},
352
            {"identity", &OGRLayer::Identity},
353
            {"update", &OGRLayer::Update},
354
            {"clip", &OGRLayer::Clip},
355
            {"erase", &OGRLayer::Erase},
356
        };
357
358
    const auto oIter = mapOperationToMethod.find(m_operation);
359
    CPLAssert(oIter != mapOperationToMethod.end());
360
    const auto pFunc = oIter->second;
361
    bool bOK = (poInputLayer->*pFunc)(poMethodLayer, poDstLayer,
362
                                      aosOptions.List(), ctxt.m_pfnProgress,
363
                                      ctxt.m_pProgressData) == OGRERR_NONE;
364
    if (bOK && poNewRetDS)
365
    {
366
        if (bTemporaryFile)
367
        {
368
            bOK = poNewRetDS->FlushCache() == CE_None;
369
#if !defined(__APPLE__)
370
            // For some unknown reason, unlinking the file on MacOSX
371
            // leads to later "disk I/O error". See https://github.com/OSGeo/gdal/issues/13794
372
            VSIUnlink(poNewRetDS->GetDescription());
373
#endif
374
        }
375
376
        m_outputDataset.Set(std::move(poNewRetDS));
377
    }
378
379
    return bOK;
380
#else
381
0
    (void)ctxt;
382
0
    ReportError(CE_Failure, CPLE_NotSupported,
383
0
                "This algorithm is only supported for builds against GEOS");
384
0
    return false;
385
0
#endif
386
0
}
387
388
GDALVectorLayerAlgebraAlgorithmStandalone::
389
0
    ~GDALVectorLayerAlgebraAlgorithmStandalone() = default;
390
391
//! @endcond