Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: Zarr driver
5 : * Author: Even Rouault <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2021, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "zarr.h"
14 :
15 : #include "cpl_float.h"
16 : #include "cpl_mem_cache.h"
17 : #include "cpl_multiproc.h"
18 : #include "cpl_vsi_virtual.h"
19 : #include "ucs4_utf8.hpp"
20 : #include "gdal_thread_pool.h"
21 :
22 : #include "netcdf_cf_constants.h" // for CF_UNITS, etc
23 :
24 : #include <algorithm>
25 : #include <cassert>
26 : #include <cmath>
27 : #include <cstdlib>
28 : #include <limits>
29 : #include <map>
30 : #include <set>
31 :
32 : #if defined(__clang__) || defined(_MSC_VER)
33 : #define COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT
34 : #endif
35 :
36 : namespace
37 : {
38 :
39 4 : inline std::vector<GByte> UTF8ToUCS4(const char *pszStr, bool needByteSwap)
40 : {
41 4 : const size_t nLen = strlen(pszStr);
42 : // Worst case if that we need 4 more bytes than the UTF-8 one
43 : // (when the content is pure ASCII)
44 4 : if (nLen > std::numeric_limits<size_t>::max() / sizeof(uint32_t))
45 0 : throw std::bad_alloc();
46 4 : std::vector<GByte> ret(nLen * sizeof(uint32_t));
47 4 : size_t outPos = 0;
48 27 : for (size_t i = 0; i < nLen; outPos += sizeof(uint32_t))
49 : {
50 23 : uint32_t ucs4 = 0;
51 23 : int consumed = FcUtf8ToUcs4(
52 23 : reinterpret_cast<const uint8_t *>(pszStr + i), &ucs4, nLen - i);
53 23 : if (consumed <= 0)
54 : {
55 0 : ret.resize(outPos);
56 : }
57 23 : if (needByteSwap)
58 : {
59 1 : CPL_SWAP32PTR(&ucs4);
60 : }
61 23 : memcpy(&ret[outPos], &ucs4, sizeof(uint32_t));
62 23 : i += consumed;
63 : }
64 4 : ret.resize(outPos);
65 4 : return ret;
66 : }
67 :
68 9 : inline char *UCS4ToUTF8(const uint8_t *ucs4Ptr, size_t nSize, bool needByteSwap)
69 : {
70 : // A UCS4 char can require up to 6 bytes in UTF8.
71 9 : if (nSize > (std::numeric_limits<size_t>::max() - 1) / 6 * 4)
72 0 : return nullptr;
73 9 : const size_t nOutSize = nSize / 4 * 6 + 1;
74 9 : char *ret = static_cast<char *>(VSI_MALLOC_VERBOSE(nOutSize));
75 9 : if (ret == nullptr)
76 0 : return nullptr;
77 9 : size_t outPos = 0;
78 38 : for (size_t i = 0; i + sizeof(uint32_t) - 1 < nSize; i += sizeof(uint32_t))
79 : {
80 : uint32_t ucs4;
81 29 : memcpy(&ucs4, ucs4Ptr + i, sizeof(uint32_t));
82 29 : if (needByteSwap)
83 : {
84 2 : CPL_SWAP32PTR(&ucs4);
85 : }
86 : int written =
87 29 : FcUcs4ToUtf8(ucs4, reinterpret_cast<uint8_t *>(ret + outPos));
88 29 : outPos += written;
89 : }
90 9 : ret[outPos] = 0;
91 9 : return ret;
92 : }
93 :
94 : } // namespace
95 :
96 : /************************************************************************/
97 : /* ZarrArray::ParseChunkSize() */
98 : /************************************************************************/
99 :
100 1961 : /* static */ bool ZarrArray::ParseChunkSize(const CPLJSONArray &oChunks,
101 : const GDALExtendedDataType &oType,
102 : std::vector<GUInt64> &anBlockSize)
103 : {
104 1961 : size_t nBlockSize = oType.GetSize();
105 5358 : for (const auto &item : oChunks)
106 : {
107 3400 : const auto nSize = static_cast<GUInt64>(item.ToLong());
108 3400 : if (nSize == 0)
109 : {
110 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid content for chunks");
111 3 : return false;
112 : }
113 3399 : if (nBlockSize > std::numeric_limits<size_t>::max() / nSize)
114 : {
115 2 : CPLError(CE_Failure, CPLE_AppDefined, "Too large chunks");
116 2 : return false;
117 : }
118 3397 : nBlockSize *= static_cast<size_t>(nSize);
119 3397 : anBlockSize.emplace_back(nSize);
120 : }
121 :
122 1958 : return true;
123 : }
124 :
125 : /************************************************************************/
126 : /* ZarrArray::ComputeBlockCount() */
127 : /************************************************************************/
128 :
129 2430 : /* static */ uint64_t ZarrArray::ComputeBlockCount(
130 : const std::string &osName,
131 : const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
132 : const std::vector<GUInt64> &anBlockSize)
133 : {
134 2430 : uint64_t nTotalBlockCount = 1;
135 6534 : for (size_t i = 0; i < aoDims.size(); ++i)
136 : {
137 : const uint64_t nBlockThisDim =
138 4106 : cpl::div_round_up(aoDims[i]->GetSize(), anBlockSize[i]);
139 8212 : if (nBlockThisDim != 0 &&
140 : nTotalBlockCount >
141 4106 : std::numeric_limits<uint64_t>::max() / nBlockThisDim)
142 : {
143 2 : CPLError(
144 : CE_Failure, CPLE_NotSupported,
145 : "Array %s has more than 2^64 blocks. This is not supported.",
146 : osName.c_str());
147 2 : return 0;
148 : }
149 4104 : nTotalBlockCount *= nBlockThisDim;
150 : }
151 2428 : return nTotalBlockCount;
152 : }
153 :
154 : /************************************************************************/
155 : /* ComputeCountInnerBlockInOuter() */
156 : /************************************************************************/
157 :
158 : static std::vector<GUInt64>
159 2430 : ComputeCountInnerBlockInOuter(const std::vector<GUInt64> &anInnerBlockSize,
160 : const std::vector<GUInt64> &anOuterBlockSize)
161 : {
162 2430 : std::vector<GUInt64> ret;
163 2430 : CPLAssert(anInnerBlockSize.size() == anOuterBlockSize.size());
164 6536 : for (size_t i = 0; i < anInnerBlockSize.size(); ++i)
165 : {
166 : // All those assertions must be checked by the caller before
167 : // constructing the ZarrArray instance.
168 4106 : CPLAssert(anInnerBlockSize[i] > 0);
169 4106 : CPLAssert(anInnerBlockSize[i] <= anOuterBlockSize[i]);
170 4106 : CPLAssert((anOuterBlockSize[i] % anInnerBlockSize[i]) == 0);
171 4106 : ret.push_back(anOuterBlockSize[i] / anInnerBlockSize[i]);
172 : }
173 2430 : return ret;
174 : }
175 :
176 : /************************************************************************/
177 : /* ComputeInnerBlockSizeBytes() */
178 : /************************************************************************/
179 :
180 : static size_t
181 2428 : ComputeInnerBlockSizeBytes(const std::vector<DtypeElt> &aoDtypeElts,
182 : const std::vector<GUInt64> &anInnerBlockSize)
183 : {
184 : const size_t nSourceSize =
185 2428 : aoDtypeElts.back().nativeOffset + aoDtypeElts.back().nativeSize;
186 2428 : size_t nInnerBlockSizeBytes = nSourceSize;
187 6528 : for (const auto &nBlockSize : anInnerBlockSize)
188 : {
189 : // Given that ParseChunkSize() has checked that the outer block size
190 : // fits on size_t, and that m_anInnerBlockSize[i] <= m_anOuterBlockSize[i],
191 : // this cast is safe, and the multiplication cannot overflow.
192 4100 : nInnerBlockSizeBytes *= static_cast<size_t>(nBlockSize);
193 : }
194 2428 : return nInnerBlockSizeBytes;
195 : }
196 :
197 : /************************************************************************/
198 : /* ZarrArray::ZarrArray() */
199 : /************************************************************************/
200 :
201 2430 : ZarrArray::ZarrArray(
202 : const std::shared_ptr<ZarrSharedResource> &poSharedResource,
203 : const std::shared_ptr<ZarrGroupBase> &poParent, const std::string &osName,
204 : const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
205 : const GDALExtendedDataType &oType, const std::vector<DtypeElt> &aoDtypeElts,
206 : const std::vector<GUInt64> &anOuterBlockSize,
207 0 : const std::vector<GUInt64> &anInnerBlockSize)
208 : :
209 : #if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT)
210 : GDALAbstractMDArray(poParent->GetFullName(), osName),
211 : #endif
212 2430 : GDALPamMDArray(poParent->GetFullName(), osName,
213 : poSharedResource->GetPAM()),
214 : m_poSharedResource(poSharedResource), m_poParent(poParent),
215 : m_aoDims(aoDims), m_oType(oType), m_aoDtypeElts(aoDtypeElts),
216 : m_anOuterBlockSize(anOuterBlockSize),
217 : m_anInnerBlockSize(anInnerBlockSize),
218 : m_anCountInnerBlockInOuter(ComputeCountInnerBlockInOuter(
219 2430 : m_anInnerBlockSize, m_anOuterBlockSize)),
220 : m_nTotalInnerChunkCount(
221 2430 : ComputeBlockCount(osName, aoDims, m_anInnerBlockSize)),
222 : m_nInnerBlockSizeBytes(
223 2430 : m_nTotalInnerChunkCount > 0
224 2430 : ? ComputeInnerBlockSizeBytes(m_aoDtypeElts, m_anInnerBlockSize)
225 : : 0),
226 2430 : m_oAttrGroup(m_osFullName, /*bContainerIsGroup=*/false),
227 2430 : m_bUseOptimizedCodePaths(CPLTestBool(
228 12150 : CPLGetConfigOption("GDAL_ZARR_USE_OPTIMIZED_CODE_PATHS", "YES")))
229 : {
230 2430 : m_oCRSAttribute.Deinit();
231 2430 : }
232 :
233 : /************************************************************************/
234 : /* ~ZarrArray() */
235 : /************************************************************************/
236 :
237 2430 : ZarrArray::~ZarrArray()
238 : {
239 2430 : if (m_pabyNoData)
240 : {
241 1182 : m_oType.FreeDynamicMemory(&m_pabyNoData[0]);
242 1182 : CPLFree(m_pabyNoData);
243 : }
244 :
245 2430 : DeallocateDecodedBlockData();
246 2430 : }
247 :
248 : /************************************************************************/
249 : /* ZarrArray::SerializeSpecialAttributes() */
250 : /************************************************************************/
251 :
252 412 : CPLJSONObject ZarrArray::SerializeSpecialAttributes()
253 : {
254 412 : m_bSRSModified = false;
255 412 : m_oAttrGroup.UnsetModified();
256 :
257 412 : auto oAttrs = m_oAttrGroup.Serialize();
258 :
259 : const bool bUseSpatialProjConventions =
260 412 : EQUAL(m_aosCreationOptions.FetchNameValueDef(
261 : "GEOREFERENCING_CONVENTION", "GDAL"),
262 : "SPATIAL_PROJ");
263 :
264 412 : if (m_oCRSAttribute.IsValid() && bUseSpatialProjConventions)
265 1 : oAttrs.Add(CRS_ATTRIBUTE_NAME, m_oCRSAttribute);
266 :
267 42 : const auto ExportToWkt2AndPROJJSON = [this](CPLJSONObject &oContainer,
268 : const char *pszWKT2AttrName,
269 84 : const char *pszPROJJSONAttrName)
270 : {
271 42 : const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
272 42 : char *pszWKT = nullptr;
273 42 : if (m_poSRS->exportToWkt(&pszWKT, apszOptions) == OGRERR_NONE)
274 : {
275 42 : oContainer.Set(pszWKT2AttrName, pszWKT);
276 : }
277 42 : CPLFree(pszWKT);
278 :
279 : {
280 84 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
281 42 : char *projjson = nullptr;
282 84 : if (m_poSRS->exportToPROJJSON(&projjson, nullptr) == OGRERR_NONE &&
283 42 : projjson != nullptr)
284 : {
285 84 : CPLJSONDocument oDocProjJSON;
286 42 : if (oDocProjJSON.LoadMemory(std::string(projjson)))
287 : {
288 42 : oContainer.Set(pszPROJJSONAttrName, oDocProjJSON.GetRoot());
289 : }
290 : }
291 42 : CPLFree(projjson);
292 : }
293 42 : };
294 :
295 824 : CPLJSONArray oZarrConventionsArray;
296 412 : if (bUseSpatialProjConventions)
297 : {
298 3 : if (m_poSRS)
299 : {
300 6 : CPLJSONObject oConventionProj;
301 3 : oConventionProj.Set(
302 : "schema_url",
303 : "https://raw.githubusercontent.com/zarr-experimental/geo-proj/"
304 : "refs/tags/v1/schema.json");
305 3 : oConventionProj.Set("spec_url",
306 : "https://github.com/zarr-experimental/geo-proj/"
307 : "blob/v1/README.md");
308 3 : oConventionProj.Set("uuid", "f17cb550-5864-4468-aeb7-f3180cfb622f");
309 3 : oConventionProj.Set("name", "proj:"); // ending colon intended
310 3 : oConventionProj.Set(
311 : "description",
312 : "Coordinate reference system information for geospatial data");
313 :
314 3 : oZarrConventionsArray.Add(oConventionProj);
315 :
316 3 : const char *pszAuthorityName = m_poSRS->GetAuthorityName();
317 3 : const char *pszAuthorityCode = m_poSRS->GetAuthorityCode();
318 3 : if (pszAuthorityName && pszAuthorityCode)
319 : {
320 2 : oAttrs.Set("proj:code", CPLSPrintf("%s:%s", pszAuthorityName,
321 : pszAuthorityCode));
322 : }
323 : else
324 : {
325 1 : ExportToWkt2AndPROJJSON(oAttrs, "proj:wkt2", "proj:projjson");
326 : }
327 : }
328 :
329 3 : if (GetDimensionCount() >= 2)
330 : {
331 3 : bool bAddSpatialProjConvention = false;
332 :
333 3 : double dfXOff = std::numeric_limits<double>::quiet_NaN();
334 3 : double dfXRes = std::numeric_limits<double>::quiet_NaN();
335 3 : double dfYOff = std::numeric_limits<double>::quiet_NaN();
336 3 : double dfYRes = std::numeric_limits<double>::quiet_NaN();
337 6 : std::string osDimXName;
338 6 : std::string osDimYName;
339 6 : std::string osDimZName;
340 3 : double dfWidth = 0, dfHeight = 0;
341 9 : for (const auto &poDim : GetDimensions())
342 : {
343 6 : if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X)
344 : {
345 2 : osDimXName = poDim->GetName();
346 2 : dfWidth = static_cast<double>(poDim->GetSize());
347 4 : auto poVar = poDim->GetIndexingVariable();
348 2 : if (poVar && poVar->IsRegularlySpaced(dfXOff, dfXRes))
349 : {
350 1 : dfXOff -= dfXRes / 2;
351 : }
352 : }
353 4 : else if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y)
354 : {
355 2 : osDimYName = poDim->GetName();
356 2 : dfHeight = static_cast<double>(poDim->GetSize());
357 4 : auto poVar = poDim->GetIndexingVariable();
358 2 : if (poVar && poVar->IsRegularlySpaced(dfYOff, dfYRes))
359 : {
360 1 : dfYOff -= dfYRes / 2;
361 : }
362 : }
363 2 : else if (poDim->GetType() == GDAL_DIM_TYPE_VERTICAL)
364 : {
365 0 : osDimZName = poDim->GetName();
366 : }
367 : }
368 :
369 3 : GDALGeoTransform gt;
370 3 : if (!osDimXName.empty() && !osDimYName.empty())
371 : {
372 6 : const auto oGDALGeoTransform = oAttrs["gdal:geotransform"];
373 : const bool bHasGDALGeoTransform =
374 4 : (oGDALGeoTransform.GetType() ==
375 4 : CPLJSONObject::Type::Array &&
376 4 : oGDALGeoTransform.ToArray().size() == 6);
377 2 : if (bHasGDALGeoTransform)
378 : {
379 : const auto oGDALGeoTransformArray =
380 2 : oGDALGeoTransform.ToArray();
381 14 : for (int i = 0; i < 6; ++i)
382 : {
383 12 : gt[i] = oGDALGeoTransformArray[i].ToDouble();
384 : }
385 2 : bAddSpatialProjConvention = true;
386 : }
387 0 : else if (!std::isnan(dfXOff) && !std::isnan(dfXRes) &&
388 0 : !std::isnan(dfYOff) && !std::isnan(dfYRes))
389 : {
390 0 : gt.xorig = dfXOff;
391 0 : gt.xscale = dfXRes;
392 0 : gt.xrot = 0; // xrot
393 0 : gt.yorig = dfYOff;
394 0 : gt.yrot = 0; // yrot
395 0 : gt.yscale = dfYRes;
396 0 : bAddSpatialProjConvention = true;
397 : }
398 : }
399 :
400 3 : if (bAddSpatialProjConvention)
401 : {
402 : const auto osGDALMD_AREA_OR_POINT =
403 6 : oAttrs.GetString(GDALMD_AREA_OR_POINT);
404 2 : if (osGDALMD_AREA_OR_POINT == GDALMD_AOP_AREA)
405 : {
406 1 : oAttrs.Add("spatial:registration", "pixel");
407 1 : oAttrs.Delete(GDALMD_AREA_OR_POINT);
408 : }
409 1 : else if (osGDALMD_AREA_OR_POINT == GDALMD_AOP_POINT)
410 : {
411 1 : oAttrs.Add("spatial:registration", "node");
412 1 : oAttrs.Delete(GDALMD_AREA_OR_POINT);
413 :
414 : // Going from GDAL's corner convention to pixel center
415 1 : gt.xorig += 0.5 * gt.xscale + 0.5 * gt.xrot;
416 1 : gt.yorig += 0.5 * gt.yrot + 0.5 * gt.yscale;
417 1 : dfWidth -= 1.0;
418 1 : dfHeight -= 1.0;
419 : }
420 :
421 4 : CPLJSONArray oAttrSpatialTransform;
422 2 : oAttrSpatialTransform.Add(gt.xscale); // xres
423 2 : oAttrSpatialTransform.Add(gt.xrot); // xrot
424 2 : oAttrSpatialTransform.Add(gt.xorig); // xoff
425 2 : oAttrSpatialTransform.Add(gt.yrot); // yrot
426 2 : oAttrSpatialTransform.Add(gt.yscale); // yres
427 2 : oAttrSpatialTransform.Add(gt.yorig); // yoff
428 :
429 2 : oAttrs.Add("spatial:transform_type", "affine");
430 2 : oAttrs.Add("spatial:transform", oAttrSpatialTransform);
431 2 : oAttrs.Delete("gdal:geotransform");
432 :
433 : double dfX0, dfY0;
434 : double dfX1, dfY1;
435 : double dfX2, dfY2;
436 : double dfX3, dfY3;
437 2 : gt.Apply(0, 0, &dfX0, &dfY0);
438 2 : gt.Apply(dfWidth, 0, &dfX1, &dfY1);
439 2 : gt.Apply(0, dfHeight, &dfX2, &dfY2);
440 2 : gt.Apply(dfWidth, dfHeight, &dfX3, &dfY3);
441 : const double dfXMin =
442 2 : std::min(std::min(dfX0, dfX1), std::min(dfX2, dfX3));
443 : const double dfYMin =
444 2 : std::min(std::min(dfY0, dfY1), std::min(dfY2, dfY3));
445 : const double dfXMax =
446 2 : std::max(std::max(dfX0, dfX1), std::max(dfX2, dfX3));
447 : const double dfYMax =
448 2 : std::max(std::max(dfY0, dfY1), std::max(dfY2, dfY3));
449 :
450 4 : CPLJSONArray oAttrSpatialBBOX;
451 2 : oAttrSpatialBBOX.Add(dfXMin);
452 2 : oAttrSpatialBBOX.Add(dfYMin);
453 2 : oAttrSpatialBBOX.Add(dfXMax);
454 2 : oAttrSpatialBBOX.Add(dfYMax);
455 2 : oAttrs.Add("spatial:bbox", oAttrSpatialBBOX);
456 :
457 4 : CPLJSONArray aoSpatialDimensions;
458 2 : if (!osDimZName.empty())
459 0 : aoSpatialDimensions.Add(osDimZName);
460 2 : aoSpatialDimensions.Add(osDimYName);
461 2 : aoSpatialDimensions.Add(osDimXName);
462 2 : oAttrs.Add("spatial:dimensions", aoSpatialDimensions);
463 :
464 4 : CPLJSONObject oConventionSpatial;
465 2 : oConventionSpatial.Set(
466 : "schema_url",
467 : "https://raw.githubusercontent.com/zarr-conventions/"
468 : "spatial/refs/tags/v1/schema.json");
469 2 : oConventionSpatial.Set("spec_url",
470 : "https://github.com/zarr-conventions/"
471 : "spatial/blob/v1/README.md");
472 2 : oConventionSpatial.Set("uuid",
473 : "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4");
474 2 : oConventionSpatial.Set("name",
475 : "spatial:"); // ending colon intended
476 2 : oConventionSpatial.Set("description",
477 : "Spatial coordinate information");
478 :
479 2 : oZarrConventionsArray.Add(oConventionSpatial);
480 : }
481 : }
482 :
483 3 : if (oZarrConventionsArray.size() > 0)
484 : {
485 3 : oAttrs.Add("zarr_conventions", oZarrConventionsArray);
486 : }
487 : }
488 409 : else if (m_poSRS)
489 : {
490 : // GDAL convention
491 :
492 41 : CPLJSONObject oCRS;
493 41 : ExportToWkt2AndPROJJSON(oCRS, "wkt", "projjson");
494 :
495 41 : const char *pszAuthorityCode = m_poSRS->GetAuthorityCode();
496 41 : const char *pszAuthorityName = m_poSRS->GetAuthorityName();
497 41 : if (pszAuthorityCode && pszAuthorityName &&
498 8 : EQUAL(pszAuthorityName, "EPSG"))
499 : {
500 8 : oCRS.Add("url",
501 16 : std::string("http://www.opengis.net/def/crs/EPSG/0/") +
502 : pszAuthorityCode);
503 : }
504 :
505 41 : oAttrs.Add(CRS_ATTRIBUTE_NAME, oCRS);
506 : }
507 :
508 412 : if (m_osUnit.empty())
509 : {
510 403 : if (m_bUnitModified)
511 0 : oAttrs.Delete(CF_UNITS);
512 : }
513 : else
514 : {
515 9 : oAttrs.Set(CF_UNITS, m_osUnit);
516 : }
517 412 : m_bUnitModified = false;
518 :
519 412 : if (!m_bHasOffset)
520 : {
521 409 : oAttrs.Delete(CF_ADD_OFFSET);
522 : }
523 : else
524 : {
525 3 : oAttrs.Set(CF_ADD_OFFSET, m_dfOffset);
526 : }
527 412 : m_bOffsetModified = false;
528 :
529 412 : if (!m_bHasScale)
530 : {
531 409 : oAttrs.Delete(CF_SCALE_FACTOR);
532 : }
533 : else
534 : {
535 3 : oAttrs.Set(CF_SCALE_FACTOR, m_dfScale);
536 : }
537 412 : m_bScaleModified = false;
538 :
539 824 : return oAttrs;
540 : }
541 :
542 : /************************************************************************/
543 : /* FillBlockSize() */
544 : /************************************************************************/
545 :
546 : /* static */
547 529 : bool ZarrArray::FillBlockSize(
548 : const std::vector<std::shared_ptr<GDALDimension>> &aoDimensions,
549 : const GDALExtendedDataType &oDataType, std::vector<GUInt64> &anBlockSize,
550 : CSLConstList papszOptions)
551 : {
552 529 : const auto nDims = aoDimensions.size();
553 529 : anBlockSize.resize(nDims);
554 1335 : for (size_t i = 0; i < nDims; ++i)
555 806 : anBlockSize[i] = 1;
556 529 : if (nDims >= 2)
557 : {
558 538 : anBlockSize[nDims - 2] =
559 538 : std::min(std::max<GUInt64>(1, aoDimensions[nDims - 2]->GetSize()),
560 538 : static_cast<GUInt64>(256));
561 538 : anBlockSize[nDims - 1] =
562 538 : std::min(std::max<GUInt64>(1, aoDimensions[nDims - 1]->GetSize()),
563 807 : static_cast<GUInt64>(256));
564 : }
565 260 : else if (nDims == 1)
566 : {
567 213 : anBlockSize[0] = std::max<GUInt64>(1, aoDimensions[0]->GetSize());
568 : }
569 :
570 529 : const char *pszBlockSize = CSLFetchNameValue(papszOptions, "BLOCKSIZE");
571 529 : if (pszBlockSize)
572 : {
573 : const auto aszTokens(
574 54 : CPLStringList(CSLTokenizeString2(pszBlockSize, ",", 0)));
575 54 : if (static_cast<size_t>(aszTokens.size()) != nDims)
576 : {
577 5 : CPLError(CE_Failure, CPLE_AppDefined,
578 : "Invalid number of values in BLOCKSIZE");
579 5 : return false;
580 : }
581 49 : size_t nBlockSize = oDataType.GetSize();
582 157 : for (size_t i = 0; i < nDims; ++i)
583 : {
584 109 : const auto v = static_cast<GUInt64>(CPLAtoGIntBig(aszTokens[i]));
585 109 : if (v > 0)
586 : {
587 109 : anBlockSize[i] = v;
588 : }
589 109 : if (anBlockSize[i] >
590 109 : std::numeric_limits<size_t>::max() / nBlockSize)
591 : {
592 1 : CPLError(CE_Failure, CPLE_AppDefined,
593 : "Too large values in BLOCKSIZE");
594 1 : return false;
595 : }
596 108 : nBlockSize *= static_cast<size_t>(anBlockSize[i]);
597 : }
598 : }
599 523 : return true;
600 : }
601 :
602 : /************************************************************************/
603 : /* DeallocateDecodedBlockData() */
604 : /************************************************************************/
605 :
606 4554 : void ZarrArray::DeallocateDecodedBlockData()
607 : {
608 4554 : if (!m_abyDecodedBlockData.empty())
609 : {
610 424 : const size_t nDTSize = m_oType.GetSize();
611 424 : const size_t nValues = m_abyDecodedBlockData.size() / nDTSize;
612 860 : for (const auto &elt : m_aoDtypeElts)
613 : {
614 436 : if (elt.nativeType == DtypeElt::NativeType::STRING_ASCII ||
615 433 : elt.nativeType == DtypeElt::NativeType::STRING_UNICODE)
616 : {
617 3 : GByte *pDst = &m_abyDecodedBlockData[0];
618 8 : for (size_t i = 0; i < nValues; i++, pDst += nDTSize)
619 : {
620 : char *ptr;
621 5 : char **pptr =
622 5 : reinterpret_cast<char **>(pDst + elt.gdalOffset);
623 5 : memcpy(&ptr, pptr, sizeof(ptr));
624 5 : VSIFree(ptr);
625 : }
626 : }
627 : }
628 : }
629 4554 : }
630 :
631 : /************************************************************************/
632 : /* EncodeElt() */
633 : /************************************************************************/
634 :
635 : /* Encode from GDAL raw type to Zarr native type */
636 : /*static*/
637 241 : void ZarrArray::EncodeElt(const std::vector<DtypeElt> &elts, const GByte *pSrc,
638 : GByte *pDst)
639 : {
640 483 : for (const auto &elt : elts)
641 : {
642 242 : if (elt.nativeType == DtypeElt::NativeType::STRING_UNICODE)
643 : {
644 0 : const char *pStr =
645 0 : *reinterpret_cast<const char *const *>(pSrc + elt.gdalOffset);
646 0 : if (pStr)
647 : {
648 : try
649 : {
650 0 : const auto ucs4 = UTF8ToUCS4(pStr, elt.needByteSwapping);
651 0 : const auto ucs4Len = ucs4.size();
652 0 : memcpy(pDst + elt.nativeOffset, ucs4.data(),
653 0 : std::min(ucs4Len, elt.nativeSize));
654 0 : if (ucs4Len > elt.nativeSize)
655 : {
656 0 : CPLError(CE_Warning, CPLE_AppDefined,
657 : "Too long string truncated");
658 : }
659 0 : else if (ucs4Len < elt.nativeSize)
660 : {
661 0 : memset(pDst + elt.nativeOffset + ucs4Len, 0,
662 0 : elt.nativeSize - ucs4Len);
663 : }
664 : }
665 0 : catch (const std::exception &)
666 : {
667 0 : memset(pDst + elt.nativeOffset, 0, elt.nativeSize);
668 : }
669 : }
670 : else
671 : {
672 0 : memset(pDst + elt.nativeOffset, 0, elt.nativeSize);
673 : }
674 : }
675 242 : else if (elt.needByteSwapping)
676 : {
677 240 : if (elt.nativeSize == 2)
678 : {
679 48 : const uint16_t val = CPL_SWAP16(
680 : *reinterpret_cast<const uint16_t *>(pSrc + elt.gdalOffset));
681 48 : memcpy(pDst + elt.nativeOffset, &val, sizeof(val));
682 : }
683 192 : else if (elt.nativeSize == 4)
684 : {
685 72 : const uint32_t val = CPL_SWAP32(
686 : *reinterpret_cast<const uint32_t *>(pSrc + elt.gdalOffset));
687 72 : memcpy(pDst + elt.nativeOffset, &val, sizeof(val));
688 : }
689 120 : else if (elt.nativeSize == 8)
690 : {
691 96 : if (elt.nativeType == DtypeElt::NativeType::COMPLEX_IEEEFP)
692 : {
693 24 : uint32_t val =
694 24 : CPL_SWAP32(*reinterpret_cast<const uint32_t *>(
695 : pSrc + elt.gdalOffset));
696 24 : memcpy(pDst + elt.nativeOffset, &val, sizeof(val));
697 24 : val = CPL_SWAP32(*reinterpret_cast<const uint32_t *>(
698 : pSrc + elt.gdalOffset + 4));
699 24 : memcpy(pDst + elt.nativeOffset + 4, &val, sizeof(val));
700 : }
701 : else
702 : {
703 72 : const uint64_t val =
704 72 : CPL_SWAP64(*reinterpret_cast<const uint64_t *>(
705 : pSrc + elt.gdalOffset));
706 72 : memcpy(pDst + elt.nativeOffset, &val, sizeof(val));
707 : }
708 : }
709 24 : else if (elt.nativeSize == 16)
710 : {
711 24 : uint64_t val = CPL_SWAP64(
712 : *reinterpret_cast<const uint64_t *>(pSrc + elt.gdalOffset));
713 24 : memcpy(pDst + elt.nativeOffset, &val, sizeof(val));
714 24 : val = CPL_SWAP64(*reinterpret_cast<const uint64_t *>(
715 : pSrc + elt.gdalOffset + 8));
716 24 : memcpy(pDst + elt.nativeOffset + 8, &val, sizeof(val));
717 : }
718 : else
719 : {
720 0 : CPLAssert(false);
721 : }
722 : }
723 2 : else if (elt.gdalTypeIsApproxOfNative)
724 : {
725 0 : CPLAssert(false);
726 : }
727 2 : else if (elt.nativeType == DtypeElt::NativeType::STRING_ASCII)
728 : {
729 0 : const char *pStr =
730 0 : *reinterpret_cast<const char *const *>(pSrc + elt.gdalOffset);
731 0 : if (pStr)
732 : {
733 0 : const size_t nLen = strlen(pStr);
734 0 : memcpy(pDst + elt.nativeOffset, pStr,
735 0 : std::min(nLen, elt.nativeSize));
736 0 : if (nLen < elt.nativeSize)
737 0 : memset(pDst + elt.nativeOffset + nLen, 0,
738 0 : elt.nativeSize - nLen);
739 : }
740 : else
741 : {
742 0 : memset(pDst + elt.nativeOffset, 0, elt.nativeSize);
743 : }
744 : }
745 : else
746 : {
747 2 : CPLAssert(elt.nativeSize == elt.gdalSize);
748 2 : memcpy(pDst + elt.nativeOffset, pSrc + elt.gdalOffset,
749 2 : elt.nativeSize);
750 : }
751 : }
752 241 : }
753 :
754 : /************************************************************************/
755 : /* ZarrArray::SerializeNumericNoData() */
756 : /************************************************************************/
757 :
758 22 : void ZarrArray::SerializeNumericNoData(CPLJSONObject &oRoot) const
759 : {
760 22 : if (m_oType.GetNumericDataType() == GDT_Int64)
761 : {
762 0 : const auto nVal = GetNoDataValueAsInt64();
763 0 : oRoot.Add("fill_value", static_cast<GInt64>(nVal));
764 : }
765 22 : else if (m_oType.GetNumericDataType() == GDT_UInt64)
766 : {
767 0 : const auto nVal = GetNoDataValueAsUInt64();
768 0 : oRoot.Add("fill_value", static_cast<uint64_t>(nVal));
769 : }
770 : else
771 : {
772 22 : const double dfVal = GetNoDataValueAsDouble();
773 22 : if (std::isnan(dfVal))
774 3 : oRoot.Add("fill_value", "NaN");
775 19 : else if (dfVal == std::numeric_limits<double>::infinity())
776 2 : oRoot.Add("fill_value", "Infinity");
777 17 : else if (dfVal == -std::numeric_limits<double>::infinity())
778 2 : oRoot.Add("fill_value", "-Infinity");
779 15 : else if (GDALDataTypeIsInteger(m_oType.GetNumericDataType()))
780 9 : oRoot.Add("fill_value", static_cast<GInt64>(dfVal));
781 : else
782 6 : oRoot.Add("fill_value", dfVal);
783 : }
784 22 : }
785 :
786 : /************************************************************************/
787 : /* ZarrArray::GetSpatialRef() */
788 : /************************************************************************/
789 :
790 54 : std::shared_ptr<OGRSpatialReference> ZarrArray::GetSpatialRef() const
791 : {
792 54 : if (!CheckValidAndErrorOutIfNot())
793 0 : return nullptr;
794 :
795 54 : if (m_poSRS)
796 31 : return m_poSRS;
797 23 : return GDALPamMDArray::GetSpatialRef();
798 : }
799 :
800 : /************************************************************************/
801 : /* SetRawNoDataValue() */
802 : /************************************************************************/
803 :
804 25 : bool ZarrArray::SetRawNoDataValue(const void *pRawNoData)
805 : {
806 25 : if (!CheckValidAndErrorOutIfNot())
807 0 : return false;
808 :
809 25 : if (!m_bUpdatable)
810 : {
811 0 : CPLError(CE_Failure, CPLE_AppDefined, "Array opened in read-only mode");
812 0 : return false;
813 : }
814 25 : m_bDefinitionModified = true;
815 25 : RegisterNoDataValue(pRawNoData);
816 25 : return true;
817 : }
818 :
819 : /************************************************************************/
820 : /* RegisterNoDataValue() */
821 : /************************************************************************/
822 :
823 1182 : void ZarrArray::RegisterNoDataValue(const void *pNoData)
824 : {
825 1182 : if (m_pabyNoData)
826 : {
827 0 : m_oType.FreeDynamicMemory(&m_pabyNoData[0]);
828 : }
829 :
830 1182 : if (pNoData == nullptr)
831 : {
832 0 : CPLFree(m_pabyNoData);
833 0 : m_pabyNoData = nullptr;
834 : }
835 : else
836 : {
837 1182 : const auto nSize = m_oType.GetSize();
838 1182 : if (m_pabyNoData == nullptr)
839 : {
840 1182 : m_pabyNoData = static_cast<GByte *>(CPLMalloc(nSize));
841 : }
842 1182 : memset(m_pabyNoData, 0, nSize);
843 1182 : GDALExtendedDataType::CopyValue(pNoData, m_oType, m_pabyNoData,
844 1182 : m_oType);
845 : }
846 1182 : }
847 :
848 : /************************************************************************/
849 : /* DecodeSourceElt() */
850 : /************************************************************************/
851 :
852 : /* static */
853 2298 : void ZarrArray::DecodeSourceElt(const std::vector<DtypeElt> &elts,
854 : const GByte *pSrc, GByte *pDst)
855 : {
856 4636 : for (const auto &elt : elts)
857 : {
858 2338 : if (elt.nativeType == DtypeElt::NativeType::STRING_UNICODE)
859 : {
860 : char *ptr;
861 0 : char **pDstPtr = reinterpret_cast<char **>(pDst + elt.gdalOffset);
862 0 : memcpy(&ptr, pDstPtr, sizeof(ptr));
863 0 : VSIFree(ptr);
864 :
865 0 : char *pDstStr = UCS4ToUTF8(pSrc + elt.nativeOffset, elt.nativeSize,
866 0 : elt.needByteSwapping);
867 0 : memcpy(pDstPtr, &pDstStr, sizeof(pDstStr));
868 : }
869 2338 : else if (elt.needByteSwapping)
870 : {
871 2282 : if (elt.nativeSize == 2)
872 : {
873 : uint16_t val;
874 458 : memcpy(&val, pSrc + elt.nativeOffset, sizeof(val));
875 458 : *reinterpret_cast<uint16_t *>(pDst + elt.gdalOffset) =
876 458 : CPL_SWAP16(val);
877 : }
878 1824 : else if (elt.nativeSize == 4)
879 : {
880 : uint32_t val;
881 684 : memcpy(&val, pSrc + elt.nativeOffset, sizeof(val));
882 684 : *reinterpret_cast<uint32_t *>(pDst + elt.gdalOffset) =
883 684 : CPL_SWAP32(val);
884 : }
885 1140 : else if (elt.nativeSize == 8)
886 : {
887 912 : if (elt.nativeType == DtypeElt::NativeType::COMPLEX_IEEEFP)
888 : {
889 : uint32_t val;
890 228 : memcpy(&val, pSrc + elt.nativeOffset, sizeof(val));
891 228 : *reinterpret_cast<uint32_t *>(pDst + elt.gdalOffset) =
892 228 : CPL_SWAP32(val);
893 228 : memcpy(&val, pSrc + elt.nativeOffset + 4, sizeof(val));
894 228 : *reinterpret_cast<uint32_t *>(pDst + elt.gdalOffset + 4) =
895 228 : CPL_SWAP32(val);
896 : }
897 : else
898 : {
899 : uint64_t val;
900 684 : memcpy(&val, pSrc + elt.nativeOffset, sizeof(val));
901 684 : *reinterpret_cast<uint64_t *>(pDst + elt.gdalOffset) =
902 684 : CPL_SWAP64(val);
903 : }
904 : }
905 228 : else if (elt.nativeSize == 16)
906 : {
907 : uint64_t val;
908 228 : memcpy(&val, pSrc + elt.nativeOffset, sizeof(val));
909 228 : *reinterpret_cast<uint64_t *>(pDst + elt.gdalOffset) =
910 228 : CPL_SWAP64(val);
911 228 : memcpy(&val, pSrc + elt.nativeOffset + 8, sizeof(val));
912 228 : *reinterpret_cast<uint64_t *>(pDst + elt.gdalOffset + 8) =
913 228 : CPL_SWAP64(val);
914 : }
915 : else
916 : {
917 0 : CPLAssert(false);
918 : }
919 : }
920 56 : else if (elt.gdalTypeIsApproxOfNative)
921 : {
922 0 : CPLAssert(false);
923 : }
924 56 : else if (elt.nativeType == DtypeElt::NativeType::STRING_ASCII)
925 : {
926 : char *ptr;
927 7 : char **pDstPtr = reinterpret_cast<char **>(pDst + elt.gdalOffset);
928 7 : memcpy(&ptr, pDstPtr, sizeof(ptr));
929 7 : VSIFree(ptr);
930 :
931 7 : char *pDstStr = static_cast<char *>(CPLMalloc(elt.nativeSize + 1));
932 7 : memcpy(pDstStr, pSrc + elt.nativeOffset, elt.nativeSize);
933 7 : pDstStr[elt.nativeSize] = 0;
934 7 : memcpy(pDstPtr, &pDstStr, sizeof(pDstStr));
935 : }
936 : else
937 : {
938 49 : CPLAssert(elt.nativeSize == elt.gdalSize);
939 49 : memcpy(pDst + elt.gdalOffset, pSrc + elt.nativeOffset,
940 49 : elt.nativeSize);
941 : }
942 : }
943 2298 : }
944 :
945 : /************************************************************************/
946 : /* ZarrArray::IAdviseReadCommon() */
947 : /************************************************************************/
948 :
949 628 : bool ZarrArray::IAdviseReadCommon(const GUInt64 *arrayStartIdx,
950 : const size_t *count,
951 : CSLConstList papszOptions,
952 : std::vector<uint64_t> &anIndicesCur,
953 : int &nThreadsMax,
954 : std::vector<uint64_t> &anReqBlocksIndices,
955 : size_t &nReqBlocks) const
956 : {
957 628 : if (!CheckValidAndErrorOutIfNot())
958 0 : return false;
959 :
960 628 : if (!FlushDirtyBlock())
961 0 : return false;
962 :
963 628 : const size_t nDims = m_aoDims.size();
964 628 : anIndicesCur.resize(nDims);
965 1256 : std::vector<uint64_t> anIndicesMin(nDims);
966 1256 : std::vector<uint64_t> anIndicesMax(nDims);
967 :
968 : // Compute min and max tile indices in each dimension, and the total
969 : // number of tiles this represents.
970 628 : nReqBlocks = 1;
971 1890 : for (size_t i = 0; i < nDims; ++i)
972 : {
973 1262 : anIndicesMin[i] = arrayStartIdx[i] / m_anInnerBlockSize[i];
974 2524 : anIndicesMax[i] =
975 1262 : (arrayStartIdx[i] + count[i] - 1) / m_anInnerBlockSize[i];
976 : // Overflow on number of tiles already checked in Create()
977 1262 : nReqBlocks *=
978 1262 : static_cast<size_t>(anIndicesMax[i] - anIndicesMin[i] + 1);
979 : }
980 :
981 : // Find available cache size
982 628 : const size_t nCacheSize = [papszOptions]()
983 : {
984 : size_t nCacheSizeTmp;
985 : const char *pszCacheSize =
986 628 : CSLFetchNameValue(papszOptions, "CACHE_SIZE");
987 628 : if (pszCacheSize)
988 : {
989 4 : const auto nCacheSizeBig = CPLAtoGIntBig(pszCacheSize);
990 8 : if (nCacheSizeBig < 0 || static_cast<uint64_t>(nCacheSizeBig) >
991 4 : std::numeric_limits<size_t>::max() / 2)
992 : {
993 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "Too big CACHE_SIZE");
994 0 : return std::numeric_limits<size_t>::max();
995 : }
996 4 : nCacheSizeTmp = static_cast<size_t>(nCacheSizeBig);
997 : }
998 : else
999 : {
1000 : // Arbitrarily take half of remaining cache size
1001 624 : nCacheSizeTmp = static_cast<size_t>(std::min(
1002 1248 : static_cast<uint64_t>(
1003 624 : (GDALGetCacheMax64() - GDALGetCacheUsed64()) / 2),
1004 1248 : static_cast<uint64_t>(std::numeric_limits<size_t>::max() / 2)));
1005 624 : CPLDebug(ZARR_DEBUG_KEY, "Using implicit CACHE_SIZE=" CPL_FRMT_GUIB,
1006 : static_cast<GUIntBig>(nCacheSizeTmp));
1007 : }
1008 628 : return nCacheSizeTmp;
1009 628 : }();
1010 628 : if (nCacheSize == std::numeric_limits<size_t>::max())
1011 0 : return false;
1012 :
1013 : // Check that cache size is sufficient to hold all needed tiles.
1014 : // Also check that anReqBlocksIndices size computation won't overflow.
1015 628 : if (nReqBlocks > nCacheSize / std::max(m_nInnerBlockSizeBytes, nDims))
1016 : {
1017 4 : CPLError(CE_Failure, CPLE_OutOfMemory,
1018 : "CACHE_SIZE=" CPL_FRMT_GUIB " is not big enough to cache "
1019 : "all needed tiles. "
1020 : "At least " CPL_FRMT_GUIB " bytes would be needed",
1021 : static_cast<GUIntBig>(nCacheSize),
1022 : static_cast<GUIntBig>(
1023 4 : nReqBlocks * std::max(m_nInnerBlockSizeBytes, nDims)));
1024 4 : return false;
1025 : }
1026 :
1027 624 : nThreadsMax = GDALGetNumThreads(papszOptions, "NUM_THREADS",
1028 : GDAL_DEFAULT_MAX_THREAD_COUNT,
1029 : /* bDefaultAllCPUs=*/true);
1030 624 : if (nThreadsMax <= 1)
1031 336 : return true;
1032 :
1033 : // libhdf5 doesn't like concurrent access to the same file, even under
1034 : // a mutex...
1035 576 : auto poBlockPresenceArray = OpenBlockPresenceCache(false);
1036 288 : if (poBlockPresenceArray)
1037 : {
1038 6 : nThreadsMax = 1;
1039 6 : return true;
1040 : }
1041 :
1042 282 : CPLDebug(ZARR_DEBUG_KEY, "IAdviseRead(): Using up to %d threads",
1043 : nThreadsMax);
1044 :
1045 282 : m_oChunkCache.clear();
1046 282 : m_anCachedAdviseReadStart.assign(arrayStartIdx, arrayStartIdx + nDims);
1047 282 : m_anCachedAdviseReadCount.assign(count, count + nDims);
1048 :
1049 : // Overflow checked above
1050 : try
1051 : {
1052 282 : anReqBlocksIndices.resize(nDims * nReqBlocks);
1053 : }
1054 0 : catch (const std::bad_alloc &e)
1055 : {
1056 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
1057 0 : "Cannot allocate anReqBlocksIndices: %s", e.what());
1058 0 : return false;
1059 : }
1060 :
1061 282 : size_t dimIdx = 0;
1062 282 : size_t nBlockIter = 0;
1063 28274 : lbl_next_depth:
1064 28274 : if (dimIdx == nDims)
1065 : {
1066 26922 : if (nDims == 2)
1067 : {
1068 : // optimize in common case
1069 22748 : memcpy(&anReqBlocksIndices[nBlockIter * nDims], anIndicesCur.data(),
1070 : sizeof(uint64_t) * 2);
1071 : }
1072 4174 : else if (nDims == 3)
1073 : {
1074 : // optimize in common case
1075 4174 : memcpy(&anReqBlocksIndices[nBlockIter * nDims], anIndicesCur.data(),
1076 : sizeof(uint64_t) * 3);
1077 : }
1078 : else
1079 : {
1080 0 : memcpy(&anReqBlocksIndices[nBlockIter * nDims], anIndicesCur.data(),
1081 0 : sizeof(uint64_t) * nDims);
1082 : }
1083 26922 : nBlockIter++;
1084 : }
1085 : else
1086 : {
1087 : // This level of loop loops over blocks
1088 1352 : anIndicesCur[dimIdx] = anIndicesMin[dimIdx];
1089 : while (true)
1090 : {
1091 27992 : dimIdx++;
1092 27992 : goto lbl_next_depth;
1093 27992 : lbl_return_to_caller:
1094 27992 : dimIdx--;
1095 27992 : if (anIndicesCur[dimIdx] == anIndicesMax[dimIdx])
1096 1352 : break;
1097 26640 : ++anIndicesCur[dimIdx];
1098 : }
1099 : }
1100 28274 : if (dimIdx > 0)
1101 27992 : goto lbl_return_to_caller;
1102 282 : assert(nBlockIter == nReqBlocks);
1103 :
1104 282 : return true;
1105 : }
1106 :
1107 : /************************************************************************/
1108 : /* ZarrArray::IRead() */
1109 : /************************************************************************/
1110 :
1111 2748 : bool ZarrArray::IRead(const GUInt64 *arrayStartIdx, const size_t *count,
1112 : const GInt64 *arrayStep, const GPtrDiff_t *bufferStride,
1113 : const GDALExtendedDataType &bufferDataType,
1114 : void *pDstBuffer) const
1115 : {
1116 2748 : if (!CheckValidAndErrorOutIfNot())
1117 0 : return false;
1118 :
1119 2748 : if (!AllocateWorkingBuffers())
1120 3 : return false;
1121 :
1122 : // Need to be kept in top-level scope
1123 5490 : std::vector<GUInt64> arrayStartIdxMod;
1124 5490 : std::vector<GInt64> arrayStepMod;
1125 5490 : std::vector<GPtrDiff_t> bufferStrideMod;
1126 :
1127 2745 : const size_t nDims = m_aoDims.size();
1128 2745 : bool negativeStep = false;
1129 7561 : for (size_t i = 0; i < nDims; ++i)
1130 : {
1131 5150 : if (arrayStep[i] < 0)
1132 : {
1133 334 : negativeStep = true;
1134 334 : break;
1135 : }
1136 : }
1137 :
1138 : // const auto eBufferDT = bufferDataType.GetNumericDataType();
1139 2745 : const auto nBufferDTSize = static_cast<int>(bufferDataType.GetSize());
1140 :
1141 : // Make sure that arrayStep[i] are positive for sake of simplicity
1142 2745 : if (negativeStep)
1143 : {
1144 : #if defined(__GNUC__)
1145 : #pragma GCC diagnostic push
1146 : #pragma GCC diagnostic ignored "-Wnull-dereference"
1147 : #endif
1148 334 : arrayStartIdxMod.resize(nDims);
1149 334 : arrayStepMod.resize(nDims);
1150 334 : bufferStrideMod.resize(nDims);
1151 : #if defined(__GNUC__)
1152 : #pragma GCC diagnostic pop
1153 : #endif
1154 1002 : for (size_t i = 0; i < nDims; ++i)
1155 : {
1156 668 : if (arrayStep[i] < 0)
1157 : {
1158 1336 : arrayStartIdxMod[i] =
1159 668 : arrayStartIdx[i] - (count[i] - 1) * (-arrayStep[i]);
1160 668 : arrayStepMod[i] = -arrayStep[i];
1161 668 : bufferStrideMod[i] = -bufferStride[i];
1162 668 : pDstBuffer =
1163 : static_cast<GByte *>(pDstBuffer) +
1164 668 : bufferStride[i] *
1165 668 : static_cast<GPtrDiff_t>(nBufferDTSize * (count[i] - 1));
1166 : }
1167 : else
1168 : {
1169 0 : arrayStartIdxMod[i] = arrayStartIdx[i];
1170 0 : arrayStepMod[i] = arrayStep[i];
1171 0 : bufferStrideMod[i] = bufferStride[i];
1172 : }
1173 : }
1174 334 : arrayStartIdx = arrayStartIdxMod.data();
1175 334 : arrayStep = arrayStepMod.data();
1176 334 : bufferStride = bufferStrideMod.data();
1177 : }
1178 :
1179 : // Auto-parallel: prefetch multi-chunk reads via IAdviseRead().
1180 : // arrayStep[i] guaranteed positive after negative-step normalization above.
1181 : // Skip if the current read region is already covered by a previous
1182 : // IAdviseRead (explicit or auto), so we don't blow away a useful cache.
1183 2745 : if (nDims >= 2)
1184 : {
1185 2543 : bool bAlreadyCached = false;
1186 2543 : if (!m_oChunkCache.empty() && m_anCachedAdviseReadStart.size() == nDims)
1187 : {
1188 148 : bAlreadyCached = true;
1189 378 : for (size_t i = 0; i < nDims && bAlreadyCached; ++i)
1190 : {
1191 230 : const GUInt64 reqEnd =
1192 230 : arrayStartIdx[i] +
1193 230 : (count[i] - 1) * static_cast<uint64_t>(arrayStep[i]);
1194 230 : const GUInt64 cachedEnd = m_anCachedAdviseReadStart[i] +
1195 230 : m_anCachedAdviseReadCount[i] - 1;
1196 230 : if (arrayStartIdx[i] < m_anCachedAdviseReadStart[i] ||
1197 : reqEnd > cachedEnd)
1198 : {
1199 70 : bAlreadyCached = false;
1200 : }
1201 : }
1202 : }
1203 :
1204 2543 : if (!bAlreadyCached)
1205 : {
1206 : const char *pszNumThreads =
1207 2465 : CPLGetConfigOption("GDAL_NUM_THREADS", nullptr);
1208 2465 : if (pszNumThreads != nullptr)
1209 : {
1210 615 : size_t nReqChunks = 1;
1211 1851 : for (size_t i = 0; i < nDims; ++i)
1212 : {
1213 : const uint64_t startBlock =
1214 1236 : arrayStartIdx[i] / m_anInnerBlockSize[i];
1215 : const uint64_t endBlock =
1216 1236 : (arrayStartIdx[i] +
1217 2472 : (count[i] - 1) * static_cast<uint64_t>(arrayStep[i])) /
1218 1236 : m_anInnerBlockSize[i];
1219 1236 : nReqChunks *=
1220 1236 : static_cast<size_t>(endBlock - startBlock + 1);
1221 : }
1222 615 : if (nReqChunks > 1)
1223 : {
1224 : // IAdviseRead expects the contiguous element count per
1225 : // dimension, not the strided output count. When step > 1,
1226 : // expand count to cover the full element range.
1227 1230 : std::vector<size_t> anAdjustedCount(nDims);
1228 1851 : for (size_t i = 0; i < nDims; ++i)
1229 : {
1230 1236 : anAdjustedCount[i] =
1231 1236 : 1 +
1232 1236 : (count[i] - 1) * static_cast<size_t>(arrayStep[i]);
1233 : }
1234 615 : CPLStringList aosOptions;
1235 615 : aosOptions.SetNameValue("NUM_THREADS", pszNumThreads);
1236 615 : CPL_IGNORE_RET_VAL(IAdviseRead(arrayStartIdx,
1237 615 : anAdjustedCount.data(),
1238 615 : aosOptions.List()));
1239 : }
1240 : }
1241 : }
1242 : }
1243 :
1244 5490 : std::vector<uint64_t> indicesOuterLoop(nDims + 1);
1245 5490 : std::vector<GByte *> dstPtrStackOuterLoop(nDims + 1);
1246 :
1247 5490 : std::vector<uint64_t> indicesInnerLoop(nDims + 1);
1248 5490 : std::vector<GByte *> dstPtrStackInnerLoop(nDims + 1);
1249 :
1250 5490 : std::vector<GPtrDiff_t> dstBufferStrideBytes;
1251 8229 : for (size_t i = 0; i < nDims; ++i)
1252 : {
1253 5484 : dstBufferStrideBytes.push_back(bufferStride[i] *
1254 5484 : static_cast<GPtrDiff_t>(nBufferDTSize));
1255 : }
1256 2745 : dstBufferStrideBytes.push_back(0);
1257 :
1258 2745 : const auto nDTSize = m_oType.GetSize();
1259 :
1260 5490 : std::vector<uint64_t> blockIndices(nDims);
1261 : const size_t nSourceSize =
1262 2745 : m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
1263 :
1264 5490 : std::vector<size_t> countInnerLoopInit(nDims + 1, 1);
1265 5490 : std::vector<size_t> countInnerLoop(nDims);
1266 :
1267 5462 : const bool bBothAreNumericDT = m_oType.GetClass() == GEDTC_NUMERIC &&
1268 2717 : bufferDataType.GetClass() == GEDTC_NUMERIC;
1269 : const bool bSameNumericDT =
1270 5462 : bBothAreNumericDT &&
1271 2717 : m_oType.GetNumericDataType() == bufferDataType.GetNumericDataType();
1272 2745 : const auto nSameDTSize = bSameNumericDT ? m_oType.GetSize() : 0;
1273 : const bool bSameCompoundAndNoDynamicMem =
1274 2749 : m_oType.GetClass() == GEDTC_COMPOUND && m_oType == bufferDataType &&
1275 4 : !m_oType.NeedsFreeDynamicMemory();
1276 5490 : std::vector<GByte> abyTargetNoData;
1277 2745 : bool bNoDataIsZero = false;
1278 :
1279 2745 : size_t dimIdx = 0;
1280 2745 : dstPtrStackOuterLoop[0] = static_cast<GByte *>(pDstBuffer);
1281 45061 : lbl_next_depth:
1282 45061 : if (dimIdx == nDims)
1283 : {
1284 36777 : size_t dimIdxSubLoop = 0;
1285 36777 : dstPtrStackInnerLoop[0] = dstPtrStackOuterLoop[nDims];
1286 36777 : bool bEmptyChunk = false;
1287 :
1288 36777 : const GByte *pabySrcBlock = m_abyDecodedBlockData.empty()
1289 35345 : ? m_abyRawBlockData.data()
1290 36777 : : m_abyDecodedBlockData.data();
1291 36777 : bool bMatchFoundInMapChunkIndexToCachedBlock = false;
1292 :
1293 : // Use cache built by IAdviseRead() if possible
1294 36777 : if (!m_oChunkCache.empty())
1295 : {
1296 32320 : const auto oIter = m_oChunkCache.find(blockIndices);
1297 32320 : if (oIter != m_oChunkCache.end())
1298 : {
1299 31316 : bMatchFoundInMapChunkIndexToCachedBlock = true;
1300 31316 : if (oIter->second.abyDecoded.empty())
1301 : {
1302 944 : bEmptyChunk = true;
1303 : }
1304 : else
1305 : {
1306 30372 : pabySrcBlock = oIter->second.abyDecoded.data();
1307 : }
1308 : }
1309 : else
1310 : {
1311 : #ifdef DEBUG
1312 2008 : std::string key;
1313 3012 : for (size_t j = 0; j < nDims; ++j)
1314 : {
1315 2008 : if (j)
1316 1004 : key += ',';
1317 2008 : key += std::to_string(blockIndices[j]);
1318 : }
1319 1004 : CPLDebugOnly(ZARR_DEBUG_KEY, "Cache miss for tile %s",
1320 : key.c_str());
1321 : #endif
1322 : }
1323 : }
1324 :
1325 36777 : if (!bMatchFoundInMapChunkIndexToCachedBlock)
1326 : {
1327 5461 : if (!blockIndices.empty() && blockIndices == m_anCachedBlockIndices)
1328 : {
1329 243 : if (!m_bCachedBlockValid)
1330 496 : return false;
1331 243 : bEmptyChunk = m_bCachedBlockEmpty;
1332 : }
1333 : else
1334 : {
1335 5218 : if (!FlushDirtyBlock())
1336 0 : return false;
1337 :
1338 5218 : m_anCachedBlockIndices = blockIndices;
1339 5218 : m_bCachedBlockValid =
1340 5218 : LoadBlockData(blockIndices.data(), bEmptyChunk);
1341 5218 : if (!m_bCachedBlockValid)
1342 : {
1343 496 : return false;
1344 : }
1345 4722 : m_bCachedBlockEmpty = bEmptyChunk;
1346 : }
1347 :
1348 9930 : pabySrcBlock = m_abyDecodedBlockData.empty()
1349 3973 : ? m_abyRawBlockData.data()
1350 992 : : m_abyDecodedBlockData.data();
1351 : }
1352 : const size_t nSrcDTSize =
1353 36281 : m_abyDecodedBlockData.empty() ? nSourceSize : nDTSize;
1354 :
1355 113189 : for (size_t i = 0; i < nDims; ++i)
1356 : {
1357 76908 : countInnerLoopInit[i] = 1;
1358 76908 : if (arrayStep[i] != 0)
1359 : {
1360 : const auto nextBlockIdx =
1361 76206 : std::min((1 + indicesOuterLoop[i] / m_anInnerBlockSize[i]) *
1362 76206 : m_anInnerBlockSize[i],
1363 152412 : arrayStartIdx[i] + count[i] * arrayStep[i]);
1364 76206 : countInnerLoopInit[i] = static_cast<size_t>(cpl::div_round_up(
1365 76206 : nextBlockIdx - indicesOuterLoop[i], arrayStep[i]));
1366 : }
1367 : }
1368 :
1369 36281 : if (bEmptyChunk && bBothAreNumericDT && abyTargetNoData.empty())
1370 : {
1371 855 : abyTargetNoData.resize(nBufferDTSize);
1372 855 : if (m_pabyNoData)
1373 : {
1374 181 : GDALExtendedDataType::CopyValue(
1375 181 : m_pabyNoData, m_oType, &abyTargetNoData[0], bufferDataType);
1376 181 : bNoDataIsZero = true;
1377 1519 : for (size_t i = 0; i < abyTargetNoData.size(); ++i)
1378 : {
1379 1338 : if (abyTargetNoData[i] != 0)
1380 389 : bNoDataIsZero = false;
1381 : }
1382 : }
1383 : else
1384 : {
1385 674 : bNoDataIsZero = true;
1386 674 : GByte zero = 0;
1387 674 : GDALCopyWords(&zero, GDT_UInt8, 0, &abyTargetNoData[0],
1388 : bufferDataType.GetNumericDataType(), 0, 1);
1389 : }
1390 : }
1391 :
1392 35426 : lbl_next_depth_inner_loop:
1393 862906 : if (nDims == 0 || dimIdxSubLoop == nDims - 1)
1394 : {
1395 814553 : indicesInnerLoop[dimIdxSubLoop] = indicesOuterLoop[dimIdxSubLoop];
1396 814553 : void *dst_ptr = dstPtrStackInnerLoop[dimIdxSubLoop];
1397 :
1398 810944 : if (m_bUseOptimizedCodePaths && bEmptyChunk && bBothAreNumericDT &&
1399 1625500 : bNoDataIsZero &&
1400 1838 : nBufferDTSize == dstBufferStrideBytes[dimIdxSubLoop])
1401 : {
1402 1728 : memset(dst_ptr, 0,
1403 1728 : nBufferDTSize * countInnerLoopInit[dimIdxSubLoop]);
1404 1728 : goto end_inner_loop;
1405 : }
1406 809216 : else if (m_bUseOptimizedCodePaths && bEmptyChunk &&
1407 1622810 : !abyTargetNoData.empty() && bBothAreNumericDT &&
1408 769 : dstBufferStrideBytes[dimIdxSubLoop] <
1409 769 : std::numeric_limits<int>::max())
1410 : {
1411 1538 : GDALCopyWords64(
1412 769 : abyTargetNoData.data(), bufferDataType.GetNumericDataType(),
1413 : 0, dst_ptr, bufferDataType.GetNumericDataType(),
1414 769 : static_cast<int>(dstBufferStrideBytes[dimIdxSubLoop]),
1415 769 : static_cast<GPtrDiff_t>(countInnerLoopInit[dimIdxSubLoop]));
1416 769 : goto end_inner_loop;
1417 : }
1418 812056 : else if (bEmptyChunk)
1419 : {
1420 6085 : for (size_t i = 0; i < countInnerLoopInit[dimIdxSubLoop];
1421 3996 : ++i, dst_ptr = static_cast<uint8_t *>(dst_ptr) +
1422 3996 : dstBufferStrideBytes[dimIdxSubLoop])
1423 : {
1424 3996 : if (bNoDataIsZero)
1425 : {
1426 3200 : if (nBufferDTSize == 1)
1427 : {
1428 36 : *static_cast<uint8_t *>(dst_ptr) = 0;
1429 : }
1430 3164 : else if (nBufferDTSize == 2)
1431 : {
1432 48 : *static_cast<uint16_t *>(dst_ptr) = 0;
1433 : }
1434 3116 : else if (nBufferDTSize == 4)
1435 : {
1436 72 : *static_cast<uint32_t *>(dst_ptr) = 0;
1437 : }
1438 3044 : else if (nBufferDTSize == 8)
1439 : {
1440 2588 : *static_cast<uint64_t *>(dst_ptr) = 0;
1441 : }
1442 456 : else if (nBufferDTSize == 16)
1443 : {
1444 456 : static_cast<uint64_t *>(dst_ptr)[0] = 0;
1445 456 : static_cast<uint64_t *>(dst_ptr)[1] = 0;
1446 : }
1447 : else
1448 : {
1449 0 : CPLAssert(false);
1450 : }
1451 : }
1452 796 : else if (m_pabyNoData)
1453 : {
1454 795 : if (bBothAreNumericDT)
1455 : {
1456 786 : const void *src_ptr_v = abyTargetNoData.data();
1457 786 : if (nBufferDTSize == 1)
1458 24 : *static_cast<uint8_t *>(dst_ptr) =
1459 24 : *static_cast<const uint8_t *>(src_ptr_v);
1460 762 : else if (nBufferDTSize == 2)
1461 0 : *static_cast<uint16_t *>(dst_ptr) =
1462 0 : *static_cast<const uint16_t *>(src_ptr_v);
1463 762 : else if (nBufferDTSize == 4)
1464 60 : *static_cast<uint32_t *>(dst_ptr) =
1465 60 : *static_cast<const uint32_t *>(src_ptr_v);
1466 702 : else if (nBufferDTSize == 8)
1467 702 : *static_cast<uint64_t *>(dst_ptr) =
1468 702 : *static_cast<const uint64_t *>(src_ptr_v);
1469 0 : else if (nBufferDTSize == 16)
1470 : {
1471 0 : static_cast<uint64_t *>(dst_ptr)[0] =
1472 0 : static_cast<const uint64_t *>(src_ptr_v)[0];
1473 0 : static_cast<uint64_t *>(dst_ptr)[1] =
1474 0 : static_cast<const uint64_t *>(src_ptr_v)[1];
1475 : }
1476 : else
1477 : {
1478 0 : CPLAssert(false);
1479 : }
1480 : }
1481 : else
1482 : {
1483 9 : GDALExtendedDataType::CopyValue(
1484 9 : m_pabyNoData, m_oType, dst_ptr, bufferDataType);
1485 : }
1486 : }
1487 : else
1488 : {
1489 1 : memset(dst_ptr, 0, nBufferDTSize);
1490 : }
1491 : }
1492 :
1493 2089 : goto end_inner_loop;
1494 : }
1495 :
1496 809967 : size_t nOffset = 0;
1497 2781900 : for (size_t i = 0; i < nDims; i++)
1498 : {
1499 1971930 : nOffset = static_cast<size_t>(
1500 1971930 : nOffset * m_anInnerBlockSize[i] +
1501 1971930 : (indicesInnerLoop[i] -
1502 1971930 : blockIndices[i] * m_anInnerBlockSize[i]));
1503 : }
1504 809967 : const GByte *src_ptr = pabySrcBlock + nOffset * nSrcDTSize;
1505 809967 : const auto step = nDims == 0 ? 0 : arrayStep[dimIdxSubLoop];
1506 :
1507 808437 : if (m_bUseOptimizedCodePaths && bBothAreNumericDT &&
1508 808401 : step <= static_cast<GIntBig>(std::numeric_limits<int>::max() /
1509 1618400 : nDTSize) &&
1510 808401 : dstBufferStrideBytes[dimIdxSubLoop] <=
1511 808401 : std::numeric_limits<int>::max())
1512 : {
1513 808401 : GDALCopyWords64(
1514 : src_ptr, m_oType.GetNumericDataType(),
1515 : static_cast<int>(step * nDTSize), dst_ptr,
1516 : bufferDataType.GetNumericDataType(),
1517 808401 : static_cast<int>(dstBufferStrideBytes[dimIdxSubLoop]),
1518 808401 : static_cast<GPtrDiff_t>(countInnerLoopInit[dimIdxSubLoop]));
1519 :
1520 808401 : goto end_inner_loop;
1521 : }
1522 :
1523 4587 : for (size_t i = 0; i < countInnerLoopInit[dimIdxSubLoop];
1524 3021 : ++i, src_ptr += step * nSrcDTSize,
1525 3021 : dst_ptr = static_cast<uint8_t *>(dst_ptr) +
1526 3021 : dstBufferStrideBytes[dimIdxSubLoop])
1527 : {
1528 3021 : if (bSameNumericDT)
1529 : {
1530 996 : const void *src_ptr_v = src_ptr;
1531 996 : if (nSameDTSize == 1)
1532 70 : *static_cast<uint8_t *>(dst_ptr) =
1533 70 : *static_cast<const uint8_t *>(src_ptr_v);
1534 926 : else if (nSameDTSize == 2)
1535 : {
1536 56 : *static_cast<uint16_t *>(dst_ptr) =
1537 56 : *static_cast<const uint16_t *>(src_ptr_v);
1538 : }
1539 870 : else if (nSameDTSize == 4)
1540 : {
1541 154 : *static_cast<uint32_t *>(dst_ptr) =
1542 154 : *static_cast<const uint32_t *>(src_ptr_v);
1543 : }
1544 716 : else if (nSameDTSize == 8)
1545 : {
1546 652 : *static_cast<uint64_t *>(dst_ptr) =
1547 652 : *static_cast<const uint64_t *>(src_ptr_v);
1548 : }
1549 64 : else if (nSameDTSize == 16)
1550 : {
1551 64 : static_cast<uint64_t *>(dst_ptr)[0] =
1552 64 : static_cast<const uint64_t *>(src_ptr_v)[0];
1553 64 : static_cast<uint64_t *>(dst_ptr)[1] =
1554 64 : static_cast<const uint64_t *>(src_ptr_v)[1];
1555 : }
1556 : else
1557 : {
1558 0 : CPLAssert(false);
1559 : }
1560 : }
1561 2025 : else if (bSameCompoundAndNoDynamicMem)
1562 : {
1563 4 : memcpy(dst_ptr, src_ptr, nDTSize);
1564 : }
1565 2021 : else if (m_oType.GetClass() == GEDTC_STRING)
1566 : {
1567 52 : if (m_aoDtypeElts.back().nativeType ==
1568 : DtypeElt::NativeType::STRING_UNICODE)
1569 : {
1570 : char *pDstStr =
1571 18 : UCS4ToUTF8(src_ptr, nSourceSize,
1572 9 : m_aoDtypeElts.back().needByteSwapping);
1573 9 : char **pDstPtr = static_cast<char **>(dst_ptr);
1574 9 : memcpy(pDstPtr, &pDstStr, sizeof(pDstStr));
1575 : }
1576 : else
1577 : {
1578 : char *pDstStr =
1579 43 : static_cast<char *>(CPLMalloc(nSourceSize + 1));
1580 43 : memcpy(pDstStr, src_ptr, nSourceSize);
1581 43 : pDstStr[nSourceSize] = 0;
1582 43 : char **pDstPtr = static_cast<char **>(dst_ptr);
1583 43 : memcpy(pDstPtr, &pDstStr, sizeof(char *));
1584 : }
1585 : }
1586 : else
1587 : {
1588 1969 : GDALExtendedDataType::CopyValue(src_ptr, m_oType, dst_ptr,
1589 : bufferDataType);
1590 : }
1591 1566 : }
1592 : }
1593 : else
1594 : {
1595 : // This level of loop loops over individual samples, within a
1596 : // block
1597 48353 : indicesInnerLoop[dimIdxSubLoop] = indicesOuterLoop[dimIdxSubLoop];
1598 48353 : countInnerLoop[dimIdxSubLoop] = countInnerLoopInit[dimIdxSubLoop];
1599 : while (true)
1600 : {
1601 826625 : dimIdxSubLoop++;
1602 826625 : dstPtrStackInnerLoop[dimIdxSubLoop] =
1603 826625 : dstPtrStackInnerLoop[dimIdxSubLoop - 1];
1604 826625 : goto lbl_next_depth_inner_loop;
1605 826625 : lbl_return_to_caller_inner_loop:
1606 826625 : dimIdxSubLoop--;
1607 826625 : --countInnerLoop[dimIdxSubLoop];
1608 826625 : if (countInnerLoop[dimIdxSubLoop] == 0)
1609 : {
1610 48353 : break;
1611 : }
1612 778272 : indicesInnerLoop[dimIdxSubLoop] += arrayStep[dimIdxSubLoop];
1613 778272 : dstPtrStackInnerLoop[dimIdxSubLoop] +=
1614 778272 : dstBufferStrideBytes[dimIdxSubLoop];
1615 : }
1616 : }
1617 862906 : end_inner_loop:
1618 862906 : if (dimIdxSubLoop > 0)
1619 826625 : goto lbl_return_to_caller_inner_loop;
1620 : }
1621 : else
1622 : {
1623 : // This level of loop loops over blocks
1624 8284 : indicesOuterLoop[dimIdx] = arrayStartIdx[dimIdx];
1625 8284 : blockIndices[dimIdx] =
1626 8284 : indicesOuterLoop[dimIdx] / m_anInnerBlockSize[dimIdx];
1627 : while (true)
1628 : {
1629 42316 : dimIdx++;
1630 42316 : dstPtrStackOuterLoop[dimIdx] = dstPtrStackOuterLoop[dimIdx - 1];
1631 42316 : goto lbl_next_depth;
1632 41329 : lbl_return_to_caller:
1633 41329 : dimIdx--;
1634 41329 : if (count[dimIdx] == 1 || arrayStep[dimIdx] == 0)
1635 : break;
1636 :
1637 : size_t nIncr;
1638 40226 : if (static_cast<GUInt64>(arrayStep[dimIdx]) <
1639 40226 : m_anInnerBlockSize[dimIdx])
1640 : {
1641 : // Compute index at next block boundary
1642 : auto newIdx =
1643 36977 : indicesOuterLoop[dimIdx] +
1644 36977 : (m_anInnerBlockSize[dimIdx] -
1645 36977 : (indicesOuterLoop[dimIdx] % m_anInnerBlockSize[dimIdx]));
1646 : // And round up compared to arrayStartIdx, arrayStep
1647 36977 : nIncr = static_cast<size_t>(cpl::div_round_up(
1648 36977 : newIdx - indicesOuterLoop[dimIdx], arrayStep[dimIdx]));
1649 : }
1650 : else
1651 : {
1652 3249 : nIncr = 1;
1653 : }
1654 40226 : indicesOuterLoop[dimIdx] += nIncr * arrayStep[dimIdx];
1655 40226 : if (indicesOuterLoop[dimIdx] >
1656 40226 : arrayStartIdx[dimIdx] + (count[dimIdx] - 1) * arrayStep[dimIdx])
1657 6194 : break;
1658 34032 : dstPtrStackOuterLoop[dimIdx] +=
1659 34032 : bufferStride[dimIdx] *
1660 34032 : static_cast<GPtrDiff_t>(nIncr * nBufferDTSize);
1661 68064 : blockIndices[dimIdx] =
1662 34032 : indicesOuterLoop[dimIdx] / m_anInnerBlockSize[dimIdx];
1663 34032 : }
1664 : }
1665 43578 : if (dimIdx > 0)
1666 41329 : goto lbl_return_to_caller;
1667 :
1668 2249 : return true;
1669 : }
1670 :
1671 : /************************************************************************/
1672 : /* ZarrArray::IWrite() */
1673 : /************************************************************************/
1674 :
1675 763 : bool ZarrArray::IWrite(const GUInt64 *arrayStartIdx, const size_t *count,
1676 : const GInt64 *arrayStep, const GPtrDiff_t *bufferStride,
1677 : const GDALExtendedDataType &bufferDataType,
1678 : const void *pSrcBuffer)
1679 : {
1680 763 : if (!CheckValidAndErrorOutIfNot())
1681 0 : return false;
1682 :
1683 763 : if (!AllocateWorkingBuffers())
1684 0 : return false;
1685 :
1686 763 : m_oChunkCache.clear();
1687 :
1688 : // Need to be kept in top-level scope
1689 1526 : std::vector<GUInt64> arrayStartIdxMod;
1690 1526 : std::vector<GInt64> arrayStepMod;
1691 1526 : std::vector<GPtrDiff_t> bufferStrideMod;
1692 :
1693 763 : const size_t nDims = m_aoDims.size();
1694 763 : bool negativeStep = false;
1695 763 : bool bWriteWholeBlockInit = true;
1696 2163 : for (size_t i = 0; i < nDims; ++i)
1697 : {
1698 1400 : if (arrayStep[i] < 0)
1699 : {
1700 264 : negativeStep = true;
1701 264 : if (arrayStep[i] != -1 && count[i] > 1)
1702 0 : bWriteWholeBlockInit = false;
1703 : }
1704 1136 : else if (arrayStep[i] != 1 && count[i] > 1)
1705 0 : bWriteWholeBlockInit = false;
1706 : }
1707 :
1708 763 : const auto nBufferDTSize = static_cast<int>(bufferDataType.GetSize());
1709 :
1710 : // Make sure that arrayStep[i] are positive for sake of simplicity
1711 763 : if (negativeStep)
1712 : {
1713 : #if defined(__GNUC__)
1714 : #pragma GCC diagnostic push
1715 : #pragma GCC diagnostic ignored "-Wnull-dereference"
1716 : #endif
1717 132 : arrayStartIdxMod.resize(nDims);
1718 132 : arrayStepMod.resize(nDims);
1719 132 : bufferStrideMod.resize(nDims);
1720 : #if defined(__GNUC__)
1721 : #pragma GCC diagnostic pop
1722 : #endif
1723 396 : for (size_t i = 0; i < nDims; ++i)
1724 : {
1725 264 : if (arrayStep[i] < 0)
1726 : {
1727 528 : arrayStartIdxMod[i] =
1728 264 : arrayStartIdx[i] - (count[i] - 1) * (-arrayStep[i]);
1729 264 : arrayStepMod[i] = -arrayStep[i];
1730 264 : bufferStrideMod[i] = -bufferStride[i];
1731 264 : pSrcBuffer =
1732 : static_cast<const GByte *>(pSrcBuffer) +
1733 264 : bufferStride[i] *
1734 264 : static_cast<GPtrDiff_t>(nBufferDTSize * (count[i] - 1));
1735 : }
1736 : else
1737 : {
1738 0 : arrayStartIdxMod[i] = arrayStartIdx[i];
1739 0 : arrayStepMod[i] = arrayStep[i];
1740 0 : bufferStrideMod[i] = bufferStride[i];
1741 : }
1742 : }
1743 132 : arrayStartIdx = arrayStartIdxMod.data();
1744 132 : arrayStep = arrayStepMod.data();
1745 132 : bufferStride = bufferStrideMod.data();
1746 : }
1747 :
1748 1526 : std::vector<uint64_t> indicesOuterLoop(nDims + 1);
1749 1526 : std::vector<const GByte *> srcPtrStackOuterLoop(nDims + 1);
1750 :
1751 1526 : std::vector<size_t> offsetDstBuffer(nDims + 1);
1752 1526 : std::vector<const GByte *> srcPtrStackInnerLoop(nDims + 1);
1753 :
1754 1526 : std::vector<GPtrDiff_t> srcBufferStrideBytes;
1755 2163 : for (size_t i = 0; i < nDims; ++i)
1756 : {
1757 1400 : srcBufferStrideBytes.push_back(bufferStride[i] *
1758 1400 : static_cast<GPtrDiff_t>(nBufferDTSize));
1759 : }
1760 763 : srcBufferStrideBytes.push_back(0);
1761 :
1762 763 : const auto nDTSize = m_oType.GetSize();
1763 :
1764 1526 : std::vector<uint64_t> blockIndices(nDims);
1765 : const size_t nNativeSize =
1766 763 : m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
1767 :
1768 1526 : std::vector<size_t> countInnerLoopInit(nDims + 1, 1);
1769 1526 : std::vector<size_t> countInnerLoop(nDims);
1770 :
1771 1518 : const bool bBothAreNumericDT = m_oType.GetClass() == GEDTC_NUMERIC &&
1772 755 : bufferDataType.GetClass() == GEDTC_NUMERIC;
1773 : const bool bSameNumericDT =
1774 1518 : bBothAreNumericDT &&
1775 755 : m_oType.GetNumericDataType() == bufferDataType.GetNumericDataType();
1776 763 : const auto nSameDTSize = bSameNumericDT ? m_oType.GetSize() : 0;
1777 : const bool bSameCompoundAndNoDynamicMem =
1778 763 : m_oType.GetClass() == GEDTC_COMPOUND && m_oType == bufferDataType &&
1779 0 : !m_oType.NeedsFreeDynamicMemory();
1780 :
1781 763 : size_t dimIdx = 0;
1782 763 : size_t dimIdxForCopy = nDims == 0 ? 0 : nDims - 1;
1783 763 : if (nDims)
1784 : {
1785 831 : while (dimIdxForCopy > 0 && count[dimIdxForCopy] == 1)
1786 68 : --dimIdxForCopy;
1787 : }
1788 :
1789 763 : srcPtrStackOuterLoop[0] = static_cast<const GByte *>(pSrcBuffer);
1790 26423 : lbl_next_depth:
1791 26423 : if (dimIdx == nDims)
1792 : {
1793 23905 : bool bWriteWholeBlock = bWriteWholeBlockInit;
1794 23905 : bool bPartialBlock = false;
1795 71785 : for (size_t i = 0; i < nDims; ++i)
1796 : {
1797 47880 : countInnerLoopInit[i] = 1;
1798 47880 : if (arrayStep[i] != 0)
1799 : {
1800 : const auto nextBlockIdx =
1801 47843 : std::min((1 + indicesOuterLoop[i] / m_anInnerBlockSize[i]) *
1802 47843 : m_anInnerBlockSize[i],
1803 95686 : arrayStartIdx[i] + count[i] * arrayStep[i]);
1804 47843 : countInnerLoopInit[i] = static_cast<size_t>(cpl::div_round_up(
1805 47843 : nextBlockIdx - indicesOuterLoop[i], arrayStep[i]));
1806 : }
1807 47880 : if (bWriteWholeBlock)
1808 : {
1809 : const bool bWholePartialBlockThisDim =
1810 49243 : indicesOuterLoop[i] == 0 &&
1811 2529 : countInnerLoopInit[i] == m_aoDims[i]->GetSize();
1812 46714 : bWriteWholeBlock =
1813 46714 : (countInnerLoopInit[i] == m_anInnerBlockSize[i] ||
1814 : bWholePartialBlockThisDim);
1815 46714 : if (bWholePartialBlockThisDim)
1816 : {
1817 656 : bPartialBlock = true;
1818 : }
1819 : }
1820 : }
1821 :
1822 23905 : size_t dimIdxSubLoop = 0;
1823 23905 : srcPtrStackInnerLoop[0] = srcPtrStackOuterLoop[nDims];
1824 : const size_t nCacheDTSize =
1825 23905 : m_abyDecodedBlockData.empty() ? nNativeSize : nDTSize;
1826 23905 : auto &abyBlock = m_abyDecodedBlockData.empty() ? m_abyRawBlockData
1827 23905 : : m_abyDecodedBlockData;
1828 :
1829 23905 : if (!blockIndices.empty() && blockIndices == m_anCachedBlockIndices)
1830 : {
1831 5 : if (!m_bCachedBlockValid)
1832 0 : return false;
1833 : }
1834 : else
1835 : {
1836 23900 : if (!FlushDirtyBlock())
1837 0 : return false;
1838 :
1839 23900 : m_anCachedBlockIndices = blockIndices;
1840 23900 : m_bCachedBlockValid = true;
1841 :
1842 23900 : if (bWriteWholeBlock)
1843 : {
1844 21926 : if (bPartialBlock)
1845 : {
1846 428 : DeallocateDecodedBlockData();
1847 428 : memset(&abyBlock[0], 0, abyBlock.size());
1848 : }
1849 : }
1850 : else
1851 : {
1852 : // If we don't write the whole tile, we need to fetch a
1853 : // potentially existing one.
1854 1974 : bool bEmptyBlock = false;
1855 1974 : m_bCachedBlockValid =
1856 1974 : LoadBlockData(blockIndices.data(), bEmptyBlock);
1857 1974 : if (!m_bCachedBlockValid)
1858 : {
1859 0 : return false;
1860 : }
1861 :
1862 1974 : if (bEmptyBlock)
1863 : {
1864 1696 : DeallocateDecodedBlockData();
1865 :
1866 1696 : if (m_pabyNoData == nullptr)
1867 : {
1868 844 : memset(&abyBlock[0], 0, abyBlock.size());
1869 : }
1870 : else
1871 : {
1872 852 : const size_t nElts = abyBlock.size() / nCacheDTSize;
1873 852 : GByte *dstPtr = &abyBlock[0];
1874 852 : if (m_oType.GetClass() == GEDTC_NUMERIC)
1875 : {
1876 852 : GDALCopyWords64(
1877 852 : m_pabyNoData, m_oType.GetNumericDataType(), 0,
1878 : dstPtr, m_oType.GetNumericDataType(),
1879 852 : static_cast<int>(m_oType.GetSize()),
1880 : static_cast<GPtrDiff_t>(nElts));
1881 : }
1882 : else
1883 : {
1884 0 : for (size_t i = 0; i < nElts; ++i)
1885 : {
1886 0 : GDALExtendedDataType::CopyValue(
1887 0 : m_pabyNoData, m_oType, dstPtr, m_oType);
1888 0 : dstPtr += nCacheDTSize;
1889 : }
1890 : }
1891 : }
1892 : }
1893 : }
1894 : }
1895 23905 : m_bDirtyBlock = true;
1896 23905 : m_bCachedBlockEmpty = false;
1897 23905 : if (nDims)
1898 23905 : offsetDstBuffer[0] = static_cast<size_t>(
1899 23905 : indicesOuterLoop[0] - blockIndices[0] * m_anInnerBlockSize[0]);
1900 :
1901 23905 : GByte *pabyBlock = &abyBlock[0];
1902 :
1903 463537 : lbl_next_depth_inner_loop:
1904 463537 : if (dimIdxSubLoop == dimIdxForCopy)
1905 : {
1906 439576 : size_t nOffset = offsetDstBuffer[dimIdxSubLoop];
1907 439576 : GInt64 step = nDims == 0 ? 0 : arrayStep[dimIdxSubLoop];
1908 439654 : for (size_t i = dimIdxSubLoop + 1; i < nDims; ++i)
1909 : {
1910 78 : nOffset = static_cast<size_t>(
1911 78 : nOffset * m_anInnerBlockSize[i] +
1912 78 : (indicesOuterLoop[i] -
1913 78 : blockIndices[i] * m_anInnerBlockSize[i]));
1914 78 : step *= m_anInnerBlockSize[i];
1915 : }
1916 439576 : const void *src_ptr = srcPtrStackInnerLoop[dimIdxSubLoop];
1917 439576 : GByte *dst_ptr = pabyBlock + nOffset * nCacheDTSize;
1918 :
1919 439576 : if (m_bUseOptimizedCodePaths && bBothAreNumericDT)
1920 : {
1921 438116 : if (countInnerLoopInit[dimIdxSubLoop] == 1 && bSameNumericDT)
1922 : {
1923 162 : void *dst_ptr_v = dst_ptr;
1924 162 : if (nSameDTSize == 1)
1925 14 : *static_cast<uint8_t *>(dst_ptr_v) =
1926 14 : *static_cast<const uint8_t *>(src_ptr);
1927 148 : else if (nSameDTSize == 2)
1928 : {
1929 2 : *static_cast<uint16_t *>(dst_ptr_v) =
1930 2 : *static_cast<const uint16_t *>(src_ptr);
1931 : }
1932 146 : else if (nSameDTSize == 4)
1933 : {
1934 2 : *static_cast<uint32_t *>(dst_ptr_v) =
1935 2 : *static_cast<const uint32_t *>(src_ptr);
1936 : }
1937 144 : else if (nSameDTSize == 8)
1938 : {
1939 102 : *static_cast<uint64_t *>(dst_ptr_v) =
1940 102 : *static_cast<const uint64_t *>(src_ptr);
1941 : }
1942 42 : else if (nSameDTSize == 16)
1943 : {
1944 42 : static_cast<uint64_t *>(dst_ptr_v)[0] =
1945 42 : static_cast<const uint64_t *>(src_ptr)[0];
1946 42 : static_cast<uint64_t *>(dst_ptr_v)[1] =
1947 42 : static_cast<const uint64_t *>(src_ptr)[1];
1948 : }
1949 : else
1950 : {
1951 0 : CPLAssert(false);
1952 : }
1953 : }
1954 437954 : else if (step <=
1955 437954 : static_cast<GIntBig>(
1956 875908 : std::numeric_limits<int>::max() / nDTSize) &&
1957 437954 : srcBufferStrideBytes[dimIdxSubLoop] <=
1958 437954 : std::numeric_limits<int>::max())
1959 : {
1960 875908 : GDALCopyWords64(
1961 : src_ptr, bufferDataType.GetNumericDataType(),
1962 437954 : static_cast<int>(srcBufferStrideBytes[dimIdxSubLoop]),
1963 : dst_ptr, m_oType.GetNumericDataType(),
1964 : static_cast<int>(step * nDTSize),
1965 : static_cast<GPtrDiff_t>(
1966 437954 : countInnerLoopInit[dimIdxSubLoop]));
1967 : }
1968 438116 : goto end_inner_loop;
1969 : }
1970 :
1971 4381 : for (size_t i = 0; i < countInnerLoopInit[dimIdxSubLoop];
1972 2921 : ++i, dst_ptr += step * nCacheDTSize,
1973 2921 : src_ptr = static_cast<const uint8_t *>(src_ptr) +
1974 2921 : srcBufferStrideBytes[dimIdxSubLoop])
1975 : {
1976 2921 : if (bSameNumericDT)
1977 : {
1978 600 : void *dst_ptr_v = dst_ptr;
1979 600 : if (nSameDTSize == 1)
1980 0 : *static_cast<uint8_t *>(dst_ptr_v) =
1981 0 : *static_cast<const uint8_t *>(src_ptr);
1982 600 : else if (nSameDTSize == 2)
1983 : {
1984 0 : *static_cast<uint16_t *>(dst_ptr_v) =
1985 0 : *static_cast<const uint16_t *>(src_ptr);
1986 : }
1987 600 : else if (nSameDTSize == 4)
1988 : {
1989 0 : *static_cast<uint32_t *>(dst_ptr_v) =
1990 0 : *static_cast<const uint32_t *>(src_ptr);
1991 : }
1992 600 : else if (nSameDTSize == 8)
1993 : {
1994 440 : *static_cast<uint64_t *>(dst_ptr_v) =
1995 440 : *static_cast<const uint64_t *>(src_ptr);
1996 : }
1997 160 : else if (nSameDTSize == 16)
1998 : {
1999 160 : static_cast<uint64_t *>(dst_ptr_v)[0] =
2000 160 : static_cast<const uint64_t *>(src_ptr)[0];
2001 160 : static_cast<uint64_t *>(dst_ptr_v)[1] =
2002 160 : static_cast<const uint64_t *>(src_ptr)[1];
2003 : }
2004 : else
2005 : {
2006 0 : CPLAssert(false);
2007 : }
2008 : }
2009 2321 : else if (bSameCompoundAndNoDynamicMem)
2010 : {
2011 0 : memcpy(dst_ptr, src_ptr, nDTSize);
2012 : }
2013 2321 : else if (m_oType.GetClass() == GEDTC_STRING)
2014 : {
2015 17 : const char *pSrcStr =
2016 : *static_cast<const char *const *>(src_ptr);
2017 17 : if (pSrcStr)
2018 : {
2019 17 : const size_t nLen = strlen(pSrcStr);
2020 17 : if (m_aoDtypeElts.back().nativeType ==
2021 : DtypeElt::NativeType::STRING_UNICODE)
2022 : {
2023 : try
2024 : {
2025 : const auto ucs4 = UTF8ToUCS4(
2026 : pSrcStr,
2027 8 : m_aoDtypeElts.back().needByteSwapping);
2028 4 : const auto ucs4Len = ucs4.size();
2029 4 : memcpy(dst_ptr, ucs4.data(),
2030 4 : std::min(ucs4Len, nNativeSize));
2031 4 : if (ucs4Len > nNativeSize)
2032 : {
2033 1 : CPLError(CE_Warning, CPLE_AppDefined,
2034 : "Too long string truncated");
2035 : }
2036 3 : else if (ucs4Len < nNativeSize)
2037 : {
2038 1 : memset(dst_ptr + ucs4Len, 0,
2039 1 : nNativeSize - ucs4Len);
2040 : }
2041 : }
2042 0 : catch (const std::exception &)
2043 : {
2044 0 : memset(dst_ptr, 0, nNativeSize);
2045 : }
2046 : }
2047 : else
2048 : {
2049 13 : memcpy(dst_ptr, pSrcStr,
2050 13 : std::min(nLen, nNativeSize));
2051 13 : if (nLen > nNativeSize)
2052 : {
2053 2 : CPLError(CE_Warning, CPLE_AppDefined,
2054 : "Too long string truncated");
2055 : }
2056 11 : else if (nLen < nNativeSize)
2057 11 : memset(dst_ptr + nLen, 0, nNativeSize - nLen);
2058 : }
2059 : }
2060 : else
2061 : {
2062 0 : memset(dst_ptr, 0, nNativeSize);
2063 : }
2064 : }
2065 : else
2066 : {
2067 2304 : if (m_oType.NeedsFreeDynamicMemory())
2068 0 : m_oType.FreeDynamicMemory(dst_ptr);
2069 2304 : GDALExtendedDataType::CopyValue(src_ptr, bufferDataType,
2070 2304 : dst_ptr, m_oType);
2071 : }
2072 : }
2073 : }
2074 : else
2075 : {
2076 : // This level of loop loops over individual samples, within a
2077 : // block
2078 23961 : countInnerLoop[dimIdxSubLoop] = countInnerLoopInit[dimIdxSubLoop];
2079 : while (true)
2080 : {
2081 439632 : dimIdxSubLoop++;
2082 439632 : srcPtrStackInnerLoop[dimIdxSubLoop] =
2083 439632 : srcPtrStackInnerLoop[dimIdxSubLoop - 1];
2084 879264 : offsetDstBuffer[dimIdxSubLoop] = static_cast<size_t>(
2085 1318900 : offsetDstBuffer[dimIdxSubLoop - 1] *
2086 439632 : m_anInnerBlockSize[dimIdxSubLoop] +
2087 439632 : (indicesOuterLoop[dimIdxSubLoop] -
2088 879264 : blockIndices[dimIdxSubLoop] *
2089 439632 : m_anInnerBlockSize[dimIdxSubLoop]));
2090 439632 : goto lbl_next_depth_inner_loop;
2091 439632 : lbl_return_to_caller_inner_loop:
2092 439632 : dimIdxSubLoop--;
2093 439632 : --countInnerLoop[dimIdxSubLoop];
2094 439632 : if (countInnerLoop[dimIdxSubLoop] == 0)
2095 : {
2096 23961 : break;
2097 : }
2098 415671 : srcPtrStackInnerLoop[dimIdxSubLoop] +=
2099 415671 : srcBufferStrideBytes[dimIdxSubLoop];
2100 415671 : offsetDstBuffer[dimIdxSubLoop] +=
2101 415671 : static_cast<size_t>(arrayStep[dimIdxSubLoop]);
2102 : }
2103 : }
2104 439576 : end_inner_loop:
2105 463537 : if (dimIdxSubLoop > 0)
2106 439632 : goto lbl_return_to_caller_inner_loop;
2107 : }
2108 : else
2109 : {
2110 : // This level of loop loops over blocks
2111 2518 : indicesOuterLoop[dimIdx] = arrayStartIdx[dimIdx];
2112 2518 : blockIndices[dimIdx] =
2113 2518 : indicesOuterLoop[dimIdx] / m_anInnerBlockSize[dimIdx];
2114 : while (true)
2115 : {
2116 25660 : dimIdx++;
2117 25660 : srcPtrStackOuterLoop[dimIdx] = srcPtrStackOuterLoop[dimIdx - 1];
2118 25660 : goto lbl_next_depth;
2119 25660 : lbl_return_to_caller:
2120 25660 : dimIdx--;
2121 25660 : if (count[dimIdx] == 1 || arrayStep[dimIdx] == 0)
2122 : break;
2123 :
2124 : size_t nIncr;
2125 25547 : if (static_cast<GUInt64>(arrayStep[dimIdx]) <
2126 25547 : m_anInnerBlockSize[dimIdx])
2127 : {
2128 : // Compute index at next block boundary
2129 : auto newIdx =
2130 25443 : indicesOuterLoop[dimIdx] +
2131 25443 : (m_anInnerBlockSize[dimIdx] -
2132 25443 : (indicesOuterLoop[dimIdx] % m_anInnerBlockSize[dimIdx]));
2133 : // And round up compared to arrayStartIdx, arrayStep
2134 25443 : nIncr = static_cast<size_t>(cpl::div_round_up(
2135 25443 : newIdx - indicesOuterLoop[dimIdx], arrayStep[dimIdx]));
2136 : }
2137 : else
2138 : {
2139 104 : nIncr = 1;
2140 : }
2141 25547 : indicesOuterLoop[dimIdx] += nIncr * arrayStep[dimIdx];
2142 25547 : if (indicesOuterLoop[dimIdx] >
2143 25547 : arrayStartIdx[dimIdx] + (count[dimIdx] - 1) * arrayStep[dimIdx])
2144 2405 : break;
2145 23142 : srcPtrStackOuterLoop[dimIdx] +=
2146 23142 : bufferStride[dimIdx] *
2147 23142 : static_cast<GPtrDiff_t>(nIncr * nBufferDTSize);
2148 46284 : blockIndices[dimIdx] =
2149 23142 : indicesOuterLoop[dimIdx] / m_anInnerBlockSize[dimIdx];
2150 23142 : }
2151 : }
2152 26423 : if (dimIdx > 0)
2153 25660 : goto lbl_return_to_caller;
2154 :
2155 763 : return true;
2156 : }
2157 :
2158 : /************************************************************************/
2159 : /* ZarrArray::IsEmptyBlock() */
2160 : /************************************************************************/
2161 :
2162 27968 : bool ZarrArray::IsEmptyBlock(const ZarrByteVectorQuickResize &abyBlock) const
2163 : {
2164 49918 : if (m_pabyNoData == nullptr || (m_oType.GetClass() == GEDTC_NUMERIC &&
2165 21950 : GetNoDataValueAsDouble() == 0.0))
2166 : {
2167 27349 : const size_t nBytes = abyBlock.size();
2168 27349 : size_t i = 0;
2169 32664 : for (; i + (sizeof(size_t) - 1) < nBytes; i += sizeof(size_t))
2170 : {
2171 31429 : if (*reinterpret_cast<const size_t *>(abyBlock.data() + i) != 0)
2172 : {
2173 26114 : return false;
2174 : }
2175 : }
2176 3054 : for (; i < nBytes; ++i)
2177 : {
2178 1903 : if (abyBlock[i] != 0)
2179 : {
2180 84 : return false;
2181 : }
2182 : }
2183 1151 : return true;
2184 : }
2185 1238 : else if (m_oType.GetClass() == GEDTC_NUMERIC &&
2186 619 : !GDALDataTypeIsComplex(m_oType.GetNumericDataType()))
2187 : {
2188 619 : const int nDTSize = static_cast<int>(m_oType.GetSize());
2189 619 : const size_t nElts = abyBlock.size() / nDTSize;
2190 619 : const auto eDT = m_oType.GetNumericDataType();
2191 1238 : return GDALBufferHasOnlyNoData(
2192 619 : abyBlock.data(), GetNoDataValueAsDouble(),
2193 : nElts, // nWidth
2194 : 1, // nHeight
2195 : nElts, // nLineStride
2196 : 1, // nComponents
2197 : nDTSize * 8, // nBitsPerSample
2198 619 : GDALDataTypeIsInteger(eDT)
2199 224 : ? (GDALDataTypeIsSigned(eDT) ? GSF_SIGNED_INT
2200 : : GSF_UNSIGNED_INT)
2201 619 : : GSF_FLOATING_POINT);
2202 : }
2203 0 : return false;
2204 : }
2205 :
2206 : /************************************************************************/
2207 : /* ZarrArray::OpenBlockPresenceCache() */
2208 : /************************************************************************/
2209 :
2210 : std::shared_ptr<GDALMDArray>
2211 34424 : ZarrArray::OpenBlockPresenceCache(bool bCanCreate) const
2212 : {
2213 34424 : if (m_bHasTriedBlockCachePresenceArray)
2214 33188 : return m_poBlockCachePresenceArray;
2215 1236 : m_bHasTriedBlockCachePresenceArray = true;
2216 :
2217 1236 : if (m_nTotalInnerChunkCount == 1)
2218 418 : return nullptr;
2219 :
2220 1636 : std::string osCacheFilename;
2221 1636 : auto poRGCache = GetCacheRootGroup(bCanCreate, osCacheFilename);
2222 818 : if (!poRGCache)
2223 797 : return nullptr;
2224 :
2225 21 : const std::string osBlockPresenceArrayName(MassageName(GetFullName()) +
2226 42 : "_tile_presence");
2227 : auto poBlockPresenceArray =
2228 42 : poRGCache->OpenMDArray(osBlockPresenceArrayName);
2229 42 : const auto eByteDT = GDALExtendedDataType::Create(GDT_UInt8);
2230 21 : if (poBlockPresenceArray)
2231 : {
2232 16 : bool ok = true;
2233 16 : const auto &apoDimsCache = poBlockPresenceArray->GetDimensions();
2234 32 : if (poBlockPresenceArray->GetDataType() != eByteDT ||
2235 16 : apoDimsCache.size() != m_aoDims.size())
2236 : {
2237 0 : ok = false;
2238 : }
2239 : else
2240 : {
2241 48 : for (size_t i = 0; i < m_aoDims.size(); i++)
2242 : {
2243 32 : const auto nExpectedDimSize = cpl::div_round_up(
2244 32 : m_aoDims[i]->GetSize(), m_anInnerBlockSize[i]);
2245 32 : if (apoDimsCache[i]->GetSize() != nExpectedDimSize)
2246 : {
2247 0 : ok = false;
2248 0 : break;
2249 : }
2250 : }
2251 : }
2252 16 : if (!ok)
2253 : {
2254 0 : CPLError(CE_Failure, CPLE_NotSupported,
2255 : "Array %s in %s has not expected characteristics",
2256 : osBlockPresenceArrayName.c_str(), osCacheFilename.c_str());
2257 0 : return nullptr;
2258 : }
2259 :
2260 16 : if (!poBlockPresenceArray->GetAttribute("filling_status") &&
2261 0 : !bCanCreate)
2262 : {
2263 0 : CPLDebug(ZARR_DEBUG_KEY,
2264 : "Cache tile presence array for %s found, but filling not "
2265 : "finished",
2266 0 : GetFullName().c_str());
2267 0 : return nullptr;
2268 : }
2269 :
2270 16 : CPLDebug(ZARR_DEBUG_KEY, "Using cache tile presence for %s",
2271 16 : GetFullName().c_str());
2272 : }
2273 5 : else if (bCanCreate)
2274 : {
2275 5 : int idxDim = 0;
2276 5 : std::string osBlockSize;
2277 5 : std::vector<std::shared_ptr<GDALDimension>> apoNewDims;
2278 15 : for (const auto &poDim : m_aoDims)
2279 : {
2280 10 : auto poNewDim = poRGCache->CreateDimension(
2281 20 : osBlockPresenceArrayName + '_' + std::to_string(idxDim),
2282 20 : std::string(), std::string(),
2283 : cpl::div_round_up(poDim->GetSize(),
2284 20 : m_anInnerBlockSize[idxDim]));
2285 10 : if (!poNewDim)
2286 0 : return nullptr;
2287 10 : apoNewDims.emplace_back(poNewDim);
2288 :
2289 10 : if (!osBlockSize.empty())
2290 5 : osBlockSize += ',';
2291 10 : constexpr GUInt64 BLOCKSIZE = 256;
2292 : osBlockSize +=
2293 10 : std::to_string(std::min(poNewDim->GetSize(), BLOCKSIZE));
2294 :
2295 10 : idxDim++;
2296 : }
2297 :
2298 5 : CPLStringList aosOptionsBlockPresence;
2299 5 : aosOptionsBlockPresence.SetNameValue("BLOCKSIZE", osBlockSize.c_str());
2300 : poBlockPresenceArray =
2301 15 : poRGCache->CreateMDArray(osBlockPresenceArrayName, apoNewDims,
2302 10 : eByteDT, aosOptionsBlockPresence.List());
2303 5 : if (!poBlockPresenceArray)
2304 : {
2305 0 : CPLError(CE_Failure, CPLE_NotSupported, "Cannot create %s in %s",
2306 : osBlockPresenceArrayName.c_str(), osCacheFilename.c_str());
2307 0 : return nullptr;
2308 : }
2309 5 : poBlockPresenceArray->SetNoDataValue(0);
2310 : }
2311 : else
2312 : {
2313 0 : return nullptr;
2314 : }
2315 :
2316 21 : m_poBlockCachePresenceArray = poBlockPresenceArray;
2317 :
2318 21 : return poBlockPresenceArray;
2319 : }
2320 :
2321 : /************************************************************************/
2322 : /* ZarrArray::BlockCachePresence() */
2323 : /************************************************************************/
2324 :
2325 9 : bool ZarrArray::BlockCachePresence()
2326 : {
2327 9 : if (m_nTotalInnerChunkCount == 1)
2328 0 : return true;
2329 :
2330 18 : const std::string osDirectoryName = GetDataDirectory();
2331 :
2332 : auto psDir = std::unique_ptr<VSIDIR, decltype(&VSICloseDir)>(
2333 18 : VSIOpenDir(osDirectoryName.c_str(), -1, nullptr), VSICloseDir);
2334 9 : if (!psDir)
2335 0 : return false;
2336 :
2337 18 : auto poBlockPresenceArray = OpenBlockPresenceCache(true);
2338 9 : if (!poBlockPresenceArray)
2339 : {
2340 0 : return false;
2341 : }
2342 :
2343 9 : if (poBlockPresenceArray->GetAttribute("filling_status"))
2344 : {
2345 4 : CPLDebug(ZARR_DEBUG_KEY,
2346 : "BlockCachePresence(): %s already filled. Nothing to do",
2347 4 : poBlockPresenceArray->GetName().c_str());
2348 4 : return true;
2349 : }
2350 :
2351 5 : const auto nDims = m_aoDims.size();
2352 10 : std::vector<GUInt64> anInnerBlockIdx(nDims);
2353 10 : std::vector<GUInt64> anInnerBlockCounter(nDims);
2354 10 : const std::vector<size_t> anCount(nDims, 1);
2355 10 : const std::vector<GInt64> anArrayStep(nDims, 0);
2356 10 : const std::vector<GPtrDiff_t> anBufferStride(nDims, 0);
2357 5 : const auto &apoDimsCache = poBlockPresenceArray->GetDimensions();
2358 10 : const auto eByteDT = GDALExtendedDataType::Create(GDT_UInt8);
2359 :
2360 5 : CPLDebug(ZARR_DEBUG_KEY,
2361 : "BlockCachePresence(): Iterating over %s to find which tiles are "
2362 : "present...",
2363 : osDirectoryName.c_str());
2364 5 : uint64_t nCounter = 0;
2365 : const char chSrcFilenameDirSeparator =
2366 5 : VSIGetDirectorySeparator(osDirectoryName.c_str())[0];
2367 44 : while (const VSIDIREntry *psEntry = VSIGetNextDirEntry(psDir.get()))
2368 : {
2369 39 : if (!VSI_ISDIR(psEntry->nMode))
2370 : {
2371 : const CPLStringList aosTokens = GetChunkIndicesFromFilename(
2372 0 : CPLString(psEntry->pszName)
2373 29 : .replaceAll(chSrcFilenameDirSeparator, '/')
2374 58 : .c_str());
2375 29 : if (aosTokens.size() == static_cast<int>(nDims))
2376 : {
2377 : // Get tile indices from filename
2378 19 : bool unexpectedIndex = false;
2379 19 : uint64_t nInnerChunksInOuter = 1;
2380 57 : for (int i = 0; i < aosTokens.size(); ++i)
2381 : {
2382 38 : if (CPLGetValueType(aosTokens[i]) != CPL_VALUE_INTEGER)
2383 : {
2384 4 : unexpectedIndex = true;
2385 : }
2386 76 : anInnerBlockIdx[i] =
2387 38 : static_cast<GUInt64>(CPLAtoGIntBig(aosTokens[i]));
2388 : const auto nInnerChunkCounterThisDim =
2389 38 : m_anCountInnerBlockInOuter[i];
2390 38 : nInnerChunksInOuter *= nInnerChunkCounterThisDim;
2391 38 : if (anInnerBlockIdx[i] >=
2392 38 : apoDimsCache[i]->GetSize() / nInnerChunkCounterThisDim)
2393 : {
2394 6 : unexpectedIndex = true;
2395 : }
2396 38 : anInnerBlockIdx[i] *= nInnerChunkCounterThisDim;
2397 : }
2398 19 : if (unexpectedIndex)
2399 : {
2400 7 : continue;
2401 : }
2402 :
2403 12 : std::fill(anInnerBlockCounter.begin(),
2404 12 : anInnerBlockCounter.end(), 0);
2405 :
2406 36 : for (uint64_t iInnerChunk = 0;
2407 36 : iInnerChunk < nInnerChunksInOuter; ++iInnerChunk)
2408 : {
2409 24 : if (iInnerChunk > 0)
2410 : {
2411 : // Update chunk coordinates
2412 12 : size_t iDim = m_anInnerBlockSize.size() - 1;
2413 : const auto nInnerChunkCounterThisDim =
2414 12 : m_anCountInnerBlockInOuter[iDim];
2415 :
2416 12 : ++anInnerBlockIdx[iDim];
2417 12 : ++anInnerBlockCounter[iDim];
2418 :
2419 16 : while (anInnerBlockCounter[iDim] ==
2420 : nInnerChunkCounterThisDim)
2421 : {
2422 4 : anInnerBlockIdx[iDim] -= nInnerChunkCounterThisDim;
2423 4 : anInnerBlockCounter[iDim] = 0;
2424 4 : --iDim;
2425 :
2426 4 : ++anInnerBlockIdx[iDim];
2427 4 : ++anInnerBlockCounter[iDim];
2428 : }
2429 : }
2430 :
2431 24 : nCounter++;
2432 24 : if ((nCounter % 1000) == 0)
2433 : {
2434 0 : CPLDebug(
2435 : ZARR_DEBUG_KEY,
2436 : "BlockCachePresence(): Listing in progress "
2437 : "(last examined %s, at least %.02f %% completed)",
2438 0 : psEntry->pszName,
2439 0 : 100.0 * double(nCounter) /
2440 0 : double(m_nTotalInnerChunkCount));
2441 : }
2442 24 : constexpr GByte byOne = 1;
2443 : // CPLDebugOnly(ZARR_DEBUG_KEY, "Marking %s has present",
2444 : // psEntry->pszName);
2445 48 : if (!poBlockPresenceArray->Write(
2446 24 : anInnerBlockIdx.data(), anCount.data(),
2447 : anArrayStep.data(), anBufferStride.data(), eByteDT,
2448 : &byOne))
2449 : {
2450 0 : return false;
2451 : }
2452 : }
2453 : }
2454 : }
2455 39 : }
2456 5 : CPLDebug(ZARR_DEBUG_KEY, "BlockCachePresence(): finished");
2457 :
2458 : // Write filling_status attribute
2459 5 : auto poAttr = poBlockPresenceArray->CreateAttribute(
2460 10 : "filling_status", {}, GDALExtendedDataType::CreateString(), nullptr);
2461 5 : if (poAttr)
2462 : {
2463 5 : if (nCounter == 0)
2464 0 : poAttr->Write("no_tile_present");
2465 5 : else if (nCounter == m_nTotalInnerChunkCount)
2466 0 : poAttr->Write("all_tiles_present");
2467 : else
2468 5 : poAttr->Write("some_tiles_missing");
2469 : }
2470 :
2471 : // Force closing
2472 5 : m_poBlockCachePresenceArray = nullptr;
2473 5 : m_bHasTriedBlockCachePresenceArray = false;
2474 :
2475 5 : return true;
2476 : }
2477 :
2478 : /************************************************************************/
2479 : /* ZarrArray::CreateAttribute() */
2480 : /************************************************************************/
2481 :
2482 132 : std::shared_ptr<GDALAttribute> ZarrArray::CreateAttribute(
2483 : const std::string &osName, const std::vector<GUInt64> &anDimensions,
2484 : const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
2485 : {
2486 132 : if (!CheckValidAndErrorOutIfNot())
2487 0 : return nullptr;
2488 :
2489 132 : if (!m_bUpdatable)
2490 : {
2491 2 : CPLError(CE_Failure, CPLE_NotSupported,
2492 : "Dataset not open in update mode");
2493 2 : return nullptr;
2494 : }
2495 130 : if (anDimensions.size() >= 2)
2496 : {
2497 2 : CPLError(CE_Failure, CPLE_NotSupported,
2498 : "Cannot create attributes of dimension >= 2");
2499 2 : return nullptr;
2500 : }
2501 : return m_oAttrGroup.CreateAttribute(osName, anDimensions, oDataType,
2502 128 : papszOptions);
2503 : }
2504 :
2505 : /************************************************************************/
2506 : /* ZarrGroupBase::DeleteAttribute() */
2507 : /************************************************************************/
2508 :
2509 18 : bool ZarrArray::DeleteAttribute(const std::string &osName, CSLConstList)
2510 : {
2511 18 : if (!CheckValidAndErrorOutIfNot())
2512 0 : return false;
2513 :
2514 18 : if (!m_bUpdatable)
2515 : {
2516 6 : CPLError(CE_Failure, CPLE_NotSupported,
2517 : "Dataset not open in update mode");
2518 6 : return false;
2519 : }
2520 :
2521 12 : return m_oAttrGroup.DeleteAttribute(osName);
2522 : }
2523 :
2524 : /************************************************************************/
2525 : /* ZarrArray::SetSpatialRef() */
2526 : /************************************************************************/
2527 :
2528 45 : bool ZarrArray::SetSpatialRef(const OGRSpatialReference *poSRS)
2529 : {
2530 45 : if (!CheckValidAndErrorOutIfNot())
2531 0 : return false;
2532 :
2533 45 : if (!m_bUpdatable)
2534 : {
2535 2 : return GDALPamMDArray::SetSpatialRef(poSRS);
2536 : }
2537 43 : m_oCRSAttribute.Deinit();
2538 43 : m_poSRS.reset();
2539 43 : if (poSRS)
2540 43 : m_poSRS.reset(poSRS->Clone());
2541 43 : m_bSRSModified = true;
2542 43 : return true;
2543 : }
2544 :
2545 : /************************************************************************/
2546 : /* ZarrArray::SetUnit() */
2547 : /************************************************************************/
2548 :
2549 9 : bool ZarrArray::SetUnit(const std::string &osUnit)
2550 : {
2551 9 : if (!CheckValidAndErrorOutIfNot())
2552 0 : return false;
2553 :
2554 9 : if (!m_bUpdatable)
2555 : {
2556 0 : CPLError(CE_Failure, CPLE_NotSupported,
2557 : "Dataset not open in update mode");
2558 0 : return false;
2559 : }
2560 9 : m_osUnit = osUnit;
2561 9 : m_bUnitModified = true;
2562 9 : return true;
2563 : }
2564 :
2565 : /************************************************************************/
2566 : /* ZarrArray::GetOffset() */
2567 : /************************************************************************/
2568 :
2569 89 : double ZarrArray::GetOffset(bool *pbHasOffset,
2570 : GDALDataType *peStorageType) const
2571 : {
2572 89 : if (pbHasOffset)
2573 89 : *pbHasOffset = m_bHasOffset;
2574 89 : if (peStorageType)
2575 1 : *peStorageType = GDT_Unknown;
2576 89 : return m_dfOffset;
2577 : }
2578 :
2579 : /************************************************************************/
2580 : /* ZarrArray::GetScale() */
2581 : /************************************************************************/
2582 :
2583 87 : double ZarrArray::GetScale(bool *pbHasScale, GDALDataType *peStorageType) const
2584 : {
2585 87 : if (pbHasScale)
2586 87 : *pbHasScale = m_bHasScale;
2587 87 : if (peStorageType)
2588 1 : *peStorageType = GDT_Unknown;
2589 87 : return m_dfScale;
2590 : }
2591 :
2592 : /************************************************************************/
2593 : /* ZarrArray::SetOffset() */
2594 : /************************************************************************/
2595 :
2596 3 : bool ZarrArray::SetOffset(double dfOffset, GDALDataType /* eStorageType */)
2597 : {
2598 3 : if (!CheckValidAndErrorOutIfNot())
2599 0 : return false;
2600 :
2601 3 : m_dfOffset = dfOffset;
2602 3 : m_bHasOffset = true;
2603 3 : m_bOffsetModified = true;
2604 3 : return true;
2605 : }
2606 :
2607 : /************************************************************************/
2608 : /* ZarrArray::SetScale() */
2609 : /************************************************************************/
2610 :
2611 3 : bool ZarrArray::SetScale(double dfScale, GDALDataType /* eStorageType */)
2612 : {
2613 3 : if (!CheckValidAndErrorOutIfNot())
2614 0 : return false;
2615 :
2616 3 : m_dfScale = dfScale;
2617 3 : m_bHasScale = true;
2618 3 : m_bScaleModified = true;
2619 3 : return true;
2620 : }
2621 :
2622 : /************************************************************************/
2623 : /* GetDimensionTypeDirection() */
2624 : /************************************************************************/
2625 :
2626 : /* static */
2627 240 : void ZarrArray::GetDimensionTypeDirection(CPLJSONObject &oAttributes,
2628 : std::string &osType,
2629 : std::string &osDirection)
2630 : {
2631 480 : std::string osUnit;
2632 720 : const auto unit = oAttributes[CF_UNITS];
2633 240 : if (unit.GetType() == CPLJSONObject::Type::String)
2634 : {
2635 52 : osUnit = unit.ToString();
2636 : }
2637 :
2638 720 : const auto oStdName = oAttributes[CF_STD_NAME];
2639 240 : if (oStdName.GetType() == CPLJSONObject::Type::String)
2640 : {
2641 156 : const auto osStdName = oStdName.ToString();
2642 52 : if (osStdName == CF_PROJ_X_COORD || osStdName == CF_LONGITUDE_STD_NAME)
2643 : {
2644 26 : osType = GDAL_DIM_TYPE_HORIZONTAL_X;
2645 26 : oAttributes.Delete(CF_STD_NAME);
2646 26 : if (osUnit == CF_DEGREES_EAST)
2647 : {
2648 24 : osDirection = "EAST";
2649 : }
2650 : }
2651 50 : else if (osStdName == CF_PROJ_Y_COORD ||
2652 24 : osStdName == CF_LATITUDE_STD_NAME)
2653 : {
2654 26 : osType = GDAL_DIM_TYPE_HORIZONTAL_Y;
2655 26 : oAttributes.Delete(CF_STD_NAME);
2656 26 : if (osUnit == CF_DEGREES_NORTH)
2657 : {
2658 24 : osDirection = "NORTH";
2659 : }
2660 : }
2661 0 : else if (osStdName == "time")
2662 : {
2663 0 : osType = GDAL_DIM_TYPE_TEMPORAL;
2664 0 : oAttributes.Delete(CF_STD_NAME);
2665 : }
2666 : }
2667 :
2668 720 : const auto osAxis = oAttributes[CF_AXIS].ToString();
2669 240 : if (osAxis == "Z")
2670 : {
2671 0 : osType = GDAL_DIM_TYPE_VERTICAL;
2672 0 : const auto osPositive = oAttributes["positive"].ToString();
2673 0 : if (osPositive == "up")
2674 : {
2675 0 : osDirection = "UP";
2676 0 : oAttributes.Delete("positive");
2677 : }
2678 0 : else if (osPositive == "down")
2679 : {
2680 0 : osDirection = "DOWN";
2681 0 : oAttributes.Delete("positive");
2682 : }
2683 0 : oAttributes.Delete(CF_AXIS);
2684 : }
2685 240 : }
2686 :
2687 : /************************************************************************/
2688 : /* GetCoordinateVariables() */
2689 : /************************************************************************/
2690 :
2691 : std::vector<std::shared_ptr<GDALMDArray>>
2692 2 : ZarrArray::GetCoordinateVariables() const
2693 : {
2694 2 : if (!CheckValidAndErrorOutIfNot())
2695 0 : return {};
2696 :
2697 4 : std::vector<std::shared_ptr<GDALMDArray>> ret;
2698 6 : const auto poCoordinates = GetAttribute("coordinates");
2699 1 : if (poCoordinates &&
2700 3 : poCoordinates->GetDataType().GetClass() == GEDTC_STRING &&
2701 1 : poCoordinates->GetDimensionCount() == 0)
2702 : {
2703 1 : const char *pszCoordinates = poCoordinates->ReadAsString();
2704 1 : if (pszCoordinates)
2705 : {
2706 2 : auto poGroup = m_poGroupWeak.lock();
2707 1 : if (!poGroup)
2708 : {
2709 0 : CPLError(CE_Failure, CPLE_AppDefined,
2710 : "Cannot access coordinate variables of %s has "
2711 : "belonging group has gone out of scope",
2712 0 : GetName().c_str());
2713 : }
2714 : else
2715 : {
2716 : const CPLStringList aosNames(
2717 2 : CSLTokenizeString2(pszCoordinates, " ", 0));
2718 3 : for (int i = 0; i < aosNames.size(); i++)
2719 : {
2720 6 : auto poCoordinateVar = poGroup->OpenMDArray(aosNames[i]);
2721 2 : if (poCoordinateVar)
2722 : {
2723 2 : ret.emplace_back(poCoordinateVar);
2724 : }
2725 : else
2726 : {
2727 0 : CPLError(CE_Warning, CPLE_AppDefined,
2728 : "Cannot find variable corresponding to "
2729 : "coordinate %s",
2730 : aosNames[i]);
2731 : }
2732 : }
2733 : }
2734 : }
2735 : }
2736 :
2737 2 : return ret;
2738 : }
2739 :
2740 : /************************************************************************/
2741 : /* Resize() */
2742 : /************************************************************************/
2743 :
2744 16 : bool ZarrArray::Resize(const std::vector<GUInt64> &anNewDimSizes,
2745 : CSLConstList /* papszOptions */)
2746 : {
2747 16 : if (!CheckValidAndErrorOutIfNot())
2748 0 : return false;
2749 :
2750 16 : if (!IsWritable())
2751 : {
2752 3 : CPLError(CE_Failure, CPLE_AppDefined,
2753 : "Resize() not supported on read-only file");
2754 3 : return false;
2755 : }
2756 :
2757 13 : const auto nDimCount = GetDimensionCount();
2758 13 : if (anNewDimSizes.size() != nDimCount)
2759 : {
2760 0 : CPLError(CE_Failure, CPLE_IllegalArg,
2761 : "Not expected number of values in anNewDimSizes.");
2762 0 : return false;
2763 : }
2764 :
2765 13 : auto &dims = GetDimensions();
2766 26 : std::vector<size_t> anGrownDimIdx;
2767 26 : std::map<GDALDimension *, GUInt64> oMapDimToSize;
2768 27 : for (size_t i = 0; i < nDimCount; ++i)
2769 : {
2770 21 : auto oIter = oMapDimToSize.find(dims[i].get());
2771 21 : if (oIter != oMapDimToSize.end() && oIter->second != anNewDimSizes[i])
2772 : {
2773 2 : CPLError(CE_Failure, CPLE_AppDefined,
2774 : "Cannot resize a dimension referenced several times "
2775 : "to different sizes");
2776 7 : return false;
2777 : }
2778 19 : if (anNewDimSizes[i] != dims[i]->GetSize())
2779 : {
2780 14 : if (anNewDimSizes[i] < dims[i]->GetSize())
2781 : {
2782 5 : CPLError(CE_Failure, CPLE_NotSupported,
2783 : "Resize() does not support shrinking the array.");
2784 5 : return false;
2785 : }
2786 :
2787 9 : oMapDimToSize[dims[i].get()] = anNewDimSizes[i];
2788 9 : anGrownDimIdx.push_back(i);
2789 : }
2790 : else
2791 : {
2792 5 : oMapDimToSize[dims[i].get()] = dims[i]->GetSize();
2793 : }
2794 : }
2795 6 : if (!anGrownDimIdx.empty())
2796 : {
2797 6 : m_bDefinitionModified = true;
2798 13 : for (size_t dimIdx : anGrownDimIdx)
2799 : {
2800 14 : auto dim = std::dynamic_pointer_cast<ZarrDimension>(dims[dimIdx]);
2801 7 : if (dim)
2802 : {
2803 7 : dim->SetSize(anNewDimSizes[dimIdx]);
2804 7 : if (dim->GetName() != dim->GetFullName())
2805 : {
2806 : // This is not a local dimension
2807 7 : m_poSharedResource->UpdateDimensionSize(dim);
2808 : }
2809 : }
2810 : else
2811 : {
2812 0 : CPLAssert(false);
2813 : }
2814 : }
2815 : }
2816 6 : return true;
2817 : }
2818 :
2819 : /************************************************************************/
2820 : /* NotifyChildrenOfRenaming() */
2821 : /************************************************************************/
2822 :
2823 15 : void ZarrArray::NotifyChildrenOfRenaming()
2824 : {
2825 15 : m_oAttrGroup.ParentRenamed(m_osFullName);
2826 15 : }
2827 :
2828 : /************************************************************************/
2829 : /* ParentRenamed() */
2830 : /************************************************************************/
2831 :
2832 9 : void ZarrArray::ParentRenamed(const std::string &osNewParentFullName)
2833 : {
2834 9 : GDALMDArray::ParentRenamed(osNewParentFullName);
2835 :
2836 9 : auto poParent = m_poGroupWeak.lock();
2837 : // The parent necessarily exist, since it notified us
2838 9 : CPLAssert(poParent);
2839 :
2840 27 : m_osFilename = CPLFormFilenameSafe(
2841 18 : CPLFormFilenameSafe(poParent->GetDirectoryName().c_str(),
2842 9 : m_osName.c_str(), nullptr)
2843 : .c_str(),
2844 9 : CPLGetFilename(m_osFilename.c_str()), nullptr);
2845 9 : }
2846 :
2847 : /************************************************************************/
2848 : /* Rename() */
2849 : /************************************************************************/
2850 :
2851 21 : bool ZarrArray::Rename(const std::string &osNewName)
2852 : {
2853 21 : if (!CheckValidAndErrorOutIfNot())
2854 0 : return false;
2855 :
2856 21 : if (!m_bUpdatable)
2857 : {
2858 6 : CPLError(CE_Failure, CPLE_NotSupported,
2859 : "Dataset not open in update mode");
2860 6 : return false;
2861 : }
2862 15 : if (!ZarrGroupBase::IsValidObjectName(osNewName))
2863 : {
2864 3 : CPLError(CE_Failure, CPLE_NotSupported, "Invalid array name");
2865 3 : return false;
2866 : }
2867 :
2868 24 : auto poParent = m_poGroupWeak.lock();
2869 12 : if (poParent)
2870 : {
2871 12 : if (!poParent->CheckArrayOrGroupWithSameNameDoesNotExist(osNewName))
2872 6 : return false;
2873 : }
2874 :
2875 : const std::string osRootDirectoryName(
2876 12 : CPLGetDirnameSafe(CPLGetDirnameSafe(m_osFilename.c_str()).c_str()));
2877 : const std::string osOldDirectoryName = CPLFormFilenameSafe(
2878 12 : osRootDirectoryName.c_str(), m_osName.c_str(), nullptr);
2879 : const std::string osNewDirectoryName = CPLFormFilenameSafe(
2880 12 : osRootDirectoryName.c_str(), osNewName.c_str(), nullptr);
2881 :
2882 6 : if (VSIRename(osOldDirectoryName.c_str(), osNewDirectoryName.c_str()) != 0)
2883 : {
2884 0 : CPLError(CE_Failure, CPLE_AppDefined, "Renaming of %s to %s failed",
2885 : osOldDirectoryName.c_str(), osNewDirectoryName.c_str());
2886 0 : return false;
2887 : }
2888 :
2889 6 : m_poSharedResource->RenameZMetadataRecursive(osOldDirectoryName,
2890 : osNewDirectoryName);
2891 :
2892 : m_osFilename =
2893 12 : CPLFormFilenameSafe(osNewDirectoryName.c_str(),
2894 6 : CPLGetFilename(m_osFilename.c_str()), nullptr);
2895 :
2896 6 : if (poParent)
2897 : {
2898 6 : poParent->NotifyArrayRenamed(m_osName, osNewName);
2899 : }
2900 :
2901 6 : BaseRename(osNewName);
2902 :
2903 6 : return true;
2904 : }
2905 :
2906 : /************************************************************************/
2907 : /* NotifyChildrenOfDeletion() */
2908 : /************************************************************************/
2909 :
2910 10 : void ZarrArray::NotifyChildrenOfDeletion()
2911 : {
2912 10 : m_oAttrGroup.ParentDeleted();
2913 10 : }
2914 :
2915 : /************************************************************************/
2916 : /* ParseProjCRS() */
2917 : /************************************************************************/
2918 :
2919 1931 : static void ParseProjCRS(const ZarrAttributeGroup *poAttrGroup,
2920 : CPLJSONObject &oAttributes, bool bFoundProjUUID,
2921 : std::shared_ptr<OGRSpatialReference> &poSRS)
2922 : {
2923 : const auto poAttrProjCode =
2924 3869 : bFoundProjUUID ? poAttrGroup->GetAttribute("proj:code") : nullptr;
2925 : const char *pszProjCode =
2926 1931 : poAttrProjCode ? poAttrProjCode->ReadAsString() : nullptr;
2927 1931 : if (pszProjCode)
2928 : {
2929 2 : poSRS = std::make_shared<OGRSpatialReference>();
2930 2 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2931 2 : if (poSRS->SetFromUserInput(
2932 : pszProjCode,
2933 2 : OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
2934 : OGRERR_NONE)
2935 : {
2936 0 : poSRS.reset();
2937 : }
2938 : else
2939 : {
2940 2 : oAttributes.Delete("proj:code");
2941 : }
2942 : }
2943 : else
2944 : {
2945 : // EOP Sentinel Zarr Samples Service only
2946 5787 : const auto poAttrProjEPSG = poAttrGroup->GetAttribute("proj:epsg");
2947 1929 : if (poAttrProjEPSG)
2948 : {
2949 1 : poSRS = std::make_shared<OGRSpatialReference>();
2950 1 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2951 1 : if (poSRS->importFromEPSG(poAttrProjEPSG->ReadAsInt()) !=
2952 : OGRERR_NONE)
2953 : {
2954 0 : poSRS.reset();
2955 : }
2956 : else
2957 : {
2958 1 : oAttributes.Delete("proj:epsg");
2959 : }
2960 : }
2961 : else
2962 : {
2963 : // Both EOPF Sentinel Zarr Samples Service and geo-proj convention
2964 5784 : const auto poAttrProjWKT2 = poAttrGroup->GetAttribute("proj:wkt2");
2965 : const char *pszProjWKT2 =
2966 1928 : poAttrProjWKT2 ? poAttrProjWKT2->ReadAsString() : nullptr;
2967 1928 : if (pszProjWKT2)
2968 : {
2969 3 : poSRS = std::make_shared<OGRSpatialReference>();
2970 3 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2971 3 : if (poSRS->importFromWkt(pszProjWKT2) != OGRERR_NONE)
2972 : {
2973 0 : poSRS.reset();
2974 : }
2975 : else
2976 : {
2977 3 : oAttributes.Delete("proj:wkt2");
2978 : }
2979 : }
2980 1925 : else if (bFoundProjUUID)
2981 : {
2982 : // geo-proj convention
2983 : const auto poAttrProjPROJJSON =
2984 9 : poAttrGroup->GetAttribute("proj:projjson");
2985 : const char *pszProjPROJJSON =
2986 3 : poAttrProjPROJJSON ? poAttrProjPROJJSON->ReadAsString()
2987 3 : : nullptr;
2988 3 : if (pszProjPROJJSON)
2989 : {
2990 1 : poSRS = std::make_shared<OGRSpatialReference>();
2991 1 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2992 1 : if (poSRS->SetFromUserInput(
2993 : pszProjPROJJSON,
2994 : OGRSpatialReference::
2995 1 : SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
2996 : OGRERR_NONE)
2997 : {
2998 0 : poSRS.reset();
2999 : }
3000 : else
3001 : {
3002 1 : oAttributes.Delete("proj:projjson");
3003 : }
3004 : }
3005 : }
3006 : }
3007 : }
3008 1931 : }
3009 :
3010 : /************************************************************************/
3011 : /* ParseSpatialConventions() */
3012 : /************************************************************************/
3013 :
3014 116 : static void ParseSpatialConventions(
3015 : const std::shared_ptr<ZarrSharedResource> &poSharedResource,
3016 : const ZarrAttributeGroup *poAttrGroup, CPLJSONObject &oAttributes,
3017 : std::shared_ptr<OGRSpatialReference> &poSRS, bool &bAxisAssigned,
3018 : const std::vector<std::shared_ptr<GDALDimension>> &apoDims)
3019 : {
3020 : // From https://github.com/zarr-conventions/spatial
3021 : const auto poAttrSpatialDimensions =
3022 232 : poAttrGroup->GetAttribute("spatial:dimensions");
3023 116 : if (!poAttrSpatialDimensions)
3024 110 : return;
3025 :
3026 : const auto aosSpatialDimensions =
3027 6 : poAttrSpatialDimensions->ReadAsStringArray();
3028 6 : if (aosSpatialDimensions.size() < 2)
3029 0 : return;
3030 :
3031 6 : int iDimNameY = 0;
3032 6 : int iDimNameX = 0;
3033 : const char *pszNameY =
3034 6 : aosSpatialDimensions[aosSpatialDimensions.size() - 2];
3035 : const char *pszNameX =
3036 6 : aosSpatialDimensions[aosSpatialDimensions.size() - 1];
3037 6 : int iDim = 1;
3038 18 : for (const auto &poDim : apoDims)
3039 : {
3040 12 : if (poDim->GetName() == pszNameX)
3041 6 : iDimNameX = iDim;
3042 6 : else if (poDim->GetName() == pszNameY)
3043 6 : iDimNameY = iDim;
3044 12 : ++iDim;
3045 : }
3046 6 : if (iDimNameX == 0)
3047 : {
3048 0 : CPLError(CE_Warning, CPLE_AppDefined,
3049 : "spatial:dimensions[%d] = %s is a unknown "
3050 : "Zarr dimension",
3051 0 : static_cast<int>(aosSpatialDimensions.size() - 1), pszNameX);
3052 : }
3053 6 : if (iDimNameY == 0)
3054 : {
3055 0 : CPLError(CE_Warning, CPLE_AppDefined,
3056 : "spatial_dimensions[%d] = %s is a unknown "
3057 : "Zarr dimension",
3058 0 : static_cast<int>(aosSpatialDimensions.size() - 2), pszNameY);
3059 : }
3060 :
3061 6 : if (iDimNameX > 0 && iDimNameY > 0)
3062 : {
3063 6 : oAttributes.Delete("spatial:dimensions");
3064 :
3065 6 : if (!bAxisAssigned && poSRS)
3066 : {
3067 5 : const auto &oMapping = poSRS->GetDataAxisToSRSAxisMapping();
3068 15 : if (oMapping == std::vector<int>{2, 1} ||
3069 10 : oMapping == std::vector<int>{2, 1, 3})
3070 0 : poSRS->SetDataAxisToSRSAxisMapping({iDimNameY, iDimNameX});
3071 10 : else if (oMapping == std::vector<int>{1, 2} ||
3072 5 : oMapping == std::vector<int>{1, 2, 3})
3073 5 : poSRS->SetDataAxisToSRSAxisMapping({iDimNameX, iDimNameY});
3074 :
3075 5 : bAxisAssigned = true;
3076 : }
3077 : }
3078 :
3079 : const auto poAttrSpatialRegistration =
3080 18 : poAttrGroup->GetAttribute("spatial:registration");
3081 6 : bool bIsNodeRegistration = false;
3082 6 : if (!poAttrSpatialRegistration)
3083 3 : oAttributes.Set("spatial:registration", "pixel"); // default value
3084 : else
3085 : {
3086 : const char *pszSpatialRegistration =
3087 3 : poAttrSpatialRegistration->ReadAsString();
3088 3 : if (pszSpatialRegistration &&
3089 3 : strcmp(pszSpatialRegistration, "node") == 0)
3090 1 : bIsNodeRegistration = true;
3091 : }
3092 :
3093 : const auto poAttrSpatialTransform =
3094 18 : poAttrGroup->GetAttribute("spatial:transform");
3095 : const auto poAttrSpatialTransformType =
3096 18 : poAttrGroup->GetAttribute("spatial:transform_type");
3097 : const char *pszAttrSpatialTransformType =
3098 6 : poAttrSpatialTransformType ? poAttrSpatialTransformType->ReadAsString()
3099 6 : : nullptr;
3100 :
3101 8 : if (poAttrSpatialTransform &&
3102 2 : (!pszAttrSpatialTransformType ||
3103 8 : strcmp(pszAttrSpatialTransformType, "affine") == 0))
3104 : {
3105 12 : auto adfSpatialTransform = poAttrSpatialTransform->ReadAsDoubleArray();
3106 6 : if (adfSpatialTransform.size() == 6)
3107 : {
3108 6 : oAttributes.Delete("spatial:transform");
3109 6 : oAttributes.Delete("spatial:transform_type");
3110 :
3111 : // If we have rotation/shear coefficients, expose a gdal:geotransform
3112 : // attributes
3113 6 : if (adfSpatialTransform[1] != 0 || adfSpatialTransform[3] != 0)
3114 : {
3115 2 : if (bIsNodeRegistration)
3116 : {
3117 : // From pixel center convention to GDAL's corner convention
3118 1 : adfSpatialTransform[2] -= 0.5 * adfSpatialTransform[0] +
3119 1 : 0.5 * adfSpatialTransform[1];
3120 2 : adfSpatialTransform[5] -= 0.5 * adfSpatialTransform[3] +
3121 1 : 0.5 * adfSpatialTransform[4];
3122 : }
3123 :
3124 2 : CPLJSONArray oGeoTransform;
3125 : // Reorder coefficients to GDAL convention
3126 14 : for (int idx : {2, 0, 1, 5, 3, 4})
3127 12 : oGeoTransform.Add(adfSpatialTransform[idx]);
3128 2 : oAttributes["gdal:geotransform"] = oGeoTransform;
3129 : }
3130 : else
3131 : {
3132 4 : auto &poDimX = apoDims[iDimNameX - 1];
3133 4 : auto &poDimY = apoDims[iDimNameY - 1];
3134 4 : if (!dynamic_cast<GDALMDArrayRegularlySpaced *>(
3135 16 : poDimX->GetIndexingVariable().get()) &&
3136 4 : !dynamic_cast<GDALMDArrayRegularlySpaced *>(
3137 9 : poDimY->GetIndexingVariable().get()))
3138 : {
3139 : auto poIndexingVarX = GDALMDArrayRegularlySpaced::Create(
3140 4 : std::string(), poDimX->GetName(), poDimX,
3141 12 : adfSpatialTransform[2] + adfSpatialTransform[0] / 2,
3142 12 : adfSpatialTransform[0], 0);
3143 4 : poDimX->SetIndexingVariable(poIndexingVarX);
3144 :
3145 : // Make the shared resource hold a strong
3146 : // reference on the indexing variable,
3147 : // so that it remains available to anyone
3148 : // querying the dimension for it.
3149 4 : poSharedResource->RegisterIndexingVariable(
3150 : poDimX->GetFullName(), poIndexingVarX);
3151 :
3152 : auto poIndexingVarY = GDALMDArrayRegularlySpaced::Create(
3153 4 : std::string(), poDimY->GetName(), poDimY,
3154 12 : adfSpatialTransform[5] + adfSpatialTransform[4] / 2,
3155 8 : adfSpatialTransform[4], 0);
3156 4 : poDimY->SetIndexingVariable(poIndexingVarY);
3157 4 : poSharedResource->RegisterIndexingVariable(
3158 : poDimY->GetFullName(), poIndexingVarY);
3159 : }
3160 : }
3161 : }
3162 : else
3163 : {
3164 0 : CPLError(CE_Warning, CPLE_AppDefined,
3165 : "spatial:transform[] contains an "
3166 : "unexpected number of values: %d",
3167 0 : static_cast<int>(adfSpatialTransform.size()));
3168 : }
3169 : }
3170 : }
3171 :
3172 : /************************************************************************/
3173 : /* DetectSRSFromEOPFSampleServiceMetadata() */
3174 : /************************************************************************/
3175 :
3176 : /* This function is derived from ExtractCoordinateMetadata() of
3177 : * https://github.com/EOPF-Sample-Service/GDAL-ZARR-EOPF/blob/main/src/eopf_metadata.cpp
3178 : * released under the MIT license and
3179 : * Copyright (c) 2024 Yuvraj Adagale and contributors
3180 : *
3181 : * Note: it does not handle defaulting to EPSG:4326 as it is not clear to me
3182 : * (E. Rouault) why this would be needed, at least for Sentinel2 L1C or L2
3183 : * products
3184 : */
3185 13 : static void DetectSRSFromEOPFSampleServiceMetadata(
3186 : const std::string &osRootDirectoryName,
3187 : const std::shared_ptr<ZarrGroupBase> &poGroup,
3188 : std::shared_ptr<OGRSpatialReference> &poSRS)
3189 : {
3190 13 : const CPLJSONObject obj = poGroup->GetAttributeGroup().Serialize();
3191 :
3192 : // -----------------------------------
3193 : // STEP 1: Extract spatial reference information
3194 : // -----------------------------------
3195 :
3196 : // Find EPSG code directly or in STAC properties
3197 13 : int nEPSGCode = 0;
3198 13 : const CPLJSONObject &stacDiscovery = obj.GetObj("stac_discovery");
3199 13 : if (stacDiscovery.IsValid())
3200 : {
3201 14 : const CPLJSONObject &properties = stacDiscovery.GetObj("properties");
3202 7 : if (properties.IsValid())
3203 : {
3204 : // Try to get proj:epsg as a number first, then as a string
3205 6 : nEPSGCode = properties.GetInteger("proj:epsg", 0);
3206 6 : if (nEPSGCode <= 0)
3207 : {
3208 : nEPSGCode =
3209 4 : std::atoi(properties.GetString("proj:epsg", "").c_str());
3210 : }
3211 6 : if (nEPSGCode > 0)
3212 : {
3213 2 : CPLDebugOnly(ZARR_DEBUG_KEY,
3214 : "Found proj:epsg in STAC properties: %d",
3215 : nEPSGCode);
3216 : }
3217 : }
3218 : }
3219 :
3220 : // If not found in STAC, try top level
3221 13 : if (nEPSGCode <= 0)
3222 : {
3223 11 : nEPSGCode = obj.GetInteger("proj:epsg", obj.GetInteger("epsg", 0));
3224 11 : if (nEPSGCode <= 0)
3225 : {
3226 9 : nEPSGCode = std::atoi(
3227 18 : obj.GetString("proj:epsg", obj.GetString("epsg", "")).c_str());
3228 : }
3229 11 : if (nEPSGCode > 0)
3230 : {
3231 2 : CPLDebugOnly(ZARR_DEBUG_KEY, "Found proj:epsg at top level: %d",
3232 : nEPSGCode);
3233 : }
3234 : }
3235 :
3236 : // If still not found, simple search in common locations
3237 13 : if (nEPSGCode <= 0)
3238 : {
3239 17 : for (const auto &child : obj.GetChildren())
3240 : {
3241 8 : if (child.GetType() == CPLJSONObject::Type::Object)
3242 : {
3243 : nEPSGCode =
3244 7 : child.GetInteger("proj:epsg", child.GetInteger("epsg", 0));
3245 7 : if (nEPSGCode <= 0)
3246 : {
3247 5 : nEPSGCode = std::atoi(
3248 : child
3249 10 : .GetString("proj:epsg", child.GetString("epsg", ""))
3250 : .c_str());
3251 : }
3252 7 : if (nEPSGCode > 0)
3253 : {
3254 2 : CPLDebugOnly(ZARR_DEBUG_KEY,
3255 : "Found proj:epsg in child %s: %d",
3256 : child.GetName().c_str(), nEPSGCode);
3257 2 : break;
3258 : }
3259 : }
3260 : }
3261 : }
3262 :
3263 : // Enhanced search for STAC discovery metadata with better structure parsing
3264 13 : if (nEPSGCode <= 0 && stacDiscovery.IsValid())
3265 : {
3266 : // Try to get the full STAC item
3267 10 : const CPLJSONObject &geometry = stacDiscovery.GetObj("geometry");
3268 :
3269 5 : if (geometry.IsValid())
3270 : {
3271 2 : const CPLJSONObject &geomCrs = geometry.GetObj("crs");
3272 1 : if (geomCrs.IsValid())
3273 : {
3274 2 : const CPLJSONObject &geomProps = geomCrs.GetObj("properties");
3275 1 : if (geomProps.IsValid())
3276 : {
3277 1 : const int nGeomEpsg = geomProps.GetInteger("code", 0);
3278 1 : if (nGeomEpsg != 0)
3279 : {
3280 1 : nEPSGCode = nGeomEpsg;
3281 1 : CPLDebugOnly(ZARR_DEBUG_KEY,
3282 : "Found CRS code in STAC geometry: %d",
3283 : nEPSGCode);
3284 : }
3285 : }
3286 : }
3287 : }
3288 : }
3289 :
3290 : // Try to infer CRS from Sentinel-2 tile naming convention
3291 13 : if (nEPSGCode <= 0)
3292 : {
3293 : // Look for Sentinel-2 tile ID pattern in dataset name or metadata
3294 12 : std::string tileName;
3295 :
3296 : // First, try to extract from dataset name if it contains T##XXX pattern
3297 12 : std::string dsNameStr(CPLGetFilename(osRootDirectoryName.c_str()));
3298 6 : const size_t tilePos = dsNameStr.find("_T");
3299 6 : if (tilePos != std::string::npos && tilePos + 6 < dsNameStr.length())
3300 : {
3301 1 : tileName = dsNameStr.substr(tilePos + 1, 6); // Extract T##XXX
3302 1 : CPLDebugOnly(ZARR_DEBUG_KEY,
3303 : "Extracted tile name from dataset name: %s",
3304 : tileName.c_str());
3305 : }
3306 :
3307 : // Also check in STAC discovery metadata
3308 6 : if (tileName.empty() && stacDiscovery.IsValid())
3309 : {
3310 : const CPLJSONObject &properties =
3311 8 : stacDiscovery.GetObj("properties");
3312 4 : if (properties.IsValid())
3313 : {
3314 8 : tileName = properties.GetString(
3315 : "s2:mgrs_tile",
3316 8 : properties.GetString("mgrs_tile",
3317 12 : properties.GetString("tile_id", "")));
3318 4 : if (!tileName.empty())
3319 : {
3320 3 : CPLDebug("EOPFZARR",
3321 : "Found tile name in STAC properties: %s",
3322 : tileName.c_str());
3323 : }
3324 : }
3325 : }
3326 :
3327 : // Parse tile name to get EPSG code (T##XXX -> UTM Zone ## North/South)
3328 6 : if (!tileName.empty() && tileName.length() >= 3 && tileName[0] == 'T')
3329 : {
3330 : // Extract zone number (characters 1-2)
3331 8 : const std::string zoneStr = tileName.substr(1, 2);
3332 4 : const int zone = std::atoi(zoneStr.c_str());
3333 :
3334 4 : if (zone >= 1 && zone <= 60)
3335 : {
3336 : // Determine hemisphere from the third character
3337 : const char hemisphere =
3338 4 : tileName.length() > 3 ? tileName[3] : 'N';
3339 :
3340 : // For Sentinel-2, assume Northern hemisphere unless explicitly Southern
3341 : // Most Sentinel-2 data is Northern hemisphere
3342 : // Cf https://en.wikipedia.org/wiki/Military_Grid_Reference_System#Grid_zone_designation
3343 4 : const bool isNorth = (hemisphere >= 'N' && hemisphere <= 'X');
3344 :
3345 4 : nEPSGCode = isNorth ? (32600 + zone) : (32700 + zone);
3346 4 : CPLDebugOnly(ZARR_DEBUG_KEY,
3347 : "Inferred EPSG %d from Sentinel-2 tile %s (zone "
3348 : "%d, %s hemisphere)",
3349 : nEPSGCode, tileName.c_str(), zone,
3350 : isNorth ? "North" : "South");
3351 : }
3352 : }
3353 : }
3354 :
3355 13 : if (nEPSGCode > 0)
3356 : {
3357 11 : poSRS = std::make_shared<OGRSpatialReference>();
3358 11 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3359 11 : if (poSRS->importFromEPSG(nEPSGCode) == OGRERR_NONE)
3360 : {
3361 11 : return;
3362 : }
3363 0 : poSRS.reset();
3364 : }
3365 :
3366 : // Look for WKT
3367 4 : std::string wkt = obj.GetString("spatial_ref", "");
3368 2 : if (wkt.empty() && stacDiscovery.IsValid())
3369 : {
3370 2 : const CPLJSONObject &properties = stacDiscovery.GetObj("properties");
3371 1 : if (properties.IsValid())
3372 : {
3373 1 : wkt = properties.GetString("spatial_ref", "");
3374 : }
3375 : }
3376 2 : if (!wkt.empty())
3377 : {
3378 2 : poSRS = std::make_shared<OGRSpatialReference>();
3379 2 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3380 2 : if (poSRS->importFromWkt(wkt.c_str()) == OGRERR_NONE)
3381 : {
3382 2 : return;
3383 : }
3384 0 : poSRS.reset();
3385 : }
3386 : }
3387 :
3388 : /************************************************************************/
3389 : /* SetAttributes() */
3390 : /************************************************************************/
3391 :
3392 1910 : void ZarrArray::SetAttributes(const std::shared_ptr<ZarrGroupBase> &poGroup,
3393 : CPLJSONObject &oAttributes)
3394 : {
3395 5730 : const auto crs = oAttributes[CRS_ATTRIBUTE_NAME];
3396 1910 : std::shared_ptr<OGRSpatialReference> poSRS;
3397 1910 : if (crs.GetType() == CPLJSONObject::Type::Object)
3398 : {
3399 51 : for (const char *key : {"url", "wkt", "projjson"})
3400 : {
3401 102 : const auto item = crs[key];
3402 51 : if (item.IsValid())
3403 : {
3404 32 : poSRS = std::make_shared<OGRSpatialReference>();
3405 32 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3406 64 : if (poSRS->SetFromUserInput(
3407 64 : item.ToString().c_str(),
3408 : OGRSpatialReference::
3409 32 : SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
3410 : OGRERR_NONE)
3411 : {
3412 32 : m_oCRSAttribute = crs;
3413 32 : oAttributes.Delete(CRS_ATTRIBUTE_NAME);
3414 32 : break;
3415 : }
3416 0 : poSRS.reset();
3417 : }
3418 : }
3419 : }
3420 : else
3421 : {
3422 : // Check if SRS is using CF-1 conventions
3423 5634 : const auto gridMapping = oAttributes["grid_mapping"];
3424 1878 : if (gridMapping.GetType() == CPLJSONObject::Type::String)
3425 : {
3426 : const auto gridMappingArray =
3427 6 : poGroup->OpenMDArray(gridMapping.ToString());
3428 2 : if (gridMappingArray)
3429 : {
3430 2 : poSRS = std::make_shared<OGRSpatialReference>();
3431 2 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3432 4 : CPLStringList aosKeyValues;
3433 22 : for (const auto &poAttr : gridMappingArray->GetAttributes())
3434 : {
3435 20 : if (poAttr->GetDataType().GetClass() == GEDTC_STRING)
3436 : {
3437 4 : aosKeyValues.SetNameValue(poAttr->GetName().c_str(),
3438 8 : poAttr->ReadAsString());
3439 : }
3440 16 : else if (poAttr->GetDataType().GetClass() == GEDTC_NUMERIC)
3441 : {
3442 32 : std::string osVal;
3443 32 : for (double val : poAttr->ReadAsDoubleArray())
3444 : {
3445 16 : if (!osVal.empty())
3446 0 : osVal += ',';
3447 16 : osVal += CPLSPrintf("%.17g", val);
3448 : }
3449 16 : aosKeyValues.SetNameValue(poAttr->GetName().c_str(),
3450 32 : osVal.c_str());
3451 : }
3452 : }
3453 2 : if (poSRS->importFromCF1(aosKeyValues.List(), nullptr) !=
3454 : OGRERR_NONE)
3455 : {
3456 0 : poSRS.reset();
3457 : }
3458 : }
3459 : }
3460 : }
3461 :
3462 : // For EOPF Sentinel Zarr Samples Service datasets, read attributes from
3463 : // the STAC Proj extension attributes to get the CRS.
3464 : // There is also partly intersection with https://github.com/zarr-conventions/geo-proj
3465 : // For zarr-conventions/geo-proj and zarr-conventions/spatial, try first
3466 : // at array level, then parent and finally grandparent.
3467 :
3468 3820 : auto poRootGroup = std::dynamic_pointer_cast<ZarrGroupBase>(GetRootGroup());
3469 :
3470 3820 : std::vector<const ZarrAttributeGroup *> apoAttrGroup;
3471 :
3472 1910 : ZarrAttributeGroup oThisAttrGroup(std::string(),
3473 3820 : /* bContainerIsGroup = */ false);
3474 1910 : oThisAttrGroup.Init(oAttributes, /* bUpdatable=*/false);
3475 :
3476 1910 : apoAttrGroup.push_back(&oThisAttrGroup);
3477 1910 : if (GetDimensionCount() >= 2)
3478 : {
3479 1389 : apoAttrGroup.push_back(&(poGroup->GetAttributeGroup()));
3480 : // Only use root group to detect conventions
3481 1389 : if (poRootGroup)
3482 1389 : apoAttrGroup.push_back(&(poRootGroup->GetAttributeGroup()));
3483 : }
3484 :
3485 : // Look for declaration of geo-proj and spatial conventions
3486 1910 : bool bFoundSpatialUUID = false;
3487 1910 : bool bFoundProjUUID = false;
3488 6419 : for (const ZarrAttributeGroup *poAttrGroup : apoAttrGroup)
3489 : {
3490 : const auto poAttrZarrConventions =
3491 9332 : poAttrGroup->GetAttribute("zarr_conventions");
3492 4666 : if (poAttrZarrConventions)
3493 : {
3494 : const char *pszZarrConventions =
3495 157 : poAttrZarrConventions->ReadAsString();
3496 157 : if (pszZarrConventions)
3497 : {
3498 314 : CPLJSONDocument oDoc;
3499 157 : if (oDoc.LoadMemory(pszZarrConventions))
3500 : {
3501 314 : const auto oZarrConventions = oDoc.GetRoot();
3502 157 : if (oZarrConventions.GetType() ==
3503 : CPLJSONObject::Type::Array)
3504 : {
3505 : const auto oZarrConventionsArray =
3506 157 : oZarrConventions.ToArray();
3507 :
3508 : const auto hasSpatialUUIDLambda =
3509 214 : [](const CPLJSONObject &obj)
3510 : {
3511 214 : constexpr const char *SPATIAL_UUID =
3512 : "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4";
3513 214 : return obj.GetString("uuid") == SPATIAL_UUID;
3514 : };
3515 : bFoundSpatialUUID =
3516 314 : std::find_if(oZarrConventionsArray.begin(),
3517 314 : oZarrConventionsArray.end(),
3518 157 : hasSpatialUUIDLambda) !=
3519 314 : oZarrConventionsArray.end();
3520 :
3521 : const auto hasProjUUIDLambda =
3522 209 : [](const CPLJSONObject &obj)
3523 : {
3524 209 : constexpr const char *PROJ_UUID =
3525 : "f17cb550-5864-4468-aeb7-f3180cfb622f";
3526 209 : return obj.GetString("uuid") == PROJ_UUID;
3527 : };
3528 : bFoundProjUUID =
3529 314 : std::find_if(oZarrConventionsArray.begin(),
3530 314 : oZarrConventionsArray.end(),
3531 157 : hasProjUUIDLambda) !=
3532 314 : oZarrConventionsArray.end();
3533 : }
3534 : }
3535 : }
3536 157 : break;
3537 : }
3538 : }
3539 :
3540 : // If there is neither spatial nor geo-proj, just consider the current array
3541 : // for EOPF Sentinel Zarr Samples Service datasets
3542 1910 : if (!bFoundSpatialUUID && !bFoundProjUUID)
3543 1852 : apoAttrGroup.resize(1);
3544 58 : else if (apoAttrGroup.size() == 3)
3545 58 : apoAttrGroup.resize(2);
3546 :
3547 1910 : bool bAxisAssigned = false;
3548 3878 : for (const ZarrAttributeGroup *poAttrGroup : apoAttrGroup)
3549 : {
3550 1968 : if (!poSRS)
3551 : {
3552 1931 : ParseProjCRS(poAttrGroup, oAttributes, bFoundProjUUID, poSRS);
3553 : }
3554 :
3555 1968 : if (GetDimensionCount() >= 2 && bFoundSpatialUUID)
3556 : {
3557 116 : const bool bAxisAssignedBefore = bAxisAssigned;
3558 116 : ParseSpatialConventions(m_poSharedResource, poAttrGroup,
3559 : oAttributes, poSRS, bAxisAssigned,
3560 116 : m_aoDims);
3561 116 : if (bAxisAssigned && !bAxisAssignedBefore)
3562 5 : SetSRS(poSRS);
3563 :
3564 : // Note: we ignore EOPF Sentinel Zarr Samples Service "proj:transform"
3565 : // attribute, as we don't need to
3566 : // use it since the x and y dimensions are already associated with a
3567 : // 1-dimensional array with the values.
3568 : }
3569 : }
3570 :
3571 1910 : if (!poSRS && poRootGroup && oAttributes.GetObj("_eopf_attrs").IsValid())
3572 : {
3573 13 : DetectSRSFromEOPFSampleServiceMetadata(
3574 13 : m_poSharedResource->GetRootDirectoryName(), poRootGroup, poSRS);
3575 : }
3576 :
3577 1910 : if (poSRS && !bAxisAssigned)
3578 : {
3579 49 : int iDimX = 0;
3580 49 : int iDimY = 0;
3581 49 : int iCount = 1;
3582 147 : for (const auto &poDim : GetDimensions())
3583 : {
3584 98 : if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X)
3585 2 : iDimX = iCount;
3586 96 : else if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y)
3587 2 : iDimY = iCount;
3588 98 : iCount++;
3589 : }
3590 49 : if ((iDimX == 0 || iDimY == 0) && GetDimensionCount() >= 2)
3591 : {
3592 47 : iDimX = static_cast<int>(GetDimensionCount());
3593 47 : iDimY = iDimX - 1;
3594 : }
3595 49 : if (iDimX > 0 && iDimY > 0)
3596 : {
3597 49 : const auto &oMapping = poSRS->GetDataAxisToSRSAxisMapping();
3598 140 : if (oMapping == std::vector<int>{2, 1} ||
3599 91 : oMapping == std::vector<int>{2, 1, 3})
3600 7 : poSRS->SetDataAxisToSRSAxisMapping({iDimY, iDimX});
3601 85 : else if (oMapping == std::vector<int>{1, 2} ||
3602 43 : oMapping == std::vector<int>{1, 2, 3})
3603 42 : poSRS->SetDataAxisToSRSAxisMapping({iDimX, iDimY});
3604 : }
3605 :
3606 49 : SetSRS(poSRS);
3607 : }
3608 :
3609 5730 : const auto unit = oAttributes[CF_UNITS];
3610 1910 : if (unit.GetType() == CPLJSONObject::Type::String)
3611 : {
3612 165 : std::string osUnit = unit.ToString();
3613 55 : oAttributes.Delete(CF_UNITS);
3614 55 : RegisterUnit(osUnit);
3615 : }
3616 :
3617 5730 : const auto offset = oAttributes[CF_ADD_OFFSET];
3618 1910 : const auto offsetType = offset.GetType();
3619 1910 : if (offsetType == CPLJSONObject::Type::Integer ||
3620 1910 : offsetType == CPLJSONObject::Type::Long ||
3621 : offsetType == CPLJSONObject::Type::Double)
3622 : {
3623 3 : double dfOffset = offset.ToDouble();
3624 3 : oAttributes.Delete(CF_ADD_OFFSET);
3625 3 : RegisterOffset(dfOffset);
3626 : }
3627 :
3628 5730 : const auto scale = oAttributes[CF_SCALE_FACTOR];
3629 1910 : const auto scaleType = scale.GetType();
3630 1910 : if (scaleType == CPLJSONObject::Type::Integer ||
3631 1910 : scaleType == CPLJSONObject::Type::Long ||
3632 : scaleType == CPLJSONObject::Type::Double)
3633 : {
3634 3 : double dfScale = scale.ToDouble();
3635 3 : oAttributes.Delete(CF_SCALE_FACTOR);
3636 3 : RegisterScale(dfScale);
3637 : }
3638 :
3639 1910 : m_oAttrGroup.Init(oAttributes, m_bUpdatable);
3640 1910 : }
3641 :
3642 : /************************************************************************/
3643 : /* SetStatistics() */
3644 : /************************************************************************/
3645 :
3646 1 : bool ZarrArray::SetStatistics(bool bApproxStats, double dfMin, double dfMax,
3647 : double dfMean, double dfStdDev,
3648 : GUInt64 nValidCount, CSLConstList papszOptions)
3649 : {
3650 2 : if (!bApproxStats && m_bUpdatable &&
3651 1 : CPLTestBool(
3652 : CSLFetchNameValueDef(papszOptions, "UPDATE_METADATA", "NO")))
3653 : {
3654 3 : auto poAttr = GetAttribute("actual_range");
3655 1 : if (!poAttr)
3656 : {
3657 : poAttr =
3658 1 : CreateAttribute("actual_range", {2}, GetDataType(), nullptr);
3659 : }
3660 1 : if (poAttr)
3661 : {
3662 2 : std::vector<GUInt64> startIdx = {0};
3663 2 : std::vector<size_t> count = {2};
3664 1 : std::vector<double> values = {dfMin, dfMax};
3665 2 : poAttr->Write(startIdx.data(), count.data(), nullptr, nullptr,
3666 2 : GDALExtendedDataType::Create(GDT_Float64),
3667 1 : values.data(), nullptr, 0);
3668 : }
3669 : }
3670 1 : return GDALPamMDArray::SetStatistics(bApproxStats, dfMin, dfMax, dfMean,
3671 1 : dfStdDev, nValidCount, papszOptions);
3672 : }
3673 :
3674 : /************************************************************************/
3675 : /* ZarrArray::IsBlockMissingFromCacheInfo() */
3676 : /************************************************************************/
3677 :
3678 34127 : bool ZarrArray::IsBlockMissingFromCacheInfo(const std::string &osFilename,
3679 : const uint64_t *blockIndices) const
3680 : {
3681 34127 : CPL_IGNORE_RET_VAL(osFilename);
3682 68254 : auto poBlockPresenceArray = OpenBlockPresenceCache(false);
3683 34127 : if (poBlockPresenceArray)
3684 : {
3685 72 : std::vector<GUInt64> anBlockIdx(m_aoDims.size());
3686 72 : const std::vector<size_t> anCount(m_aoDims.size(), 1);
3687 72 : const std::vector<GInt64> anArrayStep(m_aoDims.size(), 0);
3688 72 : const std::vector<GPtrDiff_t> anBufferStride(m_aoDims.size(), 0);
3689 72 : const auto eByteDT = GDALExtendedDataType::Create(GDT_UInt8);
3690 216 : for (size_t i = 0; i < m_aoDims.size(); ++i)
3691 : {
3692 144 : anBlockIdx[i] = static_cast<GUInt64>(blockIndices[i]);
3693 : }
3694 72 : GByte byValue = 0;
3695 144 : if (poBlockPresenceArray->Read(
3696 72 : anBlockIdx.data(), anCount.data(), anArrayStep.data(),
3697 144 : anBufferStride.data(), eByteDT, &byValue) &&
3698 72 : byValue == 0)
3699 : {
3700 52 : CPLDebugOnly(ZARR_DEBUG_KEY, "Block %s missing (=nodata)",
3701 : osFilename.c_str());
3702 52 : return true;
3703 : }
3704 : }
3705 34075 : return false;
3706 : }
3707 :
3708 : /************************************************************************/
3709 : /* ZarrArray::GetRawBlockInfo() */
3710 : /************************************************************************/
3711 :
3712 16 : bool ZarrArray::GetRawBlockInfo(const uint64_t *panBlockCoordinates,
3713 : GDALMDArrayRawBlockInfo &info) const
3714 : {
3715 16 : info.clear();
3716 33 : for (size_t i = 0; i < m_anInnerBlockSize.size(); ++i)
3717 : {
3718 20 : const auto nBlockSize = m_anInnerBlockSize[i];
3719 : const auto nBlockCount =
3720 20 : cpl::div_round_up(m_aoDims[i]->GetSize(), nBlockSize);
3721 20 : if (panBlockCoordinates[i] >= nBlockCount)
3722 : {
3723 3 : CPLError(CE_Failure, CPLE_AppDefined,
3724 : "GetRawBlockInfo() failed: array %s: "
3725 : "invalid block coordinate (%u) for dimension %u",
3726 3 : GetName().c_str(),
3727 3 : static_cast<unsigned>(panBlockCoordinates[i]),
3728 : static_cast<unsigned>(i));
3729 3 : return false;
3730 : }
3731 : }
3732 :
3733 26 : std::vector<uint64_t> anOuterBlockIndices;
3734 30 : for (size_t i = 0; i < m_anCountInnerBlockInOuter.size(); ++i)
3735 : {
3736 17 : anOuterBlockIndices.push_back(panBlockCoordinates[i] /
3737 17 : m_anCountInnerBlockInOuter[i]);
3738 : }
3739 :
3740 26 : std::string osFilename = BuildChunkFilename(anOuterBlockIndices.data());
3741 :
3742 : // For network file systems, get the streaming version of the filename,
3743 : // as we don't need arbitrary seeking in the file
3744 13 : osFilename = VSIFileManager::GetHandler(osFilename.c_str())
3745 13 : ->GetStreamingFilename(osFilename);
3746 : {
3747 13 : std::lock_guard<std::mutex> oLock(m_oMutex);
3748 13 : if (IsBlockMissingFromCacheInfo(osFilename, panBlockCoordinates))
3749 0 : return true;
3750 : }
3751 :
3752 13 : VSILFILE *fp = nullptr;
3753 : // This is the number of files returned in a S3 directory listing operation
3754 13 : const char *const apszOpenOptions[] = {"IGNORE_FILENAME_RESTRICTIONS=YES",
3755 : nullptr};
3756 13 : const auto nErrorBefore = CPLGetErrorCounter();
3757 : {
3758 : // Avoid issuing ReadDir() when a lot of files are expected
3759 : CPLConfigOptionSetter optionSetter("GDAL_DISABLE_READDIR_ON_OPEN",
3760 13 : "YES", true);
3761 13 : fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
3762 : }
3763 13 : if (fp == nullptr)
3764 : {
3765 1 : if (nErrorBefore != CPLGetErrorCounter())
3766 : {
3767 0 : return false;
3768 : }
3769 : else
3770 : {
3771 : // Missing files are OK and indicate nodata_value
3772 1 : return true;
3773 : }
3774 : }
3775 12 : VSIFSeekL(fp, 0, SEEK_END);
3776 12 : const auto nFileSize = VSIFTellL(fp);
3777 12 : VSIFCloseL(fp);
3778 :
3779 : // For Kerchunk files, get information on the actual location
3780 : const CPLStringList aosMetadata(
3781 12 : VSIGetFileMetadata(osFilename.c_str(), "CHUNK_INFO", nullptr));
3782 12 : if (!aosMetadata.empty())
3783 : {
3784 2 : const char *pszFilename = aosMetadata.FetchNameValue("FILENAME");
3785 2 : if (pszFilename)
3786 1 : info.pszFilename = CPLStrdup(pszFilename);
3787 2 : info.nOffset = std::strtoull(
3788 : aosMetadata.FetchNameValueDef("OFFSET", "0"), nullptr, 10);
3789 2 : info.nSize = std::strtoull(aosMetadata.FetchNameValueDef("SIZE", "0"),
3790 : nullptr, 10);
3791 2 : const char *pszBase64 = aosMetadata.FetchNameValue("BASE64");
3792 2 : if (pszBase64)
3793 : {
3794 1 : const size_t nSizeBase64 = strlen(pszBase64) + 1;
3795 1 : info.pabyInlineData = static_cast<GByte *>(CPLMalloc(nSizeBase64));
3796 1 : memcpy(info.pabyInlineData, pszBase64, nSizeBase64);
3797 : const int nDecodedSize =
3798 1 : CPLBase64DecodeInPlace(info.pabyInlineData);
3799 1 : CPLAssert(static_cast<size_t>(nDecodedSize) ==
3800 : static_cast<size_t>(info.nSize));
3801 1 : CPL_IGNORE_RET_VAL(nDecodedSize);
3802 : }
3803 : }
3804 : else
3805 : {
3806 10 : info.pszFilename = CPLStrdup(osFilename.c_str());
3807 10 : info.nOffset = 0;
3808 10 : info.nSize = nFileSize;
3809 : }
3810 :
3811 12 : info.papszInfo = CSLDuplicate(GetRawBlockInfoInfo().List());
3812 :
3813 12 : return true;
3814 : }
3815 :
3816 : /************************************************************************/
3817 : /* ZarrArray::GetParentGroup() */
3818 : /************************************************************************/
3819 :
3820 67 : std::shared_ptr<ZarrGroupBase> ZarrArray::GetParentGroup() const
3821 : {
3822 67 : std::shared_ptr<ZarrGroupBase> poGroup = m_poParent.lock();
3823 67 : if (!poGroup)
3824 : {
3825 12 : if (auto poRootGroup = m_poSharedResource->GetRootGroup())
3826 : {
3827 6 : const auto nPos = m_osFullName.rfind('/');
3828 6 : if (nPos == 0)
3829 : {
3830 2 : poGroup = std::dynamic_pointer_cast<ZarrGroupBase>(poRootGroup);
3831 : }
3832 4 : else if (nPos != std::string::npos)
3833 : {
3834 8 : poGroup = std::dynamic_pointer_cast<ZarrGroupBase>(
3835 12 : poRootGroup->OpenGroupFromFullname(
3836 12 : m_osFullName.substr(0, nPos)));
3837 : }
3838 : }
3839 : }
3840 67 : return poGroup;
3841 : }
3842 :
3843 : /************************************************************************/
3844 : /* ZarrArray::IsRegularlySpaced() */
3845 : /************************************************************************/
3846 :
3847 : // Process-level LRU cache for coordinate array regularity results.
3848 : // Keyed by root directory + array full name.
3849 : // Avoids redundant HTTP reads for immutable cloud-hosted coordinate arrays.
3850 : // Thread-safe: lru11::Cache with std::mutex handles locking internally.
3851 :
3852 : struct CoordCacheEntry
3853 : {
3854 : bool bIsRegular;
3855 : double dfStart;
3856 : double dfIncrement;
3857 : };
3858 :
3859 : static lru11::Cache<std::string, CoordCacheEntry, std::mutex> g_oCoordCache{
3860 : 128};
3861 :
3862 4 : void ZarrClearCoordinateCache()
3863 : {
3864 4 : g_oCoordCache.clear();
3865 4 : }
3866 :
3867 102 : bool ZarrArray::IsRegularlySpaced(double &dfStart, double &dfIncrement) const
3868 : {
3869 : // Only cache 1D coordinate arrays (the ones that trigger HTTP reads)
3870 102 : if (GetDimensionCount() != 1)
3871 0 : return GDALMDArray::IsRegularlySpaced(dfStart, dfIncrement);
3872 :
3873 102 : const std::string &osKey = GetFilename();
3874 :
3875 : CoordCacheEntry entry;
3876 102 : if (g_oCoordCache.tryGet(osKey, entry))
3877 : {
3878 6 : CPLDebugOnly("ZARR", "IsRegularlySpaced cache hit for %s",
3879 : osKey.c_str());
3880 6 : dfStart = entry.dfStart;
3881 6 : dfIncrement = entry.dfIncrement;
3882 6 : return entry.bIsRegular;
3883 : }
3884 :
3885 : // Cache miss: perform the full coordinate read
3886 96 : const bool bResult = GDALMDArray::IsRegularlySpaced(dfStart, dfIncrement);
3887 :
3888 96 : g_oCoordCache.insert(osKey, {bResult, dfStart, dfIncrement});
3889 :
3890 96 : CPLDebugOnly("ZARR", "IsRegularlySpaced cached for %s: %s", osKey.c_str(),
3891 : bResult ? "regular" : "irregular");
3892 :
3893 96 : return bResult;
3894 : }
|