Coverage Report

Created: 2026-02-14 06:52

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
15
#include "cpl_conv.h"
16
#include "gdal_priv.h"
17
#include "gdal_utils.h"
18
#include "ogr_api.h"
19
#include "ogrsf_frmts.h"
20
21
#include <algorithm>
22
23
//! @cond Doxygen_Suppress
24
25
#ifndef _
26
0
#define _(x) (x)
27
#endif
28
29
/************************************************************************/
30
/*                  GDALVectorLayerAlgebraAlgorithm()                   */
31
/************************************************************************/
32
33
GDALVectorLayerAlgebraAlgorithm::GDALVectorLayerAlgebraAlgorithm()
34
0
    : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
35
0
{
36
0
    AddProgressArg();
37
38
0
    AddArg("operation", 0, _("Operation to perform"), &m_operation)
39
0
        .SetChoices("union", "intersection", "sym-difference", "identity",
40
0
                    "update", "clip", "erase")
41
0
        .SetRequired()
42
0
        .SetPositional();
43
44
0
    AddOutputFormatArg(&m_format).AddMetadataItem(
45
0
        GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_VECTOR, GDAL_DCAP_CREATE});
46
0
    AddOpenOptionsArg(&m_openOptions);
47
0
    AddInputFormatsArg(&m_inputFormats)
48
0
        .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_VECTOR});
49
0
    AddInputDatasetArg(&m_inputDataset, GDAL_OF_VECTOR);
50
51
0
    {
52
0
        auto &arg = AddArg("method", 0, _("Method vector dataset"),
53
0
                           &m_methodDataset, GDAL_OF_VECTOR)
54
0
                        .SetPositional()
55
0
                        .SetRequired();
56
57
0
        SetAutoCompleteFunctionForFilename(arg, GDAL_OF_VECTOR);
58
0
    }
59
0
    AddOutputDatasetArg(&m_outputDataset, GDAL_OF_VECTOR)
60
0
        .SetDatasetInputFlags(GADV_NAME | GADV_OBJECT);
61
0
    AddCreationOptionsArg(&m_creationOptions);
62
0
    AddLayerCreationOptionsArg(&m_layerCreationOptions);
63
0
    AddOverwriteArg(&m_overwrite);
64
0
    AddUpdateArg(&m_update);
65
0
    AddOverwriteLayerArg(&m_overwriteLayer);
66
0
    AddAppendLayerArg(&m_appendLayer);
67
68
0
    AddArg(GDAL_ARG_NAME_INPUT_LAYER, 0, _("Input layer name"),
69
0
           &m_inputLayerName);
70
0
    AddArg("method-layer", 0, _("Method layer name"), &m_methodLayerName);
71
0
    AddOutputLayerNameArg(&m_outputLayerName)
72
0
        .AddHiddenAlias("nln");  // For ogr2ogr nostalgic people
73
74
0
    AddGeometryTypeArg(&m_geometryType);
75
76
0
    AddArg("input-prefix", 0,
77
0
           _("Prefix for fields corresponding to input layer"), &m_inputPrefix)
78
0
        .SetCategory(GAAC_ADVANCED);
79
0
    AddArg("input-field", 0, _("Input field(s) to add to output layer"),
80
0
           &m_inputFields)
81
0
        .SetCategory(GAAC_ADVANCED)
82
0
        .SetMutualExclusionGroup("input-field");
83
0
    AddArg("no-input-field", 0, _("Do not add any input field to output layer"),
84
0
           &m_noInputFields)
85
0
        .SetCategory(GAAC_ADVANCED)
86
0
        .SetMutualExclusionGroup("input-field");
87
0
    AddArg("all-input-field", 0, _("Add all input fields to output layer"),
88
0
           &m_allInputFields)
89
0
        .SetCategory(GAAC_ADVANCED)
90
0
        .SetMutualExclusionGroup("input-field");
91
92
0
    AddArg("method-prefix", 0,
93
0
           _("Prefix for fields corresponding to method layer"),
94
0
           &m_methodPrefix)
95
0
        .SetCategory(GAAC_ADVANCED);
96
0
    AddArg("method-field", 0, _("Method field(s) to add to output layer"),
97
0
           &m_methodFields)
98
0
        .SetCategory(GAAC_ADVANCED)
99
0
        .SetMutualExclusionGroup("method-field");
100
0
    AddArg("no-method-field", 0,
101
0
           _("Do not add any method field to output layer"), &m_noMethodFields)
102
0
        .SetCategory(GAAC_ADVANCED)
103
0
        .SetMutualExclusionGroup("method-field");
104
0
    AddArg("all-method-field", 0, _("Add all method fields to output layer"),
105
0
           &m_allMethodFields)
106
0
        .SetCategory(GAAC_ADVANCED)
107
0
        .SetMutualExclusionGroup("method-field");
108
0
}
109
110
/************************************************************************/
111
/*              GDALVectorLayerAlgebraAlgorithm::RunImpl()              */
112
/************************************************************************/
113
114
bool GDALVectorLayerAlgebraAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
115
                                              void *pProgressData)
116
0
{
117
#ifdef HAVE_GEOS
118
    auto poSrcDS = m_inputDataset.GetDatasetRef();
119
    CPLAssert(poSrcDS);
120
    auto poMethodDS = m_methodDataset.GetDatasetRef();
121
    CPLAssert(poMethodDS);
122
123
    if (poSrcDS == poMethodDS)
124
    {
125
        ReportError(CE_Failure, CPLE_NotSupported,
126
                    "Input and method datasets must be different");
127
        return false;
128
    }
129
130
    auto poDstDS = m_outputDataset.GetDatasetRef();
131
    std::unique_ptr<GDALDataset> poDstDSUniquePtr;
132
    const bool bNewDataset = poDstDS == nullptr;
133
    if (poDstDS == nullptr)
134
    {
135
        if (m_format.empty())
136
        {
137
            const CPLStringList aosFormats(GDALGetOutputDriversForDatasetName(
138
                m_outputDataset.GetName().c_str(), GDAL_OF_VECTOR,
139
                /* bSingleMatch = */ true,
140
                /* bEmitWarning = */ true));
141
            if (aosFormats.size() != 1)
142
            {
143
                ReportError(CE_Failure, CPLE_AppDefined,
144
                            "Cannot guess driver for %s",
145
                            m_outputDataset.GetName().c_str());
146
                return false;
147
            }
148
            m_format = aosFormats[0];
149
        }
150
151
        auto poOutDrv =
152
            GetGDALDriverManager()->GetDriverByName(m_format.c_str());
153
        if (!poOutDrv)
154
        {
155
            // shouldn't happen given checks done in GDALAlgorithm unless
156
            // someone deregister the driver between ParseCommandLineArgs() and
157
            // Run()
158
            ReportError(CE_Failure, CPLE_AppDefined, "Driver %s does not exist",
159
                        m_format.c_str());
160
            return false;
161
        }
162
163
        const CPLStringList aosCreationOptions(m_creationOptions);
164
        poDstDSUniquePtr.reset(
165
            poOutDrv->Create(m_outputDataset.GetName().c_str(), 0, 0, 0,
166
                             GDT_Unknown, aosCreationOptions.List()));
167
        poDstDS = poDstDSUniquePtr.get();
168
        if (!poDstDS)
169
            return false;
170
    }
171
172
    OGRLayer *poDstLayer = nullptr;
173
174
    if (m_outputLayerName.empty())
175
    {
176
        if (bNewDataset)
177
        {
178
            auto poOutDrv = poDstDS->GetDriver();
179
            if (poOutDrv && EQUAL(poOutDrv->GetDescription(), "ESRI Shapefile"))
180
                m_outputLayerName =
181
                    CPLGetBasenameSafe(m_outputDataset.GetName().c_str());
182
            else
183
                m_outputLayerName = "output";
184
        }
185
        else if (m_appendLayer)
186
        {
187
            if (poDstDS->GetLayerCount() == 1)
188
                poDstLayer = poDstDS->GetLayer(0);
189
            else
190
            {
191
                ReportError(CE_Failure, CPLE_AppDefined,
192
                            "--output-layer should be specified");
193
                return false;
194
            }
195
        }
196
        else if (m_overwriteLayer)
197
        {
198
            if (poDstDS->GetLayerCount() == 1)
199
            {
200
                if (poDstDS->DeleteLayer(0) != OGRERR_NONE)
201
                {
202
                    return false;
203
                }
204
            }
205
            else
206
            {
207
                ReportError(CE_Failure, CPLE_AppDefined,
208
                            "--output-layer should be specified");
209
                return false;
210
            }
211
        }
212
        else
213
        {
214
            ReportError(CE_Failure, CPLE_AppDefined,
215
                        "--output-layer should be specified");
216
            return false;
217
        }
218
    }
219
    else if (m_overwriteLayer)
220
    {
221
        const int nLayerIdx = poDstDS->GetLayerIndex(m_outputLayerName.c_str());
222
        if (nLayerIdx < 0)
223
        {
224
            ReportError(CE_Failure, CPLE_AppDefined,
225
                        "Layer '%s' does not exist", m_outputLayerName.c_str());
226
            return false;
227
        }
228
        if (poDstDS->DeleteLayer(nLayerIdx) != OGRERR_NONE)
229
        {
230
            return false;
231
        }
232
    }
233
    else if (m_appendLayer)
234
    {
235
        poDstLayer = poDstDS->GetLayerByName(m_outputLayerName.c_str());
236
        if (!poDstLayer)
237
        {
238
            ReportError(CE_Failure, CPLE_AppDefined,
239
                        "Layer '%s' does not exist", m_outputLayerName.c_str());
240
            return false;
241
        }
242
    }
243
244
    if (!bNewDataset && m_update && !m_appendLayer && !m_overwriteLayer)
245
    {
246
        poDstLayer = poDstDS->GetLayerByName(m_outputLayerName.c_str());
247
        if (poDstLayer)
248
        {
249
            ReportError(CE_Failure, CPLE_AppDefined,
250
                        "Output layer '%s' already exists. Specify "
251
                        "--%s, --%s, --%s or "
252
                        "--%s + --output-layer with a different name",
253
                        m_outputLayerName.c_str(), GDAL_ARG_NAME_OVERWRITE,
254
                        GDAL_ARG_NAME_OVERWRITE_LAYER, GDAL_ARG_NAME_APPEND,
255
                        GDAL_ARG_NAME_UPDATE);
256
            return false;
257
        }
258
    }
259
260
    OGRLayer *poInputLayer;
261
    if (m_inputLayerName.empty() && poSrcDS->GetLayerCount() == 1)
262
        poInputLayer = poSrcDS->GetLayer(0);
263
    else
264
        poInputLayer = poSrcDS->GetLayerByName(m_inputLayerName.c_str());
265
    if (!poInputLayer)
266
    {
267
        ReportError(CE_Failure, CPLE_AppDefined, "Cannot get input layer '%s'",
268
                    m_inputLayerName.c_str());
269
        return false;
270
    }
271
272
    OGRLayer *poMethodLayer;
273
    if (m_methodLayerName.empty() && poMethodDS->GetLayerCount() == 1)
274
        poMethodLayer = poMethodDS->GetLayer(0);
275
    else
276
        poMethodLayer = poMethodDS->GetLayerByName(m_methodLayerName.c_str());
277
    if (!poMethodLayer)
278
    {
279
        ReportError(CE_Failure, CPLE_AppDefined, "Cannot get method layer '%s'",
280
                    m_methodLayerName.c_str());
281
        return false;
282
    }
283
284
    if (bNewDataset || !m_appendLayer)
285
    {
286
        const CPLStringList aosLayerCreationOptions(m_layerCreationOptions);
287
288
        const OGRwkbGeometryType eType =
289
            !m_geometryType.empty() ? OGRFromOGCGeomType(m_geometryType.c_str())
290
                                    : poInputLayer->GetGeomType();
291
        poDstLayer = poDstDS->CreateLayer(m_outputLayerName.c_str(),
292
                                          poInputLayer->GetSpatialRef(), eType,
293
                                          aosLayerCreationOptions.List());
294
    }
295
    if (!poDstLayer)
296
        return false;
297
298
    CPLStringList aosOptions;
299
300
    if (m_inputFields.empty() && !m_noInputFields)
301
        m_allInputFields = true;
302
303
    if (m_methodFields.empty() && !m_noMethodFields && !m_allMethodFields)
304
    {
305
        if (m_operation == "update" || m_operation == "clip" ||
306
            m_operation == "erase")
307
            m_noMethodFields = true;
308
        else
309
            m_allMethodFields = true;
310
    }
311
312
    if (m_noInputFields && m_noMethodFields)
313
    {
314
        aosOptions.SetNameValue("ADD_INPUT_FIELDS", "NO");
315
        aosOptions.SetNameValue("ADD_METHOD_FIELDS", "NO");
316
    }
317
    else
318
    {
319
        // Copy fields from input or method layer to output layer
320
        const auto CopyFields =
321
            [poDstLayer](OGRLayer *poSrcLayer, const std::string &prefix,
322
                         const std::vector<std::string> &srcFields)
323
        {
324
            const auto contains =
325
                [](const std::vector<std::string> &v, const std::string &s)
326
            { return std::find(v.begin(), v.end(), s) != v.end(); };
327
328
            const auto poOutFDefn = poDstLayer->GetLayerDefn();
329
            const auto poFDefn = poSrcLayer->GetLayerDefn();
330
            const int nCount = poFDefn->GetFieldCount();
331
            for (int i = 0; i < nCount; ++i)
332
            {
333
                const auto poSrcFieldDefn = poFDefn->GetFieldDefn(i);
334
                const char *pszName = poSrcFieldDefn->GetNameRef();
335
                if (srcFields.empty() || contains(srcFields, pszName))
336
                {
337
                    OGRFieldDefn oField(*poSrcFieldDefn);
338
                    const std::string outName = prefix + pszName;
339
                    whileUnsealing(&oField)->SetName(outName.c_str());
340
                    if (poOutFDefn->GetFieldIndex(outName.c_str()) < 0 &&
341
                        poDstLayer->CreateField(&oField) != OGRERR_NONE)
342
                    {
343
                        return false;
344
                    }
345
                }
346
            }
347
            return true;
348
        };
349
350
        if (!m_noInputFields)
351
        {
352
            if (!GetArg("input-prefix")->IsExplicitlySet() &&
353
                m_inputPrefix.empty() && !m_noMethodFields)
354
            {
355
                m_inputPrefix = "input_";
356
            }
357
            if (!m_inputPrefix.empty())
358
            {
359
                aosOptions.SetNameValue("INPUT_PREFIX", m_inputPrefix.c_str());
360
            }
361
            if (!CopyFields(poInputLayer, m_inputPrefix, m_inputFields))
362
                return false;
363
        }
364
365
        if (!m_noMethodFields)
366
        {
367
            if (!GetArg("method-prefix")->IsExplicitlySet() &&
368
                m_methodPrefix.empty() && !m_noInputFields)
369
            {
370
                m_methodPrefix = "method_";
371
            }
372
            if (!m_methodPrefix.empty())
373
            {
374
                aosOptions.SetNameValue("METHOD_PREFIX",
375
                                        m_methodPrefix.c_str());
376
            }
377
            if (!CopyFields(poMethodLayer, m_methodPrefix, m_methodFields))
378
                return false;
379
        }
380
    }
381
382
    if (OGR_GT_IsSubClassOf(poDstLayer->GetGeomType(), wkbGeometryCollection))
383
    {
384
        aosOptions.SetNameValue("PROMOTE_TO_MULTI", "YES");
385
    }
386
387
    const std::map<std::string, decltype(&OGRLayer::Union)>
388
        mapOperationToMethod = {
389
            {"union", &OGRLayer::Union},
390
            {"intersection", &OGRLayer::Intersection},
391
            {"sym-difference", &OGRLayer::SymDifference},
392
            {"identity", &OGRLayer::Identity},
393
            {"update", &OGRLayer::Update},
394
            {"clip", &OGRLayer::Clip},
395
            {"erase", &OGRLayer::Erase},
396
        };
397
398
    const auto oIter = mapOperationToMethod.find(m_operation);
399
    CPLAssert(oIter != mapOperationToMethod.end());
400
    const auto pFunc = oIter->second;
401
    const bool bOK =
402
        (poInputLayer->*pFunc)(poMethodLayer, poDstLayer, aosOptions.List(),
403
                               pfnProgress, pProgressData) == OGRERR_NONE;
404
    if (bOK && !m_outputDataset.GetDatasetRef())
405
    {
406
        m_outputDataset.Set(std::move(poDstDSUniquePtr));
407
    }
408
409
    return bOK;
410
#else
411
0
    (void)pfnProgress;
412
0
    (void)pProgressData;
413
0
    ReportError(CE_Failure, CPLE_NotSupported,
414
0
                "This algorithm is only supported for builds against GEOS");
415
0
    return false;
416
0
#endif
417
0
}
418
419
//! @endcond