Coverage Report

Created: 2025-06-13 06:29

/src/gdal/gcore/gdalmultidim_meshgrid.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 * Name:     gdalmultiDim_meshgrid.cpp
3
 * Project:  GDAL Core
4
 * Purpose:  Return a vector of coordinate matrices from coordinate vectors.
5
 * Author:   Even Rouault <even.rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2024, Even Rouault <even.rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdal_priv.h"
14
15
#include <algorithm>
16
#include <limits>
17
18
/************************************************************************/
19
/*                       GetConcatenatedNames()                         */
20
/************************************************************************/
21
22
static std::string
23
GetConcatenatedNames(const std::vector<std::shared_ptr<GDALMDArray>> &apoArrays)
24
0
{
25
0
    std::string ret;
26
0
    for (const auto &poArray : apoArrays)
27
0
    {
28
0
        if (!ret.empty())
29
0
            ret += ", ";
30
0
        ret += poArray->GetFullName();
31
0
    }
32
0
    return ret;
33
0
}
34
35
/************************************************************************/
36
/*                         GDALMDArrayMeshGrid                          */
37
/************************************************************************/
38
39
class GDALMDArrayMeshGrid final : public GDALMDArray
40
{
41
    const std::vector<std::shared_ptr<GDALMDArray>> m_apoArrays;
42
    std::vector<std::shared_ptr<GDALDimension>> m_apoDims{};
43
    const size_t m_iDim;
44
    const bool m_bIJIndexing;
45
46
  protected:
47
    explicit GDALMDArrayMeshGrid(
48
        const std::vector<std::shared_ptr<GDALMDArray>> &apoArrays,
49
        const std::vector<std::shared_ptr<GDALDimension>> &apoDims, size_t iDim,
50
        bool bIJIndexing)
51
0
        : GDALAbstractMDArray(std::string(),
52
0
                              "Mesh grid view of " +
53
0
                                  GetConcatenatedNames(apoArrays)),
54
0
          GDALMDArray(std::string(),
55
0
                      "Mesh grid view of " + GetConcatenatedNames(apoArrays)),
56
0
          m_apoArrays(apoArrays), m_apoDims(apoDims), m_iDim(iDim),
57
0
          m_bIJIndexing(bIJIndexing)
58
0
    {
59
0
    }
60
61
    bool IRead(const GUInt64 *arrayStartIdx, const size_t *count,
62
               const GInt64 *arrayStep, const GPtrDiff_t *bufferStride,
63
               const GDALExtendedDataType &bufferDataType,
64
               void *pDstBuffer) const override;
65
66
  public:
67
    static std::shared_ptr<GDALMDArrayMeshGrid>
68
    Create(const std::vector<std::shared_ptr<GDALMDArray>> &apoArrays,
69
           size_t iDim, bool bIJIndexing)
70
0
    {
71
0
        std::vector<std::shared_ptr<GDALDimension>> apoDims;
72
0
        for (size_t i = 0; i < apoArrays.size(); ++i)
73
0
        {
74
0
            const size_t iTranslatedDim = (!bIJIndexing && i <= 1) ? 1 - i : i;
75
0
            apoDims.push_back(apoArrays[iTranslatedDim]->GetDimensions()[0]);
76
0
        }
77
0
        auto newAr(std::shared_ptr<GDALMDArrayMeshGrid>(
78
0
            new GDALMDArrayMeshGrid(apoArrays, apoDims, iDim, bIJIndexing)));
79
0
        newAr->SetSelf(newAr);
80
0
        return newAr;
81
0
    }
82
83
    bool IsWritable() const override
84
0
    {
85
0
        return false;
86
0
    }
87
88
    const std::string &GetFilename() const override
89
0
    {
90
0
        return m_apoArrays[m_iDim]->GetFilename();
91
0
    }
92
93
    const std::vector<std::shared_ptr<GDALDimension>> &
94
    GetDimensions() const override
95
0
    {
96
0
        return m_apoDims;
97
0
    }
98
99
    const GDALExtendedDataType &GetDataType() const override
100
0
    {
101
0
        return m_apoArrays[m_iDim]->GetDataType();
102
0
    }
103
104
    std::shared_ptr<GDALAttribute>
105
    GetAttribute(const std::string &osName) const override
106
0
    {
107
0
        return m_apoArrays[m_iDim]->GetAttribute(osName);
108
0
    }
109
110
    std::vector<std::shared_ptr<GDALAttribute>>
111
    GetAttributes(CSLConstList papszOptions = nullptr) const override
112
0
    {
113
0
        return m_apoArrays[m_iDim]->GetAttributes(papszOptions);
114
0
    }
115
116
    const std::string &GetUnit() const override
117
0
    {
118
0
        return m_apoArrays[m_iDim]->GetUnit();
119
0
    }
120
121
    const void *GetRawNoDataValue() const override
122
0
    {
123
0
        return m_apoArrays[m_iDim]->GetRawNoDataValue();
124
0
    }
125
126
    double GetOffset(bool *pbHasOffset,
127
                     GDALDataType *peStorageType) const override
128
0
    {
129
0
        return m_apoArrays[m_iDim]->GetOffset(pbHasOffset, peStorageType);
130
0
    }
131
132
    double GetScale(bool *pbHasScale,
133
                    GDALDataType *peStorageType) const override
134
0
    {
135
0
        return m_apoArrays[m_iDim]->GetScale(pbHasScale, peStorageType);
136
0
    }
137
};
138
139
/************************************************************************/
140
/*                             IRead()                                  */
141
/************************************************************************/
142
143
bool GDALMDArrayMeshGrid::IRead(const GUInt64 *arrayStartIdx,
144
                                const size_t *count, const GInt64 *arrayStep,
145
                                const GPtrDiff_t *bufferStride,
146
                                const GDALExtendedDataType &bufferDataType,
147
                                void *pDstBuffer) const
148
0
{
149
0
    const size_t nBufferDTSize = bufferDataType.GetSize();
150
0
    const size_t iTranslatedDim =
151
0
        (!m_bIJIndexing && m_iDim <= 1) ? 1 - m_iDim : m_iDim;
152
0
    std::vector<GByte> abyTmpData(nBufferDTSize * count[iTranslatedDim]);
153
0
    const GPtrDiff_t strideOne[] = {1};
154
0
    if (!m_apoArrays[m_iDim]->Read(&arrayStartIdx[iTranslatedDim],
155
0
                                   &count[iTranslatedDim],
156
0
                                   &arrayStep[iTranslatedDim], strideOne,
157
0
                                   bufferDataType, abyTmpData.data()))
158
0
        return false;
159
160
0
    const auto nDims = GetDimensionCount();
161
162
0
    struct Stack
163
0
    {
164
0
        size_t nIters = 0;
165
0
        GByte *dst_ptr = nullptr;
166
0
        GPtrDiff_t dst_inc_offset = 0;
167
0
    };
168
169
    // +1 to avoid -Werror=null-dereference
170
0
    std::vector<Stack> stack(nDims + 1);
171
0
    for (size_t i = 0; i < nDims; i++)
172
0
    {
173
0
        stack[i].dst_inc_offset =
174
0
            static_cast<GPtrDiff_t>(bufferStride[i] * nBufferDTSize);
175
0
    }
176
0
    stack[0].dst_ptr = static_cast<GByte *>(pDstBuffer);
177
0
    size_t dimIdx = 0;
178
0
    size_t valIdx = 0;
179
180
0
lbl_next_depth:
181
0
    if (dimIdx == nDims - 1)
182
0
    {
183
0
        auto nIters = count[dimIdx];
184
0
        GByte *dst_ptr = stack[dimIdx].dst_ptr;
185
0
        if (dimIdx == iTranslatedDim)
186
0
        {
187
0
            valIdx = 0;
188
0
            while (true)
189
0
            {
190
0
                GDALExtendedDataType::CopyValue(
191
0
                    &abyTmpData[nBufferDTSize * valIdx], bufferDataType,
192
0
                    dst_ptr, bufferDataType);
193
0
                if ((--nIters) == 0)
194
0
                    break;
195
0
                ++valIdx;
196
0
                dst_ptr += stack[dimIdx].dst_inc_offset;
197
0
            }
198
0
        }
199
0
        else
200
0
        {
201
0
            while (true)
202
0
            {
203
0
                GDALExtendedDataType::CopyValue(
204
0
                    &abyTmpData[nBufferDTSize * valIdx], bufferDataType,
205
0
                    dst_ptr, bufferDataType);
206
0
                if ((--nIters) == 0)
207
0
                    break;
208
0
                dst_ptr += stack[dimIdx].dst_inc_offset;
209
0
            }
210
0
        }
211
0
    }
212
0
    else
213
0
    {
214
0
        if (dimIdx == iTranslatedDim)
215
0
            valIdx = 0;
216
0
        stack[dimIdx].nIters = count[dimIdx];
217
0
        while (true)
218
0
        {
219
0
            dimIdx++;
220
0
            stack[dimIdx].dst_ptr = stack[dimIdx - 1].dst_ptr;
221
0
            goto lbl_next_depth;
222
0
        lbl_return_to_caller:
223
0
            dimIdx--;
224
0
            if ((--stack[dimIdx].nIters) == 0)
225
0
                break;
226
0
            if (dimIdx == iTranslatedDim)
227
0
                ++valIdx;
228
0
            stack[dimIdx].dst_ptr += stack[dimIdx].dst_inc_offset;
229
0
        }
230
0
    }
231
0
    if (dimIdx > 0)
232
0
    {
233
0
        goto lbl_return_to_caller;
234
0
    }
235
236
0
    if (bufferDataType.NeedsFreeDynamicMemory())
237
0
    {
238
0
        for (size_t i = 0; i < count[iTranslatedDim]; ++i)
239
0
        {
240
0
            bufferDataType.FreeDynamicMemory(&abyTmpData[i * nBufferDTSize]);
241
0
        }
242
0
    }
243
244
0
    return true;
245
0
}
246
247
/************************************************************************/
248
/*                      GDALMDArrayGetMeshGrid()                        */
249
/************************************************************************/
250
251
/** Return a list of multidimensional arrays from a list of one-dimensional
252
 * arrays.
253
 *
254
 * This is typically used to transform one-dimensional longitude, latitude
255
 * arrays into 2D ones.
256
 *
257
 * More formally, for one-dimensional arrays x1, x2,..., xn with lengths
258
 * Ni=len(xi), returns (N1, N2, ..., Nn) shaped arrays if indexing="ij" or
259
 * (N2, N1, ..., Nn) shaped arrays if indexing="xy" with the elements of xi
260
 * repeated to fill the matrix along the first dimension for x1, the second
261
 * for x2 and so on.
262
 *
263
 * For example, if x = [1, 2], and y = [3, 4, 5],
264
 * GetMeshGrid([x, y], ["INDEXING=xy"]) will return [xm, ym] such that
265
 * xm=[[1, 2],[1, 2],[1, 2]] and ym=[[3, 3],[4, 4],[5, 5]],
266
 * or more generally xm[any index][i] = x[i] and ym[i][any index]=y[i]
267
 *
268
 * and
269
 * GetMeshGrid([x, y], ["INDEXING=ij"]) will return [xm, ym] such that
270
 * xm=[[1, 1, 1],[2, 2, 2]] and ym=[[3, 4, 5],[3, 4, 5]],
271
 * or more generally xm[i][any index] = x[i] and ym[any index][i]=y[i]
272
 *
273
 * The currently supported options are:
274
 * <ul>
275
 * <li>INDEXING=xy/ij: Cartesian ("xy", default) or matrix ("ij") indexing of
276
 * output.
277
 * </li>
278
 * </ul>
279
 *
280
 * This is the same as
281
 * <a href="https://numpy.org/doc/stable/reference/generated/numpy.meshgrid.html">numpy.meshgrid()</a>
282
 * function.
283
 *
284
 * This is the same as the C function GDALMDArrayGetMeshGrid()
285
 *
286
 * @param apoArrays Input arrays
287
 * @param papszOptions NULL, or NULL terminated list of options.
288
 *
289
 * @return an array of coordinate matrices
290
 * @since 3.10
291
 */
292
293
/* static */ std::vector<std::shared_ptr<GDALMDArray>> GDALMDArray::GetMeshGrid(
294
    const std::vector<std::shared_ptr<GDALMDArray>> &apoArrays,
295
    CSLConstList papszOptions)
296
0
{
297
0
    std::vector<std::shared_ptr<GDALMDArray>> ret;
298
0
    for (const auto &poArray : apoArrays)
299
0
    {
300
0
        if (poArray->GetDimensionCount() != 1)
301
0
        {
302
0
            CPLError(CE_Failure, CPLE_NotSupported,
303
0
                     "Only 1-D input arrays are accepted");
304
0
            return ret;
305
0
        }
306
0
    }
307
308
0
    const char *pszIndexing =
309
0
        CSLFetchNameValueDef(papszOptions, "INDEXING", "xy");
310
0
    if (!EQUAL(pszIndexing, "xy") && !EQUAL(pszIndexing, "ij"))
311
0
    {
312
0
        CPLError(CE_Failure, CPLE_NotSupported,
313
0
                 "Only INDEXING=xy or ij is accepted");
314
0
        return ret;
315
0
    }
316
0
    const bool bIJIndexing = EQUAL(pszIndexing, "ij");
317
318
0
    for (size_t i = 0; i < apoArrays.size(); ++i)
319
0
    {
320
0
        ret.push_back(GDALMDArrayMeshGrid::Create(apoArrays, i, bIJIndexing));
321
0
    }
322
323
0
    return ret;
324
0
}