LCOV - code coverage report
Current view: top level - apps - gdalmdimtranslate_lib.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 871 1038 83.9 %
Date: 2026-02-21 16:21:44 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         124 : GDALMultiDimTranslateAppOptionsGetParser(
      51             :     GDALMultiDimTranslateOptions *psOptions,
      52             :     GDALMultiDimTranslateOptionsForBinary *psOptionsForBinary)
      53             : {
      54             :     auto argParser = std::make_unique<GDALArgumentParser>(
      55         124 :         "gdalmdimtranslate", /* bForBinary=*/psOptionsForBinary != nullptr);
      56             : 
      57         124 :     argParser->add_description(
      58             :         _("Converts multidimensional data between different formats, and "
      59         124 :           "performs subsetting."));
      60             : 
      61         124 :     argParser->add_epilog(
      62             :         _("For more details, consult "
      63         124 :           "https://gdal.org/programs/gdalmdimtranslate.html"));
      64             : 
      65         124 :     if (psOptionsForBinary)
      66             :     {
      67             :         argParser->add_input_format_argument(
      68           3 :             &psOptionsForBinary->aosAllowInputDrivers);
      69             :     }
      70             : 
      71         124 :     argParser->add_output_format_argument(psOptions->osFormat);
      72             : 
      73         124 :     argParser->add_creation_options_argument(psOptions->aosCreateOptions);
      74             : 
      75         124 :     auto &group = argParser->add_mutually_exclusive_group();
      76         124 :     group.add_argument("-array")
      77         248 :         .metavar("<array_spec>")
      78         124 :         .append()
      79         124 :         .store_into(psOptions->aosArraySpec)
      80             :         .help(_(
      81         124 :             "Select a single array instead of converting the whole dataset."));
      82             : 
      83         124 :     argParser->add_argument("-arrayoption")
      84         248 :         .metavar("<NAME>=<VALUE>")
      85         124 :         .append()
      86           1 :         .action([psOptions](const std::string &s)
      87         125 :                 { psOptions->aosArrayOptions.AddString(s.c_str()); })
      88             :         .help(_("Option passed to GDALGroup::GetMDArrayNames() to filter "
      89         124 :                 "arrays."));
      90             : 
      91         124 :     group.add_argument("-group")
      92         248 :         .metavar("<group_spec>")
      93         124 :         .append()
      94         124 :         .store_into(psOptions->aosGroup)
      95             :         .help(_(
      96         124 :             "Select a single group instead of converting the whole dataset."));
      97             : 
      98             :     // Note: this is mutually exclusive with "view" option in -array
      99         124 :     argParser->add_argument("-subset")
     100         248 :         .metavar("<subset_spec>")
     101         124 :         .append()
     102         124 :         .store_into(psOptions->aosSubset)
     103         124 :         .help(_("Select a subset of the data."));
     104             : 
     105             :     // Note: this is mutually exclusive with "view" option in -array
     106         124 :     argParser->add_argument("-scaleaxes")
     107         248 :         .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         126 :             })
     118             :         .help(
     119         124 :             _("Applies a integral scale factor to one or several dimensions."));
     120             : 
     121         124 :     argParser->add_argument("-strict")
     122         124 :         .flag()
     123         124 :         .store_into(psOptions->bStrict)
     124         124 :         .help(_("Turn warnings into failures."));
     125             : 
     126             :     // Undocumented option used by gdal mdim convert
     127         124 :     argParser->add_argument("--overwrite")
     128         124 :         .store_into(psOptions->bOverwrite)
     129         124 :         .hidden();
     130             : 
     131             :     // Undocumented option used by gdal mdim convert
     132         124 :     argParser->add_argument("--no-overwrite")
     133         124 :         .store_into(psOptions->bNoOverwrite)
     134         124 :         .hidden();
     135             : 
     136         124 :     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         124 :     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          10 :                     if (poSrcDS->GetGeoTransform(gt) == CE_None &&
    1201           5 :                         gt.IsAxisAligned())
    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.xorig +
    1211           1 :                                           (range.m_nStartIdx + 0.5) * gt.xscale
    1212           4 :                                     : gt.yorig +
    1213           4 :                                           (range.m_nStartIdx + 0.5) * gt.yscale;
    1214             :                             const double dfIncr =
    1215           5 :                                 (srcIndexVar->GetName() == "X" ? gt.xscale
    1216           5 :                                                                : gt.yscale) *
    1217           5 :                                 range.m_nIncr;
    1218             :                             auto poSource = std::make_unique<
    1219             :                                 VRTMDArraySourceRegularlySpaced>(dfStart,
    1220           5 :                                                                  dfIncr);
    1221           5 :                             var->AddSource(std::move(poSource));
    1222           5 :                             bIndexingVarCreated = true;
    1223             :                         }
    1224             :                         // else: error emitted by CreateVRTMDArray()
    1225             :                     }
    1226             :                 }
    1227             : 
    1228             :                 // Arbitrary: to avoid blowing up RAM
    1229          10 :                 constexpr size_t MAX_SIZE_FOR_INDEXING_VAR = 1000 * 1000;
    1230          25 :                 if (!bIndexingVarCreated &&
    1231          10 :                     srcIndexVar->GetDimensionCount() == 1 &&
    1232          20 :                     srcIndexVar->GetDataType().GetClass() != GEDTC_COMPOUND &&
    1233           5 :                     srcIndexVar->GetDimensions()[0]->GetSize() <
    1234             :                         MAX_SIZE_FOR_INDEXING_VAR)
    1235             :                 {
    1236             :                     auto var = poDstGroup->CreateVRTMDArray(
    1237          20 :                         newDimName, {dstDim}, srcIndexVar->GetDataType());
    1238           5 :                     if (var)
    1239             :                     {
    1240          10 :                         std::vector<GUInt64> anOffset = {0};
    1241             :                         const size_t nCount = static_cast<size_t>(
    1242           5 :                             srcIndexVar->GetDimensions()[0]->GetSize());
    1243          10 :                         std::vector<size_t> anCount = {nCount};
    1244          10 :                         std::vector<GByte> abyValues;
    1245           5 :                         abyValues.resize(srcIndexVar->GetDataType().GetSize() *
    1246             :                                          nCount);
    1247           5 :                         const GInt64 arrayStep[] = {1};
    1248           5 :                         const GPtrDiff_t anBufferStride[] = {1};
    1249          10 :                         srcIndexVar->Read(anOffset.data(), anCount.data(),
    1250             :                                           arrayStep, anBufferStride,
    1251           5 :                                           srcIndexVar->GetDataType(),
    1252           5 :                                           abyValues.data());
    1253             :                         auto poSource =
    1254             :                             std::make_unique<VRTMDArraySourceInlinedValues>(
    1255           0 :                                 var.get(),
    1256           5 :                                 /* bIsConstantValue = */ false,
    1257           5 :                                 std::move(anOffset), std::move(anCount),
    1258          10 :                                 std::move(abyValues));
    1259           5 :                         var->AddSource(std::move(poSource));
    1260             :                     }
    1261             :                     // else: error emitted by CreateVRTMDArray()
    1262             :                 }
    1263           5 :                 else if (!bIndexingVarCreated)
    1264             :                 {
    1265           0 :                     CPLDebug("GDAL", "Cannot create indexing variable for %s",
    1266           0 :                              srcIndexVar->GetName().c_str());
    1267             :                 }
    1268             :             }
    1269             : 
    1270          76 :             CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
    1271          76 :             auto poDstIndexingVar(poDstGroup->OpenMDArray(newDimName));
    1272          38 :             if (poDstIndexingVar)
    1273          37 :                 dstDim->SetIndexingVariable(std::move(poDstIndexingVar));
    1274             :         }
    1275             :     }
    1276         258 :     if (outputType.GetClass() == GEDTC_NUMERIC &&
    1277         129 :         outputType.GetNumericDataType() == GDT_Unknown)
    1278             :     {
    1279         129 :         outputType = GDALExtendedDataType(tmpArray->GetDataType());
    1280             :     }
    1281             : 
    1282         258 :     CPLStringList aosArrayCO;
    1283         184 :     if (!bResampled && anTransposedAxis.empty() && viewExpr.empty() &&
    1284         353 :         psOptions->aosSubset.empty() && psOptions->aosScaleFactor.empty() &&
    1285          40 :         srcArray->GetDimensionCount() == dstArrayDims.size())
    1286             :     {
    1287          80 :         const auto anBlockSize = srcArray->GetBlockSize();
    1288          80 :         std::string osBlockSize;
    1289          43 :         for (auto v : anBlockSize)
    1290             :         {
    1291          42 :             if (v == 0)
    1292             :             {
    1293          39 :                 osBlockSize.clear();
    1294          39 :                 break;
    1295             :             }
    1296           3 :             if (!osBlockSize.empty())
    1297           2 :                 osBlockSize += ',';
    1298           3 :             osBlockSize += std::to_string(v);
    1299             :         }
    1300          40 :         if (!osBlockSize.empty())
    1301           1 :             aosArrayCO.SetNameValue("BLOCKSIZE", osBlockSize.c_str());
    1302             :     }
    1303             : 
    1304             :     auto dstArray = poDstGroup->CreateVRTMDArray(dstArrayName, dstArrayDims,
    1305         258 :                                                  outputType, aosArrayCO.List());
    1306         129 :     if (!dstArray)
    1307           0 :         return false;
    1308             : 
    1309         129 :     GUInt64 nCurCost = 0;
    1310         129 :     dstArray->CopyFromAllExceptValues(srcArray.get(), false, nCurCost, 0,
    1311             :                                       nullptr, nullptr);
    1312         129 :     if (bResampled)
    1313           1 :         dstArray->SetSpatialRef(tmpArray->GetSpatialRef().get());
    1314             : 
    1315         129 :     if (idxSliceSpec >= 0)
    1316             :     {
    1317         144 :         std::set<size_t> oSetParentDimIdxNotInArray;
    1318         166 :         for (size_t i = 0; i < srcArrayDims.size(); ++i)
    1319             :         {
    1320          94 :             oSetParentDimIdxNotInArray.insert(i);
    1321             :         }
    1322          72 :         const auto &viewSpec(viewSpecs[idxSliceSpec]);
    1323         145 :         for (size_t i = 0; i < tmpArrayDims.size(); ++i)
    1324             :         {
    1325          73 :             auto iParentDim = viewSpec.m_mapDimIdxToParentDimIdx[i];
    1326          73 :             if (iParentDim != static_cast<size_t>(-1))
    1327             :             {
    1328          72 :                 oSetParentDimIdxNotInArray.erase(iParentDim);
    1329             :             }
    1330             :         }
    1331          94 :         for (const auto parentDimIdx : oSetParentDimIdxNotInArray)
    1332             :         {
    1333          22 :             const auto &srcDim(srcArrayDims[parentDimIdx]);
    1334             :             const auto nStartIdx =
    1335          22 :                 viewSpec.m_parentRanges[parentDimIdx].m_nStartIdx;
    1336          22 :             if (nStartIdx < static_cast<GUInt64>(INT_MAX))
    1337             :             {
    1338             :                 auto dstAttr = dstArray->CreateAttribute(
    1339          44 :                     "DIM_" + srcDim->GetName() + "_INDEX", {},
    1340          88 :                     GDALExtendedDataType::Create(GDT_Int32));
    1341          22 :                 dstAttr->Write(static_cast<int>(nStartIdx));
    1342             :             }
    1343             :             else
    1344             :             {
    1345             :                 auto dstAttr = dstArray->CreateAttribute(
    1346           0 :                     "DIM_" + srcDim->GetName() + "_INDEX", {},
    1347           0 :                     GDALExtendedDataType::CreateString());
    1348           0 :                 dstAttr->Write(CPLSPrintf(CPL_FRMT_GUIB,
    1349             :                                           static_cast<GUIntBig>(nStartIdx)));
    1350             :             }
    1351             : 
    1352          44 :             auto srcIndexVar(srcDim->GetIndexingVariable());
    1353          22 :             if (srcIndexVar && srcIndexVar->GetDimensionCount() == 1)
    1354             :             {
    1355          22 :                 const auto &dt(srcIndexVar->GetDataType());
    1356          44 :                 std::vector<GByte> abyTmp(dt.GetSize());
    1357          22 :                 const size_t nCount = 1;
    1358          44 :                 if (srcIndexVar->Read(&nStartIdx, &nCount, nullptr, nullptr, dt,
    1359          22 :                                       &abyTmp[0], nullptr, 0))
    1360             :                 {
    1361             :                     {
    1362             :                         auto dstAttr = dstArray->CreateAttribute(
    1363          66 :                             "DIM_" + srcDim->GetName() + "_VALUE", {}, dt);
    1364          22 :                         dstAttr->Write(abyTmp.data(), abyTmp.size());
    1365          22 :                         dt.FreeDynamicMemory(&abyTmp[0]);
    1366             :                     }
    1367             : 
    1368          22 :                     const auto &unit(srcIndexVar->GetUnit());
    1369          22 :                     if (!unit.empty())
    1370             :                     {
    1371             :                         auto dstAttr = dstArray->CreateAttribute(
    1372           0 :                             "DIM_" + srcDim->GetName() + "_UNIT", {},
    1373           0 :                             GDALExtendedDataType::CreateString());
    1374           0 :                         dstAttr->Write(unit.c_str());
    1375             :                     }
    1376             :                 }
    1377             :             }
    1378             :         }
    1379             :     }
    1380             : 
    1381         129 :     double dfStart = 0.0;
    1382         129 :     double dfIncrement = 0.0;
    1383         131 :     if (!bSrcArrayAccessibleThroughSrcGroup &&
    1384           2 :         tmpArray->IsRegularlySpaced(dfStart, dfIncrement))
    1385             :     {
    1386             :         auto poSource = std::make_unique<VRTMDArraySourceRegularlySpaced>(
    1387           2 :             dfStart, dfIncrement);
    1388           2 :         dstArray->AddSource(std::move(poSource));
    1389             :     }
    1390             :     else
    1391             :     {
    1392         127 :         const auto dimCount(tmpArray->GetDimensionCount());
    1393         254 :         std::vector<GUInt64> anSrcOffset(dimCount);
    1394         254 :         std::vector<GUInt64> anCount(dimCount);
    1395         274 :         for (size_t i = 0; i < dimCount; ++i)
    1396             :         {
    1397         147 :             anCount[i] = tmpArrayDims[i]->GetSize();
    1398             :         }
    1399         254 :         std::vector<GUInt64> anStep(dimCount, 1);
    1400         254 :         std::vector<GUInt64> anDstOffset(dimCount);
    1401             :         auto poSource = std::make_unique<VRTMDArraySourceFromArray>(
    1402         254 :             dstArray.get(), false, false, poSrcDS->GetDescription(),
    1403         254 :             band < 0 ? srcArray->GetFullName() : std::string(),
    1404         255 :             band >= 1 ? CPLSPrintf("%d", band) : std::string(),
    1405         127 :             std::move(anTransposedAxis),
    1406         253 :             bResampled ? (viewExpr.empty()
    1407             :                               ? std::string("resample=true")
    1408         127 :                               : std::string("resample=true,").append(viewExpr))
    1409         126 :                        : std::move(viewExpr),
    1410         127 :             std::move(anSrcOffset), std::move(anCount), std::move(anStep),
    1411         254 :             std::move(anDstOffset));
    1412         127 :         dstArray->AddSource(std::move(poSource));
    1413             :     }
    1414             : 
    1415         129 :     return true;
    1416             : }
    1417             : 
    1418             : /************************************************************************/
    1419             : /*                              GetGroup()                              */
    1420             : /************************************************************************/
    1421             : 
    1422             : static std::shared_ptr<GDALGroup>
    1423           6 : GetGroup(const std::shared_ptr<GDALGroup> &poRootGroup,
    1424             :          const std::string &fullName)
    1425             : {
    1426          12 :     auto poCurGroup = poRootGroup;
    1427          12 :     CPLStringList aosTokens(CSLTokenizeString2(fullName.c_str(), "/", 0));
    1428          10 :     for (int i = 0; i < aosTokens.size(); i++)
    1429             :     {
    1430          10 :         auto poCurGroupNew = poCurGroup->OpenGroup(aosTokens[i], nullptr);
    1431           5 :         if (!poCurGroupNew)
    1432             :         {
    1433           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot find group %s",
    1434             :                      aosTokens[i]);
    1435           1 :             return nullptr;
    1436             :         }
    1437           4 :         poCurGroup = std::move(poCurGroupNew);
    1438             :     }
    1439           5 :     return poCurGroup;
    1440             : }
    1441             : 
    1442             : /************************************************************************/
    1443             : /*                             CopyGroup()                              */
    1444             : /************************************************************************/
    1445             : 
    1446          17 : static bool CopyGroup(
    1447             :     DimensionRemapper &oDimRemapper,
    1448             :     const std::shared_ptr<VRTGroup> &poDstRootGroup,
    1449             :     std::shared_ptr<VRTGroup> &poDstGroup,
    1450             :     const std::shared_ptr<GDALGroup> &poSrcRootGroup,
    1451             :     const std::shared_ptr<GDALGroup> &poSrcGroup, GDALDataset *poSrcDS,
    1452             :     std::map<std::string, std::shared_ptr<GDALDimension>> &mapSrcToDstDims,
    1453             :     std::map<std::string, std::shared_ptr<GDALDimension>> &mapDstDimFullNames,
    1454             :     const GDALMultiDimTranslateOptions *psOptions, bool bRecursive)
    1455             : {
    1456          34 :     const auto srcDims = poSrcGroup->GetDimensions();
    1457          34 :     std::map<std::string, std::string> mapSrcVariableNameToIndexedDimName;
    1458          29 :     for (const auto &dim : srcDims)
    1459             :     {
    1460          15 :         const auto poDimDesc = GetDimensionDesc(oDimRemapper, psOptions, dim);
    1461          15 :         if (poDimDesc == nullptr)
    1462           3 :             return false;
    1463          12 :         if (poDimDesc->bSlice)
    1464           2 :             continue;
    1465             :         auto dstDim =
    1466             :             poDstGroup->CreateDimension(dim->GetName(), dim->GetType(),
    1467          10 :                                         dim->GetDirection(), poDimDesc->nSize);
    1468          10 :         if (!dstDim)
    1469           0 :             return false;
    1470          10 :         mapSrcToDstDims[dim->GetFullName()] = dstDim;
    1471          10 :         mapDstDimFullNames[dstDim->GetFullName()] = dstDim;
    1472          20 :         auto poIndexingVarSrc(dim->GetIndexingVariable());
    1473          10 :         if (poIndexingVarSrc)
    1474             :         {
    1475          10 :             mapSrcVariableNameToIndexedDimName[poIndexingVarSrc->GetName()] =
    1476          20 :                 dim->GetFullName();
    1477             :         }
    1478             :     }
    1479             : 
    1480          14 :     if (!(poSrcGroup == poSrcRootGroup && psOptions->aosGroup.empty()))
    1481             :     {
    1482          11 :         auto attrs = poSrcGroup->GetAttributes();
    1483          15 :         for (const auto &attr : attrs)
    1484             :         {
    1485             :             auto dstAttr = poDstGroup->CreateAttribute(
    1486           4 :                 attr->GetName(), attr->GetDimensionsSize(),
    1487           8 :                 attr->GetDataType());
    1488           4 :             if (!dstAttr)
    1489             :             {
    1490           0 :                 if (!psOptions->bStrict)
    1491           0 :                     continue;
    1492           0 :                 return false;
    1493             :             }
    1494           4 :             auto raw(attr->ReadAsRaw());
    1495           4 :             if (!dstAttr->Write(raw.data(), raw.size()) && !psOptions->bStrict)
    1496           0 :                 return false;
    1497             :         }
    1498             :     }
    1499             : 
    1500             :     auto arrayNames =
    1501          28 :         poSrcGroup->GetMDArrayNames(psOptions->aosArrayOptions.List());
    1502          40 :     for (const auto &name : arrayNames)
    1503             :     {
    1504          26 :         if (!TranslateArray(oDimRemapper, nullptr, name, poSrcRootGroup,
    1505             :                             poSrcGroup, poDstRootGroup, poDstGroup, poSrcDS,
    1506             :                             mapSrcToDstDims, mapDstDimFullNames, psOptions))
    1507             :         {
    1508           0 :             return false;
    1509             :         }
    1510             : 
    1511             :         // If this array is the indexing variable of a dimension, link them
    1512             :         // together.
    1513          52 :         auto srcArray = poSrcGroup->OpenMDArray(name);
    1514          26 :         CPLAssert(srcArray);
    1515          52 :         auto dstArray = poDstGroup->OpenMDArray(name);
    1516          26 :         CPLAssert(dstArray);
    1517             :         auto oIterDimName =
    1518          26 :             mapSrcVariableNameToIndexedDimName.find(srcArray->GetName());
    1519          26 :         if (oIterDimName != mapSrcVariableNameToIndexedDimName.end())
    1520             :         {
    1521             :             auto oCorrespondingDimIter =
    1522          10 :                 mapSrcToDstDims.find(oIterDimName->second);
    1523          10 :             if (oCorrespondingDimIter != mapSrcToDstDims.end())
    1524             :             {
    1525          10 :                 CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
    1526          20 :                 oCorrespondingDimIter->second->SetIndexingVariable(
    1527          10 :                     std::move(dstArray));
    1528             :             }
    1529             :         }
    1530             :     }
    1531             : 
    1532          14 :     if (bRecursive)
    1533             :     {
    1534          14 :         auto groupNames = poSrcGroup->GetGroupNames();
    1535          20 :         for (const auto &name : groupNames)
    1536             :         {
    1537           6 :             auto srcSubGroup = poSrcGroup->OpenGroup(name);
    1538           6 :             if (!srcSubGroup)
    1539             :             {
    1540           0 :                 return false;
    1541             :             }
    1542           6 :             auto dstSubGroup = poDstGroup->CreateVRTGroup(name);
    1543           6 :             if (!dstSubGroup)
    1544             :             {
    1545           0 :                 return false;
    1546             :             }
    1547           6 :             if (!CopyGroup(oDimRemapper, poDstRootGroup, dstSubGroup,
    1548             :                            poSrcRootGroup, srcSubGroup, poSrcDS,
    1549             :                            mapSrcToDstDims, mapDstDimFullNames, psOptions,
    1550             :                            true))
    1551             :             {
    1552           0 :                 return false;
    1553             :             }
    1554             :         }
    1555             :     }
    1556          14 :     return true;
    1557             : }
    1558             : 
    1559             : /************************************************************************/
    1560             : /*                           ParseGroupSpec()                           */
    1561             : /************************************************************************/
    1562             : 
    1563             : // foo
    1564             : // name=foo,dstname=bar,recursive=no
    1565           7 : static bool ParseGroupSpec(const std::string &groupSpec, std::string &srcName,
    1566             :                            std::string &dstName, bool &bRecursive)
    1567             : {
    1568           7 :     bRecursive = true;
    1569           7 :     if (!STARTS_WITH(groupSpec.c_str(), "name="))
    1570             :     {
    1571           5 :         srcName = groupSpec;
    1572           5 :         return true;
    1573             :     }
    1574             : 
    1575           4 :     CPLStringList aosTokens(CSLTokenizeString2(groupSpec.c_str(), ",", 0));
    1576           5 :     for (int i = 0; i < aosTokens.size(); i++)
    1577             :     {
    1578           4 :         const std::string token(aosTokens[i]);
    1579           4 :         if (STARTS_WITH(token.c_str(), "name="))
    1580             :         {
    1581           2 :             srcName = token.substr(strlen("name="));
    1582             :         }
    1583           2 :         else if (STARTS_WITH(token.c_str(), "dstname="))
    1584             :         {
    1585           1 :             dstName = token.substr(strlen("dstname="));
    1586             :         }
    1587           1 :         else if (token == "recursive=no")
    1588             :         {
    1589           0 :             bRecursive = false;
    1590             :         }
    1591             :         else
    1592             :         {
    1593           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1594             :                      "Unexpected group specification part: %s", token.c_str());
    1595           1 :             return false;
    1596             :         }
    1597             :     }
    1598           1 :     return true;
    1599             : }
    1600             : 
    1601             : /************************************************************************/
    1602             : /*                         TranslateInternal()                          */
    1603             : /************************************************************************/
    1604             : 
    1605         106 : static bool TranslateInternal(std::shared_ptr<VRTGroup> &poDstRootGroup,
    1606             :                               GDALDataset *poSrcDS,
    1607             :                               const GDALMultiDimTranslateOptions *psOptions)
    1608             : {
    1609             : 
    1610         212 :     auto poSrcRootGroup = poSrcDS->GetRootGroup();
    1611         106 :     if (poSrcRootGroup)
    1612             :     {
    1613         102 :         if (psOptions->aosGroup.empty())
    1614             :         {
    1615         192 :             auto attrs = poSrcRootGroup->GetAttributes();
    1616         104 :             for (const auto &attr : attrs)
    1617             :             {
    1618           8 :                 if (attr->GetName() == "Conventions")
    1619           2 :                     continue;
    1620             :                 auto dstAttr = poDstRootGroup->CreateAttribute(
    1621           6 :                     attr->GetName(), attr->GetDimensionsSize(),
    1622          18 :                     attr->GetDataType());
    1623           6 :                 if (dstAttr)
    1624             :                 {
    1625          12 :                     auto raw(attr->ReadAsRaw());
    1626           6 :                     dstAttr->Write(raw.data(), raw.size());
    1627             :                 }
    1628             :             }
    1629             :         }
    1630             :     }
    1631             : 
    1632         212 :     DimensionRemapper oDimRemapper;
    1633         212 :     std::map<std::string, std::shared_ptr<GDALDimension>> mapSrcToDstDims;
    1634         212 :     std::map<std::string, std::shared_ptr<GDALDimension>> mapDstDimFullNames;
    1635         106 :     if (!psOptions->aosGroup.empty())
    1636             :     {
    1637           6 :         if (poSrcRootGroup == nullptr)
    1638             :         {
    1639           0 :             CPLError(
    1640             :                 CE_Failure, CPLE_AppDefined,
    1641             :                 "No multidimensional source dataset: -group cannot be used");
    1642           0 :             return false;
    1643             :         }
    1644           6 :         if (psOptions->aosGroup.size() == 1)
    1645             :         {
    1646          10 :             std::string srcName;
    1647          10 :             std::string dstName;
    1648             :             bool bRecursive;
    1649           5 :             if (!ParseGroupSpec(psOptions->aosGroup[0], srcName, dstName,
    1650             :                                 bRecursive))
    1651           1 :                 return false;
    1652           8 :             auto poSrcGroup = GetGroup(poSrcRootGroup, srcName);
    1653           4 :             if (!poSrcGroup)
    1654           1 :                 return false;
    1655           3 :             return CopyGroup(oDimRemapper, poDstRootGroup, poDstRootGroup,
    1656             :                              poSrcRootGroup, poSrcGroup, poSrcDS,
    1657             :                              mapSrcToDstDims, mapDstDimFullNames, psOptions,
    1658           3 :                              bRecursive);
    1659             :         }
    1660             :         else
    1661             :         {
    1662           3 :             for (const auto &osGroupSpec : psOptions->aosGroup)
    1663             :             {
    1664           2 :                 std::string srcName;
    1665           2 :                 std::string dstName;
    1666             :                 bool bRecursive;
    1667           2 :                 if (!ParseGroupSpec(osGroupSpec, srcName, dstName, bRecursive))
    1668           0 :                     return false;
    1669           2 :                 auto poSrcGroup = GetGroup(poSrcRootGroup, srcName);
    1670           2 :                 if (!poSrcGroup)
    1671           0 :                     return false;
    1672           2 :                 if (dstName.empty())
    1673           1 :                     dstName = poSrcGroup->GetName();
    1674           2 :                 auto dstSubGroup = poDstRootGroup->CreateVRTGroup(dstName);
    1675           4 :                 if (!dstSubGroup ||
    1676           2 :                     !CopyGroup(oDimRemapper, poDstRootGroup, dstSubGroup,
    1677             :                                poSrcRootGroup, poSrcGroup, poSrcDS,
    1678             :                                mapSrcToDstDims, mapDstDimFullNames, psOptions,
    1679             :                                bRecursive))
    1680             :                 {
    1681           0 :                     return false;
    1682             :                 }
    1683             :             }
    1684             :         }
    1685             :     }
    1686         100 :     else if (!psOptions->aosArraySpec.empty())
    1687             :     {
    1688         169 :         for (const auto &arraySpec : psOptions->aosArraySpec)
    1689             :         {
    1690          94 :             if (!TranslateArray(oDimRemapper, nullptr, arraySpec,
    1691             :                                 poSrcRootGroup, poSrcRootGroup, poDstRootGroup,
    1692             :                                 poDstRootGroup, poSrcDS, mapSrcToDstDims,
    1693             :                                 mapDstDimFullNames, psOptions))
    1694             :             {
    1695          19 :                 return false;
    1696             :             }
    1697             :         }
    1698             :     }
    1699             :     else
    1700             :     {
    1701           6 :         if (poSrcRootGroup == nullptr)
    1702             :         {
    1703           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1704             :                      "No multidimensional source dataset");
    1705           0 :             return false;
    1706             :         }
    1707           6 :         return CopyGroup(oDimRemapper, poDstRootGroup, poDstRootGroup,
    1708             :                          poSrcRootGroup, poSrcRootGroup, poSrcDS,
    1709           6 :                          mapSrcToDstDims, mapDstDimFullNames, psOptions, true);
    1710             :     }
    1711             : 
    1712          76 :     return true;
    1713             : }
    1714             : 
    1715             : /************************************************************************/
    1716             : /*                  CopyToNonMultiDimensionalDriver()                   */
    1717             : /************************************************************************/
    1718             : 
    1719             : static GDALDatasetH
    1720           4 : CopyToNonMultiDimensionalDriver(GDALDriver *poDriver, const char *pszDest,
    1721             :                                 const std::shared_ptr<GDALGroup> &poRG,
    1722             :                                 const GDALMultiDimTranslateOptions *psOptions)
    1723             : {
    1724           4 :     std::shared_ptr<GDALMDArray> srcArray;
    1725           4 :     if (psOptions && !psOptions->aosArraySpec.empty())
    1726             :     {
    1727           3 :         if (psOptions->aosArraySpec.size() != 1)
    1728             :         {
    1729           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    1730             :                      "For output to a non-multidimensional driver, only "
    1731             :                      "one array should be specified");
    1732           0 :             return nullptr;
    1733             :         }
    1734           6 :         std::string srcArrayName;
    1735           6 :         std::string dstArrayName;
    1736           3 :         int band = -1;
    1737           6 :         std::vector<int> anTransposedAxis;
    1738           6 :         std::string viewExpr;
    1739             :         GDALExtendedDataType outputType(
    1740           3 :             GDALExtendedDataType::Create(GDT_Unknown));
    1741           3 :         bool bResampled = false;
    1742           3 :         ParseArraySpec(psOptions->aosArraySpec[0], srcArrayName, dstArrayName,
    1743             :                        band, anTransposedAxis, viewExpr, outputType,
    1744             :                        bResampled);
    1745           3 :         srcArray = poRG->OpenMDArray(dstArrayName);
    1746             :     }
    1747             :     else
    1748             :     {
    1749           1 :         auto srcArrayNames = poRG->GetMDArrayNames(
    1750           1 :             psOptions ? psOptions->aosArrayOptions.List() : nullptr);
    1751           5 :         for (const auto &srcArrayName : srcArrayNames)
    1752             :         {
    1753           5 :             auto tmpArray = poRG->OpenMDArray(srcArrayName);
    1754           5 :             if (tmpArray)
    1755             :             {
    1756           5 :                 const auto &dims(tmpArray->GetDimensions());
    1757          13 :                 if (!(dims.size() == 1 && dims[0]->GetIndexingVariable() &&
    1758           8 :                       dims[0]->GetIndexingVariable()->GetName() ==
    1759             :                           srcArrayName))
    1760             :                 {
    1761           2 :                     if (srcArray)
    1762             :                     {
    1763           1 :                         CPLError(CE_Failure, CPLE_AppDefined,
    1764             :                                  "Several arrays exist. Select one for "
    1765             :                                  "output to non-multidimensional driver");
    1766           1 :                         return nullptr;
    1767             :                     }
    1768           1 :                     srcArray = std::move(tmpArray);
    1769             :                 }
    1770             :             }
    1771             :         }
    1772             :     }
    1773           3 :     if (!srcArray)
    1774             :     {
    1775           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find source array");
    1776           0 :         return nullptr;
    1777             :     }
    1778           3 :     size_t iXDim = static_cast<size_t>(-1);
    1779           3 :     size_t iYDim = static_cast<size_t>(-1);
    1780           3 :     const auto &dims(srcArray->GetDimensions());
    1781           8 :     for (size_t i = 0; i < dims.size(); ++i)
    1782             :     {
    1783           5 :         if (dims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X)
    1784             :         {
    1785           2 :             iXDim = i;
    1786             :         }
    1787           3 :         else if (dims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y)
    1788             :         {
    1789           3 :             iYDim = i;
    1790             :         }
    1791             :     }
    1792           3 :     if (dims.size() == 1)
    1793             :     {
    1794           1 :         iXDim = 0;
    1795             :     }
    1796           2 :     else if (dims.size() >= 2 && (iXDim == static_cast<size_t>(-1) ||
    1797             :                                   iYDim == static_cast<size_t>(-1)))
    1798             :     {
    1799           0 :         iXDim = dims.size() - 1;
    1800           0 :         iYDim = dims.size() - 2;
    1801             :     }
    1802             :     std::unique_ptr<GDALDataset> poTmpSrcDS(
    1803           6 :         srcArray->AsClassicDataset(iXDim, iYDim));
    1804           3 :     if (!poTmpSrcDS)
    1805           0 :         return nullptr;
    1806           6 :     return GDALDataset::ToHandle(poDriver->CreateCopy(
    1807             :         pszDest, poTmpSrcDS.get(), false,
    1808           3 :         psOptions ? const_cast<char **>(psOptions->aosCreateOptions.List())
    1809             :                   : nullptr,
    1810             :         psOptions ? psOptions->pfnProgress : nullptr,
    1811           3 :         psOptions ? psOptions->pProgressData : nullptr));
    1812             : }
    1813             : 
    1814             : /************************************************************************/
    1815             : /*                       GDALMultiDimTranslate()                        */
    1816             : /************************************************************************/
    1817             : 
    1818             : /* clang-format off */
    1819             : /**
    1820             :  * Converts raster data between different formats.
    1821             :  *
    1822             :  * This is the equivalent of the
    1823             :  * <a href="/programs/gdalmdimtranslate.html">gdalmdimtranslate</a> utility.
    1824             :  *
    1825             :  * GDALMultiDimTranslateOptions* must be allocated and freed with
    1826             :  * GDALMultiDimTranslateOptionsNew() and GDALMultiDimTranslateOptionsFree()
    1827             :  * respectively. pszDest and hDstDS cannot be used at the same time.
    1828             :  *
    1829             :  * @param pszDest the destination dataset path or NULL.
    1830             :  * @param hDstDS the destination dataset or NULL.
    1831             :  * @param nSrcCount the number of input datasets.
    1832             :  * @param pahSrcDS the list of input datasets.
    1833             :  * @param psOptions the options struct returned by
    1834             :  * GDALMultiDimTranslateOptionsNew() or NULL.
    1835             :  * @param pbUsageError pointer to a integer output variable to store if any
    1836             :  * usage error has occurred or NULL.
    1837             :  * @return the output dataset (new dataset that must be closed using
    1838             :  * GDALClose(), or hDstDS is not NULL) or NULL in case of error.
    1839             :  *
    1840             :  * @since GDAL 3.1
    1841             :  */
    1842             : /* clang-format on */
    1843             : 
    1844             : GDALDatasetH
    1845         123 : GDALMultiDimTranslate(const char *pszDest, GDALDatasetH hDstDS, int nSrcCount,
    1846             :                       GDALDatasetH *pahSrcDS,
    1847             :                       const GDALMultiDimTranslateOptions *psOptions,
    1848             :                       int *pbUsageError)
    1849             : {
    1850         123 :     if (pbUsageError)
    1851         111 :         *pbUsageError = false;
    1852         123 :     if (nSrcCount != 1 || pahSrcDS[0] == nullptr)
    1853             :     {
    1854           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1855             :                  "Only one source dataset is supported");
    1856           0 :         if (pbUsageError)
    1857           0 :             *pbUsageError = true;
    1858           0 :         return nullptr;
    1859             :     }
    1860             : 
    1861         123 :     if (hDstDS)
    1862             :     {
    1863           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1864             :                  "Update of existing file not supported yet");
    1865           0 :         GDALClose(hDstDS);
    1866           0 :         return nullptr;
    1867             :     }
    1868             : 
    1869         369 :     CPLString osFormat(psOptions ? psOptions->osFormat : "");
    1870         123 :     if (pszDest == nullptr /* && hDstDS == nullptr */)
    1871             :     {
    1872           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1873             :                  "Both pszDest and hDstDS are NULL.");
    1874           0 :         if (pbUsageError)
    1875           0 :             *pbUsageError = true;
    1876           0 :         return nullptr;
    1877             :     }
    1878             : 
    1879         123 :     GDALDriver *poDriver = nullptr;
    1880             : 
    1881             : #ifdef this_is_dead_code_for_now
    1882             :     const bool bCloseOutDSOnError = hDstDS == nullptr;
    1883             :     if (pszDest == nullptr)
    1884             :         pszDest = GDALGetDescription(hDstDS);
    1885             : #endif
    1886             : 
    1887         123 :     if (psOptions && psOptions->bOverwrite && !EQUAL(pszDest, ""))
    1888             :     {
    1889           1 :         VSIRmdirRecursive(pszDest);
    1890             :     }
    1891         122 :     else if (psOptions && psOptions->bNoOverwrite && !EQUAL(pszDest, ""))
    1892             :     {
    1893             :         VSIStatBufL sStat;
    1894          10 :         if (VSIStatL(pszDest, &sStat) == 0)
    1895             :         {
    1896           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1897             :                      "File '%s' already exists. Specify the --overwrite "
    1898             :                      "option to overwrite it.",
    1899             :                      pszDest);
    1900           0 :             return nullptr;
    1901             :         }
    1902          10 :         else if (std::unique_ptr<GDALDataset>(GDALDataset::Open(pszDest)))
    1903             :         {
    1904           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1905             :                      "Dataset '%s' already exists. Specify the --overwrite "
    1906             :                      "option to overwrite it.",
    1907             :                      pszDest);
    1908           0 :             return nullptr;
    1909             :         }
    1910             :     }
    1911             : 
    1912             : #ifdef this_is_dead_code_for_now
    1913             :     if (hDstDS == nullptr)
    1914             : #endif
    1915             :     {
    1916         123 :         if (osFormat.empty())
    1917             :         {
    1918         110 :             if (EQUAL(CPLGetExtensionSafe(pszDest).c_str(), "nc"))
    1919           4 :                 osFormat = "netCDF";
    1920             :             else
    1921         106 :                 osFormat = GetOutputDriverForRaster(pszDest);
    1922         110 :             if (osFormat.empty())
    1923             :             {
    1924           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1925             :                          "Cannot determine output driver for dataset name '%s'",
    1926             :                          pszDest);
    1927           1 :                 return nullptr;
    1928             :             }
    1929             :         }
    1930         122 :         poDriver = GDALDriver::FromHandle(GDALGetDriverByName(osFormat));
    1931             :         CSLConstList papszDriverMD =
    1932         122 :             poDriver ? poDriver->GetMetadata() : nullptr;
    1933         244 :         if (poDriver == nullptr ||
    1934         122 :             (!CPLTestBool(CSLFetchNameValueDef(papszDriverMD, GDAL_DCAP_RASTER,
    1935           0 :                                                "FALSE")) &&
    1936           0 :              !CPLTestBool(CSLFetchNameValueDef(
    1937         244 :                  papszDriverMD, GDAL_DCAP_MULTIDIM_RASTER, "FALSE"))) ||
    1938         122 :             (!CPLTestBool(CSLFetchNameValueDef(papszDriverMD, GDAL_DCAP_CREATE,
    1939           0 :                                                "FALSE")) &&
    1940           0 :              !CPLTestBool(CSLFetchNameValueDef(
    1941           0 :                  papszDriverMD, GDAL_DCAP_CREATECOPY, "FALSE")) &&
    1942           0 :              !CPLTestBool(CSLFetchNameValueDef(
    1943           0 :                  papszDriverMD, GDAL_DCAP_CREATE_MULTIDIMENSIONAL, "FALSE")) &&
    1944           0 :              !CPLTestBool(CSLFetchNameValueDef(
    1945             :                  papszDriverMD, GDAL_DCAP_CREATECOPY_MULTIDIMENSIONAL,
    1946             :                  "FALSE"))))
    1947             :         {
    1948           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    1949             :                      "Output driver `%s' not recognised or does not support "
    1950             :                      "output file creation.",
    1951             :                      osFormat.c_str());
    1952           0 :             return nullptr;
    1953             :         }
    1954             :     }
    1955             : 
    1956         122 :     GDALDataset *poSrcDS = GDALDataset::FromHandle(pahSrcDS[0]);
    1957             : 
    1958         122 :     std::unique_ptr<GDALDataset> poTmpDS;
    1959         122 :     GDALDataset *poTmpSrcDS = poSrcDS;
    1960         244 :     if (psOptions &&
    1961         122 :         (!psOptions->aosArraySpec.empty() || !psOptions->aosGroup.empty() ||
    1962          22 :          !psOptions->aosSubset.empty() || !psOptions->aosScaleFactor.empty() ||
    1963          17 :          !psOptions->aosArrayOptions.empty()))
    1964             :     {
    1965             :         auto poVRTDS =
    1966         106 :             VRTDataset::CreateVRTMultiDimensional("", nullptr, nullptr);
    1967         106 :         CPLAssert(poVRTDS);
    1968             : 
    1969         106 :         auto poDstRootGroup = poVRTDS->GetRootVRTGroup();
    1970         106 :         CPLAssert(poDstRootGroup);
    1971             : 
    1972         106 :         if (!TranslateInternal(poDstRootGroup, poSrcDS, psOptions))
    1973             :         {
    1974             : #ifdef this_is_dead_code_for_now
    1975             :             if (bCloseOutDSOnError)
    1976             : #endif
    1977             :             {
    1978          24 :                 GDALClose(hDstDS);
    1979          24 :                 hDstDS = nullptr;
    1980             :             }
    1981          24 :             return nullptr;
    1982             :         }
    1983             : 
    1984          82 :         poTmpDS = std::move(poVRTDS);
    1985          82 :         poTmpSrcDS = poTmpDS.get();
    1986             :     }
    1987             : 
    1988          98 :     auto poRG(poTmpSrcDS->GetRootGroup());
    1989         193 :     if (poRG &&
    1990          95 :         poDriver->GetMetadataItem(GDAL_DCAP_CREATE_MULTIDIMENSIONAL) ==
    1991         193 :             nullptr &&
    1992           4 :         poDriver->GetMetadataItem(GDAL_DCAP_CREATECOPY_MULTIDIMENSIONAL) ==
    1993             :             nullptr)
    1994             :     {
    1995             : #ifdef this_is_dead_code_for_now
    1996             :         if (hDstDS)
    1997             :         {
    1998             :             CPLError(CE_Failure, CPLE_NotSupported,
    1999             :                      "Appending to non-multidimensional driver not supported.");
    2000             :             GDALClose(hDstDS);
    2001             :             hDstDS = nullptr;
    2002             :             return nullptr;
    2003             :         }
    2004             : #endif
    2005             :         hDstDS =
    2006           4 :             CopyToNonMultiDimensionalDriver(poDriver, pszDest, poRG, psOptions);
    2007             :     }
    2008             :     else
    2009             :     {
    2010         188 :         hDstDS = GDALDataset::ToHandle(poDriver->CreateCopy(
    2011             :             pszDest, poTmpSrcDS, false,
    2012          94 :             psOptions ? const_cast<char **>(psOptions->aosCreateOptions.List())
    2013             :                       : nullptr,
    2014             :             psOptions ? psOptions->pfnProgress : nullptr,
    2015             :             psOptions ? psOptions->pProgressData : nullptr));
    2016             :     }
    2017             : 
    2018          98 :     return hDstDS;
    2019             : }
    2020             : 
    2021             : /************************************************************************/
    2022             : /*                  GDALMultiDimTranslateOptionsNew()                   */
    2023             : /************************************************************************/
    2024             : 
    2025             : /**
    2026             :  * Allocates a GDALMultiDimTranslateOptions struct.
    2027             :  *
    2028             :  * @param papszArgv NULL terminated list of options (potentially including
    2029             :  * filename and open options too), or NULL. The accepted options are the ones of
    2030             :  * the <a href="/programs/gdalmdimtranslate.html">gdalmdimtranslate</a> utility.
    2031             :  * @param psOptionsForBinary should be nullptr, unless called from
    2032             :  * gdalmdimtranslate_bin.cpp
    2033             :  * @return pointer to the allocated GDALMultiDimTranslateOptions struct. Must be
    2034             :  * freed with GDALMultiDimTranslateOptionsFree().
    2035             :  *
    2036             :  * @since GDAL 3.1
    2037             :  */
    2038             : 
    2039         124 : GDALMultiDimTranslateOptions *GDALMultiDimTranslateOptionsNew(
    2040             :     char **papszArgv, GDALMultiDimTranslateOptionsForBinary *psOptionsForBinary)
    2041             : {
    2042             : 
    2043         248 :     auto psOptions = std::make_unique<GDALMultiDimTranslateOptions>();
    2044             : 
    2045             :     /* -------------------------------------------------------------------- */
    2046             :     /*      Parse arguments.                                                */
    2047             :     /* -------------------------------------------------------------------- */
    2048             :     try
    2049             :     {
    2050             :         auto argParser = GDALMultiDimTranslateAppOptionsGetParser(
    2051         124 :             psOptions.get(), psOptionsForBinary);
    2052             : 
    2053         124 :         argParser->parse_args_without_binary_name(papszArgv);
    2054             : 
    2055             :         // Check for invalid options:
    2056             :         // -scaleaxes is not compatible with -array = "view"
    2057             :         // -subset is not compatible with -array = "view"
    2058         124 :         if (std::find(psOptions->aosArraySpec.cbegin(),
    2059         124 :                       psOptions->aosArraySpec.cend(),
    2060         372 :                       "view") != psOptions->aosArraySpec.cend())
    2061             :         {
    2062           0 :             if (!psOptions->aosScaleFactor.empty())
    2063             :             {
    2064           0 :                 CPLError(CE_Failure, CPLE_NotSupported,
    2065             :                          "The -scaleaxes option is not compatible with the "
    2066             :                          "-array \"view\" option.");
    2067           0 :                 return nullptr;
    2068             :             }
    2069             : 
    2070           0 :             if (!psOptions->aosSubset.empty())
    2071             :             {
    2072           0 :                 CPLError(CE_Failure, CPLE_NotSupported,
    2073             :                          "The -subset option is not compatible with the -array "
    2074             :                          "\"view\" option.");
    2075           0 :                 return nullptr;
    2076             :             }
    2077             :         }
    2078             :     }
    2079           0 :     catch (const std::exception &error)
    2080             :     {
    2081           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s", error.what());
    2082           0 :         return nullptr;
    2083             :     }
    2084             : 
    2085         124 :     if (psOptionsForBinary)
    2086             :     {
    2087             :         // Note: bUpdate is apparently never changed by the command line options
    2088           3 :         psOptionsForBinary->bUpdate = psOptions->bUpdate;
    2089           3 :         if (!psOptions->osFormat.empty())
    2090           0 :             psOptionsForBinary->osFormat = psOptions->osFormat;
    2091             :     }
    2092             : 
    2093         124 :     return psOptions.release();
    2094             : }
    2095             : 
    2096             : /************************************************************************/
    2097             : /*                  GDALMultiDimTranslateOptionsFree()                  */
    2098             : /************************************************************************/
    2099             : 
    2100             : /**
    2101             :  * Frees the GDALMultiDimTranslateOptions struct.
    2102             :  *
    2103             :  * @param psOptions the options struct for GDALMultiDimTranslate().
    2104             :  *
    2105             :  * @since GDAL 3.1
    2106             :  */
    2107             : 
    2108         123 : void GDALMultiDimTranslateOptionsFree(GDALMultiDimTranslateOptions *psOptions)
    2109             : {
    2110         123 :     delete psOptions;
    2111         123 : }
    2112             : 
    2113             : /************************************************************************/
    2114             : /*              GDALMultiDimTranslateOptionsSetProgress()               */
    2115             : /************************************************************************/
    2116             : 
    2117             : /**
    2118             :  * Set a progress function.
    2119             :  *
    2120             :  * @param psOptions the options struct for GDALMultiDimTranslate().
    2121             :  * @param pfnProgress the progress callback.
    2122             :  * @param pProgressData the user data for the progress callback.
    2123             :  *
    2124             :  * @since GDAL 3.1
    2125             :  */
    2126             : 
    2127          15 : void GDALMultiDimTranslateOptionsSetProgress(
    2128             :     GDALMultiDimTranslateOptions *psOptions, GDALProgressFunc pfnProgress,
    2129             :     void *pProgressData)
    2130             : {
    2131          15 :     psOptions->pfnProgress = pfnProgress;
    2132          15 :     psOptions->pProgressData = pProgressData;
    2133          15 : }

Generated by: LCOV version 1.14