Coverage Report

Created: 2026-04-01 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalalg_mdim_mosaic.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  gdal "mdim mosaic" subcommand
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdalalg_mdim_mosaic.h"
14
15
#include "cpl_conv.h"
16
#include "cpl_vsi_virtual.h"
17
#include "gdal_priv.h"
18
#include "vrtdataset.h"
19
20
#include <algorithm>
21
#include <cmath>
22
#include <optional>
23
24
//! @cond Doxygen_Suppress
25
26
#ifndef _
27
0
#define _(x) (x)
28
#endif
29
30
/************************************************************************/
31
/*          GDALMdimMosaicAlgorithm::GDALMdimMosaicAlgorithm()          */
32
/************************************************************************/
33
34
GDALMdimMosaicAlgorithm::GDALMdimMosaicAlgorithm()
35
0
    : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
36
0
{
37
0
    AddProgressArg();
38
0
    AddOutputFormatArg(&m_outputFormat)
39
0
        .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
40
0
                         {GDAL_DCAP_CREATE_MULTIDIMENSIONAL});
41
0
    AddOpenOptionsArg(&m_openOptions);
42
0
    AddInputFormatsArg(&m_inputFormats)
43
0
        .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
44
0
                         {GDAL_ALG_DCAP_RASTER_OR_MULTIDIM_RASTER});
45
0
    AddInputDatasetArg(&m_inputDatasets, GDAL_OF_MULTIDIM_RASTER)
46
0
        .SetDatasetInputFlags(GADV_NAME)
47
0
        .SetDatasetOutputFlags(0)
48
0
        .SetAutoOpenDataset(false)
49
0
        .SetMinCount(1);
50
0
    AddOutputDatasetArg(&m_outputDataset, GDAL_OF_MULTIDIM_RASTER);
51
0
    AddCreationOptionsArg(&m_creationOptions);
52
0
    AddOverwriteArg(&m_overwrite);
53
0
    AddArrayNameArg(&m_array, _("Name of array(s) to mosaic."));
54
0
}
55
56
/************************************************************************/
57
/*                          GetDimensionDesc()                          */
58
/************************************************************************/
59
60
std::optional<GDALMdimMosaicAlgorithm::DimensionDesc>
61
GDALMdimMosaicAlgorithm::GetDimensionDesc(
62
    const std::string &osDSName,
63
    const std::shared_ptr<GDALDimension> &poDim) const
64
0
{
65
0
    auto poVar = poDim->GetIndexingVariable();
66
0
    if (poVar)
67
0
    {
68
0
        if (poVar->GetDimensionCount() != 1)
69
0
        {
70
0
            ReportError(
71
0
                CE_Failure, CPLE_AppDefined,
72
0
                "Dataset %s: indexing variable %s of dimension %s is not 1D",
73
0
                osDSName.c_str(), poVar->GetName().c_str(),
74
0
                poDim->GetName().c_str());
75
0
            return std::nullopt;
76
0
        }
77
0
        if (poVar->GetDataType().GetClass() != GEDTC_NUMERIC)
78
0
        {
79
0
            ReportError(
80
0
                CE_Failure, CPLE_AppDefined,
81
0
                "Dataset %s: indexing variable %s of dimension %s has a "
82
0
                "non-numeric data type",
83
0
                osDSName.c_str(), poVar->GetName().c_str(),
84
0
                poDim->GetName().c_str());
85
0
            return std::nullopt;
86
0
        }
87
0
    }
88
0
    DimensionDesc desc;
89
0
    desc.osName = poDim->GetName();
90
0
    desc.osType = poDim->GetType();
91
0
    desc.osDirection = poDim->GetDirection();
92
0
    const auto nSize = poDim->GetSize();
93
0
    if (poVar)
94
0
        desc.attributes = poVar->GetAttributes();
95
0
    CPLAssert(nSize > 0);
96
0
    desc.nSize = nSize;
97
0
    desc.bHasIndexingVar = poVar != nullptr;
98
0
    if (!poVar)
99
0
    {
100
0
        desc.dfStart = 0;
101
0
        desc.dfIncrement = 1;
102
0
    }
103
0
    else if (nSize <= 2 ||
104
0
             !poVar->IsRegularlySpaced(desc.dfStart, desc.dfIncrement))
105
0
    {
106
0
        constexpr uint64_t LIMIT = 100 * 1000 * 1000;
107
0
        if (nSize > LIMIT)
108
0
        {
109
0
            ReportError(CE_Failure, CPLE_AppDefined,
110
0
                        "Dataset %s: indexing variable %s of dimension %s has "
111
0
                        "too large size",
112
0
                        osDSName.c_str(), poVar->GetName().c_str(),
113
0
                        desc.osName.c_str());
114
0
            return std::nullopt;
115
0
        }
116
0
        std::vector<double> adfValues(static_cast<size_t>(nSize));
117
0
        GUInt64 arrayStartIdx[] = {0};
118
0
        size_t anCount[] = {adfValues.size()};
119
0
        GInt64 arrayStep[] = {1};
120
0
        GPtrDiff_t bufferStride[] = {1};
121
0
        if (!poVar->Read(arrayStartIdx, anCount, arrayStep, bufferStride,
122
0
                         GDALExtendedDataType::Create(GDT_Float64),
123
0
                         adfValues.data()))
124
0
        {
125
0
            return std::nullopt;
126
0
        }
127
0
        if (std::isnan(adfValues[0]))
128
0
        {
129
0
            ReportError(CE_Failure, CPLE_AppDefined,
130
0
                        "Dataset %s: indexing variable %s of dimension %s has "
131
0
                        "NaN values",
132
0
                        osDSName.c_str(), poVar->GetName().c_str(),
133
0
                        desc.osName.c_str());
134
0
            return std::nullopt;
135
0
        }
136
0
        if (nSize > 1)
137
0
        {
138
0
            const int nSign = adfValues[1] > adfValues[0] ? 1 : -1;
139
0
            if (std::isnan(adfValues[1]))
140
0
            {
141
0
                ReportError(CE_Failure, CPLE_AppDefined,
142
0
                            "Dataset %s: indexing variable %s of dimension %s "
143
0
                            "has NaN values",
144
0
                            osDSName.c_str(), poVar->GetName().c_str(),
145
0
                            desc.osName.c_str());
146
0
                return std::nullopt;
147
0
            }
148
0
            for (size_t i = 2; i < nSize; ++i)
149
0
            {
150
0
                if (std::isnan(adfValues[i]))
151
0
                {
152
0
                    ReportError(CE_Failure, CPLE_AppDefined,
153
0
                                "Dataset %s: indexing variable %s of dimension "
154
0
                                "%s has NaN values",
155
0
                                osDSName.c_str(), poVar->GetName().c_str(),
156
0
                                desc.osName.c_str());
157
0
                    return std::nullopt;
158
0
                }
159
0
                const int nSign2 = adfValues[i] > adfValues[i - 1] ? 1 : -1;
160
0
                if (nSign != nSign2)
161
0
                {
162
0
                    ReportError(CE_Failure, CPLE_AppDefined,
163
0
                                "Dataset %s: indexing variable %s of dimension "
164
0
                                "%s is not strictly increasing or decreasing",
165
0
                                osDSName.c_str(), poVar->GetName().c_str(),
166
0
                                desc.osName.c_str());
167
0
                    return std::nullopt;
168
0
                }
169
0
            }
170
0
            desc.nProgressionSign = nSign;
171
0
        }
172
0
        std::sort(adfValues.begin(), adfValues.end());
173
0
        desc.aaValues.push_back(std::move(adfValues));
174
0
    }
175
0
    return desc;
176
0
}
177
178
/************************************************************************/
179
/*           GDALMdimMosaicAlgorithm::BuildArrayParameters()            */
180
/************************************************************************/
181
182
bool GDALMdimMosaicAlgorithm::BuildArrayParameters(
183
    const CPLStringList &aosInputDatasetNames,
184
    std::vector<ArrayParameters> &aoArrayParameters)
185
0
{
186
0
    for (const char *pszDatasetName : cpl::Iterate(aosInputDatasetNames))
187
0
    {
188
0
        auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
189
0
            pszDatasetName, GDAL_OF_MULTIDIM_RASTER | GDAL_OF_VERBOSE_ERROR,
190
0
            CPLStringList(m_inputFormats).List(),
191
0
            CPLStringList(m_openOptions).List(), nullptr));
192
0
        if (!poDS)
193
0
            return false;
194
0
        auto poRG = poDS->GetRootGroup();
195
0
        if (!poRG)
196
0
        {
197
0
            ReportError(CE_Failure, CPLE_AppDefined,
198
0
                        "Cannot get root group for dataset %s", pszDatasetName);
199
0
            return false;
200
0
        }
201
0
        std::vector<std::shared_ptr<GDALMDArray>> apoArrays;
202
0
        if (!m_array.empty())
203
0
        {
204
0
            for (const auto &array : m_array)
205
0
            {
206
0
                auto poArray = poRG->OpenMDArrayFromFullname(array);
207
0
                if (!poArray)
208
0
                {
209
0
                    ReportError(CE_Failure, CPLE_AppDefined,
210
0
                                "Cannot find array %s in dataset %s",
211
0
                                array.c_str(), pszDatasetName);
212
0
                    return false;
213
0
                }
214
0
                apoArrays.push_back(std::move(poArray));
215
0
            }
216
0
        }
217
0
        else
218
0
        {
219
0
            for (const std::string &arrayName :
220
0
                 poRG->GetMDArrayFullNamesRecursive())
221
0
            {
222
0
                auto poArray = poRG->OpenMDArrayFromFullname(arrayName);
223
0
                if (!poArray)
224
0
                {
225
0
                    ReportError(CE_Failure, CPLE_AppDefined,
226
0
                                "Cannot open array %s of dataset %s",
227
0
                                arrayName.c_str(), pszDatasetName);
228
0
                    return false;
229
0
                }
230
0
                if (poArray->GetDimensionCount() < 2)
231
0
                    continue;
232
0
                m_array.push_back(arrayName);
233
0
                apoArrays.push_back(std::move(poArray));
234
0
            }
235
0
            if (apoArrays.empty())
236
0
            {
237
0
                ReportError(
238
0
                    CE_Failure, CPLE_AppDefined,
239
0
                    "No array of dimension count >= 2 found in dataset %s",
240
0
                    pszDatasetName);
241
0
                return false;
242
0
            }
243
0
        }
244
245
0
        if (aoArrayParameters.empty())
246
0
            aoArrayParameters.resize(apoArrays.size());
247
248
0
        for (size_t iArray = 0; iArray < apoArrays.size(); ++iArray)
249
0
        {
250
0
            auto &arrayParameters = aoArrayParameters[iArray];
251
0
            auto &poArray = apoArrays[iArray];
252
0
            if (poArray->GetDimensionCount() == 0)
253
0
            {
254
0
                ReportError(CE_Failure, CPLE_AppDefined,
255
0
                            "Array %s of dataset %s has no dimensions",
256
0
                            poArray->GetName().c_str(), pszDatasetName);
257
0
                return false;
258
0
            }
259
260
0
            std::vector<SourceShortDimDesc> aoSourceShortDimDesc;
261
0
            const auto AddToSourceShortDimDesc =
262
0
                [&aoSourceShortDimDesc](const DimensionDesc &dimDesc,
263
0
                                        uint64_t nSize)
264
0
            {
265
0
                SourceShortDimDesc sourceDesc;
266
0
                sourceDesc.nSize = nSize;
267
0
                sourceDesc.bIsRegularlySpaced = dimDesc.aaValues.empty();
268
0
                if (sourceDesc.bIsRegularlySpaced)
269
0
                    sourceDesc.dfStart = dimDesc.dfStart;
270
0
                else
271
0
                    sourceDesc.dfStart = dimDesc.aaValues[0][0];
272
0
                aoSourceShortDimDesc.push_back(std::move(sourceDesc));
273
0
            };
274
275
0
            const auto anBlockSize = poArray->GetBlockSize();
276
0
            CPLAssert(anBlockSize.size() == poArray->GetDimensionCount());
277
278
0
            if (!arrayParameters.poFirstSourceArray)
279
0
            {
280
0
                arrayParameters.poFirstSourceArray = poArray;
281
0
                CPLAssert(arrayParameters.mosaicDimensions.empty());
282
0
                size_t iDim = 0;
283
0
                for (auto &poDim : poArray->GetDimensions())
284
0
                {
285
0
                    auto descOpt = GetDimensionDesc(pszDatasetName, poDim);
286
0
                    if (!descOpt.has_value())
287
0
                        return false;
288
0
                    auto &desc = descOpt.value();
289
0
                    AddToSourceShortDimDesc(desc, poDim->GetSize());
290
0
                    desc.nBlockSize = anBlockSize[iDim];
291
0
                    arrayParameters.mosaicDimensions.push_back(std::move(desc));
292
0
                    ++iDim;
293
0
                }
294
0
            }
295
0
            else
296
0
            {
297
0
                if (poArray->GetDimensionCount() !=
298
0
                    arrayParameters.mosaicDimensions.size())
299
0
                {
300
0
                    ReportError(CE_Failure, CPLE_AppDefined,
301
0
                                "Array %s of dataset %s does not have the same "
302
0
                                "number of dimensions as in other datasets",
303
0
                                poArray->GetName().c_str(), pszDatasetName);
304
0
                    return false;
305
0
                }
306
0
                if (poArray->GetDataType() !=
307
0
                    arrayParameters.poFirstSourceArray->GetDataType())
308
0
                {
309
0
                    ReportError(CE_Failure, CPLE_AppDefined,
310
0
                                "Array %s of dataset %s does not have the same "
311
0
                                "data type as in other datasets",
312
0
                                poArray->GetName().c_str(), pszDatasetName);
313
0
                    return false;
314
0
                }
315
0
                const void *poFirstRawNoData =
316
0
                    arrayParameters.poFirstSourceArray->GetRawNoDataValue();
317
0
                const void *poRawNoData = poArray->GetRawNoDataValue();
318
0
                if (!((!poFirstRawNoData && !poRawNoData) ||
319
0
                      (poFirstRawNoData && poRawNoData &&
320
0
                       memcmp(poFirstRawNoData, poRawNoData,
321
0
                              poArray->GetDataType().GetSize()) == 0)))
322
0
                {
323
0
                    ReportError(CE_Failure, CPLE_AppDefined,
324
0
                                "Array %s of dataset %s does not have the same "
325
0
                                "nodata value as in other datasets",
326
0
                                poArray->GetName().c_str(), pszDatasetName);
327
0
                    return false;
328
0
                }
329
0
                const std::vector<std::shared_ptr<GDALDimension>> apoDims =
330
0
                    poArray->GetDimensions();
331
0
                for (size_t iDim = 0;
332
0
                     iDim < arrayParameters.mosaicDimensions.size(); ++iDim)
333
0
                {
334
0
                    DimensionDesc &desc =
335
0
                        arrayParameters.mosaicDimensions[iDim];
336
0
                    auto &poDim = apoDims[iDim];
337
0
                    if (poDim->GetName() != desc.osName)
338
0
                    {
339
0
                        ReportError(
340
0
                            CE_Failure, CPLE_AppDefined,
341
0
                            "Dimension %d of array %s of dataset %s does "
342
0
                            "not have the same name as in other datasets",
343
0
                            static_cast<int>(iDim), poArray->GetName().c_str(),
344
0
                            pszDatasetName);
345
0
                        return false;
346
0
                    }
347
0
                    if (desc.nBlockSize != anBlockSize[iDim])
348
0
                        desc.nBlockSize = 0;
349
350
0
                    auto descThisDatasetOpt =
351
0
                        GetDimensionDesc(pszDatasetName, poDim);
352
0
                    if (!descThisDatasetOpt.has_value())
353
0
                        return false;
354
0
                    auto &descThisDataset = descThisDatasetOpt.value();
355
0
                    AddToSourceShortDimDesc(descThisDataset, poDim->GetSize());
356
0
                    if (descThisDataset.bHasIndexingVar &&
357
0
                        !desc.bHasIndexingVar)
358
0
                    {
359
0
                        ReportError(CE_Failure, CPLE_AppDefined,
360
0
                                    "Dimension %s of array %s of dataset %s "
361
0
                                    "has an indexing variable, contrary "
362
0
                                    "to other datasets",
363
0
                                    poDim->GetName().c_str(),
364
0
                                    poArray->GetName().c_str(), pszDatasetName);
365
0
                        return false;
366
0
                    }
367
0
                    else if (!descThisDataset.bHasIndexingVar &&
368
0
                             desc.bHasIndexingVar)
369
0
                    {
370
0
                        ReportError(
371
0
                            CE_Failure, CPLE_AppDefined,
372
0
                            "Dimension %s of array %s of dataset %s "
373
0
                            "does not have an indexing variable, contrary "
374
0
                            "to other datasets",
375
0
                            poDim->GetName().c_str(),
376
0
                            poArray->GetName().c_str(), pszDatasetName);
377
0
                        return false;
378
0
                    }
379
0
                    else if (!desc.bHasIndexingVar &&
380
0
                             descThisDataset.nSize != desc.nSize)
381
0
                    {
382
0
                        ReportError(
383
0
                            CE_Failure, CPLE_AppDefined,
384
0
                            "Dimension %s of array %s of dataset %s "
385
0
                            "does not have the same size as in other datasets",
386
0
                            poDim->GetName().c_str(),
387
0
                            poArray->GetName().c_str(), pszDatasetName);
388
0
                        return false;
389
0
                    }
390
0
                    else if (desc.bHasIndexingVar && desc.aaValues.empty())
391
0
                    {
392
0
                        if (!descThisDataset.aaValues.empty())
393
0
                        {
394
0
                            ReportError(
395
0
                                CE_Failure, CPLE_AppDefined,
396
0
                                "Dimension %s of array %s of dataset %s "
397
0
                                "has irregularly-spaced values, contrary "
398
0
                                "to other datasets",
399
0
                                poDim->GetName().c_str(),
400
0
                                poArray->GetName().c_str(), pszDatasetName);
401
0
                            return false;
402
0
                        }
403
0
                        if (std::fabs(descThisDataset.dfIncrement -
404
0
                                      desc.dfIncrement) >
405
0
                            1e-10 * std::fabs(desc.dfIncrement))
406
0
                        {
407
0
                            ReportError(
408
0
                                CE_Failure, CPLE_AppDefined,
409
0
                                "Dimension %s of array %s of dataset %s is "
410
0
                                "indexed by a variable with spacing %g, "
411
0
                                "whereas it is %g in other datasets",
412
0
                                poDim->GetName().c_str(),
413
0
                                poArray->GetName().c_str(), pszDatasetName,
414
0
                                descThisDataset.dfIncrement, desc.dfIncrement);
415
0
                            return false;
416
0
                        }
417
0
                        const double dfPos =
418
0
                            (descThisDataset.dfStart - desc.dfStart) /
419
0
                            desc.dfIncrement;
420
0
                        if (std::fabs(std::round(dfPos) - dfPos) > 1e-3)
421
0
                        {
422
0
                            ReportError(CE_Failure, CPLE_AppDefined,
423
0
                                        "Dimension %s of array %s of dataset "
424
0
                                        "%s is indexed "
425
0
                                        "by a variable whose start value is "
426
0
                                        "not aligned "
427
0
                                        "with the one of other datasets",
428
0
                                        poDim->GetName().c_str(),
429
0
                                        poArray->GetName().c_str(),
430
0
                                        pszDatasetName);
431
0
                            return false;
432
0
                        }
433
0
                        const double dfEnd = std::max(
434
0
                            desc.dfStart + static_cast<double>(desc.nSize) *
435
0
                                               desc.dfIncrement,
436
0
                            descThisDataset.dfStart +
437
0
                                static_cast<double>(descThisDataset.nSize) *
438
0
                                    descThisDataset.dfIncrement);
439
0
                        desc.dfStart =
440
0
                            std::min(desc.dfStart, descThisDataset.dfStart);
441
0
                        const double dfSize =
442
0
                            (dfEnd - desc.dfStart) / desc.dfIncrement;
443
0
                        constexpr double MAX_INTEGER_REPRESENTABLE =
444
0
                            static_cast<double>(1ULL << 53);
445
0
                        if (dfSize > MAX_INTEGER_REPRESENTABLE)
446
0
                        {
447
0
                            ReportError(
448
0
                                CE_Failure, CPLE_AppDefined,
449
0
                                "Dimension %s of array %s of dataset %s "
450
0
                                "would be too large if merged.",
451
0
                                poDim->GetName().c_str(),
452
0
                                poArray->GetName().c_str(), pszDatasetName);
453
0
                            return false;
454
0
                        }
455
0
                        desc.nSize = static_cast<uint64_t>(dfSize + 0.5);
456
0
                    }
457
0
                    else if (desc.bHasIndexingVar)
458
0
                    {
459
0
                        if (descThisDataset.aaValues.empty())
460
0
                        {
461
0
                            ReportError(
462
0
                                CE_Failure, CPLE_AppDefined,
463
0
                                "Dimension %s of array %s of dataset %s "
464
0
                                "has regularly spaced labels, contrary to "
465
0
                                "other datasets",
466
0
                                poDim->GetName().c_str(),
467
0
                                poArray->GetName().c_str(), pszDatasetName);
468
0
                            return false;
469
0
                        }
470
0
                        if (descThisDataset.nProgressionSign !=
471
0
                            desc.nProgressionSign)
472
0
                        {
473
0
                            ReportError(
474
0
                                CE_Failure, CPLE_AppDefined,
475
0
                                "Dataset %s: values in indexing variable %s of "
476
0
                                "dimension %s must be either increasing or "
477
0
                                "decreasing in all input datasets.",
478
0
                                pszDatasetName,
479
0
                                poDim->GetIndexingVariable()->GetName().c_str(),
480
0
                                desc.osName.c_str());
481
0
                            return false;
482
0
                        }
483
0
                        CPLAssert(descThisDataset.aaValues.size() == 1);
484
0
                        if (descThisDataset.aaValues[0][0] <
485
0
                            desc.aaValues[0][0])
486
0
                        {
487
0
                            if (descThisDataset.aaValues[0].back() >=
488
0
                                desc.aaValues[0][0])
489
0
                            {
490
0
                                ReportError(
491
0
                                    CE_Failure, CPLE_AppDefined,
492
0
                                    "Dataset %s: values in indexing variable "
493
0
                                    "%s of "
494
0
                                    "dimension %s are not the same as in other "
495
0
                                    "datasets",
496
0
                                    pszDatasetName,
497
0
                                    poDim->GetIndexingVariable()
498
0
                                        ->GetName()
499
0
                                        .c_str(),
500
0
                                    desc.osName.c_str());
501
0
                                return false;
502
0
                            }
503
0
                            desc.aaValues.insert(
504
0
                                desc.aaValues.begin(),
505
0
                                std::move(descThisDataset.aaValues[0]));
506
0
                        }
507
0
                        else
508
0
                        {
509
0
                            for (size_t i = 0; i < desc.aaValues.size(); ++i)
510
0
                            {
511
0
                                if (descThisDataset.aaValues[0][0] ==
512
0
                                    desc.aaValues[i][0])
513
0
                                {
514
0
                                    if (descThisDataset.aaValues[0] !=
515
0
                                        desc.aaValues[i])
516
0
                                    {
517
0
                                        ReportError(
518
0
                                            CE_Failure, CPLE_AppDefined,
519
0
                                            "Dataset %s: values in indexing "
520
0
                                            "variable %s of dimension %s are "
521
0
                                            "not "
522
0
                                            "the same as in other datasets",
523
0
                                            pszDatasetName,
524
0
                                            poDim->GetIndexingVariable()
525
0
                                                ->GetName()
526
0
                                                .c_str(),
527
0
                                            desc.osName.c_str());
528
0
                                        return false;
529
0
                                    }
530
0
                                }
531
0
                                else if (descThisDataset.aaValues[0][0] >
532
0
                                             desc.aaValues[i][0] &&
533
0
                                         (i + 1 == desc.aaValues.size() ||
534
0
                                          descThisDataset.aaValues[0][0] <
535
0
                                              desc.aaValues[i + 1][0]))
536
0
                                {
537
0
                                    if (descThisDataset.aaValues[0][0] <=
538
0
                                        desc.aaValues[i].back())
539
0
                                    {
540
0
                                        ReportError(
541
0
                                            CE_Failure, CPLE_AppDefined,
542
0
                                            "Dataset %s: values in indexing "
543
0
                                            "variable %s of dimension %s are "
544
0
                                            "overlapping with the ones of "
545
0
                                            "other "
546
0
                                            "datasets",
547
0
                                            pszDatasetName,
548
0
                                            poDim->GetIndexingVariable()
549
0
                                                ->GetName()
550
0
                                                .c_str(),
551
0
                                            desc.osName.c_str());
552
0
                                        return false;
553
0
                                    }
554
0
                                    if (i + 1 < desc.aaValues.size() &&
555
0
                                        descThisDataset.aaValues[0].back() >=
556
0
                                            desc.aaValues[i + 1][0])
557
0
                                    {
558
0
                                        ReportError(
559
0
                                            CE_Failure, CPLE_AppDefined,
560
0
                                            "Dataset %s: values in indexing "
561
0
                                            "variable %s of dimension %s are "
562
0
                                            "overlapping with the ones of "
563
0
                                            "other "
564
0
                                            "datasets",
565
0
                                            pszDatasetName,
566
0
                                            poDim->GetIndexingVariable()
567
0
                                                ->GetName()
568
0
                                                .c_str(),
569
0
                                            desc.osName.c_str());
570
0
                                        return false;
571
0
                                    }
572
0
                                    desc.aaValues.insert(
573
0
                                        desc.aaValues.begin() + i + 1,
574
0
                                        std::move(descThisDataset.aaValues[0]));
575
0
                                    break;
576
0
                                }
577
0
                            }
578
0
                        }
579
0
                    }
580
0
                }
581
0
            }
582
583
0
            arrayParameters.aaoSourceShortDimDesc.push_back(
584
0
                std::move(aoSourceShortDimDesc));
585
0
        }
586
0
    }
587
588
0
    return true;
589
0
}
590
591
/************************************************************************/
592
/*           GDALMdimMosaicAlgorithm::GetInputDatasetNames()            */
593
/************************************************************************/
594
595
bool GDALMdimMosaicAlgorithm::GetInputDatasetNames(
596
    GDALProgressFunc pfnProgress, void *pProgressData,
597
    CPLStringList &aosInputDatasetNames) const
598
0
{
599
0
    for (auto &ds : m_inputDatasets)
600
0
    {
601
0
        if (ds.GetName()[0] == '@')
602
0
        {
603
0
            auto f = VSIVirtualHandleUniquePtr(
604
0
                VSIFOpenL(ds.GetName().c_str() + 1, "r"));
605
0
            if (!f)
606
0
            {
607
0
                ReportError(CE_Failure, CPLE_FileIO, "Cannot open %s",
608
0
                            ds.GetName().c_str() + 1);
609
0
                return false;
610
0
            }
611
0
            while (const char *filename = CPLReadLineL(f.get()))
612
0
            {
613
0
                aosInputDatasetNames.push_back(filename);
614
0
            }
615
0
        }
616
0
        else if (ds.GetName().find_first_of("*?[") != std::string::npos)
617
0
        {
618
0
            CPLStringList aosMatches(VSIGlob(ds.GetName().c_str(), nullptr,
619
0
                                             pfnProgress, pProgressData));
620
0
            for (const char *pszStr : aosMatches)
621
0
            {
622
0
                aosInputDatasetNames.push_back(pszStr);
623
0
            }
624
0
        }
625
0
        else
626
0
        {
627
0
            std::string osDatasetName = ds.GetName();
628
0
            if (!GetReferencePathForRelativePaths().empty())
629
0
            {
630
0
                osDatasetName = GDALDataset::BuildFilename(
631
0
                    osDatasetName.c_str(),
632
0
                    GetReferencePathForRelativePaths().c_str(), true);
633
0
            }
634
0
            aosInputDatasetNames.push_back(osDatasetName.c_str());
635
0
        }
636
0
    }
637
0
    return true;
638
0
}
639
640
/************************************************************************/
641
/*                  GDALMdimMosaicAlgorithm::RunImpl()                  */
642
/************************************************************************/
643
644
bool GDALMdimMosaicAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
645
                                      void *pProgressData)
646
0
{
647
0
    CPLAssert(!m_outputDataset.GetDatasetRef());
648
649
0
    if (m_outputFormat.empty())
650
0
    {
651
0
        const auto aosFormats =
652
0
            CPLStringList(GDALGetOutputDriversForDatasetName(
653
0
                m_outputDataset.GetName().c_str(), GDAL_OF_MULTIDIM_RASTER,
654
0
                /* bSingleMatch = */ true,
655
0
                /* bWarn = */ true));
656
0
        if (aosFormats.size() != 1)
657
0
        {
658
0
            ReportError(CE_Failure, CPLE_AppDefined,
659
0
                        "Cannot guess driver for %s",
660
0
                        m_outputDataset.GetName().c_str());
661
0
            return false;
662
0
        }
663
0
        m_outputFormat = aosFormats[0];
664
0
    }
665
0
    auto poOutDrv =
666
0
        GetGDALDriverManager()->GetDriverByName(m_outputFormat.c_str());
667
0
    if (!poOutDrv)
668
0
    {
669
0
        ReportError(CE_Failure, CPLE_AppDefined, "Driver %s does not exist",
670
0
                    m_outputFormat.c_str());
671
0
        return false;
672
0
    }
673
674
0
    const bool bIsVRT = EQUAL(m_outputFormat.c_str(), "VRT");
675
676
0
    CPLStringList aosInputDatasetNames;
677
0
    const double dfIntermediatePercentage = bIsVRT ? 1.0 : 0.1;
678
0
    std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)> pScaledData(
679
0
        GDALCreateScaledProgress(0.0, dfIntermediatePercentage, pfnProgress,
680
0
                                 pProgressData),
681
0
        GDALDestroyScaledProgress);
682
0
    if (!GetInputDatasetNames(GDALScaledProgress, pScaledData.get(),
683
0
                              aosInputDatasetNames))
684
0
        return false;
685
686
0
    for (std::string &name : m_array)
687
0
    {
688
0
        if (!name.empty() && name[0] != '/')
689
0
            name = "/" + name;
690
0
    }
691
692
0
    std::vector<ArrayParameters> aoArrayParameters;
693
0
    if (!BuildArrayParameters(aosInputDatasetNames, aoArrayParameters))
694
0
    {
695
0
        return false;
696
0
    }
697
698
0
    auto poVRTDS = VRTDataset::CreateVRTMultiDimensional("", nullptr, nullptr);
699
0
    CPLAssert(poVRTDS);
700
701
0
    auto poDstGroup = poVRTDS->GetRootVRTGroup();
702
0
    CPLAssert(poDstGroup);
703
704
0
    std::map<std::string, std::shared_ptr<GDALDimension>>
705
0
        oMapAlreadyCreatedDims;
706
707
    // Iterate over arrays
708
0
    for (auto &arrayParameters : aoArrayParameters)
709
0
    {
710
0
        auto &poFirstSourceArray = arrayParameters.poFirstSourceArray;
711
0
        CPLAssert(poFirstSourceArray);
712
0
        auto &aaoSourceShortDimDesc = arrayParameters.aaoSourceShortDimDesc;
713
0
        CPLAssert(aaoSourceShortDimDesc.size() ==
714
0
                  static_cast<size_t>(aosInputDatasetNames.size()));
715
716
        // Create mosaic array dimensions
717
0
        std::vector<std::shared_ptr<GDALDimension>> apoDstDims;
718
0
        for (auto &desc : arrayParameters.mosaicDimensions)
719
0
        {
720
0
            auto oIterCreatedDims = oMapAlreadyCreatedDims.find(desc.osName);
721
0
            if (oIterCreatedDims != oMapAlreadyCreatedDims.end())
722
0
            {
723
0
                apoDstDims.push_back(oIterCreatedDims->second);
724
0
            }
725
0
            else
726
0
            {
727
0
                uint64_t nDimSize64 = desc.nSize;
728
0
                if (!desc.aaValues.empty())
729
0
                {
730
0
                    nDimSize64 = 0;
731
0
                    for (const auto &aValues : desc.aaValues)
732
0
                        nDimSize64 += aValues.size();
733
0
                }
734
0
                auto dstDim = poDstGroup->CreateDimension(
735
0
                    desc.osName, desc.osType, desc.osDirection, nDimSize64);
736
0
                if (!dstDim)
737
0
                    return false;
738
739
0
                if (desc.bHasIndexingVar)
740
0
                {
741
0
                    auto var = poDstGroup->CreateVRTMDArray(
742
0
                        desc.osName, {dstDim},
743
0
                        GDALExtendedDataType::Create(GDT_Float64));
744
0
                    if (!var)
745
0
                        return false;
746
747
0
                    for (const auto &attr : desc.attributes)
748
0
                    {
749
0
                        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
750
0
                        auto dstAttr = var->CreateAttribute(
751
0
                            attr->GetName(), attr->GetDimensionsSize(),
752
0
                            attr->GetDataType());
753
0
                        if (dstAttr)
754
0
                        {
755
0
                            auto raw(attr->ReadAsRaw());
756
0
                            CPL_IGNORE_RET_VAL(
757
0
                                dstAttr->Write(raw.data(), raw.size()));
758
0
                        }
759
0
                    }
760
761
0
                    if (desc.aaValues.empty())
762
0
                    {
763
0
                        auto poSource =
764
0
                            std::make_unique<VRTMDArraySourceRegularlySpaced>(
765
0
                                desc.dfStart, desc.dfIncrement);
766
0
                        var->AddSource(std::move(poSource));
767
0
                    }
768
0
                    else
769
0
                    {
770
0
                        const size_t nDimSize = static_cast<size_t>(nDimSize64);
771
0
                        std::vector<GUInt64> anOffset = {0};
772
0
                        std::vector<size_t> anCount = {nDimSize};
773
0
                        std::vector<double> adfValues;
774
0
                        adfValues.reserve(nDimSize);
775
0
                        if (desc.nProgressionSign >= 0)
776
0
                        {
777
0
                            for (const auto &aValues : desc.aaValues)
778
0
                                adfValues.insert(adfValues.end(),
779
0
                                                 aValues.begin(),
780
0
                                                 aValues.end());
781
0
                        }
782
0
                        else
783
0
                        {
784
0
                            for (auto oIter = desc.aaValues.rbegin();
785
0
                                 oIter != desc.aaValues.rend(); ++oIter)
786
0
                            {
787
0
                                adfValues.insert(adfValues.end(),
788
0
                                                 oIter->rbegin(),
789
0
                                                 oIter->rend());
790
0
                            }
791
0
                        }
792
0
                        std::vector<GByte> abyValues(nDimSize * sizeof(double));
793
0
                        memcpy(abyValues.data(), adfValues.data(),
794
0
                               nDimSize * sizeof(double));
795
0
                        auto poSource =
796
0
                            std::make_unique<VRTMDArraySourceInlinedValues>(
797
0
                                var.get(),
798
0
                                /* bIsConstantValue = */ false,
799
0
                                std::move(anOffset), std::move(anCount),
800
0
                                std::move(abyValues));
801
0
                        var->AddSource(std::move(poSource));
802
0
                    }
803
0
                    dstDim->SetIndexingVariable(std::move(var));
804
0
                }
805
806
0
                oMapAlreadyCreatedDims[dstDim->GetName()] = dstDim;
807
0
                apoDstDims.push_back(std::move(dstDim));
808
0
            }
809
0
        }
810
811
        // Create mosaic array
812
0
        CPLStringList aosArrayCO;
813
0
        std::string osBlockSize;
814
0
        for (size_t i = 0; i < apoDstDims.size(); ++i)
815
0
        {
816
0
            if (i > 0)
817
0
                osBlockSize += ',';
818
0
            osBlockSize +=
819
0
                std::to_string(arrayParameters.mosaicDimensions[i].nBlockSize);
820
0
        }
821
0
        if (!osBlockSize.empty())
822
0
            aosArrayCO.SetNameValue("BLOCKSIZE", osBlockSize.c_str());
823
824
0
        auto poDstArray = poDstGroup->CreateVRTMDArray(
825
0
            CPLGetFilename(poFirstSourceArray->GetName().c_str()), apoDstDims,
826
0
            poFirstSourceArray->GetDataType(), aosArrayCO);
827
0
        if (!poDstArray)
828
0
            return false;
829
830
0
        GUInt64 nCurCost = 0;
831
0
        poDstArray->CopyFromAllExceptValues(poFirstSourceArray.get(), false,
832
0
                                            nCurCost, 0, nullptr, nullptr);
833
834
        // Add sources to mosaic array
835
0
        for (int iDS = 0; iDS < aosInputDatasetNames.size(); ++iDS)
836
0
        {
837
0
            const auto &aoSourceShortDimDesc = aaoSourceShortDimDesc[iDS];
838
839
0
            const auto dimCount = arrayParameters.mosaicDimensions.size();
840
0
            std::vector<GUInt64> anSrcOffset(dimCount);
841
0
            std::vector<GUInt64> anCount(dimCount);
842
0
            std::vector<GUInt64> anDstOffset;
843
0
            CPLAssert(aoSourceShortDimDesc.size() == dimCount);
844
845
0
            for (size_t iDim = 0; iDim < dimCount; ++iDim)
846
0
            {
847
0
                const DimensionDesc &desc =
848
0
                    arrayParameters.mosaicDimensions[iDim];
849
0
                const SourceShortDimDesc &sourceDesc =
850
0
                    aoSourceShortDimDesc[iDim];
851
0
                if (sourceDesc.bIsRegularlySpaced)
852
0
                {
853
0
                    const double dfPos =
854
0
                        (sourceDesc.dfStart - desc.dfStart) / desc.dfIncrement;
855
0
                    anDstOffset.push_back(static_cast<uint64_t>(dfPos + 0.5));
856
0
                }
857
0
                else
858
0
                {
859
0
                    uint64_t nPos = 0;
860
0
                    bool bFound = false;
861
0
                    for (size_t i = 0; i < desc.aaValues.size(); ++i)
862
0
                    {
863
0
                        if (sourceDesc.dfStart == desc.aaValues[i][0])
864
0
                        {
865
0
                            bFound = true;
866
0
                            break;
867
0
                        }
868
0
                        else
869
0
                        {
870
0
                            nPos += desc.aaValues[i].size();
871
0
                        }
872
0
                    }
873
0
                    CPLAssert(bFound);
874
0
                    CPL_IGNORE_RET_VAL(bFound);
875
876
0
                    anDstOffset.push_back(nPos);
877
0
                }
878
879
0
                anCount[iDim] = sourceDesc.nSize;
880
0
            }
881
882
0
            std::vector<GUInt64> anStep(dimCount, 1);
883
0
            std::vector<int> anTransposedAxis;
884
0
            auto poSource = std::make_unique<VRTMDArraySourceFromArray>(
885
0
                poDstArray.get(), false, false, aosInputDatasetNames[iDS],
886
0
                poFirstSourceArray->GetFullName(), std::string(),
887
0
                std::move(anTransposedAxis),
888
0
                std::string(),  // viewExpr
889
0
                std::move(anSrcOffset), std::move(anCount), std::move(anStep),
890
0
                std::move(anDstOffset));
891
0
            poDstArray->AddSource(std::move(poSource));
892
0
        }
893
0
    }
894
895
0
    pScaledData.reset(GDALCreateScaledProgress(dfIntermediatePercentage, 1.0,
896
0
                                               pfnProgress, pProgressData));
897
0
    auto poOutDS = std::unique_ptr<GDALDataset>(
898
0
        poOutDrv->CreateCopy(m_outputDataset.GetName().c_str(), poVRTDS.get(),
899
0
                             false, CPLStringList(m_creationOptions).List(),
900
0
                             GDALScaledProgress, pScaledData.get()));
901
902
0
    if (poOutDS)
903
0
        m_outputDataset.Set(std::move(poOutDS));
904
905
0
    return m_outputDataset.GetDatasetRef() != nullptr;
906
0
}
907
908
//! @endcond