LCOV - code coverage report
Current view: top level - apps - gdalmdimtranslate_lib.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 829 999 83.0 %
Date: 2025-05-15 18:21:54 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         117 : GDALMultiDimTranslateAppOptionsGetParser(
      51             :     GDALMultiDimTranslateOptions *psOptions,
      52             :     GDALMultiDimTranslateOptionsForBinary *psOptionsForBinary)
      53             : {
      54             :     auto argParser = std::make_unique<GDALArgumentParser>(
      55         117 :         "gdalmdimtranslate", /* bForBinary=*/psOptionsForBinary != nullptr);
      56             : 
      57         117 :     argParser->add_description(
      58             :         _("Converts multidimensional data between different formats, and "
      59         117 :           "performs subsetting."));
      60             : 
      61         117 :     argParser->add_epilog(
      62             :         _("For more details, consult "
      63         117 :           "https://gdal.org/programs/gdalmdimtranslate.html"));
      64             : 
      65         117 :     if (psOptionsForBinary)
      66             :     {
      67             :         argParser->add_input_format_argument(
      68           3 :             &psOptionsForBinary->aosAllowInputDrivers);
      69             :     }
      70             : 
      71         117 :     argParser->add_output_format_argument(psOptions->osFormat);
      72             : 
      73         117 :     argParser->add_creation_options_argument(psOptions->aosCreateOptions);
      74             : 
      75         117 :     auto &group = argParser->add_mutually_exclusive_group();
      76         117 :     group.add_argument("-array")
      77         234 :         .metavar("<array_spec>")
      78         117 :         .append()
      79         117 :         .store_into(psOptions->aosArraySpec)
      80             :         .help(_(
      81         117 :             "Select a single array instead of converting the whole dataset."));
      82             : 
      83         117 :     argParser->add_argument("-arrayoption")
      84         234 :         .metavar("<NAME>=<VALUE>")
      85         117 :         .append()
      86           1 :         .action([psOptions](const std::string &s)
      87         118 :                 { psOptions->aosArrayOptions.AddString(s.c_str()); })
      88             :         .help(_("Option passed to GDALGroup::GetMDArrayNames() to filter "
      89         117 :                 "arrays."));
      90             : 
      91         117 :     group.add_argument("-group")
      92         234 :         .metavar("<group_spec>")
      93         117 :         .append()
      94         117 :         .store_into(psOptions->aosGroup)
      95             :         .help(_(
      96         117 :             "Select a single group instead of converting the whole dataset."));
      97             : 
      98             :     // Note: this is mutually exclusive with "view" option in -array
      99         117 :     argParser->add_argument("-subset")
     100         234 :         .metavar("<subset_spec>")
     101         117 :         .append()
     102         117 :         .store_into(psOptions->aosSubset)
     103         117 :         .help(_("Select a subset of the data."));
     104             : 
     105             :     // Note: this is mutually exclusive with "view" option in -array
     106         117 :     argParser->add_argument("-scaleaxes")
     107         234 :         .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         119 :             })
     118             :         .help(
     119         117 :             _("Applies a integral scale factor to one or several dimensions."));
     120             : 
     121         117 :     argParser->add_argument("-strict")
     122         117 :         .flag()
     123         117 :         .store_into(psOptions->bStrict)
     124         117 :         .help(_("Turn warnings into failures."));
     125             : 
     126             :     // Undocumented option used by gdal mdim convert
     127         117 :     argParser->add_argument("--overwrite")
     128         117 :         .store_into(psOptions->bOverwrite)
     129         117 :         .hidden();
     130             : 
     131             :     // Undocumented option used by gdal mdim convert
     132         117 :     argParser->add_argument("--no-overwrite")
     133         117 :         .store_into(psOptions->bNoOverwrite)
     134         117 :         .hidden();
     135             : 
     136         117 :     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         117 :     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 valus 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         147 : 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         281 :     if (!STARTS_WITH(arraySpec.c_str(), "name=") &&
     731         134 :         !STARTS_WITH(arraySpec.c_str(), "band="))
     732             :     {
     733         133 :         srcName = arraySpec;
     734         133 :         dstName = arraySpec;
     735         133 :         auto pos = dstName.rfind('/');
     736         133 :         if (pos != std::string::npos)
     737          22 :             dstName = dstName.substr(pos + 1);
     738         133 :         return true;
     739             :     }
     740             : 
     741          28 :     std::vector<std::string> tokens;
     742          28 :     std::string curToken;
     743          14 :     bool bInArray = false;
     744         588 :     for (size_t i = 0; i < arraySpec.size(); ++i)
     745             :     {
     746         574 :         if (!bInArray && arraySpec[i] == ',')
     747             :         {
     748          18 :             tokens.emplace_back(std::move(curToken));
     749          18 :             curToken = std::string();
     750             :         }
     751             :         else
     752             :         {
     753         556 :             if (arraySpec[i] == '[')
     754             :             {
     755           6 :                 bInArray = true;
     756             :             }
     757         550 :             else if (arraySpec[i] == ']')
     758             :             {
     759           6 :                 bInArray = false;
     760             :             }
     761         556 :             curToken += arraySpec[i];
     762             :         }
     763             :     }
     764          14 :     if (!curToken.empty())
     765             :     {
     766          14 :         tokens.emplace_back(std::move(curToken));
     767             :     }
     768          44 :     for (const auto &token : tokens)
     769             :     {
     770          32 :         if (STARTS_WITH(token.c_str(), "name="))
     771             :         {
     772          13 :             srcName = token.substr(strlen("name="));
     773          13 :             if (dstName.empty())
     774          13 :                 dstName = srcName;
     775             :         }
     776          19 :         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          18 :         else if (STARTS_WITH(token.c_str(), "dstname="))
     783             :         {
     784          10 :             dstName = token.substr(strlen("dstname="));
     785             :         }
     786           8 :         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           6 :         else if (STARTS_WITH(token.c_str(), "view="))
     818             :         {
     819           4 :             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          12 :     return true;
     846             : }
     847             : 
     848             : /************************************************************************/
     849             : /*                           TranslateArray()                           */
     850             : /************************************************************************/
     851             : 
     852         144 : 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<GDALGroup> &poDstRootGroup,
     859             :     std::shared_ptr<GDALGroup> &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         288 :     std::string srcArrayName;
     865         288 :     std::string dstArrayName;
     866         144 :     int band = -1;
     867         288 :     std::vector<int> anTransposedAxis;
     868         288 :     std::string viewExpr;
     869         144 :     bool bResampled = false;
     870         288 :     GDALExtendedDataType outputType(GDALExtendedDataType::Create(GDT_Unknown));
     871         144 :     if (!ParseArraySpec(arraySpec, srcArrayName, dstArrayName, band,
     872             :                         anTransposedAxis, viewExpr, outputType, bResampled))
     873             :     {
     874           2 :         return false;
     875             :     }
     876             : 
     877         142 :     std::shared_ptr<GDALMDArray> srcArray;
     878         142 :     bool bSrcArrayAccessibleThroughSrcGroup = true;
     879         142 :     if (poSrcRootGroup && poSrcGroup)
     880             :     {
     881         141 :         if (!srcArrayName.empty() && srcArrayName[0] == '/')
     882          28 :             srcArray = poSrcRootGroup->OpenMDArrayFromFullname(srcArrayName);
     883             :         else
     884         113 :             srcArray = poSrcGroup->OpenMDArray(srcArrayName);
     885         141 :         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             :     else
     901             :     {
     902           1 :         auto poBand = poSrcDS->GetRasterBand(band);
     903           1 :         if (!poBand)
     904           0 :             return false;
     905           1 :         srcArray = poBand->AsMDArray();
     906             :     }
     907             : 
     908         282 :     auto tmpArray = srcArray;
     909             : 
     910         141 :     if (bResampled)
     911             :     {
     912             :         auto newTmpArray =
     913           3 :             tmpArray->GetResampled(std::vector<std::shared_ptr<GDALDimension>>(
     914           1 :                                        tmpArray->GetDimensionCount()),
     915           2 :                                    GRIORA_NearestNeighbour, nullptr, nullptr);
     916           1 :         if (!newTmpArray)
     917           0 :             return false;
     918           1 :         tmpArray = std::move(newTmpArray);
     919             :     }
     920             : 
     921         141 :     if (!anTransposedAxis.empty())
     922             :     {
     923           1 :         auto newTmpArray = tmpArray->Transpose(anTransposedAxis);
     924           1 :         if (!newTmpArray)
     925           0 :             return false;
     926           1 :         tmpArray = std::move(newTmpArray);
     927             :     }
     928         141 :     const auto &srcArrayDims(tmpArray->GetDimensions());
     929             :     std::map<std::shared_ptr<GDALDimension>, std::shared_ptr<GDALDimension>>
     930         282 :         oMapSubsetDimToSrcDim;
     931             : 
     932         282 :     std::vector<GDALMDArray::ViewSpec> viewSpecs;
     933         141 :     if (!viewExpr.empty())
     934             :     {
     935           4 :         if (!psOptions->aosSubset.empty() || !psOptions->aosScaleFactor.empty())
     936             :         {
     937           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     938             :                      "View specification not supported when used together "
     939             :                      "with subset and/or scalefactor options");
     940           0 :             return false;
     941             :         }
     942           4 :         auto newTmpArray = tmpArray->GetView(viewExpr, true, viewSpecs);
     943           4 :         if (!newTmpArray)
     944           0 :             return false;
     945           4 :         tmpArray = std::move(newTmpArray);
     946             :     }
     947         184 :     else if (!psOptions->aosSubset.empty() ||
     948          47 :              !psOptions->aosScaleFactor.empty())
     949             :     {
     950          98 :         bool bHasModifiedDim = false;
     951          98 :         viewExpr = '[';
     952         194 :         for (size_t i = 0; i < srcArrayDims.size(); ++i)
     953             :         {
     954         112 :             const auto &srcDim(srcArrayDims[i]);
     955             :             const auto poDimDesc =
     956         112 :                 GetDimensionDesc(oDimRemapper, psOptions, srcDim);
     957         112 :             if (poDimDesc == nullptr)
     958          16 :                 return false;
     959          96 :             if (i > 0)
     960          14 :                 viewExpr += ',';
     961          76 :             if (!poDimDesc->bSlice && poDimDesc->nStartIdx == 0 &&
     962         172 :                 poDimDesc->nStep == 1 && poDimDesc->nSize == srcDim->GetSize())
     963             :             {
     964          28 :                 viewExpr += ":";
     965             :             }
     966             :             else
     967             :             {
     968          68 :                 bHasModifiedDim = true;
     969             :                 viewExpr += CPLSPrintf(
     970          68 :                     CPL_FRMT_GUIB, static_cast<GUInt64>(poDimDesc->nStartIdx));
     971          68 :                 if (!poDimDesc->bSlice)
     972             :                 {
     973          48 :                     viewExpr += ':';
     974             :                     viewExpr +=
     975             :                         CPLSPrintf(CPL_FRMT_GUIB,
     976          48 :                                    static_cast<GUInt64>(poDimDesc->nStartIdx +
     977          48 :                                                         poDimDesc->nSize *
     978          48 :                                                             poDimDesc->nStep));
     979          48 :                     viewExpr += ':';
     980             :                     viewExpr += CPLSPrintf(
     981          48 :                         CPL_FRMT_GUIB, static_cast<GUInt64>(poDimDesc->nStep));
     982             :                 }
     983             :             }
     984             :         }
     985          82 :         viewExpr += ']';
     986          82 :         if (bHasModifiedDim)
     987             :         {
     988          66 :             auto tmpArrayNew = tmpArray->GetView(viewExpr, false, viewSpecs);
     989          66 :             if (!tmpArrayNew)
     990           0 :                 return false;
     991          66 :             tmpArray = std::move(tmpArrayNew);
     992          66 :             size_t j = 0;
     993          66 :             const auto &tmpArrayDims(tmpArray->GetDimensions());
     994         146 :             for (size_t i = 0; i < srcArrayDims.size(); ++i)
     995             :             {
     996          80 :                 const auto &srcDim(srcArrayDims[i]);
     997             :                 const auto poDimDesc =
     998          80 :                     GetDimensionDesc(oDimRemapper, psOptions, srcDim);
     999          80 :                 if (poDimDesc == nullptr)
    1000           0 :                     return false;
    1001          80 :                 if (poDimDesc->bSlice)
    1002          20 :                     continue;
    1003          60 :                 CPLAssert(j < tmpArrayDims.size());
    1004          60 :                 oMapSubsetDimToSrcDim[tmpArrayDims[j]] = srcDim;
    1005          60 :                 j++;
    1006             :             }
    1007             :         }
    1008             :         else
    1009             :         {
    1010          16 :             viewExpr.clear();
    1011             :         }
    1012             :     }
    1013             : 
    1014         125 :     int idxSliceSpec = -1;
    1015         195 :     for (size_t i = 0; i < viewSpecs.size(); ++i)
    1016             :     {
    1017          70 :         if (viewSpecs[i].m_osFieldName.empty())
    1018             :         {
    1019          70 :             if (idxSliceSpec >= 0)
    1020             :             {
    1021           0 :                 idxSliceSpec = -1;
    1022           0 :                 break;
    1023             :             }
    1024             :             else
    1025             :             {
    1026          70 :                 idxSliceSpec = static_cast<int>(i);
    1027             :             }
    1028             :         }
    1029             :     }
    1030             : 
    1031             :     // Map source dimensions to target dimensions
    1032         250 :     std::vector<std::shared_ptr<GDALDimension>> dstArrayDims;
    1033         125 :     const auto &tmpArrayDims(tmpArray->GetDimensions());
    1034         265 :     for (size_t i = 0; i < tmpArrayDims.size(); ++i)
    1035             :     {
    1036         140 :         const auto &srcDim(tmpArrayDims[i]);
    1037         140 :         std::string srcDimFullName(srcDim->GetFullName());
    1038             : 
    1039           0 :         std::shared_ptr<GDALDimension> dstDim;
    1040             :         {
    1041         280 :             CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
    1042         140 :             if (!srcDimFullName.empty() && srcDimFullName[0] == '/')
    1043             :             {
    1044             :                 dstDim =
    1045          81 :                     poDstRootGroup->OpenDimensionFromFullname(srcDimFullName);
    1046             :             }
    1047             :         }
    1048         140 :         if (dstDim)
    1049             :         {
    1050          45 :             dstArrayDims.emplace_back(dstDim);
    1051          45 :             continue;
    1052             :         }
    1053             : 
    1054          95 :         auto oIter = mapSrcToDstDims.find(srcDimFullName);
    1055          95 :         if (oIter != mapSrcToDstDims.end())
    1056             :         {
    1057           2 :             dstArrayDims.emplace_back(oIter->second);
    1058           2 :             continue;
    1059             :         }
    1060          93 :         auto oIterRealSrcDim = oMapSubsetDimToSrcDim.find(srcDim);
    1061          93 :         if (oIterRealSrcDim != oMapSubsetDimToSrcDim.end())
    1062             :         {
    1063          52 :             srcDimFullName = oIterRealSrcDim->second->GetFullName();
    1064          52 :             oIter = mapSrcToDstDims.find(srcDimFullName);
    1065          52 :             if (oIter != mapSrcToDstDims.end())
    1066             :             {
    1067          10 :                 dstArrayDims.emplace_back(oIter->second);
    1068          10 :                 continue;
    1069             :             }
    1070             :         }
    1071             : 
    1072          83 :         const auto nDimSize = srcDim->GetSize();
    1073          83 :         std::string newDimNameFullName(srcDimFullName);
    1074          83 :         std::string newDimName(srcDim->GetName());
    1075          83 :         int nIncr = 2;
    1076          83 :         std::string osDstGroupFullName(poDstGroup->GetFullName());
    1077          83 :         if (osDstGroupFullName == "/")
    1078          81 :             osDstGroupFullName.clear();
    1079         166 :         auto oIter2 = mapDstDimFullNames.find(osDstGroupFullName + '/' +
    1080         166 :                                               srcDim->GetName());
    1081          88 :         while (oIter2 != mapDstDimFullNames.end() &&
    1082           4 :                oIter2->second->GetSize() != nDimSize)
    1083             :         {
    1084           1 :             newDimName = srcDim->GetName() + CPLSPrintf("_%d", nIncr);
    1085           2 :             newDimNameFullName = osDstGroupFullName + '/' + srcDim->GetName() +
    1086           1 :                                  CPLSPrintf("_%d", nIncr);
    1087           1 :             nIncr++;
    1088           1 :             oIter2 = mapDstDimFullNames.find(newDimNameFullName);
    1089             :         }
    1090          86 :         if (oIter2 != mapDstDimFullNames.end() &&
    1091           3 :             oIter2->second->GetSize() == nDimSize)
    1092             :         {
    1093           3 :             dstArrayDims.emplace_back(oIter2->second);
    1094           3 :             continue;
    1095             :         }
    1096             : 
    1097         240 :         dstDim = poDstGroup->CreateDimension(newDimName, srcDim->GetType(),
    1098         160 :                                              srcDim->GetDirection(), nDimSize);
    1099          80 :         if (!dstDim)
    1100           0 :             return false;
    1101          80 :         if (!srcDimFullName.empty() && srcDimFullName[0] == '/')
    1102             :         {
    1103          72 :             mapSrcToDstDims[srcDimFullName] = dstDim;
    1104             :         }
    1105          80 :         mapDstDimFullNames[dstDim->GetFullName()] = dstDim;
    1106          80 :         dstArrayDims.emplace_back(dstDim);
    1107             : 
    1108           0 :         std::shared_ptr<GDALMDArray> srcIndexVar;
    1109          80 :         GDALMDArray::Range range;
    1110          80 :         range.m_nStartIdx = 0;
    1111          80 :         range.m_nIncr = 1;
    1112          80 :         std::string indexingVarSpec;
    1113          80 :         if (idxSliceSpec >= 0)
    1114             :         {
    1115          49 :             const auto &viewSpec(viewSpecs[idxSliceSpec]);
    1116          49 :             auto iParentDim = viewSpec.m_mapDimIdxToParentDimIdx[i];
    1117          48 :             if (iParentDim != static_cast<size_t>(-1) &&
    1118             :                 (srcIndexVar =
    1119          97 :                      srcArrayDims[iParentDim]->GetIndexingVariable()) !=
    1120          46 :                     nullptr &&
    1121         143 :                 srcIndexVar->GetDimensionCount() == 1 &&
    1122          46 :                 srcIndexVar->GetFullName() != srcArray->GetFullName())
    1123             :             {
    1124          10 :                 CPLAssert(iParentDim < viewSpec.m_parentRanges.size());
    1125          10 :                 range = viewSpec.m_parentRanges[iParentDim];
    1126          10 :                 indexingVarSpec = "name=" + srcIndexVar->GetFullName();
    1127          10 :                 indexingVarSpec += ",dstname=" + newDimName;
    1128          20 :                 if (psOptions->aosSubset.empty() &&
    1129          10 :                     psOptions->aosScaleFactor.empty())
    1130             :                 {
    1131           7 :                     if (range.m_nStartIdx != 0 || range.m_nIncr != 1 ||
    1132           3 :                         srcArrayDims[iParentDim]->GetSize() !=
    1133           3 :                             srcDim->GetSize())
    1134             :                     {
    1135           1 :                         indexingVarSpec += ",view=[";
    1136           2 :                         if (range.m_nIncr > 0 ||
    1137           1 :                             range.m_nStartIdx != srcDim->GetSize() - 1)
    1138             :                         {
    1139             :                             indexingVarSpec +=
    1140           0 :                                 CPLSPrintf(CPL_FRMT_GUIB, range.m_nStartIdx);
    1141             :                         }
    1142           1 :                         indexingVarSpec += ':';
    1143           1 :                         if (range.m_nIncr > 0)
    1144             :                         {
    1145             :                             const auto nEndIdx =
    1146           0 :                                 range.m_nStartIdx +
    1147           0 :                                 range.m_nIncr * srcDim->GetSize();
    1148             :                             indexingVarSpec +=
    1149           0 :                                 CPLSPrintf(CPL_FRMT_GUIB, nEndIdx);
    1150             :                         }
    1151           2 :                         else if (range.m_nStartIdx >
    1152           1 :                                  -range.m_nIncr * srcDim->GetSize())
    1153             :                         {
    1154             :                             const auto nEndIdx =
    1155           0 :                                 range.m_nStartIdx +
    1156           0 :                                 range.m_nIncr * srcDim->GetSize();
    1157             :                             indexingVarSpec +=
    1158           0 :                                 CPLSPrintf(CPL_FRMT_GUIB, nEndIdx - 1);
    1159             :                         }
    1160           1 :                         indexingVarSpec += ':';
    1161             :                         indexingVarSpec +=
    1162           1 :                             CPLSPrintf(CPL_FRMT_GIB, range.m_nIncr);
    1163           1 :                         indexingVarSpec += ']';
    1164             :                     }
    1165             :                 }
    1166             :             }
    1167             :         }
    1168             :         else
    1169             :         {
    1170          31 :             srcIndexVar = srcDim->GetIndexingVariable();
    1171          31 :             if (srcIndexVar)
    1172             :             {
    1173          29 :                 indexingVarSpec = srcIndexVar->GetFullName();
    1174             :             }
    1175             :         }
    1176         119 :         if (srcIndexVar && !indexingVarSpec.empty() &&
    1177          39 :             srcIndexVar->GetFullName() != srcArray->GetFullName())
    1178             :         {
    1179          30 :             if (poSrcRootGroup)
    1180             :             {
    1181          28 :                 if (!TranslateArray(oDimRemapper, srcIndexVar, indexingVarSpec,
    1182             :                                     poSrcRootGroup, poSrcGroup, poDstRootGroup,
    1183             :                                     poDstGroup, poSrcDS, mapSrcToDstDims,
    1184             :                                     mapDstDimFullNames, psOptions))
    1185             :                 {
    1186           0 :                     return false;
    1187             :                 }
    1188             :             }
    1189             :             else
    1190             :             {
    1191             :                 double adfGT[6];
    1192           2 :                 if (poSrcDS->GetGeoTransform(adfGT) == CE_None &&
    1193           2 :                     adfGT[2] == 0.0 && adfGT[4] == 0.0)
    1194             :                 {
    1195             :                     auto var = std::dynamic_pointer_cast<VRTMDArray>(
    1196           8 :                         poDstGroup->CreateMDArray(
    1197             :                             newDimName, {dstDim},
    1198           8 :                             GDALExtendedDataType::Create(GDT_Float64)));
    1199           2 :                     if (var)
    1200             :                     {
    1201             :                         const double dfStart =
    1202           2 :                             srcIndexVar->GetName() == "X"
    1203           2 :                                 ? adfGT[0] +
    1204           1 :                                       (range.m_nStartIdx + 0.5) * adfGT[1]
    1205           1 :                                 : adfGT[3] +
    1206           1 :                                       (range.m_nStartIdx + 0.5) * adfGT[5];
    1207             :                         const double dfIncr =
    1208           2 :                             (srcIndexVar->GetName() == "X" ? adfGT[1]
    1209           2 :                                                            : adfGT[5]) *
    1210           2 :                             range.m_nIncr;
    1211             :                         std::unique_ptr<VRTMDArraySourceRegularlySpaced>
    1212             :                             poSource(new VRTMDArraySourceRegularlySpaced(
    1213           2 :                                 dfStart, dfIncr));
    1214           2 :                         var->AddSource(std::move(poSource));
    1215             :                     }
    1216             :                 }
    1217             :             }
    1218             : 
    1219          60 :             CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
    1220          60 :             auto poDstIndexingVar(poDstGroup->OpenMDArray(newDimName));
    1221          30 :             if (poDstIndexingVar)
    1222          29 :                 dstDim->SetIndexingVariable(std::move(poDstIndexingVar));
    1223             :         }
    1224             :     }
    1225         250 :     if (outputType.GetClass() == GEDTC_NUMERIC &&
    1226         125 :         outputType.GetNumericDataType() == GDT_Unknown)
    1227             :     {
    1228         125 :         outputType = GDALExtendedDataType(tmpArray->GetDataType());
    1229             :     }
    1230             :     auto dstArray =
    1231         250 :         poDstGroup->CreateMDArray(dstArrayName, dstArrayDims, outputType);
    1232         250 :     auto dstArrayVRT = std::dynamic_pointer_cast<VRTMDArray>(dstArray);
    1233         125 :     if (!dstArrayVRT)
    1234           0 :         return false;
    1235             : 
    1236         125 :     GUInt64 nCurCost = 0;
    1237         125 :     dstArray->CopyFromAllExceptValues(srcArray.get(), false, nCurCost, 0,
    1238             :                                       nullptr, nullptr);
    1239         125 :     if (bResampled)
    1240           1 :         dstArray->SetSpatialRef(tmpArray->GetSpatialRef().get());
    1241             : 
    1242         125 :     if (idxSliceSpec >= 0)
    1243             :     {
    1244         140 :         std::set<size_t> oSetParentDimIdxNotInArray;
    1245         158 :         for (size_t i = 0; i < srcArrayDims.size(); ++i)
    1246             :         {
    1247          88 :             oSetParentDimIdxNotInArray.insert(i);
    1248             :         }
    1249          70 :         const auto &viewSpec(viewSpecs[idxSliceSpec]);
    1250         138 :         for (size_t i = 0; i < tmpArrayDims.size(); ++i)
    1251             :         {
    1252          68 :             auto iParentDim = viewSpec.m_mapDimIdxToParentDimIdx[i];
    1253          68 :             if (iParentDim != static_cast<size_t>(-1))
    1254             :             {
    1255          67 :                 oSetParentDimIdxNotInArray.erase(iParentDim);
    1256             :             }
    1257             :         }
    1258          91 :         for (const auto parentDimIdx : oSetParentDimIdxNotInArray)
    1259             :         {
    1260          21 :             const auto &srcDim(srcArrayDims[parentDimIdx]);
    1261             :             const auto nStartIdx =
    1262          21 :                 viewSpec.m_parentRanges[parentDimIdx].m_nStartIdx;
    1263          21 :             if (nStartIdx < static_cast<GUInt64>(INT_MAX))
    1264             :             {
    1265          21 :                 auto dstAttr = dstArray->CreateAttribute(
    1266          42 :                     "DIM_" + srcDim->GetName() + "_INDEX", {},
    1267          84 :                     GDALExtendedDataType::Create(GDT_Int32));
    1268          21 :                 dstAttr->Write(static_cast<int>(nStartIdx));
    1269             :             }
    1270             :             else
    1271             :             {
    1272           0 :                 auto dstAttr = dstArray->CreateAttribute(
    1273           0 :                     "DIM_" + srcDim->GetName() + "_INDEX", {},
    1274           0 :                     GDALExtendedDataType::CreateString());
    1275           0 :                 dstAttr->Write(CPLSPrintf(CPL_FRMT_GUIB,
    1276             :                                           static_cast<GUIntBig>(nStartIdx)));
    1277             :             }
    1278             : 
    1279          42 :             auto srcIndexVar(srcDim->GetIndexingVariable());
    1280          21 :             if (srcIndexVar && srcIndexVar->GetDimensionCount() == 1)
    1281             :             {
    1282          21 :                 const auto &dt(srcIndexVar->GetDataType());
    1283          42 :                 std::vector<GByte> abyTmp(dt.GetSize());
    1284          21 :                 const size_t nCount = 1;
    1285          42 :                 if (srcIndexVar->Read(&nStartIdx, &nCount, nullptr, nullptr, dt,
    1286          21 :                                       &abyTmp[0], nullptr, 0))
    1287             :                 {
    1288             :                     {
    1289          21 :                         auto dstAttr = dstArray->CreateAttribute(
    1290          63 :                             "DIM_" + srcDim->GetName() + "_VALUE", {}, dt);
    1291          21 :                         dstAttr->Write(abyTmp.data(), abyTmp.size());
    1292          21 :                         dt.FreeDynamicMemory(&abyTmp[0]);
    1293             :                     }
    1294             : 
    1295          21 :                     const auto &unit(srcIndexVar->GetUnit());
    1296          21 :                     if (!unit.empty())
    1297             :                     {
    1298           0 :                         auto dstAttr = dstArray->CreateAttribute(
    1299           0 :                             "DIM_" + srcDim->GetName() + "_UNIT", {},
    1300           0 :                             GDALExtendedDataType::CreateString());
    1301           0 :                         dstAttr->Write(unit.c_str());
    1302             :                     }
    1303             :                 }
    1304             :             }
    1305             :         }
    1306             :     }
    1307             : 
    1308         125 :     double dfStart = 0.0;
    1309         125 :     double dfIncrement = 0.0;
    1310         127 :     if (!bSrcArrayAccessibleThroughSrcGroup &&
    1311           2 :         tmpArray->IsRegularlySpaced(dfStart, dfIncrement))
    1312             :     {
    1313             :         auto poSource = std::make_unique<VRTMDArraySourceRegularlySpaced>(
    1314           2 :             dfStart, dfIncrement);
    1315           2 :         dstArrayVRT->AddSource(std::move(poSource));
    1316             :     }
    1317             :     else
    1318             :     {
    1319         123 :         const auto dimCount(tmpArray->GetDimensionCount());
    1320         246 :         std::vector<GUInt64> anSrcOffset(dimCount);
    1321         246 :         std::vector<GUInt64> anCount(dimCount);
    1322         261 :         for (size_t i = 0; i < dimCount; ++i)
    1323             :         {
    1324         138 :             anCount[i] = tmpArrayDims[i]->GetSize();
    1325             :         }
    1326         246 :         std::vector<GUInt64> anStep(dimCount, 1);
    1327         246 :         std::vector<GUInt64> anDstOffset(dimCount);
    1328             :         std::unique_ptr<VRTMDArraySourceFromArray> poSource(
    1329             :             new VRTMDArraySourceFromArray(
    1330         123 :                 dstArrayVRT.get(), false, false, poSrcDS->GetDescription(),
    1331         246 :                 band < 0 ? srcArray->GetFullName() : std::string(),
    1332         247 :                 band >= 1 ? CPLSPrintf("%d", band) : std::string(),
    1333         123 :                 std::move(anTransposedAxis),
    1334             :                 bResampled
    1335         368 :                     ? (viewExpr.empty()
    1336             :                            ? std::string("resample=true")
    1337         123 :                            : std::string("resample=true,").append(viewExpr))
    1338         122 :                     : std::move(viewExpr),
    1339         123 :                 std::move(anSrcOffset), std::move(anCount), std::move(anStep),
    1340         369 :                 std::move(anDstOffset)));
    1341         123 :         dstArrayVRT->AddSource(std::move(poSource));
    1342             :     }
    1343             : 
    1344         125 :     return true;
    1345             : }
    1346             : 
    1347             : /************************************************************************/
    1348             : /*                               GetGroup()                             */
    1349             : /************************************************************************/
    1350             : 
    1351             : static std::shared_ptr<GDALGroup>
    1352           6 : GetGroup(const std::shared_ptr<GDALGroup> &poRootGroup,
    1353             :          const std::string &fullName)
    1354             : {
    1355          12 :     auto poCurGroup = poRootGroup;
    1356          12 :     CPLStringList aosTokens(CSLTokenizeString2(fullName.c_str(), "/", 0));
    1357          10 :     for (int i = 0; i < aosTokens.size(); i++)
    1358             :     {
    1359          10 :         auto poCurGroupNew = poCurGroup->OpenGroup(aosTokens[i], nullptr);
    1360           5 :         if (!poCurGroupNew)
    1361             :         {
    1362           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot find group %s",
    1363             :                      aosTokens[i]);
    1364           1 :             return nullptr;
    1365             :         }
    1366           4 :         poCurGroup = std::move(poCurGroupNew);
    1367             :     }
    1368           5 :     return poCurGroup;
    1369             : }
    1370             : 
    1371             : /************************************************************************/
    1372             : /*                                CopyGroup()                           */
    1373             : /************************************************************************/
    1374             : 
    1375          17 : static bool CopyGroup(
    1376             :     DimensionRemapper &oDimRemapper,
    1377             :     const std::shared_ptr<GDALGroup> &poDstRootGroup,
    1378             :     std::shared_ptr<GDALGroup> &poDstGroup,
    1379             :     const std::shared_ptr<GDALGroup> &poSrcRootGroup,
    1380             :     const std::shared_ptr<GDALGroup> &poSrcGroup, GDALDataset *poSrcDS,
    1381             :     std::map<std::string, std::shared_ptr<GDALDimension>> &mapSrcToDstDims,
    1382             :     std::map<std::string, std::shared_ptr<GDALDimension>> &mapDstDimFullNames,
    1383             :     const GDALMultiDimTranslateOptions *psOptions, bool bRecursive)
    1384             : {
    1385          34 :     const auto srcDims = poSrcGroup->GetDimensions();
    1386          34 :     std::map<std::string, std::string> mapSrcVariableNameToIndexedDimName;
    1387          29 :     for (const auto &dim : srcDims)
    1388             :     {
    1389          15 :         const auto poDimDesc = GetDimensionDesc(oDimRemapper, psOptions, dim);
    1390          15 :         if (poDimDesc == nullptr)
    1391           3 :             return false;
    1392          12 :         if (poDimDesc->bSlice)
    1393           2 :             continue;
    1394             :         auto dstDim =
    1395          10 :             poDstGroup->CreateDimension(dim->GetName(), dim->GetType(),
    1396          10 :                                         dim->GetDirection(), poDimDesc->nSize);
    1397          10 :         if (!dstDim)
    1398           0 :             return false;
    1399          10 :         mapSrcToDstDims[dim->GetFullName()] = dstDim;
    1400          10 :         mapDstDimFullNames[dstDim->GetFullName()] = dstDim;
    1401          20 :         auto poIndexingVarSrc(dim->GetIndexingVariable());
    1402          10 :         if (poIndexingVarSrc)
    1403             :         {
    1404          10 :             mapSrcVariableNameToIndexedDimName[poIndexingVarSrc->GetName()] =
    1405          20 :                 dim->GetFullName();
    1406             :         }
    1407             :     }
    1408             : 
    1409          14 :     if (!(poSrcGroup == poSrcRootGroup && psOptions->aosGroup.empty()))
    1410             :     {
    1411          11 :         auto attrs = poSrcGroup->GetAttributes();
    1412          15 :         for (const auto &attr : attrs)
    1413             :         {
    1414           4 :             auto dstAttr = poDstGroup->CreateAttribute(
    1415           4 :                 attr->GetName(), attr->GetDimensionsSize(),
    1416           8 :                 attr->GetDataType());
    1417           4 :             if (!dstAttr)
    1418             :             {
    1419           0 :                 if (!psOptions->bStrict)
    1420           0 :                     continue;
    1421           0 :                 return false;
    1422             :             }
    1423           4 :             auto raw(attr->ReadAsRaw());
    1424           4 :             if (!dstAttr->Write(raw.data(), raw.size()) && !psOptions->bStrict)
    1425           0 :                 return false;
    1426             :         }
    1427             :     }
    1428             : 
    1429             :     auto arrayNames =
    1430          28 :         poSrcGroup->GetMDArrayNames(psOptions->aosArrayOptions.List());
    1431          40 :     for (const auto &name : arrayNames)
    1432             :     {
    1433          26 :         if (!TranslateArray(oDimRemapper, nullptr, name, poSrcRootGroup,
    1434             :                             poSrcGroup, poDstRootGroup, poDstGroup, poSrcDS,
    1435             :                             mapSrcToDstDims, mapDstDimFullNames, psOptions))
    1436             :         {
    1437           0 :             return false;
    1438             :         }
    1439             : 
    1440             :         // If this array is the indexing variable of a dimension, link them
    1441             :         // together.
    1442          52 :         auto srcArray = poSrcGroup->OpenMDArray(name);
    1443          26 :         CPLAssert(srcArray);
    1444          52 :         auto dstArray = poDstGroup->OpenMDArray(name);
    1445          26 :         CPLAssert(dstArray);
    1446             :         auto oIterDimName =
    1447          26 :             mapSrcVariableNameToIndexedDimName.find(srcArray->GetName());
    1448          26 :         if (oIterDimName != mapSrcVariableNameToIndexedDimName.end())
    1449             :         {
    1450             :             auto oCorrespondingDimIter =
    1451          10 :                 mapSrcToDstDims.find(oIterDimName->second);
    1452          10 :             if (oCorrespondingDimIter != mapSrcToDstDims.end())
    1453             :             {
    1454          10 :                 CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
    1455          20 :                 oCorrespondingDimIter->second->SetIndexingVariable(
    1456          10 :                     std::move(dstArray));
    1457             :             }
    1458             :         }
    1459             :     }
    1460             : 
    1461          14 :     if (bRecursive)
    1462             :     {
    1463          14 :         auto groupNames = poSrcGroup->GetGroupNames();
    1464          20 :         for (const auto &name : groupNames)
    1465             :         {
    1466           6 :             auto srcSubGroup = poSrcGroup->OpenGroup(name);
    1467           6 :             if (!srcSubGroup)
    1468             :             {
    1469           0 :                 return false;
    1470             :             }
    1471           6 :             auto dstSubGroup = poDstGroup->CreateGroup(name);
    1472           6 :             if (!dstSubGroup)
    1473             :             {
    1474           0 :                 return false;
    1475             :             }
    1476           6 :             if (!CopyGroup(oDimRemapper, poDstRootGroup, dstSubGroup,
    1477             :                            poSrcRootGroup, srcSubGroup, poSrcDS,
    1478             :                            mapSrcToDstDims, mapDstDimFullNames, psOptions,
    1479             :                            true))
    1480             :             {
    1481           0 :                 return false;
    1482             :             }
    1483             :         }
    1484             :     }
    1485          14 :     return true;
    1486             : }
    1487             : 
    1488             : /************************************************************************/
    1489             : /*                           ParseGroupSpec()                           */
    1490             : /************************************************************************/
    1491             : 
    1492             : // foo
    1493             : // name=foo,dstname=bar,recursive=no
    1494           7 : static bool ParseGroupSpec(const std::string &groupSpec, std::string &srcName,
    1495             :                            std::string &dstName, bool &bRecursive)
    1496             : {
    1497           7 :     bRecursive = true;
    1498           7 :     if (!STARTS_WITH(groupSpec.c_str(), "name="))
    1499             :     {
    1500           5 :         srcName = groupSpec;
    1501           5 :         return true;
    1502             :     }
    1503             : 
    1504           4 :     CPLStringList aosTokens(CSLTokenizeString2(groupSpec.c_str(), ",", 0));
    1505           5 :     for (int i = 0; i < aosTokens.size(); i++)
    1506             :     {
    1507           4 :         const std::string token(aosTokens[i]);
    1508           4 :         if (STARTS_WITH(token.c_str(), "name="))
    1509             :         {
    1510           2 :             srcName = token.substr(strlen("name="));
    1511             :         }
    1512           2 :         else if (STARTS_WITH(token.c_str(), "dstname="))
    1513             :         {
    1514           1 :             dstName = token.substr(strlen("dstname="));
    1515             :         }
    1516           1 :         else if (token == "recursive=no")
    1517             :         {
    1518           0 :             bRecursive = false;
    1519             :         }
    1520             :         else
    1521             :         {
    1522           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1523             :                      "Unexpected group specification part: %s", token.c_str());
    1524           1 :             return false;
    1525             :         }
    1526             :     }
    1527           1 :     return true;
    1528             : }
    1529             : 
    1530             : /************************************************************************/
    1531             : /*                           TranslateInternal()                        */
    1532             : /************************************************************************/
    1533             : 
    1534         102 : static bool TranslateInternal(std::shared_ptr<GDALGroup> &poDstRootGroup,
    1535             :                               GDALDataset *poSrcDS,
    1536             :                               const GDALMultiDimTranslateOptions *psOptions)
    1537             : {
    1538             : 
    1539         204 :     auto poSrcRootGroup = poSrcDS->GetRootGroup();
    1540         102 :     if (poSrcRootGroup)
    1541             :     {
    1542         101 :         if (psOptions->aosGroup.empty())
    1543             :         {
    1544         190 :             auto attrs = poSrcRootGroup->GetAttributes();
    1545          99 :             for (const auto &attr : attrs)
    1546             :             {
    1547           4 :                 if (attr->GetName() == "Conventions")
    1548           1 :                     continue;
    1549           3 :                 auto dstAttr = poDstRootGroup->CreateAttribute(
    1550           3 :                     attr->GetName(), attr->GetDimensionsSize(),
    1551           9 :                     attr->GetDataType());
    1552           3 :                 if (dstAttr)
    1553             :                 {
    1554           6 :                     auto raw(attr->ReadAsRaw());
    1555           3 :                     dstAttr->Write(raw.data(), raw.size());
    1556             :                 }
    1557             :             }
    1558             :         }
    1559             :     }
    1560             : 
    1561         204 :     DimensionRemapper oDimRemapper;
    1562         204 :     std::map<std::string, std::shared_ptr<GDALDimension>> mapSrcToDstDims;
    1563         204 :     std::map<std::string, std::shared_ptr<GDALDimension>> mapDstDimFullNames;
    1564         102 :     if (!psOptions->aosGroup.empty())
    1565             :     {
    1566           6 :         if (poSrcRootGroup == nullptr)
    1567             :         {
    1568           0 :             CPLError(
    1569             :                 CE_Failure, CPLE_AppDefined,
    1570             :                 "No multidimensional source dataset: -group cannot be used");
    1571           0 :             return false;
    1572             :         }
    1573           6 :         if (psOptions->aosGroup.size() == 1)
    1574             :         {
    1575          10 :             std::string srcName;
    1576          10 :             std::string dstName;
    1577             :             bool bRecursive;
    1578           5 :             if (!ParseGroupSpec(psOptions->aosGroup[0], srcName, dstName,
    1579             :                                 bRecursive))
    1580           1 :                 return false;
    1581           8 :             auto poSrcGroup = GetGroup(poSrcRootGroup, srcName);
    1582           4 :             if (!poSrcGroup)
    1583           1 :                 return false;
    1584           3 :             return CopyGroup(oDimRemapper, poDstRootGroup, poDstRootGroup,
    1585             :                              poSrcRootGroup, poSrcGroup, poSrcDS,
    1586             :                              mapSrcToDstDims, mapDstDimFullNames, psOptions,
    1587           3 :                              bRecursive);
    1588             :         }
    1589             :         else
    1590             :         {
    1591           3 :             for (const auto &osGroupSpec : psOptions->aosGroup)
    1592             :             {
    1593           2 :                 std::string srcName;
    1594           2 :                 std::string dstName;
    1595             :                 bool bRecursive;
    1596           2 :                 if (!ParseGroupSpec(osGroupSpec, srcName, dstName, bRecursive))
    1597           0 :                     return false;
    1598           2 :                 auto poSrcGroup = GetGroup(poSrcRootGroup, srcName);
    1599           2 :                 if (!poSrcGroup)
    1600           0 :                     return false;
    1601           2 :                 if (dstName.empty())
    1602           1 :                     dstName = poSrcGroup->GetName();
    1603           2 :                 auto dstSubGroup = poDstRootGroup->CreateGroup(dstName);
    1604           4 :                 if (!dstSubGroup ||
    1605           2 :                     !CopyGroup(oDimRemapper, poDstRootGroup, dstSubGroup,
    1606             :                                poSrcRootGroup, poSrcGroup, poSrcDS,
    1607             :                                mapSrcToDstDims, mapDstDimFullNames, psOptions,
    1608             :                                bRecursive))
    1609             :                 {
    1610           0 :                     return false;
    1611             :                 }
    1612             :             }
    1613             :         }
    1614             :     }
    1615          96 :     else if (!psOptions->aosArraySpec.empty())
    1616             :     {
    1617         161 :         for (const auto &arraySpec : psOptions->aosArraySpec)
    1618             :         {
    1619          90 :             if (!TranslateArray(oDimRemapper, nullptr, arraySpec,
    1620             :                                 poSrcRootGroup, poSrcRootGroup, poDstRootGroup,
    1621             :                                 poDstRootGroup, poSrcDS, mapSrcToDstDims,
    1622             :                                 mapDstDimFullNames, psOptions))
    1623             :             {
    1624          19 :                 return false;
    1625             :             }
    1626             :         }
    1627             :     }
    1628             :     else
    1629             :     {
    1630           6 :         if (poSrcRootGroup == nullptr)
    1631             :         {
    1632           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1633             :                      "No multidimensional source dataset");
    1634           0 :             return false;
    1635             :         }
    1636           6 :         return CopyGroup(oDimRemapper, poDstRootGroup, poDstRootGroup,
    1637             :                          poSrcRootGroup, poSrcRootGroup, poSrcDS,
    1638           6 :                          mapSrcToDstDims, mapDstDimFullNames, psOptions, true);
    1639             :     }
    1640             : 
    1641          72 :     return true;
    1642             : }
    1643             : 
    1644             : /************************************************************************/
    1645             : /*                      CopyToNonMultiDimensionalDriver()               */
    1646             : /************************************************************************/
    1647             : 
    1648             : static GDALDatasetH
    1649           4 : CopyToNonMultiDimensionalDriver(GDALDriver *poDriver, const char *pszDest,
    1650             :                                 const std::shared_ptr<GDALGroup> &poRG,
    1651             :                                 const GDALMultiDimTranslateOptions *psOptions)
    1652             : {
    1653           4 :     std::shared_ptr<GDALMDArray> srcArray;
    1654           4 :     if (psOptions && !psOptions->aosArraySpec.empty())
    1655             :     {
    1656           3 :         if (psOptions->aosArraySpec.size() != 1)
    1657             :         {
    1658           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    1659             :                      "For output to a non-multidimensional driver, only "
    1660             :                      "one array should be specified");
    1661           0 :             return nullptr;
    1662             :         }
    1663           6 :         std::string srcArrayName;
    1664           6 :         std::string dstArrayName;
    1665           3 :         int band = -1;
    1666           6 :         std::vector<int> anTransposedAxis;
    1667           6 :         std::string viewExpr;
    1668             :         GDALExtendedDataType outputType(
    1669           3 :             GDALExtendedDataType::Create(GDT_Unknown));
    1670           3 :         bool bResampled = false;
    1671           3 :         ParseArraySpec(psOptions->aosArraySpec[0], srcArrayName, dstArrayName,
    1672             :                        band, anTransposedAxis, viewExpr, outputType,
    1673             :                        bResampled);
    1674           3 :         srcArray = poRG->OpenMDArray(dstArrayName);
    1675             :     }
    1676             :     else
    1677             :     {
    1678           1 :         auto srcArrayNames = poRG->GetMDArrayNames(
    1679           1 :             psOptions ? psOptions->aosArrayOptions.List() : nullptr);
    1680           4 :         for (const auto &srcArrayName : srcArrayNames)
    1681             :         {
    1682           4 :             auto tmpArray = poRG->OpenMDArray(srcArrayName);
    1683           4 :             if (tmpArray)
    1684             :             {
    1685           4 :                 const auto &dims(tmpArray->GetDimensions());
    1686          10 :                 if (!(dims.size() == 1 && dims[0]->GetIndexingVariable() &&
    1687           6 :                       dims[0]->GetIndexingVariable()->GetName() ==
    1688             :                           srcArrayName))
    1689             :                 {
    1690           2 :                     if (srcArray)
    1691             :                     {
    1692           1 :                         CPLError(CE_Failure, CPLE_AppDefined,
    1693             :                                  "Several arrays exist. Select one for "
    1694             :                                  "output to non-multidimensional driver");
    1695           1 :                         return nullptr;
    1696             :                     }
    1697           1 :                     srcArray = std::move(tmpArray);
    1698             :                 }
    1699             :             }
    1700             :         }
    1701             :     }
    1702           3 :     if (!srcArray)
    1703             :     {
    1704           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find source array");
    1705           0 :         return nullptr;
    1706             :     }
    1707           3 :     size_t iXDim = static_cast<size_t>(-1);
    1708           3 :     size_t iYDim = static_cast<size_t>(-1);
    1709           3 :     const auto &dims(srcArray->GetDimensions());
    1710           8 :     for (size_t i = 0; i < dims.size(); ++i)
    1711             :     {
    1712           5 :         if (dims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X)
    1713             :         {
    1714           2 :             iXDim = i;
    1715             :         }
    1716           3 :         else if (dims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y)
    1717             :         {
    1718           3 :             iYDim = i;
    1719             :         }
    1720             :     }
    1721           3 :     if (dims.size() == 1)
    1722             :     {
    1723           1 :         iXDim = 0;
    1724             :     }
    1725           2 :     else if (dims.size() >= 2 && (iXDim == static_cast<size_t>(-1) ||
    1726             :                                   iYDim == static_cast<size_t>(-1)))
    1727             :     {
    1728           0 :         iXDim = dims.size() - 1;
    1729           0 :         iYDim = dims.size() - 2;
    1730             :     }
    1731             :     std::unique_ptr<GDALDataset> poTmpSrcDS(
    1732           6 :         srcArray->AsClassicDataset(iXDim, iYDim));
    1733           3 :     if (!poTmpSrcDS)
    1734           0 :         return nullptr;
    1735           6 :     return GDALDataset::ToHandle(poDriver->CreateCopy(
    1736             :         pszDest, poTmpSrcDS.get(), false,
    1737           3 :         psOptions ? const_cast<char **>(psOptions->aosCreateOptions.List())
    1738             :                   : nullptr,
    1739             :         psOptions ? psOptions->pfnProgress : nullptr,
    1740           3 :         psOptions ? psOptions->pProgressData : nullptr));
    1741             : }
    1742             : 
    1743             : /************************************************************************/
    1744             : /*                        GDALMultiDimTranslate()                       */
    1745             : /************************************************************************/
    1746             : 
    1747             : /* clang-format off */
    1748             : /**
    1749             :  * Converts raster data between different formats.
    1750             :  *
    1751             :  * This is the equivalent of the
    1752             :  * <a href="/programs/gdalmdimtranslate.html">gdalmdimtranslate</a> utility.
    1753             :  *
    1754             :  * GDALMultiDimTranslateOptions* must be allocated and freed with
    1755             :  * GDALMultiDimTranslateOptionsNew() and GDALMultiDimTranslateOptionsFree()
    1756             :  * respectively. pszDest and hDstDS cannot be used at the same time.
    1757             :  *
    1758             :  * @param pszDest the destination dataset path or NULL.
    1759             :  * @param hDstDS the destination dataset or NULL.
    1760             :  * @param nSrcCount the number of input datasets.
    1761             :  * @param pahSrcDS the list of input datasets.
    1762             :  * @param psOptions the options struct returned by
    1763             :  * GDALMultiDimTranslateOptionsNew() or NULL.
    1764             :  * @param pbUsageError pointer to a integer output variable to store if any
    1765             :  * usage error has occurred or NULL.
    1766             :  * @return the output dataset (new dataset that must be closed using
    1767             :  * GDALClose(), or hDstDS is not NULL) or NULL in case of error.
    1768             :  *
    1769             :  * @since GDAL 3.1
    1770             :  */
    1771             : /* clang-format on */
    1772             : 
    1773             : GDALDatasetH
    1774         116 : GDALMultiDimTranslate(const char *pszDest, GDALDatasetH hDstDS, int nSrcCount,
    1775             :                       GDALDatasetH *pahSrcDS,
    1776             :                       const GDALMultiDimTranslateOptions *psOptions,
    1777             :                       int *pbUsageError)
    1778             : {
    1779         116 :     if (pbUsageError)
    1780         106 :         *pbUsageError = false;
    1781         116 :     if (nSrcCount != 1 || pahSrcDS[0] == nullptr)
    1782             :     {
    1783           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1784             :                  "Only one source dataset is supported");
    1785           0 :         if (pbUsageError)
    1786           0 :             *pbUsageError = true;
    1787           0 :         return nullptr;
    1788             :     }
    1789             : 
    1790         116 :     if (hDstDS)
    1791             :     {
    1792           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1793             :                  "Update of existing file not supported yet");
    1794           0 :         GDALClose(hDstDS);
    1795           0 :         return nullptr;
    1796             :     }
    1797             : 
    1798         348 :     CPLString osFormat(psOptions ? psOptions->osFormat : "");
    1799         116 :     if (pszDest == nullptr /* && hDstDS == nullptr */)
    1800             :     {
    1801           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1802             :                  "Both pszDest and hDstDS are NULL.");
    1803           0 :         if (pbUsageError)
    1804           0 :             *pbUsageError = true;
    1805           0 :         return nullptr;
    1806             :     }
    1807             : 
    1808         116 :     GDALDriver *poDriver = nullptr;
    1809             : 
    1810             : #ifdef this_is_dead_code_for_now
    1811             :     const bool bCloseOutDSOnError = hDstDS == nullptr;
    1812             :     if (pszDest == nullptr)
    1813             :         pszDest = GDALGetDescription(hDstDS);
    1814             : #endif
    1815             : 
    1816         116 :     if (psOptions && psOptions->bOverwrite && !EQUAL(pszDest, ""))
    1817             :     {
    1818           1 :         VSIRmdirRecursive(pszDest);
    1819             :     }
    1820         115 :     else if (psOptions && psOptions->bNoOverwrite && !EQUAL(pszDest, ""))
    1821             :     {
    1822             :         VSIStatBufL sStat;
    1823           8 :         if (VSIStatL(pszDest, &sStat) == 0)
    1824             :         {
    1825           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1826             :                      "File '%s' already exists. Specify the --overwrite "
    1827             :                      "option to overwrite it.",
    1828             :                      pszDest);
    1829           0 :             return nullptr;
    1830             :         }
    1831           8 :         else if (std::unique_ptr<GDALDataset>(GDALDataset::Open(pszDest)))
    1832             :         {
    1833           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1834             :                      "Dataset '%s' already exists. Specify the --overwrite "
    1835             :                      "option to overwrite it.",
    1836             :                      pszDest);
    1837           0 :             return nullptr;
    1838             :         }
    1839             :     }
    1840             : 
    1841             : #ifdef this_is_dead_code_for_now
    1842             :     if (hDstDS == nullptr)
    1843             : #endif
    1844             :     {
    1845         116 :         if (osFormat.empty())
    1846             :         {
    1847         107 :             if (EQUAL(CPLGetExtensionSafe(pszDest).c_str(), "nc"))
    1848           2 :                 osFormat = "netCDF";
    1849             :             else
    1850         105 :                 osFormat = GetOutputDriverForRaster(pszDest);
    1851         107 :             if (osFormat.empty())
    1852             :             {
    1853           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1854             :                          "Cannot determine output driver for dataset name '%s'",
    1855             :                          pszDest);
    1856           1 :                 return nullptr;
    1857             :             }
    1858             :         }
    1859         115 :         poDriver = GDALDriver::FromHandle(GDALGetDriverByName(osFormat));
    1860         115 :         char **papszDriverMD = poDriver ? poDriver->GetMetadata() : nullptr;
    1861         230 :         if (poDriver == nullptr ||
    1862         115 :             (!CPLTestBool(CSLFetchNameValueDef(papszDriverMD, GDAL_DCAP_RASTER,
    1863           0 :                                                "FALSE")) &&
    1864           0 :              !CPLTestBool(CSLFetchNameValueDef(
    1865         230 :                  papszDriverMD, GDAL_DCAP_MULTIDIM_RASTER, "FALSE"))) ||
    1866         115 :             (!CPLTestBool(CSLFetchNameValueDef(papszDriverMD, GDAL_DCAP_CREATE,
    1867           0 :                                                "FALSE")) &&
    1868           0 :              !CPLTestBool(CSLFetchNameValueDef(
    1869           0 :                  papszDriverMD, GDAL_DCAP_CREATECOPY, "FALSE")) &&
    1870           0 :              !CPLTestBool(CSLFetchNameValueDef(
    1871           0 :                  papszDriverMD, GDAL_DCAP_CREATE_MULTIDIMENSIONAL, "FALSE")) &&
    1872           0 :              !CPLTestBool(CSLFetchNameValueDef(
    1873             :                  papszDriverMD, GDAL_DCAP_CREATECOPY_MULTIDIMENSIONAL,
    1874             :                  "FALSE"))))
    1875             :         {
    1876           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    1877             :                      "Output driver `%s' not recognised or does not support "
    1878             :                      "output file creation.",
    1879             :                      osFormat.c_str());
    1880           0 :             return nullptr;
    1881             :         }
    1882             :     }
    1883             : 
    1884         115 :     GDALDataset *poSrcDS = GDALDataset::FromHandle(pahSrcDS[0]);
    1885             : 
    1886         115 :     std::unique_ptr<GDALDataset> poTmpDS;
    1887         115 :     GDALDataset *poTmpSrcDS = poSrcDS;
    1888         230 :     if (psOptions &&
    1889         115 :         (!psOptions->aosArraySpec.empty() || !psOptions->aosGroup.empty() ||
    1890          19 :          !psOptions->aosSubset.empty() || !psOptions->aosScaleFactor.empty() ||
    1891          14 :          !psOptions->aosArrayOptions.empty()))
    1892             :     {
    1893         102 :         poTmpDS.reset(VRTDataset::CreateMultiDimensional("", nullptr, nullptr));
    1894         102 :         CPLAssert(poTmpDS);
    1895         102 :         poTmpSrcDS = poTmpDS.get();
    1896             : 
    1897         102 :         auto poDstRootGroup = poTmpDS->GetRootGroup();
    1898         102 :         CPLAssert(poDstRootGroup);
    1899             : 
    1900         102 :         if (!TranslateInternal(poDstRootGroup, poSrcDS, psOptions))
    1901             :         {
    1902             : #ifdef this_is_dead_code_for_now
    1903             :             if (bCloseOutDSOnError)
    1904             : #endif
    1905             :             {
    1906          24 :                 GDALClose(hDstDS);
    1907          24 :                 hDstDS = nullptr;
    1908             :             }
    1909          24 :             return nullptr;
    1910             :         }
    1911             :     }
    1912             : 
    1913          91 :     auto poRG(poTmpSrcDS->GetRootGroup());
    1914         180 :     if (poRG &&
    1915          89 :         poDriver->GetMetadataItem(GDAL_DCAP_CREATE_MULTIDIMENSIONAL) ==
    1916         180 :             nullptr &&
    1917           4 :         poDriver->GetMetadataItem(GDAL_DCAP_CREATECOPY_MULTIDIMENSIONAL) ==
    1918             :             nullptr)
    1919             :     {
    1920             : #ifdef this_is_dead_code_for_now
    1921             :         if (hDstDS)
    1922             :         {
    1923             :             CPLError(CE_Failure, CPLE_NotSupported,
    1924             :                      "Appending to non-multidimensional driver not supported.");
    1925             :             GDALClose(hDstDS);
    1926             :             hDstDS = nullptr;
    1927             :             return nullptr;
    1928             :         }
    1929             : #endif
    1930             :         hDstDS =
    1931           4 :             CopyToNonMultiDimensionalDriver(poDriver, pszDest, poRG, psOptions);
    1932             :     }
    1933             :     else
    1934             :     {
    1935         174 :         hDstDS = GDALDataset::ToHandle(poDriver->CreateCopy(
    1936             :             pszDest, poTmpSrcDS, false,
    1937          87 :             psOptions ? const_cast<char **>(psOptions->aosCreateOptions.List())
    1938             :                       : nullptr,
    1939             :             psOptions ? psOptions->pfnProgress : nullptr,
    1940             :             psOptions ? psOptions->pProgressData : nullptr));
    1941             :     }
    1942             : 
    1943          91 :     return hDstDS;
    1944             : }
    1945             : 
    1946             : /************************************************************************/
    1947             : /*                     GDALMultiDimTranslateOptionsNew()                */
    1948             : /************************************************************************/
    1949             : 
    1950             : /**
    1951             :  * Allocates a GDALMultiDimTranslateOptions struct.
    1952             :  *
    1953             :  * @param papszArgv NULL terminated list of options (potentially including
    1954             :  * filename and open options too), or NULL. The accepted options are the ones of
    1955             :  * the <a href="/programs/gdalmdimtranslate.html">gdalmdimtranslate</a> utility.
    1956             :  * @param psOptionsForBinary should be nullptr, unless called from
    1957             :  * gdalmdimtranslate_bin.cpp
    1958             :  * @return pointer to the allocated GDALMultiDimTranslateOptions struct. Must be
    1959             :  * freed with GDALMultiDimTranslateOptionsFree().
    1960             :  *
    1961             :  * @since GDAL 3.1
    1962             :  */
    1963             : 
    1964         117 : GDALMultiDimTranslateOptions *GDALMultiDimTranslateOptionsNew(
    1965             :     char **papszArgv, GDALMultiDimTranslateOptionsForBinary *psOptionsForBinary)
    1966             : {
    1967             : 
    1968         234 :     auto psOptions = std::make_unique<GDALMultiDimTranslateOptions>();
    1969             : 
    1970             :     /* -------------------------------------------------------------------- */
    1971             :     /*      Parse arguments.                                                */
    1972             :     /* -------------------------------------------------------------------- */
    1973             :     try
    1974             :     {
    1975             :         auto argParser = GDALMultiDimTranslateAppOptionsGetParser(
    1976         117 :             psOptions.get(), psOptionsForBinary);
    1977             : 
    1978         117 :         argParser->parse_args_without_binary_name(papszArgv);
    1979             : 
    1980             :         // Check for invalid options:
    1981             :         // -scaleaxes is not compatible with -array = "view"
    1982             :         // -subset is not compatible with -array = "view"
    1983         117 :         if (std::find(psOptions->aosArraySpec.cbegin(),
    1984         117 :                       psOptions->aosArraySpec.cend(),
    1985         351 :                       "view") != psOptions->aosArraySpec.cend())
    1986             :         {
    1987           0 :             if (!psOptions->aosScaleFactor.empty())
    1988             :             {
    1989           0 :                 CPLError(CE_Failure, CPLE_NotSupported,
    1990             :                          "The -scaleaxes option is not compatible with the "
    1991             :                          "-array \"view\" option.");
    1992           0 :                 return nullptr;
    1993             :             }
    1994             : 
    1995           0 :             if (!psOptions->aosSubset.empty())
    1996             :             {
    1997           0 :                 CPLError(CE_Failure, CPLE_NotSupported,
    1998             :                          "The -subset option is not compatible with the -array "
    1999             :                          "\"view\" option.");
    2000           0 :                 return nullptr;
    2001             :             }
    2002             :         }
    2003             :     }
    2004           0 :     catch (const std::exception &error)
    2005             :     {
    2006           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s", error.what());
    2007           0 :         return nullptr;
    2008             :     }
    2009             : 
    2010         117 :     if (psOptionsForBinary)
    2011             :     {
    2012             :         // Note: bUpdate is apparently never changed by the command line options
    2013           3 :         psOptionsForBinary->bUpdate = psOptions->bUpdate;
    2014           3 :         if (!psOptions->osFormat.empty())
    2015           0 :             psOptionsForBinary->osFormat = psOptions->osFormat;
    2016             :     }
    2017             : 
    2018         117 :     return psOptions.release();
    2019             : }
    2020             : 
    2021             : /************************************************************************/
    2022             : /*                     GDALMultiDimTranslateOptionsFree()               */
    2023             : /************************************************************************/
    2024             : 
    2025             : /**
    2026             :  * Frees the GDALMultiDimTranslateOptions struct.
    2027             :  *
    2028             :  * @param psOptions the options struct for GDALMultiDimTranslate().
    2029             :  *
    2030             :  * @since GDAL 3.1
    2031             :  */
    2032             : 
    2033         116 : void GDALMultiDimTranslateOptionsFree(GDALMultiDimTranslateOptions *psOptions)
    2034             : {
    2035         116 :     delete psOptions;
    2036         116 : }
    2037             : 
    2038             : /************************************************************************/
    2039             : /*               GDALMultiDimTranslateOptionsSetProgress()              */
    2040             : /************************************************************************/
    2041             : 
    2042             : /**
    2043             :  * Set a progress function.
    2044             :  *
    2045             :  * @param psOptions the options struct for GDALMultiDimTranslate().
    2046             :  * @param pfnProgress the progress callback.
    2047             :  * @param pProgressData the user data for the progress callback.
    2048             :  *
    2049             :  * @since GDAL 3.1
    2050             :  */
    2051             : 
    2052          13 : void GDALMultiDimTranslateOptionsSetProgress(
    2053             :     GDALMultiDimTranslateOptions *psOptions, GDALProgressFunc pfnProgress,
    2054             :     void *pProgressData)
    2055             : {
    2056          13 :     psOptions->pfnProgress = pfnProgress;
    2057          13 :     psOptions->pProgressData = pProgressData;
    2058          13 : }

Generated by: LCOV version 1.14