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_compare.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  gdal "vector compare" subcommand
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2026, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdalalg_vector_compare.h"
14
15
#include "cpl_conv.h"
16
#include "cpl_enumerate.h"
17
#include "gdal_dataset.h"
18
#include "ogrsf_frmts.h"
19
20
//! @cond Doxygen_Suppress
21
22
#ifndef _
23
0
#define _(x) (x)
24
#endif
25
26
/************************************************************************/
27
/*       GDALVectorCompareAlgorithm::GDALVectorCompareAlgorithm()       */
28
/************************************************************************/
29
30
GDALVectorCompareAlgorithm::GDALVectorCompareAlgorithm(bool standaloneStep)
31
0
    : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
32
0
                                      ConstructorOptions()
33
0
                                          .SetStandaloneStep(standaloneStep)
34
0
                                          .SetInputDatasetMaxCount(1)
35
0
                                          .SetAddDefaultArguments(false))
36
0
{
37
0
    if (standaloneStep)
38
0
    {
39
0
        AddProgressArg();
40
0
    }
41
0
    else
42
0
    {
43
0
        AddVectorHiddenInputDatasetArg();
44
0
    }
45
46
0
    auto &referenceDatasetArg = AddArg("reference", 0, _("Reference dataset"),
47
0
                                       &m_referenceDataset, GDAL_OF_VECTOR)
48
0
                                    .SetPositional()
49
0
                                    .SetRequired();
50
51
0
    SetAutoCompleteFunctionForFilename(referenceDatasetArg, GDAL_OF_VECTOR);
52
53
0
    if (standaloneStep)
54
0
    {
55
0
        AddVectorInputArgs(/* hiddenForCLI = */ false);
56
0
    }
57
58
0
    AddArg("lax-geometry", 0, _("Lax geometry comparison"),
59
0
           &m_laxGeometryComparison);
60
61
0
    AddArg("skip-all-optional", 0, _("Skip all optional comparisons"),
62
0
           &m_skipAllOptional);
63
0
    AddArg("skip-binary", 0, _("Skip binary file comparison"), &m_skipBinary);
64
0
    AddArg("skip-crs", 0, _("Skip CRS comparison"), &m_skipCRS);
65
0
    AddArg("skip-metadata", 0, _("Skip metadata comparison"), &m_skipMetadata);
66
0
    AddArg("skip-fid", 0, _("Skip FID comparison"), &m_skipFID);
67
68
0
    AddOutputStringArg(&m_output);
69
70
0
    AddArg("return-code", 0, _("Return code"), &m_retCode)
71
0
        .SetHiddenForCLI()
72
0
        .SetIsInput(false)
73
0
        .SetIsOutput(true);
74
0
}
75
76
/************************************************************************/
77
/*                GDALVectorCompareAlgorithm::RunStep()                 */
78
/************************************************************************/
79
80
bool GDALVectorCompareAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
81
0
{
82
0
    auto poRefDS = m_referenceDataset.GetDatasetRef();
83
0
    CPLAssert(poRefDS);
84
85
0
    CPLAssert(m_inputDataset.size() == 1);
86
0
    auto poInputDS = m_inputDataset[0].GetDatasetRef();
87
0
    CPLAssert(poInputDS);
88
89
0
    if (m_skipAllOptional)
90
0
    {
91
0
        m_skipBinary = true;
92
0
        m_skipCRS = true;
93
0
        m_skipMetadata = true;
94
0
        m_skipFID = true;
95
0
    }
96
97
0
    if (poRefDS == poInputDS)
98
0
    {
99
0
        return true;
100
0
    }
101
102
0
    std::vector<std::string> aosReport;
103
104
0
    if (!m_skipBinary && poRefDS->GetDriver() == poInputDS->GetDriver() &&
105
0
        CPLStringList(poRefDS->GetFileList()).size() == 1 &&
106
0
        CPLStringList(poInputDS->GetFileList()).size() == 1)
107
0
    {
108
0
        if (BinaryComparison(this, aosReport, poRefDS, poInputDS))
109
0
        {
110
0
            return true;
111
0
        }
112
0
    }
113
114
0
    if (!m_skipMetadata)
115
0
    {
116
0
        MetadataComparison(aosReport, "(dataset default metadata domain)",
117
0
                           poRefDS->GetMetadata(), poInputDS->GetMetadata());
118
0
    }
119
120
0
    if (m_inputLayerNames.empty())
121
0
    {
122
0
        const int nRefCount = poRefDS->GetLayerCount();
123
0
        const int nInputCount = poInputDS->GetLayerCount();
124
0
        if (nRefCount != nInputCount)
125
0
        {
126
0
            aosReport.push_back(CPLSPrintf("Reference dataset has %d layer(s), "
127
0
                                           "whereas input dataset has %d",
128
0
                                           nRefCount, nInputCount));
129
0
        }
130
131
0
        if (nRefCount == 1 && nInputCount == 1)
132
0
        {
133
            // Special case to compare for example 2 shapefiles whose layer name
134
            // is related to the filename
135
0
            if (!CompareLayer(aosReport, poRefDS->GetLayer(0),
136
0
                              poInputDS->GetLayer(0), ctxt.m_pfnProgress,
137
0
                              ctxt.m_pProgressData))
138
0
            {
139
0
                return false;
140
0
            }
141
0
        }
142
0
        else
143
0
        {
144
0
            int iCurLayer = 0;
145
0
            for (auto *poRefLayer : poRefDS->GetLayers())
146
0
            {
147
0
                auto *poInputLayer =
148
0
                    poInputDS->GetLayerByName(poRefLayer->GetName());
149
0
                if (poInputLayer)
150
0
                {
151
0
                    std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)>
152
0
                        pScaledProgress(
153
0
                            GDALCreateScaledProgress(
154
0
                                static_cast<double>(iCurLayer) / nRefCount,
155
0
                                static_cast<double>(iCurLayer + 1) / nRefCount,
156
0
                                ctxt.m_pfnProgress, ctxt.m_pProgressData),
157
0
                            GDALDestroyScaledProgress);
158
0
                    ++iCurLayer;
159
0
                    if (!CompareLayer(aosReport, poRefLayer, poInputLayer,
160
0
                                      pScaledProgress ? GDALScaledProgress
161
0
                                                      : nullptr,
162
0
                                      pScaledProgress.get()))
163
0
                    {
164
0
                        return false;
165
0
                    }
166
0
                }
167
0
                else
168
0
                {
169
0
                    aosReport.push_back(
170
0
                        CPLSPrintf("Layer %s present in reference dataset is "
171
0
                                   "absent from input dataset",
172
0
                                   poRefLayer->GetName()));
173
0
                }
174
0
            }
175
176
0
            for (auto *poInputLayer : poInputDS->GetLayers())
177
0
            {
178
0
                if (!poRefDS->GetLayerByName(poInputLayer->GetName()))
179
0
                {
180
0
                    aosReport.push_back(
181
0
                        CPLSPrintf("Layer %s present in input dataset is "
182
0
                                   "absent from reference dataset",
183
0
                                   poInputLayer->GetName()));
184
0
                }
185
0
            }
186
0
        }
187
0
    }
188
0
    else
189
0
    {
190
0
        for (const auto &[i, osLayerName] : cpl::enumerate(m_inputLayerNames))
191
0
        {
192
0
            auto *poRefLayer = poRefDS->GetLayerByName(osLayerName.c_str());
193
0
            auto *poInputLayer = poInputDS->GetLayerByName(osLayerName.c_str());
194
0
            CPLAssert(poInputLayer);  // guaranteed by GDALAlgorithm
195
0
            if (poRefLayer)
196
0
            {
197
0
                std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)>
198
0
                    pScaledProgress(
199
0
                        GDALCreateScaledProgress(
200
0
                            static_cast<double>(i) /
201
0
                                static_cast<double>(m_inputLayerNames.size()),
202
0
                            static_cast<double>(i + 1) /
203
0
                                static_cast<double>(m_inputLayerNames.size()),
204
0
                            ctxt.m_pfnProgress, ctxt.m_pProgressData),
205
0
                        GDALDestroyScaledProgress);
206
0
                if (!CompareLayer(aosReport, poRefLayer, poInputLayer,
207
0
                                  pScaledProgress ? GDALScaledProgress
208
0
                                                  : nullptr,
209
0
                                  pScaledProgress.get()))
210
0
                {
211
0
                    return false;
212
0
                }
213
0
            }
214
0
            else
215
0
            {
216
0
                ReportError(CE_Failure, CPLE_AppDefined,
217
0
                            "Layer %s present in input dataset is absent from "
218
0
                            "reference dataset",
219
0
                            osLayerName.c_str());
220
0
                return false;
221
0
            }
222
0
        }
223
0
    }
224
225
    // Ignore difference related to DBF_DATE_LAST_UPDATE if no other difference
226
0
    if (aosReport.size() == 1 &&
227
0
        aosReport[0].find("DBF_DATE_LAST_UPDATE") != std::string::npos)
228
0
        aosReport.clear();
229
230
0
    for (const auto &s : aosReport)
231
0
    {
232
0
        m_output += s;
233
0
        m_output += '\n';
234
0
    }
235
236
0
    m_retCode = static_cast<int>(aosReport.size());
237
238
0
    return true;
239
0
}
240
241
/************************************************************************/
242
/*              GDALVectorCompareAlgorithm::CompareLayer()              */
243
/************************************************************************/
244
245
bool GDALVectorCompareAlgorithm::CompareLayer(
246
    std::vector<std::string> &aosReport, OGRLayer *poRefLayer,
247
    OGRLayer *poInputLayer, GDALProgressFunc pfnProgressFunc,
248
    void *pProgressData)
249
0
{
250
0
    const bool bSameLayerName =
251
0
        EQUAL(poRefLayer->GetName(), poInputLayer->GetName());
252
0
    const std::string osLayerCtxt =
253
0
        bSameLayerName ? CPLSPrintf("Layer %s: ", poRefLayer->GetName()) : "";
254
255
0
    if (!m_skipMetadata)
256
0
    {
257
0
        MetadataComparison(
258
0
            aosReport,
259
0
            CPLSPrintf(
260
0
                "(layer%s default metadata domain)",
261
0
                bSameLayerName
262
0
                    ? std::string(" ").append(poRefLayer->GetName()).c_str()
263
0
                    : ""),
264
0
            poRefLayer->GetMetadata(), poInputLayer->GetMetadata());
265
0
    }
266
267
0
    const OGRFeatureDefn *poRefDefn = poRefLayer->GetLayerDefn();
268
0
    const OGRFeatureDefn *poInputDefn = poInputLayer->GetLayerDefn();
269
270
0
    auto poRefDS = m_referenceDataset.GetDatasetRef();
271
0
    CPLAssert(poRefDS);
272
273
0
    CPLAssert(m_inputDataset.size() == 1);
274
0
    auto poInputDS = m_inputDataset[0].GetDatasetRef();
275
0
    CPLAssert(poInputDS);
276
277
0
    auto poRefDriver = poRefDS->GetDriver();
278
0
    auto poInputDriver = poInputDS->GetDriver();
279
280
0
    const bool bRefWidthIncludesSign =
281
0
        poRefDriver && poRefDriver->GetMetadataItem(
282
0
                           GDAL_DMD_NUMERIC_FIELD_WIDTH_INCLUDES_SIGN);
283
0
    const bool bInputWidthIncludesSign =
284
0
        poInputDriver && poInputDriver->GetMetadataItem(
285
0
                             GDAL_DMD_NUMERIC_FIELD_WIDTH_INCLUDES_SIGN);
286
0
    const bool bRefWidthIncludesDecimalSeparator =
287
0
        poRefDriver &&
288
0
        poRefDriver->GetMetadataItem(
289
0
            GDAL_DMD_NUMERIC_FIELD_WIDTH_INCLUDES_DECIMAL_SEPARATOR);
290
0
    const bool bInputWidthIncludesDecimalSeparator =
291
0
        poInputDriver &&
292
0
        poInputDriver->GetMetadataItem(
293
0
            GDAL_DMD_NUMERIC_FIELD_WIDTH_INCLUDES_DECIMAL_SEPARATOR);
294
295
    // Compare attribute field definitions
296
0
    const int nRefFieldCount = poRefDefn->GetFieldCount();
297
0
    const int nInputFieldCount = poInputDefn->GetFieldCount();
298
0
    if (nRefFieldCount != nInputFieldCount)
299
0
    {
300
0
        aosReport.push_back(CPLSPrintf("%sReference layer has %d attribute "
301
0
                                       "field(s), whereas input layer has %d",
302
0
                                       osLayerCtxt.c_str(), nRefFieldCount,
303
0
                                       nInputFieldCount));
304
0
    }
305
306
0
    std::vector<int> anMapRefToInputFields;
307
0
    for (const auto *poRefFieldDefn : poRefDefn->GetFields())
308
0
    {
309
0
        const int nInputFieldDefnIdx =
310
0
            poInputDefn->GetFieldIndex(poRefFieldDefn->GetNameRef());
311
0
        anMapRefToInputFields.push_back(nInputFieldDefnIdx);
312
0
        if (nInputFieldDefnIdx >= 0)
313
0
        {
314
0
            const auto *poInputFieldDefn =
315
0
                poInputDefn->GetFieldDefn(nInputFieldDefnIdx);
316
317
0
            if (poRefFieldDefn->GetType() != poInputFieldDefn->GetType())
318
0
            {
319
0
                aosReport.push_back(CPLSPrintf(
320
0
                    "%sField '%s' has type %s in reference layer, but %s in "
321
0
                    "input layer",
322
0
                    osLayerCtxt.c_str(), poInputFieldDefn->GetNameRef(),
323
0
                    OGR_GetFieldTypeName(poRefFieldDefn->GetType()),
324
0
                    OGR_GetFieldTypeName(poInputFieldDefn->GetType())));
325
0
            }
326
327
0
            if (poRefFieldDefn->GetSubType() != poInputFieldDefn->GetSubType())
328
0
            {
329
0
                aosReport.push_back(CPLSPrintf(
330
0
                    "%sField '%s' has subtype %s in reference layer, but %s in "
331
0
                    "input layer",
332
0
                    osLayerCtxt.c_str(), poInputFieldDefn->GetNameRef(),
333
0
                    OGR_GetFieldSubTypeName(poRefFieldDefn->GetSubType()),
334
0
                    OGR_GetFieldSubTypeName(poInputFieldDefn->GetSubType())));
335
0
            }
336
337
0
            if (poRefFieldDefn->GetType() == OFTReal &&
338
0
                poInputFieldDefn->GetType() == OFTReal &&
339
0
                (poRefFieldDefn->GetWidth() != 0 ||
340
0
                 poInputFieldDefn->GetWidth() != 0) &&
341
0
                (bRefWidthIncludesSign != bInputWidthIncludesSign))
342
0
            {
343
0
                aosReport.push_back(CPLSPrintf(
344
0
                    "%sField '%s' has width %d in reference layer and %s sign "
345
0
                    "but width %d in input layer and %s sign",
346
0
                    osLayerCtxt.c_str(), poInputFieldDefn->GetNameRef(),
347
0
                    poRefFieldDefn->GetWidth(),
348
0
                    bRefWidthIncludesSign ? "includes" : "does not include",
349
0
                    poInputFieldDefn->GetWidth(),
350
0
                    bInputWidthIncludesSign ? "includes" : "does not include"));
351
0
            }
352
0
            else if (poRefFieldDefn->GetWidth() != poInputFieldDefn->GetWidth())
353
0
            {
354
0
                aosReport.push_back(CPLSPrintf(
355
0
                    "%sField '%s' has width %d in reference layer, but %d in "
356
0
                    "input layer",
357
0
                    osLayerCtxt.c_str(), poInputFieldDefn->GetNameRef(),
358
0
                    poRefFieldDefn->GetWidth(), poInputFieldDefn->GetWidth()));
359
0
            }
360
361
0
            if (poRefFieldDefn->GetType() == OFTReal &&
362
0
                poInputFieldDefn->GetType() == OFTReal &&
363
0
                (poRefFieldDefn->GetPrecision() != 0 ||
364
0
                 poInputFieldDefn->GetPrecision() != 0) &&
365
0
                (bRefWidthIncludesDecimalSeparator !=
366
0
                 bInputWidthIncludesDecimalSeparator))
367
0
            {
368
0
                aosReport.push_back(CPLSPrintf(
369
0
                    "%sField '%s' has precision %d in reference layer and %s "
370
0
                    "decimal separator but precision %d in input layer and %s "
371
0
                    "decimal separator",
372
0
                    osLayerCtxt.c_str(), poInputFieldDefn->GetNameRef(),
373
0
                    poRefFieldDefn->GetPrecision(),
374
0
                    bRefWidthIncludesDecimalSeparator ? "includes"
375
0
                                                      : "does not include",
376
0
                    poInputFieldDefn->GetPrecision(),
377
0
                    bInputWidthIncludesDecimalSeparator ? "includes"
378
0
                                                        : "does not include"));
379
0
            }
380
0
            else if (poRefFieldDefn->GetPrecision() !=
381
0
                     poInputFieldDefn->GetPrecision())
382
0
            {
383
0
                aosReport.push_back(CPLSPrintf(
384
0
                    "%sField '%s' has precision %d in reference layer, but %d "
385
0
                    "in "
386
0
                    "input layer",
387
0
                    osLayerCtxt.c_str(), poInputFieldDefn->GetNameRef(),
388
0
                    poRefFieldDefn->GetPrecision(),
389
0
                    poInputFieldDefn->GetPrecision()));
390
0
            }
391
392
0
            if (!EQUAL(poRefFieldDefn->GetAlternativeNameRef(),
393
0
                       poInputFieldDefn->GetAlternativeNameRef()))
394
0
            {
395
0
                aosReport.push_back(CPLSPrintf(
396
0
                    "%sField '%s' has '%s' as alternative name in reference "
397
0
                    "layer, but '%s' in input layer",
398
0
                    osLayerCtxt.c_str(), poInputFieldDefn->GetNameRef(),
399
0
                    poRefFieldDefn->GetAlternativeNameRef(),
400
0
                    poInputFieldDefn->GetAlternativeNameRef()));
401
0
            }
402
403
0
            if (poRefFieldDefn->IsNullable() != poInputFieldDefn->IsNullable())
404
0
            {
405
0
                aosReport.push_back(CPLSPrintf(
406
0
                    "%sField '%s' has nullable=%d in reference layer, but "
407
0
                    "nullable=%d in input layer",
408
0
                    osLayerCtxt.c_str(), poInputFieldDefn->GetNameRef(),
409
0
                    poRefFieldDefn->IsNullable(),
410
0
                    poInputFieldDefn->IsNullable()));
411
0
            }
412
413
0
            if (poRefFieldDefn->IsUnique() != poInputFieldDefn->IsUnique())
414
0
            {
415
0
                aosReport.push_back(CPLSPrintf(
416
0
                    "%sField '%s' has unique constraint=%d in reference layer, "
417
0
                    "but unique constraint=%d in input layer",
418
0
                    osLayerCtxt.c_str(), poInputFieldDefn->GetNameRef(),
419
0
                    poRefFieldDefn->IsUnique(), poInputFieldDefn->IsUnique()));
420
0
            }
421
422
0
            if (poRefFieldDefn->IsGenerated() !=
423
0
                poInputFieldDefn->IsGenerated())
424
0
            {
425
0
                aosReport.push_back(CPLSPrintf(
426
0
                    "%sField '%s' has generated status=%d in reference layer, "
427
0
                    "but generated status=%d in input layer",
428
0
                    osLayerCtxt.c_str(), poInputFieldDefn->GetNameRef(),
429
0
                    poRefFieldDefn->IsGenerated(),
430
0
                    poInputFieldDefn->IsGenerated()));
431
0
            }
432
433
0
            if (poRefFieldDefn->GetComment() != poInputFieldDefn->GetComment())
434
0
            {
435
0
                aosReport.push_back(CPLSPrintf(
436
0
                    "%sField '%s' has '%s' as comment in reference layer, but "
437
0
                    "'%s' in input layer",
438
0
                    osLayerCtxt.c_str(), poInputFieldDefn->GetNameRef(),
439
0
                    poRefFieldDefn->GetComment().c_str(),
440
0
                    poInputFieldDefn->GetComment().c_str()));
441
0
            }
442
443
0
            if (poRefFieldDefn->GetDomainName() !=
444
0
                poInputFieldDefn->GetDomainName())
445
0
            {
446
0
                aosReport.push_back(CPLSPrintf(
447
0
                    "%sField '%s' has '%s' as domain name in reference layer, "
448
0
                    "but '%s' in input layer",
449
0
                    osLayerCtxt.c_str(), poInputFieldDefn->GetNameRef(),
450
0
                    poRefFieldDefn->GetDomainName().c_str(),
451
0
                    poInputFieldDefn->GetDomainName().c_str()));
452
0
            }
453
454
0
            const char *pszRefDefault = poRefFieldDefn->GetDefault();
455
0
            const char *pszInputDefault = poInputFieldDefn->GetDefault();
456
0
            if (((pszRefDefault != nullptr) != (pszInputDefault != nullptr)) ||
457
0
                (pszRefDefault && pszInputDefault &&
458
0
                 !EQUAL(pszRefDefault, pszInputDefault)))
459
0
            {
460
0
                aosReport.push_back(CPLSPrintf(
461
0
                    "%sField '%s' has '%s' as default in reference layer, but "
462
0
                    "'%s' in input layer",
463
0
                    osLayerCtxt.c_str(), poInputFieldDefn->GetNameRef(),
464
0
                    pszRefDefault ? pszRefDefault : "(null)",
465
0
                    pszInputDefault ? pszInputDefault : "(null)"));
466
0
            }
467
0
        }
468
0
        else
469
0
        {
470
0
            aosReport.push_back(CPLSPrintf("%sReference layer has field %s, "
471
0
                                           "which is absent in input layer",
472
0
                                           osLayerCtxt.c_str(),
473
0
                                           poRefFieldDefn->GetNameRef()));
474
0
        }
475
0
    }
476
477
0
    for (const auto *poInputFieldDefn : poInputDefn->GetFields())
478
0
    {
479
0
        if (poRefDefn->GetFieldIndex(poInputFieldDefn->GetNameRef()) < 0)
480
0
        {
481
0
            aosReport.push_back(CPLSPrintf("%sInput layer has field %s, which "
482
0
                                           "is absent in reference layer",
483
0
                                           osLayerCtxt.c_str(),
484
0
                                           poInputFieldDefn->GetNameRef()));
485
0
        }
486
0
    }
487
488
    // Compare geometry field definitions
489
0
    const int nRefGeomFieldCount = poRefDefn->GetGeomFieldCount();
490
0
    const int nInputGeomFieldCount = poInputDefn->GetGeomFieldCount();
491
0
    if (nRefGeomFieldCount != nInputGeomFieldCount)
492
0
    {
493
0
        aosReport.push_back(CPLSPrintf("%sReference layer has %d geometry "
494
0
                                       "field(s), whereas input layer has %d",
495
0
                                       osLayerCtxt.c_str(), nRefGeomFieldCount,
496
0
                                       nInputGeomFieldCount));
497
0
    }
498
499
0
    const bool bSingleGeomField =
500
0
        nRefGeomFieldCount == 1 && nInputGeomFieldCount == 1;
501
502
0
    std::vector<int> anMapRefToInputGeomFields;
503
0
    for (const auto *poRefFieldDefn : poRefDefn->GetGeomFields())
504
0
    {
505
0
        const int nInputFieldDefnIdx =
506
0
            bSingleGeomField
507
0
                ? 0
508
0
                : poInputDefn->GetGeomFieldIndex(poRefFieldDefn->GetNameRef());
509
0
        anMapRefToInputGeomFields.push_back(nInputFieldDefnIdx);
510
0
        if (nInputFieldDefnIdx >= 0)
511
0
        {
512
0
            const auto *poInputFieldDefn =
513
0
                poInputDefn->GetGeomFieldDefn(nInputFieldDefnIdx);
514
515
0
            if (poRefFieldDefn->GetType() != poInputFieldDefn->GetType())
516
0
            {
517
0
                aosReport.push_back(CPLSPrintf(
518
0
                    "%sGeometry field '%s' has geometry type %s in reference "
519
0
                    "layer, but %s in input layer",
520
0
                    osLayerCtxt.c_str(), poInputFieldDefn->GetNameRef(),
521
0
                    OGRGeometryTypeToName(poRefFieldDefn->GetType()),
522
0
                    OGRGeometryTypeToName(poInputFieldDefn->GetType())));
523
0
            }
524
525
0
            if (poRefFieldDefn->IsNullable() != poInputFieldDefn->IsNullable())
526
0
            {
527
0
                aosReport.push_back(CPLSPrintf(
528
0
                    "%sGeometry field '%s' has nullable=%d in reference layer, "
529
0
                    "but nullable=%d in input layer",
530
0
                    osLayerCtxt.c_str(), poInputFieldDefn->GetNameRef(),
531
0
                    poRefFieldDefn->IsNullable(),
532
0
                    poInputFieldDefn->IsNullable()));
533
0
            }
534
535
0
            if (!m_skipCRS)
536
0
            {
537
0
                const auto poRefSRS = poRefFieldDefn->GetSpatialRef();
538
0
                const auto poInputSRS = poInputFieldDefn->GetSpatialRef();
539
0
                if (((poRefSRS != nullptr) != (poInputSRS != nullptr)) ||
540
0
                    (poRefSRS && poInputSRS && !poRefSRS->IsSame(poInputSRS)))
541
0
                {
542
0
                    const char *apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
543
0
                    aosReport.push_back(CPLSPrintf(
544
0
                        "%sGeometry field '%s' has different CRS in reference "
545
0
                        "and input layers. Value in reference layer is %s, "
546
0
                        "whereas it is %s in input layer",
547
0
                        osLayerCtxt.c_str(), poInputFieldDefn->GetNameRef(),
548
0
                        poRefSRS ? poRefSRS->exportToWkt(apszOptions).c_str()
549
0
                                 : "null",
550
0
                        poInputSRS
551
0
                            ? poInputSRS->exportToWkt(apszOptions).c_str()
552
0
                            : "null"));
553
0
                }
554
0
            }
555
0
        }
556
0
        else
557
0
        {
558
0
            aosReport.push_back(
559
0
                CPLSPrintf("%sReference layer has geometry field '%s', which "
560
0
                           "is absent in input layer",
561
0
                           osLayerCtxt.c_str(), poRefFieldDefn->GetNameRef()));
562
0
        }
563
0
    }
564
565
0
    if (!bSingleGeomField)
566
0
    {
567
0
        for (const auto *poInputFieldDefn : poInputDefn->GetGeomFields())
568
0
        {
569
0
            if (poRefDefn->GetGeomFieldIndex(poInputFieldDefn->GetNameRef()) <
570
0
                0)
571
0
            {
572
0
                aosReport.push_back(CPLSPrintf(
573
0
                    "%sInput layer has geometry field '%s', which is absent in "
574
0
                    "reference layer",
575
0
                    osLayerCtxt.c_str(), poInputFieldDefn->GetNameRef()));
576
0
            }
577
0
        }
578
0
    }
579
580
0
    const GIntBig nRefFeatureCount =
581
0
        pfnProgressFunc ? poRefLayer->GetFeatureCount() : -1;
582
0
    const GIntBig nInputFeatureCount =
583
0
        pfnProgressFunc ? poInputLayer->GetFeatureCount() : -1;
584
0
    if (nRefFeatureCount != nInputFeatureCount && nRefFeatureCount != -1 &&
585
0
        nInputFeatureCount != -1)
586
0
    {
587
0
        aosReport.push_back(CPLSPrintf(
588
0
            "%sReference layer has " CPL_FRMT_GIB
589
0
            " feature(s), whereas input layer has " CPL_FRMT_GIB,
590
0
            osLayerCtxt.c_str(), nRefFeatureCount, nInputFeatureCount));
591
0
    }
592
593
    // Compare features
594
0
    poRefLayer->ResetReading();
595
0
    poInputLayer->ResetReading();
596
597
0
    GIntBig nCount = 0;
598
0
    while (true)
599
0
    {
600
0
        auto poRefFeature =
601
0
            std::unique_ptr<OGRFeature>(poRefLayer->GetNextFeature());
602
0
        auto poInputFeature =
603
0
            std::unique_ptr<OGRFeature>(poInputLayer->GetNextFeature());
604
0
        if (!poRefFeature)
605
0
        {
606
0
            if (poInputFeature &&
607
0
                (nRefFeatureCount < 0 || nInputFeatureCount < 0))
608
0
            {
609
0
                GIntBig nExtraFeatures = 1;
610
0
                while (
611
0
                    std::unique_ptr<OGRFeature>(poInputLayer->GetNextFeature()))
612
0
                {
613
0
                    nExtraFeatures++;
614
0
                }
615
0
                aosReport.push_back(CPLSPrintf(
616
0
                    "%sInput layer has " CPL_FRMT_GIB
617
0
                    " feature(s), whereas reference layer has " CPL_FRMT_GIB,
618
0
                    osLayerCtxt.c_str(), nCount + nExtraFeatures, nCount));
619
0
            }
620
0
            break;
621
0
        }
622
623
0
        if (!poInputFeature)
624
0
        {
625
0
            if (nRefFeatureCount < 0 || nInputFeatureCount < 0)
626
0
            {
627
0
                GIntBig nExtraFeatures = 1;
628
0
                while (
629
0
                    std::unique_ptr<OGRFeature>(poRefLayer->GetNextFeature()))
630
0
                {
631
0
                    nExtraFeatures++;
632
0
                }
633
0
                aosReport.push_back(CPLSPrintf(
634
0
                    "%sReference layer has " CPL_FRMT_GIB
635
0
                    " feature(s), whereas input layer has " CPL_FRMT_GIB,
636
0
                    osLayerCtxt.c_str(), nCount + nExtraFeatures, nCount));
637
0
            }
638
0
            break;
639
0
        }
640
641
0
        if (!m_skipFID && poRefFeature->GetFID() != poInputFeature->GetFID())
642
0
        {
643
0
            aosReport.push_back(
644
0
                CPLSPrintf("%sFeature at index " CPL_FRMT_GIB
645
0
                           " has feature id " CPL_FRMT_GIB
646
0
                           " in reference layer, whereas it is " CPL_FRMT_GIB
647
0
                           " in input layer",
648
0
                           osLayerCtxt.c_str(), nCount, poRefFeature->GetFID(),
649
0
                           poInputFeature->GetFID()));
650
0
        }
651
652
        // Compare attribute field values
653
0
        for (int i = 0; i < nRefFieldCount; ++i)
654
0
        {
655
0
            const int j = anMapRefToInputFields[i];
656
0
            if (j >= 0)
657
0
            {
658
0
                if (!OGRFeature::IsSameFieldValue(poRefFeature.get(), i,
659
0
                                                  poInputFeature.get(), j))
660
0
                {
661
0
                    aosReport.push_back(
662
0
                        CPLSPrintf("%sFeature at index " CPL_FRMT_GIB
663
0
                                   " has value '%s' for field %s in reference "
664
0
                                   "layer, whereas it is '%s' in input layer",
665
0
                                   osLayerCtxt.c_str(), nCount,
666
0
                                   poRefFeature->GetFieldAsString(i),
667
0
                                   poRefDefn->GetFieldDefn(i)->GetNameRef(),
668
0
                                   poInputFeature->GetFieldAsString(j)));
669
0
                }
670
0
            }
671
0
        }
672
673
        // Compare geometry field values
674
0
        for (int i = 0; i < nRefGeomFieldCount; ++i)
675
0
        {
676
0
            const int j = anMapRefToInputGeomFields[i];
677
0
            if (j >= 0)
678
0
            {
679
0
                const auto poRefGeom = poRefFeature->GetGeomFieldRef(i);
680
0
                const auto poInputGeom = poInputFeature->GetGeomFieldRef(j);
681
0
                if ((poRefGeom != nullptr) != (poInputGeom != nullptr) ||
682
0
                    (poRefGeom && poInputGeom &&
683
0
                     !poRefGeom->Equals(poInputGeom)))
684
0
                {
685
0
                    bool bSame = false;
686
0
                    if (poRefGeom && poInputGeom && m_laxGeometryComparison)
687
0
                    {
688
0
                        if (wkbFlatten(OGR_GT_GetCollection(
689
0
                                poRefGeom->getGeometryType())) ==
690
0
                            poInputGeom->getGeometryType())
691
0
                        {
692
0
                            auto poNewGeom = OGRGeometryFactory::forceTo(
693
0
                                std::unique_ptr<OGRGeometry>(
694
0
                                    poRefGeom->clone()),
695
0
                                poInputGeom->getGeometryType());
696
0
                            bSame = poNewGeom && poNewGeom->Equals(poInputGeom);
697
0
                        }
698
0
                        else if (wkbFlatten(OGR_GT_GetCollection(
699
0
                                     poInputGeom->getGeometryType())) ==
700
0
                                 poRefGeom->getGeometryType())
701
0
                        {
702
0
                            auto poNewGeom = OGRGeometryFactory::forceTo(
703
0
                                std::unique_ptr<OGRGeometry>(
704
0
                                    poInputGeom->clone()),
705
0
                                poRefGeom->getGeometryType());
706
0
                            bSame = poNewGeom && poNewGeom->Equals(poRefGeom);
707
0
                        }
708
0
                    }
709
0
                    if (!bSame)
710
0
                    {
711
0
                        aosReport.push_back(CPLSPrintf(
712
0
                            "%sFeature at index " CPL_FRMT_GIB
713
0
                            " has value %s for geometry field '%s' in "
714
0
                            "reference layer, whereas it is %s in input layer",
715
0
                            osLayerCtxt.c_str(), nCount,
716
0
                            poRefGeom ? poRefGeom->exportToWkt().c_str()
717
0
                                      : "null",
718
0
                            poRefDefn->GetGeomFieldDefn(i)->GetNameRef(),
719
0
                            poInputGeom ? poInputGeom->exportToWkt().c_str()
720
0
                                        : "null"));
721
0
                    }
722
0
                }
723
0
            }
724
0
        }
725
726
0
        if (pfnProgressFunc && nRefFeatureCount > 0 &&
727
0
            (nCount < 1000 || (nCount % 128) == 0) &&
728
0
            !pfnProgressFunc(static_cast<double>(nCount) /
729
0
                                 static_cast<double>(nRefFeatureCount),
730
0
                             "", pProgressData))
731
0
        {
732
0
            ReportError(CE_Failure, CPLE_UserInterrupt, "Interrupted by user");
733
0
            return false;
734
0
        }
735
736
0
        ++nCount;
737
0
    }
738
739
0
    if (pfnProgressFunc && !pfnProgressFunc(1.0, "", pProgressData))
740
0
    {
741
0
        ReportError(CE_Failure, CPLE_UserInterrupt, "Interrupted by user");
742
0
        return false;
743
0
    }
744
0
    return true;
745
0
}
746
747
/************************************************************************/
748
/*               ~GDALVectorCompareAlgorithmStandalone()                */
749
/************************************************************************/
750
751
0
GDALVectorCompareAlgorithmStandalone::~GDALVectorCompareAlgorithmStandalone() =
752
    default;
753
754
//! @endcond