LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_compare.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 597 611 97.7 %
Date: 2025-12-01 18:11:08 Functions: 60 60 100.0 %

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

Generated by: LCOV version 1.14