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