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