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 1006 : ZarrDataset::ZarrDataset(const std::shared_ptr<GDALGroup> &poRootGroup)
31 1006 : : m_poRootGroup(poRootGroup)
32 : {
33 1006 : }
34 :
35 : /************************************************************************/
36 : /* OpenMultidim() */
37 : /************************************************************************/
38 :
39 681 : GDALDataset *ZarrDataset::OpenMultidim(const char *pszFilename,
40 : bool bUpdateMode,
41 : CSLConstList papszOpenOptionsIn)
42 : {
43 1362 : CPLString osFilename(pszFilename);
44 681 : if (osFilename.back() == '/')
45 0 : osFilename.pop_back();
46 :
47 1362 : auto poSharedResource = ZarrSharedResource::Create(osFilename, bUpdateMode);
48 681 : poSharedResource->SetOpenOptions(papszOpenOptionsIn);
49 :
50 1362 : auto poRG = poSharedResource->GetRootGroup();
51 681 : if (!poRG)
52 77 : return nullptr;
53 604 : return new ZarrDataset(poRG);
54 : }
55 :
56 : /************************************************************************/
57 : /* ExploreGroup() */
58 : /************************************************************************/
59 :
60 77 : static bool ExploreGroup(const std::shared_ptr<GDALGroup> &poGroup,
61 : std::vector<std::string> &aosArrays, int nRecCount)
62 : {
63 77 : if (nRecCount == 32)
64 : {
65 0 : CPLError(CE_Failure, CPLE_NotSupported,
66 : "Too deep recursion level in ExploreGroup()");
67 0 : return false;
68 : }
69 154 : const auto aosGroupArrayNames = poGroup->GetMDArrayNames();
70 227 : for (const auto &osArrayName : aosGroupArrayNames)
71 : {
72 150 : std::string osArrayFullname = poGroup->GetFullName();
73 150 : if (osArrayName != "/")
74 : {
75 150 : if (osArrayFullname != "/")
76 1 : osArrayFullname += '/';
77 150 : osArrayFullname += osArrayName;
78 : }
79 150 : aosArrays.emplace_back(std::move(osArrayFullname));
80 150 : 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 154 : const auto aosSubGroups = poGroup->GetGroupNames();
89 79 : 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 77 : 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 99 : static void GetXYDimensionIndices(const std::shared_ptr<GDALMDArray> &poArray,
129 : const GDALOpenInfo *poOpenInfo, size_t &iXDim,
130 : size_t &iYDim)
131 : {
132 99 : const size_t nDims = poArray->GetDimensionCount();
133 99 : iYDim = nDims >= 2 ? nDims - 2 : 0;
134 99 : iXDim = nDims >= 2 ? nDims - 1 : 0;
135 :
136 99 : if (nDims >= 2)
137 : {
138 : const char *pszDimX =
139 89 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "DIM_X");
140 : const char *pszDimY =
141 89 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "DIM_Y");
142 89 : bool bFoundX = false;
143 89 : bool bFoundY = false;
144 89 : const auto &apoDims = poArray->GetDimensions();
145 316 : for (size_t i = 0; i < nDims; ++i)
146 : {
147 227 : if (pszDimX && apoDims[i]->GetName() == pszDimX)
148 : {
149 1 : bFoundX = true;
150 1 : iXDim = i;
151 : }
152 226 : else if (pszDimY && apoDims[i]->GetName() == pszDimY)
153 : {
154 1 : bFoundY = true;
155 1 : iYDim = i;
156 : }
157 653 : else if (!pszDimX &&
158 428 : (apoDims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X ||
159 213 : apoDims[i]->GetName() == "X"))
160 52 : iXDim = i;
161 497 : else if (!pszDimY &&
162 324 : (apoDims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y ||
163 161 : apoDims[i]->GetName() == "Y"))
164 52 : iYDim = i;
165 : }
166 89 : 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 89 : 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 99 : }
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 680 : GDALDataset *ZarrDataset::Open(GDALOpenInfo *poOpenInfo)
226 : {
227 680 : if (!ZARRDriverIdentify(poOpenInfo))
228 : {
229 0 : return nullptr;
230 : }
231 :
232 1360 : CPLString osFilename(poOpenInfo->pszFilename);
233 1360 : CPLString osArrayOfInterest;
234 1360 : std::vector<uint64_t> anExtraDimIndices;
235 680 : 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 677 : OpenMultidim(osFilename.c_str(), poOpenInfo->eAccess == GA_Update,
279 1354 : poOpenInfo->papszOpenOptions));
280 1277 : if (poDSMultiDim == nullptr ||
281 600 : (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER) != 0)
282 : {
283 585 : return poDSMultiDim.release();
284 : }
285 :
286 184 : auto poRG = poDSMultiDim->GetRootGroup();
287 :
288 184 : auto poDS = std::make_unique<ZarrDataset>(nullptr);
289 92 : std::shared_ptr<GDALMDArray> poMainArray;
290 184 : std::vector<std::string> aosArrays;
291 184 : std::string osMainArray;
292 92 : const bool bMultiband = CPLTestBool(
293 92 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "MULTIBAND", "YES"));
294 92 : size_t iXDim = 0;
295 92 : size_t iYDim = 0;
296 :
297 92 : 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 75 : ExploreGroup(poRG, aosArrays, 0);
373 75 : if (aosArrays.empty())
374 0 : return nullptr;
375 :
376 75 : if (aosArrays.size() == 1)
377 : {
378 38 : poMainArray = poRG->OpenMDArrayFromFullname(aosArrays[0]);
379 38 : if (poMainArray)
380 38 : 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 75 : if (poMainArray)
405 75 : GetXYDimensionIndices(poMainArray, poOpenInfo, iXDim, iYDim);
406 :
407 75 : int iCountSubDS = 1;
408 :
409 75 : 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 75 : 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 87 : if (poMainArray && (bMultiband || poMainArray->GetDimensionCount() <= 2))
496 : {
497 : // Pass papszOpenOptions for LOAD_EXTRA_DIM_METADATA_DELAY
498 : auto poNewDS =
499 83 : std::unique_ptr<GDALDataset>(poMainArray->AsClassicDataset(
500 166 : iXDim, iYDim, poRG, poOpenInfo->papszOpenOptions));
501 83 : if (!poNewDS)
502 3 : return nullptr;
503 :
504 80 : 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 71 : 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 80 : if (!poDS->m_aosSubdatasets.empty())
545 : {
546 4 : poNewDS->SetMetadata(poDS->m_aosSubdatasets.List(), "SUBDATASETS");
547 : }
548 80 : 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 30439 : const char *GetMetadataItem(const char *pszName,
618 : const char *pszDomain) override
619 : {
620 30439 : if (EQUAL(pszName, "COMPRESSORS") ||
621 30423 : EQUAL(pszName, "BLOSC_COMPRESSORS") ||
622 30422 : EQUAL(pszName, GDAL_DMD_CREATIONOPTIONLIST) ||
623 30298 : EQUAL(pszName, GDAL_DMD_MULTIDIM_ARRAY_CREATIONOPTIONLIST))
624 : {
625 142 : InitMetadata();
626 : }
627 30441 : 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 178 : void ZarrDriver::InitMetadata()
638 : {
639 178 : if (m_bMetadataInitialized)
640 169 : 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 84 : 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 168 : const std::string osName = pszName;
1001 84 : pszName = osName.c_str();
1002 :
1003 84 : 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 83 : const bool bAppendSubDS = CPLTestBool(
1011 : CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO"));
1012 83 : const char *pszArrayName = CSLFetchNameValue(papszOptions, "ARRAY_NAME");
1013 :
1014 83 : std::shared_ptr<ZarrGroupBase> poRG;
1015 83 : 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 79 : const bool bExists = VSIStatL(pszName, &sStat) == 0;
1037 79 : const bool bIsFile = bExists && !VSI_ISDIR(sStat.st_mode);
1038 : const bool bIsDirectory =
1039 158 : !bIsFile && ((bExists && VSI_ISDIR(sStat.st_mode)) ||
1040 158 : !CPLStringList(VSIReadDirEx(pszName, 1)).empty());
1041 79 : 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 79 : CSLFetchNameValueDef(papszOptions, "FORMAT", "ZARR_V2");
1053 : auto poSharedResource =
1054 237 : ZarrSharedResource::Create(pszName, /*bUpdatable=*/true);
1055 79 : if (EQUAL(pszFormat, "ZARR_V3"))
1056 : {
1057 10 : poRG = ZarrV3Group::CreateOnDisk(poSharedResource, std::string(),
1058 5 : "/", pszName);
1059 : }
1060 : else
1061 : {
1062 74 : const bool bCreateZMetadata = CPLTestBool(
1063 : CSLFetchNameValueDef(papszOptions, "CREATE_ZMETADATA", "YES"));
1064 74 : if (bCreateZMetadata)
1065 : {
1066 74 : poSharedResource->EnableZMetadata();
1067 : }
1068 148 : poRG = ZarrV2Group::CreateOnDisk(poSharedResource, std::string(),
1069 74 : "/", pszName);
1070 : }
1071 79 : poSharedResource->SetRootGroup(poRG);
1072 : }
1073 83 : if (!poRG)
1074 3 : return nullptr;
1075 :
1076 160 : auto poDS = std::make_unique<ZarrDataset>(poRG);
1077 80 : poDS->SetDescription(pszName);
1078 80 : poDS->nRasterYSize = nYSize;
1079 80 : poDS->nRasterXSize = nXSize;
1080 80 : 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(
1093 4 : CPLFormFilenameSafe(pszName, pszArrayName, nullptr).c_str());
1094 : }
1095 : else
1096 : {
1097 : // Be a bit careful before wiping too much stuff...
1098 : // At most 5 files expected for ZARR_V2: .zgroup, .zmetadata,
1099 : // one (empty) subdir, . and ..
1100 : // and for ZARR_V3: zarr.json, one (empty) subdir, . and ..
1101 16 : const CPLStringList aosFiles(VSIReadDirEx(pszName, 6));
1102 8 : if (aosFiles.size() < 6)
1103 : {
1104 40 : for (const char *pszFile : aosFiles)
1105 : {
1106 32 : if (pszArrayName && strcmp(pszFile, pszArrayName) == 0)
1107 : {
1108 0 : VSIRmdir(CPLFormFilenameSafe(pszName, pszFile, nullptr)
1109 : .c_str());
1110 : }
1111 64 : else if (!pszArrayName &&
1112 32 : strcmp(pszFile,
1113 64 : CPLGetBasenameSafe(pszName).c_str()) == 0)
1114 : {
1115 1 : VSIRmdir(CPLFormFilenameSafe(pszName, pszFile, nullptr)
1116 : .c_str());
1117 : }
1118 31 : else if (strcmp(pszFile, ".zgroup") == 0 ||
1119 24 : strcmp(pszFile, ".zmetadata") == 0 ||
1120 17 : strcmp(pszFile, "zarr.json") == 0)
1121 : {
1122 15 : VSIUnlink(CPLFormFilenameSafe(pszName, pszFile, nullptr)
1123 : .c_str());
1124 : }
1125 : }
1126 8 : VSIRmdir(pszName);
1127 : }
1128 : }
1129 10 : };
1130 :
1131 80 : if (bAppendSubDS)
1132 : {
1133 8 : auto aoDims = poRG->GetDimensions();
1134 12 : for (const auto &poDim : aoDims)
1135 : {
1136 12 : if (poDim->GetName() == "Y" &&
1137 4 : poDim->GetSize() == static_cast<GUInt64>(nYSize))
1138 : {
1139 1 : poDS->m_poDimY = poDim;
1140 : }
1141 11 : else if (poDim->GetName() == "X" &&
1142 4 : poDim->GetSize() == static_cast<GUInt64>(nXSize))
1143 : {
1144 1 : poDS->m_poDimX = poDim;
1145 : }
1146 : }
1147 4 : if (poDS->m_poDimY == nullptr)
1148 : {
1149 3 : poDS->m_poDimY =
1150 9 : poRG->CreateDimension(std::string(pszArrayName) + "_Y",
1151 9 : std::string(), std::string(), nYSize);
1152 : }
1153 4 : if (poDS->m_poDimX == nullptr)
1154 : {
1155 3 : poDS->m_poDimX =
1156 9 : poRG->CreateDimension(std::string(pszArrayName) + "_X",
1157 9 : std::string(), std::string(), nXSize);
1158 : }
1159 : }
1160 : else
1161 : {
1162 76 : poDS->m_poDimY =
1163 152 : poRG->CreateDimension("Y", std::string(), std::string(), nYSize);
1164 76 : poDS->m_poDimX =
1165 152 : poRG->CreateDimension("X", std::string(), std::string(), nXSize);
1166 : }
1167 80 : if (poDS->m_poDimY == nullptr || poDS->m_poDimX == nullptr)
1168 : {
1169 0 : CleanupCreatedFiles();
1170 0 : return nullptr;
1171 : }
1172 :
1173 : const bool bSingleArray =
1174 80 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "SINGLE_ARRAY", "YES"));
1175 : const bool bBandInterleave =
1176 80 : EQUAL(CSLFetchNameValueDef(papszOptions, "INTERLEAVE", "BAND"), "BAND");
1177 : const std::shared_ptr<GDALDimension> poBandDim(
1178 78 : (bSingleArray && nBandsIn > 1)
1179 104 : ? poRG->CreateDimension("Band", std::string(), std::string(),
1180 24 : nBandsIn)
1181 310 : : nullptr);
1182 :
1183 : const std::string osNonNullArrayName =
1184 160 : pszArrayName ? std::string(pszArrayName) : CPLGetBasenameSafe(pszName);
1185 80 : if (poBandDim)
1186 : {
1187 : const std::vector<std::shared_ptr<GDALDimension>> apoDims(
1188 : bBandInterleave
1189 : ? std::vector<std::shared_ptr<GDALDimension>>{poBandDim,
1190 23 : poDS->m_poDimY,
1191 92 : poDS->m_poDimX}
1192 : : std::vector<std::shared_ptr<GDALDimension>>{
1193 98 : poDS->m_poDimY, poDS->m_poDimX, poBandDim});
1194 72 : poDS->m_poSingleArray = poRG->CreateMDArray(
1195 : osNonNullArrayName.c_str(), apoDims,
1196 72 : GDALExtendedDataType::Create(eType), papszOptions);
1197 24 : if (!poDS->m_poSingleArray)
1198 : {
1199 2 : CleanupCreatedFiles();
1200 2 : return nullptr;
1201 : }
1202 44 : poDS->SetMetadataItem("INTERLEAVE", bBandInterleave ? "BAND" : "PIXEL",
1203 22 : "IMAGE_STRUCTURE");
1204 92 : for (int i = 0; i < nBandsIn; i++)
1205 : {
1206 70 : auto poSlicedArray = poDS->m_poSingleArray->GetView(
1207 210 : CPLSPrintf(bBandInterleave ? "[%d,::,::]" : "[::,::,%d]", i));
1208 70 : poDS->SetBand(i + 1, new ZarrRasterBand(poSlicedArray));
1209 : }
1210 : }
1211 : else
1212 : {
1213 : const auto apoDims = std::vector<std::shared_ptr<GDALDimension>>{
1214 224 : poDS->m_poDimY, poDS->m_poDimX};
1215 108 : for (int i = 0; i < nBandsIn; i++)
1216 : {
1217 60 : auto poArray = poRG->CreateMDArray(
1218 54 : nBandsIn == 1 ? osNonNullArrayName.c_str()
1219 6 : : pszArrayName ? CPLSPrintf("%s_band%d", pszArrayName, i + 1)
1220 0 : : CPLSPrintf("Band%d", i + 1),
1221 180 : apoDims, GDALExtendedDataType::Create(eType), papszOptions);
1222 60 : if (poArray == nullptr)
1223 : {
1224 8 : CleanupCreatedFiles();
1225 8 : return nullptr;
1226 : }
1227 52 : poDS->SetBand(i + 1, new ZarrRasterBand(poArray));
1228 : }
1229 : }
1230 :
1231 70 : return poDS.release();
1232 : }
1233 :
1234 : /************************************************************************/
1235 : /* ~ZarrDataset() */
1236 : /************************************************************************/
1237 :
1238 2012 : ZarrDataset::~ZarrDataset()
1239 : {
1240 1006 : ZarrDataset::FlushCache(true);
1241 2012 : }
1242 :
1243 : /************************************************************************/
1244 : /* FlushCache() */
1245 : /************************************************************************/
1246 :
1247 1009 : CPLErr ZarrDataset::FlushCache(bool bAtClosing)
1248 : {
1249 1009 : CPLErr eErr = GDALDataset::FlushCache(bAtClosing);
1250 1009 : if (m_poSingleArray)
1251 : {
1252 23 : bool bFound = false;
1253 96 : for (int i = 0; i < nBands; ++i)
1254 : {
1255 73 : if (papoBands[i]->GetColorInterpretation() != GCI_Undefined)
1256 12 : bFound = true;
1257 : }
1258 23 : if (bFound)
1259 : {
1260 8 : const auto oStringDT = GDALExtendedDataType::CreateString();
1261 12 : auto poAttr = m_poSingleArray->GetAttribute("COLOR_INTERPRETATION");
1262 4 : if (!poAttr)
1263 9 : poAttr = m_poSingleArray->CreateAttribute(
1264 3 : "COLOR_INTERPRETATION", {static_cast<GUInt64>(nBands)},
1265 6 : oStringDT);
1266 4 : if (poAttr)
1267 : {
1268 4 : const GUInt64 nStartIndex = 0;
1269 4 : const size_t nCount = nBands;
1270 4 : const GInt64 arrayStep = 1;
1271 4 : const GPtrDiff_t bufferStride = 1;
1272 8 : std::vector<const char *> apszValues;
1273 16 : for (int i = 0; i < nBands; ++i)
1274 : {
1275 : const auto eColorInterp =
1276 12 : papoBands[i]->GetColorInterpretation();
1277 12 : apszValues.push_back(
1278 12 : GDALGetColorInterpretationName(eColorInterp));
1279 : }
1280 8 : poAttr->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
1281 4 : oStringDT, apszValues.data());
1282 : }
1283 : }
1284 : }
1285 1009 : return eErr;
1286 : }
1287 :
1288 : /************************************************************************/
1289 : /* GetSpatialRef() */
1290 : /************************************************************************/
1291 :
1292 2 : const OGRSpatialReference *ZarrDataset::GetSpatialRef() const
1293 : {
1294 2 : if (nBands >= 1)
1295 2 : return cpl::down_cast<ZarrRasterBand *>(papoBands[0])
1296 4 : ->m_poArray->GetSpatialRef()
1297 2 : .get();
1298 0 : return nullptr;
1299 : }
1300 :
1301 : /************************************************************************/
1302 : /* SetSpatialRef() */
1303 : /************************************************************************/
1304 :
1305 57 : CPLErr ZarrDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
1306 : {
1307 162 : for (int i = 0; i < nBands; ++i)
1308 : {
1309 105 : cpl::down_cast<ZarrRasterBand *>(papoBands[i])
1310 105 : ->m_poArray->SetSpatialRef(poSRS);
1311 : }
1312 57 : return CE_None;
1313 : }
1314 :
1315 : /************************************************************************/
1316 : /* GetGeoTransform() */
1317 : /************************************************************************/
1318 :
1319 29 : CPLErr ZarrDataset::GetGeoTransform(double *padfTransform)
1320 : {
1321 29 : memcpy(padfTransform, &m_adfGeoTransform[0], 6 * sizeof(double));
1322 29 : return m_bHasGT ? CE_None : CE_Failure;
1323 : }
1324 :
1325 : /************************************************************************/
1326 : /* SetGeoTransform() */
1327 : /************************************************************************/
1328 :
1329 59 : CPLErr ZarrDataset::SetGeoTransform(double *padfTransform)
1330 : {
1331 59 : if (padfTransform[2] != 0 || padfTransform[4] != 0)
1332 : {
1333 0 : CPLError(CE_Failure, CPLE_NotSupported,
1334 : "Geotransform with rotated terms not supported");
1335 0 : return CE_Failure;
1336 : }
1337 59 : if (m_poDimX == nullptr || m_poDimY == nullptr)
1338 0 : return CE_Failure;
1339 :
1340 59 : memcpy(&m_adfGeoTransform[0], padfTransform, 6 * sizeof(double));
1341 59 : m_bHasGT = true;
1342 :
1343 118 : const auto oDTFloat64 = GDALExtendedDataType::Create(GDT_Float64);
1344 : {
1345 59 : auto poX = m_poRootGroup->OpenMDArray(m_poDimX->GetName());
1346 59 : if (!poX)
1347 232 : poX = m_poRootGroup->CreateMDArray(m_poDimX->GetName(), {m_poDimX},
1348 174 : oDTFloat64, nullptr);
1349 59 : if (!poX)
1350 0 : return CE_Failure;
1351 59 : m_poDimX->SetIndexingVariable(poX);
1352 59 : std::vector<double> adfX;
1353 : try
1354 : {
1355 59 : adfX.reserve(nRasterXSize);
1356 3585 : for (int i = 0; i < nRasterXSize; ++i)
1357 3526 : adfX.emplace_back(padfTransform[0] +
1358 3526 : padfTransform[1] * (i + 0.5));
1359 : }
1360 0 : catch (const std::exception &)
1361 : {
1362 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
1363 : "Out of memory when allocating X array");
1364 0 : return CE_Failure;
1365 : }
1366 59 : const GUInt64 nStartIndex = 0;
1367 59 : const size_t nCount = adfX.size();
1368 59 : const GInt64 arrayStep = 1;
1369 59 : const GPtrDiff_t bufferStride = 1;
1370 118 : if (!poX->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
1371 59 : poX->GetDataType(), adfX.data()))
1372 : {
1373 0 : return CE_Failure;
1374 : }
1375 : }
1376 :
1377 : {
1378 59 : auto poY = m_poRootGroup->OpenMDArray(m_poDimY->GetName());
1379 59 : if (!poY)
1380 232 : poY = m_poRootGroup->CreateMDArray(m_poDimY->GetName(), {m_poDimY},
1381 174 : oDTFloat64, nullptr);
1382 59 : if (!poY)
1383 0 : return CE_Failure;
1384 59 : m_poDimY->SetIndexingVariable(poY);
1385 59 : std::vector<double> adfY;
1386 : try
1387 : {
1388 59 : adfY.reserve(nRasterYSize);
1389 3373 : for (int i = 0; i < nRasterYSize; ++i)
1390 3314 : adfY.emplace_back(padfTransform[3] +
1391 3314 : padfTransform[5] * (i + 0.5));
1392 : }
1393 0 : catch (const std::exception &)
1394 : {
1395 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
1396 : "Out of memory when allocating Y array");
1397 0 : return CE_Failure;
1398 : }
1399 59 : const GUInt64 nStartIndex = 0;
1400 59 : const size_t nCount = adfY.size();
1401 59 : const GInt64 arrayStep = 1;
1402 59 : const GPtrDiff_t bufferStride = 1;
1403 118 : if (!poY->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
1404 59 : poY->GetDataType(), adfY.data()))
1405 : {
1406 0 : return CE_Failure;
1407 : }
1408 : }
1409 :
1410 59 : return CE_None;
1411 : }
1412 :
1413 : /************************************************************************/
1414 : /* SetMetadata() */
1415 : /************************************************************************/
1416 :
1417 31 : CPLErr ZarrDataset::SetMetadata(char **papszMetadata, const char *pszDomain)
1418 : {
1419 31 : if (nBands >= 1 && (pszDomain == nullptr || pszDomain[0] == '\0'))
1420 : {
1421 62 : const auto oStringDT = GDALExtendedDataType::CreateString();
1422 31 : const auto bSingleArray = m_poSingleArray != nullptr;
1423 31 : const int nIters = bSingleArray ? 1 : nBands;
1424 66 : for (int i = 0; i < nIters; ++i)
1425 : {
1426 : auto *poArray = bSingleArray
1427 35 : ? m_poSingleArray.get()
1428 28 : : cpl::down_cast<ZarrRasterBand *>(papoBands[i])
1429 28 : ->m_poArray.get();
1430 70 : for (auto iter = papszMetadata; iter && *iter; ++iter)
1431 : {
1432 35 : char *pszKey = nullptr;
1433 35 : const char *pszValue = CPLParseNameValue(*iter, &pszKey);
1434 35 : if (pszKey && pszValue)
1435 : {
1436 : auto poAttr =
1437 36 : poArray->CreateAttribute(pszKey, {}, oStringDT);
1438 12 : if (poAttr)
1439 : {
1440 12 : const GUInt64 nStartIndex = 0;
1441 12 : const size_t nCount = 1;
1442 12 : const GInt64 arrayStep = 1;
1443 12 : const GPtrDiff_t bufferStride = 1;
1444 12 : poAttr->Write(&nStartIndex, &nCount, &arrayStep,
1445 : &bufferStride, oStringDT, &pszValue);
1446 : }
1447 : }
1448 35 : CPLFree(pszKey);
1449 : }
1450 : }
1451 : }
1452 31 : return GDALDataset::SetMetadata(papszMetadata, pszDomain);
1453 : }
1454 :
1455 : /************************************************************************/
1456 : /* ZarrRasterBand::ZarrRasterBand() */
1457 : /************************************************************************/
1458 :
1459 122 : ZarrRasterBand::ZarrRasterBand(const std::shared_ptr<GDALMDArray> &poArray)
1460 122 : : m_poArray(poArray)
1461 : {
1462 122 : assert(poArray->GetDimensionCount() == 2);
1463 122 : eDataType = poArray->GetDataType().GetNumericDataType();
1464 122 : nBlockXSize = static_cast<int>(poArray->GetBlockSize()[1]);
1465 122 : nBlockYSize = static_cast<int>(poArray->GetBlockSize()[0]);
1466 122 : }
1467 :
1468 : /************************************************************************/
1469 : /* GetNoDataValue() */
1470 : /************************************************************************/
1471 :
1472 6 : double ZarrRasterBand::GetNoDataValue(int *pbHasNoData)
1473 : {
1474 6 : bool bHasNodata = false;
1475 6 : const auto res = m_poArray->GetNoDataValueAsDouble(&bHasNodata);
1476 6 : if (pbHasNoData)
1477 6 : *pbHasNoData = bHasNodata;
1478 6 : return res;
1479 : }
1480 :
1481 : /************************************************************************/
1482 : /* GetNoDataValueAsInt64() */
1483 : /************************************************************************/
1484 :
1485 0 : int64_t ZarrRasterBand::GetNoDataValueAsInt64(int *pbHasNoData)
1486 : {
1487 0 : bool bHasNodata = false;
1488 0 : const auto res = m_poArray->GetNoDataValueAsInt64(&bHasNodata);
1489 0 : if (pbHasNoData)
1490 0 : *pbHasNoData = bHasNodata;
1491 0 : return res;
1492 : }
1493 :
1494 : /************************************************************************/
1495 : /* GetNoDataValueAsUInt64() */
1496 : /************************************************************************/
1497 :
1498 0 : uint64_t ZarrRasterBand::GetNoDataValueAsUInt64(int *pbHasNoData)
1499 : {
1500 0 : bool bHasNodata = false;
1501 0 : const auto res = m_poArray->GetNoDataValueAsUInt64(&bHasNodata);
1502 0 : if (pbHasNoData)
1503 0 : *pbHasNoData = bHasNodata;
1504 0 : return res;
1505 : }
1506 :
1507 : /************************************************************************/
1508 : /* SetNoDataValue() */
1509 : /************************************************************************/
1510 :
1511 2 : CPLErr ZarrRasterBand::SetNoDataValue(double dfNoData)
1512 : {
1513 2 : return m_poArray->SetNoDataValue(dfNoData) ? CE_None : CE_Failure;
1514 : }
1515 :
1516 : /************************************************************************/
1517 : /* SetNoDataValueAsInt64() */
1518 : /************************************************************************/
1519 :
1520 0 : CPLErr ZarrRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
1521 : {
1522 0 : return m_poArray->SetNoDataValue(nNoData) ? CE_None : CE_Failure;
1523 : }
1524 :
1525 : /************************************************************************/
1526 : /* SetNoDataValueAsUInt64() */
1527 : /************************************************************************/
1528 :
1529 0 : CPLErr ZarrRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
1530 : {
1531 0 : return m_poArray->SetNoDataValue(nNoData) ? CE_None : CE_Failure;
1532 : }
1533 :
1534 : /************************************************************************/
1535 : /* GetOffset() */
1536 : /************************************************************************/
1537 :
1538 2 : double ZarrRasterBand::GetOffset(int *pbSuccess)
1539 : {
1540 2 : bool bHasValue = false;
1541 2 : double dfRet = m_poArray->GetOffset(&bHasValue);
1542 2 : if (pbSuccess)
1543 2 : *pbSuccess = bHasValue ? TRUE : FALSE;
1544 2 : return dfRet;
1545 : }
1546 :
1547 : /************************************************************************/
1548 : /* SetOffset() */
1549 : /************************************************************************/
1550 :
1551 2 : CPLErr ZarrRasterBand::SetOffset(double dfNewOffset)
1552 : {
1553 2 : return m_poArray->SetOffset(dfNewOffset) ? CE_None : CE_Failure;
1554 : }
1555 :
1556 : /************************************************************************/
1557 : /* GetScale() */
1558 : /************************************************************************/
1559 :
1560 2 : double ZarrRasterBand::GetScale(int *pbSuccess)
1561 : {
1562 2 : bool bHasValue = false;
1563 2 : double dfRet = m_poArray->GetScale(&bHasValue);
1564 2 : if (pbSuccess)
1565 2 : *pbSuccess = bHasValue ? TRUE : FALSE;
1566 2 : return dfRet;
1567 : }
1568 :
1569 : /************************************************************************/
1570 : /* SetScale() */
1571 : /************************************************************************/
1572 :
1573 2 : CPLErr ZarrRasterBand::SetScale(double dfNewScale)
1574 : {
1575 2 : return m_poArray->SetScale(dfNewScale) ? CE_None : CE_Failure;
1576 : }
1577 :
1578 : /************************************************************************/
1579 : /* GetUnitType() */
1580 : /************************************************************************/
1581 :
1582 2 : const char *ZarrRasterBand::GetUnitType()
1583 : {
1584 2 : return m_poArray->GetUnit().c_str();
1585 : }
1586 :
1587 : /************************************************************************/
1588 : /* SetUnitType() */
1589 : /************************************************************************/
1590 :
1591 2 : CPLErr ZarrRasterBand::SetUnitType(const char *pszNewValue)
1592 : {
1593 4 : return m_poArray->SetUnit(pszNewValue ? pszNewValue : "") ? CE_None
1594 4 : : CE_Failure;
1595 : }
1596 :
1597 : /************************************************************************/
1598 : /* GetColorInterpretation() */
1599 : /************************************************************************/
1600 :
1601 97 : GDALColorInterp ZarrRasterBand::GetColorInterpretation()
1602 : {
1603 97 : return m_eColorInterp;
1604 : }
1605 :
1606 : /************************************************************************/
1607 : /* SetColorInterpretation() */
1608 : /************************************************************************/
1609 :
1610 12 : CPLErr ZarrRasterBand::SetColorInterpretation(GDALColorInterp eColorInterp)
1611 : {
1612 12 : auto poGDS = cpl::down_cast<ZarrDataset *>(poDS);
1613 12 : m_eColorInterp = eColorInterp;
1614 12 : if (!poGDS->m_poSingleArray)
1615 : {
1616 3 : const auto oStringDT = GDALExtendedDataType::CreateString();
1617 6 : auto poAttr = m_poArray->GetAttribute("COLOR_INTERPRETATION");
1618 3 : if (poAttr && (poAttr->GetDimensionCount() != 0 ||
1619 3 : poAttr->GetDataType().GetClass() != GEDTC_STRING))
1620 0 : return CE_None;
1621 3 : if (!poAttr)
1622 9 : poAttr = m_poArray->CreateAttribute("COLOR_INTERPRETATION", {},
1623 6 : oStringDT);
1624 3 : if (poAttr)
1625 : {
1626 3 : const GUInt64 nStartIndex = 0;
1627 3 : const size_t nCount = 1;
1628 3 : const GInt64 arrayStep = 1;
1629 3 : const GPtrDiff_t bufferStride = 1;
1630 3 : const char *pszValue = GDALGetColorInterpretationName(eColorInterp);
1631 3 : poAttr->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
1632 : oStringDT, &pszValue);
1633 : }
1634 : }
1635 12 : return CE_None;
1636 : }
1637 :
1638 : /************************************************************************/
1639 : /* ZarrRasterBand::IReadBlock() */
1640 : /************************************************************************/
1641 :
1642 2 : CPLErr ZarrRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pData)
1643 : {
1644 :
1645 2 : const int nXOff = nBlockXOff * nBlockXSize;
1646 2 : const int nYOff = nBlockYOff * nBlockYSize;
1647 2 : const int nReqXSize = std::min(nRasterXSize - nXOff, nBlockXSize);
1648 2 : const int nReqYSize = std::min(nRasterYSize - nYOff, nBlockYSize);
1649 2 : GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
1650 2 : static_cast<GUInt64>(nXOff)};
1651 2 : size_t count[] = {static_cast<size_t>(nReqYSize),
1652 2 : static_cast<size_t>(nReqXSize)};
1653 2 : constexpr GInt64 arrayStep[] = {1, 1};
1654 2 : GPtrDiff_t bufferStride[] = {nBlockXSize, 1};
1655 4 : return m_poArray->Read(arrayStartIdx, count, arrayStep, bufferStride,
1656 2 : m_poArray->GetDataType(), pData)
1657 2 : ? CE_None
1658 2 : : CE_Failure;
1659 : }
1660 :
1661 : /************************************************************************/
1662 : /* ZarrRasterBand::IWriteBlock() */
1663 : /************************************************************************/
1664 :
1665 0 : CPLErr ZarrRasterBand::IWriteBlock(int nBlockXOff, int nBlockYOff, void *pData)
1666 : {
1667 0 : const int nXOff = nBlockXOff * nBlockXSize;
1668 0 : const int nYOff = nBlockYOff * nBlockYSize;
1669 0 : const int nReqXSize = std::min(nRasterXSize - nXOff, nBlockXSize);
1670 0 : const int nReqYSize = std::min(nRasterYSize - nYOff, nBlockYSize);
1671 0 : GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
1672 0 : static_cast<GUInt64>(nXOff)};
1673 0 : size_t count[] = {static_cast<size_t>(nReqYSize),
1674 0 : static_cast<size_t>(nReqXSize)};
1675 0 : constexpr GInt64 arrayStep[] = {1, 1};
1676 0 : GPtrDiff_t bufferStride[] = {nBlockXSize, 1};
1677 0 : return m_poArray->Write(arrayStartIdx, count, arrayStep, bufferStride,
1678 0 : m_poArray->GetDataType(), pData)
1679 0 : ? CE_None
1680 0 : : CE_Failure;
1681 : }
1682 :
1683 : /************************************************************************/
1684 : /* IRasterIO() */
1685 : /************************************************************************/
1686 :
1687 48 : CPLErr ZarrRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
1688 : int nXSize, int nYSize, void *pData,
1689 : int nBufXSize, int nBufYSize,
1690 : GDALDataType eBufType, GSpacing nPixelSpaceBuf,
1691 : GSpacing nLineSpaceBuf,
1692 : GDALRasterIOExtraArg *psExtraArg)
1693 : {
1694 48 : const int nBufferDTSize(GDALGetDataTypeSizeBytes(eBufType));
1695 48 : if (nXSize == nBufXSize && nYSize == nBufYSize && nBufferDTSize > 0 &&
1696 48 : (nPixelSpaceBuf % nBufferDTSize) == 0 &&
1697 48 : (nLineSpaceBuf % nBufferDTSize) == 0)
1698 : {
1699 48 : GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
1700 48 : static_cast<GUInt64>(nXOff)};
1701 48 : size_t count[] = {static_cast<size_t>(nYSize),
1702 48 : static_cast<size_t>(nXSize)};
1703 48 : constexpr GInt64 arrayStep[] = {1, 1};
1704 : GPtrDiff_t bufferStride[] = {
1705 48 : static_cast<GPtrDiff_t>(nLineSpaceBuf / nBufferDTSize),
1706 48 : static_cast<GPtrDiff_t>(nPixelSpaceBuf / nBufferDTSize)};
1707 :
1708 48 : if (eRWFlag == GF_Read)
1709 : {
1710 4 : return m_poArray->Read(
1711 : arrayStartIdx, count, arrayStep, bufferStride,
1712 4 : GDALExtendedDataType::Create(eBufType), pData)
1713 2 : ? CE_None
1714 2 : : CE_Failure;
1715 : }
1716 : else
1717 : {
1718 92 : return m_poArray->Write(
1719 : arrayStartIdx, count, arrayStep, bufferStride,
1720 92 : GDALExtendedDataType::Create(eBufType), pData)
1721 46 : ? CE_None
1722 46 : : CE_Failure;
1723 : }
1724 : }
1725 0 : return GDALRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
1726 : pData, nBufXSize, nBufYSize, eBufType,
1727 0 : nPixelSpaceBuf, nLineSpaceBuf, psExtraArg);
1728 : }
1729 :
1730 : /************************************************************************/
1731 : /* GDALRegister_Zarr() */
1732 : /************************************************************************/
1733 :
1734 1682 : void GDALRegister_Zarr()
1735 :
1736 : {
1737 1682 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
1738 301 : return;
1739 :
1740 1381 : GDALDriver *poDriver = new ZarrDriver();
1741 1381 : ZARRDriverSetCommonMetadata(poDriver);
1742 :
1743 1381 : poDriver->pfnOpen = ZarrDataset::Open;
1744 1381 : poDriver->pfnCreateMultiDimensional = ZarrDataset::CreateMultiDimensional;
1745 1381 : poDriver->pfnCreate = ZarrDataset::Create;
1746 1381 : poDriver->pfnDelete = ZarrDatasetDelete;
1747 1381 : poDriver->pfnRename = ZarrDatasetRename;
1748 1381 : poDriver->pfnCopyFiles = ZarrDatasetCopyFiles;
1749 :
1750 1381 : GetGDALDriverManager()->RegisterDriver(poDriver);
1751 : }
|