LCOV - code coverage report
Current view: top level - apps - gdalmdimtranslate_lib.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 810 978 82.8 %
Date: 2024-11-21 22:18:42 Functions: 16 18 88.9 %

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

Generated by: LCOV version 1.14