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