2006 lines
70 KiB
C++
2006 lines
70 KiB
C++
/******************************************************************************
|
|
*
|
|
* Project: GDAL Utilities
|
|
* Purpose: Command line application to convert a multidimensional raster
|
|
* Author: Even Rouault,<even.rouault at spatialys.com>
|
|
*
|
|
* ****************************************************************************
|
|
* Copyright (c) 2019, Even Rouault <even.rouault at spatialys.com>
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
****************************************************************************/
|
|
|
|
#include "cpl_port.h"
|
|
#include "commonutils.h"
|
|
#include "gdal_priv.h"
|
|
#include "gdal_utils.h"
|
|
#include "gdal_utils_priv.h"
|
|
#include "gdalargumentparser.h"
|
|
#include "vrtdataset.h"
|
|
#include <algorithm>
|
|
#include <map>
|
|
#include <set>
|
|
|
|
/************************************************************************/
|
|
/* GDALMultiDimTranslateOptions */
|
|
/************************************************************************/
|
|
|
|
struct GDALMultiDimTranslateOptions
|
|
{
|
|
std::string osFormat{};
|
|
CPLStringList aosCreateOptions{};
|
|
std::vector<std::string> aosArraySpec{};
|
|
CPLStringList aosArrayOptions{};
|
|
std::vector<std::string> aosSubset{};
|
|
std::vector<std::string> aosScaleFactor{};
|
|
std::vector<std::string> aosGroup{};
|
|
GDALProgressFunc pfnProgress = GDALDummyProgress;
|
|
bool bStrict = false;
|
|
void *pProgressData = nullptr;
|
|
bool bUpdate = false;
|
|
};
|
|
|
|
/*************************************************************************/
|
|
/* GDALMultiDimTranslateAppOptionsGetParser() */
|
|
/************************************************************************/
|
|
|
|
static std::unique_ptr<GDALArgumentParser>
|
|
GDALMultiDimTranslateAppOptionsGetParser(
|
|
GDALMultiDimTranslateOptions *psOptions,
|
|
GDALMultiDimTranslateOptionsForBinary *psOptionsForBinary)
|
|
{
|
|
auto argParser = std::make_unique<GDALArgumentParser>(
|
|
"gdalmdimtranslate", /* bForBinary=*/psOptionsForBinary != nullptr);
|
|
|
|
argParser->add_description(
|
|
_("Converts multidimensional data between different formats, and "
|
|
"performs subsetting."));
|
|
|
|
argParser->add_epilog(
|
|
_("For more details, consult "
|
|
"https://gdal.org/programs/gdalmdimtranslate.html"));
|
|
|
|
if (psOptionsForBinary)
|
|
{
|
|
argParser->add_input_format_argument(
|
|
&psOptionsForBinary->aosAllowInputDrivers);
|
|
}
|
|
|
|
argParser->add_output_format_argument(psOptions->osFormat);
|
|
|
|
argParser->add_creation_options_argument(psOptions->aosCreateOptions);
|
|
|
|
auto &group = argParser->add_mutually_exclusive_group();
|
|
group.add_argument("-array")
|
|
.metavar("<array_spec>")
|
|
.append()
|
|
.store_into(psOptions->aosArraySpec)
|
|
.help(_(
|
|
"Select a single array instead of converting the whole dataset."));
|
|
|
|
argParser->add_argument("-arrayoption")
|
|
.metavar("<NAME>=<VALUE>")
|
|
.append()
|
|
.action([psOptions](const std::string &s)
|
|
{ psOptions->aosArrayOptions.AddString(s.c_str()); })
|
|
.help(_("Option passed to GDALGroup::GetMDArrayNames() to filter "
|
|
"reported arrays."));
|
|
|
|
group.add_argument("-group")
|
|
.metavar("<group_spec>")
|
|
.append()
|
|
.store_into(psOptions->aosGroup)
|
|
.help(_(
|
|
"Select a single group instead of converting the whole dataset."));
|
|
|
|
// Note: this is mutually exclusive with "view" option in -array
|
|
argParser->add_argument("-subset")
|
|
.metavar("<subset_spec>")
|
|
.append()
|
|
.store_into(psOptions->aosSubset)
|
|
.help(_("Select a subset of the data."));
|
|
|
|
// Note: this is mutually exclusive with "view" option in -array
|
|
argParser->add_argument("-scaleaxes")
|
|
.metavar("<scaleaxes_spec>")
|
|
.action(
|
|
[psOptions](const std::string &s)
|
|
{
|
|
CPLStringList aosScaleFactors(
|
|
CSLTokenizeString2(s.c_str(), ",", 0));
|
|
for (int j = 0; j < aosScaleFactors.size(); j++)
|
|
{
|
|
psOptions->aosScaleFactor.push_back(aosScaleFactors[j]);
|
|
}
|
|
})
|
|
.help(
|
|
_("Applies a integral scale factor to one or several dimensions."));
|
|
|
|
argParser->add_argument("-strict")
|
|
.flag()
|
|
.store_into(psOptions->bStrict)
|
|
.help(_("Turn warnings into failures."));
|
|
|
|
if (psOptionsForBinary)
|
|
{
|
|
argParser->add_open_options_argument(
|
|
psOptionsForBinary->aosOpenOptions);
|
|
|
|
argParser->add_argument("src_dataset")
|
|
.metavar("<src_dataset>")
|
|
.store_into(psOptionsForBinary->osSource)
|
|
.help(_("The source dataset name."));
|
|
|
|
argParser->add_argument("dst_dataset")
|
|
.metavar("<dst_dataset>")
|
|
.store_into(psOptionsForBinary->osDest)
|
|
.help(_("The destination file name."));
|
|
|
|
argParser->add_quiet_argument(&psOptionsForBinary->bQuiet);
|
|
}
|
|
|
|
return argParser;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GDALMultiDimTranslateAppGetParserUsage() */
|
|
/************************************************************************/
|
|
|
|
std::string GDALMultiDimTranslateAppGetParserUsage()
|
|
{
|
|
try
|
|
{
|
|
GDALMultiDimTranslateOptions sOptions;
|
|
GDALMultiDimTranslateOptionsForBinary sOptionsForBinary;
|
|
auto argParser = GDALMultiDimTranslateAppOptionsGetParser(
|
|
&sOptions, &sOptionsForBinary);
|
|
return argParser->usage();
|
|
}
|
|
catch (const std::exception &err)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
|
|
err.what());
|
|
return std::string();
|
|
}
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* FindMinMaxIdxNumeric() */
|
|
/************************************************************************/
|
|
|
|
static void FindMinMaxIdxNumeric(const GDALMDArray *var, double *pdfTmp,
|
|
const size_t nCount, const GUInt64 nStartIdx,
|
|
const double dfMin, const double dfMax,
|
|
const bool bSlice, bool &bFoundMinIdx,
|
|
GUInt64 &nMinIdx, bool &bFoundMaxIdx,
|
|
GUInt64 &nMaxIdx, bool &bLastWasReversed,
|
|
bool &bEmpty, const double EPS)
|
|
{
|
|
if (nCount >= 2)
|
|
{
|
|
bool bReversed = false;
|
|
if (pdfTmp[0] > pdfTmp[nCount - 1])
|
|
{
|
|
bReversed = true;
|
|
std::reverse(pdfTmp, pdfTmp + nCount);
|
|
}
|
|
if (nStartIdx > 0 && bLastWasReversed != bReversed)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Variable %s is non monotonic", var->GetName().c_str());
|
|
bEmpty = true;
|
|
return;
|
|
}
|
|
bLastWasReversed = bReversed;
|
|
|
|
if (!bFoundMinIdx)
|
|
{
|
|
if (bReversed && nStartIdx == 0 && dfMin > pdfTmp[nCount - 1])
|
|
{
|
|
bEmpty = true;
|
|
return;
|
|
}
|
|
else if (!bReversed && dfMin < pdfTmp[0] - EPS)
|
|
{
|
|
if (bSlice)
|
|
{
|
|
bEmpty = true;
|
|
return;
|
|
}
|
|
bFoundMinIdx = true;
|
|
nMinIdx = nStartIdx;
|
|
}
|
|
else if (dfMin >= pdfTmp[0] - EPS &&
|
|
dfMin <= pdfTmp[nCount - 1] + EPS)
|
|
{
|
|
for (size_t i = 0; i < nCount; i++)
|
|
{
|
|
if (dfMin <= pdfTmp[i] + EPS)
|
|
{
|
|
bFoundMinIdx = true;
|
|
nMinIdx = nStartIdx + (bReversed ? nCount - 1 - i : i);
|
|
break;
|
|
}
|
|
}
|
|
CPLAssert(bFoundMinIdx);
|
|
}
|
|
}
|
|
if (!bFoundMaxIdx)
|
|
{
|
|
if (bReversed && nStartIdx == 0 && dfMax > pdfTmp[nCount - 1])
|
|
{
|
|
if (bSlice)
|
|
{
|
|
bEmpty = true;
|
|
return;
|
|
}
|
|
bFoundMaxIdx = true;
|
|
nMaxIdx = 0;
|
|
}
|
|
else if (!bReversed && dfMax < pdfTmp[0] - EPS)
|
|
{
|
|
if (nStartIdx == 0)
|
|
{
|
|
bEmpty = true;
|
|
return;
|
|
}
|
|
bFoundMaxIdx = true;
|
|
nMaxIdx = nStartIdx - 1;
|
|
}
|
|
else if (dfMax > pdfTmp[0] - EPS &&
|
|
dfMax <= pdfTmp[nCount - 1] + EPS)
|
|
{
|
|
for (size_t i = 1; i < nCount; i++)
|
|
{
|
|
if (dfMax <= pdfTmp[i] - EPS)
|
|
{
|
|
bFoundMaxIdx = true;
|
|
nMaxIdx = nStartIdx +
|
|
(bReversed ? nCount - 1 - (i - 1) : i - 1);
|
|
break;
|
|
}
|
|
}
|
|
if (!bFoundMaxIdx)
|
|
{
|
|
bFoundMaxIdx = true;
|
|
nMaxIdx = nStartIdx + (bReversed ? 0 : nCount - 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!bFoundMinIdx)
|
|
{
|
|
if (dfMin <= pdfTmp[0] + EPS)
|
|
{
|
|
bFoundMinIdx = true;
|
|
nMinIdx = nStartIdx;
|
|
}
|
|
else if (bLastWasReversed && nStartIdx > 0)
|
|
{
|
|
bFoundMinIdx = true;
|
|
nMinIdx = nStartIdx - 1;
|
|
}
|
|
}
|
|
if (!bFoundMaxIdx)
|
|
{
|
|
if (dfMax >= pdfTmp[0] - EPS)
|
|
{
|
|
bFoundMaxIdx = true;
|
|
nMaxIdx = nStartIdx;
|
|
}
|
|
else if (!bLastWasReversed && nStartIdx > 0)
|
|
{
|
|
bFoundMaxIdx = true;
|
|
nMaxIdx = nStartIdx - 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* FindMinMaxIdxString() */
|
|
/************************************************************************/
|
|
|
|
static void FindMinMaxIdxString(const GDALMDArray *var, const char **ppszTmp,
|
|
const size_t nCount, const GUInt64 nStartIdx,
|
|
const std::string &osMin,
|
|
const std::string &osMax, const bool bSlice,
|
|
bool &bFoundMinIdx, GUInt64 &nMinIdx,
|
|
bool &bFoundMaxIdx, GUInt64 &nMaxIdx,
|
|
bool &bLastWasReversed, bool &bEmpty)
|
|
{
|
|
bool bFoundNull = false;
|
|
for (size_t i = 0; i < nCount; i++)
|
|
{
|
|
if (ppszTmp[i] == nullptr)
|
|
{
|
|
bFoundNull = true;
|
|
break;
|
|
}
|
|
}
|
|
if (bFoundNull)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Variable %s contains null strings", var->GetName().c_str());
|
|
bEmpty = true;
|
|
return;
|
|
}
|
|
if (nCount >= 2)
|
|
{
|
|
bool bReversed = false;
|
|
if (std::string(ppszTmp[0]) > std::string(ppszTmp[nCount - 1]))
|
|
{
|
|
bReversed = true;
|
|
std::reverse(ppszTmp, ppszTmp + nCount);
|
|
}
|
|
if (nStartIdx > 0 && bLastWasReversed != bReversed)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Variable %s is non monotonic", var->GetName().c_str());
|
|
bEmpty = true;
|
|
return;
|
|
}
|
|
bLastWasReversed = bReversed;
|
|
|
|
if (!bFoundMinIdx)
|
|
{
|
|
if (bReversed && nStartIdx == 0 &&
|
|
osMin > std::string(ppszTmp[nCount - 1]))
|
|
{
|
|
bEmpty = true;
|
|
return;
|
|
}
|
|
else if (!bReversed && osMin < std::string(ppszTmp[0]))
|
|
{
|
|
if (bSlice)
|
|
{
|
|
bEmpty = true;
|
|
return;
|
|
}
|
|
bFoundMinIdx = true;
|
|
nMinIdx = nStartIdx;
|
|
}
|
|
else if (osMin >= std::string(ppszTmp[0]) &&
|
|
osMin <= std::string(ppszTmp[nCount - 1]))
|
|
{
|
|
for (size_t i = 0; i < nCount; i++)
|
|
{
|
|
if (osMin <= std::string(ppszTmp[i]))
|
|
{
|
|
bFoundMinIdx = true;
|
|
nMinIdx = nStartIdx + (bReversed ? nCount - 1 - i : i);
|
|
break;
|
|
}
|
|
}
|
|
CPLAssert(bFoundMinIdx);
|
|
}
|
|
}
|
|
if (!bFoundMaxIdx)
|
|
{
|
|
if (bReversed && nStartIdx == 0 &&
|
|
osMax > std::string(ppszTmp[nCount - 1]))
|
|
{
|
|
if (bSlice)
|
|
{
|
|
bEmpty = true;
|
|
return;
|
|
}
|
|
bFoundMaxIdx = true;
|
|
nMaxIdx = 0;
|
|
}
|
|
else if (!bReversed && osMax < std::string(ppszTmp[0]))
|
|
{
|
|
if (nStartIdx == 0)
|
|
{
|
|
bEmpty = true;
|
|
return;
|
|
}
|
|
bFoundMaxIdx = true;
|
|
nMaxIdx = nStartIdx - 1;
|
|
}
|
|
else if (osMax == std::string(ppszTmp[0]))
|
|
{
|
|
bFoundMaxIdx = true;
|
|
nMaxIdx = nStartIdx + (bReversed ? nCount - 1 : 0);
|
|
}
|
|
else if (osMax > std::string(ppszTmp[0]) &&
|
|
osMax <= std::string(ppszTmp[nCount - 1]))
|
|
{
|
|
for (size_t i = 1; i < nCount; i++)
|
|
{
|
|
if (osMax <= std::string(ppszTmp[i]))
|
|
{
|
|
bFoundMaxIdx = true;
|
|
if (osMax == std::string(ppszTmp[i]))
|
|
nMaxIdx =
|
|
nStartIdx + (bReversed ? nCount - 1 - i : i);
|
|
else
|
|
nMaxIdx =
|
|
nStartIdx +
|
|
(bReversed ? nCount - 1 - (i - 1) : i - 1);
|
|
break;
|
|
}
|
|
}
|
|
CPLAssert(bFoundMaxIdx);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!bFoundMinIdx)
|
|
{
|
|
if (osMin <= std::string(ppszTmp[0]))
|
|
{
|
|
bFoundMinIdx = true;
|
|
nMinIdx = nStartIdx;
|
|
}
|
|
else if (bLastWasReversed && nStartIdx > 0)
|
|
{
|
|
bFoundMinIdx = true;
|
|
nMinIdx = nStartIdx - 1;
|
|
}
|
|
}
|
|
if (!bFoundMaxIdx)
|
|
{
|
|
if (osMax >= std::string(ppszTmp[0]))
|
|
{
|
|
bFoundMaxIdx = true;
|
|
nMaxIdx = nStartIdx;
|
|
}
|
|
else if (!bLastWasReversed && nStartIdx > 0)
|
|
{
|
|
bFoundMaxIdx = true;
|
|
nMaxIdx = nStartIdx - 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetDimensionDesc() */
|
|
/************************************************************************/
|
|
|
|
struct DimensionDesc
|
|
{
|
|
GUInt64 nStartIdx = 0;
|
|
GUInt64 nStep = 1;
|
|
GUInt64 nSize = 0;
|
|
GUInt64 nOriSize = 0;
|
|
bool bSlice = false;
|
|
};
|
|
|
|
struct DimensionRemapper
|
|
{
|
|
std::map<std::string, DimensionDesc> oMap{};
|
|
};
|
|
|
|
static const DimensionDesc *
|
|
GetDimensionDesc(DimensionRemapper &oDimRemapper,
|
|
const GDALMultiDimTranslateOptions *psOptions,
|
|
const std::shared_ptr<GDALDimension> &poDim)
|
|
{
|
|
std::string osKey(poDim->GetFullName());
|
|
osKey +=
|
|
CPLSPrintf("_" CPL_FRMT_GUIB, static_cast<GUIntBig>(poDim->GetSize()));
|
|
auto oIter = oDimRemapper.oMap.find(osKey);
|
|
if (oIter != oDimRemapper.oMap.end() &&
|
|
oIter->second.nOriSize == poDim->GetSize())
|
|
{
|
|
return &(oIter->second);
|
|
}
|
|
DimensionDesc desc;
|
|
desc.nSize = poDim->GetSize();
|
|
desc.nOriSize = desc.nSize;
|
|
|
|
CPLString osRadix(poDim->GetName());
|
|
osRadix += '(';
|
|
for (const auto &subset : psOptions->aosSubset)
|
|
{
|
|
if (STARTS_WITH(subset.c_str(), osRadix.c_str()))
|
|
{
|
|
auto var = poDim->GetIndexingVariable();
|
|
if (!var || var->GetDimensionCount() != 1 ||
|
|
var->GetDimensions()[0]->GetSize() != poDim->GetSize())
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Dimension %s has a subset specification, but lacks "
|
|
"a single dimension indexing variable",
|
|
poDim->GetName().c_str());
|
|
return nullptr;
|
|
}
|
|
if (subset.back() != ')')
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Missing ')' in subset specification.");
|
|
return nullptr;
|
|
}
|
|
CPLStringList aosTokens(CSLTokenizeString2(
|
|
subset
|
|
.substr(osRadix.size(), subset.size() - 1 - osRadix.size())
|
|
.c_str(),
|
|
",", CSLT_HONOURSTRINGS));
|
|
if (aosTokens.size() == 1)
|
|
{
|
|
desc.bSlice = true;
|
|
}
|
|
if (aosTokens.size() != 1 && aosTokens.size() != 2)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Invalid number of valus in subset specification.");
|
|
return nullptr;
|
|
}
|
|
|
|
const bool bIsNumeric =
|
|
var->GetDataType().GetClass() == GEDTC_NUMERIC;
|
|
const GDALExtendedDataType dt(
|
|
bIsNumeric ? GDALExtendedDataType::Create(GDT_Float64)
|
|
: GDALExtendedDataType::CreateString());
|
|
|
|
double dfMin = 0;
|
|
double dfMax = 0;
|
|
std::string osMin;
|
|
std::string osMax;
|
|
if (bIsNumeric)
|
|
{
|
|
if (CPLGetValueType(aosTokens[0]) == CPL_VALUE_STRING ||
|
|
(aosTokens.size() == 2 &&
|
|
CPLGetValueType(aosTokens[1]) == CPL_VALUE_STRING))
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Non numeric bound in subset specification.");
|
|
return nullptr;
|
|
}
|
|
dfMin = CPLAtof(aosTokens[0]);
|
|
dfMax = dfMin;
|
|
if (aosTokens.size() == 2)
|
|
dfMax = CPLAtof(aosTokens[1]);
|
|
if (dfMin > dfMax)
|
|
std::swap(dfMin, dfMax);
|
|
}
|
|
else
|
|
{
|
|
osMin = aosTokens[0];
|
|
osMax = osMin;
|
|
if (aosTokens.size() == 2)
|
|
osMax = aosTokens[1];
|
|
if (osMin > osMax)
|
|
std::swap(osMin, osMax);
|
|
}
|
|
|
|
const size_t nDTSize(dt.GetSize());
|
|
const size_t nMaxChunkSize = static_cast<size_t>(std::min(
|
|
static_cast<GUInt64>(10 * 1000 * 1000), poDim->GetSize()));
|
|
std::vector<GByte> abyTmp(nDTSize * nMaxChunkSize);
|
|
double *pdfTmp = reinterpret_cast<double *>(&abyTmp[0]);
|
|
const char **ppszTmp = reinterpret_cast<const char **>(&abyTmp[0]);
|
|
GUInt64 nStartIdx = 0;
|
|
const double EPS = std::max(std::max(1e-10, fabs(dfMin) / 1e10),
|
|
fabs(dfMax) / 1e10);
|
|
bool bFoundMinIdx = false;
|
|
bool bFoundMaxIdx = false;
|
|
GUInt64 nMinIdx = 0;
|
|
GUInt64 nMaxIdx = 0;
|
|
bool bLastWasReversed = false;
|
|
bool bEmpty = false;
|
|
while (true)
|
|
{
|
|
const size_t nCount = static_cast<size_t>(
|
|
std::min(static_cast<GUInt64>(nMaxChunkSize),
|
|
poDim->GetSize() - nStartIdx));
|
|
if (nCount == 0)
|
|
break;
|
|
const GUInt64 anStartId[] = {nStartIdx};
|
|
const size_t anCount[] = {nCount};
|
|
if (!var->Read(anStartId, anCount, nullptr, nullptr, dt,
|
|
&abyTmp[0], nullptr, 0))
|
|
{
|
|
return nullptr;
|
|
}
|
|
if (bIsNumeric)
|
|
{
|
|
FindMinMaxIdxNumeric(
|
|
var.get(), pdfTmp, nCount, nStartIdx, dfMin, dfMax,
|
|
desc.bSlice, bFoundMinIdx, nMinIdx, bFoundMaxIdx,
|
|
nMaxIdx, bLastWasReversed, bEmpty, EPS);
|
|
}
|
|
else
|
|
{
|
|
FindMinMaxIdxString(var.get(), ppszTmp, nCount, nStartIdx,
|
|
osMin, osMax, desc.bSlice, bFoundMinIdx,
|
|
nMinIdx, bFoundMaxIdx, nMaxIdx,
|
|
bLastWasReversed, bEmpty);
|
|
}
|
|
if (dt.NeedsFreeDynamicMemory())
|
|
{
|
|
for (size_t i = 0; i < nCount; i++)
|
|
{
|
|
dt.FreeDynamicMemory(&abyTmp[i * nDTSize]);
|
|
}
|
|
}
|
|
if (bEmpty || (bFoundMinIdx && bFoundMaxIdx) ||
|
|
nCount < nMaxChunkSize)
|
|
{
|
|
break;
|
|
}
|
|
nStartIdx += nMaxChunkSize;
|
|
}
|
|
|
|
// cppcheck-suppress knownConditionTrueFalse
|
|
if (!bLastWasReversed)
|
|
{
|
|
if (!bFoundMinIdx)
|
|
bEmpty = true;
|
|
else if (!bFoundMaxIdx)
|
|
nMaxIdx = poDim->GetSize() - 1;
|
|
else
|
|
bEmpty = nMaxIdx < nMinIdx;
|
|
}
|
|
else
|
|
{
|
|
if (!bFoundMaxIdx)
|
|
bEmpty = true;
|
|
else if (!bFoundMinIdx)
|
|
nMinIdx = poDim->GetSize() - 1;
|
|
else
|
|
bEmpty = nMinIdx < nMaxIdx;
|
|
}
|
|
if (bEmpty)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Subset specification results in an empty set");
|
|
return nullptr;
|
|
}
|
|
|
|
// cppcheck-suppress knownConditionTrueFalse
|
|
if (!bLastWasReversed)
|
|
{
|
|
CPLAssert(nMaxIdx >= nMinIdx);
|
|
desc.nStartIdx = nMinIdx;
|
|
desc.nSize = nMaxIdx - nMinIdx + 1;
|
|
}
|
|
else
|
|
{
|
|
CPLAssert(nMaxIdx <= nMinIdx);
|
|
desc.nStartIdx = nMaxIdx;
|
|
desc.nSize = nMinIdx - nMaxIdx + 1;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (const auto &scaleFactor : psOptions->aosScaleFactor)
|
|
{
|
|
if (STARTS_WITH(scaleFactor.c_str(), osRadix.c_str()))
|
|
{
|
|
if (scaleFactor.back() != ')')
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Missing ')' in scalefactor specification.");
|
|
return nullptr;
|
|
}
|
|
std::string osScaleFactor(scaleFactor.substr(
|
|
osRadix.size(), scaleFactor.size() - 1 - osRadix.size()));
|
|
int nScaleFactor = atoi(osScaleFactor.c_str());
|
|
if (CPLGetValueType(osScaleFactor.c_str()) != CPL_VALUE_INTEGER ||
|
|
nScaleFactor <= 0)
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported,
|
|
"Only positive integer scale factor is supported");
|
|
return nullptr;
|
|
}
|
|
desc.nSize /= nScaleFactor;
|
|
if (desc.nSize == 0)
|
|
desc.nSize = 1;
|
|
desc.nStep *= nScaleFactor;
|
|
break;
|
|
}
|
|
}
|
|
|
|
oDimRemapper.oMap[osKey] = desc;
|
|
return &oDimRemapper.oMap[osKey];
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ParseArraySpec() */
|
|
/************************************************************************/
|
|
|
|
// foo
|
|
// name=foo,transpose=[1,0],view=[0],dstname=bar,ot=Float32
|
|
static bool ParseArraySpec(const std::string &arraySpec, std::string &srcName,
|
|
std::string &dstName, int &band,
|
|
std::vector<int> &anTransposedAxis,
|
|
std::string &viewExpr,
|
|
GDALExtendedDataType &outputType, bool &bResampled)
|
|
{
|
|
if (!STARTS_WITH(arraySpec.c_str(), "name=") &&
|
|
!STARTS_WITH(arraySpec.c_str(), "band="))
|
|
{
|
|
srcName = arraySpec;
|
|
dstName = arraySpec;
|
|
auto pos = dstName.rfind('/');
|
|
if (pos != std::string::npos)
|
|
dstName = dstName.substr(pos + 1);
|
|
return true;
|
|
}
|
|
|
|
std::vector<std::string> tokens;
|
|
std::string curToken;
|
|
bool bInArray = false;
|
|
for (size_t i = 0; i < arraySpec.size(); ++i)
|
|
{
|
|
if (!bInArray && arraySpec[i] == ',')
|
|
{
|
|
tokens.emplace_back(std::move(curToken));
|
|
curToken = std::string();
|
|
}
|
|
else
|
|
{
|
|
if (arraySpec[i] == '[')
|
|
{
|
|
bInArray = true;
|
|
}
|
|
else if (arraySpec[i] == ']')
|
|
{
|
|
bInArray = false;
|
|
}
|
|
curToken += arraySpec[i];
|
|
}
|
|
}
|
|
if (!curToken.empty())
|
|
{
|
|
tokens.emplace_back(std::move(curToken));
|
|
}
|
|
for (const auto &token : tokens)
|
|
{
|
|
if (STARTS_WITH(token.c_str(), "name="))
|
|
{
|
|
srcName = token.substr(strlen("name="));
|
|
if (dstName.empty())
|
|
dstName = srcName;
|
|
}
|
|
else if (STARTS_WITH(token.c_str(), "band="))
|
|
{
|
|
band = atoi(token.substr(strlen("band=")).c_str());
|
|
if (dstName.empty())
|
|
dstName = CPLSPrintf("Band%d", band);
|
|
}
|
|
else if (STARTS_WITH(token.c_str(), "dstname="))
|
|
{
|
|
dstName = token.substr(strlen("dstname="));
|
|
}
|
|
else if (STARTS_WITH(token.c_str(), "transpose="))
|
|
{
|
|
auto transposeExpr = token.substr(strlen("transpose="));
|
|
if (transposeExpr.size() < 3 || transposeExpr[0] != '[' ||
|
|
transposeExpr.back() != ']')
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Invalid value for transpose");
|
|
return false;
|
|
}
|
|
transposeExpr = transposeExpr.substr(1, transposeExpr.size() - 2);
|
|
CPLStringList aosAxis(
|
|
CSLTokenizeString2(transposeExpr.c_str(), ",", 0));
|
|
for (int i = 0; i < aosAxis.size(); ++i)
|
|
{
|
|
anTransposedAxis.push_back(atoi(aosAxis[i]));
|
|
}
|
|
}
|
|
else if (STARTS_WITH(token.c_str(), "view="))
|
|
{
|
|
viewExpr = token.substr(strlen("view="));
|
|
}
|
|
else if (STARTS_WITH(token.c_str(), "ot="))
|
|
{
|
|
auto outputTypeStr = token.substr(strlen("ot="));
|
|
if (outputTypeStr == "String")
|
|
outputType = GDALExtendedDataType::CreateString();
|
|
else
|
|
{
|
|
auto eDT = GDALGetDataTypeByName(outputTypeStr.c_str());
|
|
if (eDT == GDT_Unknown)
|
|
return false;
|
|
outputType = GDALExtendedDataType::Create(eDT);
|
|
}
|
|
}
|
|
else if (STARTS_WITH(token.c_str(), "resample="))
|
|
{
|
|
bResampled = CPLTestBool(token.c_str() + strlen("resample="));
|
|
}
|
|
else
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Unexpected array specification part: %s", token.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* TranslateArray() */
|
|
/************************************************************************/
|
|
|
|
static bool TranslateArray(
|
|
DimensionRemapper &oDimRemapper,
|
|
const std::shared_ptr<GDALMDArray> &poSrcArrayIn,
|
|
const std::string &arraySpec,
|
|
const std::shared_ptr<GDALGroup> &poSrcRootGroup,
|
|
const std::shared_ptr<GDALGroup> &poSrcGroup,
|
|
const std::shared_ptr<GDALGroup> &poDstRootGroup,
|
|
std::shared_ptr<GDALGroup> &poDstGroup, GDALDataset *poSrcDS,
|
|
std::map<std::string, std::shared_ptr<GDALDimension>> &mapSrcToDstDims,
|
|
std::map<std::string, std::shared_ptr<GDALDimension>> &mapDstDimFullNames,
|
|
const GDALMultiDimTranslateOptions *psOptions)
|
|
{
|
|
std::string srcArrayName;
|
|
std::string dstArrayName;
|
|
int band = -1;
|
|
std::vector<int> anTransposedAxis;
|
|
std::string viewExpr;
|
|
bool bResampled = false;
|
|
GDALExtendedDataType outputType(GDALExtendedDataType::Create(GDT_Unknown));
|
|
if (!ParseArraySpec(arraySpec, srcArrayName, dstArrayName, band,
|
|
anTransposedAxis, viewExpr, outputType, bResampled))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<GDALMDArray> srcArray;
|
|
bool bSrcArrayAccessibleThroughSrcGroup = true;
|
|
if (poSrcRootGroup && poSrcGroup)
|
|
{
|
|
if (!srcArrayName.empty() && srcArrayName[0] == '/')
|
|
srcArray = poSrcRootGroup->OpenMDArrayFromFullname(srcArrayName);
|
|
else
|
|
srcArray = poSrcGroup->OpenMDArray(srcArrayName);
|
|
if (!srcArray)
|
|
{
|
|
if (poSrcArrayIn && poSrcArrayIn->GetFullName() == arraySpec)
|
|
{
|
|
bSrcArrayAccessibleThroughSrcGroup = false;
|
|
srcArray = poSrcArrayIn;
|
|
}
|
|
else
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Cannot find array %s",
|
|
srcArrayName.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto poBand = poSrcDS->GetRasterBand(band);
|
|
if (!poBand)
|
|
return false;
|
|
srcArray = poBand->AsMDArray();
|
|
}
|
|
|
|
auto tmpArray = srcArray;
|
|
|
|
if (bResampled)
|
|
{
|
|
auto newTmpArray =
|
|
tmpArray->GetResampled(std::vector<std::shared_ptr<GDALDimension>>(
|
|
tmpArray->GetDimensionCount()),
|
|
GRIORA_NearestNeighbour, nullptr, nullptr);
|
|
if (!newTmpArray)
|
|
return false;
|
|
tmpArray = std::move(newTmpArray);
|
|
}
|
|
|
|
if (!anTransposedAxis.empty())
|
|
{
|
|
auto newTmpArray = tmpArray->Transpose(anTransposedAxis);
|
|
if (!newTmpArray)
|
|
return false;
|
|
tmpArray = std::move(newTmpArray);
|
|
}
|
|
const auto &srcArrayDims(tmpArray->GetDimensions());
|
|
std::map<std::shared_ptr<GDALDimension>, std::shared_ptr<GDALDimension>>
|
|
oMapSubsetDimToSrcDim;
|
|
|
|
std::vector<GDALMDArray::ViewSpec> viewSpecs;
|
|
if (!viewExpr.empty())
|
|
{
|
|
if (!psOptions->aosSubset.empty() || !psOptions->aosScaleFactor.empty())
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported,
|
|
"View specification not supported when used together "
|
|
"with subset and/or scalefactor options");
|
|
return false;
|
|
}
|
|
auto newTmpArray = tmpArray->GetView(viewExpr, true, viewSpecs);
|
|
if (!newTmpArray)
|
|
return false;
|
|
tmpArray = std::move(newTmpArray);
|
|
}
|
|
else if (!psOptions->aosSubset.empty() ||
|
|
!psOptions->aosScaleFactor.empty())
|
|
{
|
|
bool bHasModifiedDim = false;
|
|
viewExpr = '[';
|
|
for (size_t i = 0; i < srcArrayDims.size(); ++i)
|
|
{
|
|
const auto &srcDim(srcArrayDims[i]);
|
|
const auto poDimDesc =
|
|
GetDimensionDesc(oDimRemapper, psOptions, srcDim);
|
|
if (poDimDesc == nullptr)
|
|
return false;
|
|
if (i > 0)
|
|
viewExpr += ',';
|
|
if (!poDimDesc->bSlice && poDimDesc->nStartIdx == 0 &&
|
|
poDimDesc->nStep == 1 && poDimDesc->nSize == srcDim->GetSize())
|
|
{
|
|
viewExpr += ":";
|
|
}
|
|
else
|
|
{
|
|
bHasModifiedDim = true;
|
|
viewExpr += CPLSPrintf(
|
|
CPL_FRMT_GUIB, static_cast<GUInt64>(poDimDesc->nStartIdx));
|
|
if (!poDimDesc->bSlice)
|
|
{
|
|
viewExpr += ':';
|
|
viewExpr +=
|
|
CPLSPrintf(CPL_FRMT_GUIB,
|
|
static_cast<GUInt64>(poDimDesc->nStartIdx +
|
|
poDimDesc->nSize *
|
|
poDimDesc->nStep));
|
|
viewExpr += ':';
|
|
viewExpr += CPLSPrintf(
|
|
CPL_FRMT_GUIB, static_cast<GUInt64>(poDimDesc->nStep));
|
|
}
|
|
}
|
|
}
|
|
viewExpr += ']';
|
|
if (bHasModifiedDim)
|
|
{
|
|
auto tmpArrayNew = tmpArray->GetView(viewExpr, false, viewSpecs);
|
|
if (!tmpArrayNew)
|
|
return false;
|
|
tmpArray = std::move(tmpArrayNew);
|
|
size_t j = 0;
|
|
const auto &tmpArrayDims(tmpArray->GetDimensions());
|
|
for (size_t i = 0; i < srcArrayDims.size(); ++i)
|
|
{
|
|
const auto &srcDim(srcArrayDims[i]);
|
|
const auto poDimDesc =
|
|
GetDimensionDesc(oDimRemapper, psOptions, srcDim);
|
|
if (poDimDesc == nullptr)
|
|
return false;
|
|
if (poDimDesc->bSlice)
|
|
continue;
|
|
CPLAssert(j < tmpArrayDims.size());
|
|
oMapSubsetDimToSrcDim[tmpArrayDims[j]] = srcDim;
|
|
j++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
viewExpr.clear();
|
|
}
|
|
}
|
|
|
|
int idxSliceSpec = -1;
|
|
for (size_t i = 0; i < viewSpecs.size(); ++i)
|
|
{
|
|
if (viewSpecs[i].m_osFieldName.empty())
|
|
{
|
|
if (idxSliceSpec >= 0)
|
|
{
|
|
idxSliceSpec = -1;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
idxSliceSpec = static_cast<int>(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Map source dimensions to target dimensions
|
|
std::vector<std::shared_ptr<GDALDimension>> dstArrayDims;
|
|
const auto &tmpArrayDims(tmpArray->GetDimensions());
|
|
for (size_t i = 0; i < tmpArrayDims.size(); ++i)
|
|
{
|
|
const auto &srcDim(tmpArrayDims[i]);
|
|
std::string srcDimFullName(srcDim->GetFullName());
|
|
|
|
std::shared_ptr<GDALDimension> dstDim;
|
|
{
|
|
CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
|
|
if (!srcDimFullName.empty() && srcDimFullName[0] == '/')
|
|
{
|
|
dstDim =
|
|
poDstRootGroup->OpenDimensionFromFullname(srcDimFullName);
|
|
}
|
|
}
|
|
if (dstDim)
|
|
{
|
|
dstArrayDims.emplace_back(dstDim);
|
|
continue;
|
|
}
|
|
|
|
auto oIter = mapSrcToDstDims.find(srcDimFullName);
|
|
if (oIter != mapSrcToDstDims.end())
|
|
{
|
|
dstArrayDims.emplace_back(oIter->second);
|
|
continue;
|
|
}
|
|
auto oIterRealSrcDim = oMapSubsetDimToSrcDim.find(srcDim);
|
|
if (oIterRealSrcDim != oMapSubsetDimToSrcDim.end())
|
|
{
|
|
srcDimFullName = oIterRealSrcDim->second->GetFullName();
|
|
oIter = mapSrcToDstDims.find(srcDimFullName);
|
|
if (oIter != mapSrcToDstDims.end())
|
|
{
|
|
dstArrayDims.emplace_back(oIter->second);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
const auto nDimSize = srcDim->GetSize();
|
|
std::string newDimNameFullName(srcDimFullName);
|
|
std::string newDimName(srcDim->GetName());
|
|
int nIncr = 2;
|
|
std::string osDstGroupFullName(poDstGroup->GetFullName());
|
|
if (osDstGroupFullName == "/")
|
|
osDstGroupFullName.clear();
|
|
auto oIter2 = mapDstDimFullNames.find(osDstGroupFullName + '/' +
|
|
srcDim->GetName());
|
|
while (oIter2 != mapDstDimFullNames.end() &&
|
|
oIter2->second->GetSize() != nDimSize)
|
|
{
|
|
newDimName = srcDim->GetName() + CPLSPrintf("_%d", nIncr);
|
|
newDimNameFullName = osDstGroupFullName + '/' + srcDim->GetName() +
|
|
CPLSPrintf("_%d", nIncr);
|
|
nIncr++;
|
|
oIter2 = mapDstDimFullNames.find(newDimNameFullName);
|
|
}
|
|
if (oIter2 != mapDstDimFullNames.end() &&
|
|
oIter2->second->GetSize() == nDimSize)
|
|
{
|
|
dstArrayDims.emplace_back(oIter2->second);
|
|
continue;
|
|
}
|
|
|
|
dstDim = poDstGroup->CreateDimension(newDimName, srcDim->GetType(),
|
|
srcDim->GetDirection(), nDimSize);
|
|
if (!dstDim)
|
|
return false;
|
|
if (!srcDimFullName.empty() && srcDimFullName[0] == '/')
|
|
{
|
|
mapSrcToDstDims[srcDimFullName] = dstDim;
|
|
}
|
|
mapDstDimFullNames[dstDim->GetFullName()] = dstDim;
|
|
dstArrayDims.emplace_back(dstDim);
|
|
|
|
std::shared_ptr<GDALMDArray> srcIndexVar;
|
|
GDALMDArray::Range range;
|
|
range.m_nStartIdx = 0;
|
|
range.m_nIncr = 1;
|
|
std::string indexingVarSpec;
|
|
if (idxSliceSpec >= 0)
|
|
{
|
|
const auto &viewSpec(viewSpecs[idxSliceSpec]);
|
|
auto iParentDim = viewSpec.m_mapDimIdxToParentDimIdx[i];
|
|
if (iParentDim != static_cast<size_t>(-1) &&
|
|
(srcIndexVar =
|
|
srcArrayDims[iParentDim]->GetIndexingVariable()) !=
|
|
nullptr &&
|
|
srcIndexVar->GetDimensionCount() == 1 &&
|
|
srcIndexVar->GetFullName() != srcArray->GetFullName())
|
|
{
|
|
CPLAssert(iParentDim < viewSpec.m_parentRanges.size());
|
|
range = viewSpec.m_parentRanges[iParentDim];
|
|
indexingVarSpec = "name=" + srcIndexVar->GetFullName();
|
|
indexingVarSpec += ",dstname=" + newDimName;
|
|
if (psOptions->aosSubset.empty() &&
|
|
psOptions->aosScaleFactor.empty())
|
|
{
|
|
if (range.m_nStartIdx != 0 || range.m_nIncr != 1 ||
|
|
srcArrayDims[iParentDim]->GetSize() !=
|
|
srcDim->GetSize())
|
|
{
|
|
indexingVarSpec += ",view=[";
|
|
if (range.m_nIncr > 0 ||
|
|
range.m_nStartIdx != srcDim->GetSize() - 1)
|
|
{
|
|
indexingVarSpec +=
|
|
CPLSPrintf(CPL_FRMT_GUIB, range.m_nStartIdx);
|
|
}
|
|
indexingVarSpec += ':';
|
|
if (range.m_nIncr > 0)
|
|
{
|
|
const auto nEndIdx =
|
|
range.m_nStartIdx +
|
|
range.m_nIncr * srcDim->GetSize();
|
|
indexingVarSpec +=
|
|
CPLSPrintf(CPL_FRMT_GUIB, nEndIdx);
|
|
}
|
|
else if (range.m_nStartIdx >
|
|
-range.m_nIncr * srcDim->GetSize())
|
|
{
|
|
const auto nEndIdx =
|
|
range.m_nStartIdx +
|
|
range.m_nIncr * srcDim->GetSize();
|
|
indexingVarSpec +=
|
|
CPLSPrintf(CPL_FRMT_GUIB, nEndIdx - 1);
|
|
}
|
|
indexingVarSpec += ':';
|
|
indexingVarSpec +=
|
|
CPLSPrintf(CPL_FRMT_GIB, range.m_nIncr);
|
|
indexingVarSpec += ']';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
srcIndexVar = srcDim->GetIndexingVariable();
|
|
if (srcIndexVar)
|
|
{
|
|
indexingVarSpec = srcIndexVar->GetFullName();
|
|
}
|
|
}
|
|
if (srcIndexVar && !indexingVarSpec.empty() &&
|
|
srcIndexVar->GetFullName() != srcArray->GetFullName())
|
|
{
|
|
if (poSrcRootGroup)
|
|
{
|
|
if (!TranslateArray(oDimRemapper, srcIndexVar, indexingVarSpec,
|
|
poSrcRootGroup, poSrcGroup, poDstRootGroup,
|
|
poDstGroup, poSrcDS, mapSrcToDstDims,
|
|
mapDstDimFullNames, psOptions))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
double adfGT[6];
|
|
if (poSrcDS->GetGeoTransform(adfGT) == CE_None &&
|
|
adfGT[2] == 0.0 && adfGT[4] == 0.0)
|
|
{
|
|
auto var = std::dynamic_pointer_cast<VRTMDArray>(
|
|
poDstGroup->CreateMDArray(
|
|
newDimName, {dstDim},
|
|
GDALExtendedDataType::Create(GDT_Float64)));
|
|
if (var)
|
|
{
|
|
const double dfStart =
|
|
srcIndexVar->GetName() == "X"
|
|
? adfGT[0] +
|
|
(range.m_nStartIdx + 0.5) * adfGT[1]
|
|
: adfGT[3] +
|
|
(range.m_nStartIdx + 0.5) * adfGT[5];
|
|
const double dfIncr =
|
|
(srcIndexVar->GetName() == "X" ? adfGT[1]
|
|
: adfGT[5]) *
|
|
range.m_nIncr;
|
|
std::unique_ptr<VRTMDArraySourceRegularlySpaced>
|
|
poSource(new VRTMDArraySourceRegularlySpaced(
|
|
dfStart, dfIncr));
|
|
var->AddSource(std::move(poSource));
|
|
}
|
|
}
|
|
}
|
|
|
|
CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
|
|
auto poDstIndexingVar(poDstGroup->OpenMDArray(newDimName));
|
|
if (poDstIndexingVar)
|
|
dstDim->SetIndexingVariable(std::move(poDstIndexingVar));
|
|
}
|
|
}
|
|
if (outputType.GetClass() == GEDTC_NUMERIC &&
|
|
outputType.GetNumericDataType() == GDT_Unknown)
|
|
{
|
|
outputType = GDALExtendedDataType(tmpArray->GetDataType());
|
|
}
|
|
auto dstArray =
|
|
poDstGroup->CreateMDArray(dstArrayName, dstArrayDims, outputType);
|
|
auto dstArrayVRT = std::dynamic_pointer_cast<VRTMDArray>(dstArray);
|
|
if (!dstArrayVRT)
|
|
return false;
|
|
|
|
GUInt64 nCurCost = 0;
|
|
dstArray->CopyFromAllExceptValues(srcArray.get(), false, nCurCost, 0,
|
|
nullptr, nullptr);
|
|
if (bResampled)
|
|
dstArray->SetSpatialRef(tmpArray->GetSpatialRef().get());
|
|
|
|
if (idxSliceSpec >= 0)
|
|
{
|
|
std::set<size_t> oSetParentDimIdxNotInArray;
|
|
for (size_t i = 0; i < srcArrayDims.size(); ++i)
|
|
{
|
|
oSetParentDimIdxNotInArray.insert(i);
|
|
}
|
|
const auto &viewSpec(viewSpecs[idxSliceSpec]);
|
|
for (size_t i = 0; i < tmpArrayDims.size(); ++i)
|
|
{
|
|
auto iParentDim = viewSpec.m_mapDimIdxToParentDimIdx[i];
|
|
if (iParentDim != static_cast<size_t>(-1))
|
|
{
|
|
oSetParentDimIdxNotInArray.erase(iParentDim);
|
|
}
|
|
}
|
|
for (const auto parentDimIdx : oSetParentDimIdxNotInArray)
|
|
{
|
|
const auto &srcDim(srcArrayDims[parentDimIdx]);
|
|
const auto nStartIdx =
|
|
viewSpec.m_parentRanges[parentDimIdx].m_nStartIdx;
|
|
if (nStartIdx < static_cast<GUInt64>(INT_MAX))
|
|
{
|
|
auto dstAttr = dstArray->CreateAttribute(
|
|
"DIM_" + srcDim->GetName() + "_INDEX", {},
|
|
GDALExtendedDataType::Create(GDT_Int32));
|
|
dstAttr->Write(static_cast<int>(nStartIdx));
|
|
}
|
|
else
|
|
{
|
|
auto dstAttr = dstArray->CreateAttribute(
|
|
"DIM_" + srcDim->GetName() + "_INDEX", {},
|
|
GDALExtendedDataType::CreateString());
|
|
dstAttr->Write(CPLSPrintf(CPL_FRMT_GUIB,
|
|
static_cast<GUIntBig>(nStartIdx)));
|
|
}
|
|
|
|
auto srcIndexVar(srcDim->GetIndexingVariable());
|
|
if (srcIndexVar && srcIndexVar->GetDimensionCount() == 1)
|
|
{
|
|
const auto &dt(srcIndexVar->GetDataType());
|
|
std::vector<GByte> abyTmp(dt.GetSize());
|
|
const size_t nCount = 1;
|
|
if (srcIndexVar->Read(&nStartIdx, &nCount, nullptr, nullptr, dt,
|
|
&abyTmp[0], nullptr, 0))
|
|
{
|
|
{
|
|
auto dstAttr = dstArray->CreateAttribute(
|
|
"DIM_" + srcDim->GetName() + "_VALUE", {}, dt);
|
|
dstAttr->Write(abyTmp.data(), abyTmp.size());
|
|
dt.FreeDynamicMemory(&abyTmp[0]);
|
|
}
|
|
|
|
const auto &unit(srcIndexVar->GetUnit());
|
|
if (!unit.empty())
|
|
{
|
|
auto dstAttr = dstArray->CreateAttribute(
|
|
"DIM_" + srcDim->GetName() + "_UNIT", {},
|
|
GDALExtendedDataType::CreateString());
|
|
dstAttr->Write(unit.c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
double dfStart = 0.0;
|
|
double dfIncrement = 0.0;
|
|
if (!bSrcArrayAccessibleThroughSrcGroup &&
|
|
tmpArray->IsRegularlySpaced(dfStart, dfIncrement))
|
|
{
|
|
auto poSource = std::make_unique<VRTMDArraySourceRegularlySpaced>(
|
|
dfStart, dfIncrement);
|
|
dstArrayVRT->AddSource(std::move(poSource));
|
|
}
|
|
else
|
|
{
|
|
const auto dimCount(tmpArray->GetDimensionCount());
|
|
std::vector<GUInt64> anSrcOffset(dimCount);
|
|
std::vector<GUInt64> anCount(dimCount);
|
|
for (size_t i = 0; i < dimCount; ++i)
|
|
{
|
|
anCount[i] = tmpArrayDims[i]->GetSize();
|
|
}
|
|
std::vector<GUInt64> anStep(dimCount, 1);
|
|
std::vector<GUInt64> anDstOffset(dimCount);
|
|
std::unique_ptr<VRTMDArraySourceFromArray> poSource(
|
|
new VRTMDArraySourceFromArray(
|
|
dstArrayVRT.get(), false, false, poSrcDS->GetDescription(),
|
|
band < 0 ? srcArray->GetFullName() : std::string(),
|
|
band >= 1 ? CPLSPrintf("%d", band) : std::string(),
|
|
std::move(anTransposedAxis),
|
|
bResampled
|
|
? (viewExpr.empty()
|
|
? std::string("resample=true")
|
|
: std::string("resample=true,").append(viewExpr))
|
|
: std::move(viewExpr),
|
|
std::move(anSrcOffset), std::move(anCount), std::move(anStep),
|
|
std::move(anDstOffset)));
|
|
dstArrayVRT->AddSource(std::move(poSource));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetGroup() */
|
|
/************************************************************************/
|
|
|
|
static std::shared_ptr<GDALGroup>
|
|
GetGroup(const std::shared_ptr<GDALGroup> &poRootGroup,
|
|
const std::string &fullName)
|
|
{
|
|
auto poCurGroup = poRootGroup;
|
|
CPLStringList aosTokens(CSLTokenizeString2(fullName.c_str(), "/", 0));
|
|
for (int i = 0; i < aosTokens.size(); i++)
|
|
{
|
|
auto poCurGroupNew = poCurGroup->OpenGroup(aosTokens[i], nullptr);
|
|
if (!poCurGroupNew)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Cannot find group %s",
|
|
aosTokens[i]);
|
|
return nullptr;
|
|
}
|
|
poCurGroup = std::move(poCurGroupNew);
|
|
}
|
|
return poCurGroup;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CopyGroup() */
|
|
/************************************************************************/
|
|
|
|
static bool CopyGroup(
|
|
DimensionRemapper &oDimRemapper,
|
|
const std::shared_ptr<GDALGroup> &poDstRootGroup,
|
|
std::shared_ptr<GDALGroup> &poDstGroup,
|
|
const std::shared_ptr<GDALGroup> &poSrcRootGroup,
|
|
const std::shared_ptr<GDALGroup> &poSrcGroup, GDALDataset *poSrcDS,
|
|
std::map<std::string, std::shared_ptr<GDALDimension>> &mapSrcToDstDims,
|
|
std::map<std::string, std::shared_ptr<GDALDimension>> &mapDstDimFullNames,
|
|
const GDALMultiDimTranslateOptions *psOptions, bool bRecursive)
|
|
{
|
|
const auto srcDims = poSrcGroup->GetDimensions();
|
|
std::map<std::string, std::string> mapSrcVariableNameToIndexedDimName;
|
|
for (const auto &dim : srcDims)
|
|
{
|
|
const auto poDimDesc = GetDimensionDesc(oDimRemapper, psOptions, dim);
|
|
if (poDimDesc == nullptr)
|
|
return false;
|
|
if (poDimDesc->bSlice)
|
|
continue;
|
|
auto dstDim =
|
|
poDstGroup->CreateDimension(dim->GetName(), dim->GetType(),
|
|
dim->GetDirection(), poDimDesc->nSize);
|
|
if (!dstDim)
|
|
return false;
|
|
mapSrcToDstDims[dim->GetFullName()] = dstDim;
|
|
mapDstDimFullNames[dstDim->GetFullName()] = dstDim;
|
|
auto poIndexingVarSrc(dim->GetIndexingVariable());
|
|
if (poIndexingVarSrc)
|
|
{
|
|
mapSrcVariableNameToIndexedDimName[poIndexingVarSrc->GetName()] =
|
|
dim->GetFullName();
|
|
}
|
|
}
|
|
|
|
if (!(poSrcGroup == poSrcRootGroup && psOptions->aosGroup.empty()))
|
|
{
|
|
auto attrs = poSrcGroup->GetAttributes();
|
|
for (const auto &attr : attrs)
|
|
{
|
|
auto dstAttr = poDstGroup->CreateAttribute(
|
|
attr->GetName(), attr->GetDimensionsSize(),
|
|
attr->GetDataType());
|
|
if (!dstAttr)
|
|
{
|
|
if (!psOptions->bStrict)
|
|
continue;
|
|
return false;
|
|
}
|
|
auto raw(attr->ReadAsRaw());
|
|
if (!dstAttr->Write(raw.data(), raw.size()) && !psOptions->bStrict)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
auto arrayNames =
|
|
poSrcGroup->GetMDArrayNames(psOptions->aosArrayOptions.List());
|
|
for (const auto &name : arrayNames)
|
|
{
|
|
if (!TranslateArray(oDimRemapper, nullptr, name, poSrcRootGroup,
|
|
poSrcGroup, poDstRootGroup, poDstGroup, poSrcDS,
|
|
mapSrcToDstDims, mapDstDimFullNames, psOptions))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If this array is the indexing variable of a dimension, link them
|
|
// together.
|
|
auto srcArray = poSrcGroup->OpenMDArray(name);
|
|
CPLAssert(srcArray);
|
|
auto dstArray = poDstGroup->OpenMDArray(name);
|
|
CPLAssert(dstArray);
|
|
auto oIterDimName =
|
|
mapSrcVariableNameToIndexedDimName.find(srcArray->GetName());
|
|
if (oIterDimName != mapSrcVariableNameToIndexedDimName.end())
|
|
{
|
|
auto oCorrespondingDimIter =
|
|
mapSrcToDstDims.find(oIterDimName->second);
|
|
if (oCorrespondingDimIter != mapSrcToDstDims.end())
|
|
{
|
|
CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
|
|
oCorrespondingDimIter->second->SetIndexingVariable(
|
|
std::move(dstArray));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bRecursive)
|
|
{
|
|
auto groupNames = poSrcGroup->GetGroupNames();
|
|
for (const auto &name : groupNames)
|
|
{
|
|
auto srcSubGroup = poSrcGroup->OpenGroup(name);
|
|
if (!srcSubGroup)
|
|
{
|
|
return false;
|
|
}
|
|
auto dstSubGroup = poDstGroup->CreateGroup(name);
|
|
if (!dstSubGroup)
|
|
{
|
|
return false;
|
|
}
|
|
if (!CopyGroup(oDimRemapper, poDstRootGroup, dstSubGroup,
|
|
poSrcRootGroup, srcSubGroup, poSrcDS,
|
|
mapSrcToDstDims, mapDstDimFullNames, psOptions,
|
|
true))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ParseGroupSpec() */
|
|
/************************************************************************/
|
|
|
|
// foo
|
|
// name=foo,dstname=bar,recursive=no
|
|
static bool ParseGroupSpec(const std::string &groupSpec, std::string &srcName,
|
|
std::string &dstName, bool &bRecursive)
|
|
{
|
|
bRecursive = true;
|
|
if (!STARTS_WITH(groupSpec.c_str(), "name="))
|
|
{
|
|
srcName = groupSpec;
|
|
return true;
|
|
}
|
|
|
|
CPLStringList aosTokens(CSLTokenizeString2(groupSpec.c_str(), ",", 0));
|
|
for (int i = 0; i < aosTokens.size(); i++)
|
|
{
|
|
const std::string token(aosTokens[i]);
|
|
if (STARTS_WITH(token.c_str(), "name="))
|
|
{
|
|
srcName = token.substr(strlen("name="));
|
|
}
|
|
else if (STARTS_WITH(token.c_str(), "dstname="))
|
|
{
|
|
dstName = token.substr(strlen("dstname="));
|
|
}
|
|
else if (token == "recursive=no")
|
|
{
|
|
bRecursive = false;
|
|
}
|
|
else
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Unexpected group specification part: %s", token.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* TranslateInternal() */
|
|
/************************************************************************/
|
|
|
|
static bool TranslateInternal(std::shared_ptr<GDALGroup> &poDstRootGroup,
|
|
GDALDataset *poSrcDS,
|
|
const GDALMultiDimTranslateOptions *psOptions)
|
|
{
|
|
|
|
auto poSrcRootGroup = poSrcDS->GetRootGroup();
|
|
if (poSrcRootGroup)
|
|
{
|
|
if (psOptions->aosGroup.empty())
|
|
{
|
|
auto attrs = poSrcRootGroup->GetAttributes();
|
|
for (const auto &attr : attrs)
|
|
{
|
|
if (attr->GetName() == "Conventions")
|
|
continue;
|
|
auto dstAttr = poDstRootGroup->CreateAttribute(
|
|
attr->GetName(), attr->GetDimensionsSize(),
|
|
attr->GetDataType());
|
|
if (dstAttr)
|
|
{
|
|
auto raw(attr->ReadAsRaw());
|
|
dstAttr->Write(raw.data(), raw.size());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DimensionRemapper oDimRemapper;
|
|
std::map<std::string, std::shared_ptr<GDALDimension>> mapSrcToDstDims;
|
|
std::map<std::string, std::shared_ptr<GDALDimension>> mapDstDimFullNames;
|
|
if (!psOptions->aosGroup.empty())
|
|
{
|
|
if (poSrcRootGroup == nullptr)
|
|
{
|
|
CPLError(
|
|
CE_Failure, CPLE_AppDefined,
|
|
"No multidimensional source dataset: -group cannot be used");
|
|
return false;
|
|
}
|
|
if (psOptions->aosGroup.size() == 1)
|
|
{
|
|
std::string srcName;
|
|
std::string dstName;
|
|
bool bRecursive;
|
|
if (!ParseGroupSpec(psOptions->aosGroup[0], srcName, dstName,
|
|
bRecursive))
|
|
return false;
|
|
auto poSrcGroup = GetGroup(poSrcRootGroup, srcName);
|
|
if (!poSrcGroup)
|
|
return false;
|
|
return CopyGroup(oDimRemapper, poDstRootGroup, poDstRootGroup,
|
|
poSrcRootGroup, poSrcGroup, poSrcDS,
|
|
mapSrcToDstDims, mapDstDimFullNames, psOptions,
|
|
bRecursive);
|
|
}
|
|
else
|
|
{
|
|
for (const auto &osGroupSpec : psOptions->aosGroup)
|
|
{
|
|
std::string srcName;
|
|
std::string dstName;
|
|
bool bRecursive;
|
|
if (!ParseGroupSpec(osGroupSpec, srcName, dstName, bRecursive))
|
|
return false;
|
|
auto poSrcGroup = GetGroup(poSrcRootGroup, srcName);
|
|
if (!poSrcGroup)
|
|
return false;
|
|
if (dstName.empty())
|
|
dstName = poSrcGroup->GetName();
|
|
auto dstSubGroup = poDstRootGroup->CreateGroup(dstName);
|
|
if (!dstSubGroup ||
|
|
!CopyGroup(oDimRemapper, poDstRootGroup, dstSubGroup,
|
|
poSrcRootGroup, poSrcGroup, poSrcDS,
|
|
mapSrcToDstDims, mapDstDimFullNames, psOptions,
|
|
bRecursive))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (!psOptions->aosArraySpec.empty())
|
|
{
|
|
for (const auto &arraySpec : psOptions->aosArraySpec)
|
|
{
|
|
if (!TranslateArray(oDimRemapper, nullptr, arraySpec,
|
|
poSrcRootGroup, poSrcRootGroup, poDstRootGroup,
|
|
poDstRootGroup, poSrcDS, mapSrcToDstDims,
|
|
mapDstDimFullNames, psOptions))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (poSrcRootGroup == nullptr)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"No multidimensional source dataset");
|
|
return false;
|
|
}
|
|
return CopyGroup(oDimRemapper, poDstRootGroup, poDstRootGroup,
|
|
poSrcRootGroup, poSrcRootGroup, poSrcDS,
|
|
mapSrcToDstDims, mapDstDimFullNames, psOptions, true);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CopyToNonMultiDimensionalDriver() */
|
|
/************************************************************************/
|
|
|
|
static GDALDatasetH
|
|
CopyToNonMultiDimensionalDriver(GDALDriver *poDriver, const char *pszDest,
|
|
const std::shared_ptr<GDALGroup> &poRG,
|
|
const GDALMultiDimTranslateOptions *psOptions)
|
|
{
|
|
std::shared_ptr<GDALMDArray> srcArray;
|
|
if (psOptions && !psOptions->aosArraySpec.empty())
|
|
{
|
|
if (psOptions->aosArraySpec.size() != 1)
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported,
|
|
"For output to a non-multidimensional driver, only "
|
|
"one array should be specified");
|
|
return nullptr;
|
|
}
|
|
std::string srcArrayName;
|
|
std::string dstArrayName;
|
|
int band = -1;
|
|
std::vector<int> anTransposedAxis;
|
|
std::string viewExpr;
|
|
GDALExtendedDataType outputType(
|
|
GDALExtendedDataType::Create(GDT_Unknown));
|
|
bool bResampled = false;
|
|
ParseArraySpec(psOptions->aosArraySpec[0], srcArrayName, dstArrayName,
|
|
band, anTransposedAxis, viewExpr, outputType,
|
|
bResampled);
|
|
srcArray = poRG->OpenMDArray(dstArrayName);
|
|
}
|
|
else
|
|
{
|
|
auto srcArrayNames = poRG->GetMDArrayNames(
|
|
psOptions ? psOptions->aosArrayOptions.List() : nullptr);
|
|
for (const auto &srcArrayName : srcArrayNames)
|
|
{
|
|
auto tmpArray = poRG->OpenMDArray(srcArrayName);
|
|
if (tmpArray)
|
|
{
|
|
const auto &dims(tmpArray->GetDimensions());
|
|
if (!(dims.size() == 1 && dims[0]->GetIndexingVariable() &&
|
|
dims[0]->GetIndexingVariable()->GetName() ==
|
|
srcArrayName))
|
|
{
|
|
if (srcArray)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Several arrays exist. Select one for "
|
|
"output to non-multidimensional driver");
|
|
return nullptr;
|
|
}
|
|
srcArray = std::move(tmpArray);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!srcArray)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Cannot find source array");
|
|
return nullptr;
|
|
}
|
|
size_t iXDim = static_cast<size_t>(-1);
|
|
size_t iYDim = static_cast<size_t>(-1);
|
|
const auto &dims(srcArray->GetDimensions());
|
|
for (size_t i = 0; i < dims.size(); ++i)
|
|
{
|
|
if (dims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X)
|
|
{
|
|
iXDim = i;
|
|
}
|
|
else if (dims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y)
|
|
{
|
|
iYDim = i;
|
|
}
|
|
}
|
|
if (dims.size() == 1)
|
|
{
|
|
iXDim = 0;
|
|
}
|
|
else if (dims.size() >= 2 && (iXDim == static_cast<size_t>(-1) ||
|
|
iYDim == static_cast<size_t>(-1)))
|
|
{
|
|
iXDim = dims.size() - 1;
|
|
iYDim = dims.size() - 2;
|
|
}
|
|
std::unique_ptr<GDALDataset> poTmpSrcDS(
|
|
srcArray->AsClassicDataset(iXDim, iYDim));
|
|
if (!poTmpSrcDS)
|
|
return nullptr;
|
|
return GDALDataset::ToHandle(poDriver->CreateCopy(
|
|
pszDest, poTmpSrcDS.get(), false,
|
|
psOptions ? const_cast<char **>(psOptions->aosCreateOptions.List())
|
|
: nullptr,
|
|
psOptions ? psOptions->pfnProgress : nullptr,
|
|
psOptions ? psOptions->pProgressData : nullptr));
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GDALMultiDimTranslate() */
|
|
/************************************************************************/
|
|
|
|
/* clang-format off */
|
|
/**
|
|
* Converts raster data between different formats.
|
|
*
|
|
* This is the equivalent of the
|
|
* <a href="/programs/gdalmdimtranslate.html">gdalmdimtranslate</a> utility.
|
|
*
|
|
* GDALMultiDimTranslateOptions* must be allocated and freed with
|
|
* GDALMultiDimTranslateOptionsNew() and GDALMultiDimTranslateOptionsFree()
|
|
* respectively. pszDest and hDstDS cannot be used at the same time.
|
|
*
|
|
* @param pszDest the destination dataset path or NULL.
|
|
* @param hDstDS the destination dataset or NULL.
|
|
* @param nSrcCount the number of input datasets.
|
|
* @param pahSrcDS the list of input datasets.
|
|
* @param psOptions the options struct returned by
|
|
* GDALMultiDimTranslateOptionsNew() or NULL.
|
|
* @param pbUsageError pointer to a integer output variable to store if any
|
|
* usage error has occurred or NULL.
|
|
* @return the output dataset (new dataset that must be closed using
|
|
* GDALClose(), or hDstDS is not NULL) or NULL in case of error.
|
|
*
|
|
* @since GDAL 3.1
|
|
*/
|
|
/* clang-format on */
|
|
|
|
GDALDatasetH
|
|
GDALMultiDimTranslate(const char *pszDest, GDALDatasetH hDstDS, int nSrcCount,
|
|
GDALDatasetH *pahSrcDS,
|
|
const GDALMultiDimTranslateOptions *psOptions,
|
|
int *pbUsageError)
|
|
{
|
|
if (pbUsageError)
|
|
*pbUsageError = false;
|
|
if (nSrcCount != 1 || pahSrcDS[0] == nullptr)
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported,
|
|
"Only one source dataset is supported");
|
|
if (pbUsageError)
|
|
*pbUsageError = true;
|
|
return nullptr;
|
|
}
|
|
|
|
if (hDstDS)
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported,
|
|
"Update of existing file not supported yet");
|
|
GDALClose(hDstDS);
|
|
return nullptr;
|
|
}
|
|
|
|
CPLString osFormat(psOptions ? psOptions->osFormat : "");
|
|
if (pszDest == nullptr /* && hDstDS == nullptr */)
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported,
|
|
"Both pszDest and hDstDS are NULL.");
|
|
if (pbUsageError)
|
|
*pbUsageError = true;
|
|
return nullptr;
|
|
}
|
|
|
|
GDALDriver *poDriver = nullptr;
|
|
|
|
#ifdef this_is_dead_code_for_now
|
|
const bool bCloseOutDSOnError = hDstDS == nullptr;
|
|
if (pszDest == nullptr)
|
|
pszDest = GDALGetDescription(hDstDS);
|
|
#endif
|
|
|
|
#ifdef this_is_dead_code_for_now
|
|
if (hDstDS == nullptr)
|
|
#endif
|
|
{
|
|
if (osFormat.empty())
|
|
{
|
|
if (EQUAL(CPLGetExtension(pszDest), "nc"))
|
|
osFormat = "netCDF";
|
|
else
|
|
osFormat = GetOutputDriverForRaster(pszDest);
|
|
if (osFormat.empty())
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
poDriver = GDALDriver::FromHandle(GDALGetDriverByName(osFormat));
|
|
char **papszDriverMD = poDriver ? poDriver->GetMetadata() : nullptr;
|
|
if (poDriver == nullptr ||
|
|
(!CPLTestBool(CSLFetchNameValueDef(papszDriverMD, GDAL_DCAP_RASTER,
|
|
"FALSE")) &&
|
|
!CPLTestBool(CSLFetchNameValueDef(
|
|
papszDriverMD, GDAL_DCAP_MULTIDIM_RASTER, "FALSE"))) ||
|
|
(!CPLTestBool(CSLFetchNameValueDef(papszDriverMD, GDAL_DCAP_CREATE,
|
|
"FALSE")) &&
|
|
!CPLTestBool(CSLFetchNameValueDef(
|
|
papszDriverMD, GDAL_DCAP_CREATECOPY, "FALSE")) &&
|
|
!CPLTestBool(CSLFetchNameValueDef(
|
|
papszDriverMD, GDAL_DCAP_CREATE_MULTIDIMENSIONAL, "FALSE")) &&
|
|
!CPLTestBool(CSLFetchNameValueDef(
|
|
papszDriverMD, GDAL_DCAP_CREATECOPY_MULTIDIMENSIONAL,
|
|
"FALSE"))))
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported,
|
|
"Output driver `%s' not recognised or does not support "
|
|
"output file creation.",
|
|
osFormat.c_str());
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
GDALDataset *poSrcDS = GDALDataset::FromHandle(pahSrcDS[0]);
|
|
|
|
std::unique_ptr<GDALDataset> poTmpDS;
|
|
GDALDataset *poTmpSrcDS = poSrcDS;
|
|
if (psOptions &&
|
|
(!psOptions->aosArraySpec.empty() || !psOptions->aosGroup.empty() ||
|
|
!psOptions->aosSubset.empty() || !psOptions->aosScaleFactor.empty() ||
|
|
!psOptions->aosArrayOptions.empty()))
|
|
{
|
|
poTmpDS.reset(VRTDataset::CreateMultiDimensional("", nullptr, nullptr));
|
|
CPLAssert(poTmpDS);
|
|
poTmpSrcDS = poTmpDS.get();
|
|
|
|
auto poDstRootGroup = poTmpDS->GetRootGroup();
|
|
CPLAssert(poDstRootGroup);
|
|
|
|
if (!TranslateInternal(poDstRootGroup, poSrcDS, psOptions))
|
|
{
|
|
#ifdef this_is_dead_code_for_now
|
|
if (bCloseOutDSOnError)
|
|
#endif
|
|
{
|
|
GDALClose(hDstDS);
|
|
hDstDS = nullptr;
|
|
}
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
auto poRG(poTmpSrcDS->GetRootGroup());
|
|
if (poRG &&
|
|
poDriver->GetMetadataItem(GDAL_DCAP_CREATE_MULTIDIMENSIONAL) ==
|
|
nullptr &&
|
|
poDriver->GetMetadataItem(GDAL_DCAP_CREATECOPY_MULTIDIMENSIONAL) ==
|
|
nullptr)
|
|
{
|
|
#ifdef this_is_dead_code_for_now
|
|
if (hDstDS)
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported,
|
|
"Appending to non-multidimensional driver not supported.");
|
|
GDALClose(hDstDS);
|
|
hDstDS = nullptr;
|
|
return nullptr;
|
|
}
|
|
#endif
|
|
hDstDS =
|
|
CopyToNonMultiDimensionalDriver(poDriver, pszDest, poRG, psOptions);
|
|
}
|
|
else
|
|
{
|
|
hDstDS = GDALDataset::ToHandle(poDriver->CreateCopy(
|
|
pszDest, poTmpSrcDS, false,
|
|
psOptions ? const_cast<char **>(psOptions->aosCreateOptions.List())
|
|
: nullptr,
|
|
psOptions ? psOptions->pfnProgress : nullptr,
|
|
psOptions ? psOptions->pProgressData : nullptr));
|
|
}
|
|
|
|
return hDstDS;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GDALMultiDimTranslateOptionsNew() */
|
|
/************************************************************************/
|
|
|
|
/**
|
|
* Allocates a GDALMultiDimTranslateOptions struct.
|
|
*
|
|
* @param papszArgv NULL terminated list of options (potentially including
|
|
* filename and open options too), or NULL. The accepted options are the ones of
|
|
* the <a href="/programs/gdalmdimtranslate.html">gdalmdimtranslate</a> utility.
|
|
* @param psOptionsForBinary should be nullptr, unless called from
|
|
* gdalmdimtranslate_bin.cpp
|
|
* @return pointer to the allocated GDALMultiDimTranslateOptions struct. Must be
|
|
* freed with GDALMultiDimTranslateOptionsFree().
|
|
*
|
|
* @since GDAL 3.1
|
|
*/
|
|
|
|
GDALMultiDimTranslateOptions *GDALMultiDimTranslateOptionsNew(
|
|
char **papszArgv, GDALMultiDimTranslateOptionsForBinary *psOptionsForBinary)
|
|
{
|
|
|
|
auto psOptions = std::make_unique<GDALMultiDimTranslateOptions>();
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Parse arguments. */
|
|
/* -------------------------------------------------------------------- */
|
|
try
|
|
{
|
|
auto argParser = GDALMultiDimTranslateAppOptionsGetParser(
|
|
psOptions.get(), psOptionsForBinary);
|
|
|
|
argParser->parse_args_without_binary_name(papszArgv);
|
|
|
|
// Check for invalid options:
|
|
// -scaleaxes is not compatible with -array = "view"
|
|
// -subset is not compatible with -array = "view"
|
|
if (std::find(psOptions->aosArraySpec.cbegin(),
|
|
psOptions->aosArraySpec.cend(),
|
|
"view") != psOptions->aosArraySpec.cend())
|
|
{
|
|
if (!psOptions->aosScaleFactor.empty())
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported,
|
|
"The -scaleaxes option is not compatible with the "
|
|
"-array \"view\" option.");
|
|
return nullptr;
|
|
}
|
|
|
|
if (!psOptions->aosSubset.empty())
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported,
|
|
"The -subset option is not compatible with the -array "
|
|
"\"view\" option.");
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
catch (const std::exception &error)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "%s", error.what());
|
|
return nullptr;
|
|
}
|
|
|
|
if (psOptionsForBinary)
|
|
{
|
|
// Note: bUpdate is apparently never changed by the command line options
|
|
psOptionsForBinary->bUpdate = psOptions->bUpdate;
|
|
if (!psOptions->osFormat.empty())
|
|
psOptionsForBinary->osFormat = psOptions->osFormat;
|
|
}
|
|
|
|
return psOptions.release();
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GDALMultiDimTranslateOptionsFree() */
|
|
/************************************************************************/
|
|
|
|
/**
|
|
* Frees the GDALMultiDimTranslateOptions struct.
|
|
*
|
|
* @param psOptions the options struct for GDALMultiDimTranslate().
|
|
*
|
|
* @since GDAL 3.1
|
|
*/
|
|
|
|
void GDALMultiDimTranslateOptionsFree(GDALMultiDimTranslateOptions *psOptions)
|
|
{
|
|
delete psOptions;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GDALMultiDimTranslateOptionsSetProgress() */
|
|
/************************************************************************/
|
|
|
|
/**
|
|
* Set a progress function.
|
|
*
|
|
* @param psOptions the options struct for GDALMultiDimTranslate().
|
|
* @param pfnProgress the progress callback.
|
|
* @param pProgressData the user data for the progress callback.
|
|
*
|
|
* @since GDAL 3.1
|
|
*/
|
|
|
|
void GDALMultiDimTranslateOptionsSetProgress(
|
|
GDALMultiDimTranslateOptions *psOptions, GDALProgressFunc pfnProgress,
|
|
void *pProgressData)
|
|
{
|
|
psOptions->pfnProgress = pfnProgress;
|
|
psOptions->pProgressData = pProgressData;
|
|
}
|