LCOV - code coverage report
Current view: top level - apps - gdalmdimtranslate_lib.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 854 1021 83.6 %
Date: 2025-10-01 17:07:58 Functions: 17 18 94.4 %

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

Generated by: LCOV version 1.14