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 : #include "zarrdrivercore.h"
15 :
16 : #include "cpl_minixml.h"
17 :
18 : #include <algorithm>
19 : #include <cassert>
20 : #include <limits>
21 :
22 : #ifdef HAVE_BLOSC
23 : #include <blosc.h>
24 : #endif
25 :
26 : /************************************************************************/
27 : /* ZarrDataset() */
28 : /************************************************************************/
29 :
30 997 : ZarrDataset::ZarrDataset(const std::shared_ptr<GDALGroup> &poRootGroup)
31 997 : : m_poRootGroup(poRootGroup)
32 : {
33 997 : }
34 :
35 : /************************************************************************/
36 : /* OpenMultidim() */
37 : /************************************************************************/
38 :
39 677 : GDALDataset *ZarrDataset::OpenMultidim(const char *pszFilename,
40 : bool bUpdateMode,
41 : CSLConstList papszOpenOptionsIn)
42 : {
43 1354 : CPLString osFilename(pszFilename);
44 677 : if (osFilename.back() == '/')
45 0 : osFilename.pop_back();
46 :
47 1354 : auto poSharedResource = ZarrSharedResource::Create(osFilename, bUpdateMode);
48 677 : poSharedResource->SetOpenOptions(papszOpenOptionsIn);
49 :
50 1354 : auto poRG = poSharedResource->GetRootGroup();
51 677 : if (!poRG)
52 77 : return nullptr;
53 600 : return new ZarrDataset(poRG);
54 : }
55 :
56 : /************************************************************************/
57 : /* ExploreGroup() */
58 : /************************************************************************/
59 :
60 73 : static bool ExploreGroup(const std::shared_ptr<GDALGroup> &poGroup,
61 : std::vector<std::string> &aosArrays, int nRecCount)
62 : {
63 73 : if (nRecCount == 32)
64 : {
65 0 : CPLError(CE_Failure, CPLE_NotSupported,
66 : "Too deep recursion level in ExploreGroup()");
67 0 : return false;
68 : }
69 146 : const auto aosGroupArrayNames = poGroup->GetMDArrayNames();
70 219 : for (const auto &osArrayName : aosGroupArrayNames)
71 : {
72 146 : std::string osArrayFullname = poGroup->GetFullName();
73 146 : if (osArrayName != "/")
74 : {
75 146 : if (osArrayFullname != "/")
76 1 : osArrayFullname += '/';
77 146 : osArrayFullname += osArrayName;
78 : }
79 146 : aosArrays.emplace_back(std::move(osArrayFullname));
80 146 : if (aosArrays.size() == 10000)
81 : {
82 0 : CPLError(CE_Failure, CPLE_NotSupported,
83 : "Too many arrays found by ExploreGroup()");
84 0 : return false;
85 : }
86 : }
87 :
88 146 : const auto aosSubGroups = poGroup->GetGroupNames();
89 75 : for (const auto &osSubGroup : aosSubGroups)
90 : {
91 2 : const auto poSubGroup = poGroup->OpenGroup(osSubGroup);
92 2 : if (poSubGroup)
93 : {
94 2 : if (!ExploreGroup(poSubGroup, aosArrays, nRecCount + 1))
95 0 : return false;
96 : }
97 : }
98 73 : return true;
99 : }
100 :
101 : /************************************************************************/
102 : /* GetMetadataItem() */
103 : /************************************************************************/
104 :
105 43 : const char *ZarrDataset::GetMetadataItem(const char *pszName,
106 : const char *pszDomain)
107 : {
108 43 : if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
109 0 : return m_aosSubdatasets.FetchNameValue(pszName);
110 43 : return nullptr;
111 : }
112 :
113 : /************************************************************************/
114 : /* GetMetadata() */
115 : /************************************************************************/
116 :
117 17 : char **ZarrDataset::GetMetadata(const char *pszDomain)
118 : {
119 17 : if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
120 4 : return m_aosSubdatasets.List();
121 13 : return nullptr;
122 : }
123 :
124 : /************************************************************************/
125 : /* GetXYDimensionIndices() */
126 : /************************************************************************/
127 :
128 95 : static void GetXYDimensionIndices(const std::shared_ptr<GDALMDArray> &poArray,
129 : const GDALOpenInfo *poOpenInfo, size_t &iXDim,
130 : size_t &iYDim)
131 : {
132 95 : const size_t nDims = poArray->GetDimensionCount();
133 95 : iYDim = nDims >= 2 ? nDims - 2 : 0;
134 95 : iXDim = nDims >= 2 ? nDims - 1 : 0;
135 :
136 95 : if (nDims >= 2)
137 : {
138 : const char *pszDimX =
139 85 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "DIM_X");
140 : const char *pszDimY =
141 85 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "DIM_Y");
142 85 : bool bFoundX = false;
143 85 : bool bFoundY = false;
144 85 : const auto &apoDims = poArray->GetDimensions();
145 304 : for (size_t i = 0; i < nDims; ++i)
146 : {
147 219 : if (pszDimX && apoDims[i]->GetName() == pszDimX)
148 : {
149 1 : bFoundX = true;
150 1 : iXDim = i;
151 : }
152 218 : else if (pszDimY && apoDims[i]->GetName() == pszDimY)
153 : {
154 1 : bFoundY = true;
155 1 : iYDim = i;
156 : }
157 629 : else if (!pszDimX &&
158 412 : (apoDims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X ||
159 205 : apoDims[i]->GetName() == "X"))
160 51 : iXDim = i;
161 476 : else if (!pszDimY &&
162 310 : (apoDims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y ||
163 154 : apoDims[i]->GetName() == "Y"))
164 51 : iYDim = i;
165 : }
166 85 : if (pszDimX)
167 : {
168 4 : if (!bFoundX && CPLGetValueType(pszDimX) == CPL_VALUE_INTEGER)
169 : {
170 2 : const int nTmp = atoi(pszDimX);
171 2 : if (nTmp >= 0 && nTmp <= static_cast<int>(nDims))
172 : {
173 2 : iXDim = nTmp;
174 2 : bFoundX = true;
175 : }
176 : }
177 4 : if (!bFoundX)
178 : {
179 1 : CPLError(CE_Warning, CPLE_AppDefined,
180 : "Cannot find dimension DIM_X=%s", pszDimX);
181 : }
182 : }
183 85 : if (pszDimY)
184 : {
185 4 : if (!bFoundY && CPLGetValueType(pszDimY) == CPL_VALUE_INTEGER)
186 : {
187 2 : const int nTmp = atoi(pszDimY);
188 2 : if (nTmp >= 0 && nTmp <= static_cast<int>(nDims))
189 : {
190 2 : iYDim = nTmp;
191 2 : bFoundY = true;
192 : }
193 : }
194 4 : if (!bFoundY)
195 : {
196 1 : CPLError(CE_Warning, CPLE_AppDefined,
197 : "Cannot find dimension DIM_Y=%s", pszDimY);
198 : }
199 : }
200 : }
201 95 : }
202 :
203 : /************************************************************************/
204 : /* GetExtraDimSampleCount() */
205 : /************************************************************************/
206 :
207 : static uint64_t
208 31 : GetExtraDimSampleCount(const std::shared_ptr<GDALMDArray> &poArray,
209 : size_t iXDim, size_t iYDim)
210 : {
211 31 : uint64_t nExtraDimSamples = 1;
212 31 : const auto &apoDims = poArray->GetDimensions();
213 126 : for (size_t i = 0; i < apoDims.size(); ++i)
214 : {
215 95 : if (i != iXDim && i != iYDim)
216 35 : nExtraDimSamples *= apoDims[i]->GetSize();
217 : }
218 31 : return nExtraDimSamples;
219 : }
220 :
221 : /************************************************************************/
222 : /* Open() */
223 : /************************************************************************/
224 :
225 676 : GDALDataset *ZarrDataset::Open(GDALOpenInfo *poOpenInfo)
226 : {
227 676 : if (!ZARRDriverIdentify(poOpenInfo))
228 : {
229 0 : return nullptr;
230 : }
231 :
232 1352 : CPLString osFilename(poOpenInfo->pszFilename);
233 1352 : CPLString osArrayOfInterest;
234 1352 : std::vector<uint64_t> anExtraDimIndices;
235 676 : if (STARTS_WITH(poOpenInfo->pszFilename, "ZARR:"))
236 : {
237 : const CPLStringList aosTokens(CSLTokenizeString2(
238 23 : poOpenInfo->pszFilename, ":", CSLT_HONOURSTRINGS));
239 23 : if (aosTokens.size() < 2)
240 0 : return nullptr;
241 23 : osFilename = aosTokens[1];
242 23 : std::string osErrorMsg;
243 23 : if (osFilename == "http" || osFilename == "https")
244 : {
245 : osErrorMsg = "There is likely a quoting error of the whole "
246 : "connection string, and the filename should "
247 1 : "likely be prefixed with /vsicurl/";
248 : }
249 44 : else if (osFilename == "/vsicurl/http" ||
250 22 : osFilename == "/vsicurl/https")
251 : {
252 : osErrorMsg = "There is likely a quoting error of the whole "
253 1 : "connection string.";
254 : }
255 42 : else if (STARTS_WITH(osFilename.c_str(), "http://") ||
256 21 : STARTS_WITH(osFilename.c_str(), "https://"))
257 : {
258 : osErrorMsg =
259 1 : "The filename should likely be prefixed with /vsicurl/";
260 : }
261 23 : if (!osErrorMsg.empty())
262 : {
263 3 : CPLError(CE_Failure, CPLE_AppDefined, "%s", osErrorMsg.c_str());
264 3 : return nullptr;
265 : }
266 20 : if (aosTokens.size() >= 3)
267 : {
268 17 : osArrayOfInterest = aosTokens[2];
269 35 : for (int i = 3; i < aosTokens.size(); ++i)
270 : {
271 18 : anExtraDimIndices.push_back(
272 18 : static_cast<uint64_t>(CPLAtoGIntBig(aosTokens[i])));
273 : }
274 : }
275 : }
276 :
277 : auto poDSMultiDim = std::unique_ptr<GDALDataset>(
278 673 : OpenMultidim(osFilename.c_str(), poOpenInfo->eAccess == GA_Update,
279 1346 : poOpenInfo->papszOpenOptions));
280 1269 : if (poDSMultiDim == nullptr ||
281 596 : (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER) != 0)
282 : {
283 585 : return poDSMultiDim.release();
284 : }
285 :
286 176 : auto poRG = poDSMultiDim->GetRootGroup();
287 :
288 176 : auto poDS = std::make_unique<ZarrDataset>(nullptr);
289 88 : std::shared_ptr<GDALMDArray> poMainArray;
290 176 : std::vector<std::string> aosArrays;
291 176 : std::string osMainArray;
292 88 : const bool bMultiband = CPLTestBool(
293 88 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "MULTIBAND", "YES"));
294 88 : size_t iXDim = 0;
295 88 : size_t iYDim = 0;
296 :
297 88 : if (!osArrayOfInterest.empty())
298 : {
299 17 : poMainArray = osArrayOfInterest == "/"
300 34 : ? poRG->OpenMDArray("/")
301 17 : : poRG->OpenMDArrayFromFullname(osArrayOfInterest);
302 17 : if (poMainArray == nullptr)
303 1 : return nullptr;
304 16 : GetXYDimensionIndices(poMainArray, poOpenInfo, iXDim, iYDim);
305 :
306 16 : if (poMainArray->GetDimensionCount() > 2)
307 : {
308 11 : if (anExtraDimIndices.empty())
309 : {
310 : const uint64_t nExtraDimSamples =
311 1 : GetExtraDimSampleCount(poMainArray, iXDim, iYDim);
312 1 : if (bMultiband)
313 : {
314 0 : if (nExtraDimSamples > 65536) // arbitrary limit
315 : {
316 0 : if (poMainArray->GetDimensionCount() == 3)
317 : {
318 0 : CPLError(CE_Warning, CPLE_AppDefined,
319 : "Too many samples along the > 2D "
320 : "dimensions of %s. "
321 : "Use ZARR:\"%s\":%s:{i} syntax",
322 : osArrayOfInterest.c_str(),
323 : osFilename.c_str(),
324 : osArrayOfInterest.c_str());
325 : }
326 : else
327 : {
328 0 : CPLError(CE_Warning, CPLE_AppDefined,
329 : "Too many samples along the > 2D "
330 : "dimensions of %s. "
331 : "Use ZARR:\"%s\":%s:{i}:{j} syntax",
332 : osArrayOfInterest.c_str(),
333 : osFilename.c_str(),
334 : osArrayOfInterest.c_str());
335 : }
336 0 : return nullptr;
337 : }
338 : }
339 1 : else if (nExtraDimSamples != 1)
340 : {
341 1 : CPLError(CE_Failure, CPLE_AppDefined,
342 : "Indices of extra dimensions must be specified");
343 1 : return nullptr;
344 : }
345 : }
346 10 : else if (anExtraDimIndices.size() !=
347 10 : poMainArray->GetDimensionCount() - 2)
348 : {
349 1 : CPLError(CE_Failure, CPLE_AppDefined,
350 : "Wrong number of indices of extra dimensions");
351 1 : return nullptr;
352 : }
353 : else
354 : {
355 23 : for (const auto idx : anExtraDimIndices)
356 : {
357 15 : poMainArray = poMainArray->at(idx);
358 15 : if (poMainArray == nullptr)
359 1 : return nullptr;
360 : }
361 8 : GetXYDimensionIndices(poMainArray, poOpenInfo, iXDim, iYDim);
362 : }
363 : }
364 5 : else if (!anExtraDimIndices.empty())
365 : {
366 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unexpected extra indices");
367 1 : return nullptr;
368 : }
369 : }
370 : else
371 : {
372 71 : ExploreGroup(poRG, aosArrays, 0);
373 71 : if (aosArrays.empty())
374 0 : return nullptr;
375 :
376 71 : if (aosArrays.size() == 1)
377 : {
378 34 : poMainArray = poRG->OpenMDArrayFromFullname(aosArrays[0]);
379 34 : if (poMainArray)
380 34 : osMainArray = poMainArray->GetFullName();
381 : }
382 : else // at least 2 arrays
383 : {
384 149 : for (const auto &osArrayName : aosArrays)
385 : {
386 112 : auto poArray = poRG->OpenMDArrayFromFullname(osArrayName);
387 112 : if (poArray && poArray->GetDimensionCount() >= 2)
388 : {
389 37 : if (osMainArray.empty())
390 : {
391 37 : poMainArray = std::move(poArray);
392 37 : osMainArray = osArrayName;
393 : }
394 : else
395 : {
396 0 : poMainArray.reset();
397 0 : osMainArray.clear();
398 0 : break;
399 : }
400 : }
401 : }
402 : }
403 :
404 71 : if (poMainArray)
405 71 : GetXYDimensionIndices(poMainArray, poOpenInfo, iXDim, iYDim);
406 :
407 71 : int iCountSubDS = 1;
408 :
409 71 : if (poMainArray && poMainArray->GetDimensionCount() > 2)
410 : {
411 30 : const auto &apoDims = poMainArray->GetDimensions();
412 : const uint64_t nExtraDimSamples =
413 30 : GetExtraDimSampleCount(poMainArray, iXDim, iYDim);
414 30 : if (nExtraDimSamples > 65536) // arbitrary limit
415 : {
416 3 : if (apoDims.size() == 3)
417 : {
418 2 : CPLError(
419 : CE_Warning, CPLE_AppDefined,
420 : "Too many samples along the > 2D dimensions of %s. "
421 : "Use ZARR:\"%s\":%s:{i} syntax",
422 : osMainArray.c_str(), osFilename.c_str(),
423 : osMainArray.c_str());
424 : }
425 : else
426 : {
427 1 : CPLError(
428 : CE_Warning, CPLE_AppDefined,
429 : "Too many samples along the > 2D dimensions of %s. "
430 : "Use ZARR:\"%s\":%s:{i}:{j} syntax",
431 : osMainArray.c_str(), osFilename.c_str(),
432 : osMainArray.c_str());
433 : }
434 : }
435 27 : else if (nExtraDimSamples > 1 && bMultiband)
436 : {
437 : // nothing to do
438 : }
439 2 : else if (nExtraDimSamples > 1 && apoDims.size() == 3)
440 : {
441 3 : for (int i = 0; i < static_cast<int>(nExtraDimSamples); ++i)
442 : {
443 2 : poDS->m_aosSubdatasets.AddString(CPLSPrintf(
444 : "SUBDATASET_%d_NAME=ZARR:\"%s\":%s:%d", iCountSubDS,
445 2 : osFilename.c_str(), osMainArray.c_str(), i));
446 2 : poDS->m_aosSubdatasets.AddString(CPLSPrintf(
447 : "SUBDATASET_%d_DESC=Array %s at index %d of %s",
448 : iCountSubDS, osMainArray.c_str(), i,
449 2 : apoDims[0]->GetName().c_str()));
450 2 : ++iCountSubDS;
451 : }
452 : }
453 1 : else if (nExtraDimSamples > 1)
454 : {
455 1 : int nDimIdxI = 0;
456 1 : int nDimIdxJ = 0;
457 7 : for (int i = 0; i < static_cast<int>(nExtraDimSamples); ++i)
458 : {
459 6 : poDS->m_aosSubdatasets.AddString(
460 : CPLSPrintf("SUBDATASET_%d_NAME=ZARR:\"%s\":%s:%d:%d",
461 : iCountSubDS, osFilename.c_str(),
462 6 : osMainArray.c_str(), nDimIdxI, nDimIdxJ));
463 6 : poDS->m_aosSubdatasets.AddString(
464 : CPLSPrintf("SUBDATASET_%d_DESC=Array %s at "
465 : "index %d of %s and %d of %s",
466 : iCountSubDS, osMainArray.c_str(), nDimIdxI,
467 6 : apoDims[0]->GetName().c_str(), nDimIdxJ,
468 12 : apoDims[1]->GetName().c_str()));
469 6 : ++iCountSubDS;
470 6 : ++nDimIdxJ;
471 6 : if (nDimIdxJ == static_cast<int>(apoDims[1]->GetSize()))
472 : {
473 3 : nDimIdxJ = 0;
474 3 : ++nDimIdxI;
475 : }
476 : }
477 : }
478 : }
479 :
480 71 : if (aosArrays.size() >= 2)
481 : {
482 149 : for (size_t i = 0; i < aosArrays.size(); ++i)
483 : {
484 112 : poDS->m_aosSubdatasets.AddString(
485 : CPLSPrintf("SUBDATASET_%d_NAME=ZARR:\"%s\":%s", iCountSubDS,
486 112 : osFilename.c_str(), aosArrays[i].c_str()));
487 112 : poDS->m_aosSubdatasets.AddString(
488 : CPLSPrintf("SUBDATASET_%d_DESC=Array %s", iCountSubDS,
489 112 : aosArrays[i].c_str()));
490 112 : ++iCountSubDS;
491 : }
492 : }
493 : }
494 :
495 83 : if (poMainArray && (bMultiband || poMainArray->GetDimensionCount() <= 2))
496 : {
497 : // Pass papszOpenOptions for LOAD_EXTRA_DIM_METADATA_DELAY
498 : auto poNewDS =
499 79 : std::unique_ptr<GDALDataset>(poMainArray->AsClassicDataset(
500 158 : iXDim, iYDim, poRG, poOpenInfo->papszOpenOptions));
501 79 : if (!poNewDS)
502 3 : return nullptr;
503 :
504 76 : if (poMainArray->GetDimensionCount() >= 2)
505 : {
506 : // If we have 3 arrays, check that the 2 ones that are not the main
507 : // 2D array are indexing variables of its dimensions. If so, don't
508 : // expose them as subdatasets
509 67 : if (aosArrays.size() == 3)
510 : {
511 68 : std::vector<std::string> aosOtherArrays;
512 136 : for (size_t i = 0; i < aosArrays.size(); ++i)
513 : {
514 102 : if (aosArrays[i] != osMainArray)
515 : {
516 68 : aosOtherArrays.emplace_back(aosArrays[i]);
517 : }
518 : }
519 34 : bool bMatchFound[] = {false, false};
520 102 : for (int i = 0; i < 2; i++)
521 : {
522 : auto poIndexingVar =
523 68 : poMainArray->GetDimensions()[i == 0 ? iXDim : iYDim]
524 136 : ->GetIndexingVariable();
525 68 : if (poIndexingVar)
526 : {
527 99 : for (int j = 0; j < 2; j++)
528 : {
529 99 : if (aosOtherArrays[j] ==
530 99 : poIndexingVar->GetFullName())
531 : {
532 66 : bMatchFound[i] = true;
533 66 : break;
534 : }
535 : }
536 : }
537 : }
538 34 : if (bMatchFound[0] && bMatchFound[1])
539 : {
540 33 : poDS->m_aosSubdatasets.Clear();
541 : }
542 : }
543 : }
544 76 : if (!poDS->m_aosSubdatasets.empty())
545 : {
546 4 : poNewDS->SetMetadata(poDS->m_aosSubdatasets.List(), "SUBDATASETS");
547 : }
548 76 : return poNewDS.release();
549 : }
550 :
551 4 : return poDS.release();
552 : }
553 :
554 : /************************************************************************/
555 : /* ZarrDatasetDelete() */
556 : /************************************************************************/
557 :
558 4 : static CPLErr ZarrDatasetDelete(const char *pszFilename)
559 : {
560 4 : if (STARTS_WITH(pszFilename, "ZARR:"))
561 : {
562 0 : CPLError(CE_Failure, CPLE_AppDefined,
563 : "Delete() only supported on ZARR connection names "
564 : "not starting with the ZARR: prefix");
565 0 : return CE_Failure;
566 : }
567 4 : return VSIRmdirRecursive(pszFilename) == 0 ? CE_None : CE_Failure;
568 : }
569 :
570 : /************************************************************************/
571 : /* ZarrDatasetRename() */
572 : /************************************************************************/
573 :
574 2 : static CPLErr ZarrDatasetRename(const char *pszNewName, const char *pszOldName)
575 : {
576 2 : if (STARTS_WITH(pszNewName, "ZARR:") || STARTS_WITH(pszOldName, "ZARR:"))
577 : {
578 0 : CPLError(CE_Failure, CPLE_AppDefined,
579 : "Rename() only supported on ZARR connection names "
580 : "not starting with the ZARR: prefix");
581 0 : return CE_Failure;
582 : }
583 2 : return VSIRename(pszOldName, pszNewName) == 0 ? CE_None : CE_Failure;
584 : }
585 :
586 : /************************************************************************/
587 : /* ZarrDatasetCopyFiles() */
588 : /************************************************************************/
589 :
590 2 : static CPLErr ZarrDatasetCopyFiles(const char *pszNewName,
591 : const char *pszOldName)
592 : {
593 2 : if (STARTS_WITH(pszNewName, "ZARR:") || STARTS_WITH(pszOldName, "ZARR:"))
594 : {
595 0 : CPLError(CE_Failure, CPLE_AppDefined,
596 : "CopyFiles() only supported on ZARR connection names "
597 : "not starting with the ZARR: prefix");
598 0 : return CE_Failure;
599 : }
600 : // VSISync() returns true in case of success
601 4 : return VSISync((std::string(pszOldName) + '/').c_str(), pszNewName, nullptr,
602 : nullptr, nullptr, nullptr)
603 2 : ? CE_None
604 2 : : CE_Failure;
605 : }
606 :
607 : /************************************************************************/
608 : /* ZarrDriver() */
609 : /************************************************************************/
610 :
611 : class ZarrDriver final : public GDALDriver
612 : {
613 : bool m_bMetadataInitialized = false;
614 : void InitMetadata();
615 :
616 : public:
617 29883 : const char *GetMetadataItem(const char *pszName,
618 : const char *pszDomain) override
619 : {
620 29883 : if (EQUAL(pszName, "COMPRESSORS") ||
621 29869 : EQUAL(pszName, "BLOSC_COMPRESSORS") ||
622 29862 : EQUAL(pszName, GDAL_DMD_CREATIONOPTIONLIST) ||
623 29742 : EQUAL(pszName, GDAL_DMD_MULTIDIM_ARRAY_CREATIONOPTIONLIST))
624 : {
625 142 : InitMetadata();
626 : }
627 29884 : return GDALDriver::GetMetadataItem(pszName, pszDomain);
628 : }
629 :
630 34 : char **GetMetadata(const char *pszDomain) override
631 : {
632 34 : InitMetadata();
633 34 : return GDALDriver::GetMetadata(pszDomain);
634 : }
635 : };
636 :
637 177 : void ZarrDriver::InitMetadata()
638 : {
639 177 : if (m_bMetadataInitialized)
640 168 : return;
641 9 : m_bMetadataInitialized = true;
642 :
643 : {
644 : // A bit of a hack. Normally GetMetadata() should also return it,
645 : // but as this is only used for tests, just make GetMetadataItem()
646 : // handle it
647 18 : std::string osCompressors;
648 18 : std::string osFilters;
649 9 : char **decompressors = CPLGetDecompressors();
650 72 : for (auto iter = decompressors; iter && *iter; ++iter)
651 : {
652 63 : const auto psCompressor = CPLGetCompressor(*iter);
653 63 : if (psCompressor)
654 : {
655 63 : if (psCompressor->eType == CCT_COMPRESSOR)
656 : {
657 54 : if (!osCompressors.empty())
658 45 : osCompressors += ',';
659 54 : osCompressors += *iter;
660 : }
661 9 : else if (psCompressor->eType == CCT_FILTER)
662 : {
663 9 : if (!osFilters.empty())
664 0 : osFilters += ',';
665 9 : osFilters += *iter;
666 : }
667 : }
668 : }
669 9 : CSLDestroy(decompressors);
670 9 : GDALDriver::SetMetadataItem("COMPRESSORS", osCompressors.c_str());
671 9 : GDALDriver::SetMetadataItem("FILTERS", osFilters.c_str());
672 : }
673 : #ifdef HAVE_BLOSC
674 : {
675 9 : GDALDriver::SetMetadataItem("BLOSC_COMPRESSORS",
676 : blosc_list_compressors());
677 : }
678 : #endif
679 :
680 : {
681 : CPLXMLTreeCloser oTree(
682 18 : CPLCreateXMLNode(nullptr, CXT_Element, "CreationOptionList"));
683 9 : char **compressors = CPLGetCompressors();
684 :
685 : auto psCompressNode =
686 9 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
687 9 : CPLAddXMLAttributeAndValue(psCompressNode, "name", "COMPRESS");
688 9 : CPLAddXMLAttributeAndValue(psCompressNode, "type", "string-select");
689 9 : CPLAddXMLAttributeAndValue(psCompressNode, "description",
690 : "Compression method");
691 9 : CPLAddXMLAttributeAndValue(psCompressNode, "default", "NONE");
692 : {
693 : auto poValueNode =
694 9 : CPLCreateXMLNode(psCompressNode, CXT_Element, "Value");
695 9 : CPLCreateXMLNode(poValueNode, CXT_Text, "NONE");
696 : }
697 :
698 : auto psFilterNode =
699 9 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
700 9 : CPLAddXMLAttributeAndValue(psFilterNode, "name", "FILTER");
701 9 : CPLAddXMLAttributeAndValue(psFilterNode, "type", "string-select");
702 9 : CPLAddXMLAttributeAndValue(psFilterNode, "description",
703 : "Filter method (only for ZARR_V2)");
704 9 : CPLAddXMLAttributeAndValue(psFilterNode, "default", "NONE");
705 : {
706 : auto poValueNode =
707 9 : CPLCreateXMLNode(psFilterNode, CXT_Element, "Value");
708 9 : CPLCreateXMLNode(poValueNode, CXT_Text, "NONE");
709 : }
710 :
711 : auto psBlockSizeNode =
712 9 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
713 9 : CPLAddXMLAttributeAndValue(psBlockSizeNode, "name", "BLOCKSIZE");
714 9 : CPLAddXMLAttributeAndValue(psBlockSizeNode, "type", "string");
715 9 : CPLAddXMLAttributeAndValue(
716 : psBlockSizeNode, "description",
717 : "Comma separated list of chunk size along each dimension");
718 :
719 : auto psChunkMemoryLayout =
720 9 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
721 9 : CPLAddXMLAttributeAndValue(psChunkMemoryLayout, "name",
722 : "CHUNK_MEMORY_LAYOUT");
723 9 : CPLAddXMLAttributeAndValue(psChunkMemoryLayout, "type",
724 : "string-select");
725 9 : CPLAddXMLAttributeAndValue(psChunkMemoryLayout, "description",
726 : "Whether to use C (row-major) order or F "
727 : "(column-major) order in chunks");
728 9 : CPLAddXMLAttributeAndValue(psChunkMemoryLayout, "default", "C");
729 : {
730 : auto poValueNode =
731 9 : CPLCreateXMLNode(psChunkMemoryLayout, CXT_Element, "Value");
732 9 : CPLCreateXMLNode(poValueNode, CXT_Text, "C");
733 : }
734 : {
735 : auto poValueNode =
736 9 : CPLCreateXMLNode(psChunkMemoryLayout, CXT_Element, "Value");
737 9 : CPLCreateXMLNode(poValueNode, CXT_Text, "F");
738 : }
739 :
740 : auto psStringFormat =
741 9 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
742 9 : CPLAddXMLAttributeAndValue(psStringFormat, "name", "STRING_FORMAT");
743 9 : CPLAddXMLAttributeAndValue(psStringFormat, "type", "string-select");
744 9 : CPLAddXMLAttributeAndValue(psStringFormat, "default", "STRING");
745 : {
746 : auto poValueNode =
747 9 : CPLCreateXMLNode(psStringFormat, CXT_Element, "Value");
748 9 : CPLCreateXMLNode(poValueNode, CXT_Text, "STRING");
749 : }
750 : {
751 : auto poValueNode =
752 9 : CPLCreateXMLNode(psStringFormat, CXT_Element, "Value");
753 9 : CPLCreateXMLNode(poValueNode, CXT_Text, "UNICODE");
754 : }
755 :
756 : auto psDimSeparatorNode =
757 9 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
758 9 : CPLAddXMLAttributeAndValue(psDimSeparatorNode, "name", "DIM_SEPARATOR");
759 9 : CPLAddXMLAttributeAndValue(psDimSeparatorNode, "type", "string");
760 9 : CPLAddXMLAttributeAndValue(
761 : psDimSeparatorNode, "description",
762 : "Dimension separator in chunk filenames. Default to decimal point "
763 : "for ZarrV2 and slash for ZarrV3");
764 :
765 72 : for (auto iter = compressors; iter && *iter; ++iter)
766 : {
767 63 : const auto psCompressor = CPLGetCompressor(*iter);
768 63 : if (psCompressor)
769 : {
770 63 : auto poValueNode = CPLCreateXMLNode(
771 63 : (psCompressor->eType == CCT_COMPRESSOR) ? psCompressNode
772 : : psFilterNode,
773 : CXT_Element, "Value");
774 63 : CPLCreateXMLNode(poValueNode, CXT_Text,
775 126 : CPLString(*iter).toupper().c_str());
776 :
777 : const char *pszOptions =
778 63 : CSLFetchNameValue(psCompressor->papszMetadata, "OPTIONS");
779 63 : if (pszOptions)
780 : {
781 : CPLXMLTreeCloser oTreeCompressor(
782 126 : CPLParseXMLString(pszOptions));
783 : const auto psRoot =
784 63 : oTreeCompressor.get()
785 63 : ? CPLGetXMLNode(oTreeCompressor.get(), "=Options")
786 63 : : nullptr;
787 63 : if (psRoot)
788 : {
789 63 : for (CPLXMLNode *psNode = psRoot->psChild;
790 189 : psNode != nullptr; psNode = psNode->psNext)
791 : {
792 126 : if (psNode->eType == CXT_Element)
793 : {
794 : const char *pszName =
795 126 : CPLGetXMLValue(psNode, "name", nullptr);
796 126 : if (pszName &&
797 126 : !EQUAL(pszName, "TYPESIZE") // Blosc
798 117 : && !EQUAL(pszName, "HEADER") // LZ4
799 : )
800 : {
801 108 : CPLXMLNode *psNext = psNode->psNext;
802 108 : psNode->psNext = nullptr;
803 : CPLXMLNode *psOption =
804 108 : CPLCloneXMLTree(psNode);
805 :
806 : CPLXMLNode *psName =
807 108 : CPLGetXMLNode(psOption, "name");
808 108 : if (psName &&
809 108 : psName->eType == CXT_Attribute &&
810 108 : psName->psChild &&
811 108 : psName->psChild->pszValue)
812 : {
813 108 : CPLString osNewValue(*iter);
814 108 : osNewValue = osNewValue.toupper();
815 108 : osNewValue += '_';
816 108 : osNewValue += psName->psChild->pszValue;
817 108 : CPLFree(psName->psChild->pszValue);
818 216 : psName->psChild->pszValue =
819 108 : CPLStrdup(osNewValue.c_str());
820 : }
821 :
822 : CPLXMLNode *psDescription =
823 108 : CPLGetXMLNode(psOption, "description");
824 108 : if (psDescription &&
825 108 : psDescription->eType == CXT_Attribute &&
826 108 : psDescription->psChild &&
827 108 : psDescription->psChild->pszValue)
828 : {
829 : std::string osNewValue(
830 108 : psDescription->psChild->pszValue);
831 108 : if (psCompressor->eType ==
832 : CCT_COMPRESSOR)
833 : {
834 : osNewValue +=
835 99 : ". Only used when COMPRESS=";
836 : }
837 : else
838 : {
839 : osNewValue +=
840 9 : ". Only used when FILTER=";
841 : }
842 : osNewValue +=
843 108 : CPLString(*iter).toupper();
844 108 : CPLFree(
845 108 : psDescription->psChild->pszValue);
846 216 : psDescription->psChild->pszValue =
847 108 : CPLStrdup(osNewValue.c_str());
848 : }
849 :
850 108 : CPLAddXMLChild(oTree.get(), psOption);
851 108 : psNode->psNext = psNext;
852 : }
853 : }
854 : }
855 : }
856 : }
857 : }
858 : }
859 9 : CSLDestroy(compressors);
860 :
861 : {
862 9 : char *pszXML = CPLSerializeXMLTree(oTree.get());
863 9 : GDALDriver::SetMetadataItem(
864 : GDAL_DMD_MULTIDIM_ARRAY_CREATIONOPTIONLIST,
865 9 : CPLString(pszXML)
866 : .replaceAll("CreationOptionList",
867 18 : "MultiDimArrayCreationOptionList")
868 : .c_str());
869 9 : CPLFree(pszXML);
870 : }
871 :
872 : {
873 : auto psArrayNameOption =
874 9 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
875 9 : CPLAddXMLAttributeAndValue(psArrayNameOption, "name", "ARRAY_NAME");
876 9 : CPLAddXMLAttributeAndValue(psArrayNameOption, "type", "string");
877 9 : CPLAddXMLAttributeAndValue(
878 : psArrayNameOption, "description",
879 : "Array name. If not specified, deduced from the filename");
880 :
881 : auto psAppendSubDSOption =
882 9 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
883 9 : CPLAddXMLAttributeAndValue(psAppendSubDSOption, "name",
884 : "APPEND_SUBDATASET");
885 9 : CPLAddXMLAttributeAndValue(psAppendSubDSOption, "type", "boolean");
886 9 : CPLAddXMLAttributeAndValue(psAppendSubDSOption, "description",
887 : "Whether to append the new dataset to "
888 : "an existing Zarr hierarchy");
889 9 : CPLAddXMLAttributeAndValue(psAppendSubDSOption, "default", "NO");
890 :
891 : auto psFormat =
892 9 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
893 9 : CPLAddXMLAttributeAndValue(psFormat, "name", "FORMAT");
894 9 : CPLAddXMLAttributeAndValue(psFormat, "type", "string-select");
895 9 : CPLAddXMLAttributeAndValue(psFormat, "default", "ZARR_V2");
896 : {
897 : auto poValueNode =
898 9 : CPLCreateXMLNode(psFormat, CXT_Element, "Value");
899 9 : CPLCreateXMLNode(poValueNode, CXT_Text, "ZARR_V2");
900 : }
901 : {
902 : auto poValueNode =
903 9 : CPLCreateXMLNode(psFormat, CXT_Element, "Value");
904 9 : CPLCreateXMLNode(poValueNode, CXT_Text, "ZARR_V3");
905 : }
906 :
907 : auto psCreateZMetadata =
908 9 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
909 9 : CPLAddXMLAttributeAndValue(psCreateZMetadata, "name",
910 : "CREATE_ZMETADATA");
911 9 : CPLAddXMLAttributeAndValue(psCreateZMetadata, "type", "boolean");
912 9 : CPLAddXMLAttributeAndValue(
913 : psCreateZMetadata, "description",
914 : "Whether to create consolidated metadata into .zmetadata (Zarr "
915 : "V2 only)");
916 9 : CPLAddXMLAttributeAndValue(psCreateZMetadata, "default", "YES");
917 :
918 : auto psSingleArrayNode =
919 9 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
920 9 : CPLAddXMLAttributeAndValue(psSingleArrayNode, "name",
921 : "SINGLE_ARRAY");
922 9 : CPLAddXMLAttributeAndValue(psSingleArrayNode, "type", "boolean");
923 9 : CPLAddXMLAttributeAndValue(
924 : psSingleArrayNode, "description",
925 : "Whether to write a multi-band dataset as a single array, or "
926 : "one array per band");
927 9 : CPLAddXMLAttributeAndValue(psSingleArrayNode, "default", "YES");
928 :
929 : auto psInterleaveNode =
930 9 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
931 9 : CPLAddXMLAttributeAndValue(psInterleaveNode, "name", "INTERLEAVE");
932 9 : CPLAddXMLAttributeAndValue(psInterleaveNode, "type",
933 : "string-select");
934 9 : CPLAddXMLAttributeAndValue(psInterleaveNode, "default", "BAND");
935 : {
936 : auto poValueNode =
937 9 : CPLCreateXMLNode(psInterleaveNode, CXT_Element, "Value");
938 9 : CPLCreateXMLNode(poValueNode, CXT_Text, "BAND");
939 : }
940 : {
941 : auto poValueNode =
942 9 : CPLCreateXMLNode(psInterleaveNode, CXT_Element, "Value");
943 9 : CPLCreateXMLNode(poValueNode, CXT_Text, "PIXEL");
944 : }
945 :
946 9 : char *pszXML = CPLSerializeXMLTree(oTree.get());
947 9 : GDALDriver::SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST, pszXML);
948 9 : CPLFree(pszXML);
949 : }
950 : }
951 : }
952 :
953 : /************************************************************************/
954 : /* CreateMultiDimensional() */
955 : /************************************************************************/
956 :
957 : GDALDataset *
958 230 : ZarrDataset::CreateMultiDimensional(const char *pszFilename,
959 : CSLConstList /*papszRootGroupOptions*/,
960 : CSLConstList papszOptions)
961 : {
962 : const char *pszFormat =
963 230 : CSLFetchNameValueDef(papszOptions, "FORMAT", "ZARR_V2");
964 230 : std::shared_ptr<ZarrGroupBase> poRG;
965 : auto poSharedResource =
966 690 : ZarrSharedResource::Create(pszFilename, /*bUpdatable=*/true);
967 230 : if (EQUAL(pszFormat, "ZARR_V3"))
968 : {
969 246 : poRG = ZarrV3Group::CreateOnDisk(poSharedResource, std::string(), "/",
970 123 : pszFilename);
971 : }
972 : else
973 : {
974 107 : const bool bCreateZMetadata = CPLTestBool(
975 : CSLFetchNameValueDef(papszOptions, "CREATE_ZMETADATA", "YES"));
976 107 : if (bCreateZMetadata)
977 : {
978 91 : poSharedResource->EnableZMetadata();
979 : }
980 214 : poRG = ZarrV2Group::CreateOnDisk(poSharedResource, std::string(), "/",
981 107 : pszFilename);
982 : }
983 230 : if (!poRG)
984 0 : return nullptr;
985 :
986 230 : auto poDS = new ZarrDataset(poRG);
987 230 : poDS->SetDescription(pszFilename);
988 230 : return poDS;
989 : }
990 :
991 : /************************************************************************/
992 : /* Create() */
993 : /************************************************************************/
994 :
995 83 : GDALDataset *ZarrDataset::Create(const char *pszName, int nXSize, int nYSize,
996 : int nBandsIn, GDALDataType eType,
997 : char **papszOptions)
998 : {
999 : // To avoid any issue with short-lived string that would be passed to us
1000 166 : const std::string osName = pszName;
1001 83 : pszName = osName.c_str();
1002 :
1003 83 : if (nBandsIn <= 0 || nXSize <= 0 || nYSize <= 0)
1004 : {
1005 1 : CPLError(CE_Failure, CPLE_NotSupported,
1006 : "nBands, nXSize, nYSize should be > 0");
1007 1 : return nullptr;
1008 : }
1009 :
1010 82 : const bool bAppendSubDS = CPLTestBool(
1011 : CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO"));
1012 82 : const char *pszArrayName = CSLFetchNameValue(papszOptions, "ARRAY_NAME");
1013 :
1014 82 : std::shared_ptr<ZarrGroupBase> poRG;
1015 82 : if (bAppendSubDS)
1016 : {
1017 4 : if (pszArrayName == nullptr)
1018 : {
1019 0 : CPLError(CE_Failure, CPLE_AppDefined,
1020 : "ARRAY_NAME should be provided when "
1021 : "APPEND_SUBDATASET is set to YES");
1022 0 : return nullptr;
1023 : }
1024 : auto poDS =
1025 4 : std::unique_ptr<GDALDataset>(OpenMultidim(pszName, true, nullptr));
1026 4 : if (poDS == nullptr)
1027 : {
1028 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s", pszName);
1029 0 : return nullptr;
1030 : }
1031 4 : poRG = std::dynamic_pointer_cast<ZarrGroupBase>(poDS->GetRootGroup());
1032 : }
1033 : else
1034 : {
1035 : VSIStatBufL sStat;
1036 78 : const bool bExists = VSIStatL(pszName, &sStat) == 0;
1037 78 : const bool bIsFile = bExists && !VSI_ISDIR(sStat.st_mode);
1038 : const bool bIsDirectory =
1039 156 : !bIsFile && ((bExists && VSI_ISDIR(sStat.st_mode)) ||
1040 156 : !CPLStringList(VSIReadDirEx(pszName, 1)).empty());
1041 78 : if (bIsFile || bIsDirectory || bExists)
1042 : {
1043 0 : CPLError(CE_Failure, CPLE_FileIO, "%s %s already exists.",
1044 : bIsFile ? "File"
1045 0 : : bIsDirectory ? "Directory"
1046 : : "Object",
1047 : pszName);
1048 0 : return nullptr;
1049 : }
1050 :
1051 : const char *pszFormat =
1052 78 : CSLFetchNameValueDef(papszOptions, "FORMAT", "ZARR_V2");
1053 : auto poSharedResource =
1054 234 : ZarrSharedResource::Create(pszName, /*bUpdatable=*/true);
1055 78 : if (EQUAL(pszFormat, "ZARR_V3"))
1056 : {
1057 10 : poRG = ZarrV3Group::CreateOnDisk(poSharedResource, std::string(),
1058 5 : "/", pszName);
1059 : }
1060 : else
1061 : {
1062 73 : const bool bCreateZMetadata = CPLTestBool(
1063 : CSLFetchNameValueDef(papszOptions, "CREATE_ZMETADATA", "YES"));
1064 73 : if (bCreateZMetadata)
1065 : {
1066 73 : poSharedResource->EnableZMetadata();
1067 : }
1068 146 : poRG = ZarrV2Group::CreateOnDisk(poSharedResource, std::string(),
1069 73 : "/", pszName);
1070 : }
1071 78 : poSharedResource->SetRootGroup(poRG);
1072 : }
1073 82 : if (!poRG)
1074 3 : return nullptr;
1075 :
1076 158 : auto poDS = std::make_unique<ZarrDataset>(poRG);
1077 79 : poDS->SetDescription(pszName);
1078 79 : poDS->nRasterYSize = nYSize;
1079 79 : poDS->nRasterXSize = nXSize;
1080 79 : poDS->eAccess = GA_Update;
1081 :
1082 : const auto CleanupCreatedFiles =
1083 160 : [bAppendSubDS, pszName, pszArrayName, &poRG, &poDS]()
1084 : {
1085 : // Make sure all objects are released so that ZarrSharedResource
1086 : // is finalized and all files are serialized.
1087 10 : poRG.reset();
1088 10 : poDS.reset();
1089 :
1090 10 : if (bAppendSubDS)
1091 : {
1092 2 : VSIRmdir(CPLFormFilename(pszName, pszArrayName, nullptr));
1093 : }
1094 : else
1095 : {
1096 : // Be a bit careful before wiping too much stuff...
1097 : // At most 5 files expected for ZARR_V2: .zgroup, .zmetadata,
1098 : // one (empty) subdir, . and ..
1099 : // and for ZARR_V3: zarr.json, one (empty) subdir, . and ..
1100 16 : const CPLStringList aosFiles(VSIReadDirEx(pszName, 6));
1101 8 : if (aosFiles.size() < 6)
1102 : {
1103 40 : for (const char *pszFile : aosFiles)
1104 : {
1105 32 : if (pszArrayName && strcmp(pszFile, pszArrayName) == 0)
1106 : {
1107 0 : VSIRmdir(CPLFormFilename(pszName, pszFile, nullptr));
1108 : }
1109 64 : else if (!pszArrayName &&
1110 32 : strcmp(pszFile, CPLGetBasename(pszName)) == 0)
1111 : {
1112 1 : VSIRmdir(CPLFormFilename(pszName, pszFile, nullptr));
1113 : }
1114 31 : else if (strcmp(pszFile, ".zgroup") == 0 ||
1115 24 : strcmp(pszFile, ".zmetadata") == 0 ||
1116 17 : strcmp(pszFile, "zarr.json") == 0)
1117 : {
1118 15 : VSIUnlink(CPLFormFilename(pszName, pszFile, nullptr));
1119 : }
1120 : }
1121 8 : VSIRmdir(pszName);
1122 : }
1123 : }
1124 10 : };
1125 :
1126 79 : if (bAppendSubDS)
1127 : {
1128 8 : auto aoDims = poRG->GetDimensions();
1129 12 : for (const auto &poDim : aoDims)
1130 : {
1131 12 : if (poDim->GetName() == "Y" &&
1132 4 : poDim->GetSize() == static_cast<GUInt64>(nYSize))
1133 : {
1134 1 : poDS->m_poDimY = poDim;
1135 : }
1136 11 : else if (poDim->GetName() == "X" &&
1137 4 : poDim->GetSize() == static_cast<GUInt64>(nXSize))
1138 : {
1139 1 : poDS->m_poDimX = poDim;
1140 : }
1141 : }
1142 4 : if (poDS->m_poDimY == nullptr)
1143 : {
1144 3 : poDS->m_poDimY =
1145 9 : poRG->CreateDimension(std::string(pszArrayName) + "_Y",
1146 9 : std::string(), std::string(), nYSize);
1147 : }
1148 4 : if (poDS->m_poDimX == nullptr)
1149 : {
1150 3 : poDS->m_poDimX =
1151 9 : poRG->CreateDimension(std::string(pszArrayName) + "_X",
1152 9 : std::string(), std::string(), nXSize);
1153 : }
1154 : }
1155 : else
1156 : {
1157 75 : poDS->m_poDimY =
1158 150 : poRG->CreateDimension("Y", std::string(), std::string(), nYSize);
1159 75 : poDS->m_poDimX =
1160 150 : poRG->CreateDimension("X", std::string(), std::string(), nXSize);
1161 : }
1162 79 : if (poDS->m_poDimY == nullptr || poDS->m_poDimX == nullptr)
1163 : {
1164 0 : CleanupCreatedFiles();
1165 0 : return nullptr;
1166 : }
1167 :
1168 : const bool bSingleArray =
1169 79 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "SINGLE_ARRAY", "YES"));
1170 : const bool bBandInterleave =
1171 79 : EQUAL(CSLFetchNameValueDef(papszOptions, "INTERLEAVE", "BAND"), "BAND");
1172 : const std::shared_ptr<GDALDimension> poBandDim(
1173 77 : (bSingleArray && nBandsIn > 1)
1174 103 : ? poRG->CreateDimension("Band", std::string(), std::string(),
1175 24 : nBandsIn)
1176 307 : : nullptr);
1177 :
1178 : const char *pszNonNullArrayName =
1179 79 : pszArrayName ? pszArrayName : CPLGetBasename(pszName);
1180 79 : if (poBandDim)
1181 : {
1182 : const std::vector<std::shared_ptr<GDALDimension>> apoDims(
1183 : bBandInterleave
1184 : ? std::vector<std::shared_ptr<GDALDimension>>{poBandDim,
1185 23 : poDS->m_poDimY,
1186 92 : poDS->m_poDimX}
1187 : : std::vector<std::shared_ptr<GDALDimension>>{
1188 98 : poDS->m_poDimY, poDS->m_poDimX, poBandDim});
1189 72 : poDS->m_poSingleArray = poRG->CreateMDArray(
1190 24 : pszNonNullArrayName, apoDims, GDALExtendedDataType::Create(eType),
1191 48 : papszOptions);
1192 24 : if (!poDS->m_poSingleArray)
1193 : {
1194 2 : CleanupCreatedFiles();
1195 2 : return nullptr;
1196 : }
1197 44 : poDS->SetMetadataItem("INTERLEAVE", bBandInterleave ? "BAND" : "PIXEL",
1198 22 : "IMAGE_STRUCTURE");
1199 92 : for (int i = 0; i < nBandsIn; i++)
1200 : {
1201 70 : auto poSlicedArray = poDS->m_poSingleArray->GetView(
1202 210 : CPLSPrintf(bBandInterleave ? "[%d,::,::]" : "[::,::,%d]", i));
1203 70 : poDS->SetBand(i + 1, new ZarrRasterBand(poSlicedArray));
1204 : }
1205 : }
1206 : else
1207 : {
1208 : const auto apoDims = std::vector<std::shared_ptr<GDALDimension>>{
1209 220 : poDS->m_poDimY, poDS->m_poDimX};
1210 106 : for (int i = 0; i < nBandsIn; i++)
1211 : {
1212 59 : auto poArray = poRG->CreateMDArray(
1213 : nBandsIn == 1 ? pszNonNullArrayName
1214 6 : : pszArrayName ? CPLSPrintf("%s_band%d", pszArrayName, i + 1)
1215 0 : : CPLSPrintf("Band%d", i + 1),
1216 124 : apoDims, GDALExtendedDataType::Create(eType), papszOptions);
1217 59 : if (poArray == nullptr)
1218 : {
1219 8 : CleanupCreatedFiles();
1220 8 : return nullptr;
1221 : }
1222 51 : poDS->SetBand(i + 1, new ZarrRasterBand(poArray));
1223 : }
1224 : }
1225 :
1226 69 : return poDS.release();
1227 : }
1228 :
1229 : /************************************************************************/
1230 : /* ~ZarrDataset() */
1231 : /************************************************************************/
1232 :
1233 1994 : ZarrDataset::~ZarrDataset()
1234 : {
1235 997 : ZarrDataset::FlushCache(true);
1236 1994 : }
1237 :
1238 : /************************************************************************/
1239 : /* FlushCache() */
1240 : /************************************************************************/
1241 :
1242 1000 : CPLErr ZarrDataset::FlushCache(bool bAtClosing)
1243 : {
1244 1000 : CPLErr eErr = GDALDataset::FlushCache(bAtClosing);
1245 1000 : if (m_poSingleArray)
1246 : {
1247 23 : bool bFound = false;
1248 96 : for (int i = 0; i < nBands; ++i)
1249 : {
1250 73 : if (papoBands[i]->GetColorInterpretation() != GCI_Undefined)
1251 12 : bFound = true;
1252 : }
1253 23 : if (bFound)
1254 : {
1255 8 : const auto oStringDT = GDALExtendedDataType::CreateString();
1256 12 : auto poAttr = m_poSingleArray->GetAttribute("COLOR_INTERPRETATION");
1257 4 : if (!poAttr)
1258 9 : poAttr = m_poSingleArray->CreateAttribute(
1259 3 : "COLOR_INTERPRETATION", {static_cast<GUInt64>(nBands)},
1260 6 : oStringDT);
1261 4 : if (poAttr)
1262 : {
1263 4 : const GUInt64 nStartIndex = 0;
1264 4 : const size_t nCount = nBands;
1265 4 : const GInt64 arrayStep = 1;
1266 4 : const GPtrDiff_t bufferStride = 1;
1267 8 : std::vector<const char *> apszValues;
1268 16 : for (int i = 0; i < nBands; ++i)
1269 : {
1270 : const auto eColorInterp =
1271 12 : papoBands[i]->GetColorInterpretation();
1272 12 : apszValues.push_back(
1273 12 : GDALGetColorInterpretationName(eColorInterp));
1274 : }
1275 8 : poAttr->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
1276 4 : oStringDT, apszValues.data());
1277 : }
1278 : }
1279 : }
1280 1000 : return eErr;
1281 : }
1282 :
1283 : /************************************************************************/
1284 : /* GetSpatialRef() */
1285 : /************************************************************************/
1286 :
1287 2 : const OGRSpatialReference *ZarrDataset::GetSpatialRef() const
1288 : {
1289 2 : if (nBands >= 1)
1290 2 : return cpl::down_cast<ZarrRasterBand *>(papoBands[0])
1291 4 : ->m_poArray->GetSpatialRef()
1292 2 : .get();
1293 0 : return nullptr;
1294 : }
1295 :
1296 : /************************************************************************/
1297 : /* SetSpatialRef() */
1298 : /************************************************************************/
1299 :
1300 56 : CPLErr ZarrDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
1301 : {
1302 160 : for (int i = 0; i < nBands; ++i)
1303 : {
1304 104 : cpl::down_cast<ZarrRasterBand *>(papoBands[i])
1305 104 : ->m_poArray->SetSpatialRef(poSRS);
1306 : }
1307 56 : return CE_None;
1308 : }
1309 :
1310 : /************************************************************************/
1311 : /* GetGeoTransform() */
1312 : /************************************************************************/
1313 :
1314 29 : CPLErr ZarrDataset::GetGeoTransform(double *padfTransform)
1315 : {
1316 29 : memcpy(padfTransform, &m_adfGeoTransform[0], 6 * sizeof(double));
1317 29 : return m_bHasGT ? CE_None : CE_Failure;
1318 : }
1319 :
1320 : /************************************************************************/
1321 : /* SetGeoTransform() */
1322 : /************************************************************************/
1323 :
1324 59 : CPLErr ZarrDataset::SetGeoTransform(double *padfTransform)
1325 : {
1326 59 : if (padfTransform[2] != 0 || padfTransform[4] != 0)
1327 : {
1328 0 : CPLError(CE_Failure, CPLE_NotSupported,
1329 : "Geotransform with rotated terms not supported");
1330 0 : return CE_Failure;
1331 : }
1332 59 : if (m_poDimX == nullptr || m_poDimY == nullptr)
1333 0 : return CE_Failure;
1334 :
1335 59 : memcpy(&m_adfGeoTransform[0], padfTransform, 6 * sizeof(double));
1336 59 : m_bHasGT = true;
1337 :
1338 118 : const auto oDTFloat64 = GDALExtendedDataType::Create(GDT_Float64);
1339 : {
1340 59 : auto poX = m_poRootGroup->OpenMDArray(m_poDimX->GetName());
1341 59 : if (!poX)
1342 232 : poX = m_poRootGroup->CreateMDArray(m_poDimX->GetName(), {m_poDimX},
1343 174 : oDTFloat64, nullptr);
1344 59 : if (!poX)
1345 0 : return CE_Failure;
1346 59 : m_poDimX->SetIndexingVariable(poX);
1347 59 : std::vector<double> adfX;
1348 : try
1349 : {
1350 59 : adfX.reserve(nRasterXSize);
1351 3585 : for (int i = 0; i < nRasterXSize; ++i)
1352 3526 : adfX.emplace_back(padfTransform[0] +
1353 3526 : padfTransform[1] * (i + 0.5));
1354 : }
1355 0 : catch (const std::exception &)
1356 : {
1357 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
1358 : "Out of memory when allocating X array");
1359 0 : return CE_Failure;
1360 : }
1361 59 : const GUInt64 nStartIndex = 0;
1362 59 : const size_t nCount = adfX.size();
1363 59 : const GInt64 arrayStep = 1;
1364 59 : const GPtrDiff_t bufferStride = 1;
1365 118 : if (!poX->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
1366 59 : poX->GetDataType(), adfX.data()))
1367 : {
1368 0 : return CE_Failure;
1369 : }
1370 : }
1371 :
1372 : {
1373 59 : auto poY = m_poRootGroup->OpenMDArray(m_poDimY->GetName());
1374 59 : if (!poY)
1375 232 : poY = m_poRootGroup->CreateMDArray(m_poDimY->GetName(), {m_poDimY},
1376 174 : oDTFloat64, nullptr);
1377 59 : if (!poY)
1378 0 : return CE_Failure;
1379 59 : m_poDimY->SetIndexingVariable(poY);
1380 59 : std::vector<double> adfY;
1381 : try
1382 : {
1383 59 : adfY.reserve(nRasterYSize);
1384 3373 : for (int i = 0; i < nRasterYSize; ++i)
1385 3314 : adfY.emplace_back(padfTransform[3] +
1386 3314 : padfTransform[5] * (i + 0.5));
1387 : }
1388 0 : catch (const std::exception &)
1389 : {
1390 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
1391 : "Out of memory when allocating Y array");
1392 0 : return CE_Failure;
1393 : }
1394 59 : const GUInt64 nStartIndex = 0;
1395 59 : const size_t nCount = adfY.size();
1396 59 : const GInt64 arrayStep = 1;
1397 59 : const GPtrDiff_t bufferStride = 1;
1398 118 : if (!poY->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
1399 59 : poY->GetDataType(), adfY.data()))
1400 : {
1401 0 : return CE_Failure;
1402 : }
1403 : }
1404 :
1405 59 : return CE_None;
1406 : }
1407 :
1408 : /************************************************************************/
1409 : /* SetMetadata() */
1410 : /************************************************************************/
1411 :
1412 31 : CPLErr ZarrDataset::SetMetadata(char **papszMetadata, const char *pszDomain)
1413 : {
1414 31 : if (nBands >= 1 && (pszDomain == nullptr || pszDomain[0] == '\0'))
1415 : {
1416 62 : const auto oStringDT = GDALExtendedDataType::CreateString();
1417 31 : const auto bSingleArray = m_poSingleArray != nullptr;
1418 31 : const int nIters = bSingleArray ? 1 : nBands;
1419 66 : for (int i = 0; i < nIters; ++i)
1420 : {
1421 : auto *poArray = bSingleArray
1422 35 : ? m_poSingleArray.get()
1423 28 : : cpl::down_cast<ZarrRasterBand *>(papoBands[i])
1424 28 : ->m_poArray.get();
1425 70 : for (auto iter = papszMetadata; iter && *iter; ++iter)
1426 : {
1427 35 : char *pszKey = nullptr;
1428 35 : const char *pszValue = CPLParseNameValue(*iter, &pszKey);
1429 35 : if (pszKey && pszValue)
1430 : {
1431 : auto poAttr =
1432 36 : poArray->CreateAttribute(pszKey, {}, oStringDT);
1433 12 : if (poAttr)
1434 : {
1435 12 : const GUInt64 nStartIndex = 0;
1436 12 : const size_t nCount = 1;
1437 12 : const GInt64 arrayStep = 1;
1438 12 : const GPtrDiff_t bufferStride = 1;
1439 12 : poAttr->Write(&nStartIndex, &nCount, &arrayStep,
1440 : &bufferStride, oStringDT, &pszValue);
1441 : }
1442 : }
1443 35 : CPLFree(pszKey);
1444 : }
1445 : }
1446 : }
1447 31 : return GDALDataset::SetMetadata(papszMetadata, pszDomain);
1448 : }
1449 :
1450 : /************************************************************************/
1451 : /* ZarrRasterBand::ZarrRasterBand() */
1452 : /************************************************************************/
1453 :
1454 121 : ZarrRasterBand::ZarrRasterBand(const std::shared_ptr<GDALMDArray> &poArray)
1455 121 : : m_poArray(poArray)
1456 : {
1457 121 : assert(poArray->GetDimensionCount() == 2);
1458 121 : eDataType = poArray->GetDataType().GetNumericDataType();
1459 121 : nBlockXSize = static_cast<int>(poArray->GetBlockSize()[1]);
1460 121 : nBlockYSize = static_cast<int>(poArray->GetBlockSize()[0]);
1461 121 : }
1462 :
1463 : /************************************************************************/
1464 : /* GetNoDataValue() */
1465 : /************************************************************************/
1466 :
1467 6 : double ZarrRasterBand::GetNoDataValue(int *pbHasNoData)
1468 : {
1469 6 : bool bHasNodata = false;
1470 6 : const auto res = m_poArray->GetNoDataValueAsDouble(&bHasNodata);
1471 6 : if (pbHasNoData)
1472 6 : *pbHasNoData = bHasNodata;
1473 6 : return res;
1474 : }
1475 :
1476 : /************************************************************************/
1477 : /* GetNoDataValueAsInt64() */
1478 : /************************************************************************/
1479 :
1480 0 : int64_t ZarrRasterBand::GetNoDataValueAsInt64(int *pbHasNoData)
1481 : {
1482 0 : bool bHasNodata = false;
1483 0 : const auto res = m_poArray->GetNoDataValueAsInt64(&bHasNodata);
1484 0 : if (pbHasNoData)
1485 0 : *pbHasNoData = bHasNodata;
1486 0 : return res;
1487 : }
1488 :
1489 : /************************************************************************/
1490 : /* GetNoDataValueAsUInt64() */
1491 : /************************************************************************/
1492 :
1493 0 : uint64_t ZarrRasterBand::GetNoDataValueAsUInt64(int *pbHasNoData)
1494 : {
1495 0 : bool bHasNodata = false;
1496 0 : const auto res = m_poArray->GetNoDataValueAsUInt64(&bHasNodata);
1497 0 : if (pbHasNoData)
1498 0 : *pbHasNoData = bHasNodata;
1499 0 : return res;
1500 : }
1501 :
1502 : /************************************************************************/
1503 : /* SetNoDataValue() */
1504 : /************************************************************************/
1505 :
1506 2 : CPLErr ZarrRasterBand::SetNoDataValue(double dfNoData)
1507 : {
1508 2 : return m_poArray->SetNoDataValue(dfNoData) ? CE_None : CE_Failure;
1509 : }
1510 :
1511 : /************************************************************************/
1512 : /* SetNoDataValueAsInt64() */
1513 : /************************************************************************/
1514 :
1515 0 : CPLErr ZarrRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
1516 : {
1517 0 : return m_poArray->SetNoDataValue(nNoData) ? CE_None : CE_Failure;
1518 : }
1519 :
1520 : /************************************************************************/
1521 : /* SetNoDataValueAsUInt64() */
1522 : /************************************************************************/
1523 :
1524 0 : CPLErr ZarrRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
1525 : {
1526 0 : return m_poArray->SetNoDataValue(nNoData) ? CE_None : CE_Failure;
1527 : }
1528 :
1529 : /************************************************************************/
1530 : /* GetOffset() */
1531 : /************************************************************************/
1532 :
1533 2 : double ZarrRasterBand::GetOffset(int *pbSuccess)
1534 : {
1535 2 : bool bHasValue = false;
1536 2 : double dfRet = m_poArray->GetOffset(&bHasValue);
1537 2 : if (pbSuccess)
1538 2 : *pbSuccess = bHasValue ? TRUE : FALSE;
1539 2 : return dfRet;
1540 : }
1541 :
1542 : /************************************************************************/
1543 : /* SetOffset() */
1544 : /************************************************************************/
1545 :
1546 2 : CPLErr ZarrRasterBand::SetOffset(double dfNewOffset)
1547 : {
1548 2 : return m_poArray->SetOffset(dfNewOffset) ? CE_None : CE_Failure;
1549 : }
1550 :
1551 : /************************************************************************/
1552 : /* GetScale() */
1553 : /************************************************************************/
1554 :
1555 2 : double ZarrRasterBand::GetScale(int *pbSuccess)
1556 : {
1557 2 : bool bHasValue = false;
1558 2 : double dfRet = m_poArray->GetScale(&bHasValue);
1559 2 : if (pbSuccess)
1560 2 : *pbSuccess = bHasValue ? TRUE : FALSE;
1561 2 : return dfRet;
1562 : }
1563 :
1564 : /************************************************************************/
1565 : /* SetScale() */
1566 : /************************************************************************/
1567 :
1568 2 : CPLErr ZarrRasterBand::SetScale(double dfNewScale)
1569 : {
1570 2 : return m_poArray->SetScale(dfNewScale) ? CE_None : CE_Failure;
1571 : }
1572 :
1573 : /************************************************************************/
1574 : /* GetUnitType() */
1575 : /************************************************************************/
1576 :
1577 2 : const char *ZarrRasterBand::GetUnitType()
1578 : {
1579 2 : return m_poArray->GetUnit().c_str();
1580 : }
1581 :
1582 : /************************************************************************/
1583 : /* SetUnitType() */
1584 : /************************************************************************/
1585 :
1586 2 : CPLErr ZarrRasterBand::SetUnitType(const char *pszNewValue)
1587 : {
1588 4 : return m_poArray->SetUnit(pszNewValue ? pszNewValue : "") ? CE_None
1589 4 : : CE_Failure;
1590 : }
1591 :
1592 : /************************************************************************/
1593 : /* GetColorInterpretation() */
1594 : /************************************************************************/
1595 :
1596 97 : GDALColorInterp ZarrRasterBand::GetColorInterpretation()
1597 : {
1598 97 : return m_eColorInterp;
1599 : }
1600 :
1601 : /************************************************************************/
1602 : /* SetColorInterpretation() */
1603 : /************************************************************************/
1604 :
1605 12 : CPLErr ZarrRasterBand::SetColorInterpretation(GDALColorInterp eColorInterp)
1606 : {
1607 12 : auto poGDS = cpl::down_cast<ZarrDataset *>(poDS);
1608 12 : m_eColorInterp = eColorInterp;
1609 12 : if (!poGDS->m_poSingleArray)
1610 : {
1611 3 : const auto oStringDT = GDALExtendedDataType::CreateString();
1612 6 : auto poAttr = m_poArray->GetAttribute("COLOR_INTERPRETATION");
1613 3 : if (poAttr && (poAttr->GetDimensionCount() != 0 ||
1614 3 : poAttr->GetDataType().GetClass() != GEDTC_STRING))
1615 0 : return CE_None;
1616 3 : if (!poAttr)
1617 9 : poAttr = m_poArray->CreateAttribute("COLOR_INTERPRETATION", {},
1618 6 : oStringDT);
1619 3 : if (poAttr)
1620 : {
1621 3 : const GUInt64 nStartIndex = 0;
1622 3 : const size_t nCount = 1;
1623 3 : const GInt64 arrayStep = 1;
1624 3 : const GPtrDiff_t bufferStride = 1;
1625 3 : const char *pszValue = GDALGetColorInterpretationName(eColorInterp);
1626 3 : poAttr->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
1627 : oStringDT, &pszValue);
1628 : }
1629 : }
1630 12 : return CE_None;
1631 : }
1632 :
1633 : /************************************************************************/
1634 : /* ZarrRasterBand::IReadBlock() */
1635 : /************************************************************************/
1636 :
1637 2 : CPLErr ZarrRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pData)
1638 : {
1639 :
1640 2 : const int nXOff = nBlockXOff * nBlockXSize;
1641 2 : const int nYOff = nBlockYOff * nBlockYSize;
1642 2 : const int nReqXSize = std::min(nRasterXSize - nXOff, nBlockXSize);
1643 2 : const int nReqYSize = std::min(nRasterYSize - nYOff, nBlockYSize);
1644 2 : GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
1645 2 : static_cast<GUInt64>(nXOff)};
1646 2 : size_t count[] = {static_cast<size_t>(nReqYSize),
1647 2 : static_cast<size_t>(nReqXSize)};
1648 2 : constexpr GInt64 arrayStep[] = {1, 1};
1649 2 : GPtrDiff_t bufferStride[] = {nBlockXSize, 1};
1650 4 : return m_poArray->Read(arrayStartIdx, count, arrayStep, bufferStride,
1651 2 : m_poArray->GetDataType(), pData)
1652 2 : ? CE_None
1653 2 : : CE_Failure;
1654 : }
1655 :
1656 : /************************************************************************/
1657 : /* ZarrRasterBand::IWriteBlock() */
1658 : /************************************************************************/
1659 :
1660 0 : CPLErr ZarrRasterBand::IWriteBlock(int nBlockXOff, int nBlockYOff, void *pData)
1661 : {
1662 0 : const int nXOff = nBlockXOff * nBlockXSize;
1663 0 : const int nYOff = nBlockYOff * nBlockYSize;
1664 0 : const int nReqXSize = std::min(nRasterXSize - nXOff, nBlockXSize);
1665 0 : const int nReqYSize = std::min(nRasterYSize - nYOff, nBlockYSize);
1666 0 : GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
1667 0 : static_cast<GUInt64>(nXOff)};
1668 0 : size_t count[] = {static_cast<size_t>(nReqYSize),
1669 0 : static_cast<size_t>(nReqXSize)};
1670 0 : constexpr GInt64 arrayStep[] = {1, 1};
1671 0 : GPtrDiff_t bufferStride[] = {nBlockXSize, 1};
1672 0 : return m_poArray->Write(arrayStartIdx, count, arrayStep, bufferStride,
1673 0 : m_poArray->GetDataType(), pData)
1674 0 : ? CE_None
1675 0 : : CE_Failure;
1676 : }
1677 :
1678 : /************************************************************************/
1679 : /* IRasterIO() */
1680 : /************************************************************************/
1681 :
1682 48 : CPLErr ZarrRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
1683 : int nXSize, int nYSize, void *pData,
1684 : int nBufXSize, int nBufYSize,
1685 : GDALDataType eBufType, GSpacing nPixelSpaceBuf,
1686 : GSpacing nLineSpaceBuf,
1687 : GDALRasterIOExtraArg *psExtraArg)
1688 : {
1689 48 : const int nBufferDTSize(GDALGetDataTypeSizeBytes(eBufType));
1690 48 : if (nXSize == nBufXSize && nYSize == nBufYSize && nBufferDTSize > 0 &&
1691 48 : (nPixelSpaceBuf % nBufferDTSize) == 0 &&
1692 48 : (nLineSpaceBuf % nBufferDTSize) == 0)
1693 : {
1694 48 : GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
1695 48 : static_cast<GUInt64>(nXOff)};
1696 48 : size_t count[] = {static_cast<size_t>(nYSize),
1697 48 : static_cast<size_t>(nXSize)};
1698 48 : constexpr GInt64 arrayStep[] = {1, 1};
1699 : GPtrDiff_t bufferStride[] = {
1700 48 : static_cast<GPtrDiff_t>(nLineSpaceBuf / nBufferDTSize),
1701 48 : static_cast<GPtrDiff_t>(nPixelSpaceBuf / nBufferDTSize)};
1702 :
1703 48 : if (eRWFlag == GF_Read)
1704 : {
1705 4 : return m_poArray->Read(
1706 : arrayStartIdx, count, arrayStep, bufferStride,
1707 4 : GDALExtendedDataType::Create(eBufType), pData)
1708 2 : ? CE_None
1709 2 : : CE_Failure;
1710 : }
1711 : else
1712 : {
1713 92 : return m_poArray->Write(
1714 : arrayStartIdx, count, arrayStep, bufferStride,
1715 92 : GDALExtendedDataType::Create(eBufType), pData)
1716 46 : ? CE_None
1717 46 : : CE_Failure;
1718 : }
1719 : }
1720 0 : return GDALRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
1721 : pData, nBufXSize, nBufYSize, eBufType,
1722 0 : nPixelSpaceBuf, nLineSpaceBuf, psExtraArg);
1723 : }
1724 :
1725 : /************************************************************************/
1726 : /* GDALRegister_Zarr() */
1727 : /************************************************************************/
1728 :
1729 1595 : void GDALRegister_Zarr()
1730 :
1731 : {
1732 1595 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
1733 302 : return;
1734 :
1735 1293 : GDALDriver *poDriver = new ZarrDriver();
1736 1293 : ZARRDriverSetCommonMetadata(poDriver);
1737 :
1738 1293 : poDriver->pfnOpen = ZarrDataset::Open;
1739 1293 : poDriver->pfnCreateMultiDimensional = ZarrDataset::CreateMultiDimensional;
1740 1293 : poDriver->pfnCreate = ZarrDataset::Create;
1741 1293 : poDriver->pfnDelete = ZarrDatasetDelete;
1742 1293 : poDriver->pfnRename = ZarrDatasetRename;
1743 1293 : poDriver->pfnCopyFiles = ZarrDatasetCopyFiles;
1744 :
1745 1293 : GetGDALDriverManager()->RegisterDriver(poDriver);
1746 : }
|