/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 | } |