LCOV - code coverage report
Current view: top level - apps - gdalalg_vector_compare.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 280 288 97.2 %
Date: 2026-09-11 05:09:32 Functions: 3 3 100.0 %

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

Generated by: LCOV version 1.14