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 2034 : ZarrDataset::ZarrDataset(const std::shared_ptr<ZarrGroupBase> &poRootGroup)
37 2034 : : m_poRootGroup(poRootGroup)
38 : {
39 2034 : }
40 :
41 : /************************************************************************/
42 : /* OpenMultidim() */
43 : /************************************************************************/
44 :
45 1623 : GDALDataset *ZarrDataset::OpenMultidim(const char *pszFilename,
46 : bool bUpdateMode,
47 : CSLConstList papszOpenOptionsIn)
48 : {
49 3246 : CPLString osFilename(pszFilename);
50 1623 : if (osFilename.back() == '/')
51 0 : osFilename.pop_back();
52 :
53 3246 : auto poSharedResource = ZarrSharedResource::Create(osFilename, bUpdateMode);
54 1623 : poSharedResource->SetOpenOptions(papszOpenOptionsIn);
55 :
56 3246 : auto poRG = poSharedResource->GetRootGroup();
57 1623 : 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 1515 : return new ZarrDataset(poRG);
68 : }
69 :
70 : /************************************************************************/
71 : /* ExploreGroup() */
72 : /************************************************************************/
73 :
74 141 : static bool ExploreGroup(const std::shared_ptr<GDALGroup> &poGroup,
75 : std::vector<std::string> &aosArrays, int nRecCount)
76 : {
77 141 : if (nRecCount == 32)
78 : {
79 0 : CPLError(CE_Failure, CPLE_NotSupported,
80 : "Too deep recursion level in ExploreGroup()");
81 0 : return false;
82 : }
83 282 : const auto aosGroupArrayNames = poGroup->GetMDArrayNames();
84 369 : for (const auto &osArrayName : aosGroupArrayNames)
85 : {
86 228 : std::string osArrayFullname = poGroup->GetFullName();
87 228 : if (osArrayName != "/")
88 : {
89 228 : if (osArrayFullname != "/")
90 8 : osArrayFullname += '/';
91 228 : osArrayFullname += osArrayName;
92 : }
93 228 : aosArrays.emplace_back(std::move(osArrayFullname));
94 228 : 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 282 : const auto aosSubGroups = poGroup->GetGroupNames();
103 155 : 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 141 : 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 155 : static void GetXYDimensionIndices(const std::shared_ptr<GDALMDArray> &poArray,
147 : const GDALOpenInfo *poOpenInfo, size_t &iXDim,
148 : size_t &iYDim)
149 : {
150 155 : const size_t nDims = poArray->GetDimensionCount();
151 155 : iYDim = nDims >= 2 ? nDims - 2 : 0;
152 155 : iXDim = nDims >= 2 ? nDims - 1 : 0;
153 :
154 155 : if (nDims >= 2)
155 : {
156 : const char *pszDimX =
157 139 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "DIM_X");
158 : const char *pszDimY =
159 139 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "DIM_Y");
160 139 : bool bFoundX = false;
161 139 : bool bFoundY = false;
162 139 : const auto &apoDims = poArray->GetDimensions();
163 470 : for (size_t i = 0; i < nDims; ++i)
164 : {
165 331 : if (pszDimX && apoDims[i]->GetName() == pszDimX)
166 : {
167 1 : bFoundX = true;
168 1 : iXDim = i;
169 : }
170 330 : else if (pszDimY && apoDims[i]->GetName() == pszDimY)
171 : {
172 1 : bFoundY = true;
173 1 : iYDim = i;
174 : }
175 965 : else if (!pszDimX &&
176 636 : (apoDims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X ||
177 317 : apoDims[i]->GetName() == "X"))
178 72 : iXDim = i;
179 749 : else if (!pszDimY &&
180 492 : (apoDims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y ||
181 245 : apoDims[i]->GetName() == "Y"))
182 72 : iYDim = i;
183 : }
184 139 : 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 139 : 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 155 : }
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 139 : static void PrefetchCoordArrays(const std::shared_ptr<GDALMDArray> &poArray,
248 : size_t iXDim, size_t iYDim)
249 : {
250 139 : const auto nDimCount = poArray->GetDimensionCount();
251 139 : if (nDimCount < 2 || iXDim >= nDimCount || iYDim >= nDimCount)
252 139 : return;
253 122 : const auto &dims = poArray->GetDimensions();
254 122 : auto poVarX = dims[iXDim]->GetIndexingVariable();
255 122 : auto poVarY = dims[iYDim]->GetIndexingVariable();
256 170 : if (!poVarX || poVarX->GetDimensionCount() != 1 || !poVarY ||
257 48 : poVarY->GetDimensionCount() != 1)
258 74 : return;
259 48 : if (VSIIsLocal(poVarX->GetFilename().c_str()))
260 48 : 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 1641 : GDALDataset *ZarrDataset::Open(GDALOpenInfo *poOpenInfo)
275 : {
276 1641 : 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 1641 : 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 1640 : const bool bKerchunkCached = CPLFetchBool(poOpenInfo->papszOpenOptions,
300 : "CACHE_KERCHUNK_JSON", false);
301 :
302 1640 : 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 1621 : else if (STARTS_WITH(poOpenInfo->pszFilename, JSON_REF_FS_PREFIX) ||
318 1619 : 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 3238 : CPLString osFilename(poOpenInfo->pszFilename);
329 1619 : if (!poOpenInfo->bIsDirectory)
330 : {
331 115 : osFilename = CPLGetPathSafe(osFilename);
332 : }
333 3238 : CPLString osArrayOfInterest;
334 3238 : std::vector<uint64_t> anExtraDimIndices;
335 1619 : 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 1614 : OpenMultidim(osFilename.c_str(), poOpenInfo->eAccess == GA_Update,
397 3228 : poOpenInfo->papszOpenOptions));
398 3125 : if (poDSMultiDim == nullptr ||
399 1511 : (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER) != 0)
400 : {
401 1464 : return poDSMultiDim.release();
402 : }
403 :
404 300 : auto poRG = poDSMultiDim->GetRootGroup();
405 :
406 300 : auto poDS = std::make_unique<ZarrDataset>(nullptr);
407 150 : std::shared_ptr<GDALMDArray> poMainArray;
408 300 : std::vector<std::string> aosArrays;
409 300 : std::string osMainArray;
410 150 : const bool bMultiband = CPLTestBool(
411 150 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "MULTIBAND", "YES"));
412 150 : size_t iXDim = 0;
413 150 : size_t iYDim = 0;
414 :
415 150 : 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 127 : ExploreGroup(poRG, aosArrays, 0);
491 127 : if (aosArrays.empty())
492 0 : return nullptr;
493 :
494 127 : const bool bListAllArrays = CPLTestBool(CSLFetchNameValueDef(
495 127 : poOpenInfo->papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
496 :
497 127 : if (!bListAllArrays)
498 : {
499 125 : 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 198 : for (const auto &osArrayName : aosArrays)
508 : {
509 148 : auto poArray = poRG->OpenMDArrayFromFullname(osArrayName);
510 199 : if (poArray && poArray->GetDimensionCount() >= 2 &&
511 51 : osArrayName.find("/ovr_") == std::string::npos)
512 : {
513 50 : if (osMainArray.empty())
514 : {
515 50 : poMainArray = std::move(poArray);
516 50 : osMainArray = osArrayName;
517 : }
518 : else
519 : {
520 0 : poMainArray.reset();
521 0 : osMainArray.clear();
522 0 : break;
523 : }
524 : }
525 : }
526 : }
527 :
528 125 : if (poMainArray)
529 125 : GetXYDimensionIndices(poMainArray, poOpenInfo, iXDim, iYDim);
530 : }
531 :
532 127 : int iCountSubDS = 1;
533 :
534 127 : 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 127 : if (bListAllArrays || aosArrays.size() >= 2)
606 : {
607 205 : for (size_t i = 0; i < aosArrays.size(); ++i)
608 : {
609 306 : auto poArray = poRG->OpenMDArrayFromFullname(aosArrays[i]);
610 153 : if (poArray && (bListAllArrays || aosArrays[i].find("/ovr_") ==
611 153 : std::string::npos))
612 : {
613 152 : bool bAddSubDS = false;
614 152 : if (bListAllArrays)
615 : {
616 5 : bAddSubDS = true;
617 : }
618 147 : else if (poArray->GetDimensionCount() >= 2)
619 : {
620 50 : bAddSubDS = true;
621 : }
622 152 : if (bAddSubDS)
623 : {
624 110 : std::string osDim;
625 55 : const auto &apoDims = poArray->GetDimensions();
626 180 : for (const auto &poDim : apoDims)
627 : {
628 125 : if (!osDim.empty())
629 70 : osDim += "x";
630 : osDim += CPLSPrintf(
631 : "%" PRIu64,
632 125 : static_cast<uint64_t>(poDim->GetSize()));
633 : }
634 :
635 55 : std::string osDataType;
636 55 : if (poArray->GetDataType().GetClass() == GEDTC_STRING)
637 : {
638 0 : osDataType = "string type";
639 : }
640 55 : else if (poArray->GetDataType().GetClass() ==
641 : GEDTC_NUMERIC)
642 : {
643 : osDataType = GDALGetDataTypeName(
644 55 : poArray->GetDataType().GetNumericDataType());
645 : }
646 : else
647 : {
648 0 : osDataType = "compound type";
649 : }
650 :
651 55 : poDS->m_aosSubdatasets.AddString(CPLSPrintf(
652 : "SUBDATASET_%d_NAME=ZARR:\"%s\":%s", iCountSubDS,
653 55 : osFilename.c_str(), aosArrays[i].c_str()));
654 55 : poDS->m_aosSubdatasets.AddString(CPLSPrintf(
655 : "SUBDATASET_%d_DESC=[%s] %s (%s)", iCountSubDS,
656 55 : osDim.c_str(), aosArrays[i].c_str(),
657 110 : osDataType.c_str()));
658 55 : ++iCountSubDS;
659 : }
660 : }
661 : }
662 : }
663 : }
664 :
665 145 : if (poMainArray && (bMultiband || poMainArray->GetDimensionCount() <= 2))
666 : {
667 139 : PrefetchCoordArrays(poMainArray, iXDim, iYDim);
668 :
669 : // Pass papszOpenOptions for LOAD_EXTRA_DIM_METADATA_DELAY
670 : auto poNewDS =
671 139 : std::unique_ptr<GDALDataset>(poMainArray->AsClassicDataset(
672 278 : iXDim, iYDim, poRG, poOpenInfo->papszOpenOptions));
673 139 : if (!poNewDS)
674 3 : return nullptr;
675 :
676 136 : 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 121 : if (aosArrays.size() == 3)
682 : {
683 88 : std::vector<std::string> aosOtherArrays;
684 176 : for (size_t i = 0; i < aosArrays.size(); ++i)
685 : {
686 132 : if (aosArrays[i] != osMainArray)
687 : {
688 88 : aosOtherArrays.emplace_back(aosArrays[i]);
689 : }
690 : }
691 44 : bool bMatchFound[] = {false, false};
692 132 : for (int i = 0; i < 2; i++)
693 : {
694 : auto poIndexingVar =
695 88 : poMainArray->GetDimensions()[i == 0 ? iXDim : iYDim]
696 176 : ->GetIndexingVariable();
697 88 : if (poIndexingVar)
698 : {
699 132 : for (int j = 0; j < 2; j++)
700 : {
701 130 : if (aosOtherArrays[j] ==
702 130 : poIndexingVar->GetFullName())
703 : {
704 84 : bMatchFound[i] = true;
705 84 : break;
706 : }
707 : }
708 : }
709 : }
710 44 : if (bMatchFound[0] && bMatchFound[1])
711 : {
712 42 : poDS->m_aosSubdatasets.Clear();
713 : }
714 : }
715 : }
716 136 : if (!poDS->m_aosSubdatasets.empty())
717 : {
718 8 : poNewDS->SetMetadata(poDS->m_aosSubdatasets.List(), "SUBDATASETS");
719 : }
720 136 : 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 36789 : const char *ZarrDriver::GetMetadataItem(const char *pszName,
812 : const char *pszDomain)
813 : {
814 73578 : std::lock_guard oLock(m_oMutex);
815 36789 : if (EQUAL(pszName, "COMPRESSORS") || EQUAL(pszName, "BLOSC_COMPRESSORS") ||
816 36752 : EQUAL(pszName, GDAL_DMD_CREATIONOPTIONLIST) ||
817 36401 : EQUAL(pszName, GDAL_DMD_MULTIDIM_ARRAY_CREATIONOPTIONLIST))
818 : {
819 389 : InitMetadata();
820 : }
821 73578 : 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 char *pszObjType = nullptr;
1262 90 : if (bExists && !VSI_ISDIR(sStat.st_mode))
1263 0 : pszObjType = "File";
1264 180 : else if ((bExists /* && VSI_ISDIR(sStat.st_mode)*/) ||
1265 180 : !CPLStringList(VSIReadDirEx(pszName, 1)).empty())
1266 0 : pszObjType = "Directory";
1267 90 : if (pszObjType)
1268 : {
1269 0 : CPLError(CE_Failure, CPLE_FileIO, "%s %s already exists.",
1270 : pszObjType, pszName);
1271 0 : return nullptr;
1272 : }
1273 :
1274 : const char *pszFormat =
1275 90 : CSLFetchNameValueDef(papszOptions, "FORMAT", "ZARR_V2");
1276 : auto poSharedResource =
1277 270 : ZarrSharedResource::Create(pszName, /*bUpdatable=*/true);
1278 90 : const bool bCreateZMetadata = CPLTestBool(CSLFetchNameValueDef(
1279 : papszOptions, "CREATE_CONSOLIDATED_METADATA",
1280 : CSLFetchNameValueDef(papszOptions, "CREATE_ZMETADATA", "YES")));
1281 90 : if (bCreateZMetadata)
1282 : {
1283 180 : poSharedResource->EnableConsolidatedMetadata(
1284 90 : EQUAL(pszFormat, "ZARR_V3")
1285 : ? ZarrSharedResource::ConsolidatedMetadataKind::INTERNAL
1286 : : ZarrSharedResource::ConsolidatedMetadataKind::EXTERNAL);
1287 : }
1288 90 : if (EQUAL(pszFormat, "ZARR_V3"))
1289 : {
1290 26 : poRG = ZarrV3Group::CreateOnDisk(poSharedResource, std::string(),
1291 13 : "/", pszName);
1292 : }
1293 : else
1294 : {
1295 154 : poRG = ZarrV2Group::CreateOnDisk(poSharedResource, std::string(),
1296 77 : "/", pszName);
1297 : }
1298 90 : poSharedResource->SetRootGroup(poRG);
1299 : }
1300 94 : if (!poRG)
1301 3 : return nullptr;
1302 :
1303 182 : auto poDS = std::make_unique<ZarrDataset>(poRG);
1304 91 : poDS->SetDescription(pszName);
1305 91 : poDS->nRasterYSize = nYSize;
1306 91 : poDS->nRasterXSize = nXSize;
1307 91 : poDS->eAccess = GA_Update;
1308 :
1309 : const auto CleanupCreatedFiles =
1310 160 : [bAppendSubDS, pszName, pszArrayName, &poRG, &poDS]()
1311 : {
1312 : // Make sure all objects are released so that ZarrSharedResource
1313 : // is finalized and all files are serialized.
1314 10 : poRG.reset();
1315 10 : poDS.reset();
1316 :
1317 10 : if (bAppendSubDS)
1318 : {
1319 2 : VSIRmdir(
1320 4 : CPLFormFilenameSafe(pszName, pszArrayName, nullptr).c_str());
1321 : }
1322 : else
1323 : {
1324 : // Be a bit careful before wiping too much stuff...
1325 : // At most 5 files expected for ZARR_V2: .zgroup, .zmetadata,
1326 : // one (empty) subdir, . and ..
1327 : // and for ZARR_V3: zarr.json, one (empty) subdir, . and ..
1328 16 : const CPLStringList aosFiles(VSIReadDirEx(pszName, 6));
1329 8 : if (aosFiles.size() < 6)
1330 : {
1331 40 : for (const char *pszFile : aosFiles)
1332 : {
1333 32 : if (pszArrayName && strcmp(pszFile, pszArrayName) == 0)
1334 : {
1335 0 : VSIRmdir(CPLFormFilenameSafe(pszName, pszFile, nullptr)
1336 : .c_str());
1337 : }
1338 64 : else if (!pszArrayName &&
1339 32 : strcmp(pszFile,
1340 64 : CPLGetBasenameSafe(pszName).c_str()) == 0)
1341 : {
1342 1 : VSIRmdir(CPLFormFilenameSafe(pszName, pszFile, nullptr)
1343 : .c_str());
1344 : }
1345 31 : else if (strcmp(pszFile, ".zgroup") == 0 ||
1346 24 : strcmp(pszFile, ".zmetadata") == 0 ||
1347 17 : strcmp(pszFile, "zarr.json") == 0)
1348 : {
1349 15 : VSIUnlink(CPLFormFilenameSafe(pszName, pszFile, nullptr)
1350 : .c_str());
1351 : }
1352 : }
1353 8 : VSIRmdir(pszName);
1354 : }
1355 : }
1356 10 : };
1357 :
1358 182 : std::string osDimXType, osDimYType;
1359 91 : if (CPLTestBool(
1360 : CSLFetchNameValueDef(papszOptions, "@HAS_GEOTRANSFORM", "NO")))
1361 : {
1362 45 : osDimXType = GDAL_DIM_TYPE_HORIZONTAL_X;
1363 45 : osDimYType = GDAL_DIM_TYPE_HORIZONTAL_Y;
1364 : }
1365 91 : poDS->m_bSpatialProjConvention = EQUAL(
1366 : CSLFetchNameValueDef(papszOptions, "GEOREFERENCING_CONVENTION", "GDAL"),
1367 : "SPATIAL_PROJ");
1368 :
1369 91 : if (bAppendSubDS)
1370 : {
1371 8 : auto aoDims = poRG->GetDimensions();
1372 12 : for (const auto &poDim : aoDims)
1373 : {
1374 12 : if (poDim->GetName() == "Y" &&
1375 4 : poDim->GetSize() == static_cast<GUInt64>(nYSize))
1376 : {
1377 1 : poDS->m_poDimY = poDim;
1378 : }
1379 11 : else if (poDim->GetName() == "X" &&
1380 4 : poDim->GetSize() == static_cast<GUInt64>(nXSize))
1381 : {
1382 1 : poDS->m_poDimX = poDim;
1383 : }
1384 : }
1385 4 : if (poDS->m_poDimY == nullptr)
1386 : {
1387 3 : poDS->m_poDimY =
1388 9 : poRG->CreateDimension(std::string(pszArrayName) + "_Y",
1389 9 : osDimYType, std::string(), nYSize);
1390 : }
1391 4 : if (poDS->m_poDimX == nullptr)
1392 : {
1393 3 : poDS->m_poDimX =
1394 9 : poRG->CreateDimension(std::string(pszArrayName) + "_X",
1395 9 : osDimXType, std::string(), nXSize);
1396 : }
1397 : }
1398 : else
1399 : {
1400 87 : poDS->m_poDimY =
1401 174 : poRG->CreateDimension("Y", osDimYType, std::string(), nYSize);
1402 87 : poDS->m_poDimX =
1403 174 : poRG->CreateDimension("X", osDimXType, std::string(), nXSize);
1404 : }
1405 91 : if (poDS->m_poDimY == nullptr || poDS->m_poDimX == nullptr)
1406 : {
1407 0 : CleanupCreatedFiles();
1408 0 : return nullptr;
1409 : }
1410 :
1411 : const bool bSingleArray =
1412 91 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "SINGLE_ARRAY", "YES"));
1413 : const bool bBandInterleave =
1414 91 : EQUAL(CSLFetchNameValueDef(papszOptions, "INTERLEAVE", "BAND"), "BAND");
1415 : std::shared_ptr<GDALDimension> poBandDim(
1416 89 : (bSingleArray && nBandsIn > 1)
1417 120 : ? poRG->CreateDimension("Band", std::string(), std::string(),
1418 29 : nBandsIn)
1419 358 : : nullptr);
1420 :
1421 : const std::string osNonNullArrayName =
1422 182 : pszArrayName ? std::string(pszArrayName) : CPLGetBasenameSafe(pszName);
1423 91 : if (poBandDim)
1424 : {
1425 29 : std::vector<std::shared_ptr<GDALDimension>> apoDims;
1426 29 : if (bBandInterleave)
1427 : {
1428 168 : apoDims = std::vector<std::shared_ptr<GDALDimension>>{
1429 140 : poBandDim, poDS->m_poDimY, poDS->m_poDimX};
1430 : }
1431 : else
1432 : {
1433 5 : apoDims = std::vector<std::shared_ptr<GDALDimension>>{
1434 5 : poDS->m_poDimY, poDS->m_poDimX, poBandDim};
1435 : }
1436 29 : CPL_IGNORE_RET_VAL(poBandDim);
1437 29 : poDS->m_poSingleArray =
1438 87 : std::dynamic_pointer_cast<ZarrArray>(poRG->CreateMDArray(
1439 : osNonNullArrayName.c_str(), apoDims,
1440 87 : GDALExtendedDataType::Create(eType), papszOptions));
1441 29 : if (!poDS->m_poSingleArray)
1442 : {
1443 2 : CleanupCreatedFiles();
1444 2 : return nullptr;
1445 : }
1446 54 : poDS->SetMetadataItem("INTERLEAVE", bBandInterleave ? "BAND" : "PIXEL",
1447 27 : "IMAGE_STRUCTURE");
1448 27 : if (bBandInterleave)
1449 : {
1450 : const char *pszBlockSize =
1451 26 : CSLFetchNameValue(papszOptions, "BLOCKSIZE");
1452 26 : if (pszBlockSize)
1453 : {
1454 : const CPLStringList aosTokens(
1455 12 : CSLTokenizeString2(pszBlockSize, ",", 0));
1456 6 : if (aosTokens.size() == 3 && atoi(aosTokens[0]) == nBandsIn)
1457 : {
1458 : // Actually expose as pixel interleaved
1459 3 : poDS->SetMetadataItem("INTERLEAVE", "PIXEL",
1460 3 : "IMAGE_STRUCTURE");
1461 : }
1462 : }
1463 : }
1464 112 : for (int i = 0; i < nBandsIn; i++)
1465 : {
1466 85 : auto poSlicedArray = poDS->m_poSingleArray->GetView(
1467 170 : CPLSPrintf(bBandInterleave ? "[%d,::,::]" : "[::,::,%d]", i));
1468 170 : poDS->SetBand(i + 1,
1469 170 : std::make_unique<ZarrRasterBand>(poSlicedArray));
1470 : }
1471 : }
1472 : else
1473 : {
1474 : const auto apoDims = std::vector<std::shared_ptr<GDALDimension>>{
1475 248 : poDS->m_poDimY, poDS->m_poDimX};
1476 120 : for (int i = 0; i < nBandsIn; i++)
1477 : {
1478 66 : auto poArray = poRG->CreateMDArray(
1479 60 : nBandsIn == 1 ? osNonNullArrayName.c_str()
1480 6 : : pszArrayName ? CPLSPrintf("%s_band%d", pszArrayName, i + 1)
1481 0 : : CPLSPrintf("Band%d", i + 1),
1482 198 : apoDims, GDALExtendedDataType::Create(eType), papszOptions);
1483 66 : if (poArray == nullptr)
1484 : {
1485 8 : CleanupCreatedFiles();
1486 8 : return nullptr;
1487 : }
1488 58 : poDS->SetBand(i + 1, std::make_unique<ZarrRasterBand>(poArray));
1489 : }
1490 : }
1491 :
1492 81 : return poDS.release();
1493 : }
1494 :
1495 : /************************************************************************/
1496 : /* ~ZarrDataset() */
1497 : /************************************************************************/
1498 :
1499 4068 : ZarrDataset::~ZarrDataset()
1500 : {
1501 2034 : ZarrDataset::FlushCache(true);
1502 4068 : }
1503 :
1504 : /************************************************************************/
1505 : /* FlushCache() */
1506 : /************************************************************************/
1507 :
1508 2076 : CPLErr ZarrDataset::FlushCache(bool bAtClosing)
1509 : {
1510 2076 : CPLErr eErr = GDALDataset::FlushCache(bAtClosing);
1511 :
1512 2076 : if (m_poSingleArray && !m_poSingleArray->Flush())
1513 : {
1514 0 : eErr = CE_Failure;
1515 : }
1516 :
1517 2076 : if (bAtClosing && m_poSingleArray)
1518 : {
1519 27 : bool bFound = false;
1520 112 : for (int i = 0; i < nBands; ++i)
1521 : {
1522 85 : if (papoBands[i]->GetColorInterpretation() != GCI_Undefined)
1523 24 : bFound = true;
1524 : }
1525 27 : if (bFound)
1526 : {
1527 16 : const auto oStringDT = GDALExtendedDataType::CreateString();
1528 24 : auto poAttr = m_poSingleArray->GetAttribute("COLOR_INTERPRETATION");
1529 8 : if (!poAttr)
1530 24 : poAttr = m_poSingleArray->CreateAttribute(
1531 8 : "COLOR_INTERPRETATION", {static_cast<GUInt64>(nBands)},
1532 16 : oStringDT);
1533 8 : if (poAttr)
1534 : {
1535 8 : const GUInt64 nStartIndex = 0;
1536 8 : const size_t nCount = nBands;
1537 8 : const GInt64 arrayStep = 1;
1538 8 : const GPtrDiff_t bufferStride = 1;
1539 16 : std::vector<const char *> apszValues;
1540 32 : for (int i = 0; i < nBands; ++i)
1541 : {
1542 : const auto eColorInterp =
1543 24 : papoBands[i]->GetColorInterpretation();
1544 24 : apszValues.push_back(
1545 24 : GDALGetColorInterpretationName(eColorInterp));
1546 : }
1547 16 : poAttr->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
1548 8 : oStringDT, apszValues.data());
1549 : }
1550 : }
1551 : }
1552 :
1553 2076 : if (m_poRootGroup)
1554 : {
1555 1926 : if (bAtClosing)
1556 : {
1557 1884 : if (!m_poRootGroup->Close())
1558 6 : eErr = CE_Failure;
1559 : }
1560 : else
1561 : {
1562 42 : if (!m_poRootGroup->Flush())
1563 3 : eErr = CE_Failure;
1564 : }
1565 : }
1566 :
1567 2076 : return eErr;
1568 : }
1569 :
1570 : /************************************************************************/
1571 : /* GetRootGroup() */
1572 : /************************************************************************/
1573 :
1574 1801 : std::shared_ptr<GDALGroup> ZarrDataset::GetRootGroup() const
1575 : {
1576 1801 : return m_poRootGroup;
1577 : }
1578 :
1579 : /************************************************************************/
1580 : /* GetSpatialRef() */
1581 : /************************************************************************/
1582 :
1583 2 : const OGRSpatialReference *ZarrDataset::GetSpatialRef() const
1584 : {
1585 2 : if (nBands >= 1)
1586 2 : return cpl::down_cast<ZarrRasterBand *>(papoBands[0])
1587 4 : ->m_poArray->GetSpatialRef()
1588 2 : .get();
1589 0 : return nullptr;
1590 : }
1591 :
1592 : /************************************************************************/
1593 : /* SetSpatialRef() */
1594 : /************************************************************************/
1595 :
1596 67 : CPLErr ZarrDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
1597 : {
1598 192 : for (int i = 0; i < nBands; ++i)
1599 : {
1600 125 : cpl::down_cast<ZarrRasterBand *>(papoBands[i])
1601 125 : ->m_poArray->SetSpatialRef(poSRS);
1602 : }
1603 67 : return CE_None;
1604 : }
1605 :
1606 : /************************************************************************/
1607 : /* GetGeoTransform() */
1608 : /************************************************************************/
1609 :
1610 29 : CPLErr ZarrDataset::GetGeoTransform(GDALGeoTransform >) const
1611 : {
1612 29 : gt = m_gt;
1613 29 : return m_bHasGT ? CE_None : CE_Failure;
1614 : }
1615 :
1616 : /************************************************************************/
1617 : /* SetGeoTransform() */
1618 : /************************************************************************/
1619 :
1620 69 : CPLErr ZarrDataset::SetGeoTransform(const GDALGeoTransform >)
1621 : {
1622 69 : const bool bHasRotatedTerms = (gt.xrot != 0 || gt.yrot != 0);
1623 :
1624 69 : if (bHasRotatedTerms)
1625 : {
1626 1 : if (!m_bSpatialProjConvention)
1627 : {
1628 0 : CPLError(CE_Failure, CPLE_NotSupported,
1629 : "Geotransform with rotated terms not supported with "
1630 : "GEOREFERENCING_CONVENTION=GDAL, but would be with "
1631 : "SPATIAL_PROJ");
1632 0 : return CE_Failure;
1633 : }
1634 : }
1635 68 : else if (m_poDimX == nullptr || m_poDimY == nullptr)
1636 : {
1637 0 : CPLError(CE_Failure, CPLE_AppDefined,
1638 : "SetGeoTransform() failed because of missing X/Y dimension");
1639 0 : return CE_Failure;
1640 : }
1641 :
1642 69 : m_gt = gt;
1643 69 : m_bHasGT = true;
1644 :
1645 69 : if (m_bSpatialProjConvention)
1646 : {
1647 2 : const auto bSingleArray = m_poSingleArray != nullptr;
1648 2 : const int nIters = bSingleArray ? 1 : nBands;
1649 4 : for (int i = 0; i < nIters; ++i)
1650 : {
1651 : auto *poArray = bSingleArray
1652 2 : ? m_poSingleArray.get()
1653 2 : : cpl::down_cast<ZarrRasterBand *>(papoBands[i])
1654 2 : ->m_poArray.get();
1655 4 : auto oAttrDT = GDALExtendedDataType::Create(GDT_Float64);
1656 : auto poAttr =
1657 6 : poArray->CreateAttribute("gdal:geotransform", {6}, oAttrDT);
1658 2 : if (poAttr)
1659 : {
1660 2 : const GUInt64 nStartIndex = 0;
1661 2 : const size_t nCount = 6;
1662 2 : const GInt64 arrayStep = 1;
1663 2 : const GPtrDiff_t bufferStride = 1;
1664 4 : poAttr->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
1665 2 : oAttrDT, m_gt.data());
1666 : }
1667 : }
1668 : }
1669 :
1670 69 : if (!bHasRotatedTerms)
1671 : {
1672 68 : CPLAssert(m_poDimX);
1673 68 : CPLAssert(m_poDimY);
1674 :
1675 68 : const auto oDTFloat64 = GDALExtendedDataType::Create(GDT_Float64);
1676 : {
1677 68 : auto poX = m_poRootGroup->OpenMDArray(m_poDimX->GetName());
1678 68 : if (!poX)
1679 335 : poX = m_poRootGroup->CreateMDArray(
1680 268 : m_poDimX->GetName(), {m_poDimX}, oDTFloat64, nullptr);
1681 68 : if (!poX)
1682 0 : return CE_Failure;
1683 68 : m_poDimX->SetIndexingVariable(poX);
1684 68 : std::vector<double> adfX;
1685 : try
1686 : {
1687 68 : adfX.reserve(nRasterXSize);
1688 5782 : for (int i = 0; i < nRasterXSize; ++i)
1689 5714 : adfX.emplace_back(m_gt.xorig + m_gt.xscale * (i + 0.5));
1690 : }
1691 0 : catch (const std::exception &)
1692 : {
1693 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
1694 : "Out of memory when allocating X array");
1695 0 : return CE_Failure;
1696 : }
1697 68 : const GUInt64 nStartIndex = 0;
1698 68 : const size_t nCount = adfX.size();
1699 68 : const GInt64 arrayStep = 1;
1700 68 : const GPtrDiff_t bufferStride = 1;
1701 136 : if (!poX->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
1702 68 : poX->GetDataType(), adfX.data()))
1703 : {
1704 0 : return CE_Failure;
1705 : }
1706 : }
1707 :
1708 68 : auto poY = m_poRootGroup->OpenMDArray(m_poDimY->GetName());
1709 68 : if (!poY)
1710 268 : poY = m_poRootGroup->CreateMDArray(m_poDimY->GetName(), {m_poDimY},
1711 201 : oDTFloat64, nullptr);
1712 68 : if (!poY)
1713 0 : return CE_Failure;
1714 68 : m_poDimY->SetIndexingVariable(poY);
1715 68 : std::vector<double> adfY;
1716 : try
1717 : {
1718 68 : adfY.reserve(nRasterYSize);
1719 4570 : for (int i = 0; i < nRasterYSize; ++i)
1720 4502 : adfY.emplace_back(m_gt.yorig + m_gt.yscale * (i + 0.5));
1721 : }
1722 0 : catch (const std::exception &)
1723 : {
1724 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
1725 : "Out of memory when allocating Y array");
1726 0 : return CE_Failure;
1727 : }
1728 68 : const GUInt64 nStartIndex = 0;
1729 68 : const size_t nCount = adfY.size();
1730 68 : const GInt64 arrayStep = 1;
1731 68 : const GPtrDiff_t bufferStride = 1;
1732 136 : if (!poY->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
1733 68 : poY->GetDataType(), adfY.data()))
1734 : {
1735 0 : return CE_Failure;
1736 : }
1737 : }
1738 :
1739 69 : return CE_None;
1740 : }
1741 :
1742 : /************************************************************************/
1743 : /* SetMetadata() */
1744 : /************************************************************************/
1745 :
1746 40 : CPLErr ZarrDataset::SetMetadata(CSLConstList papszMetadata,
1747 : const char *pszDomain)
1748 : {
1749 40 : if (nBands >= 1 && (pszDomain == nullptr || pszDomain[0] == '\0'))
1750 : {
1751 80 : const auto oStringDT = GDALExtendedDataType::CreateString();
1752 40 : const auto bSingleArray = m_poSingleArray != nullptr;
1753 40 : const int nIters = bSingleArray ? 1 : nBands;
1754 84 : for (int i = 0; i < nIters; ++i)
1755 : {
1756 : auto *poArray = bSingleArray
1757 44 : ? m_poSingleArray.get()
1758 32 : : cpl::down_cast<ZarrRasterBand *>(papoBands[i])
1759 32 : ->m_poArray.get();
1760 88 : for (auto iter = papszMetadata; iter && *iter; ++iter)
1761 : {
1762 44 : char *pszKey = nullptr;
1763 44 : const char *pszValue = CPLParseNameValue(*iter, &pszKey);
1764 44 : if (pszKey && pszValue)
1765 : {
1766 : auto poAttr =
1767 63 : poArray->CreateAttribute(pszKey, {}, oStringDT);
1768 21 : if (poAttr)
1769 : {
1770 21 : const GUInt64 nStartIndex = 0;
1771 21 : const size_t nCount = 1;
1772 21 : const GInt64 arrayStep = 1;
1773 21 : const GPtrDiff_t bufferStride = 1;
1774 21 : poAttr->Write(&nStartIndex, &nCount, &arrayStep,
1775 : &bufferStride, oStringDT, &pszValue);
1776 : }
1777 : }
1778 44 : CPLFree(pszKey);
1779 : }
1780 : }
1781 : }
1782 40 : return GDALDataset::SetMetadata(papszMetadata, pszDomain);
1783 : }
1784 :
1785 : /************************************************************************/
1786 : /* ZarrRasterBand::ZarrRasterBand() */
1787 : /************************************************************************/
1788 :
1789 143 : ZarrRasterBand::ZarrRasterBand(const std::shared_ptr<GDALMDArray> &poArray)
1790 143 : : m_poArray(poArray)
1791 : {
1792 143 : assert(poArray->GetDimensionCount() == 2);
1793 143 : eDataType = poArray->GetDataType().GetNumericDataType();
1794 143 : nBlockXSize = static_cast<int>(poArray->GetBlockSize()[1]);
1795 143 : nBlockYSize = static_cast<int>(poArray->GetBlockSize()[0]);
1796 143 : }
1797 :
1798 : /************************************************************************/
1799 : /* GetNoDataValue() */
1800 : /************************************************************************/
1801 :
1802 6 : double ZarrRasterBand::GetNoDataValue(int *pbHasNoData)
1803 : {
1804 6 : bool bHasNodata = false;
1805 6 : const auto res = m_poArray->GetNoDataValueAsDouble(&bHasNodata);
1806 6 : if (pbHasNoData)
1807 6 : *pbHasNoData = bHasNodata;
1808 6 : return res;
1809 : }
1810 :
1811 : /************************************************************************/
1812 : /* GetNoDataValueAsInt64() */
1813 : /************************************************************************/
1814 :
1815 0 : int64_t ZarrRasterBand::GetNoDataValueAsInt64(int *pbHasNoData)
1816 : {
1817 0 : bool bHasNodata = false;
1818 0 : const auto res = m_poArray->GetNoDataValueAsInt64(&bHasNodata);
1819 0 : if (pbHasNoData)
1820 0 : *pbHasNoData = bHasNodata;
1821 0 : return res;
1822 : }
1823 :
1824 : /************************************************************************/
1825 : /* GetNoDataValueAsUInt64() */
1826 : /************************************************************************/
1827 :
1828 0 : uint64_t ZarrRasterBand::GetNoDataValueAsUInt64(int *pbHasNoData)
1829 : {
1830 0 : bool bHasNodata = false;
1831 0 : const auto res = m_poArray->GetNoDataValueAsUInt64(&bHasNodata);
1832 0 : if (pbHasNoData)
1833 0 : *pbHasNoData = bHasNodata;
1834 0 : return res;
1835 : }
1836 :
1837 : /************************************************************************/
1838 : /* SetNoDataValue() */
1839 : /************************************************************************/
1840 :
1841 2 : CPLErr ZarrRasterBand::SetNoDataValue(double dfNoData)
1842 : {
1843 2 : return m_poArray->SetNoDataValue(dfNoData) ? CE_None : CE_Failure;
1844 : }
1845 :
1846 : /************************************************************************/
1847 : /* SetNoDataValueAsInt64() */
1848 : /************************************************************************/
1849 :
1850 0 : CPLErr ZarrRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
1851 : {
1852 0 : return m_poArray->SetNoDataValue(nNoData) ? CE_None : CE_Failure;
1853 : }
1854 :
1855 : /************************************************************************/
1856 : /* SetNoDataValueAsUInt64() */
1857 : /************************************************************************/
1858 :
1859 0 : CPLErr ZarrRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
1860 : {
1861 0 : return m_poArray->SetNoDataValue(nNoData) ? CE_None : CE_Failure;
1862 : }
1863 :
1864 : /************************************************************************/
1865 : /* GetOffset() */
1866 : /************************************************************************/
1867 :
1868 2 : double ZarrRasterBand::GetOffset(int *pbSuccess)
1869 : {
1870 2 : bool bHasValue = false;
1871 2 : double dfRet = m_poArray->GetOffset(&bHasValue);
1872 2 : if (pbSuccess)
1873 2 : *pbSuccess = bHasValue ? TRUE : FALSE;
1874 2 : return dfRet;
1875 : }
1876 :
1877 : /************************************************************************/
1878 : /* SetOffset() */
1879 : /************************************************************************/
1880 :
1881 2 : CPLErr ZarrRasterBand::SetOffset(double dfNewOffset)
1882 : {
1883 2 : return m_poArray->SetOffset(dfNewOffset) ? CE_None : CE_Failure;
1884 : }
1885 :
1886 : /************************************************************************/
1887 : /* GetScale() */
1888 : /************************************************************************/
1889 :
1890 2 : double ZarrRasterBand::GetScale(int *pbSuccess)
1891 : {
1892 2 : bool bHasValue = false;
1893 2 : double dfRet = m_poArray->GetScale(&bHasValue);
1894 2 : if (pbSuccess)
1895 2 : *pbSuccess = bHasValue ? TRUE : FALSE;
1896 2 : return dfRet;
1897 : }
1898 :
1899 : /************************************************************************/
1900 : /* SetScale() */
1901 : /************************************************************************/
1902 :
1903 2 : CPLErr ZarrRasterBand::SetScale(double dfNewScale)
1904 : {
1905 2 : return m_poArray->SetScale(dfNewScale) ? CE_None : CE_Failure;
1906 : }
1907 :
1908 : /************************************************************************/
1909 : /* GetUnitType() */
1910 : /************************************************************************/
1911 :
1912 2 : const char *ZarrRasterBand::GetUnitType()
1913 : {
1914 2 : return m_poArray->GetUnit().c_str();
1915 : }
1916 :
1917 : /************************************************************************/
1918 : /* SetUnitType() */
1919 : /************************************************************************/
1920 :
1921 2 : CPLErr ZarrRasterBand::SetUnitType(const char *pszNewValue)
1922 : {
1923 4 : return m_poArray->SetUnit(pszNewValue ? pszNewValue : "") ? CE_None
1924 4 : : CE_Failure;
1925 : }
1926 :
1927 : /************************************************************************/
1928 : /* GetColorInterpretation() */
1929 : /************************************************************************/
1930 :
1931 140 : GDALColorInterp ZarrRasterBand::GetColorInterpretation()
1932 : {
1933 140 : return m_eColorInterp;
1934 : }
1935 :
1936 : /************************************************************************/
1937 : /* SetColorInterpretation() */
1938 : /************************************************************************/
1939 :
1940 31 : CPLErr ZarrRasterBand::SetColorInterpretation(GDALColorInterp eColorInterp)
1941 : {
1942 31 : auto poGDS = cpl::down_cast<ZarrDataset *>(poDS);
1943 31 : m_eColorInterp = eColorInterp;
1944 31 : if (!poGDS->m_poSingleArray)
1945 : {
1946 7 : const auto oStringDT = GDALExtendedDataType::CreateString();
1947 14 : auto poAttr = m_poArray->GetAttribute("COLOR_INTERPRETATION");
1948 7 : if (poAttr && (poAttr->GetDimensionCount() != 0 ||
1949 7 : poAttr->GetDataType().GetClass() != GEDTC_STRING))
1950 0 : return CE_None;
1951 7 : if (!poAttr)
1952 21 : poAttr = m_poArray->CreateAttribute("COLOR_INTERPRETATION", {},
1953 14 : oStringDT);
1954 7 : if (poAttr)
1955 : {
1956 7 : const GUInt64 nStartIndex = 0;
1957 7 : const size_t nCount = 1;
1958 7 : const GInt64 arrayStep = 1;
1959 7 : const GPtrDiff_t bufferStride = 1;
1960 7 : const char *pszValue = GDALGetColorInterpretationName(eColorInterp);
1961 7 : poAttr->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
1962 : oStringDT, &pszValue);
1963 : }
1964 : }
1965 31 : return CE_None;
1966 : }
1967 :
1968 : /************************************************************************/
1969 : /* ZarrRasterBand::IReadBlock() */
1970 : /************************************************************************/
1971 :
1972 2 : CPLErr ZarrRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pData)
1973 : {
1974 :
1975 2 : const int nXOff = nBlockXOff * nBlockXSize;
1976 2 : const int nYOff = nBlockYOff * nBlockYSize;
1977 2 : const int nReqXSize = std::min(nRasterXSize - nXOff, nBlockXSize);
1978 2 : const int nReqYSize = std::min(nRasterYSize - nYOff, nBlockYSize);
1979 2 : GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
1980 2 : static_cast<GUInt64>(nXOff)};
1981 2 : size_t count[] = {static_cast<size_t>(nReqYSize),
1982 2 : static_cast<size_t>(nReqXSize)};
1983 2 : constexpr GInt64 arrayStep[] = {1, 1};
1984 2 : GPtrDiff_t bufferStride[] = {nBlockXSize, 1};
1985 4 : return m_poArray->Read(arrayStartIdx, count, arrayStep, bufferStride,
1986 2 : m_poArray->GetDataType(), pData)
1987 2 : ? CE_None
1988 2 : : CE_Failure;
1989 : }
1990 :
1991 : /************************************************************************/
1992 : /* ZarrRasterBand::IWriteBlock() */
1993 : /************************************************************************/
1994 :
1995 0 : CPLErr ZarrRasterBand::IWriteBlock(int nBlockXOff, int nBlockYOff, void *pData)
1996 : {
1997 0 : const int nXOff = nBlockXOff * nBlockXSize;
1998 0 : const int nYOff = nBlockYOff * nBlockYSize;
1999 0 : const int nReqXSize = std::min(nRasterXSize - nXOff, nBlockXSize);
2000 0 : const int nReqYSize = std::min(nRasterYSize - nYOff, nBlockYSize);
2001 0 : GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
2002 0 : static_cast<GUInt64>(nXOff)};
2003 0 : size_t count[] = {static_cast<size_t>(nReqYSize),
2004 0 : static_cast<size_t>(nReqXSize)};
2005 0 : constexpr GInt64 arrayStep[] = {1, 1};
2006 0 : GPtrDiff_t bufferStride[] = {nBlockXSize, 1};
2007 0 : return m_poArray->Write(arrayStartIdx, count, arrayStep, bufferStride,
2008 0 : m_poArray->GetDataType(), pData)
2009 0 : ? CE_None
2010 0 : : CE_Failure;
2011 : }
2012 :
2013 : /************************************************************************/
2014 : /* IRasterIO() */
2015 : /************************************************************************/
2016 :
2017 50 : CPLErr ZarrRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
2018 : int nXSize, int nYSize, void *pData,
2019 : int nBufXSize, int nBufYSize,
2020 : GDALDataType eBufType, GSpacing nPixelSpaceBuf,
2021 : GSpacing nLineSpaceBuf,
2022 : GDALRasterIOExtraArg *psExtraArg)
2023 : {
2024 50 : const int nBufferDTSize(GDALGetDataTypeSizeBytes(eBufType));
2025 : // If reading/writing at full resolution and with proper stride, go
2026 : // directly to the array, but, for performance reasons,
2027 : // only if exactly on chunk boundaries, otherwise go through the block cache.
2028 50 : if (nXSize == nBufXSize && nYSize == nBufYSize && nBufferDTSize > 0 &&
2029 50 : (nPixelSpaceBuf % nBufferDTSize) == 0 &&
2030 50 : (nLineSpaceBuf % nBufferDTSize) == 0 && (nXOff % nBlockXSize) == 0 &&
2031 50 : (nYOff % nBlockYSize) == 0 &&
2032 50 : ((nXSize % nBlockXSize) == 0 || nXOff + nXSize == nRasterXSize) &&
2033 50 : ((nYSize % nBlockYSize) == 0 || nYOff + nYSize == nRasterYSize))
2034 : {
2035 50 : GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
2036 50 : static_cast<GUInt64>(nXOff)};
2037 50 : size_t count[] = {static_cast<size_t>(nYSize),
2038 50 : static_cast<size_t>(nXSize)};
2039 50 : constexpr GInt64 arrayStep[] = {1, 1};
2040 : GPtrDiff_t bufferStride[] = {
2041 50 : static_cast<GPtrDiff_t>(nLineSpaceBuf / nBufferDTSize),
2042 50 : static_cast<GPtrDiff_t>(nPixelSpaceBuf / nBufferDTSize)};
2043 :
2044 50 : if (eRWFlag == GF_Read)
2045 : {
2046 4 : return m_poArray->Read(
2047 : arrayStartIdx, count, arrayStep, bufferStride,
2048 4 : GDALExtendedDataType::Create(eBufType), pData)
2049 2 : ? CE_None
2050 2 : : CE_Failure;
2051 : }
2052 : else
2053 : {
2054 96 : return m_poArray->Write(
2055 : arrayStartIdx, count, arrayStep, bufferStride,
2056 96 : GDALExtendedDataType::Create(eBufType), pData)
2057 48 : ? CE_None
2058 48 : : CE_Failure;
2059 : }
2060 : }
2061 :
2062 0 : return GDALRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
2063 : pData, nBufXSize, nBufYSize, eBufType,
2064 0 : nPixelSpaceBuf, nLineSpaceBuf, psExtraArg);
2065 : }
2066 :
2067 : /************************************************************************/
2068 : /* ZarrDataset::IRasterIO() */
2069 : /************************************************************************/
2070 :
2071 58 : CPLErr ZarrDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
2072 : int nXSize, int nYSize, void *pData,
2073 : int nBufXSize, int nBufYSize,
2074 : GDALDataType eBufType, int nBandCount,
2075 : BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
2076 : GSpacing nLineSpace, GSpacing nBandSpace,
2077 : GDALRasterIOExtraArg *psExtraArg)
2078 : {
2079 58 : const int nBufferDTSize(GDALGetDataTypeSizeBytes(eBufType));
2080 : // If reading/writing at full resolution and with proper stride, go
2081 : // directly to the array, but, for performance reasons,
2082 : // only if exactly on chunk boundaries, otherwise go through the block cache.
2083 58 : int nBlockXSize = 0, nBlockYSize = 0;
2084 58 : papoBands[0]->GetBlockSize(&nBlockXSize, &nBlockYSize);
2085 90 : if (m_poSingleArray && nXSize == nBufXSize && nYSize == nBufYSize &&
2086 32 : nBufferDTSize > 0 && (nPixelSpace % nBufferDTSize) == 0 &&
2087 32 : (nLineSpace % nBufferDTSize) == 0 &&
2088 32 : (nBandSpace % nBufferDTSize) == 0 && (nXOff % nBlockXSize) == 0 &&
2089 32 : (nYOff % nBlockYSize) == 0 &&
2090 32 : ((nXSize % nBlockXSize) == 0 || nXOff + nXSize == nRasterXSize) &&
2091 122 : ((nYSize % nBlockYSize) == 0 || nYOff + nYSize == nRasterYSize) &&
2092 32 : IsAllBands(nBandCount, panBandMap))
2093 : {
2094 12 : CPLAssert(m_poSingleArray->GetDimensionCount() == 3);
2095 12 : if (m_poSingleArray->GetDimensions().back().get() == m_poDimX.get())
2096 : {
2097 11 : GUInt64 arrayStartIdx[] = {0, static_cast<GUInt64>(nYOff),
2098 11 : static_cast<GUInt64>(nXOff)};
2099 11 : size_t count[] = {static_cast<size_t>(nBands),
2100 11 : static_cast<size_t>(nYSize),
2101 11 : static_cast<size_t>(nXSize)};
2102 11 : constexpr GInt64 arrayStep[] = {1, 1, 1};
2103 : GPtrDiff_t bufferStride[] = {
2104 11 : static_cast<GPtrDiff_t>(nBandSpace / nBufferDTSize),
2105 11 : static_cast<GPtrDiff_t>(nLineSpace / nBufferDTSize),
2106 11 : static_cast<GPtrDiff_t>(nPixelSpace / nBufferDTSize)};
2107 :
2108 11 : if (eRWFlag == GF_Read)
2109 : {
2110 8 : return m_poSingleArray->Read(
2111 : arrayStartIdx, count, arrayStep, bufferStride,
2112 8 : GDALExtendedDataType::Create(eBufType), pData)
2113 4 : ? CE_None
2114 4 : : CE_Failure;
2115 : }
2116 : else
2117 : {
2118 14 : return m_poSingleArray->Write(
2119 : arrayStartIdx, count, arrayStep, bufferStride,
2120 14 : GDALExtendedDataType::Create(eBufType), pData)
2121 7 : ? CE_None
2122 7 : : CE_Failure;
2123 : }
2124 : }
2125 : else
2126 : {
2127 1 : GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
2128 1 : static_cast<GUInt64>(nXOff), 0};
2129 1 : size_t count[] = {static_cast<size_t>(nYSize),
2130 1 : static_cast<size_t>(nXSize),
2131 1 : static_cast<size_t>(nBands)};
2132 1 : constexpr GInt64 arrayStep[] = {1, 1, 1};
2133 : GPtrDiff_t bufferStride[] = {
2134 1 : static_cast<GPtrDiff_t>(nLineSpace / nBufferDTSize),
2135 1 : static_cast<GPtrDiff_t>(nPixelSpace / nBufferDTSize),
2136 1 : static_cast<GPtrDiff_t>(nBandSpace / nBufferDTSize),
2137 1 : };
2138 :
2139 1 : if (eRWFlag == GF_Read)
2140 : {
2141 0 : return m_poSingleArray->Read(
2142 : arrayStartIdx, count, arrayStep, bufferStride,
2143 0 : GDALExtendedDataType::Create(eBufType), pData)
2144 0 : ? CE_None
2145 0 : : CE_Failure;
2146 : }
2147 : else
2148 : {
2149 2 : return m_poSingleArray->Write(
2150 : arrayStartIdx, count, arrayStep, bufferStride,
2151 2 : GDALExtendedDataType::Create(eBufType), pData)
2152 1 : ? CE_None
2153 1 : : CE_Failure;
2154 : }
2155 : }
2156 : }
2157 :
2158 46 : return GDALDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
2159 : nBufXSize, nBufYSize, eBufType, nBandCount,
2160 : panBandMap, nPixelSpace, nLineSpace,
2161 46 : nBandSpace, psExtraArg);
2162 : }
2163 :
2164 : /************************************************************************/
2165 : /* ZarrDataset::CreateCopy() */
2166 : /************************************************************************/
2167 :
2168 : /* static */
2169 51 : GDALDataset *ZarrDataset::CreateCopy(const char *pszFilename,
2170 : GDALDataset *poSrcDS, int bStrict,
2171 : CSLConstList papszOptions,
2172 : GDALProgressFunc pfnProgress,
2173 : void *pProgressData)
2174 : {
2175 51 : if (CPLFetchBool(papszOptions, "CONVERT_TO_KERCHUNK_PARQUET_REFERENCE",
2176 : false))
2177 : {
2178 1 : if (VSIKerchunkConvertJSONToParquet(poSrcDS->GetDescription(),
2179 : pszFilename, pfnProgress,
2180 : pProgressData))
2181 : {
2182 : GDALOpenInfo oOpenInfo(
2183 2 : std::string("ZARR:\"").append(pszFilename).append("\"").c_str(),
2184 2 : GA_ReadOnly);
2185 1 : return Open(&oOpenInfo);
2186 : }
2187 : }
2188 : else
2189 : {
2190 50 : auto poDriver = GetGDALDriverManager()->GetDriverByName(DRIVER_NAME);
2191 : CPLStringList aosCreationOptions(
2192 100 : const_cast<CSLConstList>(papszOptions));
2193 50 : GDALGeoTransform gt;
2194 50 : if (poSrcDS->GetGeoTransform(gt) == CE_None)
2195 : {
2196 49 : aosCreationOptions.SetNameValue("@HAS_GEOTRANSFORM", "YES");
2197 : }
2198 : auto poDS = std::unique_ptr<GDALDataset>(poDriver->DefaultCreateCopy(
2199 50 : pszFilename, poSrcDS, bStrict, aosCreationOptions.List(),
2200 100 : pfnProgress, pProgressData));
2201 50 : if (poDS)
2202 : {
2203 38 : if (poDS->FlushCache() != CE_None)
2204 3 : poDS.reset();
2205 : }
2206 50 : return poDS.release();
2207 : }
2208 0 : return nullptr;
2209 : }
2210 :
2211 : /************************************************************************/
2212 : /* GDALRegister_Zarr() */
2213 : /************************************************************************/
2214 :
2215 2068 : void GDALRegister_Zarr()
2216 :
2217 : {
2218 2068 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
2219 263 : return;
2220 :
2221 1805 : VSIInstallKerchunkFileSystems();
2222 :
2223 1805 : GDALDriver *poDriver = new ZarrDriver();
2224 1805 : ZARRDriverSetCommonMetadata(poDriver);
2225 :
2226 1805 : poDriver->pfnOpen = ZarrDataset::Open;
2227 1805 : poDriver->pfnCreateMultiDimensional = ZarrDataset::CreateMultiDimensional;
2228 1805 : poDriver->pfnCreate = ZarrDataset::Create;
2229 1805 : poDriver->pfnCreateCopy = ZarrDataset::CreateCopy;
2230 1805 : poDriver->pfnDelete = ZarrDatasetDelete;
2231 1805 : poDriver->pfnRename = ZarrDatasetRename;
2232 1805 : poDriver->pfnCopyFiles = ZarrDatasetCopyFiles;
2233 1805 : poDriver->pfnClearCaches = ZarrDriverClearCaches;
2234 :
2235 1805 : GetGDALDriverManager()->RegisterDriver(poDriver);
2236 : }
|