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