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