Coverage Report

Created: 2025-06-09 08:44

/src/gdal/gcore/gdalmultidim_gridded.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Name:     gdalmultidim_gridded.cpp
4
 * Project:  GDAL Core
5
 * Purpose:  GDALMDArray::GetGridded() implementation
6
 * Author:   Even Rouault <even.rouault at spatialys.com>
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 2023, Even Rouault <even.rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "gdal_alg.h"
15
#include "gdalgrid.h"
16
#include "gdal_priv.h"
17
#include "gdal_pam.h"
18
#include "ogrsf_frmts.h"
19
#include "memdataset.h"
20
21
#include <algorithm>
22
#include <cassert>
23
#include <limits>
24
#include <new>
25
26
/************************************************************************/
27
/*                         GDALMDArrayGridded                           */
28
/************************************************************************/
29
30
class GDALMDArrayGridded final : public GDALPamMDArray
31
{
32
  private:
33
    std::shared_ptr<GDALMDArray> m_poParent{};
34
    std::vector<std::shared_ptr<GDALDimension>> m_apoDims{};
35
    std::shared_ptr<GDALMDArray> m_poVarX{};
36
    std::shared_ptr<GDALMDArray> m_poVarY{};
37
    std::unique_ptr<GDALDataset> m_poVectorDS{};
38
    GDALGridAlgorithm m_eAlg;
39
    std::unique_ptr<void, VSIFreeReleaser> m_poGridOptions;
40
    const GDALExtendedDataType m_dt;
41
    std::vector<GUInt64> m_anBlockSize{};
42
    const double m_dfNoDataValue;
43
    const double m_dfMinX;
44
    const double m_dfResX;
45
    const double m_dfMinY;
46
    const double m_dfResY;
47
    const double m_dfRadius;
48
    mutable std::vector<GUInt64> m_anLastStartIdx{};
49
    mutable std::vector<double> m_adfZ{};
50
51
  protected:
52
    explicit GDALMDArrayGridded(
53
        const std::shared_ptr<GDALMDArray> &poParent,
54
        const std::vector<std::shared_ptr<GDALDimension>> &apoDims,
55
        const std::shared_ptr<GDALMDArray> &poVarX,
56
        const std::shared_ptr<GDALMDArray> &poVarY,
57
        std::unique_ptr<GDALDataset> &&poVectorDS, GDALGridAlgorithm eAlg,
58
        std::unique_ptr<void, VSIFreeReleaser> &&poGridOptions,
59
        double dfNoDataValue, double dfMinX, double dfResX, double dfMinY,
60
        double dfResY, double dfRadius)
61
0
        : GDALAbstractMDArray(std::string(),
62
0
                              "Gridded view of " + poParent->GetFullName()),
63
0
          GDALPamMDArray(
64
0
              std::string(), "Gridded view of " + poParent->GetFullName(),
65
0
              GDALPamMultiDim::GetPAM(poParent), poParent->GetContext()),
66
0
          m_poParent(std::move(poParent)), m_apoDims(apoDims), m_poVarX(poVarX),
67
0
          m_poVarY(poVarY), m_poVectorDS(std::move(poVectorDS)), m_eAlg(eAlg),
68
0
          m_poGridOptions(std::move(poGridOptions)),
69
0
          m_dt(GDALExtendedDataType::Create(GDT_Float64)),
70
0
          m_dfNoDataValue(dfNoDataValue), m_dfMinX(dfMinX), m_dfResX(dfResX),
71
0
          m_dfMinY(dfMinY), m_dfResY(dfResY), m_dfRadius(dfRadius)
72
0
    {
73
0
        const auto anParentBlockSize = m_poParent->GetBlockSize();
74
0
        m_anBlockSize.resize(m_apoDims.size());
75
0
        for (size_t i = 0; i + 1 < m_apoDims.size(); ++i)
76
0
            m_anBlockSize[i] = anParentBlockSize[i];
77
0
        m_anBlockSize[m_apoDims.size() - 2] = 256;
78
0
        m_anBlockSize[m_apoDims.size() - 1] = 256;
79
0
    }
80
81
    bool IRead(const GUInt64 *arrayStartIdx, const size_t *count,
82
               const GInt64 *arrayStep, const GPtrDiff_t *bufferStride,
83
               const GDALExtendedDataType &bufferDataType,
84
               void *pDstBuffer) const override;
85
86
  public:
87
    static std::shared_ptr<GDALMDArrayGridded>
88
    Create(const std::shared_ptr<GDALMDArray> &poParent,
89
           const std::vector<std::shared_ptr<GDALDimension>> &apoDims,
90
           const std::shared_ptr<GDALMDArray> &poVarX,
91
           const std::shared_ptr<GDALMDArray> &poVarY,
92
           std::unique_ptr<GDALDataset> &&poVectorDS, GDALGridAlgorithm eAlg,
93
           std::unique_ptr<void, VSIFreeReleaser> &&poGridOptions,
94
           double dfNoDataValue, double dfMinX, double dfResX, double dfMinY,
95
           double dfResY, double dfRadius)
96
0
    {
97
0
        auto newAr(std::shared_ptr<GDALMDArrayGridded>(new GDALMDArrayGridded(
98
0
            poParent, apoDims, poVarX, poVarY, std::move(poVectorDS), eAlg,
99
0
            std::move(poGridOptions), dfNoDataValue, dfMinX, dfResX, dfMinY,
100
0
            dfResY, dfRadius)));
101
0
        newAr->SetSelf(newAr);
102
0
        return newAr;
103
0
    }
104
105
    bool IsWritable() const override
106
0
    {
107
0
        return false;
108
0
    }
109
110
    const std::string &GetFilename() const override
111
0
    {
112
0
        return m_poParent->GetFilename();
113
0
    }
114
115
    const std::vector<std::shared_ptr<GDALDimension>> &
116
    GetDimensions() const override
117
0
    {
118
0
        return m_apoDims;
119
0
    }
120
121
    const void *GetRawNoDataValue() const override
122
0
    {
123
0
        return &m_dfNoDataValue;
124
0
    }
125
126
    std::vector<GUInt64> GetBlockSize() const override
127
0
    {
128
0
        return m_anBlockSize;
129
0
    }
130
131
    const GDALExtendedDataType &GetDataType() const override
132
0
    {
133
0
        return m_dt;
134
0
    }
135
136
    const std::string &GetUnit() const override
137
0
    {
138
0
        return m_poParent->GetUnit();
139
0
    }
140
141
    std::shared_ptr<OGRSpatialReference> GetSpatialRef() const override
142
0
    {
143
0
        return m_poParent->GetSpatialRef();
144
0
    }
145
146
    std::shared_ptr<GDALAttribute>
147
    GetAttribute(const std::string &osName) const override
148
0
    {
149
0
        return m_poParent->GetAttribute(osName);
150
0
    }
151
152
    std::vector<std::shared_ptr<GDALAttribute>>
153
    GetAttributes(CSLConstList papszOptions = nullptr) const override
154
0
    {
155
0
        return m_poParent->GetAttributes(papszOptions);
156
0
    }
157
};
158
159
/************************************************************************/
160
/*                             IRead()                                  */
161
/************************************************************************/
162
163
bool GDALMDArrayGridded::IRead(const GUInt64 *arrayStartIdx,
164
                               const size_t *count, const GInt64 *arrayStep,
165
                               const GPtrDiff_t *bufferStride,
166
                               const GDALExtendedDataType &bufferDataType,
167
                               void *pDstBuffer) const
168
0
{
169
0
    if (bufferDataType.GetClass() != GEDTC_NUMERIC)
170
0
    {
171
0
        CPLError(CE_Failure, CPLE_NotSupported,
172
0
                 "GDALMDArrayGridded::IRead() only support numeric "
173
0
                 "bufferDataType");
174
0
        return false;
175
0
    }
176
0
    const auto nDims = GetDimensionCount();
177
178
0
    std::vector<GUInt64> anStartIdx;
179
0
    for (size_t i = 0; i + 2 < nDims; ++i)
180
0
    {
181
0
        anStartIdx.push_back(arrayStartIdx[i]);
182
0
        if (count[i] != 1)
183
0
        {
184
0
            CPLError(CE_Failure, CPLE_NotSupported,
185
0
                     "GDALMDArrayGridded::IRead() only support count = 1 in "
186
0
                     "the first dimensions, except the last 2 Y,X ones");
187
0
            return false;
188
0
        }
189
0
    }
190
191
0
    const auto iDimX = nDims - 1;
192
0
    const auto iDimY = nDims - 2;
193
194
0
    if (arrayStep[iDimX] < 0)
195
0
    {
196
0
        CPLError(CE_Failure, CPLE_NotSupported,
197
0
                 "GDALMDArrayGridded::IRead(): "
198
0
                 "arrayStep[iDimX] < 0 not supported");
199
0
        return false;
200
0
    }
201
202
0
    if (arrayStep[iDimY] < 0)
203
0
    {
204
0
        CPLError(CE_Failure, CPLE_NotSupported,
205
0
                 "GDALMDArrayGridded::IRead(): "
206
0
                 "arrayStep[iDimY] < 0 not supported");
207
0
        return false;
208
0
    }
209
210
    // Load the values taken by the variable at the considered slice
211
    // (if not already done)
212
0
    if (m_adfZ.empty() || m_anLastStartIdx != anStartIdx)
213
0
    {
214
0
        std::vector<GUInt64> anTempStartIdx(anStartIdx);
215
0
        anTempStartIdx.push_back(0);
216
0
        const std::vector<GInt64> anTempArrayStep(
217
0
            m_poParent->GetDimensionCount(), 1);
218
0
        std::vector<GPtrDiff_t> anTempBufferStride(
219
0
            m_poParent->GetDimensionCount() - 1, 0);
220
0
        anTempBufferStride.push_back(1);
221
0
        std::vector<size_t> anTempCount(m_poParent->GetDimensionCount() - 1, 1);
222
0
        anTempCount.push_back(
223
0
            static_cast<size_t>(m_poParent->GetDimensions().back()->GetSize()));
224
0
        CPLAssert(anTempStartIdx.size() == m_poParent->GetDimensionCount());
225
0
        CPLAssert(anTempCount.size() == m_poParent->GetDimensionCount());
226
0
        CPLAssert(anTempArrayStep.size() == m_poParent->GetDimensionCount());
227
0
        CPLAssert(anTempBufferStride.size() == m_poParent->GetDimensionCount());
228
229
0
        try
230
0
        {
231
0
            m_adfZ.resize(anTempCount.back());
232
0
        }
233
0
        catch (const std::bad_alloc &e)
234
0
        {
235
0
            CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
236
0
            return false;
237
0
        }
238
239
0
        if (!m_poParent->Read(anTempStartIdx.data(), anTempCount.data(),
240
0
                              anTempArrayStep.data(), anTempBufferStride.data(),
241
0
                              m_dt, m_adfZ.data()))
242
0
        {
243
0
            return false;
244
0
        }
245
        // GCC 13.1 warns here. Definitely a false positive.
246
#if defined(__GNUC__) && __GNUC__ >= 13
247
#pragma GCC diagnostic push
248
#pragma GCC diagnostic ignored "-Wnull-dereference"
249
#endif
250
0
        m_anLastStartIdx = std::move(anStartIdx);
251
#if defined(__GNUC__) && __GNUC__ >= 13
252
#pragma GCC diagnostic pop
253
#endif
254
0
    }
255
256
    // Determine the X,Y spatial extent of the request
257
0
    const double dfX1 = m_dfMinX + arrayStartIdx[iDimX] * m_dfResX;
258
0
    const double dfX2 = m_dfMinX + (arrayStartIdx[iDimX] +
259
0
                                    (count[iDimX] - 1) * arrayStep[iDimX]) *
260
0
                                       m_dfResX;
261
0
    const double dfMinX = std::min(dfX1, dfX2) - m_dfResX / 2;
262
0
    const double dfMaxX = std::max(dfX1, dfX2) + m_dfResX / 2;
263
264
0
    const double dfY1 = m_dfMinY + arrayStartIdx[iDimY] * m_dfResY;
265
0
    const double dfY2 = m_dfMinY + (arrayStartIdx[iDimY] +
266
0
                                    (count[iDimY] - 1) * arrayStep[iDimY]) *
267
0
                                       m_dfResY;
268
0
    const double dfMinY = std::min(dfY1, dfY2) - m_dfResY / 2;
269
0
    const double dfMaxY = std::max(dfY1, dfY2) + m_dfResY / 2;
270
271
    // Extract relevant variable values
272
0
    auto poLyr = m_poVectorDS->GetLayer(0);
273
0
    if (!(arrayStartIdx[iDimX] == 0 &&
274
0
          arrayStartIdx[iDimX] + (count[iDimX] - 1) * arrayStep[iDimX] ==
275
0
              m_apoDims[iDimX]->GetSize() - 1 &&
276
0
          arrayStartIdx[iDimY] == 0 &&
277
0
          arrayStartIdx[iDimY] + (count[iDimY] - 1) * arrayStep[iDimY] ==
278
0
              m_apoDims[iDimY]->GetSize() - 1))
279
0
    {
280
0
        poLyr->SetSpatialFilterRect(dfMinX - m_dfRadius, dfMinY - m_dfRadius,
281
0
                                    dfMaxX + m_dfRadius, dfMaxY + m_dfRadius);
282
0
    }
283
0
    else
284
0
    {
285
0
        poLyr->SetSpatialFilter(nullptr);
286
0
    }
287
0
    std::vector<double> adfX;
288
0
    std::vector<double> adfY;
289
0
    std::vector<double> adfZ;
290
0
    try
291
0
    {
292
0
        for (auto &&poFeat : poLyr)
293
0
        {
294
0
            const auto poGeom = poFeat->GetGeometryRef();
295
0
            CPLAssert(poGeom);
296
0
            CPLAssert(poGeom->getGeometryType() == wkbPoint);
297
0
            adfX.push_back(poGeom->toPoint()->getX());
298
0
            adfY.push_back(poGeom->toPoint()->getY());
299
0
            const auto nIdxInDataset = poFeat->GetFieldAsInteger64(0);
300
0
            assert(nIdxInDataset >= 0 &&
301
0
                   static_cast<size_t>(nIdxInDataset) < m_adfZ.size());
302
0
            adfZ.push_back(m_adfZ[static_cast<size_t>(nIdxInDataset)]);
303
0
        }
304
0
    }
305
0
    catch (const std::bad_alloc &e)
306
0
    {
307
0
        CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
308
0
        return false;
309
0
    }
310
311
0
    const size_t nXSize =
312
0
        static_cast<size_t>((count[iDimX] - 1) * arrayStep[iDimX] + 1);
313
0
    const size_t nYSize =
314
0
        static_cast<size_t>((count[iDimY] - 1) * arrayStep[iDimY] + 1);
315
0
    if (nXSize > std::numeric_limits<GUInt32>::max() / nYSize)
316
0
    {
317
0
        CPLError(CE_Failure, CPLE_AppDefined,
318
0
                 "Too many points queried at once");
319
0
        return false;
320
0
    }
321
0
    std::vector<double> adfRes;
322
0
    try
323
0
    {
324
0
        adfRes.resize(nXSize * nYSize, m_dfNoDataValue);
325
0
    }
326
0
    catch (const std::bad_alloc &e)
327
0
    {
328
0
        CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
329
0
        return false;
330
0
    }
331
#if 0
332
    CPLDebug("GDAL",
333
             "dfMinX=%f, dfMaxX=%f, dfMinY=%f, dfMaxY=%f",
334
             dfMinX, dfMaxX, dfMinY, dfMaxY);
335
#endif
336
337
    // Finally do the gridded interpolation
338
0
    if (!adfX.empty() &&
339
0
        GDALGridCreate(
340
0
            m_eAlg, m_poGridOptions.get(), static_cast<GUInt32>(adfX.size()),
341
0
            adfX.data(), adfY.data(), adfZ.data(), dfMinX, dfMaxX, dfMinY,
342
0
            dfMaxY, static_cast<GUInt32>(nXSize), static_cast<GUInt32>(nYSize),
343
0
            GDT_Float64, adfRes.data(), nullptr, nullptr) != CE_None)
344
0
    {
345
0
        return false;
346
0
    }
347
348
    // Copy interpolated data into destination buffer
349
0
    GByte *pabyDestBuffer = static_cast<GByte *>(pDstBuffer);
350
0
    const auto eBufferDT = bufferDataType.GetNumericDataType();
351
0
    const auto nBufferDTSize = GDALGetDataTypeSizeBytes(eBufferDT);
352
0
    for (size_t iY = 0; iY < count[iDimY]; ++iY)
353
0
    {
354
0
        GDALCopyWords64(
355
0
            adfRes.data() + iY * arrayStep[iDimY] * nXSize, GDT_Float64,
356
0
            static_cast<int>(sizeof(double) * arrayStep[iDimX]),
357
0
            pabyDestBuffer + iY * bufferStride[iDimY] * nBufferDTSize,
358
0
            eBufferDT, static_cast<int>(bufferStride[iDimX] * nBufferDTSize),
359
0
            static_cast<GPtrDiff_t>(count[iDimX]));
360
0
    }
361
362
0
    return true;
363
0
}
364
365
/************************************************************************/
366
/*                            GetGridded()                              */
367
/************************************************************************/
368
369
/** Return a gridded array from scattered point data, that is from an array
370
 * whose last dimension is the indexing variable of X and Y arrays.
371
 *
372
 * The gridding is done in 2D, using GDALGridCreate(), on-the-fly at Read()
373
 * time, taking into account the spatial extent of the request to limit the
374
 * gridding. The results got on the whole extent or a subset of it might not be
375
 * strictly identical depending on the gridding algorithm and its radius.
376
 * Setting a radius in osGridOptions is recommended to improve performance.
377
 * For arrays which have more dimensions than the dimension of the indexing
378
 * variable of the X and Y arrays, Read() must be called on slices of the extra
379
 * dimensions (ie count[i] must be set to 1, except for the X and Y dimensions
380
 * of the array returned by this method).
381
 *
382
 * This is the same as the C function GDALMDArrayGetGridded().
383
 *
384
 * @param osGridOptions Gridding algorithm and options.
385
 * e.g. "invdist:nodata=nan:radius1=1:radius2=1:max_points=5".
386
 * See documentation of the <a href="/programs/gdal_grid.html">gdal_grid</a>
387
 * utility for all options.
388
 * @param poXArrayIn Single-dimension array containing X values, and whose
389
 * dimension is the last one of this array. If set to nullptr, the "coordinates"
390
 * attribute must exist on this array, and the X variable will be the (N-1)th one
391
 * mentioned in it, unless there is a "x" or "lon" variable in "coordinates".
392
 * @param poYArrayIn Single-dimension array containing Y values, and whose
393
 * dimension is the last one of this array. If set to nullptr, the "coordinates"
394
 * attribute must exist on this array, and the Y variable will be the (N-2)th one
395
 * mentioned in it,  unless there is a "y" or "lat" variable in "coordinates".
396
 * @param papszOptions NULL terminated list of options, or nullptr. Supported
397
 * options are:
398
 * <ul>
399
 * <li>RESOLUTION=val: Spatial resolution of the returned array. If not set,
400
 * will be guessed from the typical spacing of (X,Y) points.</li>
401
 * </ul>
402
 *
403
 * @return gridded array, or nullptr in case of error.
404
 *
405
 * @since GDAL 3.7
406
 */
407
std::shared_ptr<GDALMDArray>
408
GDALMDArray::GetGridded(const std::string &osGridOptions,
409
                        const std::shared_ptr<GDALMDArray> &poXArrayIn,
410
                        const std::shared_ptr<GDALMDArray> &poYArrayIn,
411
                        CSLConstList papszOptions) const
412
0
{
413
0
    auto self = std::dynamic_pointer_cast<GDALMDArray>(m_pSelf.lock());
414
0
    if (!self)
415
0
    {
416
0
        CPLError(CE_Failure, CPLE_AppDefined,
417
0
                 "Driver implementation issue: m_pSelf not set !");
418
0
        return nullptr;
419
0
    }
420
421
0
    GDALGridAlgorithm eAlg;
422
0
    void *pOptions = nullptr;
423
0
    if (GDALGridParseAlgorithmAndOptions(osGridOptions.c_str(), &eAlg,
424
0
                                         &pOptions) != CE_None)
425
0
    {
426
0
        return nullptr;
427
0
    }
428
429
0
    std::unique_ptr<void, VSIFreeReleaser> poGridOptions(pOptions);
430
431
0
    if (GetDataType().GetClass() != GEDTC_NUMERIC)
432
0
    {
433
0
        CPLError(CE_Failure, CPLE_NotSupported,
434
0
                 "GetDataType().GetClass() != GEDTC_NUMERIC");
435
0
        return nullptr;
436
0
    }
437
438
0
    if (GetDimensionCount() == 0)
439
0
    {
440
0
        CPLError(CE_Failure, CPLE_NotSupported, "GetDimensionCount() == 0");
441
0
        return nullptr;
442
0
    }
443
444
0
    if (poXArrayIn && !poYArrayIn)
445
0
    {
446
0
        CPLError(
447
0
            CE_Failure, CPLE_AppDefined,
448
0
            "As poXArrayIn is specified, poYArrayIn must also be specified");
449
0
        return nullptr;
450
0
    }
451
0
    else if (!poXArrayIn && poYArrayIn)
452
0
    {
453
0
        CPLError(
454
0
            CE_Failure, CPLE_AppDefined,
455
0
            "As poYArrayIn is specified, poXArrayIn must also be specified");
456
0
        return nullptr;
457
0
    }
458
0
    std::shared_ptr<GDALMDArray> poXArray = poXArrayIn;
459
0
    std::shared_ptr<GDALMDArray> poYArray = poYArrayIn;
460
461
0
    if (!poXArray)
462
0
    {
463
0
        const auto aoCoordVariables = GetCoordinateVariables();
464
0
        if (aoCoordVariables.size() < 2)
465
0
        {
466
0
            CPLError(CE_Failure, CPLE_NotSupported,
467
0
                     "aoCoordVariables.size() < 2");
468
0
            return nullptr;
469
0
        }
470
471
0
        if (aoCoordVariables.size() != GetDimensionCount() + 1)
472
0
        {
473
0
            CPLError(CE_Failure, CPLE_NotSupported,
474
0
                     "aoCoordVariables.size() != GetDimensionCount() + 1");
475
0
            return nullptr;
476
0
        }
477
478
        // Default choice for X and Y arrays
479
0
        poYArray = aoCoordVariables[aoCoordVariables.size() - 2];
480
0
        poXArray = aoCoordVariables[aoCoordVariables.size() - 1];
481
482
        // Detect X and Y array from coordinate variables
483
0
        for (const auto &poVar : aoCoordVariables)
484
0
        {
485
0
            const auto &osVarName = poVar->GetName();
486
#ifdef disabled
487
            if (poVar->GetDimensionCount() != 1)
488
            {
489
                CPLError(CE_Failure, CPLE_NotSupported,
490
                         "Coordinate variable %s is not 1-dimensional",
491
                         osVarName.c_str());
492
                return nullptr;
493
            }
494
            const auto &osVarDimName = poVar->GetDimensions()[0]->GetFullName();
495
            bool bFound = false;
496
            for (const auto &poDim : GetDimensions())
497
            {
498
                if (osVarDimName == poDim->GetFullName())
499
                {
500
                    bFound = true;
501
                    break;
502
                }
503
            }
504
            if (!bFound)
505
            {
506
                CPLError(CE_Failure, CPLE_NotSupported,
507
                         "Dimension %s of coordinate variable %s is not a "
508
                         "dimension of this array",
509
                         osVarDimName.c_str(), osVarName.c_str());
510
                return nullptr;
511
            }
512
#endif
513
0
            if (osVarName == "x" || osVarName == "lon")
514
0
            {
515
0
                poXArray = poVar;
516
0
            }
517
0
            else if (osVarName == "y" || osVarName == "lat")
518
0
            {
519
0
                poYArray = poVar;
520
0
            }
521
0
        }
522
0
    }
523
524
0
    if (poYArray->GetDimensionCount() != 1)
525
0
    {
526
0
        CPLError(CE_Failure, CPLE_NotSupported,
527
0
                 "aoCoordVariables[aoCoordVariables.size() - "
528
0
                 "2]->GetDimensionCount() != 1");
529
0
        return nullptr;
530
0
    }
531
0
    if (poYArray->GetDataType().GetClass() != GEDTC_NUMERIC)
532
0
    {
533
0
        CPLError(CE_Failure, CPLE_NotSupported,
534
0
                 "poYArray->GetDataType().GetClass() != GEDTC_NUMERIC");
535
0
        return nullptr;
536
0
    }
537
538
0
    if (poXArray->GetDimensionCount() != 1)
539
0
    {
540
0
        CPLError(CE_Failure, CPLE_NotSupported,
541
0
                 "aoCoordVariables[aoCoordVariables.size() - "
542
0
                 "1]->GetDimensionCount() != 1");
543
0
        return nullptr;
544
0
    }
545
0
    if (poXArray->GetDataType().GetClass() != GEDTC_NUMERIC)
546
0
    {
547
0
        CPLError(CE_Failure, CPLE_NotSupported,
548
0
                 "poXArray->GetDataType().GetClass() != GEDTC_NUMERIC");
549
0
        return nullptr;
550
0
    }
551
552
0
    if (poYArray->GetDimensions()[0]->GetFullName() !=
553
0
        poXArray->GetDimensions()[0]->GetFullName())
554
0
    {
555
0
        CPLError(CE_Failure, CPLE_NotSupported,
556
0
                 "poYArray->GetDimensions()[0]->GetFullName() != "
557
0
                 "poXArray->GetDimensions()[0]->GetFullName()");
558
0
        return nullptr;
559
0
    }
560
561
0
    if (poXArray->GetDimensions()[0]->GetFullName() !=
562
0
        GetDimensions().back()->GetFullName())
563
0
    {
564
0
        CPLError(CE_Failure, CPLE_NotSupported,
565
0
                 "poYArray->GetDimensions()[0]->GetFullName() != "
566
0
                 "GetDimensions().back()->GetFullName()");
567
0
        return nullptr;
568
0
    }
569
570
0
    if (poXArray->GetTotalElementsCount() <= 2)
571
0
    {
572
0
        CPLError(CE_Failure, CPLE_NotSupported,
573
0
                 "poXArray->GetTotalElementsCount() <= 2");
574
0
        return nullptr;
575
0
    }
576
577
0
    if (poXArray->GetTotalElementsCount() >
578
0
        std::numeric_limits<size_t>::max() / 2)
579
0
    {
580
0
        CPLError(CE_Failure, CPLE_NotSupported,
581
0
                 "poXArray->GetTotalElementsCount() > "
582
0
                 "std::numeric_limits<size_t>::max() / 2");
583
0
        return nullptr;
584
0
    }
585
586
0
    if (poXArray->GetTotalElementsCount() > 10 * 1024 * 1024 &&
587
0
        !CPLTestBool(CSLFetchNameValueDef(
588
0
            papszOptions, "ACCEPT_BIG_SPATIAL_INDEXING_VARIABLE", "NO")))
589
0
    {
590
0
        CPLError(CE_Failure, CPLE_AppDefined,
591
0
                 "The spatial indexing variable has " CPL_FRMT_GIB " elements. "
592
0
                 "Set the ACCEPT_BIG_SPATIAL_INDEXING_VARIABLE=YES option of "
593
0
                 "GetGridded() to mean you want to continue and are aware of "
594
0
                 "big RAM and CPU time requirements",
595
0
                 static_cast<GIntBig>(poXArray->GetTotalElementsCount()));
596
0
        return nullptr;
597
0
    }
598
599
0
    std::vector<double> adfXVals;
600
0
    std::vector<double> adfYVals;
601
0
    try
602
0
    {
603
0
        adfXVals.resize(static_cast<size_t>(poXArray->GetTotalElementsCount()));
604
0
        adfYVals.resize(static_cast<size_t>(poXArray->GetTotalElementsCount()));
605
0
    }
606
0
    catch (const std::bad_alloc &e)
607
0
    {
608
0
        CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
609
0
        return nullptr;
610
0
    }
611
612
    // Ingest X and Y arrays
613
0
    const GUInt64 arrayStartIdx[] = {0};
614
0
    const size_t count[] = {adfXVals.size()};
615
0
    const GInt64 arrayStep[] = {1};
616
0
    const GPtrDiff_t bufferStride[] = {1};
617
0
    if (!poXArray->Read(arrayStartIdx, count, arrayStep, bufferStride,
618
0
                        GDALExtendedDataType::Create(GDT_Float64),
619
0
                        adfXVals.data()))
620
0
    {
621
0
        CPLError(CE_Failure, CPLE_AppDefined, "poXArray->Read() failed");
622
0
        return nullptr;
623
0
    }
624
0
    if (!poYArray->Read(arrayStartIdx, count, arrayStep, bufferStride,
625
0
                        GDALExtendedDataType::Create(GDT_Float64),
626
0
                        adfYVals.data()))
627
0
    {
628
0
        CPLError(CE_Failure, CPLE_AppDefined, "poYArray->Read() failed");
629
0
        return nullptr;
630
0
    }
631
632
0
    const char *pszExt = "fgb";
633
0
    GDALDriver *poDrv = GetGDALDriverManager()->GetDriverByName("FlatGeoBuf");
634
0
    if (!poDrv)
635
0
    {
636
0
        pszExt = "gpkg";
637
0
        poDrv = GetGDALDriverManager()->GetDriverByName("GPKG");
638
0
        if (!poDrv)
639
0
        {
640
0
            pszExt = "mem";
641
0
        }
642
0
    }
643
644
    // Create a in-memory vector layer with (X,Y) points
645
0
    const std::string osTmpFilename(VSIMemGenerateHiddenFilename(
646
0
        std::string("tmp.").append(pszExt).c_str()));
647
0
    auto poDS = std::unique_ptr<GDALDataset>(
648
0
        poDrv ? poDrv->Create(osTmpFilename.c_str(), 0, 0, 0, GDT_Unknown,
649
0
                              nullptr)
650
0
              : MEMDataset::Create(osTmpFilename.c_str(), 0, 0, 0, GDT_Unknown,
651
0
                                   nullptr));
652
0
    if (!poDS)
653
0
        return nullptr;
654
0
    auto poLyr = poDS->CreateLayer("layer", nullptr, wkbPoint);
655
0
    if (!poLyr)
656
0
        return nullptr;
657
0
    OGRFieldDefn oFieldDefn("IDX", OFTInteger64);
658
0
    poLyr->CreateField(&oFieldDefn);
659
0
    if (poLyr->StartTransaction() != OGRERR_NONE)
660
0
        return nullptr;
661
0
    OGRFeature oFeat(poLyr->GetLayerDefn());
662
0
    for (size_t i = 0; i < adfXVals.size(); ++i)
663
0
    {
664
0
        auto poPoint = new OGRPoint(adfXVals[i], adfYVals[i]);
665
0
        oFeat.SetFID(OGRNullFID);
666
0
        oFeat.SetGeometryDirectly(poPoint);
667
0
        oFeat.SetField(0, static_cast<GIntBig>(i));
668
0
        if (poLyr->CreateFeature(&oFeat) != OGRERR_NONE)
669
0
            return nullptr;
670
0
    }
671
0
    if (poLyr->CommitTransaction() != OGRERR_NONE)
672
0
        return nullptr;
673
0
    OGREnvelope sEnvelope;
674
0
    CPL_IGNORE_RET_VAL(poLyr->GetExtent(&sEnvelope));
675
0
    if (!EQUAL(pszExt, "mem"))
676
0
    {
677
0
        if (poDS->Close() != OGRERR_NONE)
678
0
            return nullptr;
679
0
        poDS.reset(GDALDataset::Open(osTmpFilename.c_str(), GDAL_OF_VECTOR));
680
0
        if (!poDS)
681
0
            return nullptr;
682
0
        poDS->MarkSuppressOnClose();
683
0
    }
684
685
    /* Set of constraints:
686
    nX * nY = nCount
687
    nX * res = MaxX - MinX
688
    nY * res = MaxY - MinY
689
    */
690
691
0
    double dfRes;
692
0
    const char *pszRes = CSLFetchNameValue(papszOptions, "RESOLUTION");
693
0
    if (pszRes)
694
0
    {
695
0
        dfRes = CPLAtofM(pszRes);
696
0
    }
697
0
    else
698
0
    {
699
0
        const double dfTotalArea = (sEnvelope.MaxY - sEnvelope.MinY) *
700
0
                                   (sEnvelope.MaxX - sEnvelope.MinX);
701
0
        dfRes = sqrt(dfTotalArea / static_cast<double>(adfXVals.size()));
702
        // CPLDebug("GDAL", "dfRes = %f", dfRes);
703
704
        // Take 10 "random" points in the set, and find the minimum distance from
705
        // each to the closest one. And take the geometric average of those minimum
706
        // distances as the resolution.
707
0
        const size_t nNumSamplePoints = std::min<size_t>(10, adfXVals.size());
708
709
0
        poLyr = poDS->GetLayer(0);
710
0
        double dfSumDist2Min = 0;
711
0
        int nCountDistMin = 0;
712
0
        for (size_t i = 0; i < nNumSamplePoints; ++i)
713
0
        {
714
0
            const auto nIdx = i * adfXVals.size() / nNumSamplePoints;
715
0
            poLyr->SetSpatialFilterRect(
716
0
                adfXVals[nIdx] - 2 * dfRes, adfYVals[nIdx] - 2 * dfRes,
717
0
                adfXVals[nIdx] + 2 * dfRes, adfYVals[nIdx] + 2 * dfRes);
718
0
            double dfDist2Min = std::numeric_limits<double>::max();
719
0
            for (auto &&poFeat : poLyr)
720
0
            {
721
0
                const auto poGeom = poFeat->GetGeometryRef();
722
0
                CPLAssert(poGeom);
723
0
                CPLAssert(poGeom->getGeometryType() == wkbPoint);
724
0
                double dfDX = poGeom->toPoint()->getX() - adfXVals[nIdx];
725
0
                double dfDY = poGeom->toPoint()->getY() - adfYVals[nIdx];
726
0
                double dfDist2 = dfDX * dfDX + dfDY * dfDY;
727
0
                if (dfDist2 > 0 && dfDist2 < dfDist2Min)
728
0
                    dfDist2Min = dfDist2;
729
0
            }
730
0
            if (dfDist2Min < std::numeric_limits<double>::max())
731
0
            {
732
0
                dfSumDist2Min += dfDist2Min;
733
0
                nCountDistMin++;
734
0
            }
735
0
        }
736
0
        poLyr->SetSpatialFilter(nullptr);
737
0
        if (nCountDistMin > 0)
738
0
        {
739
0
            const double dfNewRes = sqrt(dfSumDist2Min / nCountDistMin);
740
            // CPLDebug("GDAL", "dfRes = %f, dfNewRes = %f", dfRes, dfNewRes);
741
0
            dfRes = dfNewRes;
742
0
        }
743
0
    }
744
745
0
    if (!(dfRes > 0))
746
0
    {
747
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid RESOLUTION");
748
0
        return nullptr;
749
0
    }
750
751
0
    constexpr double EPS = 1e-8;
752
0
    const double dfXSize =
753
0
        1 + std::floor((sEnvelope.MaxX - sEnvelope.MinX) / dfRes + EPS);
754
0
    if (dfXSize > std::numeric_limits<int>::max())
755
0
    {
756
0
        CPLError(CE_Failure, CPLE_AppDefined, "Integer overflow with dfXSize");
757
0
        return nullptr;
758
0
    }
759
0
    const int nXSize = std::max(2, static_cast<int>(dfXSize));
760
761
0
    const double dfYSize =
762
0
        1 + std::floor((sEnvelope.MaxY - sEnvelope.MinY) / dfRes + EPS);
763
0
    if (dfYSize > std::numeric_limits<int>::max())
764
0
    {
765
0
        CPLError(CE_Failure, CPLE_AppDefined, "Integer overflow with dfYSize");
766
0
        return nullptr;
767
0
    }
768
0
    const int nYSize = std::max(2, static_cast<int>(dfYSize));
769
770
0
    const double dfResX = (sEnvelope.MaxX - sEnvelope.MinX) / (nXSize - 1);
771
0
    const double dfResY = (sEnvelope.MaxY - sEnvelope.MinY) / (nYSize - 1);
772
    // CPLDebug("GDAL", "nXSize = %d, nYSize = %d", nXSize, nYSize);
773
774
0
    std::vector<std::shared_ptr<GDALDimension>> apoNewDims;
775
0
    const auto &apoSelfDims = GetDimensions();
776
0
    for (size_t i = 0; i < GetDimensionCount() - 1; ++i)
777
0
        apoNewDims.emplace_back(apoSelfDims[i]);
778
779
0
    auto poDimY = std::make_shared<GDALDimensionWeakIndexingVar>(
780
0
        std::string(), "dimY", GDAL_DIM_TYPE_HORIZONTAL_Y, "NORTH", nYSize);
781
0
    auto varY = GDALMDArrayRegularlySpaced::Create(
782
0
        std::string(), poDimY->GetName(), poDimY, sEnvelope.MinY, dfResY, 0);
783
0
    poDimY->SetIndexingVariable(varY);
784
785
0
    auto poDimX = std::make_shared<GDALDimensionWeakIndexingVar>(
786
0
        std::string(), "dimX", GDAL_DIM_TYPE_HORIZONTAL_X, "EAST", nXSize);
787
0
    auto varX = GDALMDArrayRegularlySpaced::Create(
788
0
        std::string(), poDimX->GetName(), poDimX, sEnvelope.MinX, dfResX, 0);
789
0
    poDimX->SetIndexingVariable(varX);
790
791
0
    apoNewDims.emplace_back(poDimY);
792
0
    apoNewDims.emplace_back(poDimX);
793
794
0
    const CPLStringList aosTokens(
795
0
        CSLTokenizeString2(osGridOptions.c_str(), ":", FALSE));
796
797
    // Extract nodata value from gridding options
798
0
    const char *pszNoDataValue = aosTokens.FetchNameValue("nodata");
799
0
    double dfNoDataValue = 0;
800
0
    if (pszNoDataValue != nullptr)
801
0
    {
802
0
        dfNoDataValue = CPLAtofM(pszNoDataValue);
803
0
    }
804
805
    // Extract radius from gridding options
806
0
    double dfRadius = 5 * std::max(dfResX, dfResY);
807
0
    const char *pszRadius = aosTokens.FetchNameValue("radius");
808
0
    if (pszRadius)
809
0
    {
810
0
        dfRadius = CPLAtofM(pszRadius);
811
0
    }
812
0
    else
813
0
    {
814
0
        const char *pszRadius1 = aosTokens.FetchNameValue("radius1");
815
0
        if (pszRadius1)
816
0
        {
817
0
            dfRadius = CPLAtofM(pszRadius1);
818
0
            const char *pszRadius2 = aosTokens.FetchNameValue("radius2");
819
0
            if (pszRadius2)
820
0
            {
821
0
                dfRadius = std::max(dfRadius, CPLAtofM(pszRadius2));
822
0
            }
823
0
        }
824
0
    }
825
826
0
    return GDALMDArrayGridded::Create(
827
0
        self, apoNewDims, varX, varY, std::move(poDS), eAlg,
828
0
        std::move(poGridOptions), dfNoDataValue, sEnvelope.MinX, dfResX,
829
0
        sEnvelope.MinY, dfResY, dfRadius);
830
0
}