LCOV - code coverage report
Current view: top level - frmts/vrt - vrtpansharpened.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 809 891 90.8 %
Date: 2025-05-31 00:00:17 Functions: 20 20 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  Virtual GDAL Datasets
       4             :  * Purpose:  Implementation of VRTPansharpenedRasterBand and
       5             :  *VRTPansharpenedDataset. Author:   Even Rouault <even.rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2015, Even Rouault <even.rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_port.h"
      14             : #include "gdal_vrt.h"
      15             : #include "vrtdataset.h"
      16             : 
      17             : #include <cassert>
      18             : #include <cmath>
      19             : #include <cstddef>
      20             : #include <cstdlib>
      21             : #include <cstring>
      22             : 
      23             : #include <algorithm>
      24             : #include <limits>
      25             : #include <map>
      26             : #include <set>
      27             : #include <string>
      28             : #include <utility>
      29             : #include <vector>
      30             : 
      31             : #include "cpl_conv.h"
      32             : #include "cpl_error.h"
      33             : #include "cpl_minixml.h"
      34             : #include "cpl_string.h"
      35             : #include "cpl_vsi.h"
      36             : #include "gdal.h"
      37             : #include "gdal_priv.h"
      38             : #include "gdalpansharpen.h"
      39             : #include "ogr_core.h"
      40             : #include "ogr_spatialref.h"
      41             : 
      42             : /************************************************************************/
      43             : /*                    GDALCreatePansharpenedVRT()                       */
      44             : /************************************************************************/
      45             : 
      46             : /**
      47             :  * Create a virtual pansharpened dataset.
      48             :  *
      49             :  * This function will create a virtual pansharpened dataset.
      50             :  *
      51             :  * Starting with GDAL 3.12, a reference will be taken on the dataset owing
      52             :  * each input bands.
      53             :  * Before 3.12, no reference were taken on the passed bands. Consequently,
      54             :  * they or their dataset to which they belong had to be kept open until
      55             :  * this virtual pansharpened dataset is closed.
      56             :  *
      57             :  * The returned dataset will have no associated filename for itself.  If you
      58             :  * want to write the virtual dataset description to a file, use the
      59             :  * GDALSetDescription() function (or SetDescription() method) on the dataset
      60             :  * to assign a filename before it is closed.
      61             :  *
      62             :  * @param pszXML Pansharpened VRT XML where &lt;SpectralBand&gt; elements have
      63             :  * no explicit SourceFilename and SourceBand. The spectral bands in the XML will
      64             :  * be assigned the successive values of the pahInputSpectralBands array. Must
      65             :  * not be NULL.
      66             :  *
      67             :  * @param hPanchroBand Panchromatic band. Must not be NULL.
      68             :  *
      69             :  * @param nInputSpectralBands Number of input spectral bands.  Must be greater
      70             :  * than zero.
      71             :  *
      72             :  * @param pahInputSpectralBands Array of nInputSpectralBands spectral bands.
      73             :  *
      74             :  * @return NULL on failure, or a new virtual dataset handle on success to be
      75             :  * closed with GDALClose().
      76             :  *
      77             :  * @since GDAL 2.1
      78             :  */
      79             : 
      80          16 : GDALDatasetH GDALCreatePansharpenedVRT(const char *pszXML,
      81             :                                        GDALRasterBandH hPanchroBand,
      82             :                                        int nInputSpectralBands,
      83             :                                        GDALRasterBandH *pahInputSpectralBands)
      84             : {
      85          16 :     VALIDATE_POINTER1(pszXML, "GDALCreatePansharpenedVRT", nullptr);
      86          16 :     VALIDATE_POINTER1(hPanchroBand, "GDALCreatePansharpenedVRT", nullptr);
      87          16 :     VALIDATE_POINTER1(pahInputSpectralBands, "GDALCreatePansharpenedVRT",
      88             :                       nullptr);
      89             : 
      90          16 :     CPLXMLNode *psTree = CPLParseXMLString(pszXML);
      91          16 :     if (psTree == nullptr)
      92           1 :         return nullptr;
      93          15 :     VRTPansharpenedDataset *poDS = new VRTPansharpenedDataset(0, 0);
      94          15 :     CPLErr eErr = poDS->XMLInit(psTree, nullptr, hPanchroBand,
      95             :                                 nInputSpectralBands, pahInputSpectralBands);
      96          15 :     CPLDestroyXMLNode(psTree);
      97          15 :     if (eErr != CE_None)
      98             :     {
      99           3 :         delete poDS;
     100           3 :         return nullptr;
     101             :     }
     102          12 :     return GDALDataset::ToHandle(poDS);
     103             : }
     104             : 
     105             : /*! @cond Doxygen_Suppress */
     106             : 
     107             : /************************************************************************/
     108             : /* ==================================================================== */
     109             : /*                        VRTPansharpenedDataset                        */
     110             : /* ==================================================================== */
     111             : /************************************************************************/
     112             : 
     113             : /************************************************************************/
     114             : /*                       VRTPansharpenedDataset()                       */
     115             : /************************************************************************/
     116             : 
     117          92 : VRTPansharpenedDataset::VRTPansharpenedDataset(int nXSize, int nYSize,
     118          92 :                                                int nBlockXSize, int nBlockYSize)
     119             :     : VRTDataset(nXSize, nYSize,
     120          92 :                  nBlockXSize > 0 ? nBlockXSize : std::min(nXSize, 512),
     121          92 :                  nBlockYSize > 0 ? nBlockYSize : std::min(nYSize, 512)),
     122             :       m_poPansharpener(nullptr), m_poMainDataset(nullptr),
     123             :       m_bLoadingOtherBands(FALSE), m_pabyLastBufferBandRasterIO(nullptr),
     124             :       m_nLastBandRasterIOXOff(0), m_nLastBandRasterIOYOff(0),
     125             :       m_nLastBandRasterIOXSize(0), m_nLastBandRasterIOYSize(0),
     126             :       m_eLastBandRasterIODataType(GDT_Unknown), m_eGTAdjustment(GTAdjust_Union),
     127         276 :       m_bNoDataDisabled(FALSE)
     128             : {
     129          92 :     eAccess = GA_Update;
     130          92 :     m_poMainDataset = this;
     131          92 : }
     132             : 
     133             : /************************************************************************/
     134             : /*                    ~VRTPansharpenedDataset()                         */
     135             : /************************************************************************/
     136             : 
     137         184 : VRTPansharpenedDataset::~VRTPansharpenedDataset()
     138             : 
     139             : {
     140          92 :     VRTPansharpenedDataset::FlushCache(true);
     141          92 :     VRTPansharpenedDataset::CloseDependentDatasets();
     142          92 :     CPLFree(m_pabyLastBufferBandRasterIO);
     143         184 : }
     144             : 
     145             : /************************************************************************/
     146             : /*                        CloseDependentDatasets()                      */
     147             : /************************************************************************/
     148             : 
     149          95 : int VRTPansharpenedDataset::CloseDependentDatasets()
     150             : {
     151          95 :     if (m_poMainDataset == nullptr)
     152           3 :         return FALSE;
     153             : 
     154          92 :     VRTPansharpenedDataset *poMainDatasetLocal = m_poMainDataset;
     155          92 :     m_poMainDataset = nullptr;
     156          92 :     int bHasDroppedRef = VRTDataset::CloseDependentDatasets();
     157             : 
     158             :     /* -------------------------------------------------------------------- */
     159             :     /*      Destroy the raster bands if they exist.                         */
     160             :     /* -------------------------------------------------------------------- */
     161         288 :     for (int iBand = 0; iBand < nBands; iBand++)
     162             :     {
     163         196 :         delete papoBands[iBand];
     164             :     }
     165          92 :     nBands = 0;
     166             : 
     167             :     // Destroy the overviews before m_poPansharpener as they might reference
     168             :     // files that are in m_apoDatasetsToReleaseRef.
     169          92 :     bHasDroppedRef |= !m_apoOverviewDatasets.empty();
     170          92 :     m_apoOverviewDatasets.clear();
     171             : 
     172          92 :     if (m_poPansharpener != nullptr)
     173             :     {
     174             :         // Delete the pansharper object before closing the dataset
     175             :         // because it may have warped the bands into an intermediate VRT
     176          69 :         delete m_poPansharpener;
     177          69 :         m_poPansharpener = nullptr;
     178             : 
     179             :         // Close in reverse order (VRT firsts and real datasets after)
     180         324 :         for (int i = static_cast<int>(m_apoDatasetsToReleaseRef.size()) - 1;
     181         324 :              i >= 0; i--)
     182             :         {
     183         255 :             bHasDroppedRef = TRUE;
     184         255 :             m_apoDatasetsToReleaseRef[i].reset();
     185             :         }
     186          69 :         m_apoDatasetsToReleaseRef.clear();
     187             :     }
     188             : 
     189          92 :     if (poMainDatasetLocal != this)
     190             :     {
     191             :         // To avoid killing us
     192           3 :         for (size_t i = 0; i < poMainDatasetLocal->m_apoOverviewDatasets.size();
     193             :              i++)
     194             :         {
     195           3 :             if (poMainDatasetLocal->m_apoOverviewDatasets[i].get() == this)
     196             :             {
     197             :                 // coverity[leaked_storage]
     198           3 :                 CPL_IGNORE_RET_VAL(
     199           3 :                     poMainDatasetLocal->m_apoOverviewDatasets[i].release());
     200           3 :                 break;
     201             :             }
     202             :         }
     203           3 :         bHasDroppedRef |= poMainDatasetLocal->CloseDependentDatasets();
     204             :     }
     205             : 
     206          92 :     return bHasDroppedRef;
     207             : }
     208             : 
     209             : /************************************************************************/
     210             : /*                            GetFileList()                             */
     211             : /************************************************************************/
     212             : 
     213           1 : char **VRTPansharpenedDataset::GetFileList()
     214             : {
     215           1 :     char **papszFileList = GDALDataset::GetFileList();
     216             : 
     217           1 :     if (m_poPansharpener != nullptr)
     218             :     {
     219           1 :         GDALPansharpenOptions *psOptions = m_poPansharpener->GetOptions();
     220           1 :         if (psOptions != nullptr)
     221             :         {
     222           2 :             std::set<CPLString> oSetNames;
     223           1 :             if (psOptions->hPanchroBand != nullptr)
     224             :             {
     225           1 :                 GDALDatasetH hDS = GDALGetBandDataset(psOptions->hPanchroBand);
     226           1 :                 if (hDS != nullptr)
     227             :                 {
     228             :                     papszFileList =
     229           1 :                         CSLAddString(papszFileList, GDALGetDescription(hDS));
     230           1 :                     oSetNames.insert(GDALGetDescription(hDS));
     231             :                 }
     232             :             }
     233           4 :             for (int i = 0; i < psOptions->nInputSpectralBands; i++)
     234             :             {
     235           3 :                 if (psOptions->pahInputSpectralBands[i] != nullptr)
     236             :                 {
     237             :                     GDALDatasetH hDS =
     238           3 :                         GDALGetBandDataset(psOptions->pahInputSpectralBands[i]);
     239           6 :                     if (hDS != nullptr && oSetNames.find(GDALGetDescription(
     240           6 :                                               hDS)) == oSetNames.end())
     241             :                     {
     242           1 :                         papszFileList = CSLAddString(papszFileList,
     243             :                                                      GDALGetDescription(hDS));
     244           1 :                         oSetNames.insert(GDALGetDescription(hDS));
     245             :                     }
     246             :                 }
     247             :             }
     248             :         }
     249             :     }
     250             : 
     251           1 :     return papszFileList;
     252             : }
     253             : 
     254             : /************************************************************************/
     255             : /*                              XMLInit()                               */
     256             : /************************************************************************/
     257             : 
     258          74 : CPLErr VRTPansharpenedDataset::XMLInit(const CPLXMLNode *psTree,
     259             :                                        const char *pszVRTPathIn)
     260             : 
     261             : {
     262          74 :     return XMLInit(psTree, pszVRTPathIn, nullptr, 0, nullptr);
     263             : }
     264             : 
     265          89 : CPLErr VRTPansharpenedDataset::XMLInit(const CPLXMLNode *psTree,
     266             :                                        const char *pszVRTPathIn,
     267             :                                        GDALRasterBandH hPanchroBandIn,
     268             :                                        int nInputSpectralBandsIn,
     269             :                                        GDALRasterBandH *pahInputSpectralBandsIn)
     270             : {
     271             :     CPLErr eErr;
     272             :     GDALPansharpenOptions *psPanOptions;
     273             : 
     274             :     /* -------------------------------------------------------------------- */
     275             :     /*      Initialize blocksize before calling sub-init so that the        */
     276             :     /*      band initializers can get it from the dataset object when       */
     277             :     /*      they are created.                                               */
     278             :     /* -------------------------------------------------------------------- */
     279          89 :     m_nBlockXSize = atoi(CPLGetXMLValue(psTree, "BlockXSize", "512"));
     280          89 :     m_nBlockYSize = atoi(CPLGetXMLValue(psTree, "BlockYSize", "512"));
     281             : 
     282             :     /* -------------------------------------------------------------------- */
     283             :     /*      Parse PansharpeningOptions                                      */
     284             :     /* -------------------------------------------------------------------- */
     285             : 
     286          89 :     const CPLXMLNode *psOptions = CPLGetXMLNode(psTree, "PansharpeningOptions");
     287          89 :     if (psOptions == nullptr)
     288             :     {
     289           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing PansharpeningOptions");
     290           1 :         return CE_Failure;
     291             :     }
     292             : 
     293         176 :     if (CPLGetXMLValue(psOptions, "MSShiftX", nullptr) != nullptr ||
     294          88 :         CPLGetXMLValue(psOptions, "MSShiftY", nullptr) != nullptr)
     295             :     {
     296           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     297             :                  "Options MSShiftX and MSShiftY are no longer supported. "
     298             :                  "Spatial adjustment between panchromatic and multispectral "
     299             :                  "bands is done through their geotransform.");
     300           0 :         return CE_Failure;
     301             :     }
     302             : 
     303         176 :     CPLString osSourceFilename;
     304          88 :     GDALDataset *poPanDataset = nullptr;
     305          88 :     GDALRasterBand *poPanBand = nullptr;
     306         176 :     std::map<CPLString, GDALDataset *> oMapNamesToDataset;
     307             :     int nPanBand;
     308             : 
     309          88 :     if (hPanchroBandIn == nullptr)
     310             :     {
     311             :         const CPLXMLNode *psPanchroBand =
     312          73 :             CPLGetXMLNode(psOptions, "PanchroBand");
     313          73 :         if (psPanchroBand == nullptr)
     314             :         {
     315           1 :             CPLError(CE_Failure, CPLE_AppDefined, "PanchroBand missing");
     316           4 :             return CE_Failure;
     317             :         }
     318             : 
     319          72 :         osSourceFilename = CPLGetXMLValue(psPanchroBand, "SourceFilename", "");
     320          72 :         if (osSourceFilename.empty())
     321             :         {
     322           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     323             :                      "PanchroBand.SourceFilename missing");
     324           1 :             return CE_Failure;
     325             :         }
     326          71 :         const bool bRelativeToVRT = CPL_TO_BOOL(atoi(CPLGetXMLValue(
     327             :             psPanchroBand, "SourceFilename.relativetoVRT", "0")));
     328          71 :         if (bRelativeToVRT)
     329             :         {
     330             :             const std::string osAbs = CPLProjectRelativeFilenameSafe(
     331          41 :                 pszVRTPathIn, osSourceFilename.c_str());
     332          41 :             m_oMapToRelativeFilenames[osAbs] = osSourceFilename;
     333          41 :             osSourceFilename = osAbs;
     334             :         }
     335             : 
     336             :         const CPLStringList aosOpenOptions(
     337          71 :             GDALDeserializeOpenOptionsFromXML(psPanchroBand));
     338             : 
     339          71 :         poPanDataset = GDALDataset::Open(osSourceFilename,
     340             :                                          GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
     341             :                                          nullptr, aosOpenOptions.List());
     342          71 :         if (poPanDataset == nullptr)
     343             :         {
     344           1 :             return CE_Failure;
     345             :         }
     346             : 
     347             :         const char *pszSourceBand =
     348          70 :             CPLGetXMLValue(psPanchroBand, "SourceBand", "1");
     349          70 :         nPanBand = atoi(pszSourceBand);
     350          70 :         if (poPanBand == nullptr)
     351          70 :             poPanBand = poPanDataset->GetRasterBand(nPanBand);
     352          70 :         if (poPanBand == nullptr)
     353             :         {
     354           1 :             CPLError(CE_Failure, CPLE_AppDefined, "%s invalid band of %s",
     355             :                      pszSourceBand, osSourceFilename.c_str());
     356           1 :             GDALClose(poPanDataset);
     357           1 :             return CE_Failure;
     358             :         }
     359          69 :         oMapNamesToDataset[osSourceFilename] = poPanDataset;
     360             :     }
     361             :     else
     362             :     {
     363          15 :         poPanBand = GDALRasterBand::FromHandle(hPanchroBandIn);
     364          15 :         nPanBand = poPanBand->GetBand();
     365          15 :         poPanDataset = poPanBand->GetDataset();
     366          15 :         if (poPanDataset == nullptr)
     367             :         {
     368           0 :             CPLError(
     369             :                 CE_Failure, CPLE_AppDefined,
     370             :                 "Cannot retrieve dataset associated with panchromatic band");
     371           0 :             return CE_Failure;
     372             :         }
     373          15 :         oMapNamesToDataset[CPLSPrintf("%p", poPanDataset)] = poPanDataset;
     374          15 :         poPanDataset->Reference();
     375             :     }
     376          84 :     m_apoDatasetsToReleaseRef.push_back(
     377         168 :         std::unique_ptr<GDALDataset, GDALDatasetUniquePtrReleaser>(
     378             :             poPanDataset));
     379          84 :     poPanDataset = m_apoDatasetsToReleaseRef.back().get();
     380             :     // Figure out which kind of adjustment we should do if the pan and spectral
     381             :     // bands do not share the same geotransform
     382             :     const char *pszGTAdjustment =
     383          84 :         CPLGetXMLValue(psOptions, "SpatialExtentAdjustment", "Union");
     384          84 :     if (EQUAL(pszGTAdjustment, "Union"))
     385          78 :         m_eGTAdjustment = GTAdjust_Union;
     386           6 :     else if (EQUAL(pszGTAdjustment, "Intersection"))
     387           2 :         m_eGTAdjustment = GTAdjust_Intersection;
     388           4 :     else if (EQUAL(pszGTAdjustment, "None"))
     389           2 :         m_eGTAdjustment = GTAdjust_None;
     390           2 :     else if (EQUAL(pszGTAdjustment, "NoneWithoutWarning"))
     391           1 :         m_eGTAdjustment = GTAdjust_NoneWithoutWarning;
     392             :     else
     393             :     {
     394           1 :         m_eGTAdjustment = GTAdjust_Union;
     395           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     396             :                  "Unsupported value for GeoTransformAdjustment. Defaulting to "
     397             :                  "Union");
     398             :     }
     399             : 
     400             :     const char *pszNumThreads =
     401          84 :         CPLGetXMLValue(psOptions, "NumThreads", nullptr);
     402          84 :     int nThreads = 0;
     403          84 :     if (pszNumThreads != nullptr)
     404             :     {
     405          37 :         if (EQUAL(pszNumThreads, "ALL_CPUS"))
     406          37 :             nThreads = -1;
     407             :         else
     408           0 :             nThreads = atoi(pszNumThreads);
     409             :     }
     410             : 
     411             :     const char *pszAlgorithm =
     412          84 :         CPLGetXMLValue(psOptions, "Algorithm", "WeightedBrovey");
     413          84 :     if (!EQUAL(pszAlgorithm, "WeightedBrovey"))
     414             :     {
     415           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Algorithm %s unsupported",
     416             :                  pszAlgorithm);
     417           1 :         return CE_Failure;
     418             :     }
     419             : 
     420         166 :     std::vector<double> adfWeights;
     421          83 :     if (const CPLXMLNode *psAlgOptions =
     422          83 :             CPLGetXMLNode(psOptions, "AlgorithmOptions"))
     423             :     {
     424             :         const char *pszWeights =
     425          35 :             CPLGetXMLValue(psAlgOptions, "Weights", nullptr);
     426          35 :         if (pszWeights != nullptr)
     427             :         {
     428          35 :             char **papszTokens = CSLTokenizeString2(pszWeights, " ,", 0);
     429         124 :             for (int i = 0; papszTokens && papszTokens[i]; i++)
     430          89 :                 adfWeights.push_back(CPLAtof(papszTokens[i]));
     431          35 :             CSLDestroy(papszTokens);
     432             :         }
     433             :     }
     434             : 
     435          83 :     GDALRIOResampleAlg eResampleAlg = GDALRasterIOGetResampleAlg(
     436             :         CPLGetXMLValue(psOptions, "Resampling", "Cubic"));
     437             : 
     438         166 :     std::vector<GDALRasterBand *> ahSpectralBands;
     439         166 :     std::map<int, int> aMapDstBandToSpectralBand;
     440          83 :     std::map<int, int>::iterator aMapDstBandToSpectralBandIter;
     441          83 :     int nBitDepth = 0;
     442          83 :     bool bFoundNonMatchingGT = false;
     443          83 :     double adfPanGT[6] = {0, 0, 0, 0, 0, 0};
     444             :     const bool bPanGeoTransformValid =
     445          83 :         (poPanDataset->GetGeoTransform(adfPanGT) == CE_None);
     446          83 :     if (!bPanGeoTransformValid)
     447             :     {
     448           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     449             :                  "Panchromatic band has no associated geotransform");
     450           1 :         return CE_Failure;
     451             :     }
     452          82 :     int nPanXSize = poPanBand->GetXSize();
     453          82 :     int nPanYSize = poPanBand->GetYSize();
     454          82 :     int bHasNoData = FALSE;
     455          82 :     double dfNoData = poPanBand->GetNoDataValue(&bHasNoData);
     456          82 :     double dfLRPanX =
     457          82 :         adfPanGT[0] + nPanXSize * adfPanGT[1] + nPanYSize * adfPanGT[2];
     458          82 :     double dfLRPanY =
     459          82 :         adfPanGT[3] + nPanXSize * adfPanGT[4] + nPanYSize * adfPanGT[5];
     460          82 :     bool bFoundRotatingTerms = (adfPanGT[2] != 0.0 || adfPanGT[4] != 0.0);
     461          82 :     double dfMinX = adfPanGT[0];
     462          82 :     double dfMaxX = dfLRPanX;
     463          82 :     double dfMaxY = adfPanGT[3];
     464          82 :     double dfMinY = dfLRPanY;
     465          82 :     if (dfMinY > dfMaxY)
     466          11 :         std::swap(dfMinY, dfMaxY);
     467          82 :     const double dfPanMinY = dfMinY;
     468          82 :     const double dfPanMaxY = dfMaxY;
     469             : 
     470          82 :     const auto poPanSRS = poPanDataset->GetSpatialRef();
     471             : 
     472             :     /* -------------------------------------------------------------------- */
     473             :     /*      First pass on spectral datasets to check their georeferencing.  */
     474             :     /* -------------------------------------------------------------------- */
     475          82 :     int iSpectralBand = 0;
     476         528 :     for (const CPLXMLNode *psIter = psOptions->psChild; psIter;
     477         446 :          psIter = psIter->psNext)
     478             :     {
     479             :         GDALDataset *poDataset;
     480         450 :         if (psIter->eType != CXT_Element ||
     481         450 :             !EQUAL(psIter->pszValue, "SpectralBand"))
     482         246 :             continue;
     483             : 
     484         204 :         if (nInputSpectralBandsIn && pahInputSpectralBandsIn != nullptr)
     485             :         {
     486          40 :             if (iSpectralBand == nInputSpectralBandsIn)
     487             :             {
     488           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     489             :                          "More SpectralBand elements than in source array");
     490           4 :                 goto error;
     491             :             }
     492             :             poDataset = GDALRasterBand::FromHandle(
     493          39 :                             pahInputSpectralBandsIn[iSpectralBand])
     494          39 :                             ->GetDataset();
     495          39 :             if (poDataset == nullptr)
     496             :             {
     497           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     498             :                          "SpectralBand has no associated dataset");
     499           0 :                 goto error;
     500             :             }
     501          39 :             osSourceFilename = poDataset->GetDescription();
     502             : 
     503          39 :             oMapNamesToDataset[CPLSPrintf("%p", poDataset)] = poDataset;
     504          39 :             poDataset->Reference();
     505             :         }
     506             :         else
     507             :         {
     508         164 :             osSourceFilename = CPLGetXMLValue(psIter, "SourceFilename", "");
     509         164 :             if (osSourceFilename.empty())
     510             :             {
     511           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     512             :                          "SpectralBand.SourceFilename missing");
     513           1 :                 goto error;
     514             :             }
     515         163 :             int bRelativeToVRT = atoi(
     516             :                 CPLGetXMLValue(psIter, "SourceFilename.relativetoVRT", "0"));
     517         163 :             if (bRelativeToVRT)
     518             :             {
     519             :                 const std::string osAbs = CPLProjectRelativeFilenameSafe(
     520         107 :                     pszVRTPathIn, osSourceFilename.c_str());
     521         107 :                 m_oMapToRelativeFilenames[osAbs] = osSourceFilename;
     522         107 :                 osSourceFilename = osAbs;
     523             :             }
     524         163 :             poDataset = oMapNamesToDataset[osSourceFilename];
     525         163 :             if (poDataset == nullptr)
     526             :             {
     527             :                 const CPLStringList aosOpenOptions(
     528          68 :                     GDALDeserializeOpenOptionsFromXML(psIter));
     529             : 
     530          68 :                 poDataset = GDALDataset::Open(
     531             :                     osSourceFilename, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
     532             :                     nullptr, aosOpenOptions.List());
     533          68 :                 if (poDataset == nullptr)
     534             :                 {
     535           1 :                     goto error;
     536             :                 }
     537          67 :                 oMapNamesToDataset[osSourceFilename] = poDataset;
     538             :             }
     539             :             else
     540             :             {
     541          95 :                 poDataset->Reference();
     542             :             }
     543             :         }
     544         201 :         m_apoDatasetsToReleaseRef.push_back(
     545         402 :             std::unique_ptr<GDALDataset, GDALDatasetUniquePtrReleaser>(
     546             :                 poDataset));
     547         201 :         poDataset = m_apoDatasetsToReleaseRef.back().get();
     548             : 
     549             :         // Check that the spectral band has a georeferencing consistent
     550             :         // of the pan band. Allow an error of at most the size of one pixel
     551             :         // of the spectral band.
     552         201 :         const auto poSpectralSRS = poDataset->GetSpatialRef();
     553         201 :         if (poPanSRS)
     554             :         {
     555         164 :             if (poSpectralSRS)
     556             :             {
     557         164 :                 if (!poPanSRS->IsSame(poSpectralSRS))
     558             :                 {
     559           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
     560             :                              "Pan dataset and %s do not seem to "
     561             :                              "have same projection. Results might "
     562             :                              "be incorrect",
     563             :                              osSourceFilename.c_str());
     564             :                 }
     565             :             }
     566             :             else
     567             :             {
     568           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     569             :                          "Pan dataset has a projection, whereas %s "
     570             :                          "not. Results might be incorrect",
     571             :                          osSourceFilename.c_str());
     572             :             }
     573             :         }
     574          37 :         else if (poSpectralSRS)
     575             :         {
     576           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     577             :                      "Pan dataset has no projection, whereas %s has "
     578             :                      "one. Results might be incorrect",
     579             :                      osSourceFilename.c_str());
     580             :         }
     581             : 
     582             :         double adfSpectralGeoTransform[6];
     583         201 :         if (poDataset->GetGeoTransform(adfSpectralGeoTransform) != CE_None)
     584             :         {
     585           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     586             :                      "Spectral band has no associated geotransform");
     587           0 :             goto error;
     588             :         }
     589         201 :         if (adfSpectralGeoTransform[3] * adfPanGT[3] < 0)
     590             :         {
     591           1 :             CPLError(CE_Failure, CPLE_NotSupported,
     592             :                      "Spectral band vertical orientation is "
     593             :                      "different from pan one");
     594           1 :             goto error;
     595             :         }
     596             : 
     597         200 :         int bIsThisOneNonMatching = FALSE;
     598             :         double dfPixelSize = std::max(adfSpectralGeoTransform[1],
     599         200 :                                       std::abs(adfSpectralGeoTransform[5]));
     600         376 :         if (std::abs(adfPanGT[0] - adfSpectralGeoTransform[0]) > dfPixelSize ||
     601         176 :             std::abs(adfPanGT[3] - adfSpectralGeoTransform[3]) > dfPixelSize)
     602             :         {
     603          24 :             bIsThisOneNonMatching = TRUE;
     604          24 :             if (m_eGTAdjustment == GTAdjust_None)
     605             :             {
     606           2 :                 CPLError(CE_Warning, CPLE_AppDefined,
     607             :                          "Georeferencing of top-left corner of pan "
     608             :                          "dataset and %s do not match",
     609             :                          osSourceFilename.c_str());
     610             :             }
     611             :         }
     612         200 :         bFoundRotatingTerms =
     613         400 :             bFoundRotatingTerms || (adfSpectralGeoTransform[2] != 0.0 ||
     614         200 :                                     adfSpectralGeoTransform[4] != 0.0);
     615             :         double dfLRSpectralX =
     616         400 :             adfSpectralGeoTransform[0] +
     617         200 :             poDataset->GetRasterXSize() * adfSpectralGeoTransform[1] +
     618         200 :             poDataset->GetRasterYSize() * adfSpectralGeoTransform[2];
     619             :         double dfLRSpectralY =
     620         400 :             adfSpectralGeoTransform[3] +
     621         200 :             poDataset->GetRasterXSize() * adfSpectralGeoTransform[4] +
     622         200 :             poDataset->GetRasterYSize() * adfSpectralGeoTransform[5];
     623         376 :         if (std::abs(dfLRPanX - dfLRSpectralX) > dfPixelSize ||
     624         176 :             std::abs(dfLRPanY - dfLRSpectralY) > dfPixelSize)
     625             :         {
     626          24 :             bIsThisOneNonMatching = TRUE;
     627          24 :             if (m_eGTAdjustment == GTAdjust_None)
     628             :             {
     629           2 :                 CPLError(CE_Warning, CPLE_AppDefined,
     630             :                          "Georeferencing of bottom-right corner of "
     631             :                          "pan dataset and %s do not match",
     632             :                          osSourceFilename.c_str());
     633             :             }
     634             :         }
     635             : 
     636         200 :         double dfThisMinY = dfLRSpectralY;
     637         200 :         double dfThisMaxY = adfSpectralGeoTransform[3];
     638         200 :         if (dfThisMinY > dfThisMaxY)
     639          23 :             std::swap(dfThisMinY, dfThisMaxY);
     640         200 :         if (bIsThisOneNonMatching && m_eGTAdjustment == GTAdjust_Union)
     641             :         {
     642          20 :             dfMinX = std::min(dfMinX, adfSpectralGeoTransform[0]);
     643          20 :             dfMinY = std::min(dfMinY, dfThisMinY);
     644          20 :             dfMaxX = std::max(dfMaxX, dfLRSpectralX);
     645          20 :             dfMaxY = std::max(dfMaxY, dfThisMaxY);
     646             :         }
     647         180 :         else if (bIsThisOneNonMatching &&
     648           4 :                  m_eGTAdjustment == GTAdjust_Intersection)
     649             :         {
     650           1 :             dfMinX = std::max(dfMinX, adfSpectralGeoTransform[0]);
     651           1 :             dfMinY = std::max(dfMinY, dfThisMinY);
     652           1 :             dfMaxX = std::min(dfMaxX, dfLRSpectralX);
     653           1 :             dfMaxY = std::min(dfMaxY, dfThisMaxY);
     654             :         }
     655             : 
     656         200 :         bFoundNonMatchingGT = bFoundNonMatchingGT || bIsThisOneNonMatching;
     657             : 
     658         200 :         iSpectralBand++;
     659             :     }
     660             : 
     661          78 :     if (nInputSpectralBandsIn && iSpectralBand != nInputSpectralBandsIn)
     662             :     {
     663           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     664             :                  "Less SpectralBand elements than in source array");
     665           1 :         goto error;
     666             :     }
     667             : 
     668             :     /* -------------------------------------------------------------------- */
     669             :     /*      On-the-fly spatial extent adjustment if needed and asked.       */
     670             :     /* -------------------------------------------------------------------- */
     671             : 
     672          77 :     if (bFoundNonMatchingGT && (m_eGTAdjustment == GTAdjust_Union ||
     673           4 :                                 m_eGTAdjustment == GTAdjust_Intersection))
     674             :     {
     675           8 :         if (bFoundRotatingTerms)
     676             :         {
     677           0 :             CPLError(
     678             :                 CE_Failure, CPLE_NotSupported,
     679             :                 "One of the panchromatic or spectral datasets has rotating "
     680             :                 "terms in their geotransform matrix. Adjustment not possible");
     681           0 :             goto error;
     682             :         }
     683           8 :         if (m_eGTAdjustment == GTAdjust_Intersection &&
     684           1 :             (dfMinX >= dfMaxX || dfMinY >= dfMaxY))
     685             :         {
     686           0 :             CPLError(
     687             :                 CE_Failure, CPLE_NotSupported,
     688             :                 "One of the panchromatic or spectral datasets has rotating "
     689             :                 "terms in their geotransform matrix. Adjustment not possible");
     690           0 :             goto error;
     691             :         }
     692           8 :         if (m_eGTAdjustment == GTAdjust_Union)
     693           7 :             CPLDebug("VRT", "Do union of bounding box of panchromatic and "
     694             :                             "spectral datasets");
     695             :         else
     696           1 :             CPLDebug("VRT", "Do intersection of bounding box of panchromatic "
     697             :                             "and spectral datasets");
     698             : 
     699             :         // If the pandataset needs adjustments, make sure the coordinates of the
     700             :         // union/intersection properly align with the grid of the pandataset
     701             :         // to avoid annoying sub-pixel shifts on the panchro band.
     702           8 :         double dfPixelSize = std::max(adfPanGT[1], std::abs(adfPanGT[5]));
     703           8 :         if (std::abs(adfPanGT[0] - dfMinX) > dfPixelSize ||
     704           3 :             std::abs(dfPanMaxY - dfMaxY) > dfPixelSize ||
     705          13 :             std::abs(dfLRPanX - dfMaxX) > dfPixelSize ||
     706           2 :             std::abs(dfPanMinY - dfMinY) > dfPixelSize)
     707             :         {
     708           6 :             dfMinX = adfPanGT[0] +
     709           6 :                      std::floor((dfMinX - adfPanGT[0]) / adfPanGT[1] + 0.5) *
     710           6 :                          adfPanGT[1];
     711           6 :             dfMaxX =
     712           6 :                 dfLRPanX + std::floor((dfMaxX - dfLRPanX) / adfPanGT[1] + 0.5) *
     713           6 :                                adfPanGT[1];
     714           6 :             if (adfPanGT[5] > 0)
     715             :             {
     716           0 :                 dfMinY = dfPanMinY +
     717           0 :                          std::floor((dfMinY - dfPanMinY) / adfPanGT[5] + 0.5) *
     718           0 :                              adfPanGT[5];
     719           0 :                 dfMaxY = dfPanMinY +
     720           0 :                          std::floor((dfMaxY - dfPanMinY) / adfPanGT[5] + 0.5) *
     721           0 :                              adfPanGT[5];
     722             :             }
     723             :             else
     724             :             {
     725           6 :                 dfMinY = dfPanMaxY +
     726           6 :                          std::floor((dfMinY - dfPanMaxY) / adfPanGT[5] + 0.5) *
     727           6 :                              adfPanGT[5];
     728           6 :                 dfMaxY = dfPanMaxY +
     729           6 :                          std::floor((dfMaxY - dfPanMaxY) / adfPanGT[5] + 0.5) *
     730           6 :                              adfPanGT[5];
     731             :             }
     732             :         }
     733             : 
     734             :         std::map<CPLString, GDALDataset *>::iterator oIter =
     735           8 :             oMapNamesToDataset.begin();
     736          24 :         for (; oIter != oMapNamesToDataset.end(); ++oIter)
     737             :         {
     738          16 :             GDALDataset *poSrcDS = oIter->second;
     739             :             double adfGT[6];
     740          16 :             if (poSrcDS->GetGeoTransform(adfGT) != CE_None)
     741           3 :                 continue;
     742             : 
     743             :             // Check if this dataset needs adjustments
     744          16 :             dfPixelSize = std::max(adfGT[1], std::abs(adfGT[5]));
     745          16 :             dfPixelSize = std::max(adfPanGT[1], dfPixelSize);
     746          16 :             dfPixelSize = std::max(std::abs(adfPanGT[5]), dfPixelSize);
     747          16 :             double dfThisMinX = adfGT[0];
     748          16 :             double dfThisMaxX = adfGT[0] + poSrcDS->GetRasterXSize() * adfGT[1];
     749          16 :             double dfThisMaxY = adfGT[3];
     750          16 :             double dfThisMinY = adfGT[3] + poSrcDS->GetRasterYSize() * adfGT[5];
     751          16 :             if (dfThisMinY > dfThisMaxY)
     752           2 :                 std::swap(dfThisMinY, dfThisMaxY);
     753          16 :             if (std::abs(dfThisMinX - dfMinX) <= dfPixelSize &&
     754           8 :                 std::abs(dfThisMaxY - dfMaxY) <= dfPixelSize &&
     755          27 :                 std::abs(dfThisMaxX - dfMaxX) <= dfPixelSize &&
     756           3 :                 std::abs(dfThisMinY - dfMinY) <= dfPixelSize)
     757             :             {
     758           3 :                 continue;
     759             :             }
     760             : 
     761             :             double adfAdjustedGT[6];
     762          13 :             adfAdjustedGT[0] = dfMinX;
     763          13 :             adfAdjustedGT[1] = adfGT[1];
     764          13 :             adfAdjustedGT[2] = 0;
     765          13 :             adfAdjustedGT[3] = adfGT[5] > 0 ? dfMinY : dfMaxY;
     766          13 :             adfAdjustedGT[4] = 0;
     767          13 :             adfAdjustedGT[5] = adfGT[5];
     768          13 :             int nAdjustRasterXSize =
     769          13 :                 static_cast<int>(0.5 + (dfMaxX - dfMinX) / adfAdjustedGT[1]);
     770             :             int nAdjustRasterYSize = static_cast<int>(
     771          13 :                 0.5 + (dfMaxY - dfMinY) / std::abs(adfAdjustedGT[5]));
     772             : 
     773             :             VRTDataset *poVDS =
     774          13 :                 new VRTDataset(nAdjustRasterXSize, nAdjustRasterYSize);
     775          13 :             poVDS->SetWritable(FALSE);
     776          26 :             poVDS->SetDescription(std::string("Shifted ")
     777          13 :                                       .append(poSrcDS->GetDescription())
     778          13 :                                       .c_str());
     779          13 :             poVDS->SetGeoTransform(adfAdjustedGT);
     780          13 :             poVDS->SetProjection(poPanDataset->GetProjectionRef());
     781             : 
     782          32 :             for (int i = 0; i < poSrcDS->GetRasterCount(); i++)
     783             :             {
     784          19 :                 GDALRasterBand *poSrcBand = poSrcDS->GetRasterBand(i + 1);
     785          19 :                 poVDS->AddBand(poSrcBand->GetRasterDataType(), nullptr);
     786             :                 VRTSourcedRasterBand *poVRTBand =
     787             :                     static_cast<VRTSourcedRasterBand *>(
     788          19 :                         poVDS->GetRasterBand(i + 1));
     789             : 
     790             :                 const char *pszNBITS =
     791          19 :                     poSrcBand->GetMetadataItem("NBITS", "IMAGE_STRUCTURE");
     792          19 :                 if (pszNBITS)
     793           0 :                     poVRTBand->SetMetadataItem("NBITS", pszNBITS,
     794           0 :                                                "IMAGE_STRUCTURE");
     795             : 
     796          19 :                 VRTSimpleSource *poSimpleSource = new VRTSimpleSource();
     797          76 :                 poVRTBand->ConfigureSource(
     798             :                     poSimpleSource, poSrcBand, FALSE,
     799          19 :                     static_cast<int>(
     800          19 :                         std::floor((dfMinX - adfGT[0]) / adfGT[1] + 0.001)),
     801          19 :                     adfGT[5] < 0
     802          16 :                         ? static_cast<int>(std::floor(
     803          16 :                               (dfMaxY - dfThisMaxY) / adfGT[5] + 0.001))
     804           3 :                         : static_cast<int>(std::floor(
     805           3 :                               (dfMinY - dfThisMinY) / adfGT[5] + 0.001)),
     806          19 :                     static_cast<int>(0.5 + (dfMaxX - dfMinX) / adfGT[1]),
     807          19 :                     static_cast<int>(0.5 +
     808          19 :                                      (dfMaxY - dfMinY) / std::abs(adfGT[5])),
     809             :                     0, 0, nAdjustRasterXSize, nAdjustRasterYSize);
     810             : 
     811          19 :                 poVRTBand->AddSource(poSimpleSource);
     812             :             }
     813             : 
     814          13 :             oIter->second = poVDS;
     815          13 :             if (poSrcDS == poPanDataset)
     816             :             {
     817           6 :                 memcpy(adfPanGT, adfAdjustedGT, 6 * sizeof(double));
     818           6 :                 poPanDataset = poVDS;
     819           6 :                 poPanBand = poPanDataset->GetRasterBand(nPanBand);
     820           6 :                 nPanXSize = poPanDataset->GetRasterXSize();
     821           6 :                 nPanYSize = poPanDataset->GetRasterYSize();
     822             :             }
     823             : 
     824          13 :             m_apoDatasetsToReleaseRef.push_back(
     825          26 :                 std::unique_ptr<GDALDataset, GDALDatasetUniquePtrReleaser>(
     826             :                     poVDS));
     827             :         }
     828             :     }
     829             : 
     830          77 :     if (nRasterXSize == 0 && nRasterYSize == 0)
     831             :     {
     832          57 :         nRasterXSize = nPanXSize;
     833          57 :         nRasterYSize = nPanYSize;
     834             :     }
     835          20 :     else if (nRasterXSize != nPanXSize || nRasterYSize != nPanYSize)
     836             :     {
     837           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     838             :                  "Inconsistent declared VRT dimensions with panchro dataset");
     839           1 :         goto error;
     840             :     }
     841             : 
     842             :     /* -------------------------------------------------------------------- */
     843             :     /*      Initialize all the general VRT stuff.  This will even           */
     844             :     /*      create the VRTPansharpenedRasterBands and initialize them.      */
     845             :     /* -------------------------------------------------------------------- */
     846          76 :     eErr = VRTDataset::XMLInit(psTree, pszVRTPathIn);
     847             : 
     848          76 :     if (eErr != CE_None)
     849             :     {
     850           1 :         goto error;
     851             :     }
     852             : 
     853             :     /* -------------------------------------------------------------------- */
     854             :     /*      Inherit georeferencing info from panchro band if not defined    */
     855             :     /*      in VRT.                                                         */
     856             :     /* -------------------------------------------------------------------- */
     857             : 
     858             :     {
     859             :         double adfOutGT[6];
     860         140 :         if (GetGeoTransform(adfOutGT) != CE_None && GetGCPCount() == 0 &&
     861          65 :             GetSpatialRef() == nullptr)
     862             :         {
     863          65 :             if (bPanGeoTransformValid)
     864             :             {
     865          65 :                 SetGeoTransform(adfPanGT);
     866             :             }
     867          65 :             if (poPanSRS)
     868             :             {
     869          47 :                 SetSpatialRef(poPanSRS);
     870             :             }
     871             :         }
     872             :     }
     873             : 
     874             :     /* -------------------------------------------------------------------- */
     875             :     /*      Parse rest of PansharpeningOptions                              */
     876             :     /* -------------------------------------------------------------------- */
     877          75 :     iSpectralBand = 0;
     878         476 :     for (const CPLXMLNode *psIter = psOptions->psChild; psIter;
     879         401 :          psIter = psIter->psNext)
     880             :     {
     881         404 :         if (psIter->eType != CXT_Element ||
     882         404 :             !EQUAL(psIter->pszValue, "SpectralBand"))
     883         220 :             continue;
     884             : 
     885             :         GDALDataset *poDataset;
     886             :         GDALRasterBand *poBand;
     887             : 
     888         184 :         const char *pszDstBand = CPLGetXMLValue(psIter, "dstBand", nullptr);
     889         184 :         int nDstBand = -1;
     890         184 :         if (pszDstBand != nullptr)
     891             :         {
     892         178 :             nDstBand = atoi(pszDstBand);
     893         178 :             if (nDstBand <= 0)
     894             :             {
     895           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     896             :                          "SpectralBand.dstBand = '%s' invalid", pszDstBand);
     897           3 :                 goto error;
     898             :             }
     899             :         }
     900             : 
     901         183 :         if (nInputSpectralBandsIn && pahInputSpectralBandsIn != nullptr)
     902             :         {
     903          68 :             poBand = GDALRasterBand::FromHandle(
     904          34 :                 pahInputSpectralBandsIn[iSpectralBand]);
     905          34 :             poDataset = poBand->GetDataset();
     906          34 :             if (poDataset)
     907             :             {
     908          34 :                 poDataset = oMapNamesToDataset[CPLSPrintf("%p", poDataset)];
     909          34 :                 CPLAssert(poDataset);
     910          34 :                 if (poDataset)
     911          34 :                     poBand = poDataset->GetRasterBand(poBand->GetBand());
     912             :             }
     913             :         }
     914             :         else
     915             :         {
     916         149 :             osSourceFilename = CPLGetXMLValue(psIter, "SourceFilename", "");
     917         149 :             const bool bRelativeToVRT = CPL_TO_BOOL(atoi(
     918             :                 CPLGetXMLValue(psIter, "SourceFilename.relativetoVRT", "0")));
     919         149 :             if (bRelativeToVRT)
     920         188 :                 osSourceFilename = CPLProjectRelativeFilenameSafe(
     921          94 :                     pszVRTPathIn, osSourceFilename.c_str());
     922         149 :             poDataset = oMapNamesToDataset[osSourceFilename];
     923         149 :             CPLAssert(poDataset);
     924             :             const char *pszSourceBand =
     925         149 :                 CPLGetXMLValue(psIter, "SourceBand", "1");
     926         149 :             const int nBand = atoi(pszSourceBand);
     927         149 :             poBand = poDataset->GetRasterBand(nBand);
     928         149 :             if (poBand == nullptr)
     929             :             {
     930           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "%s invalid band of %s",
     931             :                          pszSourceBand, osSourceFilename.c_str());
     932           1 :                 goto error;
     933             :             }
     934             :         }
     935             : 
     936         182 :         if (bHasNoData)
     937             :         {
     938           3 :             double dfSpectralNoData = poPanBand->GetNoDataValue(&bHasNoData);
     939           3 :             if (bHasNoData && dfSpectralNoData != dfNoData)
     940           0 :                 bHasNoData = FALSE;
     941             :         }
     942             : 
     943         182 :         ahSpectralBands.push_back(poBand);
     944         182 :         if (nDstBand >= 1)
     945             :         {
     946         176 :             if (aMapDstBandToSpectralBand.find(nDstBand - 1) !=
     947         352 :                 aMapDstBandToSpectralBand.end())
     948             :             {
     949           1 :                 CPLError(
     950             :                     CE_Failure, CPLE_AppDefined,
     951             :                     "Another spectral band is already mapped to output band %d",
     952             :                     nDstBand);
     953           1 :                 goto error;
     954             :             }
     955         175 :             aMapDstBandToSpectralBand[nDstBand - 1] =
     956         175 :                 static_cast<int>(ahSpectralBands.size() - 1);
     957             :         }
     958             : 
     959         181 :         iSpectralBand++;
     960             :     }
     961             : 
     962          72 :     if (ahSpectralBands.empty())
     963             :     {
     964           1 :         CPLError(CE_Failure, CPLE_AppDefined, "No spectral band defined");
     965           1 :         goto error;
     966             :     }
     967             : 
     968             :     {
     969          71 :         const char *pszNoData = CPLGetXMLValue(psOptions, "NoData", nullptr);
     970          71 :         if (pszNoData)
     971             :         {
     972           7 :             if (EQUAL(pszNoData, "NONE"))
     973             :             {
     974           0 :                 m_bNoDataDisabled = TRUE;
     975           0 :                 bHasNoData = FALSE;
     976             :             }
     977             :             else
     978             :             {
     979           7 :                 bHasNoData = TRUE;
     980           7 :                 dfNoData = CPLAtof(pszNoData);
     981             :             }
     982             :         }
     983             :     }
     984             : 
     985          71 :     if (GetRasterCount() == 0)
     986             :     {
     987         156 :         for (int i = 0; i < static_cast<int>(aMapDstBandToSpectralBand.size());
     988             :              i++)
     989             :         {
     990         109 :             if (aMapDstBandToSpectralBand.find(i) ==
     991         218 :                 aMapDstBandToSpectralBand.end())
     992             :             {
     993           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     994             :                          "Hole in SpectralBand.dstBand numbering");
     995           1 :                 goto error;
     996             :             }
     997         108 :             GDALRasterBand *poInputBand = GDALRasterBand::FromHandle(
     998         108 :                 ahSpectralBands[aMapDstBandToSpectralBand[i]]);
     999             :             GDALRasterBand *poBand = new VRTPansharpenedRasterBand(
    1000         108 :                 this, i + 1, poInputBand->GetRasterDataType());
    1001         108 :             poBand->SetColorInterpretation(
    1002         108 :                 poInputBand->GetColorInterpretation());
    1003         108 :             if (bHasNoData)
    1004          13 :                 poBand->SetNoDataValue(dfNoData);
    1005         108 :             SetBand(i + 1, poBand);
    1006             :         }
    1007             :     }
    1008             :     else
    1009             :     {
    1010          23 :         int nIdxAsPansharpenedBand = 0;
    1011          91 :         for (int i = 0; i < nBands; i++)
    1012             :         {
    1013          69 :             if (static_cast<VRTRasterBand *>(GetRasterBand(i + 1))
    1014          69 :                     ->IsPansharpenRasterBand())
    1015             :             {
    1016          65 :                 if (aMapDstBandToSpectralBand.find(i) ==
    1017         130 :                     aMapDstBandToSpectralBand.end())
    1018             :                 {
    1019           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1020             :                              "Band %d of type VRTPansharpenedRasterBand, but "
    1021             :                              "no corresponding SpectralBand",
    1022             :                              i + 1);
    1023           1 :                     goto error;
    1024             :                 }
    1025             :                 else
    1026             :                 {
    1027             :                     static_cast<VRTPansharpenedRasterBand *>(
    1028          64 :                         GetRasterBand(i + 1))
    1029          64 :                         ->SetIndexAsPansharpenedBand(nIdxAsPansharpenedBand);
    1030          64 :                     nIdxAsPansharpenedBand++;
    1031             :                 }
    1032             :             }
    1033             :         }
    1034             :     }
    1035             : 
    1036             :     // Figure out bit depth
    1037             :     {
    1038             :         const char *pszBitDepth =
    1039          69 :             CPLGetXMLValue(psOptions, "BitDepth", nullptr);
    1040          69 :         if (pszBitDepth == nullptr)
    1041          56 :             pszBitDepth = GDALRasterBand::FromHandle(ahSpectralBands[0])
    1042          56 :                               ->GetMetadataItem("NBITS", "IMAGE_STRUCTURE");
    1043          69 :         if (pszBitDepth)
    1044          15 :             nBitDepth = atoi(pszBitDepth);
    1045          69 :         if (nBitDepth)
    1046             :         {
    1047          44 :             for (int i = 0; i < nBands; i++)
    1048             :             {
    1049          29 :                 if (!static_cast<VRTRasterBand *>(GetRasterBand(i + 1))
    1050          29 :                          ->IsPansharpenRasterBand())
    1051           2 :                     continue;
    1052          27 :                 if (GetRasterBand(i + 1)->GetMetadataItem(
    1053          27 :                         "NBITS", "IMAGE_STRUCTURE") == nullptr)
    1054             :                 {
    1055          27 :                     if (nBitDepth != 8 && nBitDepth != 16 && nBitDepth != 32)
    1056             :                     {
    1057          12 :                         GetRasterBand(i + 1)->SetMetadataItem(
    1058             :                             "NBITS", CPLSPrintf("%d", nBitDepth),
    1059           6 :                             "IMAGE_STRUCTURE");
    1060             :                     }
    1061             :                 }
    1062           0 :                 else if (nBitDepth == 8 || nBitDepth == 16 || nBitDepth == 32)
    1063             :                 {
    1064           0 :                     GetRasterBand(i + 1)->SetMetadataItem("NBITS", nullptr,
    1065           0 :                                                           "IMAGE_STRUCTURE");
    1066             :                 }
    1067             :             }
    1068             :         }
    1069             :     }
    1070             : 
    1071          69 :     if (GDALGetRasterBandXSize(ahSpectralBands[0]) >
    1072         138 :             GDALGetRasterBandXSize(poPanBand) ||
    1073          69 :         GDALGetRasterBandYSize(ahSpectralBands[0]) >
    1074          69 :             GDALGetRasterBandYSize(poPanBand))
    1075             :     {
    1076           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    1077             :                  "Dimensions of spectral band larger than panchro band");
    1078             :     }
    1079             : 
    1080          69 :     aMapDstBandToSpectralBandIter = aMapDstBandToSpectralBand.begin();
    1081         236 :     for (; aMapDstBandToSpectralBandIter != aMapDstBandToSpectralBand.end();
    1082         167 :          ++aMapDstBandToSpectralBandIter)
    1083             :     {
    1084         168 :         const int nDstBand = 1 + aMapDstBandToSpectralBandIter->first;
    1085         335 :         if (nDstBand > nBands ||
    1086         167 :             !static_cast<VRTRasterBand *>(GetRasterBand(nDstBand))
    1087         167 :                  ->IsPansharpenRasterBand())
    1088             :         {
    1089           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1090             :                      "SpectralBand.dstBand = '%d' invalid", nDstBand);
    1091           1 :             goto error;
    1092             :         }
    1093             :     }
    1094             : 
    1095          68 :     if (adfWeights.empty())
    1096             :     {
    1097         158 :         for (int i = 0; i < static_cast<int>(ahSpectralBands.size()); i++)
    1098             :         {
    1099         114 :             adfWeights.push_back(1.0 / ahSpectralBands.size());
    1100             :         }
    1101             :     }
    1102          24 :     else if (adfWeights.size() != ahSpectralBands.size())
    1103             :     {
    1104           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1105             :                  "%d weights defined, but %d input spectral bands",
    1106           1 :                  static_cast<int>(adfWeights.size()),
    1107           1 :                  static_cast<int>(ahSpectralBands.size()));
    1108           1 :         goto error;
    1109             :     }
    1110             : 
    1111          67 :     if (aMapDstBandToSpectralBand.empty())
    1112             :     {
    1113           1 :         CPLError(CE_Warning, CPLE_AppDefined,
    1114             :                  "No spectral band is mapped to an output band");
    1115             :     }
    1116             : 
    1117             :     /* -------------------------------------------------------------------- */
    1118             :     /*      Instantiate poPansharpener                                      */
    1119             :     /* -------------------------------------------------------------------- */
    1120          67 :     psPanOptions = GDALCreatePansharpenOptions();
    1121          67 :     psPanOptions->ePansharpenAlg = GDAL_PSH_WEIGHTED_BROVEY;
    1122          67 :     psPanOptions->eResampleAlg = eResampleAlg;
    1123          67 :     psPanOptions->nBitDepth = nBitDepth;
    1124          67 :     psPanOptions->nWeightCount = static_cast<int>(adfWeights.size());
    1125          67 :     psPanOptions->padfWeights =
    1126          67 :         static_cast<double *>(CPLMalloc(sizeof(double) * adfWeights.size()));
    1127          67 :     memcpy(psPanOptions->padfWeights, &adfWeights[0],
    1128          67 :            sizeof(double) * adfWeights.size());
    1129          67 :     psPanOptions->hPanchroBand = poPanBand;
    1130          67 :     psPanOptions->nInputSpectralBands =
    1131          67 :         static_cast<int>(ahSpectralBands.size());
    1132          67 :     psPanOptions->pahInputSpectralBands = static_cast<GDALRasterBandH *>(
    1133          67 :         CPLMalloc(sizeof(GDALRasterBandH) * ahSpectralBands.size()));
    1134          67 :     memcpy(psPanOptions->pahInputSpectralBands, &ahSpectralBands[0],
    1135          67 :            sizeof(GDALRasterBandH) * ahSpectralBands.size());
    1136          67 :     psPanOptions->nOutPansharpenedBands =
    1137          67 :         static_cast<int>(aMapDstBandToSpectralBand.size());
    1138          67 :     psPanOptions->panOutPansharpenedBands = static_cast<int *>(
    1139          67 :         CPLMalloc(sizeof(int) * aMapDstBandToSpectralBand.size()));
    1140          67 :     aMapDstBandToSpectralBandIter = aMapDstBandToSpectralBand.begin();
    1141         229 :     for (int i = 0;
    1142         229 :          aMapDstBandToSpectralBandIter != aMapDstBandToSpectralBand.end();
    1143         162 :          ++aMapDstBandToSpectralBandIter, ++i)
    1144             :     {
    1145         162 :         psPanOptions->panOutPansharpenedBands[i] =
    1146         162 :             aMapDstBandToSpectralBandIter->second;
    1147             :     }
    1148          67 :     psPanOptions->bHasNoData = bHasNoData;
    1149          67 :     psPanOptions->dfNoData = dfNoData;
    1150          67 :     psPanOptions->nThreads = nThreads;
    1151             : 
    1152          67 :     if (nBands == psPanOptions->nOutPansharpenedBands)
    1153          63 :         SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    1154             : 
    1155          67 :     m_poPansharpener = new GDALPansharpenOperation();
    1156          67 :     eErr = m_poPansharpener->Initialize(psPanOptions);
    1157          67 :     if (eErr != CE_None)
    1158             :     {
    1159             :         // Delete the pansharper object before closing the dataset
    1160             :         // because it may have warped the bands into an intermediate VRT
    1161           1 :         delete m_poPansharpener;
    1162           1 :         m_poPansharpener = nullptr;
    1163             : 
    1164             :         // Close in reverse order (VRT firsts and real datasets after)
    1165           4 :         for (int i = static_cast<int>(m_apoDatasetsToReleaseRef.size()) - 1;
    1166           4 :              i >= 0; i--)
    1167             :         {
    1168           3 :             m_apoDatasetsToReleaseRef[i].reset();
    1169             :         }
    1170           1 :         m_apoDatasetsToReleaseRef.clear();
    1171             :     }
    1172          67 :     GDALDestroyPansharpenOptions(psPanOptions);
    1173             : 
    1174          67 :     return eErr;
    1175             : 
    1176          15 : error:
    1177             :     // Close in reverse order (VRT firsts and real datasets after)
    1178          63 :     for (int i = static_cast<int>(m_apoDatasetsToReleaseRef.size()) - 1; i >= 0;
    1179             :          i--)
    1180             :     {
    1181          48 :         m_apoDatasetsToReleaseRef[i].reset();
    1182             :     }
    1183          15 :     m_apoDatasetsToReleaseRef.clear();
    1184          15 :     return CE_Failure;
    1185             : }
    1186             : 
    1187             : /************************************************************************/
    1188             : /*                           SerializeToXML()                           */
    1189             : /************************************************************************/
    1190             : 
    1191           7 : CPLXMLNode *VRTPansharpenedDataset::SerializeToXML(const char *pszVRTPathIn)
    1192             : 
    1193             : {
    1194           7 :     CPLXMLNode *psTree = VRTDataset::SerializeToXML(pszVRTPathIn);
    1195             : 
    1196           7 :     if (psTree == nullptr)
    1197           0 :         return psTree;
    1198             : 
    1199             :     /* -------------------------------------------------------------------- */
    1200             :     /*      Set subclass.                                                   */
    1201             :     /* -------------------------------------------------------------------- */
    1202           7 :     CPLCreateXMLNode(CPLCreateXMLNode(psTree, CXT_Attribute, "subClass"),
    1203             :                      CXT_Text, "VRTPansharpenedDataset");
    1204             : 
    1205             :     /* -------------------------------------------------------------------- */
    1206             :     /*      Serialize the block size.                                       */
    1207             :     /* -------------------------------------------------------------------- */
    1208           7 :     CPLCreateXMLElementAndValue(psTree, "BlockXSize",
    1209             :                                 CPLSPrintf("%d", m_nBlockXSize));
    1210           7 :     CPLCreateXMLElementAndValue(psTree, "BlockYSize",
    1211             :                                 CPLSPrintf("%d", m_nBlockYSize));
    1212             : 
    1213             :     /* -------------------------------------------------------------------- */
    1214             :     /*      Serialize the options.                                          */
    1215             :     /* -------------------------------------------------------------------- */
    1216           7 :     if (m_poPansharpener == nullptr)
    1217           0 :         return psTree;
    1218           7 :     GDALPansharpenOptions *psOptions = m_poPansharpener->GetOptions();
    1219           7 :     if (psOptions == nullptr)
    1220           0 :         return psTree;
    1221             : 
    1222             :     CPLXMLNode *psOptionsNode =
    1223           7 :         CPLCreateXMLNode(psTree, CXT_Element, "PansharpeningOptions");
    1224             : 
    1225           7 :     if (psOptions->ePansharpenAlg == GDAL_PSH_WEIGHTED_BROVEY)
    1226             :     {
    1227           7 :         CPLCreateXMLElementAndValue(psOptionsNode, "Algorithm",
    1228             :                                     "WeightedBrovey");
    1229             :     }
    1230             :     else
    1231             :     {
    1232           0 :         CPLAssert(false);
    1233             :     }
    1234           7 :     if (psOptions->nWeightCount)
    1235             :     {
    1236          14 :         CPLString osWeights;
    1237          27 :         for (int i = 0; i < psOptions->nWeightCount; i++)
    1238             :         {
    1239          20 :             if (i)
    1240          13 :                 osWeights += ",";
    1241          20 :             osWeights += CPLSPrintf("%.16g", psOptions->padfWeights[i]);
    1242             :         }
    1243           7 :         CPLCreateXMLElementAndValue(
    1244             :             CPLCreateXMLNode(psOptionsNode, CXT_Element, "AlgorithmOptions"),
    1245             :             "Weights", osWeights.c_str());
    1246             :     }
    1247           7 :     CPLCreateXMLElementAndValue(
    1248             :         psOptionsNode, "Resampling",
    1249             :         GDALRasterIOGetResampleAlg(psOptions->eResampleAlg));
    1250             : 
    1251           7 :     if (psOptions->nThreads == -1)
    1252             :     {
    1253           6 :         CPLCreateXMLElementAndValue(psOptionsNode, "NumThreads", "ALL_CPUS");
    1254             :     }
    1255           1 :     else if (psOptions->nThreads > 1)
    1256             :     {
    1257           0 :         CPLCreateXMLElementAndValue(psOptionsNode, "NumThreads",
    1258             :                                     CPLSPrintf("%d", psOptions->nThreads));
    1259             :     }
    1260             : 
    1261           7 :     if (psOptions->nBitDepth)
    1262           0 :         CPLCreateXMLElementAndValue(psOptionsNode, "BitDepth",
    1263             :                                     CPLSPrintf("%d", psOptions->nBitDepth));
    1264             : 
    1265           7 :     const char *pszAdjust = nullptr;
    1266           7 :     switch (m_eGTAdjustment)
    1267             :     {
    1268           7 :         case GTAdjust_Union:
    1269           7 :             pszAdjust = "Union";
    1270           7 :             break;
    1271           0 :         case GTAdjust_Intersection:
    1272           0 :             pszAdjust = "Intersection";
    1273           0 :             break;
    1274           0 :         case GTAdjust_None:
    1275           0 :             pszAdjust = "None";
    1276           0 :             break;
    1277           0 :         case GTAdjust_NoneWithoutWarning:
    1278           0 :             pszAdjust = "NoneWithoutWarning";
    1279           0 :             break;
    1280           0 :         default:
    1281           0 :             break;
    1282             :     }
    1283             : 
    1284           7 :     if (psOptions->bHasNoData)
    1285             :     {
    1286           1 :         CPLCreateXMLElementAndValue(psOptionsNode, "NoData",
    1287             :                                     CPLSPrintf("%.16g", psOptions->dfNoData));
    1288             :     }
    1289           6 :     else if (m_bNoDataDisabled)
    1290             :     {
    1291           0 :         CPLCreateXMLElementAndValue(psOptionsNode, "NoData", "None");
    1292             :     }
    1293             : 
    1294           7 :     if (pszAdjust)
    1295           7 :         CPLCreateXMLElementAndValue(psOptionsNode, "SpatialExtentAdjustment",
    1296             :                                     pszAdjust);
    1297             : 
    1298           7 :     if (psOptions->hPanchroBand)
    1299             :     {
    1300             :         CPLXMLNode *psBand =
    1301           7 :             CPLCreateXMLNode(psOptionsNode, CXT_Element, "PanchroBand");
    1302             :         GDALRasterBand *poBand =
    1303           7 :             GDALRasterBand::FromHandle(psOptions->hPanchroBand);
    1304           7 :         if (poBand->GetDataset())
    1305             :         {
    1306           7 :             auto poPanchoDS = poBand->GetDataset();
    1307             :             std::map<CPLString, CPLString>::iterator oIter =
    1308           7 :                 m_oMapToRelativeFilenames.find(poPanchoDS->GetDescription());
    1309           7 :             if (oIter == m_oMapToRelativeFilenames.end())
    1310             :             {
    1311           6 :                 CPLCreateXMLElementAndValue(psBand, "SourceFilename",
    1312           6 :                                             poPanchoDS->GetDescription());
    1313             :             }
    1314             :             else
    1315             :             {
    1316           1 :                 CPLXMLNode *psSourceFilename = CPLCreateXMLElementAndValue(
    1317           1 :                     psBand, "SourceFilename", oIter->second);
    1318           1 :                 CPLCreateXMLNode(CPLCreateXMLNode(psSourceFilename,
    1319             :                                                   CXT_Attribute,
    1320             :                                                   "relativeToVRT"),
    1321             :                                  CXT_Text, "1");
    1322             :             }
    1323             : 
    1324           7 :             GDALSerializeOpenOptionsToXML(psBand, poPanchoDS->GetOpenOptions());
    1325             : 
    1326           7 :             CPLCreateXMLElementAndValue(psBand, "SourceBand",
    1327             :                                         CPLSPrintf("%d", poBand->GetBand()));
    1328             :         }
    1329             :     }
    1330          27 :     for (int i = 0; i < psOptions->nInputSpectralBands; i++)
    1331             :     {
    1332             :         CPLXMLNode *psBand =
    1333          20 :             CPLCreateXMLNode(psOptionsNode, CXT_Element, "SpectralBand");
    1334             : 
    1335          39 :         for (int j = 0; j < psOptions->nOutPansharpenedBands; j++)
    1336             :         {
    1337          39 :             if (psOptions->panOutPansharpenedBands[j] == i)
    1338             :             {
    1339          40 :                 for (int k = 0; k < nBands; k++)
    1340             :                 {
    1341          40 :                     if (static_cast<VRTRasterBand *>(GetRasterBand(k + 1))
    1342          40 :                             ->IsPansharpenRasterBand())
    1343             :                     {
    1344          39 :                         if (static_cast<VRTPansharpenedRasterBand *>(
    1345          39 :                                 GetRasterBand(k + 1))
    1346          39 :                                 ->GetIndexAsPansharpenedBand() == j)
    1347             :                         {
    1348          20 :                             CPLCreateXMLNode(CPLCreateXMLNode(psBand,
    1349             :                                                               CXT_Attribute,
    1350             :                                                               "dstBand"),
    1351             :                                              CXT_Text, CPLSPrintf("%d", k + 1));
    1352          20 :                             break;
    1353             :                         }
    1354             :                     }
    1355             :                 }
    1356          20 :                 break;
    1357             :             }
    1358             :         }
    1359             : 
    1360             :         GDALRasterBand *poBand =
    1361          20 :             GDALRasterBand::FromHandle(psOptions->pahInputSpectralBands[i]);
    1362          20 :         if (poBand->GetDataset())
    1363             :         {
    1364          20 :             auto poSpectralDS = poBand->GetDataset();
    1365             :             std::map<CPLString, CPLString>::iterator oIter =
    1366          20 :                 m_oMapToRelativeFilenames.find(poSpectralDS->GetDescription());
    1367          20 :             if (oIter == m_oMapToRelativeFilenames.end())
    1368             :             {
    1369          20 :                 CPLCreateXMLElementAndValue(psBand, "SourceFilename",
    1370          20 :                                             poSpectralDS->GetDescription());
    1371             :             }
    1372             :             else
    1373             :             {
    1374           0 :                 CPLXMLNode *psSourceFilename = CPLCreateXMLElementAndValue(
    1375           0 :                     psBand, "SourceFilename", oIter->second);
    1376           0 :                 CPLCreateXMLNode(CPLCreateXMLNode(psSourceFilename,
    1377             :                                                   CXT_Attribute,
    1378             :                                                   "relativeToVRT"),
    1379             :                                  CXT_Text, "1");
    1380             :             }
    1381             : 
    1382          20 :             GDALSerializeOpenOptionsToXML(psBand,
    1383          20 :                                           poSpectralDS->GetOpenOptions());
    1384             : 
    1385          20 :             CPLCreateXMLElementAndValue(psBand, "SourceBand",
    1386             :                                         CPLSPrintf("%d", poBand->GetBand()));
    1387             :         }
    1388             :     }
    1389             : 
    1390           7 :     return psTree;
    1391             : }
    1392             : 
    1393             : /************************************************************************/
    1394             : /*                            GetBlockSize()                            */
    1395             : /************************************************************************/
    1396             : 
    1397         192 : void VRTPansharpenedDataset::GetBlockSize(int *pnBlockXSize,
    1398             :                                           int *pnBlockYSize) const
    1399             : 
    1400             : {
    1401         192 :     assert(nullptr != pnBlockXSize);
    1402         192 :     assert(nullptr != pnBlockYSize);
    1403             : 
    1404         192 :     *pnBlockXSize = m_nBlockXSize;
    1405         192 :     *pnBlockYSize = m_nBlockYSize;
    1406         192 : }
    1407             : 
    1408             : /************************************************************************/
    1409             : /*                              AddBand()                               */
    1410             : /************************************************************************/
    1411             : 
    1412           1 : CPLErr VRTPansharpenedDataset::AddBand(CPL_UNUSED GDALDataType eType,
    1413             :                                        CPL_UNUSED char **papszOptions)
    1414             : 
    1415             : {
    1416           1 :     CPLError(CE_Failure, CPLE_NotSupported, "AddBand() not supported");
    1417             : 
    1418           1 :     return CE_Failure;
    1419             : }
    1420             : 
    1421             : /************************************************************************/
    1422             : /*                              IRasterIO()                             */
    1423             : /************************************************************************/
    1424             : 
    1425          21 : CPLErr VRTPansharpenedDataset::IRasterIO(
    1426             :     GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
    1427             :     void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
    1428             :     int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
    1429             :     GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
    1430             : {
    1431          21 :     if (eRWFlag == GF_Write)
    1432           0 :         return CE_Failure;
    1433             : 
    1434             :     /* Try to pass the request to the most appropriate overview dataset */
    1435          21 :     if (nBufXSize < nXSize && nBufYSize < nYSize)
    1436             :     {
    1437             :         int bTried;
    1438           1 :         CPLErr eErr = TryOverviewRasterIO(
    1439             :             eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
    1440             :             eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace,
    1441             :             nBandSpace, psExtraArg, &bTried);
    1442           1 :         if (bTried)
    1443           0 :             return eErr;
    1444             :     }
    1445             : 
    1446          21 :     const int nDataTypeSize = GDALGetDataTypeSizeBytes(eBufType);
    1447          21 :     if (nXSize == nBufXSize && nYSize == nBufYSize &&
    1448          20 :         nDataTypeSize == nPixelSpace && nLineSpace == nPixelSpace * nBufXSize &&
    1449          20 :         nBandSpace == nLineSpace * nBufYSize && nBandCount == nBands)
    1450             :     {
    1451          82 :         for (int i = 0; i < nBands; i++)
    1452             :         {
    1453         124 :             if (panBandMap[i] != i + 1 ||
    1454          62 :                 !static_cast<VRTRasterBand *>(GetRasterBand(i + 1))
    1455          62 :                      ->IsPansharpenRasterBand())
    1456             :             {
    1457           0 :                 goto default_path;
    1458             :             }
    1459             :         }
    1460             : 
    1461             :         //{static int bDone = 0; if (!bDone) printf("(2)\n"); bDone = 1; }
    1462          20 :         return m_poPansharpener->ProcessRegion(nXOff, nYOff, nXSize, nYSize,
    1463          20 :                                                pData, eBufType);
    1464             :     }
    1465             : 
    1466           1 : default_path:
    1467             :     //{static int bDone = 0; if (!bDone) printf("(3)\n"); bDone = 1; }
    1468           1 :     return VRTDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
    1469             :                                  nBufXSize, nBufYSize, eBufType, nBandCount,
    1470             :                                  panBandMap, nPixelSpace, nLineSpace,
    1471           1 :                                  nBandSpace, psExtraArg);
    1472             : }
    1473             : 
    1474             : /************************************************************************/
    1475             : /* ==================================================================== */
    1476             : /*                        VRTPansharpenedRasterBand                     */
    1477             : /* ==================================================================== */
    1478             : /************************************************************************/
    1479             : 
    1480             : /************************************************************************/
    1481             : /*                        VRTPansharpenedRasterBand()                   */
    1482             : /************************************************************************/
    1483             : 
    1484         192 : VRTPansharpenedRasterBand::VRTPansharpenedRasterBand(GDALDataset *poDSIn,
    1485             :                                                      int nBandIn,
    1486         192 :                                                      GDALDataType eDataTypeIn)
    1487         192 :     : m_nIndexAsPansharpenedBand(nBandIn - 1)
    1488             : {
    1489         192 :     Initialize(poDSIn->GetRasterXSize(), poDSIn->GetRasterYSize());
    1490             : 
    1491         192 :     poDS = poDSIn;
    1492         192 :     nBand = nBandIn;
    1493         192 :     eAccess = GA_Update;
    1494         192 :     eDataType = eDataTypeIn;
    1495             : 
    1496         192 :     static_cast<VRTPansharpenedDataset *>(poDS)->GetBlockSize(&nBlockXSize,
    1497             :                                                               &nBlockYSize);
    1498         192 : }
    1499             : 
    1500             : /************************************************************************/
    1501             : /*                        ~VRTPansharpenedRasterBand()                  */
    1502             : /************************************************************************/
    1503             : 
    1504         384 : VRTPansharpenedRasterBand::~VRTPansharpenedRasterBand()
    1505             : 
    1506             : {
    1507         192 :     FlushCache(true);
    1508         384 : }
    1509             : 
    1510             : /************************************************************************/
    1511             : /*                             IReadBlock()                             */
    1512             : /************************************************************************/
    1513             : 
    1514          26 : CPLErr VRTPansharpenedRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
    1515             :                                              void *pImage)
    1516             : 
    1517             : {
    1518          26 :     const int nReqXOff = nBlockXOff * nBlockXSize;
    1519          26 :     const int nReqYOff = nBlockYOff * nBlockYSize;
    1520          26 :     int nReqXSize = nBlockXSize;
    1521          26 :     int nReqYSize = nBlockYSize;
    1522          26 :     if (nReqXOff + nReqXSize > nRasterXSize)
    1523          13 :         nReqXSize = nRasterXSize - nReqXOff;
    1524          26 :     if (nReqYOff + nReqYSize > nRasterYSize)
    1525          22 :         nReqYSize = nRasterYSize - nReqYOff;
    1526             : 
    1527             :     //{static int bDone = 0; if (!bDone) printf("(4)\n"); bDone = 1; }
    1528          26 :     const int nDataTypeSize = GDALGetDataTypeSize(eDataType) / 8;
    1529             :     GDALRasterIOExtraArg sExtraArg;
    1530          26 :     INIT_RASTERIO_EXTRA_ARG(sExtraArg);
    1531          52 :     if (IRasterIO(GF_Read, nReqXOff, nReqYOff, nReqXSize, nReqYSize, pImage,
    1532             :                   nReqXSize, nReqYSize, eDataType, nDataTypeSize,
    1533          26 :                   static_cast<GSpacing>(nDataTypeSize) * nReqXSize,
    1534          26 :                   &sExtraArg) != CE_None)
    1535             :     {
    1536           0 :         return CE_Failure;
    1537             :     }
    1538             : 
    1539          26 :     if (nReqXSize < nBlockXSize)
    1540             :     {
    1541        5613 :         for (int j = nReqYSize - 1; j >= 0; j--)
    1542             :         {
    1543        5600 :             memmove(static_cast<GByte *>(pImage) +
    1544        5600 :                         static_cast<size_t>(j) * nDataTypeSize * nBlockXSize,
    1545        5600 :                     static_cast<GByte *>(pImage) +
    1546        5600 :                         static_cast<size_t>(j) * nDataTypeSize * nReqXSize,
    1547        5600 :                     static_cast<size_t>(nReqXSize) * nDataTypeSize);
    1548        5600 :             memset(static_cast<GByte *>(pImage) +
    1549        5600 :                        (static_cast<size_t>(j) * nBlockXSize + nReqXSize) *
    1550        5600 :                            nDataTypeSize,
    1551             :                    0,
    1552        5600 :                    static_cast<size_t>(nBlockXSize - nReqXSize) *
    1553        5600 :                        nDataTypeSize);
    1554             :         }
    1555             :     }
    1556          26 :     if (nReqYSize < nBlockYSize)
    1557             :     {
    1558          22 :         memset(static_cast<GByte *>(pImage) +
    1559          22 :                    static_cast<size_t>(nReqYSize) * nBlockXSize * nDataTypeSize,
    1560             :                0,
    1561          22 :                static_cast<size_t>(nBlockYSize - nReqYSize) * nBlockXSize *
    1562          22 :                    nDataTypeSize);
    1563             :     }
    1564             : 
    1565             :     // Cache other bands
    1566          26 :     CPLErr eErr = CE_None;
    1567          26 :     VRTPansharpenedDataset *poGDS = static_cast<VRTPansharpenedDataset *>(poDS);
    1568          26 :     if (poGDS->nBands != 1 && !poGDS->m_bLoadingOtherBands)
    1569             :     {
    1570          10 :         poGDS->m_bLoadingOtherBands = TRUE;
    1571             : 
    1572          36 :         for (int iOtherBand = 1; iOtherBand <= poGDS->nBands; iOtherBand++)
    1573             :         {
    1574          26 :             if (iOtherBand == nBand)
    1575          10 :                 continue;
    1576             : 
    1577             :             GDALRasterBlock *poBlock =
    1578          16 :                 poGDS->GetRasterBand(iOtherBand)
    1579          16 :                     ->GetLockedBlockRef(nBlockXOff, nBlockYOff);
    1580          16 :             if (poBlock == nullptr)
    1581             :             {
    1582           0 :                 eErr = CE_Failure;
    1583           0 :                 break;
    1584             :             }
    1585          16 :             poBlock->DropLock();
    1586             :         }
    1587             : 
    1588          10 :         poGDS->m_bLoadingOtherBands = FALSE;
    1589             :     }
    1590             : 
    1591          26 :     return eErr;
    1592             : }
    1593             : 
    1594             : /************************************************************************/
    1595             : /*                              IRasterIO()                             */
    1596             : /************************************************************************/
    1597             : 
    1598         127 : CPLErr VRTPansharpenedRasterBand::IRasterIO(
    1599             :     GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
    1600             :     void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
    1601             :     GSpacing nPixelSpace, GSpacing nLineSpace, GDALRasterIOExtraArg *psExtraArg)
    1602             : {
    1603         127 :     if (eRWFlag == GF_Write)
    1604           0 :         return CE_Failure;
    1605             : 
    1606         127 :     VRTPansharpenedDataset *poGDS = static_cast<VRTPansharpenedDataset *>(poDS);
    1607             : 
    1608             :     /* Try to pass the request to the most appropriate overview dataset */
    1609         127 :     if (nBufXSize < nXSize && nBufYSize < nYSize)
    1610             :     {
    1611             :         int bTried;
    1612           3 :         CPLErr eErr = TryOverviewRasterIO(
    1613             :             eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
    1614             :             eBufType, nPixelSpace, nLineSpace, psExtraArg, &bTried);
    1615           3 :         if (bTried)
    1616           0 :             return eErr;
    1617             :     }
    1618             : 
    1619         127 :     const int nDataTypeSize = GDALGetDataTypeSizeBytes(eBufType);
    1620         127 :     if (nDataTypeSize > 0 && nXSize == nBufXSize && nYSize == nBufYSize &&
    1621         124 :         nDataTypeSize == nPixelSpace && nLineSpace == nPixelSpace * nBufXSize)
    1622             :     {
    1623             :         GDALPansharpenOptions *psOptions =
    1624         124 :             poGDS->m_poPansharpener->GetOptions();
    1625             : 
    1626             :         // Have we already done this request for another band ?
    1627             :         // If so use the cached result
    1628         124 :         const size_t nBufferSizePerBand =
    1629         124 :             static_cast<size_t>(nXSize) * nYSize * nDataTypeSize;
    1630         124 :         if (nXOff == poGDS->m_nLastBandRasterIOXOff &&
    1631         118 :             nYOff >= poGDS->m_nLastBandRasterIOYOff &&
    1632         111 :             nXSize == poGDS->m_nLastBandRasterIOXSize &&
    1633          69 :             nYOff + nYSize <= poGDS->m_nLastBandRasterIOYOff +
    1634          69 :                                   poGDS->m_nLastBandRasterIOYSize &&
    1635          59 :             eBufType == poGDS->m_eLastBandRasterIODataType)
    1636             :         {
    1637             :             //{static int bDone = 0; if (!bDone) printf("(6)\n"); bDone = 1; }
    1638          53 :             if (poGDS->m_pabyLastBufferBandRasterIO == nullptr)
    1639           0 :                 return CE_Failure;
    1640          53 :             const size_t nBufferSizePerBandCached =
    1641          53 :                 static_cast<size_t>(nXSize) * poGDS->m_nLastBandRasterIOYSize *
    1642          53 :                 nDataTypeSize;
    1643          53 :             memcpy(pData,
    1644          53 :                    poGDS->m_pabyLastBufferBandRasterIO +
    1645          53 :                        nBufferSizePerBandCached * m_nIndexAsPansharpenedBand +
    1646          53 :                        static_cast<size_t>(nYOff -
    1647          53 :                                            poGDS->m_nLastBandRasterIOYOff) *
    1648          53 :                            nXSize * nDataTypeSize,
    1649             :                    nBufferSizePerBand);
    1650          53 :             return CE_None;
    1651             :         }
    1652             : 
    1653          71 :         int nYSizeToCache = nYSize;
    1654          71 :         if (nYSize == 1 && nXSize == nRasterXSize)
    1655             :         {
    1656             :             //{static int bDone = 0; if (!bDone) printf("(7)\n"); bDone = 1; }
    1657             :             // For efficiency, try to cache at leak 256 K
    1658           0 :             nYSizeToCache = (256 * 1024) / nXSize / nDataTypeSize;
    1659           0 :             if (nYSizeToCache == 0)
    1660           0 :                 nYSizeToCache = 1;
    1661           0 :             else if (nYOff + nYSizeToCache > nRasterYSize)
    1662           0 :                 nYSizeToCache = nRasterYSize - nYOff;
    1663             :         }
    1664          71 :         const GUIntBig nBufferSize = static_cast<GUIntBig>(nXSize) *
    1665          71 :                                      nYSizeToCache * nDataTypeSize *
    1666          71 :                                      psOptions->nOutPansharpenedBands;
    1667             :         // Check the we don't overflow (for 32 bit platforms)
    1668          71 :         if (nBufferSize > std::numeric_limits<size_t>::max() / 2)
    1669             :         {
    1670           0 :             CPLError(CE_Failure, CPLE_OutOfMemory,
    1671             :                      "Out of memory error while allocating working buffers");
    1672           0 :             return CE_Failure;
    1673             :         }
    1674             :         GByte *pabyTemp = static_cast<GByte *>(
    1675          71 :             VSI_REALLOC_VERBOSE(poGDS->m_pabyLastBufferBandRasterIO,
    1676             :                                 static_cast<size_t>(nBufferSize)));
    1677          71 :         if (pabyTemp == nullptr)
    1678             :         {
    1679           0 :             return CE_Failure;
    1680             :         }
    1681          71 :         poGDS->m_nLastBandRasterIOXOff = nXOff;
    1682          71 :         poGDS->m_nLastBandRasterIOYOff = nYOff;
    1683          71 :         poGDS->m_nLastBandRasterIOXSize = nXSize;
    1684          71 :         poGDS->m_nLastBandRasterIOYSize = nYSizeToCache;
    1685          71 :         poGDS->m_eLastBandRasterIODataType = eBufType;
    1686          71 :         poGDS->m_pabyLastBufferBandRasterIO = pabyTemp;
    1687             : 
    1688         142 :         CPLErr eErr = poGDS->m_poPansharpener->ProcessRegion(
    1689             :             nXOff, nYOff, nXSize, nYSizeToCache,
    1690          71 :             poGDS->m_pabyLastBufferBandRasterIO, eBufType);
    1691          71 :         if (eErr == CE_None)
    1692             :         {
    1693             :             //{static int bDone = 0; if (!bDone) printf("(8)\n"); bDone = 1; }
    1694          71 :             size_t nBufferSizePerBandCached = static_cast<size_t>(nXSize) *
    1695          71 :                                               poGDS->m_nLastBandRasterIOYSize *
    1696          71 :                                               nDataTypeSize;
    1697          71 :             memcpy(pData,
    1698          71 :                    poGDS->m_pabyLastBufferBandRasterIO +
    1699          71 :                        nBufferSizePerBandCached * m_nIndexAsPansharpenedBand,
    1700             :                    nBufferSizePerBand);
    1701             :         }
    1702             :         else
    1703             :         {
    1704           0 :             VSIFree(poGDS->m_pabyLastBufferBandRasterIO);
    1705           0 :             poGDS->m_pabyLastBufferBandRasterIO = nullptr;
    1706             :         }
    1707          71 :         return eErr;
    1708             :     }
    1709             : 
    1710             :     //{static int bDone = 0; if (!bDone) printf("(9)\n"); bDone = 1; }
    1711           3 :     CPLErr eErr = VRTRasterBand::IRasterIO(
    1712             :         eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
    1713             :         eBufType, nPixelSpace, nLineSpace, psExtraArg);
    1714             : 
    1715           3 :     return eErr;
    1716             : }
    1717             : 
    1718             : /************************************************************************/
    1719             : /*                           SerializeToXML()                           */
    1720             : /************************************************************************/
    1721             : 
    1722             : CPLXMLNode *
    1723          20 : VRTPansharpenedRasterBand::SerializeToXML(const char *pszVRTPathIn,
    1724             :                                           bool &bHasWarnedAboutRAMUsage,
    1725             :                                           size_t &nAccRAMUsage)
    1726             : 
    1727             : {
    1728          20 :     CPLXMLNode *psTree = VRTRasterBand::SerializeToXML(
    1729             :         pszVRTPathIn, bHasWarnedAboutRAMUsage, nAccRAMUsage);
    1730             : 
    1731             :     /* -------------------------------------------------------------------- */
    1732             :     /*      Set subclass.                                                   */
    1733             :     /* -------------------------------------------------------------------- */
    1734          20 :     CPLCreateXMLNode(CPLCreateXMLNode(psTree, CXT_Attribute, "subClass"),
    1735             :                      CXT_Text, "VRTPansharpenedRasterBand");
    1736             : 
    1737          20 :     return psTree;
    1738             : }
    1739             : 
    1740             : /************************************************************************/
    1741             : /*                         GetOverviewCount()                           */
    1742             : /************************************************************************/
    1743             : 
    1744          23 : int VRTPansharpenedRasterBand::GetOverviewCount()
    1745             : {
    1746          23 :     VRTPansharpenedDataset *poGDS = static_cast<VRTPansharpenedDataset *>(poDS);
    1747             : 
    1748             :     // Build on-the-fly overviews from overviews of pan and spectral bands
    1749          23 :     if (poGDS->m_poPansharpener != nullptr &&
    1750          46 :         poGDS->m_apoOverviewDatasets.empty() && poGDS->m_poMainDataset == poGDS)
    1751             :     {
    1752             :         GDALPansharpenOptions *psOptions =
    1753          19 :             poGDS->m_poPansharpener->GetOptions();
    1754             : 
    1755             :         GDALRasterBand *poPanBand =
    1756          19 :             GDALRasterBand::FromHandle(psOptions->hPanchroBand);
    1757          19 :         const int nPanOvrCount = poPanBand->GetOverviewCount();
    1758          19 :         if (nPanOvrCount > 0)
    1759             :         {
    1760          14 :             for (int i = 0; i < poGDS->GetRasterCount(); i++)
    1761             :             {
    1762          10 :                 if (!static_cast<VRTRasterBand *>(poGDS->GetRasterBand(i + 1))
    1763          10 :                          ->IsPansharpenRasterBand())
    1764             :                 {
    1765           0 :                     return 0;
    1766             :                 }
    1767             :             }
    1768             : 
    1769             :             int nSpectralOvrCount =
    1770           4 :                 GDALRasterBand::FromHandle(psOptions->pahInputSpectralBands[0])
    1771           4 :                     ->GetOverviewCount();
    1772          10 :             for (int i = 1; i < psOptions->nInputSpectralBands; i++)
    1773             :             {
    1774           6 :                 auto poSpectralBand = GDALRasterBand::FromHandle(
    1775           6 :                     psOptions->pahInputSpectralBands[i]);
    1776           6 :                 if (poSpectralBand->GetOverviewCount() != nSpectralOvrCount)
    1777             :                 {
    1778           0 :                     return 0;
    1779             :                 }
    1780             :             }
    1781           4 :             auto poPanBandDS = poPanBand->GetDataset();
    1782           7 :             for (int j = 0; j < std::min(nPanOvrCount, nSpectralOvrCount); j++)
    1783             :             {
    1784             :                 std::unique_ptr<GDALDataset, GDALDatasetUniquePtrReleaser>
    1785           3 :                     poPanOvrDS(GDALCreateOverviewDataset(poPanBandDS, j, true));
    1786           3 :                 if (!poPanOvrDS)
    1787             :                 {
    1788           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1789             :                              "GDALCreateOverviewDataset(poPanBandDS, %d, true) "
    1790             :                              "failed",
    1791             :                              j);
    1792           0 :                     break;
    1793             :                 }
    1794             :                 GDALRasterBand *poPanOvrBand =
    1795           3 :                     poPanOvrDS->GetRasterBand(poPanBand->GetBand());
    1796             :                 auto poOvrDS = std::make_unique<VRTPansharpenedDataset>(
    1797           3 :                     poPanOvrBand->GetXSize(), poPanOvrBand->GetYSize());
    1798           6 :                 poOvrDS->m_apoDatasetsToReleaseRef.push_back(
    1799           3 :                     std::move(poPanOvrDS));
    1800          10 :                 for (int i = 0; i < poGDS->GetRasterCount(); i++)
    1801             :                 {
    1802           7 :                     GDALRasterBand *poSrcBand = poGDS->GetRasterBand(i + 1);
    1803             :                     GDALRasterBand *poBand = new VRTPansharpenedRasterBand(
    1804           7 :                         poOvrDS.get(), i + 1, poSrcBand->GetRasterDataType());
    1805             :                     const char *pszNBITS =
    1806           7 :                         poSrcBand->GetMetadataItem("NBITS", "IMAGE_STRUCTURE");
    1807           7 :                     if (pszNBITS)
    1808           0 :                         poBand->SetMetadataItem("NBITS", pszNBITS,
    1809           0 :                                                 "IMAGE_STRUCTURE");
    1810           7 :                     poOvrDS->SetBand(i + 1, poBand);
    1811             :                 }
    1812             : 
    1813             :                 GDALPansharpenOptions *psPanOvrOptions =
    1814           3 :                     GDALClonePansharpenOptions(psOptions);
    1815           3 :                 psPanOvrOptions->hPanchroBand = poPanOvrBand;
    1816          10 :                 for (int i = 0; i < psOptions->nInputSpectralBands; i++)
    1817             :                 {
    1818           7 :                     auto poSpectralBand = GDALRasterBand::FromHandle(
    1819           7 :                         psOptions->pahInputSpectralBands[i]);
    1820             :                     std::unique_ptr<GDALDataset, GDALDatasetUniquePtrReleaser>
    1821             :                         poSpectralOvrDS(GDALCreateOverviewDataset(
    1822           7 :                             poSpectralBand->GetDataset(), j, true));
    1823           7 :                     if (!poSpectralOvrDS)
    1824             :                     {
    1825           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
    1826             :                                  "GDALCreateOverviewDataset(poSpectralBand->"
    1827             :                                  "GetDataset(), %d, true) failed",
    1828             :                                  j);
    1829           0 :                         GDALDestroyPansharpenOptions(psPanOvrOptions);
    1830           0 :                         return 0;
    1831             :                     }
    1832          14 :                     psPanOvrOptions->pahInputSpectralBands[i] =
    1833           7 :                         poSpectralOvrDS->GetRasterBand(
    1834             :                             poSpectralBand->GetBand());
    1835          14 :                     poOvrDS->m_apoDatasetsToReleaseRef.push_back(
    1836           7 :                         std::move(poSpectralOvrDS));
    1837             :                 }
    1838           3 :                 poOvrDS->m_poPansharpener = new GDALPansharpenOperation();
    1839           3 :                 if (poOvrDS->m_poPansharpener->Initialize(psPanOvrOptions) !=
    1840             :                     CE_None)
    1841             :                 {
    1842           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1843             :                              "Unable to initialize pansharpener.");
    1844           0 :                     GDALDestroyPansharpenOptions(psPanOvrOptions);
    1845           0 :                     return 0;
    1846             :                 }
    1847           3 :                 GDALDestroyPansharpenOptions(psPanOvrOptions);
    1848             : 
    1849           3 :                 poOvrDS->m_poMainDataset = poGDS;
    1850           3 :                 poOvrDS->SetMetadataItem("INTERLEAVE", "PIXEL",
    1851           3 :                                          "IMAGE_STRUCTURE");
    1852             : 
    1853           3 :                 poGDS->m_apoOverviewDatasets.push_back(std::move(poOvrDS));
    1854             :             }
    1855             :         }
    1856             :     }
    1857          23 :     return static_cast<int>(poGDS->m_apoOverviewDatasets.size());
    1858             : }
    1859             : 
    1860             : /************************************************************************/
    1861             : /*                            GetOverview()                             */
    1862             : /************************************************************************/
    1863             : 
    1864           6 : GDALRasterBand *VRTPansharpenedRasterBand::GetOverview(int iOvr)
    1865             : {
    1866           6 :     if (iOvr < 0 || iOvr >= GetOverviewCount())
    1867           2 :         return nullptr;
    1868             : 
    1869           4 :     VRTPansharpenedDataset *poGDS = static_cast<VRTPansharpenedDataset *>(poDS);
    1870             : 
    1871           4 :     return poGDS->m_apoOverviewDatasets[iOvr]->GetRasterBand(nBand);
    1872             : }
    1873             : 
    1874             : /*! @endcond */

Generated by: LCOV version 1.14