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