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

Generated by: LCOV version 1.14