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
|