LCOV - code coverage report
Current view: top level - apps - gdalmdimtranslate_lib.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 789 959 82.3 %
Date: 2024-05-03 15:49:35 Functions: 14 14 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL Utilities
       4             :  * Purpose:  Command line application to convert a multidimensional raster
       5             :  * Author:   Even Rouault,<even.rouault at spatialys.com>
       6             :  *
       7             :  * ****************************************************************************
       8             :  * Copyright (c) 2019, Even Rouault <even.rouault at spatialys.com>
       9             :  *
      10             :  * Permission is hereby granted, free of charge, to any person obtaining a
      11             :  * copy of this software and associated documentation files (the "Software"),
      12             :  * to deal in the Software without restriction, including without limitation
      13             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      14             :  * and/or sell copies of the Software, and to permit persons to whom the
      15             :  * Software is furnished to do so, subject to the following conditions:
      16             :  *
      17             :  * The above copyright notice and this permission notice shall be included
      18             :  * in all copies or substantial portions of the Software.
      19             :  *
      20             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      21             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      22             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      23             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      24             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      25             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      26             :  * DEALINGS IN THE SOFTWARE.
      27             :  ****************************************************************************/
      28             : 
      29             : #include "cpl_port.h"
      30             : #include "commonutils.h"
      31             : #include "gdal_priv.h"
      32             : #include "gdal_utils.h"
      33             : #include "gdal_utils_priv.h"
      34             : #include "vrtdataset.h"
      35             : #include <algorithm>
      36             : #include <map>
      37             : #include <set>
      38             : 
      39             : /************************************************************************/
      40             : /*                     GDALMultiDimTranslateOptions                     */
      41             : /************************************************************************/
      42             : 
      43             : struct GDALMultiDimTranslateOptions
      44             : {
      45             :     std::string osFormat{};
      46             :     CPLStringList aosCreateOptions{};
      47             :     std::vector<std::string> aosArraySpec{};
      48             :     CPLStringList aosArrayOptions{};
      49             :     std::vector<std::string> aosSubset{};
      50             :     std::vector<std::string> aosScaleFactor{};
      51             :     std::vector<std::string> aosGroup{};
      52             :     GDALProgressFunc pfnProgress = GDALDummyProgress;
      53             :     bool bStrict = false;
      54             :     void *pProgressData = nullptr;
      55             :     bool bUpdate = false;
      56             : };
      57             : 
      58             : /************************************************************************/
      59             : /*                        FindMinMaxIdxNumeric()                        */
      60             : /************************************************************************/
      61             : 
      62          39 : static void FindMinMaxIdxNumeric(const GDALMDArray *var, double *pdfTmp,
      63             :                                  const size_t nCount, const GUInt64 nStartIdx,
      64             :                                  const double dfMin, const double dfMax,
      65             :                                  const bool bSlice, bool &bFoundMinIdx,
      66             :                                  GUInt64 &nMinIdx, bool &bFoundMaxIdx,
      67             :                                  GUInt64 &nMaxIdx, bool &bLastWasReversed,
      68             :                                  bool &bEmpty, const double EPS)
      69             : {
      70          39 :     if (nCount >= 2)
      71             :     {
      72          39 :         bool bReversed = false;
      73          39 :         if (pdfTmp[0] > pdfTmp[nCount - 1])
      74             :         {
      75          18 :             bReversed = true;
      76          18 :             std::reverse(pdfTmp, pdfTmp + nCount);
      77             :         }
      78          39 :         if (nStartIdx > 0 && bLastWasReversed != bReversed)
      79             :         {
      80           0 :             CPLError(CE_Failure, CPLE_AppDefined,
      81           0 :                      "Variable %s is non monotonic", var->GetName().c_str());
      82           0 :             bEmpty = true;
      83           0 :             return;
      84             :         }
      85          39 :         bLastWasReversed = bReversed;
      86             : 
      87          39 :         if (!bFoundMinIdx)
      88             :         {
      89          39 :             if (bReversed && nStartIdx == 0 && dfMin > pdfTmp[nCount - 1])
      90             :             {
      91           2 :                 bEmpty = true;
      92           2 :                 return;
      93             :             }
      94          37 :             else if (!bReversed && dfMin < pdfTmp[0] - EPS)
      95             :             {
      96           6 :                 if (bSlice)
      97             :                 {
      98           1 :                     bEmpty = true;
      99           1 :                     return;
     100             :                 }
     101           5 :                 bFoundMinIdx = true;
     102           5 :                 nMinIdx = nStartIdx;
     103             :             }
     104          31 :             else if (dfMin >= pdfTmp[0] - EPS &&
     105          27 :                      dfMin <= pdfTmp[nCount - 1] + EPS)
     106             :             {
     107         128 :                 for (size_t i = 0; i < nCount; i++)
     108             :                 {
     109         128 :                     if (dfMin <= pdfTmp[i] + EPS)
     110             :                     {
     111          25 :                         bFoundMinIdx = true;
     112          25 :                         nMinIdx = nStartIdx + (bReversed ? nCount - 1 - i : i);
     113          25 :                         break;
     114             :                     }
     115             :                 }
     116          25 :                 CPLAssert(bFoundMinIdx);
     117             :             }
     118             :         }
     119          36 :         if (!bFoundMaxIdx)
     120             :         {
     121          36 :             if (bReversed && nStartIdx == 0 && dfMax > pdfTmp[nCount - 1])
     122             :             {
     123           2 :                 if (bSlice)
     124             :                 {
     125           0 :                     bEmpty = true;
     126           0 :                     return;
     127             :                 }
     128           2 :                 bFoundMaxIdx = true;
     129           2 :                 nMaxIdx = 0;
     130             :             }
     131          34 :             else if (!bReversed && dfMax < pdfTmp[0] - EPS)
     132             :             {
     133           1 :                 if (nStartIdx == 0)
     134             :                 {
     135           1 :                     bEmpty = true;
     136           1 :                     return;
     137             :                 }
     138           0 :                 bFoundMaxIdx = true;
     139           0 :                 nMaxIdx = nStartIdx - 1;
     140             :             }
     141          33 :             else if (dfMax > pdfTmp[0] - EPS &&
     142          31 :                      dfMax <= pdfTmp[nCount - 1] + EPS)
     143             :             {
     144         147 :                 for (size_t i = 1; i < nCount; i++)
     145             :                 {
     146         139 :                     if (dfMax <= pdfTmp[i] - EPS)
     147             :                     {
     148          17 :                         bFoundMaxIdx = true;
     149          17 :                         nMaxIdx = nStartIdx +
     150          17 :                                   (bReversed ? nCount - 1 - (i - 1) : i - 1);
     151          17 :                         break;
     152             :                     }
     153             :                 }
     154          25 :                 if (!bFoundMaxIdx)
     155             :                 {
     156           8 :                     bFoundMaxIdx = true;
     157           8 :                     nMaxIdx = nStartIdx + (bReversed ? 0 : nCount - 1);
     158             :                 }
     159             :             }
     160             :         }
     161             :     }
     162             :     else
     163             :     {
     164           0 :         if (!bFoundMinIdx)
     165             :         {
     166           0 :             if (dfMin <= pdfTmp[0] + EPS)
     167             :             {
     168           0 :                 bFoundMinIdx = true;
     169           0 :                 nMinIdx = nStartIdx;
     170             :             }
     171           0 :             else if (bLastWasReversed && nStartIdx > 0)
     172             :             {
     173           0 :                 bFoundMinIdx = true;
     174           0 :                 nMinIdx = nStartIdx - 1;
     175             :             }
     176             :         }
     177           0 :         if (!bFoundMaxIdx)
     178             :         {
     179           0 :             if (dfMax >= pdfTmp[0] - EPS)
     180             :             {
     181           0 :                 bFoundMaxIdx = true;
     182           0 :                 nMaxIdx = nStartIdx;
     183             :             }
     184           0 :             else if (!bLastWasReversed && nStartIdx > 0)
     185             :             {
     186           0 :                 bFoundMaxIdx = true;
     187           0 :                 nMaxIdx = nStartIdx - 1;
     188             :             }
     189             :         }
     190             :     }
     191             : }
     192             : 
     193             : /************************************************************************/
     194             : /*                        FindMinMaxIdxString()                         */
     195             : /************************************************************************/
     196             : 
     197          39 : static void FindMinMaxIdxString(const GDALMDArray *var, const char **ppszTmp,
     198             :                                 const size_t nCount, const GUInt64 nStartIdx,
     199             :                                 const std::string &osMin,
     200             :                                 const std::string &osMax, const bool bSlice,
     201             :                                 bool &bFoundMinIdx, GUInt64 &nMinIdx,
     202             :                                 bool &bFoundMaxIdx, GUInt64 &nMaxIdx,
     203             :                                 bool &bLastWasReversed, bool &bEmpty)
     204             : {
     205          39 :     bool bFoundNull = false;
     206         195 :     for (size_t i = 0; i < nCount; i++)
     207             :     {
     208         156 :         if (ppszTmp[i] == nullptr)
     209             :         {
     210           0 :             bFoundNull = true;
     211           0 :             break;
     212             :         }
     213             :     }
     214          39 :     if (bFoundNull)
     215             :     {
     216           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     217           0 :                  "Variable %s contains null strings", var->GetName().c_str());
     218           0 :         bEmpty = true;
     219           0 :         return;
     220             :     }
     221          39 :     if (nCount >= 2)
     222             :     {
     223          39 :         bool bReversed = false;
     224          39 :         if (std::string(ppszTmp[0]) > std::string(ppszTmp[nCount - 1]))
     225             :         {
     226          19 :             bReversed = true;
     227          19 :             std::reverse(ppszTmp, ppszTmp + nCount);
     228             :         }
     229          39 :         if (nStartIdx > 0 && bLastWasReversed != bReversed)
     230             :         {
     231           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     232           0 :                      "Variable %s is non monotonic", var->GetName().c_str());
     233           0 :             bEmpty = true;
     234           0 :             return;
     235             :         }
     236          39 :         bLastWasReversed = bReversed;
     237             : 
     238          39 :         if (!bFoundMinIdx)
     239             :         {
     240          58 :             if (bReversed && nStartIdx == 0 &&
     241          58 :                 osMin > std::string(ppszTmp[nCount - 1]))
     242             :             {
     243           2 :                 bEmpty = true;
     244           2 :                 return;
     245             :             }
     246          37 :             else if (!bReversed && osMin < std::string(ppszTmp[0]))
     247             :             {
     248           5 :                 if (bSlice)
     249             :                 {
     250           1 :                     bEmpty = true;
     251           1 :                     return;
     252             :                 }
     253           4 :                 bFoundMinIdx = true;
     254           4 :                 nMinIdx = nStartIdx;
     255             :             }
     256          91 :             else if (osMin >= std::string(ppszTmp[0]) &&
     257          59 :                      osMin <= std::string(ppszTmp[nCount - 1]))
     258             :             {
     259          65 :                 for (size_t i = 0; i < nCount; i++)
     260             :                 {
     261          65 :                     if (osMin <= std::string(ppszTmp[i]))
     262             :                     {
     263          25 :                         bFoundMinIdx = true;
     264          25 :                         nMinIdx = nStartIdx + (bReversed ? nCount - 1 - i : i);
     265          25 :                         break;
     266             :                     }
     267             :                 }
     268          25 :                 CPLAssert(bFoundMinIdx);
     269             :             }
     270             :         }
     271          36 :         if (!bFoundMaxIdx)
     272             :         {
     273          53 :             if (bReversed && nStartIdx == 0 &&
     274          53 :                 osMax > std::string(ppszTmp[nCount - 1]))
     275             :             {
     276           3 :                 if (bSlice)
     277             :                 {
     278           0 :                     bEmpty = true;
     279           0 :                     return;
     280             :                 }
     281           3 :                 bFoundMaxIdx = true;
     282           3 :                 nMaxIdx = 0;
     283             :             }
     284          33 :             else if (!bReversed && osMax < std::string(ppszTmp[0]))
     285             :             {
     286           1 :                 if (nStartIdx == 0)
     287             :                 {
     288           1 :                     bEmpty = true;
     289           1 :                     return;
     290             :                 }
     291           0 :                 bFoundMaxIdx = true;
     292           0 :                 nMaxIdx = nStartIdx - 1;
     293             :             }
     294          32 :             else if (osMax == std::string(ppszTmp[0]))
     295             :             {
     296           6 :                 bFoundMaxIdx = true;
     297           6 :                 nMaxIdx = nStartIdx + (bReversed ? nCount - 1 : 0);
     298             :             }
     299          76 :             else if (osMax > std::string(ppszTmp[0]) &&
     300          50 :                      osMax <= std::string(ppszTmp[nCount - 1]))
     301             :             {
     302          40 :                 for (size_t i = 1; i < nCount; i++)
     303             :                 {
     304          40 :                     if (osMax <= std::string(ppszTmp[i]))
     305             :                     {
     306          19 :                         bFoundMaxIdx = true;
     307          19 :                         if (osMax == std::string(ppszTmp[i]))
     308          15 :                             nMaxIdx =
     309          15 :                                 nStartIdx + (bReversed ? nCount - 1 - i : i);
     310             :                         else
     311           4 :                             nMaxIdx =
     312           4 :                                 nStartIdx +
     313           4 :                                 (bReversed ? nCount - 1 - (i - 1) : i - 1);
     314          19 :                         break;
     315             :                     }
     316             :                 }
     317          19 :                 CPLAssert(bFoundMaxIdx);
     318             :             }
     319             :         }
     320             :     }
     321             :     else
     322             :     {
     323           0 :         if (!bFoundMinIdx)
     324             :         {
     325           0 :             if (osMin <= std::string(ppszTmp[0]))
     326             :             {
     327           0 :                 bFoundMinIdx = true;
     328           0 :                 nMinIdx = nStartIdx;
     329             :             }
     330           0 :             else if (bLastWasReversed && nStartIdx > 0)
     331             :             {
     332           0 :                 bFoundMinIdx = true;
     333           0 :                 nMinIdx = nStartIdx - 1;
     334             :             }
     335             :         }
     336           0 :         if (!bFoundMaxIdx)
     337             :         {
     338           0 :             if (osMax >= std::string(ppszTmp[0]))
     339             :             {
     340           0 :                 bFoundMaxIdx = true;
     341           0 :                 nMaxIdx = nStartIdx;
     342             :             }
     343           0 :             else if (!bLastWasReversed && nStartIdx > 0)
     344             :             {
     345           0 :                 bFoundMaxIdx = true;
     346           0 :                 nMaxIdx = nStartIdx - 1;
     347             :             }
     348             :         }
     349             :     }
     350             : }
     351             : 
     352             : /************************************************************************/
     353             : /*                             GetDimensionDesc()                       */
     354             : /************************************************************************/
     355             : 
     356             : struct DimensionDesc
     357             : {
     358             :     GUInt64 nStartIdx = 0;
     359             :     GUInt64 nStep = 1;
     360             :     GUInt64 nSize = 0;
     361             :     GUInt64 nOriSize = 0;
     362             :     bool bSlice = false;
     363             : };
     364             : 
     365             : struct DimensionRemapper
     366             : {
     367             :     std::map<std::string, DimensionDesc> oMap{};
     368             : };
     369             : 
     370             : static const DimensionDesc *
     371         167 : GetDimensionDesc(DimensionRemapper &oDimRemapper,
     372             :                  const GDALMultiDimTranslateOptions *psOptions,
     373             :                  const std::shared_ptr<GDALDimension> &poDim)
     374             : {
     375         334 :     std::string osKey(poDim->GetFullName());
     376             :     osKey +=
     377         167 :         CPLSPrintf("_" CPL_FRMT_GUIB, static_cast<GUIntBig>(poDim->GetSize()));
     378         167 :     auto oIter = oDimRemapper.oMap.find(osKey);
     379         248 :     if (oIter != oDimRemapper.oMap.end() &&
     380          81 :         oIter->second.nOriSize == poDim->GetSize())
     381             :     {
     382          81 :         return &(oIter->second);
     383             :     }
     384          86 :     DimensionDesc desc;
     385          86 :     desc.nSize = poDim->GetSize();
     386          86 :     desc.nOriSize = desc.nSize;
     387             : 
     388         172 :     CPLString osRadix(poDim->GetName());
     389          86 :     osRadix += '(';
     390          91 :     for (const auto &subset : psOptions->aosSubset)
     391             :     {
     392          86 :         if (STARTS_WITH(subset.c_str(), osRadix.c_str()))
     393             :         {
     394          81 :             auto var = poDim->GetIndexingVariable();
     395         162 :             if (!var || var->GetDimensionCount() != 1 ||
     396          81 :                 var->GetDimensions()[0]->GetSize() != poDim->GetSize())
     397             :             {
     398           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     399             :                          "Dimension %s has a subset specification, but lacks "
     400             :                          "a single dimension indexing variable",
     401           0 :                          poDim->GetName().c_str());
     402           0 :                 return nullptr;
     403             :             }
     404          81 :             if (subset.back() != ')')
     405             :             {
     406           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
     407             :                          "Missing ')' in subset specification.");
     408           2 :                 return nullptr;
     409             :             }
     410             :             CPLStringList aosTokens(CSLTokenizeString2(
     411             :                 subset
     412          79 :                     .substr(osRadix.size(), subset.size() - 1 - osRadix.size())
     413             :                     .c_str(),
     414          79 :                 ",", CSLT_HONOURSTRINGS));
     415          79 :             if (aosTokens.size() == 1)
     416             :             {
     417          25 :                 desc.bSlice = true;
     418             :             }
     419          79 :             if (aosTokens.size() != 1 && aosTokens.size() != 2)
     420             :             {
     421           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     422             :                          "Invalid number of valus in subset specification.");
     423           1 :                 return nullptr;
     424             :             }
     425             : 
     426             :             const bool bIsNumeric =
     427          78 :                 var->GetDataType().GetClass() == GEDTC_NUMERIC;
     428             :             const GDALExtendedDataType dt(
     429             :                 bIsNumeric ? GDALExtendedDataType::Create(GDT_Float64)
     430          78 :                            : GDALExtendedDataType::CreateString());
     431             : 
     432          78 :             double dfMin = 0;
     433          78 :             double dfMax = 0;
     434          78 :             std::string osMin;
     435          78 :             std::string osMax;
     436          78 :             if (bIsNumeric)
     437             :             {
     438          78 :                 if (CPLGetValueType(aosTokens[0]) == CPL_VALUE_STRING ||
     439          39 :                     (aosTokens.size() == 2 &&
     440          27 :                      CPLGetValueType(aosTokens[1]) == CPL_VALUE_STRING))
     441             :                 {
     442           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
     443             :                              "Non numeric bound in subset specification.");
     444           0 :                     return nullptr;
     445             :                 }
     446          39 :                 dfMin = CPLAtof(aosTokens[0]);
     447          39 :                 dfMax = dfMin;
     448          39 :                 if (aosTokens.size() == 2)
     449          27 :                     dfMax = CPLAtof(aosTokens[1]);
     450          39 :                 if (dfMin > dfMax)
     451           0 :                     std::swap(dfMin, dfMax);
     452             :             }
     453             :             else
     454             :             {
     455          39 :                 osMin = aosTokens[0];
     456          39 :                 osMax = osMin;
     457          39 :                 if (aosTokens.size() == 2)
     458          26 :                     osMax = aosTokens[1];
     459          39 :                 if (osMin > osMax)
     460           0 :                     std::swap(osMin, osMax);
     461             :             }
     462             : 
     463          78 :             const size_t nDTSize(dt.GetSize());
     464             :             const size_t nMaxChunkSize = static_cast<size_t>(std::min(
     465          78 :                 static_cast<GUInt64>(10 * 1000 * 1000), poDim->GetSize()));
     466          78 :             std::vector<GByte> abyTmp(nDTSize * nMaxChunkSize);
     467          78 :             double *pdfTmp = reinterpret_cast<double *>(&abyTmp[0]);
     468          78 :             const char **ppszTmp = reinterpret_cast<const char **>(&abyTmp[0]);
     469          78 :             GUInt64 nStartIdx = 0;
     470         156 :             const double EPS = std::max(std::max(1e-10, fabs(dfMin) / 1e10),
     471          78 :                                         fabs(dfMax) / 1e10);
     472          78 :             bool bFoundMinIdx = false;
     473          78 :             bool bFoundMaxIdx = false;
     474          78 :             GUInt64 nMinIdx = 0;
     475          78 :             GUInt64 nMaxIdx = 0;
     476          78 :             bool bLastWasReversed = false;
     477          78 :             bool bEmpty = false;
     478             :             while (true)
     479             :             {
     480             :                 const size_t nCount = static_cast<size_t>(
     481         196 :                     std::min(static_cast<GUInt64>(nMaxChunkSize),
     482          98 :                              poDim->GetSize() - nStartIdx));
     483          98 :                 if (nCount == 0)
     484          20 :                     break;
     485          78 :                 const GUInt64 anStartId[] = {nStartIdx};
     486          78 :                 const size_t anCount[] = {nCount};
     487         156 :                 if (!var->Read(anStartId, anCount, nullptr, nullptr, dt,
     488          78 :                                &abyTmp[0], nullptr, 0))
     489             :                 {
     490           0 :                     return nullptr;
     491             :                 }
     492          78 :                 if (bIsNumeric)
     493             :                 {
     494          39 :                     FindMinMaxIdxNumeric(
     495          39 :                         var.get(), pdfTmp, nCount, nStartIdx, dfMin, dfMax,
     496          39 :                         desc.bSlice, bFoundMinIdx, nMinIdx, bFoundMaxIdx,
     497             :                         nMaxIdx, bLastWasReversed, bEmpty, EPS);
     498             :                 }
     499             :                 else
     500             :                 {
     501          39 :                     FindMinMaxIdxString(var.get(), ppszTmp, nCount, nStartIdx,
     502          39 :                                         osMin, osMax, desc.bSlice, bFoundMinIdx,
     503             :                                         nMinIdx, bFoundMaxIdx, nMaxIdx,
     504             :                                         bLastWasReversed, bEmpty);
     505             :                 }
     506          78 :                 if (dt.NeedsFreeDynamicMemory())
     507             :                 {
     508         195 :                     for (size_t i = 0; i < nCount; i++)
     509             :                     {
     510         156 :                         dt.FreeDynamicMemory(&abyTmp[i * nDTSize]);
     511             :                     }
     512             :                 }
     513          78 :                 if (bEmpty || (bFoundMinIdx && bFoundMaxIdx) ||
     514             :                     nCount < nMaxChunkSize)
     515             :                 {
     516             :                     break;
     517             :                 }
     518          20 :                 nStartIdx += nMaxChunkSize;
     519          20 :             }
     520             : 
     521             :             // cppcheck-suppress knownConditionTrueFalse
     522          78 :             if (!bLastWasReversed)
     523             :             {
     524          41 :                 if (!bFoundMinIdx)
     525           6 :                     bEmpty = true;
     526          35 :                 else if (!bFoundMaxIdx)
     527           9 :                     nMaxIdx = poDim->GetSize() - 1;
     528             :                 else
     529          26 :                     bEmpty = nMaxIdx < nMinIdx;
     530             :             }
     531             :             else
     532             :             {
     533          37 :                 if (!bFoundMaxIdx)
     534           8 :                     bEmpty = true;
     535          29 :                 else if (!bFoundMinIdx)
     536           5 :                     nMinIdx = poDim->GetSize() - 1;
     537             :                 else
     538          24 :                     bEmpty = nMinIdx < nMaxIdx;
     539             :             }
     540          78 :             if (bEmpty)
     541             :             {
     542          16 :                 CPLError(CE_Failure, CPLE_AppDefined,
     543             :                          "Subset specification results in an empty set");
     544          16 :                 return nullptr;
     545             :             }
     546             : 
     547             :             // cppcheck-suppress knownConditionTrueFalse
     548          62 :             if (!bLastWasReversed)
     549             :             {
     550          33 :                 CPLAssert(nMaxIdx >= nMinIdx);
     551          33 :                 desc.nStartIdx = nMinIdx;
     552          33 :                 desc.nSize = nMaxIdx - nMinIdx + 1;
     553             :             }
     554             :             else
     555             :             {
     556          29 :                 CPLAssert(nMaxIdx <= nMinIdx);
     557          29 :                 desc.nStartIdx = nMaxIdx;
     558          29 :                 desc.nSize = nMinIdx - nMaxIdx + 1;
     559             :             }
     560             : 
     561          62 :             break;
     562             :         }
     563             :     }
     564             : 
     565          69 :     for (const auto &scaleFactor : psOptions->aosScaleFactor)
     566             :     {
     567           3 :         if (STARTS_WITH(scaleFactor.c_str(), osRadix.c_str()))
     568             :         {
     569           1 :             if (scaleFactor.back() != ')')
     570             :             {
     571           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     572             :                          "Missing ')' in scalefactor specification.");
     573           0 :                 return nullptr;
     574             :             }
     575             :             std::string osScaleFactor(scaleFactor.substr(
     576           1 :                 osRadix.size(), scaleFactor.size() - 1 - osRadix.size()));
     577           1 :             int nScaleFactor = atoi(osScaleFactor.c_str());
     578           1 :             if (CPLGetValueType(osScaleFactor.c_str()) != CPL_VALUE_INTEGER ||
     579             :                 nScaleFactor <= 0)
     580             :             {
     581           0 :                 CPLError(CE_Failure, CPLE_NotSupported,
     582             :                          "Only positive integer scale factor is supported");
     583           0 :                 return nullptr;
     584             :             }
     585           1 :             desc.nSize /= nScaleFactor;
     586           1 :             if (desc.nSize == 0)
     587           0 :                 desc.nSize = 1;
     588           1 :             desc.nStep *= nScaleFactor;
     589           1 :             break;
     590             :         }
     591             :     }
     592             : 
     593          67 :     oDimRemapper.oMap[osKey] = desc;
     594          67 :     return &oDimRemapper.oMap[osKey];
     595             : }
     596             : 
     597             : /************************************************************************/
     598             : /*                           ParseArraySpec()                           */
     599             : /************************************************************************/
     600             : 
     601             : // foo
     602             : // name=foo,transpose=[1,0],view=[0],dstname=bar,ot=Float32
     603         121 : static bool ParseArraySpec(const std::string &arraySpec, std::string &srcName,
     604             :                            std::string &dstName, int &band,
     605             :                            std::vector<int> &anTransposedAxis,
     606             :                            std::string &viewExpr,
     607             :                            GDALExtendedDataType &outputType, bool &bResampled)
     608             : {
     609         233 :     if (!STARTS_WITH(arraySpec.c_str(), "name=") &&
     610         112 :         !STARTS_WITH(arraySpec.c_str(), "band="))
     611             :     {
     612         111 :         srcName = arraySpec;
     613         111 :         dstName = arraySpec;
     614         111 :         auto pos = dstName.rfind('/');
     615         111 :         if (pos != std::string::npos)
     616          16 :             dstName = dstName.substr(pos + 1);
     617         111 :         return true;
     618             :     }
     619             : 
     620          20 :     std::vector<std::string> tokens;
     621          20 :     std::string curToken;
     622          10 :     bool bInArray = false;
     623         451 :     for (size_t i = 0; i < arraySpec.size(); ++i)
     624             :     {
     625         441 :         if (!bInArray && arraySpec[i] == ',')
     626             :         {
     627          14 :             tokens.emplace_back(std::move(curToken));
     628          14 :             curToken = std::string();
     629             :         }
     630             :         else
     631             :         {
     632         427 :             if (arraySpec[i] == '[')
     633             :             {
     634           5 :                 bInArray = true;
     635             :             }
     636         422 :             else if (arraySpec[i] == ']')
     637             :             {
     638           5 :                 bInArray = false;
     639             :             }
     640         427 :             curToken += arraySpec[i];
     641             :         }
     642             :     }
     643          10 :     if (!curToken.empty())
     644             :     {
     645          10 :         tokens.emplace_back(std::move(curToken));
     646             :     }
     647          33 :     for (const auto &token : tokens)
     648             :     {
     649          24 :         if (STARTS_WITH(token.c_str(), "name="))
     650             :         {
     651           9 :             srcName = token.substr(strlen("name="));
     652           9 :             if (dstName.empty())
     653           9 :                 dstName = srcName;
     654             :         }
     655          15 :         else if (STARTS_WITH(token.c_str(), "band="))
     656             :         {
     657           1 :             band = atoi(token.substr(strlen("band=")).c_str());
     658           1 :             if (dstName.empty())
     659           1 :                 dstName = CPLSPrintf("Band%d", band);
     660             :         }
     661          14 :         else if (STARTS_WITH(token.c_str(), "dstname="))
     662             :         {
     663           7 :             dstName = token.substr(strlen("dstname="));
     664             :         }
     665           7 :         else if (STARTS_WITH(token.c_str(), "transpose="))
     666             :         {
     667           1 :             auto transposeExpr = token.substr(strlen("transpose="));
     668           2 :             if (transposeExpr.size() < 3 || transposeExpr[0] != '[' ||
     669           1 :                 transposeExpr.back() != ']')
     670             :             {
     671           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     672             :                          "Invalid value for transpose");
     673           0 :                 return false;
     674             :             }
     675           1 :             transposeExpr = transposeExpr.substr(1, transposeExpr.size() - 2);
     676             :             CPLStringList aosAxis(
     677           2 :                 CSLTokenizeString2(transposeExpr.c_str(), ",", 0));
     678           4 :             for (int i = 0; i < aosAxis.size(); ++i)
     679             :             {
     680           3 :                 anTransposedAxis.push_back(atoi(aosAxis[i]));
     681             :             }
     682             :         }
     683           6 :         else if (STARTS_WITH(token.c_str(), "view="))
     684             :         {
     685           4 :             viewExpr = token.substr(strlen("view="));
     686             :         }
     687           2 :         else if (STARTS_WITH(token.c_str(), "ot="))
     688             :         {
     689           0 :             auto outputTypeStr = token.substr(strlen("ot="));
     690           0 :             if (outputTypeStr == "String")
     691           0 :                 outputType = GDALExtendedDataType::CreateString();
     692             :             else
     693             :             {
     694           0 :                 auto eDT = GDALGetDataTypeByName(outputTypeStr.c_str());
     695           0 :                 if (eDT == GDT_Unknown)
     696           0 :                     return false;
     697           0 :                 outputType = GDALExtendedDataType::Create(eDT);
     698             :             }
     699             :         }
     700           2 :         else if (STARTS_WITH(token.c_str(), "resample="))
     701             :         {
     702           1 :             bResampled = CPLTestBool(token.c_str() + strlen("resample="));
     703             :         }
     704             :         else
     705             :         {
     706           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     707             :                      "Unexpected array specification part: %s", token.c_str());
     708           1 :             return false;
     709             :         }
     710             :     }
     711           9 :     return true;
     712             : }
     713             : 
     714             : /************************************************************************/
     715             : /*                           TranslateArray()                           */
     716             : /************************************************************************/
     717             : 
     718         119 : static bool TranslateArray(
     719             :     DimensionRemapper &oDimRemapper,
     720             :     const std::shared_ptr<GDALMDArray> &poSrcArrayIn,
     721             :     const std::string &arraySpec,
     722             :     const std::shared_ptr<GDALGroup> &poSrcRootGroup,
     723             :     const std::shared_ptr<GDALGroup> &poSrcGroup,
     724             :     const std::shared_ptr<GDALGroup> &poDstRootGroup,
     725             :     std::shared_ptr<GDALGroup> &poDstGroup, GDALDataset *poSrcDS,
     726             :     std::map<std::string, std::shared_ptr<GDALDimension>> &mapSrcToDstDims,
     727             :     std::map<std::string, std::shared_ptr<GDALDimension>> &mapDstDimFullNames,
     728             :     const GDALMultiDimTranslateOptions *psOptions)
     729             : {
     730         238 :     std::string srcArrayName;
     731         238 :     std::string dstArrayName;
     732         119 :     int band = -1;
     733         238 :     std::vector<int> anTransposedAxis;
     734         238 :     std::string viewExpr;
     735         119 :     bool bResampled = false;
     736         238 :     GDALExtendedDataType outputType(GDALExtendedDataType::Create(GDT_Unknown));
     737         119 :     if (!ParseArraySpec(arraySpec, srcArrayName, dstArrayName, band,
     738             :                         anTransposedAxis, viewExpr, outputType, bResampled))
     739             :     {
     740           1 :         return false;
     741             :     }
     742             : 
     743         118 :     std::shared_ptr<GDALMDArray> srcArray;
     744         118 :     bool bSrcArrayAccessibleThroughSrcGroup = true;
     745         118 :     if (poSrcRootGroup && poSrcGroup)
     746             :     {
     747         117 :         if (!srcArrayName.empty() && srcArrayName[0] == '/')
     748          20 :             srcArray = poSrcRootGroup->OpenMDArrayFromFullname(srcArrayName);
     749             :         else
     750          97 :             srcArray = poSrcGroup->OpenMDArray(srcArrayName);
     751         117 :         if (!srcArray)
     752             :         {
     753           3 :             if (poSrcArrayIn && poSrcArrayIn->GetFullName() == arraySpec)
     754             :             {
     755           2 :                 bSrcArrayAccessibleThroughSrcGroup = false;
     756           2 :                 srcArray = poSrcArrayIn;
     757             :             }
     758             :             else
     759             :             {
     760           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Cannot find array %s",
     761             :                          srcArrayName.c_str());
     762           1 :                 return false;
     763             :             }
     764             :         }
     765             :     }
     766             :     else
     767             :     {
     768           1 :         auto poBand = poSrcDS->GetRasterBand(band);
     769           1 :         if (!poBand)
     770           0 :             return false;
     771           1 :         srcArray = poBand->AsMDArray();
     772             :     }
     773             : 
     774         234 :     auto tmpArray = srcArray;
     775             : 
     776         117 :     if (bResampled)
     777             :     {
     778             :         auto newTmpArray =
     779           3 :             tmpArray->GetResampled(std::vector<std::shared_ptr<GDALDimension>>(
     780           1 :                                        tmpArray->GetDimensionCount()),
     781           2 :                                    GRIORA_NearestNeighbour, nullptr, nullptr);
     782           1 :         if (!newTmpArray)
     783           0 :             return false;
     784           1 :         tmpArray = std::move(newTmpArray);
     785             :     }
     786             : 
     787         117 :     if (!anTransposedAxis.empty())
     788             :     {
     789           1 :         auto newTmpArray = tmpArray->Transpose(anTransposedAxis);
     790           1 :         if (!newTmpArray)
     791           0 :             return false;
     792           1 :         tmpArray = std::move(newTmpArray);
     793             :     }
     794         117 :     const auto &srcArrayDims(tmpArray->GetDimensions());
     795             :     std::map<std::shared_ptr<GDALDimension>, std::shared_ptr<GDALDimension>>
     796         234 :         oMapSubsetDimToSrcDim;
     797             : 
     798         234 :     std::vector<GDALMDArray::ViewSpec> viewSpecs;
     799         117 :     if (!viewExpr.empty())
     800             :     {
     801           4 :         if (!psOptions->aosSubset.empty() || !psOptions->aosScaleFactor.empty())
     802             :         {
     803           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     804             :                      "View specification not supported when used together "
     805             :                      "with subset and/or scalefactor options");
     806           0 :             return false;
     807             :         }
     808           4 :         auto newTmpArray = tmpArray->GetView(viewExpr, true, viewSpecs);
     809           4 :         if (!newTmpArray)
     810           0 :             return false;
     811           4 :         tmpArray = std::move(newTmpArray);
     812             :     }
     813         143 :     else if (!psOptions->aosSubset.empty() ||
     814          30 :              !psOptions->aosScaleFactor.empty())
     815             :     {
     816          87 :         bool bHasModifiedDim = false;
     817          87 :         viewExpr = '[';
     818         165 :         for (size_t i = 0; i < srcArrayDims.size(); ++i)
     819             :         {
     820          94 :             const auto &srcDim(srcArrayDims[i]);
     821             :             const auto poDimDesc =
     822          94 :                 GetDimensionDesc(oDimRemapper, psOptions, srcDim);
     823          94 :             if (poDimDesc == nullptr)
     824          16 :                 return false;
     825          78 :             if (i > 0)
     826           7 :                 viewExpr += ',';
     827          60 :             if (!poDimDesc->bSlice && poDimDesc->nStartIdx == 0 &&
     828         138 :                 poDimDesc->nStep == 1 && poDimDesc->nSize == srcDim->GetSize())
     829             :             {
     830          18 :                 viewExpr += ":";
     831             :             }
     832             :             else
     833             :             {
     834          60 :                 bHasModifiedDim = true;
     835             :                 viewExpr += CPLSPrintf(
     836          60 :                     CPL_FRMT_GUIB, static_cast<GUInt64>(poDimDesc->nStartIdx));
     837          60 :                 if (!poDimDesc->bSlice)
     838             :                 {
     839          42 :                     viewExpr += ':';
     840             :                     viewExpr +=
     841             :                         CPLSPrintf(CPL_FRMT_GUIB,
     842          42 :                                    static_cast<GUInt64>(poDimDesc->nStartIdx +
     843          42 :                                                         poDimDesc->nSize *
     844          42 :                                                             poDimDesc->nStep));
     845          42 :                     viewExpr += ':';
     846             :                     viewExpr += CPLSPrintf(
     847          42 :                         CPL_FRMT_GUIB, static_cast<GUInt64>(poDimDesc->nStep));
     848             :                 }
     849             :             }
     850             :         }
     851          71 :         viewExpr += ']';
     852          71 :         if (bHasModifiedDim)
     853             :         {
     854          59 :             auto tmpArrayNew = tmpArray->GetView(viewExpr, false, viewSpecs);
     855          59 :             if (!tmpArrayNew)
     856           0 :                 return false;
     857          59 :             tmpArray = std::move(tmpArrayNew);
     858          59 :             size_t j = 0;
     859          59 :             const auto &tmpArrayDims(tmpArray->GetDimensions());
     860         125 :             for (size_t i = 0; i < srcArrayDims.size(); ++i)
     861             :             {
     862          66 :                 const auto &srcDim(srcArrayDims[i]);
     863             :                 const auto poDimDesc =
     864          66 :                     GetDimensionDesc(oDimRemapper, psOptions, srcDim);
     865          66 :                 if (poDimDesc == nullptr)
     866           0 :                     return false;
     867          66 :                 if (poDimDesc->bSlice)
     868          18 :                     continue;
     869          48 :                 CPLAssert(j < tmpArrayDims.size());
     870          48 :                 oMapSubsetDimToSrcDim[tmpArrayDims[j]] = srcDim;
     871          48 :                 j++;
     872             :             }
     873             :         }
     874             :         else
     875             :         {
     876          12 :             viewExpr.clear();
     877             :         }
     878             :     }
     879             : 
     880         101 :     int idxSliceSpec = -1;
     881         164 :     for (size_t i = 0; i < viewSpecs.size(); ++i)
     882             :     {
     883          63 :         if (viewSpecs[i].m_osFieldName.empty())
     884             :         {
     885          63 :             if (idxSliceSpec >= 0)
     886             :             {
     887           0 :                 idxSliceSpec = -1;
     888           0 :                 break;
     889             :             }
     890             :             else
     891             :             {
     892          63 :                 idxSliceSpec = static_cast<int>(i);
     893             :             }
     894             :         }
     895             :     }
     896             : 
     897             :     // Map source dimensions to target dimensions
     898         202 :     std::vector<std::shared_ptr<GDALDimension>> dstArrayDims;
     899         101 :     const auto &tmpArrayDims(tmpArray->GetDimensions());
     900         205 :     for (size_t i = 0; i < tmpArrayDims.size(); ++i)
     901             :     {
     902         104 :         const auto &srcDim(tmpArrayDims[i]);
     903         104 :         std::string srcDimFullName(srcDim->GetFullName());
     904             : 
     905           0 :         std::shared_ptr<GDALDimension> dstDim;
     906             :         {
     907         208 :             CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
     908         104 :             if (!srcDimFullName.empty() && srcDimFullName[0] == '/')
     909             :             {
     910             :                 dstDim =
     911          51 :                     poDstRootGroup->OpenDimensionFromFullname(srcDimFullName);
     912             :             }
     913             :         }
     914         104 :         if (dstDim)
     915             :         {
     916          21 :             dstArrayDims.emplace_back(dstDim);
     917          21 :             continue;
     918             :         }
     919             : 
     920          83 :         auto oIter = mapSrcToDstDims.find(srcDimFullName);
     921          83 :         if (oIter != mapSrcToDstDims.end())
     922             :         {
     923           2 :             dstArrayDims.emplace_back(oIter->second);
     924           2 :             continue;
     925             :         }
     926          81 :         auto oIterRealSrcDim = oMapSubsetDimToSrcDim.find(srcDim);
     927          81 :         if (oIterRealSrcDim != oMapSubsetDimToSrcDim.end())
     928             :         {
     929          44 :             srcDimFullName = oIterRealSrcDim->second->GetFullName();
     930          44 :             oIter = mapSrcToDstDims.find(srcDimFullName);
     931          44 :             if (oIter != mapSrcToDstDims.end())
     932             :             {
     933           5 :                 dstArrayDims.emplace_back(oIter->second);
     934           5 :                 continue;
     935             :             }
     936             :         }
     937             : 
     938          76 :         const auto nDimSize = srcDim->GetSize();
     939          76 :         std::string newDimNameFullName(srcDimFullName);
     940          76 :         std::string newDimName(srcDim->GetName());
     941          76 :         int nIncr = 2;
     942          76 :         std::string osDstGroupFullName(poDstGroup->GetFullName());
     943          76 :         if (osDstGroupFullName == "/")
     944          74 :             osDstGroupFullName.clear();
     945         152 :         auto oIter2 = mapDstDimFullNames.find(osDstGroupFullName + '/' +
     946         152 :                                               srcDim->GetName());
     947          81 :         while (oIter2 != mapDstDimFullNames.end() &&
     948           4 :                oIter2->second->GetSize() != nDimSize)
     949             :         {
     950           1 :             newDimName = srcDim->GetName() + CPLSPrintf("_%d", nIncr);
     951           2 :             newDimNameFullName = osDstGroupFullName + '/' + srcDim->GetName() +
     952           1 :                                  CPLSPrintf("_%d", nIncr);
     953           1 :             nIncr++;
     954           1 :             oIter2 = mapDstDimFullNames.find(newDimNameFullName);
     955             :         }
     956          79 :         if (oIter2 != mapDstDimFullNames.end() &&
     957           3 :             oIter2->second->GetSize() == nDimSize)
     958             :         {
     959           3 :             dstArrayDims.emplace_back(oIter2->second);
     960           3 :             continue;
     961             :         }
     962             : 
     963         219 :         dstDim = poDstGroup->CreateDimension(newDimName, srcDim->GetType(),
     964         146 :                                              srcDim->GetDirection(), nDimSize);
     965          73 :         if (!dstDim)
     966           0 :             return false;
     967          73 :         if (!srcDimFullName.empty() && srcDimFullName[0] == '/')
     968             :         {
     969          65 :             mapSrcToDstDims[srcDimFullName] = dstDim;
     970             :         }
     971          73 :         mapDstDimFullNames[dstDim->GetFullName()] = dstDim;
     972          73 :         dstArrayDims.emplace_back(dstDim);
     973             : 
     974           0 :         std::shared_ptr<GDALMDArray> srcIndexVar;
     975          73 :         GDALMDArray::Range range;
     976          73 :         range.m_nStartIdx = 0;
     977          73 :         range.m_nIncr = 1;
     978          73 :         std::string indexingVarSpec;
     979          73 :         if (idxSliceSpec >= 0)
     980             :         {
     981          46 :             const auto &viewSpec(viewSpecs[idxSliceSpec]);
     982          46 :             auto iParentDim = viewSpec.m_mapDimIdxToParentDimIdx[i];
     983          45 :             if (iParentDim != static_cast<size_t>(-1) &&
     984             :                 (srcIndexVar =
     985          91 :                      srcArrayDims[iParentDim]->GetIndexingVariable()) !=
     986          43 :                     nullptr &&
     987         134 :                 srcIndexVar->GetDimensionCount() == 1 &&
     988          43 :                 srcIndexVar->GetFullName() != srcArray->GetFullName())
     989             :             {
     990           7 :                 CPLAssert(iParentDim < viewSpec.m_parentRanges.size());
     991           7 :                 range = viewSpec.m_parentRanges[iParentDim];
     992           7 :                 indexingVarSpec = "name=" + srcIndexVar->GetFullName();
     993           7 :                 indexingVarSpec += ",dstname=" + newDimName;
     994          14 :                 if (psOptions->aosSubset.empty() &&
     995           7 :                     psOptions->aosScaleFactor.empty())
     996             :                 {
     997           7 :                     if (range.m_nStartIdx != 0 || range.m_nIncr != 1 ||
     998           3 :                         srcArrayDims[iParentDim]->GetSize() !=
     999           3 :                             srcDim->GetSize())
    1000             :                     {
    1001           1 :                         indexingVarSpec += ",view=[";
    1002           2 :                         if (range.m_nIncr > 0 ||
    1003           1 :                             range.m_nStartIdx != srcDim->GetSize() - 1)
    1004             :                         {
    1005             :                             indexingVarSpec +=
    1006           0 :                                 CPLSPrintf(CPL_FRMT_GUIB, range.m_nStartIdx);
    1007             :                         }
    1008           1 :                         indexingVarSpec += ':';
    1009           1 :                         if (range.m_nIncr > 0)
    1010             :                         {
    1011             :                             const auto nEndIdx =
    1012           0 :                                 range.m_nStartIdx +
    1013           0 :                                 range.m_nIncr * srcDim->GetSize();
    1014             :                             indexingVarSpec +=
    1015           0 :                                 CPLSPrintf(CPL_FRMT_GUIB, nEndIdx);
    1016             :                         }
    1017           2 :                         else if (range.m_nStartIdx >
    1018           1 :                                  -range.m_nIncr * srcDim->GetSize())
    1019             :                         {
    1020             :                             const auto nEndIdx =
    1021           0 :                                 range.m_nStartIdx +
    1022           0 :                                 range.m_nIncr * srcDim->GetSize();
    1023             :                             indexingVarSpec +=
    1024           0 :                                 CPLSPrintf(CPL_FRMT_GUIB, nEndIdx - 1);
    1025             :                         }
    1026           1 :                         indexingVarSpec += ':';
    1027             :                         indexingVarSpec +=
    1028           1 :                             CPLSPrintf(CPL_FRMT_GIB, range.m_nIncr);
    1029           1 :                         indexingVarSpec += ']';
    1030             :                     }
    1031             :                 }
    1032             :             }
    1033             :         }
    1034             :         else
    1035             :         {
    1036          27 :             srcIndexVar = srcDim->GetIndexingVariable();
    1037          27 :             if (srcIndexVar)
    1038             :             {
    1039          25 :                 indexingVarSpec = srcIndexVar->GetFullName();
    1040             :             }
    1041             :         }
    1042         105 :         if (srcIndexVar && !indexingVarSpec.empty() &&
    1043          32 :             srcIndexVar->GetFullName() != srcArray->GetFullName())
    1044             :         {
    1045          23 :             if (poSrcRootGroup)
    1046             :             {
    1047          21 :                 if (!TranslateArray(oDimRemapper, srcIndexVar, indexingVarSpec,
    1048             :                                     poSrcRootGroup, poSrcGroup, poDstRootGroup,
    1049             :                                     poDstGroup, poSrcDS, mapSrcToDstDims,
    1050             :                                     mapDstDimFullNames, psOptions))
    1051             :                 {
    1052           0 :                     return false;
    1053             :                 }
    1054             :             }
    1055             :             else
    1056             :             {
    1057             :                 double adfGT[6];
    1058           2 :                 if (poSrcDS->GetGeoTransform(adfGT) == CE_None &&
    1059           2 :                     adfGT[2] == 0.0 && adfGT[4] == 0.0)
    1060             :                 {
    1061             :                     auto var = std::dynamic_pointer_cast<VRTMDArray>(
    1062           8 :                         poDstGroup->CreateMDArray(
    1063             :                             newDimName, {dstDim},
    1064           8 :                             GDALExtendedDataType::Create(GDT_Float64)));
    1065           2 :                     if (var)
    1066             :                     {
    1067             :                         const double dfStart =
    1068           2 :                             srcIndexVar->GetName() == "X"
    1069           2 :                                 ? adfGT[0] +
    1070           1 :                                       (range.m_nStartIdx + 0.5) * adfGT[1]
    1071           1 :                                 : adfGT[3] +
    1072           1 :                                       (range.m_nStartIdx + 0.5) * adfGT[5];
    1073             :                         const double dfIncr =
    1074           2 :                             (srcIndexVar->GetName() == "X" ? adfGT[1]
    1075           2 :                                                            : adfGT[5]) *
    1076           2 :                             range.m_nIncr;
    1077             :                         std::unique_ptr<VRTMDArraySourceRegularlySpaced>
    1078             :                             poSource(new VRTMDArraySourceRegularlySpaced(
    1079           2 :                                 dfStart, dfIncr));
    1080           2 :                         var->AddSource(std::move(poSource));
    1081             :                     }
    1082             :                 }
    1083             :             }
    1084             : 
    1085          46 :             CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
    1086          46 :             auto poDstIndexingVar(poDstGroup->OpenMDArray(newDimName));
    1087          23 :             if (poDstIndexingVar)
    1088          22 :                 dstDim->SetIndexingVariable(std::move(poDstIndexingVar));
    1089             :         }
    1090             :     }
    1091         202 :     if (outputType.GetClass() == GEDTC_NUMERIC &&
    1092         101 :         outputType.GetNumericDataType() == GDT_Unknown)
    1093             :     {
    1094         101 :         outputType = GDALExtendedDataType(tmpArray->GetDataType());
    1095             :     }
    1096             :     auto dstArray =
    1097         202 :         poDstGroup->CreateMDArray(dstArrayName, dstArrayDims, outputType);
    1098         202 :     auto dstArrayVRT = std::dynamic_pointer_cast<VRTMDArray>(dstArray);
    1099         101 :     if (!dstArrayVRT)
    1100           0 :         return false;
    1101             : 
    1102         101 :     GUInt64 nCurCost = 0;
    1103         101 :     dstArray->CopyFromAllExceptValues(srcArray.get(), false, nCurCost, 0,
    1104             :                                       nullptr, nullptr);
    1105         101 :     if (bResampled)
    1106           1 :         dstArray->SetSpatialRef(tmpArray->GetSpatialRef().get());
    1107             : 
    1108         101 :     if (idxSliceSpec >= 0)
    1109             :     {
    1110         126 :         std::set<size_t> oSetParentDimIdxNotInArray;
    1111         137 :         for (size_t i = 0; i < srcArrayDims.size(); ++i)
    1112             :         {
    1113          74 :             oSetParentDimIdxNotInArray.insert(i);
    1114             :         }
    1115          63 :         const auto &viewSpec(viewSpecs[idxSliceSpec]);
    1116         119 :         for (size_t i = 0; i < tmpArrayDims.size(); ++i)
    1117             :         {
    1118          56 :             auto iParentDim = viewSpec.m_mapDimIdxToParentDimIdx[i];
    1119          56 :             if (iParentDim != static_cast<size_t>(-1))
    1120             :             {
    1121          55 :                 oSetParentDimIdxNotInArray.erase(iParentDim);
    1122             :             }
    1123             :         }
    1124          82 :         for (const auto parentDimIdx : oSetParentDimIdxNotInArray)
    1125             :         {
    1126          19 :             const auto &srcDim(srcArrayDims[parentDimIdx]);
    1127             :             const auto nStartIdx =
    1128          19 :                 viewSpec.m_parentRanges[parentDimIdx].m_nStartIdx;
    1129          19 :             if (nStartIdx < static_cast<GUInt64>(INT_MAX))
    1130             :             {
    1131          19 :                 auto dstAttr = dstArray->CreateAttribute(
    1132          38 :                     "DIM_" + srcDim->GetName() + "_INDEX", {},
    1133          76 :                     GDALExtendedDataType::Create(GDT_Int32));
    1134          19 :                 dstAttr->Write(static_cast<int>(nStartIdx));
    1135             :             }
    1136             :             else
    1137             :             {
    1138           0 :                 auto dstAttr = dstArray->CreateAttribute(
    1139           0 :                     "DIM_" + srcDim->GetName() + "_INDEX", {},
    1140           0 :                     GDALExtendedDataType::CreateString());
    1141           0 :                 dstAttr->Write(CPLSPrintf(CPL_FRMT_GUIB,
    1142             :                                           static_cast<GUIntBig>(nStartIdx)));
    1143             :             }
    1144             : 
    1145          38 :             auto srcIndexVar(srcDim->GetIndexingVariable());
    1146          19 :             if (srcIndexVar && srcIndexVar->GetDimensionCount() == 1)
    1147             :             {
    1148          19 :                 const auto &dt(srcIndexVar->GetDataType());
    1149          38 :                 std::vector<GByte> abyTmp(dt.GetSize());
    1150          19 :                 const size_t nCount = 1;
    1151          38 :                 if (srcIndexVar->Read(&nStartIdx, &nCount, nullptr, nullptr, dt,
    1152          19 :                                       &abyTmp[0], nullptr, 0))
    1153             :                 {
    1154             :                     {
    1155          19 :                         auto dstAttr = dstArray->CreateAttribute(
    1156          57 :                             "DIM_" + srcDim->GetName() + "_VALUE", {}, dt);
    1157          19 :                         dstAttr->Write(abyTmp.data(), abyTmp.size());
    1158          19 :                         dt.FreeDynamicMemory(&abyTmp[0]);
    1159             :                     }
    1160             : 
    1161          19 :                     const auto &unit(srcIndexVar->GetUnit());
    1162          19 :                     if (!unit.empty())
    1163             :                     {
    1164           0 :                         auto dstAttr = dstArray->CreateAttribute(
    1165           0 :                             "DIM_" + srcDim->GetName() + "_UNIT", {},
    1166           0 :                             GDALExtendedDataType::CreateString());
    1167           0 :                         dstAttr->Write(unit.c_str());
    1168             :                     }
    1169             :                 }
    1170             :             }
    1171             :         }
    1172             :     }
    1173             : 
    1174         101 :     double dfStart = 0.0;
    1175         101 :     double dfIncrement = 0.0;
    1176         103 :     if (!bSrcArrayAccessibleThroughSrcGroup &&
    1177           2 :         tmpArray->IsRegularlySpaced(dfStart, dfIncrement))
    1178             :     {
    1179             :         auto poSource = std::make_unique<VRTMDArraySourceRegularlySpaced>(
    1180           2 :             dfStart, dfIncrement);
    1181           2 :         dstArrayVRT->AddSource(std::move(poSource));
    1182             :     }
    1183             :     else
    1184             :     {
    1185          99 :         const auto dimCount(tmpArray->GetDimensionCount());
    1186         198 :         std::vector<GUInt64> anSrcOffset(dimCount);
    1187         198 :         std::vector<GUInt64> anCount(dimCount);
    1188         201 :         for (size_t i = 0; i < dimCount; ++i)
    1189             :         {
    1190         102 :             anCount[i] = tmpArrayDims[i]->GetSize();
    1191             :         }
    1192         198 :         std::vector<GUInt64> anStep(dimCount, 1);
    1193         198 :         std::vector<GUInt64> anDstOffset(dimCount);
    1194             :         std::unique_ptr<VRTMDArraySourceFromArray> poSource(
    1195             :             new VRTMDArraySourceFromArray(
    1196          99 :                 dstArrayVRT.get(), false, false, poSrcDS->GetDescription(),
    1197         198 :                 band < 0 ? srcArray->GetFullName() : std::string(),
    1198         199 :                 band >= 1 ? CPLSPrintf("%d", band) : std::string(),
    1199          99 :                 std::move(anTransposedAxis),
    1200             :                 bResampled
    1201         296 :                     ? (viewExpr.empty()
    1202             :                            ? std::string("resample=true")
    1203          99 :                            : std::string("resample=true,").append(viewExpr))
    1204          98 :                     : std::move(viewExpr),
    1205          99 :                 std::move(anSrcOffset), std::move(anCount), std::move(anStep),
    1206         297 :                 std::move(anDstOffset)));
    1207          99 :         dstArrayVRT->AddSource(std::move(poSource));
    1208             :     }
    1209             : 
    1210         101 :     return true;
    1211             : }
    1212             : 
    1213             : /************************************************************************/
    1214             : /*                               GetGroup()                             */
    1215             : /************************************************************************/
    1216             : 
    1217             : static std::shared_ptr<GDALGroup>
    1218           5 : GetGroup(const std::shared_ptr<GDALGroup> &poRootGroup,
    1219             :          const std::string &fullName)
    1220             : {
    1221          10 :     auto poCurGroup = poRootGroup;
    1222          10 :     CPLStringList aosTokens(CSLTokenizeString2(fullName.c_str(), "/", 0));
    1223           8 :     for (int i = 0; i < aosTokens.size(); i++)
    1224             :     {
    1225           8 :         auto poCurGroupNew = poCurGroup->OpenGroup(aosTokens[i], nullptr);
    1226           4 :         if (!poCurGroupNew)
    1227             :         {
    1228           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot find group %s",
    1229             :                      aosTokens[i]);
    1230           1 :             return nullptr;
    1231             :         }
    1232           3 :         poCurGroup = std::move(poCurGroupNew);
    1233             :     }
    1234           4 :     return poCurGroup;
    1235             : }
    1236             : 
    1237             : /************************************************************************/
    1238             : /*                                CopyGroup()                           */
    1239             : /************************************************************************/
    1240             : 
    1241          10 : static bool CopyGroup(
    1242             :     DimensionRemapper &oDimRemapper,
    1243             :     const std::shared_ptr<GDALGroup> &poDstRootGroup,
    1244             :     std::shared_ptr<GDALGroup> &poDstGroup,
    1245             :     const std::shared_ptr<GDALGroup> &poSrcRootGroup,
    1246             :     const std::shared_ptr<GDALGroup> &poSrcGroup, GDALDataset *poSrcDS,
    1247             :     std::map<std::string, std::shared_ptr<GDALDimension>> &mapSrcToDstDims,
    1248             :     std::map<std::string, std::shared_ptr<GDALDimension>> &mapDstDimFullNames,
    1249             :     const GDALMultiDimTranslateOptions *psOptions, bool bRecursive)
    1250             : {
    1251          20 :     const auto srcDims = poSrcGroup->GetDimensions();
    1252          20 :     std::map<std::string, std::string> mapSrcVariableNameToIndexedDimName;
    1253          14 :     for (const auto &dim : srcDims)
    1254             :     {
    1255           7 :         const auto poDimDesc = GetDimensionDesc(oDimRemapper, psOptions, dim);
    1256           7 :         if (poDimDesc == nullptr)
    1257           3 :             return false;
    1258           4 :         if (poDimDesc->bSlice)
    1259           1 :             continue;
    1260             :         auto dstDim =
    1261           3 :             poDstGroup->CreateDimension(dim->GetName(), dim->GetType(),
    1262           3 :                                         dim->GetDirection(), poDimDesc->nSize);
    1263           3 :         if (!dstDim)
    1264           0 :             return false;
    1265           3 :         mapSrcToDstDims[dim->GetFullName()] = dstDim;
    1266           3 :         mapDstDimFullNames[dstDim->GetFullName()] = dstDim;
    1267           6 :         auto poIndexingVarSrc(dim->GetIndexingVariable());
    1268           3 :         if (poIndexingVarSrc)
    1269             :         {
    1270           3 :             mapSrcVariableNameToIndexedDimName[poIndexingVarSrc->GetName()] =
    1271           6 :                 dim->GetFullName();
    1272             :         }
    1273             :     }
    1274             : 
    1275           7 :     if (!(poSrcGroup == poSrcRootGroup && psOptions->aosGroup.empty()))
    1276             :     {
    1277           6 :         auto attrs = poSrcGroup->GetAttributes();
    1278           8 :         for (const auto &attr : attrs)
    1279             :         {
    1280           2 :             auto dstAttr = poDstGroup->CreateAttribute(
    1281           2 :                 attr->GetName(), attr->GetDimensionsSize(),
    1282           4 :                 attr->GetDataType());
    1283           2 :             if (!dstAttr)
    1284             :             {
    1285           0 :                 if (!psOptions->bStrict)
    1286           0 :                     continue;
    1287           0 :                 return false;
    1288             :             }
    1289           2 :             auto raw(attr->ReadAsRaw());
    1290           2 :             if (!dstAttr->Write(raw.data(), raw.size()) && !psOptions->bStrict)
    1291           0 :                 return false;
    1292             :         }
    1293             :     }
    1294             : 
    1295             :     auto arrayNames =
    1296          14 :         poSrcGroup->GetMDArrayNames(psOptions->aosArrayOptions.List());
    1297          18 :     for (const auto &name : arrayNames)
    1298             :     {
    1299          11 :         if (!TranslateArray(oDimRemapper, nullptr, name, poSrcRootGroup,
    1300             :                             poSrcGroup, poDstRootGroup, poDstGroup, poSrcDS,
    1301             :                             mapSrcToDstDims, mapDstDimFullNames, psOptions))
    1302             :         {
    1303           0 :             return false;
    1304             :         }
    1305             : 
    1306             :         // If this array is the indexing variable of a dimension, link them
    1307             :         // together.
    1308          22 :         auto srcArray = poSrcGroup->OpenMDArray(name);
    1309          11 :         CPLAssert(srcArray);
    1310          22 :         auto dstArray = poDstGroup->OpenMDArray(name);
    1311          11 :         CPLAssert(dstArray);
    1312             :         auto oIterDimName =
    1313          11 :             mapSrcVariableNameToIndexedDimName.find(srcArray->GetName());
    1314          11 :         if (oIterDimName != mapSrcVariableNameToIndexedDimName.end())
    1315             :         {
    1316             :             auto oCorrespondingDimIter =
    1317           3 :                 mapSrcToDstDims.find(oIterDimName->second);
    1318           3 :             if (oCorrespondingDimIter != mapSrcToDstDims.end())
    1319             :             {
    1320           3 :                 CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
    1321           6 :                 oCorrespondingDimIter->second->SetIndexingVariable(
    1322           3 :                     std::move(dstArray));
    1323             :             }
    1324             :         }
    1325             :     }
    1326             : 
    1327           7 :     if (bRecursive)
    1328             :     {
    1329           7 :         auto groupNames = poSrcGroup->GetGroupNames();
    1330           9 :         for (const auto &name : groupNames)
    1331             :         {
    1332           2 :             auto srcSubGroup = poSrcGroup->OpenGroup(name);
    1333           2 :             if (!srcSubGroup)
    1334             :             {
    1335           0 :                 return false;
    1336             :             }
    1337           2 :             auto dstSubGroup = poDstGroup->CreateGroup(name);
    1338           2 :             if (!dstSubGroup)
    1339             :             {
    1340           0 :                 return false;
    1341             :             }
    1342           2 :             if (!CopyGroup(oDimRemapper, poDstRootGroup, dstSubGroup,
    1343             :                            poSrcRootGroup, srcSubGroup, poSrcDS,
    1344             :                            mapSrcToDstDims, mapDstDimFullNames, psOptions,
    1345             :                            true))
    1346             :             {
    1347           0 :                 return false;
    1348             :             }
    1349             :         }
    1350             :     }
    1351           7 :     return true;
    1352             : }
    1353             : 
    1354             : /************************************************************************/
    1355             : /*                           ParseGroupSpec()                           */
    1356             : /************************************************************************/
    1357             : 
    1358             : // foo
    1359             : // name=foo,dstname=bar,recursive=no
    1360           6 : static bool ParseGroupSpec(const std::string &groupSpec, std::string &srcName,
    1361             :                            std::string &dstName, bool &bRecursive)
    1362             : {
    1363           6 :     bRecursive = true;
    1364           6 :     if (!STARTS_WITH(groupSpec.c_str(), "name="))
    1365             :     {
    1366           4 :         srcName = groupSpec;
    1367           4 :         return true;
    1368             :     }
    1369             : 
    1370           4 :     CPLStringList aosTokens(CSLTokenizeString2(groupSpec.c_str(), ",", 0));
    1371           5 :     for (int i = 0; i < aosTokens.size(); i++)
    1372             :     {
    1373           4 :         const std::string token(aosTokens[i]);
    1374           4 :         if (STARTS_WITH(token.c_str(), "name="))
    1375             :         {
    1376           2 :             srcName = token.substr(strlen("name="));
    1377             :         }
    1378           2 :         else if (STARTS_WITH(token.c_str(), "dstname="))
    1379             :         {
    1380           1 :             dstName = token.substr(strlen("dstname="));
    1381             :         }
    1382           1 :         else if (token == "recursive=no")
    1383             :         {
    1384           0 :             bRecursive = false;
    1385             :         }
    1386             :         else
    1387             :         {
    1388           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1389             :                      "Unexpected group specification part: %s", token.c_str());
    1390           1 :             return false;
    1391             :         }
    1392             :     }
    1393           1 :     return true;
    1394             : }
    1395             : 
    1396             : /************************************************************************/
    1397             : /*                           TranslateInternal()                        */
    1398             : /************************************************************************/
    1399             : 
    1400          96 : static bool TranslateInternal(std::shared_ptr<GDALGroup> &poDstRootGroup,
    1401             :                               GDALDataset *poSrcDS,
    1402             :                               const GDALMultiDimTranslateOptions *psOptions)
    1403             : {
    1404             : 
    1405         192 :     auto poSrcRootGroup = poSrcDS->GetRootGroup();
    1406          96 :     if (poSrcRootGroup)
    1407             :     {
    1408          95 :         if (psOptions->aosGroup.empty())
    1409             :         {
    1410         180 :             auto attrs = poSrcRootGroup->GetAttributes();
    1411          94 :             for (const auto &attr : attrs)
    1412             :             {
    1413           4 :                 if (attr->GetName() == "Conventions")
    1414           1 :                     continue;
    1415           3 :                 auto dstAttr = poDstRootGroup->CreateAttribute(
    1416           3 :                     attr->GetName(), attr->GetDimensionsSize(),
    1417           9 :                     attr->GetDataType());
    1418           3 :                 if (dstAttr)
    1419             :                 {
    1420           6 :                     auto raw(attr->ReadAsRaw());
    1421           3 :                     dstAttr->Write(raw.data(), raw.size());
    1422             :                 }
    1423             :             }
    1424             :         }
    1425             :     }
    1426             : 
    1427         192 :     DimensionRemapper oDimRemapper;
    1428         192 :     std::map<std::string, std::shared_ptr<GDALDimension>> mapSrcToDstDims;
    1429         192 :     std::map<std::string, std::shared_ptr<GDALDimension>> mapDstDimFullNames;
    1430          96 :     if (!psOptions->aosGroup.empty())
    1431             :     {
    1432           5 :         if (poSrcRootGroup == nullptr)
    1433             :         {
    1434           0 :             CPLError(
    1435             :                 CE_Failure, CPLE_AppDefined,
    1436             :                 "No multidimensional source dataset: -group cannot be used");
    1437           0 :             return false;
    1438             :         }
    1439           5 :         if (psOptions->aosGroup.size() == 1)
    1440             :         {
    1441           8 :             std::string srcName;
    1442           8 :             std::string dstName;
    1443             :             bool bRecursive;
    1444           4 :             if (!ParseGroupSpec(psOptions->aosGroup[0], srcName, dstName,
    1445             :                                 bRecursive))
    1446           1 :                 return false;
    1447           6 :             auto poSrcGroup = GetGroup(poSrcRootGroup, srcName);
    1448           3 :             if (!poSrcGroup)
    1449           1 :                 return false;
    1450           2 :             return CopyGroup(oDimRemapper, poDstRootGroup, poDstRootGroup,
    1451             :                              poSrcRootGroup, poSrcGroup, poSrcDS,
    1452             :                              mapSrcToDstDims, mapDstDimFullNames, psOptions,
    1453           2 :                              bRecursive);
    1454             :         }
    1455             :         else
    1456             :         {
    1457           3 :             for (const auto &osGroupSpec : psOptions->aosGroup)
    1458             :             {
    1459           2 :                 std::string srcName;
    1460           2 :                 std::string dstName;
    1461             :                 bool bRecursive;
    1462           2 :                 if (!ParseGroupSpec(osGroupSpec, srcName, dstName, bRecursive))
    1463           0 :                     return false;
    1464           2 :                 auto poSrcGroup = GetGroup(poSrcRootGroup, srcName);
    1465           2 :                 if (!poSrcGroup)
    1466           0 :                     return false;
    1467           2 :                 if (dstName.empty())
    1468           1 :                     dstName = poSrcGroup->GetName();
    1469           2 :                 auto dstSubGroup = poDstRootGroup->CreateGroup(dstName);
    1470           4 :                 if (!dstSubGroup ||
    1471           2 :                     !CopyGroup(oDimRemapper, poDstRootGroup, dstSubGroup,
    1472             :                                poSrcRootGroup, poSrcGroup, poSrcDS,
    1473             :                                mapSrcToDstDims, mapDstDimFullNames, psOptions,
    1474             :                                bRecursive))
    1475             :                 {
    1476           0 :                     return false;
    1477             :                 }
    1478             :             }
    1479             :         }
    1480             :     }
    1481          91 :     else if (!psOptions->aosArraySpec.empty())
    1482             :     {
    1483         156 :         for (const auto &arraySpec : psOptions->aosArraySpec)
    1484             :         {
    1485          87 :             if (!TranslateArray(oDimRemapper, nullptr, arraySpec,
    1486             :                                 poSrcRootGroup, poSrcRootGroup, poDstRootGroup,
    1487             :                                 poDstRootGroup, poSrcDS, mapSrcToDstDims,
    1488             :                                 mapDstDimFullNames, psOptions))
    1489             :             {
    1490          18 :                 return false;
    1491             :             }
    1492             :         }
    1493             :     }
    1494             :     else
    1495             :     {
    1496           4 :         if (poSrcRootGroup == nullptr)
    1497             :         {
    1498           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1499             :                      "No multidimensional source dataset");
    1500           0 :             return false;
    1501             :         }
    1502           4 :         return CopyGroup(oDimRemapper, poDstRootGroup, poDstRootGroup,
    1503             :                          poSrcRootGroup, poSrcRootGroup, poSrcDS,
    1504           4 :                          mapSrcToDstDims, mapDstDimFullNames, psOptions, true);
    1505             :     }
    1506             : 
    1507          70 :     return true;
    1508             : }
    1509             : 
    1510             : /************************************************************************/
    1511             : /*                      CopyToNonMultiDimensionalDriver()               */
    1512             : /************************************************************************/
    1513             : 
    1514             : static GDALDatasetH
    1515           3 : CopyToNonMultiDimensionalDriver(GDALDriver *poDriver, const char *pszDest,
    1516             :                                 const std::shared_ptr<GDALGroup> &poRG,
    1517             :                                 const GDALMultiDimTranslateOptions *psOptions)
    1518             : {
    1519           3 :     std::shared_ptr<GDALMDArray> srcArray;
    1520           3 :     if (psOptions && !psOptions->aosArraySpec.empty())
    1521             :     {
    1522           2 :         if (psOptions->aosArraySpec.size() != 1)
    1523             :         {
    1524           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    1525             :                      "For output to a non-multidimensional driver, only "
    1526             :                      "one array should be specified");
    1527           0 :             return nullptr;
    1528             :         }
    1529           4 :         std::string srcArrayName;
    1530           4 :         std::string dstArrayName;
    1531           2 :         int band = -1;
    1532           4 :         std::vector<int> anTransposedAxis;
    1533           4 :         std::string viewExpr;
    1534             :         GDALExtendedDataType outputType(
    1535           2 :             GDALExtendedDataType::Create(GDT_Unknown));
    1536           2 :         bool bResampled = false;
    1537           2 :         ParseArraySpec(psOptions->aosArraySpec[0], srcArrayName, dstArrayName,
    1538             :                        band, anTransposedAxis, viewExpr, outputType,
    1539             :                        bResampled);
    1540           2 :         srcArray = poRG->OpenMDArray(dstArrayName);
    1541             :     }
    1542             :     else
    1543             :     {
    1544           1 :         auto srcArrayNames = poRG->GetMDArrayNames(
    1545           1 :             psOptions ? psOptions->aosArrayOptions.List() : nullptr);
    1546           4 :         for (const auto &srcArrayName : srcArrayNames)
    1547             :         {
    1548           4 :             auto tmpArray = poRG->OpenMDArray(srcArrayName);
    1549           4 :             if (tmpArray)
    1550             :             {
    1551           4 :                 const auto &dims(tmpArray->GetDimensions());
    1552          10 :                 if (!(dims.size() == 1 && dims[0]->GetIndexingVariable() &&
    1553           6 :                       dims[0]->GetIndexingVariable()->GetName() ==
    1554             :                           srcArrayName))
    1555             :                 {
    1556           2 :                     if (srcArray)
    1557             :                     {
    1558           1 :                         CPLError(CE_Failure, CPLE_AppDefined,
    1559             :                                  "Several arrays exist. Select one for "
    1560             :                                  "output to non-multidimensional driver");
    1561           1 :                         return nullptr;
    1562             :                     }
    1563           1 :                     srcArray = std::move(tmpArray);
    1564             :                 }
    1565             :             }
    1566             :         }
    1567             :     }
    1568           2 :     if (!srcArray)
    1569             :     {
    1570           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find source array");
    1571           0 :         return nullptr;
    1572             :     }
    1573           2 :     size_t iXDim = static_cast<size_t>(-1);
    1574           2 :     size_t iYDim = static_cast<size_t>(-1);
    1575           2 :     const auto &dims(srcArray->GetDimensions());
    1576           5 :     for (size_t i = 0; i < dims.size(); ++i)
    1577             :     {
    1578           3 :         if (dims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X)
    1579             :         {
    1580           1 :             iXDim = i;
    1581             :         }
    1582           2 :         else if (dims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y)
    1583             :         {
    1584           2 :             iYDim = i;
    1585             :         }
    1586             :     }
    1587           2 :     if (dims.size() == 1)
    1588             :     {
    1589           1 :         iXDim = 0;
    1590             :     }
    1591           1 :     else if (dims.size() >= 2 && (iXDim == static_cast<size_t>(-1) ||
    1592             :                                   iYDim == static_cast<size_t>(-1)))
    1593             :     {
    1594           0 :         iXDim = dims.size() - 1;
    1595           0 :         iYDim = dims.size() - 2;
    1596             :     }
    1597             :     std::unique_ptr<GDALDataset> poTmpSrcDS(
    1598           4 :         srcArray->AsClassicDataset(iXDim, iYDim));
    1599           2 :     if (!poTmpSrcDS)
    1600           0 :         return nullptr;
    1601           4 :     return GDALDataset::ToHandle(poDriver->CreateCopy(
    1602             :         pszDest, poTmpSrcDS.get(), false,
    1603           2 :         psOptions ? const_cast<char **>(psOptions->aosCreateOptions.List())
    1604             :                   : nullptr,
    1605             :         psOptions ? psOptions->pfnProgress : nullptr,
    1606           2 :         psOptions ? psOptions->pProgressData : nullptr));
    1607             : }
    1608             : 
    1609             : /************************************************************************/
    1610             : /*                        GDALMultiDimTranslate()                       */
    1611             : /************************************************************************/
    1612             : 
    1613             : /* clang-format off */
    1614             : /**
    1615             :  * Converts raster data between different formats.
    1616             :  *
    1617             :  * This is the equivalent of the
    1618             :  * <a href="/programs/gdalmdimtranslate.html">gdalmdimtranslate</a> utility.
    1619             :  *
    1620             :  * GDALMultiDimTranslateOptions* must be allocated and freed with
    1621             :  * GDALMultiDimTranslateOptionsNew() and GDALMultiDimTranslateOptionsFree()
    1622             :  * respectively. pszDest and hDstDS cannot be used at the same time.
    1623             :  *
    1624             :  * @param pszDest the destination dataset path or NULL.
    1625             :  * @param hDstDS the destination dataset or NULL.
    1626             :  * @param nSrcCount the number of input datasets.
    1627             :  * @param pahSrcDS the list of input datasets.
    1628             :  * @param psOptions the options struct returned by
    1629             :  * GDALMultiDimTranslateOptionsNew() or NULL.
    1630             :  * @param pbUsageError pointer to a integer output variable to store if any
    1631             :  * usage error has occurred or NULL.
    1632             :  * @return the output dataset (new dataset that must be closed using
    1633             :  * GDALClose(), or hDstDS is not NULL) or NULL in case of error.
    1634             :  *
    1635             :  * @since GDAL 3.1
    1636             :  */
    1637             : /* clang-format on */
    1638             : 
    1639             : GDALDatasetH
    1640         105 : GDALMultiDimTranslate(const char *pszDest, GDALDatasetH hDstDS, int nSrcCount,
    1641             :                       GDALDatasetH *pahSrcDS,
    1642             :                       const GDALMultiDimTranslateOptions *psOptions,
    1643             :                       int *pbUsageError)
    1644             : {
    1645         105 :     if (pbUsageError)
    1646         105 :         *pbUsageError = false;
    1647         105 :     if (nSrcCount != 1 || pahSrcDS[0] == nullptr)
    1648             :     {
    1649           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1650             :                  "Only one source dataset is supported");
    1651           0 :         if (pbUsageError)
    1652           0 :             *pbUsageError = true;
    1653           0 :         return nullptr;
    1654             :     }
    1655             : 
    1656         105 :     if (hDstDS)
    1657             :     {
    1658           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1659             :                  "Update of existing file not supported yet");
    1660           0 :         GDALClose(hDstDS);
    1661           0 :         return nullptr;
    1662             :     }
    1663             : 
    1664         315 :     CPLString osFormat(psOptions ? psOptions->osFormat : "");
    1665         105 :     if (pszDest == nullptr /* && hDstDS == nullptr */)
    1666             :     {
    1667           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1668             :                  "Both pszDest and hDstDS are NULL.");
    1669           0 :         if (pbUsageError)
    1670           0 :             *pbUsageError = true;
    1671           0 :         return nullptr;
    1672             :     }
    1673             : 
    1674         105 :     GDALDriver *poDriver = nullptr;
    1675             : 
    1676             : #ifdef this_is_dead_code_for_now
    1677             :     const bool bCloseOutDSOnError = hDstDS == nullptr;
    1678             :     if (pszDest == nullptr)
    1679             :         pszDest = GDALGetDescription(hDstDS);
    1680             : #endif
    1681             : 
    1682             : #ifdef this_is_dead_code_for_now
    1683             :     if (hDstDS == nullptr)
    1684             : #endif
    1685             :     {
    1686         105 :         if (osFormat.empty())
    1687             :         {
    1688          98 :             if (EQUAL(CPLGetExtension(pszDest), "nc"))
    1689           1 :                 osFormat = "netCDF";
    1690             :             else
    1691          97 :                 osFormat = GetOutputDriverForRaster(pszDest);
    1692          98 :             if (osFormat.empty())
    1693             :             {
    1694           0 :                 return nullptr;
    1695             :             }
    1696             :         }
    1697         105 :         poDriver = GDALDriver::FromHandle(GDALGetDriverByName(osFormat));
    1698         105 :         char **papszDriverMD = poDriver ? poDriver->GetMetadata() : nullptr;
    1699         210 :         if (poDriver == nullptr ||
    1700         105 :             (!CPLTestBool(CSLFetchNameValueDef(papszDriverMD, GDAL_DCAP_RASTER,
    1701           0 :                                                "FALSE")) &&
    1702           0 :              !CPLTestBool(CSLFetchNameValueDef(
    1703         210 :                  papszDriverMD, GDAL_DCAP_MULTIDIM_RASTER, "FALSE"))) ||
    1704         105 :             (!CPLTestBool(CSLFetchNameValueDef(papszDriverMD, GDAL_DCAP_CREATE,
    1705           0 :                                                "FALSE")) &&
    1706           0 :              !CPLTestBool(CSLFetchNameValueDef(
    1707           0 :                  papszDriverMD, GDAL_DCAP_CREATECOPY, "FALSE")) &&
    1708           0 :              !CPLTestBool(CSLFetchNameValueDef(
    1709           0 :                  papszDriverMD, GDAL_DCAP_CREATE_MULTIDIMENSIONAL, "FALSE")) &&
    1710           0 :              !CPLTestBool(CSLFetchNameValueDef(
    1711             :                  papszDriverMD, GDAL_DCAP_CREATECOPY_MULTIDIMENSIONAL,
    1712             :                  "FALSE"))))
    1713             :         {
    1714           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    1715             :                      "Output driver `%s' not recognised or does not support "
    1716             :                      "output file creation.",
    1717             :                      osFormat.c_str());
    1718           0 :             return nullptr;
    1719             :         }
    1720             :     }
    1721             : 
    1722         105 :     GDALDataset *poSrcDS = GDALDataset::FromHandle(pahSrcDS[0]);
    1723             : 
    1724         105 :     std::unique_ptr<GDALDataset> poTmpDS;
    1725         105 :     GDALDataset *poTmpSrcDS = poSrcDS;
    1726         210 :     if (psOptions &&
    1727         105 :         (!psOptions->aosArraySpec.empty() || !psOptions->aosGroup.empty() ||
    1728          13 :          !psOptions->aosSubset.empty() || !psOptions->aosScaleFactor.empty() ||
    1729           9 :          !psOptions->aosArrayOptions.empty()))
    1730             :     {
    1731          96 :         poTmpDS.reset(VRTDataset::CreateMultiDimensional("", nullptr, nullptr));
    1732          96 :         CPLAssert(poTmpDS);
    1733          96 :         poTmpSrcDS = poTmpDS.get();
    1734             : 
    1735          96 :         auto poDstRootGroup = poTmpDS->GetRootGroup();
    1736          96 :         CPLAssert(poDstRootGroup);
    1737             : 
    1738          96 :         if (!TranslateInternal(poDstRootGroup, poSrcDS, psOptions))
    1739             :         {
    1740             : #ifdef this_is_dead_code_for_now
    1741             :             if (bCloseOutDSOnError)
    1742             : #endif
    1743             :             {
    1744          23 :                 GDALClose(hDstDS);
    1745          23 :                 hDstDS = nullptr;
    1746             :             }
    1747          23 :             return nullptr;
    1748             :         }
    1749             :     }
    1750             : 
    1751          82 :     auto poRG(poTmpSrcDS->GetRootGroup());
    1752         163 :     if (poRG &&
    1753          81 :         poDriver->GetMetadataItem(GDAL_DCAP_CREATE_MULTIDIMENSIONAL) ==
    1754         163 :             nullptr &&
    1755           3 :         poDriver->GetMetadataItem(GDAL_DCAP_CREATECOPY_MULTIDIMENSIONAL) ==
    1756             :             nullptr)
    1757             :     {
    1758             : #ifdef this_is_dead_code_for_now
    1759             :         if (hDstDS)
    1760             :         {
    1761             :             CPLError(CE_Failure, CPLE_NotSupported,
    1762             :                      "Appending to non-multidimensional driver not supported.");
    1763             :             GDALClose(hDstDS);
    1764             :             hDstDS = nullptr;
    1765             :             return nullptr;
    1766             :         }
    1767             : #endif
    1768             :         hDstDS =
    1769           3 :             CopyToNonMultiDimensionalDriver(poDriver, pszDest, poRG, psOptions);
    1770             :     }
    1771             :     else
    1772             :     {
    1773         158 :         hDstDS = GDALDataset::ToHandle(poDriver->CreateCopy(
    1774             :             pszDest, poTmpSrcDS, false,
    1775          79 :             psOptions ? const_cast<char **>(psOptions->aosCreateOptions.List())
    1776             :                       : nullptr,
    1777             :             psOptions ? psOptions->pfnProgress : nullptr,
    1778             :             psOptions ? psOptions->pProgressData : nullptr));
    1779             :     }
    1780             : 
    1781          82 :     return hDstDS;
    1782             : }
    1783             : 
    1784             : /************************************************************************/
    1785             : /*                     GDALMultiDimTranslateOptionsNew()                */
    1786             : /************************************************************************/
    1787             : 
    1788             : /**
    1789             :  * Allocates a GDALMultiDimTranslateOptions struct.
    1790             :  *
    1791             :  * @param papszArgv NULL terminated list of options (potentially including
    1792             :  * filename and open options too), or NULL. The accepted options are the ones of
    1793             :  * the <a href="/programs/gdalmdimtranslate.html">gdalmdimtranslate</a> utility.
    1794             :  * @param psOptionsForBinary should be nullptr, unless called from
    1795             :  * gdalmdimtranslate_bin.cpp
    1796             :  * @return pointer to the allocated GDALMultiDimTranslateOptions struct. Must be
    1797             :  * freed with GDALMultiDimTranslateOptionsFree().
    1798             :  *
    1799             :  * @since GDAL 3.1
    1800             :  */
    1801             : 
    1802         106 : GDALMultiDimTranslateOptions *GDALMultiDimTranslateOptionsNew(
    1803             :     char **papszArgv, GDALMultiDimTranslateOptionsForBinary *psOptionsForBinary)
    1804             : {
    1805         106 :     GDALMultiDimTranslateOptions *psOptions = new GDALMultiDimTranslateOptions;
    1806             : 
    1807             :     /* -------------------------------------------------------------------- */
    1808             :     /*      Handle command line arguments.                                  */
    1809             :     /* -------------------------------------------------------------------- */
    1810         106 :     const int argc = CSLCount(papszArgv);
    1811         296 :     for (int i = 0; papszArgv != nullptr && i < argc; i++)
    1812             :     {
    1813         190 :         if (i < argc - 1 &&
    1814         187 :             (EQUAL(papszArgv[i], "-of") || EQUAL(papszArgv[i], "-f")))
    1815             :         {
    1816           7 :             ++i;
    1817           7 :             psOptions->osFormat = papszArgv[i];
    1818             :         }
    1819             : 
    1820         183 :         else if (EQUAL(papszArgv[i], "-q") || EQUAL(papszArgv[i], "-quiet"))
    1821             :         {
    1822           0 :             if (psOptionsForBinary)
    1823           0 :                 psOptionsForBinary->bQuiet = TRUE;
    1824             :         }
    1825             : 
    1826         183 :         else if (EQUAL(papszArgv[i], "-strict"))
    1827             :         {
    1828           0 :             psOptions->bStrict = true;
    1829             :         }
    1830             : 
    1831         183 :         else if (i < argc - 1 && EQUAL(papszArgv[i], "-array"))
    1832             :         {
    1833          87 :             ++i;
    1834          87 :             psOptions->aosArraySpec.push_back(papszArgv[i]);
    1835             :         }
    1836          96 :         else if (i < argc - 1 && EQUAL(papszArgv[i], "-arrayoption"))
    1837             :         {
    1838           0 :             ++i;
    1839           0 :             psOptions->aosArrayOptions.AddString(papszArgv[i]);
    1840             :         }
    1841          96 :         else if (i < argc - 1 && EQUAL(papszArgv[i], "-group"))
    1842             :         {
    1843           6 :             ++i;
    1844           6 :             psOptions->aosGroup.push_back(papszArgv[i]);
    1845             :         }
    1846             : 
    1847          90 :         else if (i < argc - 1 && EQUAL(papszArgv[i], "-subset"))
    1848             :         {
    1849          81 :             ++i;
    1850          81 :             psOptions->aosSubset.push_back(papszArgv[i]);
    1851             :         }
    1852             : 
    1853           9 :         else if (i < argc - 1 && EQUAL(papszArgv[i], "-scaleaxes"))
    1854             :         {
    1855           1 :             ++i;
    1856             :             CPLStringList aosScaleFactors(
    1857           2 :                 CSLTokenizeString2(papszArgv[i], ",", 0));
    1858           2 :             for (int j = 0; j < aosScaleFactors.size(); j++)
    1859             :             {
    1860           1 :                 psOptions->aosScaleFactor.push_back(aosScaleFactors[j]);
    1861           1 :             }
    1862             :         }
    1863             : 
    1864           8 :         else if (i < argc - 1 && EQUAL(papszArgv[i], "-co"))
    1865             :         {
    1866           0 :             ++i;
    1867           0 :             psOptions->aosCreateOptions.AddString(papszArgv[i]);
    1868             :         }
    1869             : 
    1870           8 :         else if (EQUAL(papszArgv[i], "-oo") && i + 1 < argc)
    1871             :         {
    1872           0 :             ++i;
    1873           0 :             if (psOptionsForBinary)
    1874             :             {
    1875           0 :                 psOptionsForBinary->aosOpenOptions.AddString(papszArgv[i]);
    1876             :             }
    1877             :         }
    1878             : 
    1879           8 :         else if (i + 1 < argc && EQUAL(papszArgv[i], "-if"))
    1880             :         {
    1881           2 :             i++;
    1882           2 :             if (psOptionsForBinary)
    1883             :             {
    1884           2 :                 if (GDALGetDriverByName(papszArgv[i]) == nullptr)
    1885             :                 {
    1886           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1887           1 :                              "%s is not a recognized driver", papszArgv[i]);
    1888             :                 }
    1889             :                 psOptionsForBinary->aosAllowInputDrivers.AddString(
    1890           2 :                     papszArgv[i]);
    1891             :             }
    1892             :         }
    1893             : 
    1894           6 :         else if (papszArgv[i][0] == '-')
    1895             :         {
    1896           0 :             CPLError(CE_Failure, CPLE_NotSupported, "Unknown option name '%s'",
    1897           0 :                      papszArgv[i]);
    1898           0 :             GDALMultiDimTranslateOptionsFree(psOptions);
    1899           0 :             return nullptr;
    1900             :         }
    1901           6 :         else if (psOptionsForBinary && psOptionsForBinary->osSource.empty())
    1902             :         {
    1903           3 :             psOptionsForBinary->osSource = papszArgv[i];
    1904             :         }
    1905           3 :         else if (psOptionsForBinary && psOptionsForBinary->osDest.empty())
    1906             :         {
    1907           3 :             psOptionsForBinary->osDest = papszArgv[i];
    1908             :         }
    1909             :         else
    1910             :         {
    1911           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    1912           0 :                      "Too many command options '%s'", papszArgv[i]);
    1913           0 :             GDALMultiDimTranslateOptionsFree(psOptions);
    1914           0 :             return nullptr;
    1915             :         }
    1916             :     }
    1917             : 
    1918         106 :     if (!psOptions->aosArraySpec.empty() && !psOptions->aosGroup.empty())
    1919             :     {
    1920           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1921             :                  "-array and -group are mutually exclusive");
    1922           0 :         GDALMultiDimTranslateOptionsFree(psOptions);
    1923           0 :         return nullptr;
    1924             :     }
    1925             : 
    1926         106 :     if (psOptionsForBinary)
    1927             :     {
    1928           3 :         psOptionsForBinary->bUpdate = psOptions->bUpdate;
    1929           3 :         if (!psOptions->osFormat.empty())
    1930           0 :             psOptionsForBinary->osFormat = psOptions->osFormat;
    1931             :     }
    1932             : 
    1933         106 :     return psOptions;
    1934             : }
    1935             : 
    1936             : /************************************************************************/
    1937             : /*                     GDALMultiDimTranslateOptionsFree()               */
    1938             : /************************************************************************/
    1939             : 
    1940             : /**
    1941             :  * Frees the GDALMultiDimTranslateOptions struct.
    1942             :  *
    1943             :  * @param psOptions the options struct for GDALMultiDimTranslate().
    1944             :  *
    1945             :  * @since GDAL 3.1
    1946             :  */
    1947             : 
    1948         105 : void GDALMultiDimTranslateOptionsFree(GDALMultiDimTranslateOptions *psOptions)
    1949             : {
    1950         105 :     delete psOptions;
    1951         105 : }
    1952             : 
    1953             : /************************************************************************/
    1954             : /*               GDALMultiDimTranslateOptionsSetProgress()              */
    1955             : /************************************************************************/
    1956             : 
    1957             : /**
    1958             :  * Set a progress function.
    1959             :  *
    1960             :  * @param psOptions the options struct for GDALMultiDimTranslate().
    1961             :  * @param pfnProgress the progress callback.
    1962             :  * @param pProgressData the user data for the progress callback.
    1963             :  *
    1964             :  * @since GDAL 3.1
    1965             :  */
    1966             : 
    1967           3 : void GDALMultiDimTranslateOptionsSetProgress(
    1968             :     GDALMultiDimTranslateOptions *psOptions, GDALProgressFunc pfnProgress,
    1969             :     void *pProgressData)
    1970             : {
    1971           3 :     psOptions->pfnProgress = pfnProgress;
    1972           3 :     psOptions->pProgressData = pProgressData;
    1973           3 : }

Generated by: LCOV version 1.14