LCOV - code coverage report
Current view: top level - gcore/multidim - gdalmultidim_array_resampled.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 427 478 89.3 %
Date: 2026-04-15 22:10:00 Functions: 27 28 96.4 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Name:     gdalmultidim_array_resampled.cpp
       4             :  * Project:  GDAL Core
       5             :  * Purpose:  GDALMDArray::GetResampled() implementation
       6             :  * Author:   Even Rouault <even.rouault at spatialys.com>
       7             :  *
       8             :  ******************************************************************************
       9             :  * Copyright (c) 2019, Even Rouault <even.rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "gdal_multidim.h"
      15             : #include "gdal_pam.h"
      16             : #include "gdal_pam_multidim.h"
      17             : #include "gdal_rasterband.h"
      18             : #include "gdal_utils.h"
      19             : 
      20             : #include <algorithm>
      21             : 
      22             : //! @cond Doxygen_Suppress
      23             : 
      24             : /************************************************************************/
      25             : /*                         GDALMDArrayResampled                         */
      26             : /************************************************************************/
      27             : 
      28             : class GDALMDArrayResampledDataset;
      29             : 
      30             : class GDALMDArrayResampledDatasetRasterBand final : public GDALRasterBand
      31             : {
      32             :   protected:
      33             :     CPLErr IReadBlock(int, int, void *) override;
      34             :     CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize,
      35             :                      int nYSize, void *pData, int nBufXSize, int nBufYSize,
      36             :                      GDALDataType eBufType, GSpacing nPixelSpaceBuf,
      37             :                      GSpacing nLineSpaceBuf,
      38             :                      GDALRasterIOExtraArg *psExtraArg) override;
      39             : 
      40             :   public:
      41             :     explicit GDALMDArrayResampledDatasetRasterBand(
      42             :         GDALMDArrayResampledDataset *poDSIn);
      43             : 
      44             :     double GetNoDataValue(int *pbHasNoData) override;
      45             : };
      46             : 
      47             : class GDALMDArrayResampledDataset final : public GDALPamDataset
      48             : {
      49             :     friend class GDALMDArrayResampled;
      50             :     friend class GDALMDArrayResampledDatasetRasterBand;
      51             : 
      52             :     std::shared_ptr<GDALMDArray> m_poArray;
      53             :     const size_t m_iXDim;
      54             :     const size_t m_iYDim;
      55             :     GDALGeoTransform m_gt{};
      56             :     bool m_bHasGT = false;
      57             :     mutable std::shared_ptr<OGRSpatialReference> m_poSRS{};
      58             : 
      59             :     std::vector<GUInt64> m_anOffset{};
      60             :     std::vector<size_t> m_anCount{};
      61             :     std::vector<GPtrDiff_t> m_anStride{};
      62             : 
      63             :     std::string m_osFilenameLong{};
      64             :     std::string m_osFilenameLat{};
      65             : 
      66             :   public:
      67          24 :     GDALMDArrayResampledDataset(const std::shared_ptr<GDALMDArray> &array,
      68             :                                 size_t iXDim, size_t iYDim)
      69          24 :         : m_poArray(array), m_iXDim(iXDim), m_iYDim(iYDim),
      70          24 :           m_anOffset(m_poArray->GetDimensionCount(), 0),
      71          24 :           m_anCount(m_poArray->GetDimensionCount(), 1),
      72          72 :           m_anStride(m_poArray->GetDimensionCount(), 0)
      73             :     {
      74          24 :         const auto &dims(m_poArray->GetDimensions());
      75             : 
      76          24 :         nRasterYSize = static_cast<int>(
      77          24 :             std::min(static_cast<GUInt64>(INT_MAX), dims[iYDim]->GetSize()));
      78          24 :         nRasterXSize = static_cast<int>(
      79          24 :             std::min(static_cast<GUInt64>(INT_MAX), dims[iXDim]->GetSize()));
      80             : 
      81          24 :         m_bHasGT = m_poArray->GuessGeoTransform(m_iXDim, m_iYDim, false, m_gt);
      82             : 
      83          24 :         SetBand(1, new GDALMDArrayResampledDatasetRasterBand(this));
      84          24 :     }
      85             : 
      86             :     ~GDALMDArrayResampledDataset() override;
      87             : 
      88          43 :     CPLErr GetGeoTransform(GDALGeoTransform &gt) const override
      89             :     {
      90          43 :         gt = m_gt;
      91          43 :         return m_bHasGT ? CE_None : CE_Failure;
      92             :     }
      93             : 
      94         105 :     const OGRSpatialReference *GetSpatialRef() const override
      95             :     {
      96         105 :         m_poSRS = m_poArray->GetSpatialRef();
      97         105 :         if (m_poSRS)
      98             :         {
      99          79 :             m_poSRS.reset(m_poSRS->Clone());
     100         158 :             auto axisMapping = m_poSRS->GetDataAxisToSRSAxisMapping();
     101         237 :             for (auto &m : axisMapping)
     102             :             {
     103         158 :                 if (m == static_cast<int>(m_iXDim) + 1)
     104          79 :                     m = 1;
     105          79 :                 else if (m == static_cast<int>(m_iYDim) + 1)
     106          79 :                     m = 2;
     107             :             }
     108          79 :             m_poSRS->SetDataAxisToSRSAxisMapping(axisMapping);
     109             :         }
     110         105 :         return m_poSRS.get();
     111             :     }
     112             : 
     113           5 :     void SetGeolocationArray(const std::string &osFilenameLong,
     114             :                              const std::string &osFilenameLat)
     115             :     {
     116           5 :         m_osFilenameLong = osFilenameLong;
     117           5 :         m_osFilenameLat = osFilenameLat;
     118          10 :         CPLStringList aosGeoLoc;
     119           5 :         aosGeoLoc.SetNameValue("LINE_OFFSET", "0");
     120           5 :         aosGeoLoc.SetNameValue("LINE_STEP", "1");
     121           5 :         aosGeoLoc.SetNameValue("PIXEL_OFFSET", "0");
     122           5 :         aosGeoLoc.SetNameValue("PIXEL_STEP", "1");
     123           5 :         aosGeoLoc.SetNameValue("SRS", SRS_WKT_WGS84_LAT_LONG);  // FIXME?
     124           5 :         aosGeoLoc.SetNameValue("X_BAND", "1");
     125           5 :         aosGeoLoc.SetNameValue("X_DATASET", m_osFilenameLong.c_str());
     126           5 :         aosGeoLoc.SetNameValue("Y_BAND", "1");
     127           5 :         aosGeoLoc.SetNameValue("Y_DATASET", m_osFilenameLat.c_str());
     128           5 :         aosGeoLoc.SetNameValue("GEOREFERENCING_CONVENTION", "PIXEL_CENTER");
     129           5 :         SetMetadata(aosGeoLoc.List(), "GEOLOCATION");
     130           5 :     }
     131             : };
     132             : 
     133          48 : GDALMDArrayResampledDataset::~GDALMDArrayResampledDataset()
     134             : {
     135          24 :     if (!m_osFilenameLong.empty())
     136           5 :         VSIUnlink(m_osFilenameLong.c_str());
     137          24 :     if (!m_osFilenameLat.empty())
     138           5 :         VSIUnlink(m_osFilenameLat.c_str());
     139          48 : }
     140             : 
     141             : /************************************************************************/
     142             : /*               GDALMDArrayResampledDatasetRasterBand()                */
     143             : /************************************************************************/
     144             : 
     145          24 : GDALMDArrayResampledDatasetRasterBand::GDALMDArrayResampledDatasetRasterBand(
     146          24 :     GDALMDArrayResampledDataset *poDSIn)
     147             : {
     148          24 :     const auto &poArray(poDSIn->m_poArray);
     149          24 :     const auto blockSize(poArray->GetBlockSize());
     150          24 :     nBlockYSize = (blockSize[poDSIn->m_iYDim])
     151          24 :                       ? static_cast<int>(std::min(static_cast<GUInt64>(INT_MAX),
     152          13 :                                                   blockSize[poDSIn->m_iYDim]))
     153          24 :                       : 1;
     154          24 :     nBlockXSize = blockSize[poDSIn->m_iXDim]
     155          13 :                       ? static_cast<int>(std::min(static_cast<GUInt64>(INT_MAX),
     156          13 :                                                   blockSize[poDSIn->m_iXDim]))
     157          24 :                       : poDSIn->GetRasterXSize();
     158          24 :     eDataType = poArray->GetDataType().GetNumericDataType();
     159          24 :     eAccess = poDSIn->eAccess;
     160          24 : }
     161             : 
     162             : /************************************************************************/
     163             : /*                           GetNoDataValue()                           */
     164             : /************************************************************************/
     165             : 
     166          54 : double GDALMDArrayResampledDatasetRasterBand::GetNoDataValue(int *pbHasNoData)
     167             : {
     168          54 :     auto l_poDS(cpl::down_cast<GDALMDArrayResampledDataset *>(poDS));
     169          54 :     const auto &poArray(l_poDS->m_poArray);
     170          54 :     bool bHasNodata = false;
     171          54 :     double dfRes = poArray->GetNoDataValueAsDouble(&bHasNodata);
     172          54 :     if (pbHasNoData)
     173          48 :         *pbHasNoData = bHasNodata;
     174          54 :     return dfRes;
     175             : }
     176             : 
     177             : /************************************************************************/
     178             : /*                             IReadBlock()                             */
     179             : /************************************************************************/
     180             : 
     181           0 : CPLErr GDALMDArrayResampledDatasetRasterBand::IReadBlock(int nBlockXOff,
     182             :                                                          int nBlockYOff,
     183             :                                                          void *pImage)
     184             : {
     185           0 :     const int nDTSize(GDALGetDataTypeSizeBytes(eDataType));
     186           0 :     const int nXOff = nBlockXOff * nBlockXSize;
     187           0 :     const int nYOff = nBlockYOff * nBlockYSize;
     188           0 :     const int nReqXSize = std::min(nRasterXSize - nXOff, nBlockXSize);
     189           0 :     const int nReqYSize = std::min(nRasterYSize - nYOff, nBlockYSize);
     190             :     GDALRasterIOExtraArg sExtraArg;
     191           0 :     INIT_RASTERIO_EXTRA_ARG(sExtraArg);
     192           0 :     return IRasterIO(GF_Read, nXOff, nYOff, nReqXSize, nReqYSize, pImage,
     193             :                      nReqXSize, nReqYSize, eDataType, nDTSize,
     194           0 :                      static_cast<GSpacing>(nDTSize) * nBlockXSize, &sExtraArg);
     195             : }
     196             : 
     197             : /************************************************************************/
     198             : /*                             IRasterIO()                              */
     199             : /************************************************************************/
     200             : 
     201          32 : CPLErr GDALMDArrayResampledDatasetRasterBand::IRasterIO(
     202             :     GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
     203             :     void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
     204             :     GSpacing nPixelSpaceBuf, GSpacing nLineSpaceBuf,
     205             :     GDALRasterIOExtraArg *psExtraArg)
     206             : {
     207          32 :     auto l_poDS(cpl::down_cast<GDALMDArrayResampledDataset *>(poDS));
     208          32 :     const auto &poArray(l_poDS->m_poArray);
     209          32 :     const int nBufferDTSize(GDALGetDataTypeSizeBytes(eBufType));
     210          32 :     if (eRWFlag == GF_Read && nXSize == nBufXSize && nYSize == nBufYSize &&
     211          32 :         nBufferDTSize > 0 && (nPixelSpaceBuf % nBufferDTSize) == 0 &&
     212          32 :         (nLineSpaceBuf % nBufferDTSize) == 0)
     213             :     {
     214          32 :         l_poDS->m_anOffset[l_poDS->m_iXDim] = static_cast<GUInt64>(nXOff);
     215          32 :         l_poDS->m_anCount[l_poDS->m_iXDim] = static_cast<size_t>(nXSize);
     216          64 :         l_poDS->m_anStride[l_poDS->m_iXDim] =
     217          32 :             static_cast<GPtrDiff_t>(nPixelSpaceBuf / nBufferDTSize);
     218             : 
     219          32 :         l_poDS->m_anOffset[l_poDS->m_iYDim] = static_cast<GUInt64>(nYOff);
     220          32 :         l_poDS->m_anCount[l_poDS->m_iYDim] = static_cast<size_t>(nYSize);
     221          64 :         l_poDS->m_anStride[l_poDS->m_iYDim] =
     222          32 :             static_cast<GPtrDiff_t>(nLineSpaceBuf / nBufferDTSize);
     223             : 
     224          64 :         return poArray->Read(l_poDS->m_anOffset.data(),
     225          32 :                              l_poDS->m_anCount.data(), nullptr,
     226          32 :                              l_poDS->m_anStride.data(),
     227          64 :                              GDALExtendedDataType::Create(eBufType), pData)
     228          32 :                    ? CE_None
     229          32 :                    : CE_Failure;
     230             :     }
     231           0 :     return GDALRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
     232             :                                      pData, nBufXSize, nBufYSize, eBufType,
     233           0 :                                      nPixelSpaceBuf, nLineSpaceBuf, psExtraArg);
     234             : }
     235             : 
     236             : class GDALMDArrayResampled final : public GDALPamMDArray
     237             : {
     238             :   private:
     239             :     std::shared_ptr<GDALMDArray> m_poParent{};
     240             :     std::vector<std::shared_ptr<GDALDimension>> m_apoDims;
     241             :     std::vector<GUInt64> m_anBlockSize;
     242             :     GDALExtendedDataType m_dt;
     243             :     std::shared_ptr<OGRSpatialReference> m_poSRS{};
     244             :     std::shared_ptr<GDALMDArray> m_poVarX{};
     245             :     std::shared_ptr<GDALMDArray> m_poVarY{};
     246             :     std::unique_ptr<GDALMDArrayResampledDataset> m_poParentDS{};
     247             :     std::unique_ptr<GDALDataset> m_poReprojectedDS{};
     248             : 
     249             :   protected:
     250          21 :     GDALMDArrayResampled(
     251             :         const std::shared_ptr<GDALMDArray> &poParent,
     252             :         const std::vector<std::shared_ptr<GDALDimension>> &apoDims,
     253             :         const std::vector<GUInt64> &anBlockSize)
     254          42 :         : GDALAbstractMDArray(std::string(),
     255          42 :                               "Resampled view of " + poParent->GetFullName()),
     256             :           GDALPamMDArray(
     257          42 :               std::string(), "Resampled view of " + poParent->GetFullName(),
     258          42 :               GDALPamMultiDim::GetPAM(poParent), poParent->GetContext()),
     259          21 :           m_poParent(std::move(poParent)), m_apoDims(apoDims),
     260         105 :           m_anBlockSize(anBlockSize), m_dt(m_poParent->GetDataType())
     261             :     {
     262          21 :         CPLAssert(apoDims.size() == m_poParent->GetDimensionCount());
     263          21 :         CPLAssert(anBlockSize.size() == m_poParent->GetDimensionCount());
     264          21 :     }
     265             : 
     266             :     bool IRead(const GUInt64 *arrayStartIdx, const size_t *count,
     267             :                const GInt64 *arrayStep, const GPtrDiff_t *bufferStride,
     268             :                const GDALExtendedDataType &bufferDataType,
     269             :                void *pDstBuffer) const override;
     270             : 
     271             :   public:
     272             :     static std::shared_ptr<GDALMDArray>
     273             :     Create(const std::shared_ptr<GDALMDArray> &poParent,
     274             :            const std::vector<std::shared_ptr<GDALDimension>> &apoNewDims,
     275             :            GDALRIOResampleAlg resampleAlg,
     276             :            const OGRSpatialReference *poTargetSRS, CSLConstList papszOptions);
     277             : 
     278          42 :     ~GDALMDArrayResampled() override
     279          21 :     {
     280             :         // First close the warped VRT
     281          21 :         m_poReprojectedDS.reset();
     282          21 :         m_poParentDS.reset();
     283          42 :     }
     284             : 
     285          11 :     bool IsWritable() const override
     286             :     {
     287          11 :         return false;
     288             :     }
     289             : 
     290          63 :     const std::string &GetFilename() const override
     291             :     {
     292          63 :         return m_poParent->GetFilename();
     293             :     }
     294             : 
     295             :     const std::vector<std::shared_ptr<GDALDimension>> &
     296         283 :     GetDimensions() const override
     297             :     {
     298         283 :         return m_apoDims;
     299             :     }
     300             : 
     301         109 :     const GDALExtendedDataType &GetDataType() const override
     302             :     {
     303         109 :         return m_dt;
     304             :     }
     305             : 
     306          21 :     std::shared_ptr<OGRSpatialReference> GetSpatialRef() const override
     307             :     {
     308          21 :         return m_poSRS;
     309             :     }
     310             : 
     311          12 :     std::vector<GUInt64> GetBlockSize() const override
     312             :     {
     313          12 :         return m_anBlockSize;
     314             :     }
     315             : 
     316             :     std::shared_ptr<GDALAttribute>
     317           1 :     GetAttribute(const std::string &osName) const override
     318             :     {
     319           1 :         return m_poParent->GetAttribute(osName);
     320             :     }
     321             : 
     322             :     std::vector<std::shared_ptr<GDALAttribute>>
     323          12 :     GetAttributes(CSLConstList papszOptions = nullptr) const override
     324             :     {
     325          12 :         return m_poParent->GetAttributes(papszOptions);
     326             :     }
     327             : 
     328           1 :     const std::string &GetUnit() const override
     329             :     {
     330           1 :         return m_poParent->GetUnit();
     331             :     }
     332             : 
     333           1 :     const void *GetRawNoDataValue() const override
     334             :     {
     335           1 :         return m_poParent->GetRawNoDataValue();
     336             :     }
     337             : 
     338           1 :     double GetOffset(bool *pbHasOffset,
     339             :                      GDALDataType *peStorageType) const override
     340             :     {
     341           1 :         return m_poParent->GetOffset(pbHasOffset, peStorageType);
     342             :     }
     343             : 
     344           1 :     double GetScale(bool *pbHasScale,
     345             :                     GDALDataType *peStorageType) const override
     346             :     {
     347           1 :         return m_poParent->GetScale(pbHasScale, peStorageType);
     348             :     }
     349             : };
     350             : 
     351             : /************************************************************************/
     352             : /*                    GDALMDArrayResampled::Create()                    */
     353             : /************************************************************************/
     354             : 
     355          29 : std::shared_ptr<GDALMDArray> GDALMDArrayResampled::Create(
     356             :     const std::shared_ptr<GDALMDArray> &poParent,
     357             :     const std::vector<std::shared_ptr<GDALDimension>> &apoNewDimsIn,
     358             :     GDALRIOResampleAlg resampleAlg, const OGRSpatialReference *poTargetSRS,
     359             :     CSLConstList /* papszOptions */)
     360             : {
     361          29 :     const char *pszResampleAlg = "nearest";
     362          29 :     bool unsupported = false;
     363          29 :     switch (resampleAlg)
     364             :     {
     365          16 :         case GRIORA_NearestNeighbour:
     366          16 :             pszResampleAlg = "nearest";
     367          16 :             break;
     368           2 :         case GRIORA_Bilinear:
     369           2 :             pszResampleAlg = "bilinear";
     370           2 :             break;
     371           5 :         case GRIORA_Cubic:
     372           5 :             pszResampleAlg = "cubic";
     373           5 :             break;
     374           1 :         case GRIORA_CubicSpline:
     375           1 :             pszResampleAlg = "cubicspline";
     376           1 :             break;
     377           1 :         case GRIORA_Lanczos:
     378           1 :             pszResampleAlg = "lanczos";
     379           1 :             break;
     380           1 :         case GRIORA_Average:
     381           1 :             pszResampleAlg = "average";
     382           1 :             break;
     383           1 :         case GRIORA_Mode:
     384           1 :             pszResampleAlg = "mode";
     385           1 :             break;
     386           1 :         case GRIORA_Gauss:
     387           1 :             unsupported = true;
     388           1 :             break;
     389           0 :         case GRIORA_RESERVED_START:
     390           0 :             unsupported = true;
     391           0 :             break;
     392           0 :         case GRIORA_RESERVED_END:
     393           0 :             unsupported = true;
     394           0 :             break;
     395           1 :         case GRIORA_RMS:
     396           1 :             pszResampleAlg = "rms";
     397           1 :             break;
     398             :     }
     399          29 :     if (unsupported)
     400             :     {
     401           1 :         CPLError(CE_Failure, CPLE_NotSupported,
     402             :                  "Unsupported resample method for GetResampled()");
     403           1 :         return nullptr;
     404             :     }
     405             : 
     406          28 :     if (poParent->GetDimensionCount() < 2)
     407             :     {
     408           1 :         CPLError(CE_Failure, CPLE_NotSupported,
     409             :                  "GetResampled() only supports 2 dimensions or more");
     410           1 :         return nullptr;
     411             :     }
     412             : 
     413          27 :     const auto &aoParentDims = poParent->GetDimensions();
     414          27 :     if (apoNewDimsIn.size() != aoParentDims.size())
     415             :     {
     416           2 :         CPLError(CE_Failure, CPLE_AppDefined,
     417             :                  "GetResampled(): apoNewDims size should be the same as "
     418             :                  "GetDimensionCount()");
     419           2 :         return nullptr;
     420             :     }
     421             : 
     422          50 :     std::vector<std::shared_ptr<GDALDimension>> apoNewDims;
     423          25 :     apoNewDims.reserve(apoNewDimsIn.size());
     424             : 
     425          50 :     std::vector<GUInt64> anBlockSize;
     426          25 :     anBlockSize.reserve(apoNewDimsIn.size());
     427          50 :     const auto &anParentBlockSize = poParent->GetBlockSize();
     428             : 
     429          50 :     auto apoParentDims = poParent->GetDimensions();
     430             :     // Special case for NASA EMIT datasets
     431          30 :     const bool bYXBandOrder = (apoParentDims.size() == 3 &&
     432           7 :                                apoParentDims[0]->GetName() == "downtrack" &&
     433          32 :                                apoParentDims[1]->GetName() == "crosstrack" &&
     434           2 :                                apoParentDims[2]->GetName() == "bands");
     435             : 
     436             :     const size_t iYDimParent =
     437          25 :         bYXBandOrder ? 0 : poParent->GetDimensionCount() - 2;
     438             :     const size_t iXDimParent =
     439          25 :         bYXBandOrder ? 1 : poParent->GetDimensionCount() - 1;
     440             : 
     441          77 :     for (unsigned i = 0; i < apoNewDimsIn.size(); ++i)
     442             :     {
     443          53 :         if (i == iYDimParent || i == iXDimParent)
     444          48 :             continue;
     445           5 :         if (apoNewDimsIn[i] == nullptr)
     446             :         {
     447           3 :             apoNewDims.emplace_back(aoParentDims[i]);
     448             :         }
     449           3 :         else if (apoNewDimsIn[i]->GetSize() != aoParentDims[i]->GetSize() ||
     450           1 :                  apoNewDimsIn[i]->GetName() != aoParentDims[i]->GetName())
     451             :         {
     452           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     453             :                      "GetResampled(): apoNewDims[%u] should be the same "
     454             :                      "as its parent",
     455             :                      i);
     456           1 :             return nullptr;
     457             :         }
     458             :         else
     459             :         {
     460           1 :             apoNewDims.emplace_back(aoParentDims[i]);
     461             :         }
     462           4 :         anBlockSize.emplace_back(anParentBlockSize[i]);
     463             :     }
     464             : 
     465             :     std::unique_ptr<GDALMDArrayResampledDataset> poParentDS(
     466          48 :         new GDALMDArrayResampledDataset(poParent, iXDimParent, iYDimParent));
     467             : 
     468          24 :     double dfXStart = 0.0;
     469          24 :     double dfXSpacing = 0.0;
     470          24 :     bool gotXSpacing = false;
     471          48 :     auto poNewDimX = apoNewDimsIn[iXDimParent];
     472          24 :     if (poNewDimX)
     473             :     {
     474           4 :         if (poNewDimX->GetSize() > static_cast<GUInt64>(INT_MAX))
     475             :         {
     476           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     477             :                      "Too big size for X dimension");
     478           0 :             return nullptr;
     479             :         }
     480           4 :         auto var = poNewDimX->GetIndexingVariable();
     481           4 :         if (var)
     482             :         {
     483           2 :             if (var->GetDimensionCount() != 1 ||
     484           2 :                 var->GetDimensions()[0]->GetSize() != poNewDimX->GetSize() ||
     485           1 :                 !var->IsRegularlySpaced(dfXStart, dfXSpacing))
     486             :             {
     487           0 :                 CPLError(CE_Failure, CPLE_NotSupported,
     488             :                          "New X dimension should be indexed by a regularly "
     489             :                          "spaced variable");
     490           0 :                 return nullptr;
     491             :             }
     492           1 :             gotXSpacing = true;
     493             :         }
     494             :     }
     495             : 
     496          24 :     double dfYStart = 0.0;
     497          24 :     double dfYSpacing = 0.0;
     498          48 :     auto poNewDimY = apoNewDimsIn[iYDimParent];
     499          24 :     bool gotYSpacing = false;
     500          24 :     if (poNewDimY)
     501             :     {
     502           4 :         if (poNewDimY->GetSize() > static_cast<GUInt64>(INT_MAX))
     503             :         {
     504           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     505             :                      "Too big size for Y dimension");
     506           0 :             return nullptr;
     507             :         }
     508           4 :         auto var = poNewDimY->GetIndexingVariable();
     509           4 :         if (var)
     510             :         {
     511           2 :             if (var->GetDimensionCount() != 1 ||
     512           2 :                 var->GetDimensions()[0]->GetSize() != poNewDimY->GetSize() ||
     513           1 :                 !var->IsRegularlySpaced(dfYStart, dfYSpacing))
     514             :             {
     515           0 :                 CPLError(CE_Failure, CPLE_NotSupported,
     516             :                          "New Y dimension should be indexed by a regularly "
     517             :                          "spaced variable");
     518           0 :                 return nullptr;
     519             :             }
     520           1 :             gotYSpacing = true;
     521             :         }
     522             :     }
     523             : 
     524             :     // This limitation could probably be removed
     525          24 :     if ((gotXSpacing && !gotYSpacing) || (!gotXSpacing && gotYSpacing))
     526             :     {
     527           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     528             :                  "Either none of new X or Y dimension should have an indexing "
     529             :                  "variable, or both should both should have one.");
     530           0 :         return nullptr;
     531             :     }
     532             : 
     533          48 :     std::string osDstWKT;
     534          24 :     if (poTargetSRS)
     535             :     {
     536           2 :         char *pszDstWKT = nullptr;
     537           2 :         if (poTargetSRS->exportToWkt(&pszDstWKT) != OGRERR_NONE)
     538             :         {
     539           0 :             CPLFree(pszDstWKT);
     540           0 :             return nullptr;
     541             :         }
     542           2 :         osDstWKT = pszDstWKT;
     543           2 :         CPLFree(pszDstWKT);
     544             :     }
     545             : 
     546             :     // Use coordinate variables for geolocation array
     547          48 :     const auto apoCoordinateVars = poParent->GetCoordinateVariables();
     548          24 :     bool useGeolocationArray = false;
     549          24 :     if (apoCoordinateVars.size() >= 2)
     550             :     {
     551           0 :         std::shared_ptr<GDALMDArray> poLongVar;
     552           0 :         std::shared_ptr<GDALMDArray> poLatVar;
     553          15 :         for (const auto &poCoordVar : apoCoordinateVars)
     554             :         {
     555          10 :             const auto &osName = poCoordVar->GetName();
     556          30 :             const auto poAttr = poCoordVar->GetAttribute("standard_name");
     557          20 :             std::string osStandardName;
     558          12 :             if (poAttr && poAttr->GetDataType().GetClass() == GEDTC_STRING &&
     559           2 :                 poAttr->GetDimensionCount() == 0)
     560             :             {
     561           2 :                 const char *pszStandardName = poAttr->ReadAsString();
     562           2 :                 if (pszStandardName)
     563           2 :                     osStandardName = pszStandardName;
     564             :             }
     565          21 :             if (osName == "lon" || osName == "longitude" ||
     566          21 :                 osName == "Longitude" || osStandardName == "longitude")
     567             :             {
     568           5 :                 poLongVar = poCoordVar;
     569             :             }
     570           6 :             else if (osName == "lat" || osName == "latitude" ||
     571           6 :                      osName == "Latitude" || osStandardName == "latitude")
     572             :             {
     573           5 :                 poLatVar = poCoordVar;
     574             :             }
     575             :         }
     576           5 :         if (poLatVar != nullptr && poLongVar != nullptr)
     577             :         {
     578           5 :             const auto longDimCount = poLongVar->GetDimensionCount();
     579           5 :             const auto &longDims = poLongVar->GetDimensions();
     580           5 :             const auto latDimCount = poLatVar->GetDimensionCount();
     581           5 :             const auto &latDims = poLatVar->GetDimensions();
     582           5 :             const auto xDimSize = aoParentDims[iXDimParent]->GetSize();
     583           5 :             const auto yDimSize = aoParentDims[iYDimParent]->GetSize();
     584           0 :             if (longDimCount == 1 && longDims[0]->GetSize() == xDimSize &&
     585           5 :                 latDimCount == 1 && latDims[0]->GetSize() == yDimSize)
     586             :             {
     587             :                 // Geolocation arrays are 1D, and of consistent size with
     588             :                 // the variable
     589           0 :                 useGeolocationArray = true;
     590             :             }
     591           1 :             else if ((longDimCount == 2 ||
     592           6 :                       (longDimCount == 3 && longDims[0]->GetSize() == 1)) &&
     593          10 :                      longDims[longDimCount - 2]->GetSize() == yDimSize &&
     594          10 :                      longDims[longDimCount - 1]->GetSize() == xDimSize &&
     595           1 :                      (latDimCount == 2 ||
     596           6 :                       (latDimCount == 3 && latDims[0]->GetSize() == 1)) &&
     597          15 :                      latDims[latDimCount - 2]->GetSize() == yDimSize &&
     598           5 :                      latDims[latDimCount - 1]->GetSize() == xDimSize)
     599             : 
     600             :             {
     601             :                 // Geolocation arrays are 2D (or 3D with first dimension of
     602             :                 // size 1, as found in Sentinel 5P products), and of consistent
     603             :                 // size with the variable
     604           5 :                 useGeolocationArray = true;
     605             :             }
     606             :             else
     607             :             {
     608           0 :                 CPLDebug(
     609             :                     "GDAL",
     610             :                     "Longitude and latitude coordinate variables found, "
     611             :                     "but their characteristics are not compatible of using "
     612             :                     "them as geolocation arrays");
     613             :             }
     614           5 :             if (useGeolocationArray)
     615             :             {
     616          10 :                 CPLDebug("GDAL",
     617             :                          "Setting geolocation array from variables %s and %s",
     618           5 :                          poLongVar->GetName().c_str(),
     619           5 :                          poLatVar->GetName().c_str());
     620             :                 const std::string osFilenameLong =
     621           5 :                     VSIMemGenerateHiddenFilename("longitude.tif");
     622             :                 const std::string osFilenameLat =
     623           5 :                     VSIMemGenerateHiddenFilename("latitude.tif");
     624             :                 std::unique_ptr<GDALDataset> poTmpLongDS(
     625             :                     longDimCount == 1
     626           0 :                         ? poLongVar->AsClassicDataset(0, 0)
     627          20 :                         : poLongVar->AsClassicDataset(longDimCount - 1,
     628          15 :                                                       longDimCount - 2));
     629           5 :                 auto hTIFFLongDS = GDALTranslate(
     630             :                     osFilenameLong.c_str(),
     631             :                     GDALDataset::ToHandle(poTmpLongDS.get()), nullptr, nullptr);
     632             :                 std::unique_ptr<GDALDataset> poTmpLatDS(
     633           0 :                     latDimCount == 1 ? poLatVar->AsClassicDataset(0, 0)
     634          20 :                                      : poLatVar->AsClassicDataset(
     635          15 :                                            latDimCount - 1, latDimCount - 2));
     636           5 :                 auto hTIFFLatDS = GDALTranslate(
     637             :                     osFilenameLat.c_str(),
     638             :                     GDALDataset::ToHandle(poTmpLatDS.get()), nullptr, nullptr);
     639           5 :                 const bool bError =
     640           5 :                     (hTIFFLatDS == nullptr || hTIFFLongDS == nullptr);
     641           5 :                 GDALClose(hTIFFLongDS);
     642           5 :                 GDALClose(hTIFFLatDS);
     643           5 :                 if (bError)
     644             :                 {
     645           0 :                     VSIUnlink(osFilenameLong.c_str());
     646           0 :                     VSIUnlink(osFilenameLat.c_str());
     647           0 :                     return nullptr;
     648             :                 }
     649             : 
     650           5 :                 poParentDS->SetGeolocationArray(osFilenameLong, osFilenameLat);
     651             :             }
     652             :         }
     653             :         else
     654             :         {
     655           0 :             CPLDebug("GDAL",
     656             :                      "Coordinate variables available for %s, but "
     657             :                      "longitude and/or latitude variables were not identified",
     658           0 :                      poParent->GetName().c_str());
     659             :         }
     660             :     }
     661             : 
     662             :     // Build gdalwarp arguments
     663          48 :     CPLStringList aosArgv;
     664             : 
     665          24 :     aosArgv.AddString("-of");
     666          24 :     aosArgv.AddString("VRT");
     667             : 
     668          24 :     aosArgv.AddString("-r");
     669          24 :     aosArgv.AddString(pszResampleAlg);
     670             : 
     671          24 :     if (!osDstWKT.empty())
     672             :     {
     673           2 :         aosArgv.AddString("-t_srs");
     674           2 :         aosArgv.AddString(osDstWKT.c_str());
     675             :     }
     676             : 
     677          24 :     if (useGeolocationArray)
     678           5 :         aosArgv.AddString("-geoloc");
     679             : 
     680          24 :     if (gotXSpacing && gotYSpacing)
     681             :     {
     682           1 :         const double dfXMin = dfXStart - dfXSpacing / 2;
     683             :         const double dfXMax =
     684           1 :             dfXMin + dfXSpacing * static_cast<double>(poNewDimX->GetSize());
     685           1 :         const double dfYMax = dfYStart - dfYSpacing / 2;
     686             :         const double dfYMin =
     687           1 :             dfYMax + dfYSpacing * static_cast<double>(poNewDimY->GetSize());
     688           1 :         aosArgv.AddString("-te");
     689           1 :         aosArgv.AddString(CPLSPrintf("%.17g", dfXMin));
     690           1 :         aosArgv.AddString(CPLSPrintf("%.17g", dfYMin));
     691           1 :         aosArgv.AddString(CPLSPrintf("%.17g", dfXMax));
     692           1 :         aosArgv.AddString(CPLSPrintf("%.17g", dfYMax));
     693             :     }
     694             : 
     695          24 :     if (poNewDimX && poNewDimY)
     696             :     {
     697           3 :         aosArgv.AddString("-ts");
     698             :         aosArgv.AddString(
     699           3 :             CPLSPrintf("%d", static_cast<int>(poNewDimX->GetSize())));
     700             :         aosArgv.AddString(
     701           3 :             CPLSPrintf("%d", static_cast<int>(poNewDimY->GetSize())));
     702             :     }
     703          21 :     else if (poNewDimX)
     704             :     {
     705           1 :         aosArgv.AddString("-ts");
     706             :         aosArgv.AddString(
     707           1 :             CPLSPrintf("%d", static_cast<int>(poNewDimX->GetSize())));
     708           1 :         aosArgv.AddString("0");
     709             :     }
     710          20 :     else if (poNewDimY)
     711             :     {
     712           1 :         aosArgv.AddString("-ts");
     713           1 :         aosArgv.AddString("0");
     714             :         aosArgv.AddString(
     715           1 :             CPLSPrintf("%d", static_cast<int>(poNewDimY->GetSize())));
     716             :     }
     717             : 
     718             :     // Create a warped VRT dataset
     719             :     GDALWarpAppOptions *psOptions =
     720          24 :         GDALWarpAppOptionsNew(aosArgv.List(), nullptr);
     721          24 :     GDALDatasetH hSrcDS = GDALDataset::ToHandle(poParentDS.get());
     722             :     std::unique_ptr<GDALDataset> poReprojectedDS(GDALDataset::FromHandle(
     723          48 :         GDALWarp("", nullptr, 1, &hSrcDS, psOptions, nullptr)));
     724          24 :     GDALWarpAppOptionsFree(psOptions);
     725          24 :     if (poReprojectedDS == nullptr)
     726           3 :         return nullptr;
     727             : 
     728             :     int nBlockXSize;
     729             :     int nBlockYSize;
     730          21 :     poReprojectedDS->GetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
     731          21 :     anBlockSize.emplace_back(nBlockYSize);
     732          21 :     anBlockSize.emplace_back(nBlockXSize);
     733             : 
     734          21 :     GDALGeoTransform gt;
     735          21 :     CPLErr eErr = poReprojectedDS->GetGeoTransform(gt);
     736          21 :     CPLAssert(eErr == CE_None);
     737          21 :     CPL_IGNORE_RET_VAL(eErr);
     738             : 
     739             :     auto poDimY = std::make_shared<GDALDimensionWeakIndexingVar>(
     740           0 :         std::string(), "dimY", GDAL_DIM_TYPE_HORIZONTAL_Y, "NORTH",
     741          42 :         poReprojectedDS->GetRasterYSize());
     742             :     auto varY = GDALMDArrayRegularlySpaced::Create(
     743          63 :         std::string(), poDimY->GetName(), poDimY, gt.yorig + gt.yscale / 2,
     744          84 :         gt.yscale, 0);
     745          21 :     poDimY->SetIndexingVariable(varY);
     746             : 
     747             :     auto poDimX = std::make_shared<GDALDimensionWeakIndexingVar>(
     748           0 :         std::string(), "dimX", GDAL_DIM_TYPE_HORIZONTAL_X, "EAST",
     749          42 :         poReprojectedDS->GetRasterXSize());
     750             :     auto varX = GDALMDArrayRegularlySpaced::Create(
     751          63 :         std::string(), poDimX->GetName(), poDimX, gt.xorig + gt.xscale / 2,
     752          84 :         gt.xscale, 0);
     753          21 :     poDimX->SetIndexingVariable(varX);
     754             : 
     755          21 :     apoNewDims.emplace_back(poDimY);
     756          21 :     apoNewDims.emplace_back(poDimX);
     757             :     auto newAr(std::shared_ptr<GDALMDArrayResampled>(
     758          42 :         new GDALMDArrayResampled(poParent, apoNewDims, anBlockSize)));
     759          21 :     newAr->SetSelf(newAr);
     760          21 :     if (poTargetSRS)
     761             :     {
     762           2 :         newAr->m_poSRS.reset(poTargetSRS->Clone());
     763             :     }
     764             :     else
     765             :     {
     766          19 :         newAr->m_poSRS = poParent->GetSpatialRef();
     767             :     }
     768          21 :     newAr->m_poVarX = varX;
     769          21 :     newAr->m_poVarY = varY;
     770          21 :     newAr->m_poReprojectedDS = std::move(poReprojectedDS);
     771          21 :     newAr->m_poParentDS = std::move(poParentDS);
     772             : 
     773             :     // If the input array is y,x,band ordered, the above newAr is
     774             :     // actually band,y,x ordered as it is more convenient for
     775             :     // GDALMDArrayResampled::IRead() implementation. But transpose that
     776             :     // array to the order of the input array
     777          21 :     if (bYXBandOrder)
     778           4 :         return newAr->Transpose(std::vector<int>{1, 2, 0});
     779             : 
     780          19 :     return newAr;
     781             : }
     782             : 
     783             : /************************************************************************/
     784             : /*                    GDALMDArrayResampled::IRead()                     */
     785             : /************************************************************************/
     786             : 
     787          29 : bool GDALMDArrayResampled::IRead(const GUInt64 *arrayStartIdx,
     788             :                                  const size_t *count, const GInt64 *arrayStep,
     789             :                                  const GPtrDiff_t *bufferStride,
     790             :                                  const GDALExtendedDataType &bufferDataType,
     791             :                                  void *pDstBuffer) const
     792             : {
     793          29 :     if (bufferDataType.GetClass() != GEDTC_NUMERIC)
     794           0 :         return false;
     795             : 
     796             :     struct Stack
     797             :     {
     798             :         size_t nIters = 0;
     799             :         GByte *dst_ptr = nullptr;
     800             :         GPtrDiff_t dst_inc_offset = 0;
     801             :     };
     802             : 
     803          29 :     const auto nDims = GetDimensionCount();
     804          58 :     std::vector<Stack> stack(nDims + 1);  // +1 to avoid -Wnull-dereference
     805          29 :     const size_t nBufferDTSize = bufferDataType.GetSize();
     806          92 :     for (size_t i = 0; i < nDims; i++)
     807             :     {
     808          63 :         stack[i].dst_inc_offset =
     809          63 :             static_cast<GPtrDiff_t>(bufferStride[i] * nBufferDTSize);
     810             :     }
     811          29 :     stack[0].dst_ptr = static_cast<GByte *>(pDstBuffer);
     812             : 
     813          29 :     size_t dimIdx = 0;
     814          29 :     const size_t iDimY = nDims - 2;
     815          29 :     const size_t iDimX = nDims - 1;
     816             :     // Use an array to avoid a false positive warning from CLang Static
     817             :     // Analyzer about flushCaches being never read
     818          29 :     bool flushCaches[] = {false};
     819             :     const bool bYXBandOrder =
     820          29 :         m_poParentDS->m_iYDim == 0 && m_poParentDS->m_iXDim == 1;
     821             : 
     822          38 : lbl_next_depth:
     823          38 :     if (dimIdx == iDimY)
     824             :     {
     825          33 :         if (flushCaches[0])
     826             :         {
     827           5 :             flushCaches[0] = false;
     828             :             // When changing of 2D slice, flush GDAL 2D buffers
     829           5 :             m_poParentDS->FlushCache(false);
     830           5 :             m_poReprojectedDS->FlushCache(false);
     831             :         }
     832             : 
     833          33 :         if (!GDALMDRasterIOFromBand(m_poReprojectedDS->GetRasterBand(1),
     834             :                                     GF_Read, iDimX, iDimY, arrayStartIdx, count,
     835             :                                     arrayStep, bufferStride, bufferDataType,
     836          33 :                                     stack[dimIdx].dst_ptr))
     837             :         {
     838           0 :             return false;
     839             :         }
     840             :     }
     841             :     else
     842             :     {
     843           5 :         stack[dimIdx].nIters = count[dimIdx];
     844           5 :         if (m_poParentDS->m_anOffset[bYXBandOrder ? 2 : dimIdx] !=
     845           5 :             arrayStartIdx[dimIdx])
     846             :         {
     847           1 :             flushCaches[0] = true;
     848             :         }
     849           5 :         m_poParentDS->m_anOffset[bYXBandOrder ? 2 : dimIdx] =
     850           5 :             arrayStartIdx[dimIdx];
     851             :         while (true)
     852             :         {
     853           9 :             dimIdx++;
     854           9 :             stack[dimIdx].dst_ptr = stack[dimIdx - 1].dst_ptr;
     855           9 :             goto lbl_next_depth;
     856           9 :         lbl_return_to_caller:
     857           9 :             dimIdx--;
     858           9 :             if ((--stack[dimIdx].nIters) == 0)
     859           5 :                 break;
     860           4 :             flushCaches[0] = true;
     861           4 :             ++m_poParentDS->m_anOffset[bYXBandOrder ? 2 : dimIdx];
     862           4 :             stack[dimIdx].dst_ptr += stack[dimIdx].dst_inc_offset;
     863             :         }
     864             :     }
     865          38 :     if (dimIdx > 0)
     866           9 :         goto lbl_return_to_caller;
     867             : 
     868          29 :     return true;
     869             : }
     870             : 
     871             : //! @endcond
     872             : 
     873             : /************************************************************************/
     874             : /*                            GetResampled()                            */
     875             : /************************************************************************/
     876             : 
     877             : /** Return an array that is a resampled / reprojected view of the current array
     878             :  *
     879             :  * This is the same as the C function GDALMDArrayGetResampled().
     880             :  *
     881             :  * Currently this method can only resample along the last 2 dimensions, unless
     882             :  * orthorectifying a NASA EMIT dataset.
     883             :  *
     884             :  * For NASA EMIT datasets, if apoNewDims[] and poTargetSRS is NULL, the
     885             :  * geometry lookup table (GLT) is used by default for fast orthorectification.
     886             :  *
     887             :  * Options available are:
     888             :  * <ul>
     889             :  * <li>EMIT_ORTHORECTIFICATION=YES/NO: defaults to YES for a NASA EMIT dataset.
     890             :  * Can be set to NO to use generic reprojection method.
     891             :  * </li>
     892             :  * <li>USE_GOOD_WAVELENGTHS=YES/NO: defaults to YES. Only used for EMIT
     893             :  * orthorectification to take into account the value of the
     894             :  * /sensor_band_parameters/good_wavelengths array to decide if slices of the
     895             :  * current array along the band dimension are valid.</li>
     896             :  * </ul>
     897             :  *
     898             :  * @param apoNewDims New dimensions. Its size should be GetDimensionCount().
     899             :  *                   apoNewDims[i] can be NULL to let the method automatically
     900             :  *                   determine it.
     901             :  * @param resampleAlg Resampling algorithm
     902             :  * @param poTargetSRS Target SRS, or nullptr
     903             :  * @param papszOptions NULL-terminated list of options, or NULL.
     904             :  *
     905             :  * @return a new array, that holds a reference to the original one, and thus is
     906             :  * a view of it (not a copy), or nullptr in case of error.
     907             :  *
     908             :  * @since 3.4
     909             :  */
     910          38 : std::shared_ptr<GDALMDArray> GDALMDArray::GetResampled(
     911             :     const std::vector<std::shared_ptr<GDALDimension>> &apoNewDims,
     912             :     GDALRIOResampleAlg resampleAlg, const OGRSpatialReference *poTargetSRS,
     913             :     CSLConstList papszOptions) const
     914             : {
     915          76 :     auto self = std::dynamic_pointer_cast<GDALMDArray>(m_pSelf.lock());
     916          38 :     if (!self)
     917             :     {
     918           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     919             :                  "Driver implementation issue: m_pSelf not set !");
     920           0 :         return nullptr;
     921             :     }
     922          38 :     if (GetDataType().GetClass() != GEDTC_NUMERIC)
     923             :     {
     924           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     925             :                  "GetResampled() only supports numeric data type");
     926           0 :         return nullptr;
     927             :     }
     928             : 
     929             :     // Special case for NASA EMIT datasets
     930          76 :     auto apoDims = GetDimensions();
     931          36 :     if (poTargetSRS == nullptr &&
     932          59 :         ((apoDims.size() == 3 && apoDims[0]->GetName() == "downtrack" &&
     933          20 :           apoDims[1]->GetName() == "crosstrack" &&
     934          10 :           apoDims[2]->GetName() == "bands" &&
     935          48 :           (apoNewDims == std::vector<std::shared_ptr<GDALDimension>>(3) ||
     936           1 :            apoNewDims ==
     937          42 :                std::vector<std::shared_ptr<GDALDimension>>{nullptr, nullptr,
     938          30 :                                                            apoDims[2]})) ||
     939          51 :          (apoDims.size() == 2 && apoDims[0]->GetName() == "downtrack" &&
     940           3 :           apoDims[1]->GetName() == "crosstrack" &&
     941          77 :           apoNewDims == std::vector<std::shared_ptr<GDALDimension>>(2))) &&
     942          13 :         CPLTestBool(CSLFetchNameValueDef(papszOptions,
     943             :                                          "EMIT_ORTHORECTIFICATION", "YES")))
     944             :     {
     945           9 :         auto poRootGroup = GetRootGroup();
     946           9 :         if (poRootGroup)
     947             :         {
     948          18 :             auto poAttrGeotransform = poRootGroup->GetAttribute("geotransform");
     949          18 :             auto poLocationGroup = poRootGroup->OpenGroup("location");
     950           9 :             if (poAttrGeotransform &&
     951           9 :                 poAttrGeotransform->GetDataType().GetClass() == GEDTC_NUMERIC &&
     952           9 :                 poAttrGeotransform->GetDimensionCount() == 1 &&
     953          27 :                 poAttrGeotransform->GetDimensionsSize()[0] == 6 &&
     954           9 :                 poLocationGroup)
     955             :             {
     956          18 :                 auto poGLT_X = poLocationGroup->OpenMDArray("glt_x");
     957          18 :                 auto poGLT_Y = poLocationGroup->OpenMDArray("glt_y");
     958          27 :                 if (poGLT_X && poGLT_X->GetDimensionCount() == 2 &&
     959          18 :                     poGLT_X->GetDimensions()[0]->GetName() == "ortho_y" &&
     960          18 :                     poGLT_X->GetDimensions()[1]->GetName() == "ortho_x" &&
     961          27 :                     poGLT_Y && poGLT_Y->GetDimensionCount() == 2 &&
     962          27 :                     poGLT_Y->GetDimensions()[0]->GetName() == "ortho_y" &&
     963           9 :                     poGLT_Y->GetDimensions()[1]->GetName() == "ortho_x")
     964             :                 {
     965             :                     return CreateGLTOrthorectified(
     966             :                         self, poRootGroup, poGLT_X, poGLT_Y,
     967             :                         /* nGLTIndexOffset = */ -1,
     968          18 :                         poAttrGeotransform->ReadAsDoubleArray(), papszOptions);
     969             :                 }
     970             :             }
     971             :         }
     972             :     }
     973             : 
     974          29 :     if (CPLTestBool(CSLFetchNameValueDef(papszOptions,
     975             :                                          "EMIT_ORTHORECTIFICATION", "NO")))
     976             :     {
     977           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     978             :                  "EMIT_ORTHORECTIFICATION required, but dataset and/or "
     979             :                  "parameters are not compatible with it");
     980           0 :         return nullptr;
     981             :     }
     982             : 
     983             :     return GDALMDArrayResampled::Create(self, apoNewDims, resampleAlg,
     984          29 :                                         poTargetSRS, papszOptions);
     985             : }

Generated by: LCOV version 1.14