Coverage Report

Created: 2026-02-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalalg_raster_compare.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  gdal "raster compare" subcommand
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdalalg_raster_compare.h"
14
15
#include "cpl_conv.h"
16
#include "cpl_vsi_virtual.h"
17
#include "gdal_alg.h"
18
#include "gdal_priv.h"
19
20
#include <algorithm>
21
#include <array>
22
#include <cmath>
23
#include <limits>
24
#include <type_traits>
25
26
#if defined(__x86_64__) || defined(_M_X64)
27
#define USE_SSE2
28
#include <emmintrin.h>
29
#elif defined(USE_NEON_OPTIMIZATIONS)
30
#define USE_SSE2
31
#include "include_sse2neon.h"
32
#endif
33
34
//! @cond Doxygen_Suppress
35
36
#ifndef _
37
0
#define _(x) (x)
38
#endif
39
40
/************************************************************************/
41
/*       GDALRasterCompareAlgorithm::GDALRasterCompareAlgorithm()       */
42
/************************************************************************/
43
44
GDALRasterCompareAlgorithm::GDALRasterCompareAlgorithm(bool standaloneStep)
45
0
    : GDALRasterPipelineStepAlgorithm(
46
0
          NAME, DESCRIPTION, HELP_URL,
47
0
          ConstructorOptions()
48
0
              .SetStandaloneStep(standaloneStep)
49
0
              .SetInputDatasetMaxCount(1)
50
0
              .SetInputDatasetHelpMsg(_("Input raster dataset"))
51
0
              .SetAddDefaultArguments(false))
52
0
{
53
0
    AddProgressArg();
54
55
0
    if (!standaloneStep)
56
0
    {
57
0
        AddRasterHiddenInputDatasetArg();
58
0
    }
59
60
0
    auto &referenceDatasetArg = AddArg("reference", 0, _("Reference dataset"),
61
0
                                       &m_referenceDataset, GDAL_OF_RASTER)
62
0
                                    .SetPositional()
63
0
                                    .SetRequired();
64
65
0
    SetAutoCompleteFunctionForFilename(referenceDatasetArg, GDAL_OF_RASTER);
66
67
0
    if (standaloneStep)
68
0
    {
69
0
        AddRasterInputArgs(/* openForMixedRasterVector = */ false,
70
0
                           /* hiddenForCLI = */ false);
71
0
    }
72
73
0
    AddArg("skip-all-optional", 0, _("Skip all optional comparisons"),
74
0
           &m_skipAllOptional);
75
0
    AddArg("skip-binary", 0, _("Skip binary file comparison"), &m_skipBinary);
76
0
    AddArg("skip-crs", 0, _("Skip CRS comparison"), &m_skipCRS);
77
0
    AddArg("skip-geotransform", 0, _("Skip geotransform comparison"),
78
0
           &m_skipGeotransform);
79
0
    AddArg("skip-overview", 0, _("Skip overview comparison"), &m_skipOverview);
80
0
    AddArg("skip-metadata", 0, _("Skip metadata comparison"), &m_skipMetadata);
81
0
    AddArg("skip-rpc", 0, _("Skip RPC metadata comparison"), &m_skipRPC);
82
0
    AddArg("skip-geolocation", 0, _("Skip Geolocation metadata comparison"),
83
0
           &m_skipGeolocation);
84
0
    AddArg("skip-subdataset", 0, _("Skip subdataset comparison"),
85
0
           &m_skipSubdataset);
86
87
0
    AddOutputStringArg(&m_output);
88
89
0
    AddArg("return-code", 0, _("Return code"), &m_retCode)
90
0
        .SetHiddenForCLI()
91
0
        .SetIsInput(false)
92
0
        .SetIsOutput(true);
93
0
}
94
95
/************************************************************************/
96
/*            GDALRasterCompareAlgorithm::BinaryComparison()            */
97
/************************************************************************/
98
99
bool GDALRasterCompareAlgorithm::BinaryComparison(
100
    std::vector<std::string> &aosReport, GDALDataset *poRefDS,
101
    GDALDataset *poInputDS)
102
0
{
103
0
    if (poRefDS->GetDescription()[0] == 0)
104
0
    {
105
0
        ReportError(
106
0
            CE_Warning, CPLE_AppDefined,
107
0
            "Reference dataset has no name. Skipping binary file comparison");
108
0
        return false;
109
0
    }
110
111
0
    auto poRefDrv = poRefDS->GetDriver();
112
0
    if (poRefDrv && EQUAL(poRefDrv->GetDescription(), "MEM"))
113
0
    {
114
0
        ReportError(CE_Warning, CPLE_AppDefined,
115
0
                    "Reference dataset is a in-memory dataset. Skipping binary "
116
0
                    "file comparison");
117
0
        return false;
118
0
    }
119
120
0
    if (poInputDS->GetDescription()[0] == 0)
121
0
    {
122
0
        ReportError(
123
0
            CE_Warning, CPLE_AppDefined,
124
0
            "Input dataset has no name. Skipping binary file comparison");
125
0
        return false;
126
0
    }
127
128
0
    auto poInputDrv = poInputDS->GetDriver();
129
0
    if (poInputDrv && EQUAL(poInputDrv->GetDescription(), "MEM"))
130
0
    {
131
0
        ReportError(CE_Warning, CPLE_AppDefined,
132
0
                    "Input dataset is a in-memory dataset. Skipping binary "
133
0
                    "file comparison");
134
0
        return false;
135
0
    }
136
137
0
    VSIVirtualHandleUniquePtr fpRef(VSIFOpenL(poRefDS->GetDescription(), "rb"));
138
0
    VSIVirtualHandleUniquePtr fpInput(
139
0
        VSIFOpenL(poInputDS->GetDescription(), "rb"));
140
0
    if (!fpRef)
141
0
    {
142
0
        ReportError(CE_Warning, CPLE_AppDefined,
143
0
                    "Reference dataset '%s' is not a file. Skipping binary "
144
0
                    "file comparison",
145
0
                    poRefDS->GetDescription());
146
0
        return false;
147
0
    }
148
149
0
    if (!fpInput)
150
0
    {
151
0
        ReportError(
152
0
            CE_Warning, CPLE_AppDefined,
153
0
            "Input dataset '%s' is not a file. Skipping binary file comparison",
154
0
            poInputDS->GetDescription());
155
0
        return false;
156
0
    }
157
158
0
    fpRef->Seek(0, SEEK_END);
159
0
    fpInput->Seek(0, SEEK_END);
160
0
    const auto nRefSize = fpRef->Tell();
161
0
    const auto nInputSize = fpInput->Tell();
162
0
    if (nRefSize != nInputSize)
163
0
    {
164
0
        aosReport.push_back("Reference file has size " +
165
0
                            std::to_string(nRefSize) +
166
0
                            " bytes, whereas input file has size " +
167
0
                            std::to_string(nInputSize) + " bytes.");
168
169
0
        return false;
170
0
    }
171
172
0
    constexpr size_t BUF_SIZE = 1024 * 1024;
173
0
    std::vector<GByte> abyRef(BUF_SIZE);
174
0
    std::vector<GByte> abyInput(BUF_SIZE);
175
176
0
    fpRef->Seek(0, SEEK_SET);
177
0
    fpInput->Seek(0, SEEK_SET);
178
179
0
    do
180
0
    {
181
0
        const size_t nRefRead = fpRef->Read(abyRef.data(), 1, BUF_SIZE);
182
0
        const size_t nInputRead = fpInput->Read(abyInput.data(), 1, BUF_SIZE);
183
184
0
        if (nRefRead != BUF_SIZE && fpRef->Tell() != nRefSize)
185
0
        {
186
0
            aosReport.push_back("Failed to fully read reference file");
187
0
            return false;
188
0
        }
189
190
0
        if (nInputRead != BUF_SIZE && fpInput->Tell() != nRefSize)
191
0
        {
192
0
            aosReport.push_back("Failed to fully read input file");
193
0
            return false;
194
0
        }
195
196
0
        if (abyRef != abyInput)
197
0
        {
198
0
            aosReport.push_back(
199
0
                "Reference file and input file differ at the binary level.");
200
0
            return false;
201
0
        }
202
0
    } while (fpRef->Tell() < nRefSize);
203
204
0
    return true;
205
0
}
206
207
/************************************************************************/
208
/*             GDALRasterCompareAlgorithm::CRSComparison()              */
209
/************************************************************************/
210
211
void GDALRasterCompareAlgorithm::CRSComparison(
212
    std::vector<std::string> &aosReport, GDALDataset *poRefDS,
213
    GDALDataset *poInputDS)
214
0
{
215
0
    const auto poRefCRS = poRefDS->GetSpatialRef();
216
0
    const auto poInputCRS = poInputDS->GetSpatialRef();
217
218
0
    if (poRefCRS == nullptr)
219
0
    {
220
0
        if (poInputCRS)
221
0
        {
222
0
            aosReport.push_back(
223
0
                "Reference dataset has no CRS, but input dataset has one.");
224
0
        }
225
0
        return;
226
0
    }
227
228
0
    if (poInputCRS == nullptr)
229
0
    {
230
0
        aosReport.push_back(
231
0
            "Reference dataset has a CRS, but input dataset has none.");
232
0
        return;
233
0
    }
234
235
0
    if (poRefCRS->IsSame(poInputCRS))
236
0
        return;
237
238
0
    const char *apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
239
0
    const auto poRefWKT = poRefCRS->exportToWkt(apszOptions);
240
0
    const auto poInputWKT = poInputCRS->exportToWkt(apszOptions);
241
0
    aosReport.push_back(
242
0
        "Reference and input CRS are not equivalent. Reference one is '" +
243
0
        poRefWKT + "'. Input one is '" + poInputWKT + "'");
244
0
}
245
246
/************************************************************************/
247
/*         GDALRasterCompareAlgorithm::GeotransformComparison()         */
248
/************************************************************************/
249
250
void GDALRasterCompareAlgorithm::GeoTransformComparison(
251
    std::vector<std::string> &aosReport, GDALDataset *poRefDS,
252
    GDALDataset *poInputDS)
253
0
{
254
0
    GDALGeoTransform refGT;
255
0
    CPLErr eErr1 = poRefDS->GetGeoTransform(refGT);
256
0
    GDALGeoTransform inputGT;
257
0
    CPLErr eErr2 = poInputDS->GetGeoTransform(inputGT);
258
0
    if (eErr1 == CE_Failure && eErr2 == CE_Failure)
259
0
        return;
260
261
0
    if (eErr1 == CE_Failure && eErr2 == CE_None)
262
0
    {
263
0
        aosReport.push_back(
264
0
            "Reference dataset has no geotransform, but input one has one.");
265
0
        return;
266
0
    }
267
268
0
    if (eErr1 == CE_None && eErr2 == CE_Failure)
269
0
    {
270
0
        aosReport.push_back(
271
0
            "Reference dataset has a geotransform, but input one has none.");
272
0
        return;
273
0
    }
274
275
0
    for (int i = 0; i < 6; ++i)
276
0
    {
277
0
        if ((refGT[i] != 0 &&
278
0
             std::fabs(refGT[i] - inputGT[i]) > 1e-10 * std::fabs(refGT[i])) ||
279
0
            (refGT[i] == 0 && std::fabs(refGT[i] - inputGT[i]) > 1e-10))
280
0
        {
281
0
            std::string s = "Geotransform of reference and input dataset are "
282
0
                            "not equivalent. Reference geotransform is (";
283
0
            for (int j = 0; j < 6; ++j)
284
0
            {
285
0
                if (j > 0)
286
0
                    s += ',';
287
0
                s += std::to_string(refGT[j]);
288
0
            }
289
0
            s += "). Input geotransform is (";
290
0
            for (int j = 0; j < 6; ++j)
291
0
            {
292
0
                if (j > 0)
293
0
                    s += ',';
294
0
                s += std::to_string(inputGT[j]);
295
0
            }
296
0
            s += ')';
297
0
            aosReport.push_back(std::move(s));
298
0
            return;
299
0
        }
300
0
    }
301
0
}
302
303
#if defined(__GNUC__) && !defined(__clang__)
304
#pragma GCC push_options
305
#pragma GCC optimize("O3")
306
#endif
307
308
/************************************************************************/
309
/*                                Diff()                                */
310
/************************************************************************/
311
312
template <class T> CPL_NOSANITIZE_UNSIGNED_INT_OVERFLOW static T Diff(T a, T b)
313
0
{
314
0
    return a - b;
315
0
}
Unexecuted instantiation: gdalalg_raster_compare.cpp:unsigned char Diff<unsigned char>(unsigned char, unsigned char)
Unexecuted instantiation: gdalalg_raster_compare.cpp:unsigned short Diff<unsigned short>(unsigned short, unsigned short)
Unexecuted instantiation: gdalalg_raster_compare.cpp:unsigned int Diff<unsigned int>(unsigned int, unsigned int)
Unexecuted instantiation: gdalalg_raster_compare.cpp:unsigned long Diff<unsigned long>(unsigned long, unsigned long)
Unexecuted instantiation: gdalalg_raster_compare.cpp:float Diff<float>(float, float)
Unexecuted instantiation: gdalalg_raster_compare.cpp:double Diff<double>(double, double)
316
317
/************************************************************************/
318
/*                           CompareVectors()                           */
319
/************************************************************************/
320
321
template <class T, class Tdiff, bool bIsComplex>
322
static void CompareVectors(size_t nValCount, const T *refValues,
323
                           const T *inputValues, uint64_t &countDiffPixels,
324
                           Tdiff &maxDiffValue)
325
0
{
326
0
    constexpr bool bIsFloatingPoint = std::is_floating_point_v<T>;
327
    if constexpr (bIsComplex)
328
0
    {
329
0
        for (size_t i = 0; i < nValCount; ++i)
330
0
        {
331
            if constexpr (bIsFloatingPoint)
332
0
            {
333
0
                static_assert(std::is_same_v<T, Tdiff>);
334
0
                if (std::isnan(refValues[2 * i]) &&
335
0
                    std::isnan(inputValues[2 * i]) &&
336
0
                    std::isnan(refValues[2 * i + 1]) &&
337
0
                    std::isnan(inputValues[2 * i + 1]))
338
0
                {
339
0
                    continue;
340
0
                }
341
0
            }
342
343
0
            if (refValues[2 * i] != inputValues[2 * i] ||
344
0
                refValues[2 * i + 1] != inputValues[2 * i + 1])
345
0
            {
346
0
                const Tdiff diff =
347
0
                    std::hypot(static_cast<Tdiff>(refValues[2 * i]) -
348
0
                                   static_cast<Tdiff>(inputValues[2 * i]),
349
0
                               static_cast<Tdiff>(refValues[2 * i + 1]) -
350
0
                                   static_cast<Tdiff>(inputValues[2 * i + 1]));
351
0
                ++countDiffPixels;
352
0
                if (diff > maxDiffValue)
353
0
                    maxDiffValue = diff;
354
0
            }
355
0
        }
356
    }
357
    else
358
0
    {
359
0
        static_assert(sizeof(Tdiff) == sizeof(T));
360
0
        size_t i = 0;
361
0
#ifdef USE_SSE2
362
        if constexpr (std::is_same_v<T, float>)
363
0
        {
364
0
            static_assert(std::is_same_v<T, Tdiff>);
365
366
0
            auto vMaxDiff = _mm_setzero_ps();
367
368
            // Mask for absolute value (clears the sign bit)
369
0
            const auto absMask = _mm_castsi128_ps(
370
0
                _mm_set1_epi32(std::numeric_limits<int32_t>::max()));
371
372
0
            constexpr size_t VALS_PER_REG = sizeof(vMaxDiff) / sizeof(T);
373
0
            while (i + VALS_PER_REG <= nValCount)
374
0
            {
375
0
                auto vCountDiff = _mm_setzero_si128();
376
377
                // We can do a maximum of std::numeric_limits<uint32_t>::max()
378
                // accumulations into vCountDiff
379
0
                const size_t nInnerLimit = [i, nValCount](size_t valsPerReg)
380
0
                {
381
                    if constexpr (sizeof(size_t) > sizeof(uint32_t))
382
0
                    {
383
0
                        return std::min(
384
0
                            nValCount - valsPerReg,
385
0
                            i + std::numeric_limits<uint32_t>::max() *
386
0
                                    valsPerReg);
387
                    }
388
                    else
389
                    {
390
                        return nValCount - valsPerReg;
391
                    }
392
0
                }(VALS_PER_REG);
393
394
0
                for (; i <= nInnerLimit; i += VALS_PER_REG)
395
0
                {
396
0
                    const auto a = _mm_loadu_ps(refValues + i);
397
0
                    const auto b = _mm_loadu_ps(inputValues + i);
398
399
                    // Compute absolute value of difference
400
0
                    const auto absDiff = _mm_and_ps(_mm_sub_ps(a, b), absMask);
401
402
                    // Update vMaxDiff
403
0
                    const auto aIsNan = _mm_cmpunord_ps(a, a);
404
0
                    const auto bIsNan = _mm_cmpunord_ps(b, b);
405
0
                    const auto valNotEqual = _mm_andnot_ps(
406
0
                        _mm_or_ps(aIsNan, bIsNan), _mm_cmpneq_ps(a, b));
407
0
                    vMaxDiff =
408
0
                        _mm_max_ps(vMaxDiff, _mm_and_ps(absDiff, valNotEqual));
409
410
                    // Update vCountDiff
411
0
                    const auto nanMisMatch = _mm_xor_ps(aIsNan, bIsNan);
412
                    // if nanMisMatch OR (both values not NaN and a != b)
413
0
                    const auto maskIsDiff = _mm_or_ps(nanMisMatch, valNotEqual);
414
0
                    const auto shiftedMaskDiff =
415
0
                        _mm_srli_epi32(_mm_castps_si128(maskIsDiff), 31);
416
0
                    vCountDiff = _mm_add_epi32(vCountDiff, shiftedMaskDiff);
417
0
                }
418
419
                // Horizontal add into countDiffPixels
420
0
                uint32_t anCountDiff[VALS_PER_REG];
421
0
                _mm_storeu_si128(reinterpret_cast<__m128i *>(anCountDiff),
422
0
                                 vCountDiff);
423
0
                for (size_t j = 0; j < VALS_PER_REG; ++j)
424
0
                {
425
0
                    countDiffPixels += anCountDiff[j];
426
0
                }
427
0
            }
428
429
            // Horizontal max into maxDiffValue
430
0
            float afMaxDiffValue[VALS_PER_REG];
431
0
            _mm_storeu_ps(afMaxDiffValue, vMaxDiff);
432
0
            for (size_t j = 0; j < VALS_PER_REG; ++j)
433
0
            {
434
0
                CPLAssert(!std::isnan(afMaxDiffValue[j]));
435
0
                maxDiffValue = std::max(maxDiffValue, afMaxDiffValue[j]);
436
0
            }
437
0
        }
438
0
#endif
439
        if constexpr (bIsFloatingPoint)
440
0
        {
441
0
            static_assert(std::is_same_v<T, Tdiff>);
442
0
            for (; i < nValCount; ++i)
443
0
            {
444
0
                if (std::isnan(refValues[i]))
445
0
                {
446
0
                    if (!std::isnan(inputValues[i]))
447
0
                    {
448
0
                        ++countDiffPixels;
449
0
                    }
450
0
                    continue;
451
0
                }
452
0
                else if (std::isnan(inputValues[i]))
453
0
                {
454
0
                    ++countDiffPixels;
455
0
                    continue;
456
0
                }
457
0
                else if (refValues[i] == inputValues[i])
458
0
                {
459
0
                    continue;
460
0
                }
461
462
0
                const Tdiff diff =
463
0
                    refValues[i] >= inputValues[i]
464
0
                        ? Diff(static_cast<Tdiff>(refValues[i]),
465
0
                               static_cast<Tdiff>(inputValues[i]))
466
0
                        : Diff(static_cast<Tdiff>(inputValues[i]),
467
0
                               static_cast<Tdiff>(refValues[i]));
468
0
                if (diff > 0)
469
0
                {
470
0
                    ++countDiffPixels;
471
0
                    if (diff > maxDiffValue)
472
0
                        maxDiffValue = diff;
473
0
                }
474
0
            }
475
        }
476
        else
477
0
        {
478
0
            static_assert(std::is_unsigned_v<Tdiff>);
479
0
            while (i < nValCount)
480
0
            {
481
                // Autovectorizer friendly inner loop (GCC, clang, ICX),
482
                // by making sure it increases countDiffLocal on the same size
483
                // as Tdiff.
484
485
0
                Tdiff countDiffLocal = 0;
486
0
                const size_t innerLimit = [i, nValCount]()
487
0
                {
488
                    if constexpr (sizeof(Tdiff) < sizeof(size_t))
489
0
                    {
490
0
                        return std::min(nValCount - 1,
491
0
                                        i + std::numeric_limits<Tdiff>::max());
492
                    }
493
                    else
494
0
                    {
495
0
                        (void)i;
496
0
                        return nValCount - 1;
497
0
                    }
498
0
                }();
Unexecuted instantiation: gdalalg_raster_compare.cpp:CompareVectors<unsigned char, unsigned char, false>(unsigned long, unsigned char const*, unsigned char const*, unsigned long&, unsigned char&)::{lambda()#1}::operator()() const
Unexecuted instantiation: gdalalg_raster_compare.cpp:CompareVectors<signed char, unsigned char, false>(unsigned long, signed char const*, signed char const*, unsigned long&, unsigned char&)::{lambda()#1}::operator()() const
Unexecuted instantiation: gdalalg_raster_compare.cpp:CompareVectors<unsigned short, unsigned short, false>(unsigned long, unsigned short const*, unsigned short const*, unsigned long&, unsigned short&)::{lambda()#1}::operator()() const
Unexecuted instantiation: gdalalg_raster_compare.cpp:CompareVectors<short, unsigned short, false>(unsigned long, short const*, short const*, unsigned long&, unsigned short&)::{lambda()#1}::operator()() const
Unexecuted instantiation: gdalalg_raster_compare.cpp:CompareVectors<unsigned int, unsigned int, false>(unsigned long, unsigned int const*, unsigned int const*, unsigned long&, unsigned int&)::{lambda()#1}::operator()() const
Unexecuted instantiation: gdalalg_raster_compare.cpp:CompareVectors<int, unsigned int, false>(unsigned long, int const*, int const*, unsigned long&, unsigned int&)::{lambda()#1}::operator()() const
Unexecuted instantiation: gdalalg_raster_compare.cpp:CompareVectors<unsigned long, unsigned long, false>(unsigned long, unsigned long const*, unsigned long const*, unsigned long&, unsigned long&)::{lambda()#1}::operator()() const
Unexecuted instantiation: gdalalg_raster_compare.cpp:CompareVectors<long, unsigned long, false>(unsigned long, long const*, long const*, unsigned long&, unsigned long&)::{lambda()#1}::operator()() const
499
0
                for (; i <= innerLimit; ++i)
500
0
                {
501
0
                    const Tdiff diff =
502
0
                        refValues[i] >= inputValues[i]
503
0
                            ? Diff(static_cast<Tdiff>(refValues[i]),
504
0
                                   static_cast<Tdiff>(inputValues[i]))
505
0
                            : Diff(static_cast<Tdiff>(inputValues[i]),
506
0
                                   static_cast<Tdiff>(refValues[i]));
507
0
                    countDiffLocal += (diff > 0);
508
0
                    maxDiffValue = std::max(maxDiffValue, diff);
509
0
                }
510
0
                countDiffPixels += countDiffLocal;
511
0
            }
512
0
        }
513
0
    }
514
0
}
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<unsigned char, unsigned char, false>(unsigned long, unsigned char const*, unsigned char const*, unsigned long&, unsigned char&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<signed char, unsigned char, false>(unsigned long, signed char const*, signed char const*, unsigned long&, unsigned char&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<unsigned short, unsigned short, false>(unsigned long, unsigned short const*, unsigned short const*, unsigned long&, unsigned short&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<short, unsigned short, false>(unsigned long, short const*, short const*, unsigned long&, unsigned short&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<unsigned int, unsigned int, false>(unsigned long, unsigned int const*, unsigned int const*, unsigned long&, unsigned int&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<int, unsigned int, false>(unsigned long, int const*, int const*, unsigned long&, unsigned int&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<unsigned long, unsigned long, false>(unsigned long, unsigned long const*, unsigned long const*, unsigned long&, unsigned long&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<long, unsigned long, false>(unsigned long, long const*, long const*, unsigned long&, unsigned long&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<float, float, false>(unsigned long, float const*, float const*, unsigned long&, float&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<double, double, false>(unsigned long, double const*, double const*, unsigned long&, double&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<short, float, true>(unsigned long, short const*, short const*, unsigned long&, float&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<int, double, true>(unsigned long, int const*, int const*, unsigned long&, double&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<float, float, true>(unsigned long, float const*, float const*, unsigned long&, float&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<double, double, true>(unsigned long, double const*, double const*, unsigned long&, double&)
515
516
/************************************************************************/
517
/*                       DatasetPixelComparison()                       */
518
/************************************************************************/
519
520
template <class T, class Tdiff, bool bIsComplex>
521
static void DatasetPixelComparison(std::vector<std::string> &aosReport,
522
                                   GDALDataset *poRefDS, GDALDataset *poInputDS,
523
                                   GDALDataType eReqDT,
524
                                   GDALProgressFunc pfnProgress,
525
                                   void *pProgressData)
526
0
{
527
0
    std::vector<T> refValues;
528
0
    std::vector<T> inputValues;
529
530
0
    CPLAssert(GDALDataTypeIsComplex(eReqDT) == bIsComplex);
531
532
0
    const uint64_t nTotalPixels =
533
0
        static_cast<uint64_t>(poRefDS->GetRasterXSize()) *
534
0
        poRefDS->GetRasterYSize();
535
0
    uint64_t nIterPixels = 0;
536
537
0
    constexpr int nValPerPixel = bIsComplex ? 2 : 1;
538
0
    const int nBands = poRefDS->GetRasterCount();
539
540
0
    std::vector<Tdiff> maxDiffValue(nBands, 0);
541
0
    std::vector<uint64_t> countDiffPixels(nBands, 0);
542
543
0
    size_t nMaxSize = 0;
544
0
    const GIntBig nUsableRAM = CPLGetUsablePhysicalRAM() / 10;
545
0
    if (nUsableRAM > 0)
546
0
        nMaxSize = static_cast<size_t>(nUsableRAM);
547
548
0
    for (const auto &window : GDALRasterBand::WindowIteratorWrapper(
549
0
             *(poRefDS->GetRasterBand(1)), *(poInputDS->GetRasterBand(1)),
550
0
             nMaxSize))
551
0
    {
552
0
        const size_t nValCount =
553
0
            static_cast<size_t>(window.nXSize) * window.nYSize;
554
0
        const size_t nArraySize = nValCount * nValPerPixel * nBands;
555
0
        try
556
0
        {
557
0
            if (refValues.size() < nArraySize)
558
0
            {
559
0
                refValues.resize(nArraySize);
560
0
                inputValues.resize(nArraySize);
561
0
            }
562
0
        }
563
0
        catch (const std::exception &)
564
0
        {
565
0
            CPLError(CE_Failure, CPLE_OutOfMemory,
566
0
                     "Out of memory allocating temporary arrays");
567
0
            aosReport.push_back("Out of memory allocating temporary arrays");
568
0
            return;
569
0
        }
570
571
0
        if (poRefDS->RasterIO(GF_Read, window.nXOff, window.nYOff,
572
0
                              window.nXSize, window.nYSize, refValues.data(),
573
0
                              window.nXSize, window.nYSize, eReqDT, nBands,
574
0
                              nullptr, 0, 0, 0, nullptr) == CE_None &&
575
0
            poInputDS->RasterIO(
576
0
                GF_Read, window.nXOff, window.nYOff, window.nXSize,
577
0
                window.nYSize, inputValues.data(), window.nXSize, window.nYSize,
578
0
                eReqDT, nBands, nullptr, 0, 0, 0, nullptr) == CE_None)
579
0
        {
580
0
            for (int i = 0; i < nBands; ++i)
581
0
            {
582
0
                CompareVectors<T, Tdiff, bIsComplex>(
583
0
                    nValCount, refValues.data() + i * nValCount * nValPerPixel,
584
0
                    inputValues.data() + i * nValCount * nValPerPixel,
585
0
                    countDiffPixels[i], maxDiffValue[i]);
586
0
            }
587
0
        }
588
0
        else
589
0
        {
590
0
            aosReport.push_back("I/O error when comparing pixel values");
591
0
        }
592
593
0
        if (pfnProgress)
594
0
        {
595
0
            nIterPixels += nValCount;
596
0
            if (!pfnProgress(static_cast<double>(nIterPixels) /
597
0
                                 static_cast<double>(nTotalPixels),
598
0
                             "", pProgressData))
599
0
            {
600
0
                CPLError(CE_Failure, CPLE_UserInterrupt, "Interrupted by user");
601
0
                break;
602
0
            }
603
0
        }
604
0
    }
605
0
    for (int i = 0; i < nBands; ++i)
606
0
    {
607
0
        if (countDiffPixels[i])
608
0
        {
609
0
            aosReport.push_back(
610
0
                "Band " + std::to_string(i + 1) +
611
0
                ": pixels differing: " + std::to_string(countDiffPixels[i]));
612
0
            aosReport.push_back("Band " + std::to_string(i + 1) +
613
0
                                ": maximum pixel value difference: " +
614
0
                                std::to_string(maxDiffValue[i]));
615
0
        }
616
0
    }
617
0
}
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<unsigned char, unsigned char, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<signed char, unsigned char, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<unsigned short, unsigned short, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<short, unsigned short, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<unsigned int, unsigned int, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<int, unsigned int, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<unsigned long, unsigned long, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<long, unsigned long, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<float, float, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<double, double, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<short, float, true>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<int, double, true>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<float, float, true>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<double, double, true>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
618
619
/************************************************************************/
620
/*           GDALRasterCompareAlgorithm::DatasetComparison()            */
621
/************************************************************************/
622
623
void GDALRasterCompareAlgorithm::DatasetComparison(
624
    std::vector<std::string> &aosReport, GDALDataset *poRefDS,
625
    GDALDataset *poInputDS, GDALProgressFunc pfnProgress, void *pProgressData)
626
0
{
627
0
    if (!m_skipCRS)
628
0
    {
629
0
        CRSComparison(aosReport, poRefDS, poInputDS);
630
0
    }
631
632
0
    if (!m_skipGeotransform)
633
0
    {
634
0
        GeoTransformComparison(aosReport, poRefDS, poInputDS);
635
0
    }
636
637
0
    bool ret = true;
638
0
    if (poRefDS->GetRasterCount() != poInputDS->GetRasterCount())
639
0
    {
640
0
        aosReport.push_back("Reference dataset has " +
641
0
                            std::to_string(poRefDS->GetRasterCount()) +
642
0
                            " band(s), but input dataset has " +
643
0
                            std::to_string(poInputDS->GetRasterCount()));
644
0
        ret = false;
645
0
    }
646
647
0
    if (poRefDS->GetRasterXSize() != poInputDS->GetRasterXSize())
648
0
    {
649
0
        aosReport.push_back("Reference dataset width is " +
650
0
                            std::to_string(poRefDS->GetRasterXSize()) +
651
0
                            ", but input dataset width is " +
652
0
                            std::to_string(poInputDS->GetRasterXSize()));
653
0
        ret = false;
654
0
    }
655
656
0
    if (poRefDS->GetRasterYSize() != poInputDS->GetRasterYSize())
657
0
    {
658
0
        aosReport.push_back("Reference dataset height is " +
659
0
                            std::to_string(poRefDS->GetRasterYSize()) +
660
0
                            ", but input dataset height is " +
661
0
                            std::to_string(poInputDS->GetRasterYSize()));
662
0
        ret = false;
663
0
    }
664
665
0
    if (!m_skipMetadata)
666
0
    {
667
0
        MetadataComparison(aosReport, "(dataset default metadata domain)",
668
0
                           poRefDS->GetMetadata(), poInputDS->GetMetadata());
669
0
    }
670
671
0
    if (!m_skipRPC)
672
0
    {
673
0
        MetadataComparison(aosReport, "RPC", poRefDS->GetMetadata("RPC"),
674
0
                           poInputDS->GetMetadata("RPC"));
675
0
    }
676
677
0
    if (!m_skipGeolocation)
678
0
    {
679
0
        MetadataComparison(aosReport, "GEOLOCATION",
680
0
                           poRefDS->GetMetadata("GEOLOCATION"),
681
0
                           poInputDS->GetMetadata("GEOLOCATION"));
682
0
    }
683
684
0
    if (!ret)
685
0
        return;
686
687
0
    const int nBands = poRefDS->GetRasterCount();
688
689
0
    bool doBandBasedPixelComparison = true;
690
    // Do not do band-by-band pixel difference if there are too many interleaved
691
    // bands as this could be extremely slow
692
0
    if (nBands > 10)
693
0
    {
694
0
        const char *pszRefInterleave =
695
0
            poRefDS->GetMetadataItem("INTERLEAVE", "IMAGE_STRUCTURE");
696
0
        const char *pszInputInterleave =
697
0
            poInputDS->GetMetadataItem("INTERLEAVE", "IMAGE_STRUCTURE");
698
0
        if ((pszRefInterleave && EQUAL(pszRefInterleave, "PIXEL")) ||
699
0
            (pszInputInterleave && EQUAL(pszInputInterleave, "PIXEL")))
700
0
        {
701
0
            doBandBasedPixelComparison = false;
702
0
        }
703
0
    }
704
705
0
    for (int i = 0; i < nBands; ++i)
706
0
    {
707
0
        void *pScaledProgress = GDALCreateScaledProgress(
708
0
            static_cast<double>(i) / nBands,
709
0
            static_cast<double>(i + 1) / nBands, pfnProgress, pProgressData);
710
0
        BandComparison(
711
0
            aosReport, std::to_string(i + 1), doBandBasedPixelComparison,
712
0
            poRefDS->GetRasterBand(i + 1), poInputDS->GetRasterBand(i + 1),
713
0
            pScaledProgress ? GDALScaledProgress : nullptr, pScaledProgress);
714
0
        GDALDestroyScaledProgress(pScaledProgress);
715
0
    }
716
717
0
    if (!doBandBasedPixelComparison)
718
0
    {
719
0
        const auto eReqDT =
720
0
            GDALDataTypeUnion(poRefDS->GetRasterBand(1)->GetRasterDataType(),
721
0
                              poInputDS->GetRasterBand(1)->GetRasterDataType());
722
0
        switch (eReqDT)
723
0
        {
724
0
            case GDT_UInt8:
725
0
                DatasetPixelComparison<uint8_t, uint8_t, false>(
726
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
727
0
                    pProgressData);
728
0
                break;
729
0
            case GDT_Int8:
730
0
                DatasetPixelComparison<int8_t, uint8_t, false>(
731
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
732
0
                    pProgressData);
733
0
                break;
734
0
            case GDT_UInt16:
735
0
                DatasetPixelComparison<uint16_t, uint16_t, false>(
736
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
737
0
                    pProgressData);
738
0
                break;
739
0
            case GDT_Int16:
740
0
                DatasetPixelComparison<int16_t, uint16_t, false>(
741
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
742
0
                    pProgressData);
743
0
                break;
744
0
            case GDT_UInt32:
745
0
                DatasetPixelComparison<uint32_t, uint32_t, false>(
746
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
747
0
                    pProgressData);
748
0
                break;
749
0
            case GDT_Int32:
750
0
                DatasetPixelComparison<int32_t, uint32_t, false>(
751
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
752
0
                    pProgressData);
753
0
                break;
754
0
            case GDT_UInt64:
755
0
                DatasetPixelComparison<uint64_t, uint64_t, false>(
756
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
757
0
                    pProgressData);
758
0
                break;
759
0
            case GDT_Int64:
760
0
                DatasetPixelComparison<int64_t, uint64_t, false>(
761
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
762
0
                    pProgressData);
763
0
                break;
764
0
            case GDT_Float16:
765
0
            case GDT_Float32:
766
0
                DatasetPixelComparison<float, float, false>(
767
0
                    aosReport, poRefDS, poInputDS, GDT_Float32, pfnProgress,
768
0
                    pProgressData);
769
0
                break;
770
0
            case GDT_Float64:
771
0
                DatasetPixelComparison<double, double, false>(
772
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
773
0
                    pProgressData);
774
0
                break;
775
0
            case GDT_CInt16:
776
0
                DatasetPixelComparison<int16_t, float, true>(
777
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
778
0
                    pProgressData);
779
0
                break;
780
0
            case GDT_CInt32:
781
0
                DatasetPixelComparison<int32_t, double, true>(
782
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
783
0
                    pProgressData);
784
0
                break;
785
0
            case GDT_CFloat16:
786
0
            case GDT_CFloat32:
787
0
                DatasetPixelComparison<float, float, true>(
788
0
                    aosReport, poRefDS, poInputDS, GDT_CFloat32, pfnProgress,
789
0
                    pProgressData);
790
0
                break;
791
0
            case GDT_CFloat64:
792
0
                DatasetPixelComparison<double, double, true>(
793
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
794
0
                    pProgressData);
795
0
                break;
796
0
            case GDT_Unknown:
797
0
            case GDT_TypeCount:
798
0
                break;
799
0
        }
800
0
    }
801
0
}
802
803
/************************************************************************/
804
/*                           ComparePixels()                            */
805
/************************************************************************/
806
807
template <class T, class Tdiff, bool bIsComplex>
808
static void ComparePixels(std::vector<std::string> &aosReport,
809
                          const std::string &bandId, GDALRasterBand *poRefBand,
810
                          GDALRasterBand *poInputBand, GDALDataType eReqDT,
811
                          GDALProgressFunc pfnProgress, void *pProgressData)
812
0
{
813
0
    std::vector<T> refValues;
814
0
    std::vector<T> inputValues;
815
0
    Tdiff maxDiffValue = 0;
816
0
    uint64_t countDiffPixels = 0;
817
818
0
    CPLAssert(GDALDataTypeIsComplex(eReqDT) == bIsComplex);
819
0
    const uint64_t nTotalPixels =
820
0
        static_cast<uint64_t>(poRefBand->GetXSize()) * poRefBand->GetYSize();
821
0
    uint64_t nIterPixels = 0;
822
823
0
    constexpr int nValPerPixel = bIsComplex ? 2 : 1;
824
825
0
    size_t nMaxSize = 0;
826
0
    const GIntBig nUsableRAM = CPLGetUsablePhysicalRAM() / 10;
827
0
    if (nUsableRAM > 0)
828
0
        nMaxSize = static_cast<size_t>(nUsableRAM);
829
830
0
    for (const auto &window : GDALRasterBand::WindowIteratorWrapper(
831
0
             *poRefBand, *poInputBand, nMaxSize))
832
0
    {
833
0
        const size_t nValCount =
834
0
            static_cast<size_t>(window.nXSize) * window.nYSize;
835
0
        const size_t nArraySize = nValCount * nValPerPixel;
836
0
        try
837
0
        {
838
0
            if (refValues.size() < nArraySize)
839
0
            {
840
0
                refValues.resize(nArraySize);
841
0
                inputValues.resize(nArraySize);
842
0
            }
843
0
        }
844
0
        catch (const std::exception &)
845
0
        {
846
0
            CPLError(CE_Failure, CPLE_OutOfMemory,
847
0
                     "Out of memory allocating temporary arrays");
848
0
            aosReport.push_back("Out of memory allocating temporary arrays");
849
0
            return;
850
0
        }
851
852
0
        if (poRefBand->RasterIO(GF_Read, window.nXOff, window.nYOff,
853
0
                                window.nXSize, window.nYSize, refValues.data(),
854
0
                                window.nXSize, window.nYSize, eReqDT, 0, 0,
855
0
                                nullptr) == CE_None &&
856
0
            poInputBand->RasterIO(
857
0
                GF_Read, window.nXOff, window.nYOff, window.nXSize,
858
0
                window.nYSize, inputValues.data(), window.nXSize, window.nYSize,
859
0
                eReqDT, 0, 0, nullptr) == CE_None)
860
0
        {
861
0
            CompareVectors<T, Tdiff, bIsComplex>(nValCount, refValues.data(),
862
0
                                                 inputValues.data(),
863
0
                                                 countDiffPixels, maxDiffValue);
864
0
        }
865
0
        else
866
0
        {
867
0
            aosReport.push_back("I/O error when comparing pixel values");
868
0
        }
869
870
0
        if (pfnProgress)
871
0
        {
872
0
            nIterPixels += nValCount;
873
0
            if (!pfnProgress(static_cast<double>(nIterPixels) /
874
0
                                 static_cast<double>(nTotalPixels),
875
0
                             "", pProgressData))
876
0
            {
877
0
                CPLError(CE_Failure, CPLE_UserInterrupt, "Interrupted by user");
878
0
                break;
879
0
            }
880
0
        }
881
0
    }
882
0
    if (countDiffPixels)
883
0
    {
884
0
        aosReport.push_back(
885
0
            bandId + ": pixels differing: " + std::to_string(countDiffPixels));
886
887
0
        std::string reportMessage(bandId);
888
0
        reportMessage += ": maximum pixel value difference: ";
889
        if constexpr (std::is_floating_point_v<T>)
890
0
        {
891
0
            if (std::isinf(maxDiffValue))
892
0
                reportMessage += "inf";
893
0
            else if (std::isnan(maxDiffValue))
894
0
                reportMessage += "nan";
895
0
            else
896
0
                reportMessage += std::to_string(maxDiffValue);
897
        }
898
        else
899
0
        {
900
0
            reportMessage += std::to_string(maxDiffValue);
901
0
        }
902
0
        aosReport.push_back(std::move(reportMessage));
903
0
    }
904
0
}
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<unsigned char, unsigned char, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<signed char, unsigned char, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<unsigned short, unsigned short, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<short, unsigned short, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<unsigned int, unsigned int, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<int, unsigned int, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<unsigned long, unsigned long, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<long, unsigned long, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<float, float, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<double, double, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<short, float, true>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<int, double, true>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<float, float, true>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<double, double, true>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
905
906
/************************************************************************/
907
/*                           ComparePixels()                            */
908
/************************************************************************/
909
910
static void ComparePixels(std::vector<std::string> &aosReport,
911
                          const std::string &bandId, GDALRasterBand *poRefBand,
912
                          GDALRasterBand *poInputBand,
913
                          GDALProgressFunc pfnProgress, void *pProgressData)
914
0
{
915
0
    const auto eReqDT = GDALDataTypeUnion(poRefBand->GetRasterDataType(),
916
0
                                          poInputBand->GetRasterDataType());
917
0
    switch (eReqDT)
918
0
    {
919
0
        case GDT_UInt8:
920
0
            ComparePixels<uint8_t, uint8_t, false>(aosReport, bandId, poRefBand,
921
0
                                                   poInputBand, eReqDT,
922
0
                                                   pfnProgress, pProgressData);
923
0
            break;
924
0
        case GDT_Int8:
925
0
            ComparePixels<int8_t, uint8_t, false>(aosReport, bandId, poRefBand,
926
0
                                                  poInputBand, eReqDT,
927
0
                                                  pfnProgress, pProgressData);
928
0
            break;
929
0
        case GDT_UInt16:
930
0
            ComparePixels<uint16_t, uint16_t, false>(
931
0
                aosReport, bandId, poRefBand, poInputBand, eReqDT, pfnProgress,
932
0
                pProgressData);
933
0
            break;
934
0
        case GDT_Int16:
935
0
            ComparePixels<int16_t, uint16_t, false>(
936
0
                aosReport, bandId, poRefBand, poInputBand, eReqDT, pfnProgress,
937
0
                pProgressData);
938
0
            break;
939
0
        case GDT_UInt32:
940
0
            ComparePixels<uint32_t, uint32_t, false>(
941
0
                aosReport, bandId, poRefBand, poInputBand, eReqDT, pfnProgress,
942
0
                pProgressData);
943
0
            break;
944
0
        case GDT_Int32:
945
0
            ComparePixels<int32_t, uint32_t, false>(
946
0
                aosReport, bandId, poRefBand, poInputBand, eReqDT, pfnProgress,
947
0
                pProgressData);
948
0
            break;
949
0
        case GDT_UInt64:
950
0
            ComparePixels<uint64_t, uint64_t, false>(
951
0
                aosReport, bandId, poRefBand, poInputBand, eReqDT, pfnProgress,
952
0
                pProgressData);
953
0
            break;
954
0
        case GDT_Int64:
955
0
            ComparePixels<int64_t, uint64_t, false>(
956
0
                aosReport, bandId, poRefBand, poInputBand, eReqDT, pfnProgress,
957
0
                pProgressData);
958
0
            break;
959
0
        case GDT_Float16:
960
0
        case GDT_Float32:
961
0
            ComparePixels<float, float, false>(aosReport, bandId, poRefBand,
962
0
                                               poInputBand, GDT_Float32,
963
0
                                               pfnProgress, pProgressData);
964
0
            break;
965
0
        case GDT_Float64:
966
0
            ComparePixels<double, double, false>(aosReport, bandId, poRefBand,
967
0
                                                 poInputBand, eReqDT,
968
0
                                                 pfnProgress, pProgressData);
969
0
            break;
970
0
        case GDT_CInt16:
971
0
            ComparePixels<int16_t, float, true>(aosReport, bandId, poRefBand,
972
0
                                                poInputBand, eReqDT,
973
0
                                                pfnProgress, pProgressData);
974
0
            break;
975
0
        case GDT_CInt32:
976
0
            ComparePixels<int32_t, double, true>(aosReport, bandId, poRefBand,
977
0
                                                 poInputBand, eReqDT,
978
0
                                                 pfnProgress, pProgressData);
979
0
            break;
980
0
        case GDT_CFloat16:
981
0
        case GDT_CFloat32:
982
0
            ComparePixels<float, float, true>(aosReport, bandId, poRefBand,
983
0
                                              poInputBand, GDT_CFloat32,
984
0
                                              pfnProgress, pProgressData);
985
0
            break;
986
0
        case GDT_CFloat64:
987
0
            ComparePixels<double, double, true>(aosReport, bandId, poRefBand,
988
0
                                                poInputBand, eReqDT,
989
0
                                                pfnProgress, pProgressData);
990
0
            break;
991
0
        case GDT_Unknown:
992
0
        case GDT_TypeCount:
993
0
            break;
994
0
    }
995
0
}
996
997
#if defined(__GNUC__) && !defined(__clang__)
998
#pragma GCC pop_options
999
#endif
1000
1001
/************************************************************************/
1002
/*             GDALRasterCompareAlgorithm::BandComparison()             */
1003
/************************************************************************/
1004
1005
void GDALRasterCompareAlgorithm::BandComparison(
1006
    std::vector<std::string> &aosReport, const std::string &bandId,
1007
    bool doBandBasedPixelComparison, GDALRasterBand *poRefBand,
1008
    GDALRasterBand *poInputBand, GDALProgressFunc pfnProgress,
1009
    void *pProgressData)
1010
0
{
1011
0
    bool ret = true;
1012
1013
0
    if (poRefBand->GetXSize() != poInputBand->GetXSize())
1014
0
    {
1015
0
        aosReport.push_back("Reference band width is " +
1016
0
                            std::to_string(poRefBand->GetXSize()) +
1017
0
                            ", but input band width is " +
1018
0
                            std::to_string(poInputBand->GetXSize()));
1019
0
        ret = false;
1020
0
    }
1021
1022
0
    if (poRefBand->GetYSize() != poInputBand->GetYSize())
1023
0
    {
1024
0
        aosReport.push_back("Reference band height is " +
1025
0
                            std::to_string(poRefBand->GetYSize()) +
1026
0
                            ", but input band height is " +
1027
0
                            std::to_string(poInputBand->GetYSize()));
1028
0
        ret = false;
1029
0
    }
1030
1031
0
    if (strcmp(poRefBand->GetDescription(), poInputBand->GetDescription()) != 0)
1032
0
    {
1033
0
        aosReport.push_back("Reference band " + bandId + " has description " +
1034
0
                            std::string(poRefBand->GetDescription()) +
1035
0
                            ", but input band has description " +
1036
0
                            std::string(poInputBand->GetDescription()));
1037
0
    }
1038
1039
0
    if (poRefBand->GetRasterDataType() != poInputBand->GetRasterDataType())
1040
0
    {
1041
0
        aosReport.push_back(
1042
0
            "Reference band " + bandId + " has data type " +
1043
0
            std::string(GDALGetDataTypeName(poRefBand->GetRasterDataType())) +
1044
0
            ", but input band has data type " +
1045
0
            std::string(GDALGetDataTypeName(poInputBand->GetRasterDataType())));
1046
0
    }
1047
1048
0
    int bRefHasNoData = false;
1049
0
    const double dfRefNoData = poRefBand->GetNoDataValue(&bRefHasNoData);
1050
0
    int bInputHasNoData = false;
1051
0
    const double dfInputNoData = poInputBand->GetNoDataValue(&bInputHasNoData);
1052
0
    if (!bRefHasNoData && !bInputHasNoData)
1053
0
    {
1054
        // ok
1055
0
    }
1056
0
    else if (bRefHasNoData && !bInputHasNoData)
1057
0
    {
1058
0
        aosReport.push_back("Reference band " + bandId + " has nodata value " +
1059
0
                            std::to_string(dfRefNoData) +
1060
0
                            ", but input band has none.");
1061
0
    }
1062
0
    else if (!bRefHasNoData && bInputHasNoData)
1063
0
    {
1064
0
        aosReport.push_back("Reference band " + bandId +
1065
0
                            " has no nodata value, " +
1066
0
                            "but input band has no data value " +
1067
0
                            std::to_string(dfInputNoData) + ".");
1068
0
    }
1069
0
    else if ((std::isnan(dfRefNoData) && std::isnan(dfInputNoData)) ||
1070
0
             dfRefNoData == dfInputNoData)
1071
0
    {
1072
        // ok
1073
0
    }
1074
0
    else
1075
0
    {
1076
0
        aosReport.push_back("Reference band " + bandId + " has nodata value " +
1077
0
                            std::to_string(dfRefNoData) +
1078
0
                            ", but input band has no data value " +
1079
0
                            std::to_string(dfInputNoData) + ".");
1080
0
    }
1081
1082
0
    if (poRefBand->GetColorInterpretation() !=
1083
0
        poInputBand->GetColorInterpretation())
1084
0
    {
1085
0
        aosReport.push_back("Reference band " + bandId +
1086
0
                            " has color interpretation " +
1087
0
                            std::string(GDALGetColorInterpretationName(
1088
0
                                poRefBand->GetColorInterpretation())) +
1089
0
                            ", but input band has color interpretation " +
1090
0
                            std::string(GDALGetColorInterpretationName(
1091
0
                                poInputBand->GetColorInterpretation())));
1092
0
    }
1093
1094
0
    if (!ret)
1095
0
        return;
1096
1097
0
    const uint64_t nBasePixels =
1098
0
        static_cast<uint64_t>(poRefBand->GetXSize()) * poRefBand->GetYSize();
1099
0
    uint64_t nTotalPixels = nBasePixels;
1100
0
    const int nOvrCount = poRefBand->GetOverviewCount();
1101
0
    for (int i = 0; i < nOvrCount; ++i)
1102
0
    {
1103
0
        auto poOvrBand = poRefBand->GetOverview(i);
1104
0
        const uint64_t nOvrPixels =
1105
0
            static_cast<uint64_t>(poOvrBand->GetXSize()) *
1106
0
            poOvrBand->GetYSize();
1107
0
        nTotalPixels += nOvrPixels;
1108
0
    }
1109
1110
0
    if (doBandBasedPixelComparison)
1111
0
    {
1112
0
        void *pScaledProgress =
1113
0
            GDALCreateScaledProgress(0.0,
1114
0
                                     static_cast<double>(nBasePixels) /
1115
0
                                         static_cast<double>(nTotalPixels),
1116
0
                                     pfnProgress, pProgressData);
1117
0
        ComparePixels(aosReport, bandId, poRefBand, poInputBand,
1118
0
                      pScaledProgress ? GDALScaledProgress : nullptr,
1119
0
                      pScaledProgress);
1120
0
        GDALDestroyScaledProgress(pScaledProgress);
1121
0
    }
1122
1123
0
    if (!m_skipOverview)
1124
0
    {
1125
0
        if (nOvrCount != poInputBand->GetOverviewCount())
1126
0
        {
1127
0
            aosReport.push_back(
1128
0
                "Reference band " + bandId + " has " +
1129
0
                std::to_string(nOvrCount) +
1130
0
                " overview band(s), but input band has " +
1131
0
                std::to_string(poInputBand->GetOverviewCount()));
1132
0
        }
1133
0
        else
1134
0
        {
1135
0
            uint64_t nIterPixels = nBasePixels;
1136
1137
0
            for (int i = 0; i < nOvrCount; ++i)
1138
0
            {
1139
0
                GDALRasterBand *poOvrBand = poRefBand->GetOverview(i);
1140
0
                const uint64_t nOvrPixels =
1141
0
                    static_cast<uint64_t>(poOvrBand->GetXSize()) *
1142
0
                    poOvrBand->GetYSize();
1143
0
                void *pScaledProgress = GDALCreateScaledProgress(
1144
0
                    static_cast<double>(nIterPixels) /
1145
0
                        static_cast<double>(nTotalPixels),
1146
0
                    static_cast<double>(nIterPixels + nOvrPixels) /
1147
0
                        static_cast<double>(nTotalPixels),
1148
0
                    pfnProgress, pProgressData);
1149
0
                BandComparison(aosReport, "overview of band " + bandId,
1150
0
                               doBandBasedPixelComparison, poOvrBand,
1151
0
                               poInputBand->GetOverview(i),
1152
0
                               pScaledProgress ? GDALScaledProgress : nullptr,
1153
0
                               pScaledProgress);
1154
0
                GDALDestroyScaledProgress(pScaledProgress);
1155
0
                nIterPixels += nOvrPixels;
1156
0
            }
1157
0
        }
1158
0
    }
1159
1160
0
    if (poRefBand->GetMaskFlags() != poInputBand->GetMaskFlags())
1161
0
    {
1162
0
        aosReport.push_back("Reference band " + bandId + " has mask flags = " +
1163
0
                            std::to_string(poRefBand->GetMaskFlags()) +
1164
0
                            " , but input band has mask flags = " +
1165
0
                            std::to_string(poInputBand->GetMaskFlags()));
1166
0
    }
1167
0
    else if (poRefBand->GetMaskFlags() == GMF_PER_DATASET)
1168
0
    {
1169
0
        BandComparison(aosReport, "mask of band " + bandId, true,
1170
0
                       poRefBand->GetMaskBand(), poInputBand->GetMaskBand(),
1171
0
                       nullptr, nullptr);
1172
0
    }
1173
1174
0
    if (!m_skipMetadata)
1175
0
    {
1176
0
        MetadataComparison(aosReport, "(band default metadata domain)",
1177
0
                           poRefBand->GetMetadata(),
1178
0
                           poInputBand->GetMetadata());
1179
0
    }
1180
0
}
1181
1182
/************************************************************************/
1183
/*           GDALRasterCompareAlgorithm::MetadataComparison()           */
1184
/************************************************************************/
1185
1186
void GDALRasterCompareAlgorithm::MetadataComparison(
1187
    std::vector<std::string> &aosReport, const std::string &metadataDomain,
1188
    CSLConstList aosRef, CSLConstList aosInput)
1189
0
{
1190
0
    std::map<std::string, std::string> oMapRef;
1191
0
    std::map<std::string, std::string> oMapInput;
1192
1193
0
    std::array<const char *, 3> ignoredKeys = {
1194
0
        "backend",   // from gdalcompare.py. Not sure why
1195
0
        "ERR_BIAS",  // RPC optional key
1196
0
        "ERR_RAND",  // RPC optional key
1197
0
    };
1198
1199
0
    for (const auto &[key, value] : cpl::IterateNameValue(aosRef))
1200
0
    {
1201
0
        const char *pszKey = key;
1202
0
        const auto eq = [pszKey](const char *s)
1203
0
        { return strcmp(pszKey, s) == 0; };
1204
0
        auto it = std::find_if(ignoredKeys.begin(), ignoredKeys.end(), eq);
1205
0
        if (it == ignoredKeys.end())
1206
0
        {
1207
0
            oMapRef[key] = value;
1208
0
        }
1209
0
    }
1210
1211
0
    for (const auto &[key, value] : cpl::IterateNameValue(aosInput))
1212
0
    {
1213
0
        const char *pszKey = key;
1214
0
        const auto eq = [pszKey](const char *s)
1215
0
        { return strcmp(pszKey, s) == 0; };
1216
0
        auto it = std::find_if(ignoredKeys.begin(), ignoredKeys.end(), eq);
1217
0
        if (it == ignoredKeys.end())
1218
0
        {
1219
0
            oMapInput[key] = value;
1220
0
        }
1221
0
    }
1222
1223
0
    const auto strip = [](const std::string &s)
1224
0
    {
1225
0
        const auto posBegin = s.find_first_not_of(' ');
1226
0
        if (posBegin == std::string::npos)
1227
0
            return std::string();
1228
0
        const auto posEnd = s.find_last_not_of(' ');
1229
0
        return s.substr(posBegin, posEnd - posBegin + 1);
1230
0
    };
1231
1232
0
    for (const auto &sKeyValuePair : oMapRef)
1233
0
    {
1234
0
        const auto oIter = oMapInput.find(sKeyValuePair.first);
1235
0
        if (oIter == oMapInput.end())
1236
0
        {
1237
0
            aosReport.push_back("Reference metadata " + metadataDomain +
1238
0
                                " contains key '" + sKeyValuePair.first +
1239
0
                                "' but input metadata does not.");
1240
0
        }
1241
0
        else
1242
0
        {
1243
            // this will always have the current date set
1244
0
            if (sKeyValuePair.first == "NITF_FDT")
1245
0
                continue;
1246
1247
0
            std::string ref = oIter->second;
1248
0
            std::string input = sKeyValuePair.second;
1249
0
            if (metadataDomain == "RPC")
1250
0
            {
1251
                // _RPC.TXT files and in-file have a difference
1252
                // in white space that is not otherwise meaningful.
1253
0
                ref = strip(ref);
1254
0
                input = strip(input);
1255
0
            }
1256
0
            if (ref != input)
1257
0
            {
1258
0
                aosReport.push_back(
1259
0
                    "Reference metadata " + metadataDomain + " has value '" +
1260
0
                    ref + "' for key '" + sKeyValuePair.first +
1261
0
                    "' but input metadata has value '" + input + "'.");
1262
0
            }
1263
0
        }
1264
0
    }
1265
1266
0
    for (const auto &sKeyValuePair : oMapInput)
1267
0
    {
1268
0
        if (!cpl::contains(oMapRef, sKeyValuePair.first))
1269
0
        {
1270
0
            aosReport.push_back("Input metadata " + metadataDomain +
1271
0
                                " contains key '" + sKeyValuePair.first +
1272
0
                                "' but reference metadata does not.");
1273
0
        }
1274
0
    }
1275
0
}
1276
1277
/************************************************************************/
1278
/*                GDALRasterCompareAlgorithm::RunStep()                 */
1279
/************************************************************************/
1280
1281
bool GDALRasterCompareAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
1282
0
{
1283
0
    auto poRefDS = m_referenceDataset.GetDatasetRef();
1284
0
    CPLAssert(poRefDS);
1285
1286
0
    CPLAssert(m_inputDataset.size() == 1);
1287
0
    auto poInputDS = m_inputDataset[0].GetDatasetRef();
1288
0
    CPLAssert(poInputDS);
1289
1290
0
    if (m_skipAllOptional)
1291
0
    {
1292
0
        m_skipBinary = true;
1293
0
        m_skipCRS = true;
1294
0
        m_skipGeotransform = true;
1295
0
        m_skipOverview = true;
1296
0
        m_skipMetadata = true;
1297
0
        m_skipRPC = true;
1298
0
        m_skipGeolocation = true;
1299
0
        m_skipSubdataset = true;
1300
0
    }
1301
1302
0
    std::vector<std::string> aosReport;
1303
1304
0
    if (!m_skipBinary)
1305
0
    {
1306
0
        if (BinaryComparison(aosReport, poRefDS, poInputDS))
1307
0
        {
1308
0
            return true;
1309
0
        }
1310
0
    }
1311
1312
0
    CSLConstList papszSubDSRef =
1313
0
        m_skipSubdataset ? nullptr : poRefDS->GetMetadata("SUBDATASETS");
1314
0
    const int nCountRef = CSLCount(papszSubDSRef) / 2;
1315
0
    CSLConstList papszSubDSInput =
1316
0
        m_skipSubdataset ? nullptr : poInputDS->GetMetadata("SUBDATASETS");
1317
0
    const int nCountInput = CSLCount(papszSubDSInput) / 2;
1318
1319
0
    if (!m_skipSubdataset)
1320
0
    {
1321
0
        if (nCountRef != nCountInput)
1322
0
        {
1323
0
            aosReport.push_back("Reference dataset has " +
1324
0
                                std::to_string(nCountRef) +
1325
0
                                " subdataset(s) whereas input dataset has " +
1326
0
                                std::to_string(nCountInput) + " one(s).");
1327
0
            m_skipSubdataset = true;
1328
0
        }
1329
0
    }
1330
1331
    // Compute total number of pixels, including in subdatasets
1332
0
    const uint64_t nBasePixels =
1333
0
        static_cast<uint64_t>(poRefDS->GetRasterXSize()) *
1334
0
        poRefDS->GetRasterYSize() * poRefDS->GetRasterCount();
1335
0
    uint64_t nTotalPixels = nBasePixels;
1336
0
    if (ctxt.m_pfnProgress && !m_skipSubdataset)
1337
0
    {
1338
0
        for (int i = 0; i < nCountRef; ++i)
1339
0
        {
1340
0
            const char *pszRef = CSLFetchNameValue(
1341
0
                papszSubDSRef, CPLSPrintf("SUBDATASET_%d_NAME", i + 1));
1342
0
            const char *pszInput = CSLFetchNameValue(
1343
0
                papszSubDSInput, CPLSPrintf("SUBDATASET_%d_NAME", i + 1));
1344
0
            if (pszRef && pszInput)
1345
0
            {
1346
0
                auto poSubRef = std::unique_ptr<GDALDataset>(
1347
0
                    GDALDataset::Open(pszRef, GDAL_OF_RASTER));
1348
0
                auto poSubInput = std::unique_ptr<GDALDataset>(
1349
0
                    GDALDataset::Open(pszInput, GDAL_OF_RASTER));
1350
0
                if (poSubRef && poSubInput)
1351
0
                {
1352
0
                    const uint64_t nSubDSPixels =
1353
0
                        static_cast<uint64_t>(poSubRef->GetRasterXSize()) *
1354
0
                        poSubRef->GetRasterYSize() * poSubRef->GetRasterCount();
1355
0
                    nTotalPixels += nSubDSPixels;
1356
0
                }
1357
0
            }
1358
0
        }
1359
0
    }
1360
1361
0
    {
1362
0
        void *pScaledProgress =
1363
0
            GDALCreateScaledProgress(0.0,
1364
0
                                     static_cast<double>(nBasePixels) /
1365
0
                                         static_cast<double>(nTotalPixels),
1366
0
                                     ctxt.m_pfnProgress, ctxt.m_pProgressData);
1367
0
        DatasetComparison(aosReport, poRefDS, poInputDS,
1368
0
                          pScaledProgress ? GDALScaledProgress : nullptr,
1369
0
                          pScaledProgress);
1370
0
        GDALDestroyScaledProgress(pScaledProgress);
1371
0
    }
1372
1373
0
    if (!m_skipSubdataset)
1374
0
    {
1375
0
        uint64_t nIterPixels = nBasePixels;
1376
0
        for (int i = 0; i < nCountRef; ++i)
1377
0
        {
1378
0
            const char *pszRef = CSLFetchNameValue(
1379
0
                papszSubDSRef, CPLSPrintf("SUBDATASET_%d_NAME", i + 1));
1380
0
            const char *pszInput = CSLFetchNameValue(
1381
0
                papszSubDSInput, CPLSPrintf("SUBDATASET_%d_NAME", i + 1));
1382
0
            if (pszRef && pszInput)
1383
0
            {
1384
0
                auto poSubRef = std::unique_ptr<GDALDataset>(GDALDataset::Open(
1385
0
                    pszRef, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
1386
0
                auto poSubInput =
1387
0
                    std::unique_ptr<GDALDataset>(GDALDataset::Open(
1388
0
                        pszInput, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
1389
0
                if (poSubRef && poSubInput)
1390
0
                {
1391
0
                    const uint64_t nSubDSPixels =
1392
0
                        static_cast<uint64_t>(poSubRef->GetRasterXSize()) *
1393
0
                        poSubRef->GetRasterYSize() * poSubRef->GetRasterCount();
1394
0
                    void *pScaledProgress = GDALCreateScaledProgress(
1395
0
                        static_cast<double>(nIterPixels) /
1396
0
                            static_cast<double>(nTotalPixels),
1397
0
                        static_cast<double>(nIterPixels + nSubDSPixels) /
1398
0
                            static_cast<double>(nTotalPixels),
1399
0
                        ctxt.m_pfnProgress, ctxt.m_pProgressData);
1400
0
                    DatasetComparison(
1401
0
                        aosReport, poSubRef.get(), poSubInput.get(),
1402
0
                        pScaledProgress ? GDALScaledProgress : nullptr,
1403
0
                        pScaledProgress);
1404
0
                    GDALDestroyScaledProgress(pScaledProgress);
1405
0
                    nIterPixels += nSubDSPixels;
1406
0
                }
1407
0
            }
1408
0
        }
1409
0
    }
1410
1411
0
    for (const auto &s : aosReport)
1412
0
    {
1413
0
        m_output += s;
1414
0
        m_output += '\n';
1415
0
    }
1416
1417
0
    m_retCode = static_cast<int>(aosReport.size());
1418
1419
0
    return true;
1420
0
}
1421
1422
/************************************************************************/
1423
/*               ~GDALRasterCompareAlgorithmStandalone()                */
1424
/************************************************************************/
1425
1426
0
GDALRasterCompareAlgorithmStandalone::~GDALRasterCompareAlgorithmStandalone() =
1427
    default;
1428
1429
//! @endcond