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