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 "gdalalgorithm.h"
20 : #include "gdal_frmts.h"
21 :
22 : #include <algorithm>
23 : #include <cassert>
24 : #include <cinttypes>
25 : #include <limits>
26 : #include <future>
27 : #include <mutex>
28 :
29 : #ifdef HAVE_BLOSC
30 : #include <blosc.h>
31 : #endif
32 :
33 : /************************************************************************/
34 : /* ZarrDataset() */
35 : /************************************************************************/
36 :
37 2039 : ZarrDataset::ZarrDataset(const std::shared_ptr<ZarrGroupBase> &poRootGroup)
38 2039 : : m_poRootGroup(poRootGroup)
39 : {
40 2039 : }
41 :
42 : /************************************************************************/
43 : /* OpenMultidim() */
44 : /************************************************************************/
45 :
46 1626 : GDALDataset *ZarrDataset::OpenMultidim(const char *pszFilename,
47 : bool bUpdateMode,
48 : CSLConstList papszOpenOptionsIn)
49 : {
50 3252 : CPLString osFilename(pszFilename);
51 1626 : if (osFilename.back() == '/')
52 0 : osFilename.pop_back();
53 :
54 3252 : auto poSharedResource = ZarrSharedResource::Create(osFilename, bUpdateMode);
55 1626 : poSharedResource->SetOpenOptions(papszOpenOptionsIn);
56 :
57 3252 : auto poRG = poSharedResource->GetRootGroup();
58 1626 : if (!poRG)
59 : {
60 : // Kerchunk Parquet auto-detection: OpenRootGroup found a
61 : // .zmetadata with record_size, signaling a redirect.
62 108 : const auto &osKerchunkPath = poSharedResource->GetKerchunkParquetPath();
63 108 : if (!osKerchunkPath.empty())
64 5 : return OpenMultidim(osKerchunkPath.c_str(), bUpdateMode,
65 5 : papszOpenOptionsIn);
66 103 : return nullptr;
67 : }
68 1518 : return new ZarrDataset(poRG);
69 : }
70 :
71 : /************************************************************************/
72 : /* ExploreGroup() */
73 : /************************************************************************/
74 :
75 142 : static bool ExploreGroup(const std::shared_ptr<GDALGroup> &poGroup,
76 : std::vector<std::string> &aosArrays, int nRecCount)
77 : {
78 142 : if (nRecCount == 32)
79 : {
80 0 : CPLError(CE_Failure, CPLE_NotSupported,
81 : "Too deep recursion level in ExploreGroup()");
82 0 : return false;
83 : }
84 284 : const auto aosGroupArrayNames = poGroup->GetMDArrayNames();
85 371 : for (const auto &osArrayName : aosGroupArrayNames)
86 : {
87 229 : std::string osArrayFullname = poGroup->GetFullName();
88 229 : if (osArrayName != "/")
89 : {
90 229 : if (osArrayFullname != "/")
91 8 : osArrayFullname += '/';
92 229 : osArrayFullname += osArrayName;
93 : }
94 229 : aosArrays.emplace_back(std::move(osArrayFullname));
95 229 : if (aosArrays.size() == 10000)
96 : {
97 0 : CPLError(CE_Failure, CPLE_NotSupported,
98 : "Too many arrays found by ExploreGroup()");
99 0 : return false;
100 : }
101 : }
102 :
103 284 : const auto aosSubGroups = poGroup->GetGroupNames();
104 156 : for (const auto &osSubGroup : aosSubGroups)
105 : {
106 14 : const auto poSubGroup = poGroup->OpenGroup(osSubGroup);
107 14 : if (poSubGroup)
108 : {
109 14 : if (!ExploreGroup(poSubGroup, aosArrays, nRecCount + 1))
110 0 : return false;
111 : }
112 : }
113 142 : return true;
114 : }
115 :
116 : /************************************************************************/
117 : /* GetMetadataItem() */
118 : /************************************************************************/
119 :
120 54 : const char *ZarrDataset::GetMetadataItem(const char *pszName,
121 : const char *pszDomain)
122 : {
123 54 : if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
124 0 : return m_aosSubdatasets.FetchNameValue(pszName);
125 54 : if (pszDomain != nullptr && EQUAL(pszDomain, "IMAGE_STRUCTURE"))
126 39 : return GDALDataset::GetMetadataItem(pszName, pszDomain);
127 15 : return nullptr;
128 : }
129 :
130 : /************************************************************************/
131 : /* GetMetadata() */
132 : /************************************************************************/
133 :
134 24 : CSLConstList ZarrDataset::GetMetadata(const char *pszDomain)
135 : {
136 24 : if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
137 11 : return m_aosSubdatasets.List();
138 13 : if (pszDomain != nullptr && EQUAL(pszDomain, "IMAGE_STRUCTURE"))
139 0 : return GDALDataset::GetMetadata(pszDomain);
140 13 : return nullptr;
141 : }
142 :
143 : /************************************************************************/
144 : /* GetXYDimensionIndices() */
145 : /************************************************************************/
146 :
147 156 : static void GetXYDimensionIndices(const std::shared_ptr<GDALMDArray> &poArray,
148 : const GDALOpenInfo *poOpenInfo, size_t &iXDim,
149 : size_t &iYDim)
150 : {
151 156 : const size_t nDims = poArray->GetDimensionCount();
152 156 : iYDim = nDims >= 2 ? nDims - 2 : 0;
153 156 : iXDim = nDims >= 2 ? nDims - 1 : 0;
154 :
155 156 : if (nDims >= 2)
156 : {
157 : const char *pszDimX =
158 139 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "DIM_X");
159 : const char *pszDimY =
160 139 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "DIM_Y");
161 139 : bool bFoundX = false;
162 139 : bool bFoundY = false;
163 139 : const auto &apoDims = poArray->GetDimensions();
164 470 : for (size_t i = 0; i < nDims; ++i)
165 : {
166 331 : if (pszDimX && apoDims[i]->GetName() == pszDimX)
167 : {
168 1 : bFoundX = true;
169 1 : iXDim = i;
170 : }
171 330 : else if (pszDimY && apoDims[i]->GetName() == pszDimY)
172 : {
173 1 : bFoundY = true;
174 1 : iYDim = i;
175 : }
176 965 : else if (!pszDimX &&
177 636 : (apoDims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X ||
178 317 : apoDims[i]->GetName() == "X"))
179 72 : iXDim = i;
180 749 : else if (!pszDimY &&
181 492 : (apoDims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y ||
182 245 : apoDims[i]->GetName() == "Y"))
183 72 : iYDim = i;
184 : }
185 139 : if (pszDimX)
186 : {
187 4 : if (!bFoundX && CPLGetValueType(pszDimX) == CPL_VALUE_INTEGER)
188 : {
189 2 : const int nTmp = atoi(pszDimX);
190 2 : if (nTmp >= 0 && nTmp <= static_cast<int>(nDims))
191 : {
192 2 : iXDim = nTmp;
193 2 : bFoundX = true;
194 : }
195 : }
196 4 : if (!bFoundX)
197 : {
198 1 : CPLError(CE_Warning, CPLE_AppDefined,
199 : "Cannot find dimension DIM_X=%s", pszDimX);
200 : }
201 : }
202 139 : if (pszDimY)
203 : {
204 4 : if (!bFoundY && CPLGetValueType(pszDimY) == CPL_VALUE_INTEGER)
205 : {
206 2 : const int nTmp = atoi(pszDimY);
207 2 : if (nTmp >= 0 && nTmp <= static_cast<int>(nDims))
208 : {
209 2 : iYDim = nTmp;
210 2 : bFoundY = true;
211 : }
212 : }
213 4 : if (!bFoundY)
214 : {
215 1 : CPLError(CE_Warning, CPLE_AppDefined,
216 : "Cannot find dimension DIM_Y=%s", pszDimY);
217 : }
218 : }
219 : }
220 156 : }
221 :
222 : /************************************************************************/
223 : /* GetExtraDimSampleCount() */
224 : /************************************************************************/
225 :
226 : static uint64_t
227 35 : GetExtraDimSampleCount(const std::shared_ptr<GDALMDArray> &poArray,
228 : size_t iXDim, size_t iYDim)
229 : {
230 35 : uint64_t nExtraDimSamples = 1;
231 35 : const auto &apoDims = poArray->GetDimensions();
232 142 : for (size_t i = 0; i < apoDims.size(); ++i)
233 : {
234 107 : if (i != iXDim && i != iYDim)
235 39 : nExtraDimSamples *= apoDims[i]->GetSize();
236 : }
237 35 : return nExtraDimSamples;
238 : }
239 :
240 : /************************************************************************/
241 : /* PrefetchCoordArrays() */
242 : /************************************************************************/
243 :
244 : // Warm g_oCoordCache by reading X and Y coordinate arrays in parallel.
245 : // For remote datasets this avoids sequential HTTP round-trips in
246 : // GuessGeoTransform() (can save ~800 ms). Each ZarrArray has its own
247 : // mutex and VSI opens independent handles, so sibling reads are safe.
248 140 : static void PrefetchCoordArrays(const std::shared_ptr<GDALMDArray> &poArray,
249 : size_t iXDim, size_t iYDim)
250 : {
251 140 : const auto nDimCount = poArray->GetDimensionCount();
252 140 : if (nDimCount < 2 || iXDim >= nDimCount || iYDim >= nDimCount)
253 140 : return;
254 122 : const auto &dims = poArray->GetDimensions();
255 122 : auto poVarX = dims[iXDim]->GetIndexingVariable();
256 122 : auto poVarY = dims[iYDim]->GetIndexingVariable();
257 170 : if (!poVarX || poVarX->GetDimensionCount() != 1 || !poVarY ||
258 48 : poVarY->GetDimensionCount() != 1)
259 74 : return;
260 48 : if (VSIIsLocal(poVarX->GetFilename().c_str()))
261 48 : return;
262 :
263 0 : double dfXStart = 0, dfXSpacing = 0, dfYStart = 0, dfYSpacing = 0;
264 : auto futureX =
265 0 : std::async(std::launch::async, [&poVarX, &dfXStart, &dfXSpacing]()
266 0 : { return poVarX->IsRegularlySpaced(dfXStart, dfXSpacing); });
267 0 : CPL_IGNORE_RET_VAL(poVarY->IsRegularlySpaced(dfYStart, dfYSpacing));
268 0 : CPL_IGNORE_RET_VAL(futureX.get());
269 : }
270 :
271 : /************************************************************************/
272 : /* Open() */
273 : /************************************************************************/
274 :
275 1644 : GDALDataset *ZarrDataset::Open(GDALOpenInfo *poOpenInfo)
276 : {
277 1644 : if (!ZARRDriverIdentify(poOpenInfo))
278 : {
279 0 : return nullptr;
280 : }
281 :
282 : // Used by gdal_translate kerchunk_ref.json kerchunk_parq.parq -of ZARR -co CONVERT_TO_KERCHUNK_PARQUET_REFERENCE=YES
283 1644 : if (STARTS_WITH(poOpenInfo->pszFilename, "ZARR_DUMMY:"))
284 : {
285 : class ZarrDummyDataset final : public GDALDataset
286 : {
287 : public:
288 1 : ZarrDummyDataset()
289 1 : {
290 1 : nRasterXSize = 0;
291 1 : nRasterYSize = 0;
292 1 : }
293 : };
294 :
295 2 : auto poDS = std::make_unique<ZarrDummyDataset>();
296 1 : poDS->SetDescription(poOpenInfo->pszFilename + strlen("ZARR_DUMMY:"));
297 1 : return poDS.release();
298 : }
299 :
300 1643 : const bool bKerchunkCached = CPLFetchBool(poOpenInfo->papszOpenOptions,
301 : "CACHE_KERCHUNK_JSON", false);
302 :
303 1643 : if (ZARRIsLikelyKerchunkJSONRef(poOpenInfo))
304 : {
305 38 : GDALOpenInfo oOpenInfo(std::string("ZARR:\"")
306 : .append(bKerchunkCached
307 : ? JSON_REF_CACHED_FS_PREFIX
308 19 : : JSON_REF_FS_PREFIX)
309 19 : .append("{")
310 19 : .append(poOpenInfo->pszFilename)
311 19 : .append("}\"")
312 : .c_str(),
313 38 : GA_ReadOnly);
314 19 : oOpenInfo.nOpenFlags = poOpenInfo->nOpenFlags;
315 19 : oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
316 19 : return Open(&oOpenInfo);
317 : }
318 1624 : else if (STARTS_WITH(poOpenInfo->pszFilename, JSON_REF_FS_PREFIX) ||
319 1622 : STARTS_WITH(poOpenInfo->pszFilename, JSON_REF_CACHED_FS_PREFIX))
320 : {
321 : GDALOpenInfo oOpenInfo(
322 4 : std::string("ZARR:").append(poOpenInfo->pszFilename).c_str(),
323 4 : GA_ReadOnly);
324 2 : oOpenInfo.nOpenFlags = poOpenInfo->nOpenFlags;
325 2 : oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
326 2 : return Open(&oOpenInfo);
327 : }
328 :
329 3244 : CPLString osFilename(poOpenInfo->pszFilename);
330 1622 : if (!poOpenInfo->bIsDirectory)
331 : {
332 115 : osFilename = CPLGetPathSafe(osFilename);
333 : }
334 3244 : CPLString osArrayOfInterest;
335 3244 : std::vector<uint64_t> anExtraDimIndices;
336 1622 : if (STARTS_WITH(poOpenInfo->pszFilename, "ZARR:"))
337 : {
338 : const CPLStringList aosTokens(CSLTokenizeString2(
339 70 : poOpenInfo->pszFilename, ":", CSLT_HONOURSTRINGS));
340 70 : if (aosTokens.size() < 2)
341 2 : return nullptr;
342 68 : osFilename = aosTokens[1];
343 :
344 260 : if (!cpl::starts_with(osFilename, JSON_REF_FS_PREFIX) &&
345 111 : !cpl::starts_with(osFilename, JSON_REF_CACHED_FS_PREFIX) &&
346 111 : CPLGetExtensionSafe(osFilename) == "json")
347 : {
348 : VSIStatBufL sStat;
349 4 : if (VSIStatL(osFilename.c_str(), &sStat) == 0 &&
350 2 : !VSI_ISDIR(sStat.st_mode))
351 : {
352 : osFilename =
353 4 : std::string(bKerchunkCached ? JSON_REF_CACHED_FS_PREFIX
354 : : JSON_REF_FS_PREFIX)
355 2 : .append("{")
356 2 : .append(osFilename)
357 2 : .append("}");
358 : }
359 : }
360 :
361 68 : std::string osErrorMsg;
362 68 : if (osFilename == "http" || osFilename == "https")
363 : {
364 : osErrorMsg = "There is likely a quoting error of the whole "
365 : "connection string, and the filename should "
366 1 : "likely be prefixed with /vsicurl/";
367 : }
368 134 : else if (osFilename == "/vsicurl/http" ||
369 67 : osFilename == "/vsicurl/https")
370 : {
371 : osErrorMsg = "There is likely a quoting error of the whole "
372 1 : "connection string.";
373 : }
374 132 : else if (STARTS_WITH(osFilename.c_str(), "http://") ||
375 66 : STARTS_WITH(osFilename.c_str(), "https://"))
376 : {
377 : osErrorMsg =
378 1 : "The filename should likely be prefixed with /vsicurl/";
379 : }
380 68 : if (!osErrorMsg.empty())
381 : {
382 3 : CPLError(CE_Failure, CPLE_AppDefined, "%s", osErrorMsg.c_str());
383 3 : return nullptr;
384 : }
385 65 : if (aosTokens.size() >= 3)
386 : {
387 23 : osArrayOfInterest = aosTokens[2];
388 41 : for (int i = 3; i < aosTokens.size(); ++i)
389 : {
390 18 : anExtraDimIndices.push_back(
391 18 : static_cast<uint64_t>(CPLAtoGIntBig(aosTokens[i])));
392 : }
393 : }
394 : }
395 :
396 : auto poDSMultiDim = std::unique_ptr<GDALDataset>(
397 1617 : OpenMultidim(osFilename.c_str(), poOpenInfo->eAccess == GA_Update,
398 3234 : poOpenInfo->papszOpenOptions));
399 3131 : if (poDSMultiDim == nullptr ||
400 1514 : (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER) != 0)
401 : {
402 1466 : return poDSMultiDim.release();
403 : }
404 :
405 302 : auto poRG = poDSMultiDim->GetRootGroup();
406 :
407 302 : auto poDS = std::make_unique<ZarrDataset>(nullptr);
408 151 : std::shared_ptr<GDALMDArray> poMainArray;
409 302 : std::vector<std::string> aosArrays;
410 302 : std::string osMainArray;
411 151 : const bool bMultiband = CPLTestBool(
412 151 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "MULTIBAND", "YES"));
413 151 : size_t iXDim = 0;
414 151 : size_t iYDim = 0;
415 :
416 151 : if (!osArrayOfInterest.empty())
417 : {
418 23 : poMainArray = osArrayOfInterest == "/"
419 46 : ? poRG->OpenMDArray("/")
420 23 : : poRG->OpenMDArrayFromFullname(osArrayOfInterest);
421 23 : if (poMainArray == nullptr)
422 1 : return nullptr;
423 22 : GetXYDimensionIndices(poMainArray, poOpenInfo, iXDim, iYDim);
424 :
425 22 : if (poMainArray->GetDimensionCount() > 2)
426 : {
427 11 : if (anExtraDimIndices.empty())
428 : {
429 : const uint64_t nExtraDimSamples =
430 1 : GetExtraDimSampleCount(poMainArray, iXDim, iYDim);
431 1 : if (bMultiband)
432 : {
433 0 : if (nExtraDimSamples > 65536) // arbitrary limit
434 : {
435 0 : if (poMainArray->GetDimensionCount() == 3)
436 : {
437 0 : CPLError(CE_Warning, CPLE_AppDefined,
438 : "Too many samples along the > 2D "
439 : "dimensions of %s. "
440 : "Use ZARR:\"%s\":%s:{i} syntax",
441 : osArrayOfInterest.c_str(),
442 : osFilename.c_str(),
443 : osArrayOfInterest.c_str());
444 : }
445 : else
446 : {
447 0 : CPLError(CE_Warning, CPLE_AppDefined,
448 : "Too many samples along the > 2D "
449 : "dimensions of %s. "
450 : "Use ZARR:\"%s\":%s:{i}:{j} syntax",
451 : osArrayOfInterest.c_str(),
452 : osFilename.c_str(),
453 : osArrayOfInterest.c_str());
454 : }
455 0 : return nullptr;
456 : }
457 : }
458 1 : else if (nExtraDimSamples != 1)
459 : {
460 1 : CPLError(CE_Failure, CPLE_AppDefined,
461 : "Indices of extra dimensions must be specified");
462 1 : return nullptr;
463 : }
464 : }
465 10 : else if (anExtraDimIndices.size() !=
466 10 : poMainArray->GetDimensionCount() - 2)
467 : {
468 1 : CPLError(CE_Failure, CPLE_AppDefined,
469 : "Wrong number of indices of extra dimensions");
470 1 : return nullptr;
471 : }
472 : else
473 : {
474 23 : for (const auto idx : anExtraDimIndices)
475 : {
476 15 : poMainArray = poMainArray->at(idx);
477 15 : if (poMainArray == nullptr)
478 1 : return nullptr;
479 : }
480 8 : GetXYDimensionIndices(poMainArray, poOpenInfo, iXDim, iYDim);
481 : }
482 : }
483 11 : else if (!anExtraDimIndices.empty())
484 : {
485 1 : CPLError(CE_Failure, CPLE_AppDefined, "Unexpected extra indices");
486 1 : return nullptr;
487 : }
488 : }
489 : else
490 : {
491 128 : ExploreGroup(poRG, aosArrays, 0);
492 128 : if (aosArrays.empty())
493 0 : return nullptr;
494 :
495 128 : const bool bListAllArrays = CPLTestBool(CSLFetchNameValueDef(
496 128 : poOpenInfo->papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
497 :
498 128 : if (!bListAllArrays)
499 : {
500 126 : if (aosArrays.size() == 1)
501 : {
502 76 : poMainArray = poRG->OpenMDArrayFromFullname(aosArrays[0]);
503 76 : if (poMainArray)
504 76 : osMainArray = poMainArray->GetFullName();
505 : }
506 : else // at least 2 arrays
507 : {
508 198 : for (const auto &osArrayName : aosArrays)
509 : {
510 148 : auto poArray = poRG->OpenMDArrayFromFullname(osArrayName);
511 199 : if (poArray && poArray->GetDimensionCount() >= 2 &&
512 51 : osArrayName.find("/ovr_") == std::string::npos)
513 : {
514 50 : if (osMainArray.empty())
515 : {
516 50 : poMainArray = std::move(poArray);
517 50 : osMainArray = osArrayName;
518 : }
519 : else
520 : {
521 0 : poMainArray.reset();
522 0 : osMainArray.clear();
523 0 : break;
524 : }
525 : }
526 : }
527 : }
528 :
529 126 : if (poMainArray)
530 126 : GetXYDimensionIndices(poMainArray, poOpenInfo, iXDim, iYDim);
531 : }
532 :
533 128 : int iCountSubDS = 1;
534 :
535 128 : if (poMainArray && poMainArray->GetDimensionCount() > 2)
536 : {
537 34 : const auto &apoDims = poMainArray->GetDimensions();
538 : const uint64_t nExtraDimSamples =
539 34 : GetExtraDimSampleCount(poMainArray, iXDim, iYDim);
540 34 : if (nExtraDimSamples > 65536) // arbitrary limit
541 : {
542 3 : if (apoDims.size() == 3)
543 : {
544 2 : CPLError(
545 : CE_Warning, CPLE_AppDefined,
546 : "Too many samples along the > 2D dimensions of %s. "
547 : "Use ZARR:\"%s\":%s:{i} syntax",
548 : osMainArray.c_str(), osFilename.c_str(),
549 : osMainArray.c_str());
550 : }
551 : else
552 : {
553 1 : CPLError(
554 : CE_Warning, CPLE_AppDefined,
555 : "Too many samples along the > 2D dimensions of %s. "
556 : "Use ZARR:\"%s\":%s:{i}:{j} syntax",
557 : osMainArray.c_str(), osFilename.c_str(),
558 : osMainArray.c_str());
559 : }
560 : }
561 31 : else if (nExtraDimSamples > 1 && bMultiband)
562 : {
563 : // nothing to do
564 : }
565 2 : else if (nExtraDimSamples > 1 && apoDims.size() == 3)
566 : {
567 3 : for (int i = 0; i < static_cast<int>(nExtraDimSamples); ++i)
568 : {
569 2 : poDS->m_aosSubdatasets.AddString(CPLSPrintf(
570 : "SUBDATASET_%d_NAME=ZARR:\"%s\":%s:%d", iCountSubDS,
571 2 : osFilename.c_str(), osMainArray.c_str(), i));
572 2 : poDS->m_aosSubdatasets.AddString(CPLSPrintf(
573 : "SUBDATASET_%d_DESC=Array %s at index %d of %s",
574 : iCountSubDS, osMainArray.c_str(), i,
575 2 : apoDims[0]->GetName().c_str()));
576 2 : ++iCountSubDS;
577 : }
578 : }
579 1 : else if (nExtraDimSamples > 1)
580 : {
581 1 : int nDimIdxI = 0;
582 1 : int nDimIdxJ = 0;
583 7 : for (int i = 0; i < static_cast<int>(nExtraDimSamples); ++i)
584 : {
585 6 : poDS->m_aosSubdatasets.AddString(
586 : CPLSPrintf("SUBDATASET_%d_NAME=ZARR:\"%s\":%s:%d:%d",
587 : iCountSubDS, osFilename.c_str(),
588 6 : osMainArray.c_str(), nDimIdxI, nDimIdxJ));
589 6 : poDS->m_aosSubdatasets.AddString(
590 : CPLSPrintf("SUBDATASET_%d_DESC=Array %s at "
591 : "index %d of %s and %d of %s",
592 : iCountSubDS, osMainArray.c_str(), nDimIdxI,
593 6 : apoDims[0]->GetName().c_str(), nDimIdxJ,
594 12 : apoDims[1]->GetName().c_str()));
595 6 : ++iCountSubDS;
596 6 : ++nDimIdxJ;
597 6 : if (nDimIdxJ == static_cast<int>(apoDims[1]->GetSize()))
598 : {
599 3 : nDimIdxJ = 0;
600 3 : ++nDimIdxI;
601 : }
602 : }
603 : }
604 : }
605 :
606 128 : if (bListAllArrays || aosArrays.size() >= 2)
607 : {
608 205 : for (size_t i = 0; i < aosArrays.size(); ++i)
609 : {
610 306 : auto poArray = poRG->OpenMDArrayFromFullname(aosArrays[i]);
611 153 : if (poArray && (bListAllArrays || aosArrays[i].find("/ovr_") ==
612 153 : std::string::npos))
613 : {
614 152 : bool bAddSubDS = false;
615 152 : if (bListAllArrays)
616 : {
617 5 : bAddSubDS = true;
618 : }
619 147 : else if (poArray->GetDimensionCount() >= 2)
620 : {
621 50 : bAddSubDS = true;
622 : }
623 152 : if (bAddSubDS)
624 : {
625 110 : std::string osDim;
626 55 : const auto &apoDims = poArray->GetDimensions();
627 180 : for (const auto &poDim : apoDims)
628 : {
629 125 : if (!osDim.empty())
630 70 : osDim += "x";
631 : osDim += CPLSPrintf(
632 : "%" PRIu64,
633 125 : static_cast<uint64_t>(poDim->GetSize()));
634 : }
635 :
636 55 : std::string osDataType;
637 55 : if (poArray->GetDataType().GetClass() == GEDTC_STRING)
638 : {
639 0 : osDataType = "string type";
640 : }
641 55 : else if (poArray->GetDataType().GetClass() ==
642 : GEDTC_NUMERIC)
643 : {
644 : osDataType = GDALGetDataTypeName(
645 55 : poArray->GetDataType().GetNumericDataType());
646 : }
647 : else
648 : {
649 0 : osDataType = "compound type";
650 : }
651 :
652 55 : poDS->m_aosSubdatasets.AddString(CPLSPrintf(
653 : "SUBDATASET_%d_NAME=ZARR:\"%s\":%s", iCountSubDS,
654 55 : osFilename.c_str(), aosArrays[i].c_str()));
655 55 : poDS->m_aosSubdatasets.AddString(CPLSPrintf(
656 : "SUBDATASET_%d_DESC=[%s] %s (%s)", iCountSubDS,
657 55 : osDim.c_str(), aosArrays[i].c_str(),
658 110 : osDataType.c_str()));
659 55 : ++iCountSubDS;
660 : }
661 : }
662 : }
663 : }
664 : }
665 :
666 146 : if (poMainArray && (bMultiband || poMainArray->GetDimensionCount() <= 2))
667 : {
668 140 : PrefetchCoordArrays(poMainArray, iXDim, iYDim);
669 :
670 : // Pass papszOpenOptions for LOAD_EXTRA_DIM_METADATA_DELAY
671 : auto poNewDS =
672 140 : std::unique_ptr<GDALDataset>(poMainArray->AsClassicDataset(
673 280 : iXDim, iYDim, poRG, poOpenInfo->papszOpenOptions));
674 140 : if (!poNewDS)
675 3 : return nullptr;
676 :
677 137 : if (poMainArray->GetDimensionCount() >= 2)
678 : {
679 : // If we have 3 arrays, check that the 2 ones that are not the main
680 : // 2D array are indexing variables of its dimensions. If so, don't
681 : // expose them as subdatasets
682 121 : if (aosArrays.size() == 3)
683 : {
684 88 : std::vector<std::string> aosOtherArrays;
685 176 : for (size_t i = 0; i < aosArrays.size(); ++i)
686 : {
687 132 : if (aosArrays[i] != osMainArray)
688 : {
689 88 : aosOtherArrays.emplace_back(aosArrays[i]);
690 : }
691 : }
692 44 : bool bMatchFound[] = {false, false};
693 132 : for (int i = 0; i < 2; i++)
694 : {
695 : auto poIndexingVar =
696 88 : poMainArray->GetDimensions()[i == 0 ? iXDim : iYDim]
697 176 : ->GetIndexingVariable();
698 88 : if (poIndexingVar)
699 : {
700 132 : for (int j = 0; j < 2; j++)
701 : {
702 130 : if (aosOtherArrays[j] ==
703 130 : poIndexingVar->GetFullName())
704 : {
705 84 : bMatchFound[i] = true;
706 84 : break;
707 : }
708 : }
709 : }
710 : }
711 44 : if (bMatchFound[0] && bMatchFound[1])
712 : {
713 42 : poDS->m_aosSubdatasets.Clear();
714 : }
715 : }
716 : }
717 137 : if (!poDS->m_aosSubdatasets.empty())
718 : {
719 8 : poNewDS->SetMetadata(poDS->m_aosSubdatasets.List(), "SUBDATASETS");
720 : }
721 137 : return poNewDS.release();
722 : }
723 :
724 6 : return poDS.release();
725 : }
726 :
727 : /************************************************************************/
728 : /* ZarrDatasetDelete() */
729 : /************************************************************************/
730 :
731 5 : static CPLErr ZarrDatasetDelete(const char *pszFilename)
732 : {
733 5 : if (STARTS_WITH(pszFilename, "ZARR:"))
734 : {
735 0 : CPLError(CE_Failure, CPLE_AppDefined,
736 : "Delete() only supported on ZARR connection names "
737 : "not starting with the ZARR: prefix");
738 0 : return CE_Failure;
739 : }
740 5 : return VSIRmdirRecursive(pszFilename) == 0 ? CE_None : CE_Failure;
741 : }
742 :
743 : /************************************************************************/
744 : /* ZarrDatasetRename() */
745 : /************************************************************************/
746 :
747 2 : static CPLErr ZarrDatasetRename(const char *pszNewName, const char *pszOldName)
748 : {
749 2 : if (STARTS_WITH(pszNewName, "ZARR:") || STARTS_WITH(pszOldName, "ZARR:"))
750 : {
751 0 : CPLError(CE_Failure, CPLE_AppDefined,
752 : "Rename() only supported on ZARR connection names "
753 : "not starting with the ZARR: prefix");
754 0 : return CE_Failure;
755 : }
756 2 : return VSIRename(pszOldName, pszNewName) == 0 ? CE_None : CE_Failure;
757 : }
758 :
759 : /************************************************************************/
760 : /* ZarrDatasetCopyFiles() */
761 : /************************************************************************/
762 :
763 2 : static CPLErr ZarrDatasetCopyFiles(const char *pszNewName,
764 : const char *pszOldName)
765 : {
766 2 : if (STARTS_WITH(pszNewName, "ZARR:") || STARTS_WITH(pszOldName, "ZARR:"))
767 : {
768 0 : CPLError(CE_Failure, CPLE_AppDefined,
769 : "CopyFiles() only supported on ZARR connection names "
770 : "not starting with the ZARR: prefix");
771 0 : return CE_Failure;
772 : }
773 : // VSISync() returns true in case of success
774 4 : return VSISync((std::string(pszOldName) + '/').c_str(), pszNewName, nullptr,
775 : nullptr, nullptr, nullptr)
776 2 : ? CE_None
777 2 : : CE_Failure;
778 : }
779 :
780 : /************************************************************************/
781 : /* ZarrDriverClearCaches() */
782 : /************************************************************************/
783 :
784 4 : static void ZarrDriverClearCaches(GDALDriver *)
785 : {
786 4 : ZarrClearCoordinateCache();
787 4 : ZarrClearShardIndexCache();
788 4 : }
789 :
790 : /************************************************************************/
791 : /* ZarrDriver() */
792 : /************************************************************************/
793 :
794 : class ZarrDriver final : public GDALDriver
795 : {
796 : std::recursive_mutex m_oMutex{};
797 : bool m_bMetadataInitialized = false;
798 : void InitMetadata();
799 :
800 : public:
801 : const char *GetMetadataItem(const char *pszName,
802 : const char *pszDomain) override;
803 :
804 446 : CSLConstList GetMetadata(const char *pszDomain) override
805 : {
806 892 : std::lock_guard oLock(m_oMutex);
807 446 : InitMetadata();
808 892 : return GDALDriver::GetMetadata(pszDomain);
809 : }
810 : };
811 :
812 37208 : const char *ZarrDriver::GetMetadataItem(const char *pszName,
813 : const char *pszDomain)
814 : {
815 74416 : std::lock_guard oLock(m_oMutex);
816 37208 : if (EQUAL(pszName, "COMPRESSORS") || EQUAL(pszName, "BLOSC_COMPRESSORS") ||
817 37171 : EQUAL(pszName, GDAL_DMD_CREATIONOPTIONLIST) ||
818 36818 : EQUAL(pszName, GDAL_DMD_MULTIDIM_ARRAY_CREATIONOPTIONLIST))
819 : {
820 391 : InitMetadata();
821 : }
822 74416 : return GDALDriver::GetMetadataItem(pszName, pszDomain);
823 : }
824 :
825 837 : void ZarrDriver::InitMetadata()
826 : {
827 837 : if (m_bMetadataInitialized)
828 627 : return;
829 210 : m_bMetadataInitialized = true;
830 :
831 : {
832 : // A bit of a hack. Normally GetMetadata() should also return it,
833 : // but as this is only used for tests, just make GetMetadataItem()
834 : // handle it
835 420 : std::string osCompressors;
836 420 : std::string osFilters;
837 210 : char **decompressors = CPLGetDecompressors();
838 1680 : for (auto iter = decompressors; iter && *iter; ++iter)
839 : {
840 1470 : const auto psCompressor = CPLGetCompressor(*iter);
841 1470 : if (psCompressor)
842 : {
843 1470 : if (psCompressor->eType == CCT_COMPRESSOR)
844 : {
845 1260 : if (!osCompressors.empty())
846 1050 : osCompressors += ',';
847 1260 : osCompressors += *iter;
848 : }
849 210 : else if (psCompressor->eType == CCT_FILTER)
850 : {
851 210 : if (!osFilters.empty())
852 0 : osFilters += ',';
853 210 : osFilters += *iter;
854 : }
855 : }
856 : }
857 210 : CSLDestroy(decompressors);
858 210 : GDALDriver::SetMetadataItem("COMPRESSORS", osCompressors.c_str());
859 210 : GDALDriver::SetMetadataItem("FILTERS", osFilters.c_str());
860 : }
861 : #ifdef HAVE_BLOSC
862 : {
863 210 : GDALDriver::SetMetadataItem("BLOSC_COMPRESSORS",
864 : blosc_list_compressors());
865 : }
866 : #endif
867 :
868 : {
869 : CPLXMLTreeCloser oTree(
870 420 : CPLCreateXMLNode(nullptr, CXT_Element, "CreationOptionList"));
871 210 : char **compressors = CPLGetCompressors();
872 :
873 : auto psCompressNode =
874 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
875 210 : CPLAddXMLAttributeAndValue(psCompressNode, "name", "COMPRESS");
876 210 : CPLAddXMLAttributeAndValue(psCompressNode, "type", "string-select");
877 210 : CPLAddXMLAttributeAndValue(psCompressNode, "description",
878 : "Compression method");
879 210 : CPLAddXMLAttributeAndValue(psCompressNode, "default", "NONE");
880 : {
881 : auto poValueNode =
882 210 : CPLCreateXMLNode(psCompressNode, CXT_Element, "Value");
883 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "NONE");
884 : }
885 :
886 : auto psFilterNode =
887 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
888 210 : CPLAddXMLAttributeAndValue(psFilterNode, "name", "FILTER");
889 210 : CPLAddXMLAttributeAndValue(psFilterNode, "type", "string-select");
890 210 : CPLAddXMLAttributeAndValue(psFilterNode, "description",
891 : "Filter method (only for ZARR_V2)");
892 210 : CPLAddXMLAttributeAndValue(psFilterNode, "default", "NONE");
893 : {
894 : auto poValueNode =
895 210 : CPLCreateXMLNode(psFilterNode, CXT_Element, "Value");
896 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "NONE");
897 : }
898 :
899 : auto psBlockSizeNode =
900 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
901 210 : CPLAddXMLAttributeAndValue(psBlockSizeNode, "name", "BLOCKSIZE");
902 210 : CPLAddXMLAttributeAndValue(psBlockSizeNode, "type", "string");
903 210 : CPLAddXMLAttributeAndValue(
904 : psBlockSizeNode, "description",
905 : "Comma separated list of chunk size along each dimension");
906 :
907 : auto psChunkMemoryLayout =
908 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
909 210 : CPLAddXMLAttributeAndValue(psChunkMemoryLayout, "name",
910 : "CHUNK_MEMORY_LAYOUT");
911 210 : CPLAddXMLAttributeAndValue(psChunkMemoryLayout, "type",
912 : "string-select");
913 210 : CPLAddXMLAttributeAndValue(psChunkMemoryLayout, "description",
914 : "Whether to use C (row-major) order or F "
915 : "(column-major) order in chunks");
916 210 : CPLAddXMLAttributeAndValue(psChunkMemoryLayout, "default", "C");
917 : {
918 : auto poValueNode =
919 210 : CPLCreateXMLNode(psChunkMemoryLayout, CXT_Element, "Value");
920 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "C");
921 : }
922 : {
923 : auto poValueNode =
924 210 : CPLCreateXMLNode(psChunkMemoryLayout, CXT_Element, "Value");
925 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "F");
926 : }
927 :
928 : auto psStringFormat =
929 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
930 210 : CPLAddXMLAttributeAndValue(psStringFormat, "name", "STRING_FORMAT");
931 210 : CPLAddXMLAttributeAndValue(psStringFormat, "type", "string-select");
932 210 : CPLAddXMLAttributeAndValue(psStringFormat, "default", "STRING");
933 : {
934 : auto poValueNode =
935 210 : CPLCreateXMLNode(psStringFormat, CXT_Element, "Value");
936 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "STRING");
937 : }
938 : {
939 : auto poValueNode =
940 210 : CPLCreateXMLNode(psStringFormat, CXT_Element, "Value");
941 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "UNICODE");
942 : }
943 :
944 : auto psDimSeparatorNode =
945 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
946 210 : CPLAddXMLAttributeAndValue(psDimSeparatorNode, "name", "DIM_SEPARATOR");
947 210 : CPLAddXMLAttributeAndValue(psDimSeparatorNode, "type", "string");
948 210 : CPLAddXMLAttributeAndValue(
949 : psDimSeparatorNode, "description",
950 : "Dimension separator in chunk filenames. Default to decimal point "
951 : "for ZarrV2 and slash for ZarrV3");
952 :
953 1680 : for (auto iter = compressors; iter && *iter; ++iter)
954 : {
955 1470 : const auto psCompressor = CPLGetCompressor(*iter);
956 1470 : if (psCompressor)
957 : {
958 1470 : auto poValueNode = CPLCreateXMLNode(
959 1470 : (psCompressor->eType == CCT_COMPRESSOR) ? psCompressNode
960 : : psFilterNode,
961 : CXT_Element, "Value");
962 1470 : CPLCreateXMLNode(poValueNode, CXT_Text,
963 2940 : CPLString(*iter).toupper().c_str());
964 :
965 : const char *pszOptions =
966 1470 : CSLFetchNameValue(psCompressor->papszMetadata, "OPTIONS");
967 1470 : if (pszOptions)
968 : {
969 : CPLXMLTreeCloser oTreeCompressor(
970 2940 : CPLParseXMLString(pszOptions));
971 : const auto psRoot =
972 1470 : oTreeCompressor.get()
973 1470 : ? CPLGetXMLNode(oTreeCompressor.get(), "=Options")
974 1470 : : nullptr;
975 1470 : if (psRoot)
976 : {
977 1470 : for (CPLXMLNode *psNode = psRoot->psChild;
978 4620 : psNode != nullptr; psNode = psNode->psNext)
979 : {
980 3150 : if (psNode->eType == CXT_Element)
981 : {
982 : const char *pszName =
983 3150 : CPLGetXMLValue(psNode, "name", nullptr);
984 3150 : if (pszName &&
985 3150 : !EQUAL(pszName, "TYPESIZE") // Blosc
986 2940 : && !EQUAL(pszName, "HEADER") // LZ4
987 : )
988 : {
989 2730 : CPLXMLNode *psNext = psNode->psNext;
990 2730 : psNode->psNext = nullptr;
991 : CPLXMLNode *psOption =
992 2730 : CPLCloneXMLTree(psNode);
993 :
994 : CPLXMLNode *psName =
995 2730 : CPLGetXMLNode(psOption, "name");
996 2730 : if (psName &&
997 2730 : psName->eType == CXT_Attribute &&
998 2730 : psName->psChild &&
999 2730 : psName->psChild->pszValue)
1000 : {
1001 2730 : CPLString osNewValue(*iter);
1002 2730 : osNewValue = osNewValue.toupper();
1003 2730 : osNewValue += '_';
1004 2730 : osNewValue += psName->psChild->pszValue;
1005 2730 : CPLFree(psName->psChild->pszValue);
1006 5460 : psName->psChild->pszValue =
1007 2730 : CPLStrdup(osNewValue.c_str());
1008 : }
1009 :
1010 : CPLXMLNode *psDescription =
1011 2730 : CPLGetXMLNode(psOption, "description");
1012 2730 : if (psDescription &&
1013 2730 : psDescription->eType == CXT_Attribute &&
1014 2730 : psDescription->psChild &&
1015 2730 : psDescription->psChild->pszValue)
1016 : {
1017 : std::string osNewValue(
1018 2730 : psDescription->psChild->pszValue);
1019 2730 : if (psCompressor->eType ==
1020 : CCT_COMPRESSOR)
1021 : {
1022 : osNewValue +=
1023 2520 : ". Only used when COMPRESS=";
1024 : }
1025 : else
1026 : {
1027 : osNewValue +=
1028 210 : ". Only used when FILTER=";
1029 : }
1030 : osNewValue +=
1031 2730 : CPLString(*iter).toupper();
1032 2730 : CPLFree(
1033 2730 : psDescription->psChild->pszValue);
1034 5460 : psDescription->psChild->pszValue =
1035 2730 : CPLStrdup(osNewValue.c_str());
1036 : }
1037 :
1038 2730 : CPLAddXMLChild(oTree.get(), psOption);
1039 2730 : psNode->psNext = psNext;
1040 : }
1041 : }
1042 : }
1043 : }
1044 : }
1045 : }
1046 : }
1047 210 : CSLDestroy(compressors);
1048 :
1049 : auto psGeoreferencingConvention =
1050 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
1051 210 : CPLAddXMLAttributeAndValue(psGeoreferencingConvention, "name",
1052 : "GEOREFERENCING_CONVENTION");
1053 210 : CPLAddXMLAttributeAndValue(psGeoreferencingConvention, "type",
1054 : "string-select");
1055 210 : CPLAddXMLAttributeAndValue(psGeoreferencingConvention, "default",
1056 : "GDAL");
1057 210 : CPLAddXMLAttributeAndValue(psGeoreferencingConvention, "description",
1058 : "Georeferencing convention to use");
1059 :
1060 : {
1061 210 : auto poValueNode = CPLCreateXMLNode(psGeoreferencingConvention,
1062 : CXT_Element, "Value");
1063 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "GDAL");
1064 : }
1065 : {
1066 210 : auto poValueNode = CPLCreateXMLNode(psGeoreferencingConvention,
1067 : CXT_Element, "Value");
1068 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "SPATIAL_PROJ");
1069 : }
1070 :
1071 : {
1072 210 : char *pszXML = CPLSerializeXMLTree(oTree.get());
1073 210 : GDALDriver::SetMetadataItem(
1074 : GDAL_DMD_MULTIDIM_ARRAY_CREATIONOPTIONLIST,
1075 210 : CPLString(pszXML)
1076 : .replaceAll("CreationOptionList",
1077 420 : "MultiDimArrayCreationOptionList")
1078 : .c_str());
1079 210 : CPLFree(pszXML);
1080 : }
1081 :
1082 : {
1083 : auto psArrayNameOption =
1084 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
1085 210 : CPLAddXMLAttributeAndValue(psArrayNameOption, "name", "ARRAY_NAME");
1086 210 : CPLAddXMLAttributeAndValue(psArrayNameOption, "type", "string");
1087 210 : CPLAddXMLAttributeAndValue(
1088 : psArrayNameOption, "description",
1089 : "Array name. If not specified, deduced from the filename");
1090 :
1091 : auto psAppendSubDSOption =
1092 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
1093 210 : CPLAddXMLAttributeAndValue(psAppendSubDSOption, "name",
1094 : "APPEND_SUBDATASET");
1095 210 : CPLAddXMLAttributeAndValue(psAppendSubDSOption, "type", "boolean");
1096 210 : CPLAddXMLAttributeAndValue(psAppendSubDSOption, "description",
1097 : "Whether to append the new dataset to "
1098 : "an existing Zarr hierarchy");
1099 210 : CPLAddXMLAttributeAndValue(psAppendSubDSOption, "default", "NO");
1100 :
1101 : auto psFormat =
1102 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
1103 210 : CPLAddXMLAttributeAndValue(psFormat, "name", "FORMAT");
1104 210 : CPLAddXMLAttributeAndValue(psFormat, "type", "string-select");
1105 210 : CPLAddXMLAttributeAndValue(psFormat, "default", "ZARR_V2");
1106 : {
1107 : auto poValueNode =
1108 210 : CPLCreateXMLNode(psFormat, CXT_Element, "Value");
1109 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "ZARR_V2");
1110 : }
1111 : {
1112 : auto poValueNode =
1113 210 : CPLCreateXMLNode(psFormat, CXT_Element, "Value");
1114 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "ZARR_V3");
1115 : }
1116 :
1117 : auto psCreateZMetadata =
1118 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
1119 210 : CPLAddXMLAttributeAndValue(psCreateZMetadata, "name",
1120 : "CREATE_CONSOLIDATED_METADATA");
1121 210 : CPLAddXMLAttributeAndValue(psCreateZMetadata, "alias",
1122 : "CREATE_ZMETADATA");
1123 210 : CPLAddXMLAttributeAndValue(psCreateZMetadata, "type", "boolean");
1124 210 : CPLAddXMLAttributeAndValue(
1125 : psCreateZMetadata, "description",
1126 : "Whether to create consolidated metadata");
1127 210 : CPLAddXMLAttributeAndValue(psCreateZMetadata, "default", "YES");
1128 :
1129 : auto psSingleArrayNode =
1130 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
1131 210 : CPLAddXMLAttributeAndValue(psSingleArrayNode, "name",
1132 : "SINGLE_ARRAY");
1133 210 : CPLAddXMLAttributeAndValue(psSingleArrayNode, "type", "boolean");
1134 210 : CPLAddXMLAttributeAndValue(
1135 : psSingleArrayNode, "description",
1136 : "Whether to write a multi-band dataset as a single array, or "
1137 : "one array per band");
1138 210 : CPLAddXMLAttributeAndValue(psSingleArrayNode, "default", "YES");
1139 :
1140 : auto psInterleaveNode =
1141 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
1142 210 : CPLAddXMLAttributeAndValue(psInterleaveNode, "name", "INTERLEAVE");
1143 210 : CPLAddXMLAttributeAndValue(psInterleaveNode, "type",
1144 : "string-select");
1145 210 : CPLAddXMLAttributeAndValue(psInterleaveNode, "default", "BAND");
1146 : {
1147 : auto poValueNode =
1148 210 : CPLCreateXMLNode(psInterleaveNode, CXT_Element, "Value");
1149 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "BAND");
1150 : }
1151 : {
1152 : auto poValueNode =
1153 210 : CPLCreateXMLNode(psInterleaveNode, CXT_Element, "Value");
1154 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "PIXEL");
1155 : }
1156 :
1157 : auto psConvertToParquet =
1158 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
1159 210 : CPLAddXMLAttributeAndValue(psConvertToParquet, "name",
1160 : "CONVERT_TO_KERCHUNK_PARQUET_REFERENCE");
1161 210 : CPLAddXMLAttributeAndValue(psConvertToParquet, "type", "boolean");
1162 210 : CPLAddXMLAttributeAndValue(
1163 : psConvertToParquet, "description",
1164 : "Whether to convert a Kerchunk JSON reference store to a "
1165 : "Kerchunk Parquet reference store. (CreateCopy() only)");
1166 :
1167 210 : char *pszXML = CPLSerializeXMLTree(oTree.get());
1168 210 : GDALDriver::SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST, pszXML);
1169 210 : CPLFree(pszXML);
1170 : }
1171 : }
1172 : }
1173 :
1174 : /************************************************************************/
1175 : /* CreateMultiDimensional() */
1176 : /************************************************************************/
1177 :
1178 : GDALDataset *
1179 278 : ZarrDataset::CreateMultiDimensional(const char *pszFilename,
1180 : CSLConstList /*papszRootGroupOptions*/,
1181 : CSLConstList papszOptions)
1182 : {
1183 : const char *pszFormat =
1184 278 : CSLFetchNameValueDef(papszOptions, "FORMAT", "ZARR_V2");
1185 278 : std::shared_ptr<ZarrGroupBase> poRG;
1186 : auto poSharedResource =
1187 834 : ZarrSharedResource::Create(pszFilename, /*bUpdatable=*/true);
1188 278 : const bool bCreateZMetadata = CPLTestBool(CSLFetchNameValueDef(
1189 : papszOptions, "CREATE_CONSOLIDATED_METADATA",
1190 : CSLFetchNameValueDef(papszOptions, "CREATE_ZMETADATA", "YES")));
1191 278 : if (bCreateZMetadata)
1192 : {
1193 494 : poSharedResource->EnableConsolidatedMetadata(
1194 247 : EQUAL(pszFormat, "ZARR_V3")
1195 : ? ZarrSharedResource::ConsolidatedMetadataKind::INTERNAL
1196 : : ZarrSharedResource::ConsolidatedMetadataKind::EXTERNAL);
1197 : }
1198 278 : if (EQUAL(pszFormat, "ZARR_V3"))
1199 : {
1200 318 : poRG = ZarrV3Group::CreateOnDisk(poSharedResource, std::string(), "/",
1201 159 : pszFilename);
1202 : }
1203 : else
1204 : {
1205 238 : poRG = ZarrV2Group::CreateOnDisk(poSharedResource, std::string(), "/",
1206 119 : pszFilename);
1207 : }
1208 278 : if (!poRG)
1209 0 : return nullptr;
1210 :
1211 278 : auto poDS = new ZarrDataset(poRG);
1212 278 : poDS->SetDescription(pszFilename);
1213 278 : return poDS;
1214 : }
1215 :
1216 : /************************************************************************/
1217 : /* Create() */
1218 : /************************************************************************/
1219 :
1220 96 : GDALDataset *ZarrDataset::Create(const char *pszName, int nXSize, int nYSize,
1221 : int nBandsIn, GDALDataType eType,
1222 : CSLConstList papszOptions)
1223 : {
1224 : // To avoid any issue with short-lived string that would be passed to us
1225 192 : const std::string osName = pszName;
1226 96 : pszName = osName.c_str();
1227 :
1228 96 : if (nBandsIn <= 0 || nXSize <= 0 || nYSize <= 0)
1229 : {
1230 1 : CPLError(CE_Failure, CPLE_NotSupported,
1231 : "nBands, nXSize, nYSize should be > 0");
1232 1 : return nullptr;
1233 : }
1234 :
1235 95 : const bool bAppendSubDS = CPLTestBool(
1236 : CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO"));
1237 95 : const char *pszArrayName = CSLFetchNameValue(papszOptions, "ARRAY_NAME");
1238 :
1239 95 : std::shared_ptr<ZarrGroupBase> poRG;
1240 95 : if (bAppendSubDS)
1241 : {
1242 4 : if (pszArrayName == nullptr)
1243 : {
1244 0 : CPLError(CE_Failure, CPLE_AppDefined,
1245 : "ARRAY_NAME should be provided when "
1246 : "APPEND_SUBDATASET is set to YES");
1247 0 : return nullptr;
1248 : }
1249 : auto poDS =
1250 4 : std::unique_ptr<GDALDataset>(OpenMultidim(pszName, true, nullptr));
1251 4 : if (poDS == nullptr)
1252 : {
1253 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s", pszName);
1254 0 : return nullptr;
1255 : }
1256 4 : poRG = std::dynamic_pointer_cast<ZarrGroupBase>(poDS->GetRootGroup());
1257 : }
1258 : else
1259 : {
1260 : VSIStatBufL sStat;
1261 91 : const bool bExists = VSIStatL(pszName, &sStat) == 0;
1262 91 : const char *pszObjType = nullptr;
1263 91 : if (bExists && !VSI_ISDIR(sStat.st_mode))
1264 0 : pszObjType = "File";
1265 182 : else if ((bExists /* && VSI_ISDIR(sStat.st_mode)*/) ||
1266 182 : !CPLStringList(VSIReadDirEx(pszName, 1)).empty())
1267 0 : pszObjType = "Directory";
1268 91 : if (pszObjType)
1269 : {
1270 0 : CPLError(CE_Failure, CPLE_FileIO, "%s %s already exists.",
1271 : pszObjType, pszName);
1272 0 : return nullptr;
1273 : }
1274 :
1275 : const char *pszFormat =
1276 91 : CSLFetchNameValueDef(papszOptions, "FORMAT", "ZARR_V2");
1277 : auto poSharedResource =
1278 273 : ZarrSharedResource::Create(pszName, /*bUpdatable=*/true);
1279 91 : const bool bCreateZMetadata = CPLTestBool(CSLFetchNameValueDef(
1280 : papszOptions, "CREATE_CONSOLIDATED_METADATA",
1281 : CSLFetchNameValueDef(papszOptions, "CREATE_ZMETADATA", "YES")));
1282 91 : if (bCreateZMetadata)
1283 : {
1284 182 : poSharedResource->EnableConsolidatedMetadata(
1285 91 : EQUAL(pszFormat, "ZARR_V3")
1286 : ? ZarrSharedResource::ConsolidatedMetadataKind::INTERNAL
1287 : : ZarrSharedResource::ConsolidatedMetadataKind::EXTERNAL);
1288 : }
1289 91 : if (EQUAL(pszFormat, "ZARR_V3"))
1290 : {
1291 28 : poRG = ZarrV3Group::CreateOnDisk(poSharedResource, std::string(),
1292 14 : "/", pszName);
1293 : }
1294 : else
1295 : {
1296 154 : poRG = ZarrV2Group::CreateOnDisk(poSharedResource, std::string(),
1297 77 : "/", pszName);
1298 : }
1299 91 : poSharedResource->SetRootGroup(poRG);
1300 : }
1301 95 : if (!poRG)
1302 3 : return nullptr;
1303 :
1304 184 : auto poDS = std::make_unique<ZarrDataset>(poRG);
1305 92 : poDS->SetDescription(pszName);
1306 92 : poDS->nRasterYSize = nYSize;
1307 92 : poDS->nRasterXSize = nXSize;
1308 92 : 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 184 : std::string osDimXType, osDimYType;
1360 92 : if (CPLTestBool(
1361 : CSLFetchNameValueDef(papszOptions, "@HAS_GEOTRANSFORM", "NO")))
1362 : {
1363 46 : osDimXType = GDAL_DIM_TYPE_HORIZONTAL_X;
1364 46 : osDimYType = GDAL_DIM_TYPE_HORIZONTAL_Y;
1365 : }
1366 92 : poDS->m_bSpatialProjConvention = EQUAL(
1367 : CSLFetchNameValueDef(papszOptions, "GEOREFERENCING_CONVENTION", "GDAL"),
1368 : "SPATIAL_PROJ");
1369 :
1370 92 : 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 88 : poDS->m_poDimY =
1402 176 : poRG->CreateDimension("Y", osDimYType, std::string(), nYSize);
1403 88 : poDS->m_poDimX =
1404 176 : poRG->CreateDimension("X", osDimXType, std::string(), nXSize);
1405 : }
1406 92 : if (poDS->m_poDimY == nullptr || poDS->m_poDimX == nullptr)
1407 : {
1408 0 : CleanupCreatedFiles();
1409 0 : return nullptr;
1410 : }
1411 :
1412 : const bool bSingleArray =
1413 92 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "SINGLE_ARRAY", "YES"));
1414 : const bool bBandInterleave =
1415 92 : EQUAL(CSLFetchNameValueDef(papszOptions, "INTERLEAVE", "BAND"), "BAND");
1416 : std::shared_ptr<GDALDimension> poBandDim(
1417 90 : (bSingleArray && nBandsIn > 1)
1418 121 : ? poRG->CreateDimension("Band", std::string(), std::string(),
1419 29 : nBandsIn)
1420 361 : : nullptr);
1421 :
1422 : const std::string osNonNullArrayName =
1423 184 : pszArrayName ? std::string(pszArrayName) : CPLGetBasenameSafe(pszName);
1424 92 : 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 252 : poDS->m_poDimY, poDS->m_poDimX};
1477 122 : for (int i = 0; i < nBandsIn; i++)
1478 : {
1479 67 : auto poArray = poRG->CreateMDArray(
1480 61 : nBandsIn == 1 ? osNonNullArrayName.c_str()
1481 6 : : pszArrayName ? CPLSPrintf("%s_band%d", pszArrayName, i + 1)
1482 0 : : CPLSPrintf("Band%d", i + 1),
1483 201 : apoDims, GDALExtendedDataType::Create(eType), papszOptions);
1484 67 : if (poArray == nullptr)
1485 : {
1486 8 : CleanupCreatedFiles();
1487 8 : return nullptr;
1488 : }
1489 59 : poDS->SetBand(i + 1, std::make_unique<ZarrRasterBand>(poArray));
1490 : }
1491 : }
1492 :
1493 82 : return poDS.release();
1494 : }
1495 :
1496 : /************************************************************************/
1497 : /* ~ZarrDataset() */
1498 : /************************************************************************/
1499 :
1500 4078 : ZarrDataset::~ZarrDataset()
1501 : {
1502 2039 : ZarrDataset::FlushCache(true);
1503 4078 : }
1504 :
1505 : /************************************************************************/
1506 : /* FlushCache() */
1507 : /************************************************************************/
1508 :
1509 2083 : CPLErr ZarrDataset::FlushCache(bool bAtClosing)
1510 : {
1511 2083 : CPLErr eErr = GDALDataset::FlushCache(bAtClosing);
1512 :
1513 2083 : if (m_poSingleArray && !m_poSingleArray->Flush())
1514 : {
1515 0 : eErr = CE_Failure;
1516 : }
1517 :
1518 2083 : 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 2083 : if (m_poRootGroup)
1555 : {
1556 1932 : if (bAtClosing)
1557 : {
1558 1888 : if (!m_poRootGroup->Close())
1559 6 : eErr = CE_Failure;
1560 : }
1561 : else
1562 : {
1563 44 : if (!m_poRootGroup->Flush())
1564 3 : eErr = CE_Failure;
1565 : }
1566 : }
1567 :
1568 2083 : return eErr;
1569 : }
1570 :
1571 : /************************************************************************/
1572 : /* GetRootGroup() */
1573 : /************************************************************************/
1574 :
1575 1804 : std::shared_ptr<GDALGroup> ZarrDataset::GetRootGroup() const
1576 : {
1577 1804 : 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 68 : CPLErr ZarrDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
1598 : {
1599 194 : for (int i = 0; i < nBands; ++i)
1600 : {
1601 126 : cpl::down_cast<ZarrRasterBand *>(papoBands[i])
1602 126 : ->m_poArray->SetSpatialRef(poSRS);
1603 : }
1604 68 : 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 70 : CPLErr ZarrDataset::SetGeoTransform(const GDALGeoTransform >)
1622 : {
1623 70 : const bool bHasRotatedTerms = (gt.xrot != 0 || gt.yrot != 0);
1624 :
1625 70 : 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 69 : 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 70 : m_gt = gt;
1644 70 : m_bHasGT = true;
1645 :
1646 70 : 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 70 : if (!bHasRotatedTerms)
1672 : {
1673 69 : CPLAssert(m_poDimX);
1674 69 : CPLAssert(m_poDimY);
1675 :
1676 69 : const auto oDTFloat64 = GDALExtendedDataType::Create(GDT_Float64);
1677 : {
1678 69 : auto poX = m_poRootGroup->OpenMDArray(m_poDimX->GetName());
1679 69 : if (!poX)
1680 340 : poX = m_poRootGroup->CreateMDArray(
1681 272 : m_poDimX->GetName(), {m_poDimX}, oDTFloat64, nullptr);
1682 69 : if (!poX)
1683 0 : return CE_Failure;
1684 69 : m_poDimX->SetIndexingVariable(poX);
1685 69 : std::vector<double> adfX;
1686 : try
1687 : {
1688 69 : adfX.reserve(nRasterXSize);
1689 5803 : for (int i = 0; i < nRasterXSize; ++i)
1690 5734 : 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 69 : const GUInt64 nStartIndex = 0;
1699 69 : const size_t nCount = adfX.size();
1700 69 : const GInt64 arrayStep = 1;
1701 69 : const GPtrDiff_t bufferStride = 1;
1702 138 : if (!poX->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
1703 69 : poX->GetDataType(), adfX.data()))
1704 : {
1705 0 : return CE_Failure;
1706 : }
1707 : }
1708 :
1709 69 : auto poY = m_poRootGroup->OpenMDArray(m_poDimY->GetName());
1710 69 : if (!poY)
1711 272 : poY = m_poRootGroup->CreateMDArray(m_poDimY->GetName(), {m_poDimY},
1712 204 : oDTFloat64, nullptr);
1713 69 : if (!poY)
1714 0 : return CE_Failure;
1715 69 : m_poDimY->SetIndexingVariable(poY);
1716 69 : std::vector<double> adfY;
1717 : try
1718 : {
1719 69 : adfY.reserve(nRasterYSize);
1720 4591 : for (int i = 0; i < nRasterYSize; ++i)
1721 4522 : 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 69 : const GUInt64 nStartIndex = 0;
1730 69 : const size_t nCount = adfY.size();
1731 69 : const GInt64 arrayStep = 1;
1732 69 : const GPtrDiff_t bufferStride = 1;
1733 138 : if (!poY->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
1734 69 : poY->GetDataType(), adfY.data()))
1735 : {
1736 0 : return CE_Failure;
1737 : }
1738 : }
1739 :
1740 70 : return CE_None;
1741 : }
1742 :
1743 : /************************************************************************/
1744 : /* SetMetadata() */
1745 : /************************************************************************/
1746 :
1747 41 : CPLErr ZarrDataset::SetMetadata(CSLConstList papszMetadata,
1748 : const char *pszDomain)
1749 : {
1750 41 : if (nBands >= 1 && (pszDomain == nullptr || pszDomain[0] == '\0'))
1751 : {
1752 82 : const auto oStringDT = GDALExtendedDataType::CreateString();
1753 41 : const auto bSingleArray = m_poSingleArray != nullptr;
1754 41 : const int nIters = bSingleArray ? 1 : nBands;
1755 86 : for (int i = 0; i < nIters; ++i)
1756 : {
1757 : auto *poArray = bSingleArray
1758 45 : ? m_poSingleArray.get()
1759 33 : : cpl::down_cast<ZarrRasterBand *>(papoBands[i])
1760 33 : ->m_poArray.get();
1761 90 : for (auto iter = papszMetadata; iter && *iter; ++iter)
1762 : {
1763 45 : char *pszKey = nullptr;
1764 45 : const char *pszValue = CPLParseNameValue(*iter, &pszKey);
1765 45 : if (pszKey && pszValue)
1766 : {
1767 : auto poAttr =
1768 66 : poArray->CreateAttribute(pszKey, {}, oStringDT);
1769 22 : if (poAttr)
1770 : {
1771 22 : const GUInt64 nStartIndex = 0;
1772 22 : const size_t nCount = 1;
1773 22 : const GInt64 arrayStep = 1;
1774 22 : const GPtrDiff_t bufferStride = 1;
1775 22 : poAttr->Write(&nStartIndex, &nCount, &arrayStep,
1776 : &bufferStride, oStringDT, &pszValue);
1777 : }
1778 : }
1779 45 : CPLFree(pszKey);
1780 : }
1781 : }
1782 : }
1783 41 : return GDALDataset::SetMetadata(papszMetadata, pszDomain);
1784 : }
1785 :
1786 : /************************************************************************/
1787 : /* ZarrRasterBand::ZarrRasterBand() */
1788 : /************************************************************************/
1789 :
1790 144 : ZarrRasterBand::ZarrRasterBand(const std::shared_ptr<GDALMDArray> &poArray)
1791 144 : : m_poArray(poArray)
1792 : {
1793 144 : assert(poArray->GetDimensionCount() == 2);
1794 144 : eDataType = poArray->GetDataType().GetNumericDataType();
1795 144 : nBlockXSize = static_cast<int>(poArray->GetBlockSize()[1]);
1796 144 : nBlockYSize = static_cast<int>(poArray->GetBlockSize()[0]);
1797 144 : }
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 141 : GDALColorInterp ZarrRasterBand::GetColorInterpretation()
1933 : {
1934 141 : return m_eColorInterp;
1935 : }
1936 :
1937 : /************************************************************************/
1938 : /* SetColorInterpretation() */
1939 : /************************************************************************/
1940 :
1941 32 : CPLErr ZarrRasterBand::SetColorInterpretation(GDALColorInterp eColorInterp)
1942 : {
1943 32 : auto poGDS = cpl::down_cast<ZarrDataset *>(poDS);
1944 32 : m_eColorInterp = eColorInterp;
1945 32 : if (!poGDS->m_poSingleArray)
1946 : {
1947 8 : const auto oStringDT = GDALExtendedDataType::CreateString();
1948 16 : auto poAttr = m_poArray->GetAttribute("COLOR_INTERPRETATION");
1949 8 : if (poAttr && (poAttr->GetDimensionCount() != 0 ||
1950 8 : poAttr->GetDataType().GetClass() != GEDTC_STRING))
1951 0 : return CE_None;
1952 8 : if (!poAttr)
1953 24 : poAttr = m_poArray->CreateAttribute("COLOR_INTERPRETATION", {},
1954 16 : oStringDT);
1955 8 : if (poAttr)
1956 : {
1957 8 : const GUInt64 nStartIndex = 0;
1958 8 : const size_t nCount = 1;
1959 8 : const GInt64 arrayStep = 1;
1960 8 : const GPtrDiff_t bufferStride = 1;
1961 8 : const char *pszValue = GDALGetColorInterpretationName(eColorInterp);
1962 8 : poAttr->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
1963 : oStringDT, &pszValue);
1964 : }
1965 : }
1966 32 : 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 51 : 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 51 : 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 51 : if (nXSize == nBufXSize && nYSize == nBufYSize && nBufferDTSize > 0 &&
2030 51 : (nPixelSpaceBuf % nBufferDTSize) == 0 &&
2031 51 : (nLineSpaceBuf % nBufferDTSize) == 0 && (nXOff % nBlockXSize) == 0 &&
2032 51 : (nYOff % nBlockYSize) == 0 &&
2033 51 : ((nXSize % nBlockXSize) == 0 || nXOff + nXSize == nRasterXSize) &&
2034 51 : ((nYSize % nBlockYSize) == 0 || nYOff + nYSize == nRasterYSize))
2035 : {
2036 51 : GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
2037 51 : static_cast<GUInt64>(nXOff)};
2038 51 : size_t count[] = {static_cast<size_t>(nYSize),
2039 51 : static_cast<size_t>(nXSize)};
2040 51 : constexpr GInt64 arrayStep[] = {1, 1};
2041 : GPtrDiff_t bufferStride[] = {
2042 51 : static_cast<GPtrDiff_t>(nLineSpaceBuf / nBufferDTSize),
2043 51 : static_cast<GPtrDiff_t>(nPixelSpaceBuf / nBufferDTSize)};
2044 :
2045 51 : 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 98 : return m_poArray->Write(
2056 : arrayStartIdx, count, arrayStep, bufferStride,
2057 98 : GDALExtendedDataType::Create(eBufType), pData)
2058 49 : ? CE_None
2059 49 : : 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 59 : 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 59 : 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 59 : int nBlockXSize = 0, nBlockYSize = 0;
2085 59 : papoBands[0]->GetBlockSize(&nBlockXSize, &nBlockYSize);
2086 91 : 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 123 : ((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 47 : return GDALDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
2160 : nBufXSize, nBufYSize, eBufType, nBandCount,
2161 : panBandMap, nPixelSpace, nLineSpace,
2162 47 : nBandSpace, psExtraArg);
2163 : }
2164 :
2165 : /************************************************************************/
2166 : /* ZarrDataset::CreateCopy() */
2167 : /************************************************************************/
2168 :
2169 : /* static */
2170 52 : GDALDataset *ZarrDataset::CreateCopy(const char *pszFilename,
2171 : GDALDataset *poSrcDS, int bStrict,
2172 : CSLConstList papszOptions,
2173 : GDALProgressFunc pfnProgress,
2174 : void *pProgressData)
2175 : {
2176 52 : 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 51 : auto poDriver = GetGDALDriverManager()->GetDriverByName(DRIVER_NAME);
2192 : CPLStringList aosCreationOptions(
2193 102 : const_cast<CSLConstList>(papszOptions));
2194 51 : GDALGeoTransform gt;
2195 51 : if (poSrcDS->GetGeoTransform(gt) == CE_None)
2196 : {
2197 50 : aosCreationOptions.SetNameValue("@HAS_GEOTRANSFORM", "YES");
2198 : }
2199 : auto poDS = std::unique_ptr<GDALDataset>(poDriver->DefaultCreateCopy(
2200 51 : pszFilename, poSrcDS, bStrict, aosCreationOptions.List(),
2201 102 : pfnProgress, pProgressData));
2202 51 : if (poDS)
2203 : {
2204 39 : if (poDS->FlushCache() != CE_None)
2205 3 : poDS.reset();
2206 : }
2207 51 : return poDS.release();
2208 : }
2209 0 : return nullptr;
2210 : }
2211 :
2212 : /************************************************************************/
2213 : /* ZARRAddGeoreferencingConventionAlgorithm */
2214 : /************************************************************************/
2215 :
2216 : #ifndef _
2217 : #define _(x) (x)
2218 : #endif
2219 :
2220 : namespace
2221 : {
2222 : class ZARRAddGeoreferencingConventionAlgorithm final : public GDALAlgorithm
2223 : {
2224 : public:
2225 134 : ZARRAddGeoreferencingConventionAlgorithm()
2226 134 : : GDALAlgorithm(
2227 : "add-georeferencing-convention",
2228 268 : std::string("Add a georeferencing convention to an existing ZARR "
2229 : "dataset"),
2230 402 : "/programs/gdal_driver_zarr_add_georeferencing_convention.html")
2231 : {
2232 : AddInputDatasetArg(&m_dataset,
2233 134 : GDAL_OF_MULTIDIM_RASTER | GDAL_OF_UPDATE);
2234 : AddArg("convention", 0, _("Georeferencing convention"),
2235 268 : &m_georeferencingConvention)
2236 134 : .SetRequired()
2237 134 : .SetPositional()
2238 134 : .SetChoices("GDAL", "spatial_proj");
2239 134 : }
2240 :
2241 : protected:
2242 : bool RunImpl(GDALProgressFunc, void *) override;
2243 :
2244 : private:
2245 : GDALArgDatasetValue m_dataset{};
2246 : std::string m_georeferencingConvention{};
2247 : };
2248 :
2249 2 : bool ZARRAddGeoreferencingConventionAlgorithm::RunImpl(GDALProgressFunc, void *)
2250 : {
2251 2 : auto poDS = dynamic_cast<ZarrDataset *>(m_dataset.GetDatasetRef());
2252 2 : if (!poDS)
2253 : {
2254 1 : ReportError(CE_Failure, CPLE_AppDefined, "%s is not a ZARR dataset",
2255 1 : m_dataset.GetName().c_str());
2256 1 : return false;
2257 : }
2258 :
2259 1 : auto poRG = poDS->GetRootGroup();
2260 1 : CPLAssert(poRG);
2261 :
2262 1 : poRG->RecursivelyVisitArrays(
2263 4 : [this](const std::shared_ptr<GDALMDArray> &poArray)
2264 : {
2265 3 : ZarrArray *poZarrArray = dynamic_cast<ZarrArray *>(poArray.get());
2266 3 : if (poZarrArray && poZarrArray->GetSpatialRef())
2267 : {
2268 2 : CPLStringList aosOptions;
2269 : aosOptions.SetNameValue("GEOREFERENCING_CONVENTION",
2270 1 : m_georeferencingConvention.c_str());
2271 1 : poZarrArray->SetCreationOptions(aosOptions.List());
2272 1 : poZarrArray->InvalidateGeoreferencing();
2273 : }
2274 3 : });
2275 :
2276 1 : return true;
2277 : }
2278 : } // namespace
2279 :
2280 : /************************************************************************/
2281 : /* ZarrDriverInstantiateAlgorithm() */
2282 : /************************************************************************/
2283 :
2284 : static GDALAlgorithm *
2285 134 : ZarrDriverInstantiateAlgorithm(const std::vector<std::string> &aosPath)
2286 : {
2287 134 : if (aosPath.size() == 1 && aosPath[0] == "add-georeferencing-convention")
2288 : {
2289 268 : return std::make_unique<ZARRAddGeoreferencingConventionAlgorithm>()
2290 134 : .release();
2291 : }
2292 : else
2293 : {
2294 0 : return nullptr;
2295 : }
2296 : }
2297 :
2298 : /************************************************************************/
2299 : /* GDALRegister_Zarr() */
2300 : /************************************************************************/
2301 :
2302 2082 : void GDALRegister_Zarr()
2303 :
2304 : {
2305 2082 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
2306 263 : return;
2307 :
2308 1819 : VSIInstallKerchunkFileSystems();
2309 :
2310 1819 : GDALDriver *poDriver = new ZarrDriver();
2311 1819 : ZARRDriverSetCommonMetadata(poDriver);
2312 :
2313 1819 : poDriver->pfnOpen = ZarrDataset::Open;
2314 1819 : poDriver->pfnCreateMultiDimensional = ZarrDataset::CreateMultiDimensional;
2315 1819 : poDriver->pfnCreate = ZarrDataset::Create;
2316 1819 : poDriver->pfnCreateCopy = ZarrDataset::CreateCopy;
2317 1819 : poDriver->pfnDelete = ZarrDatasetDelete;
2318 1819 : poDriver->pfnRename = ZarrDatasetRename;
2319 1819 : poDriver->pfnCopyFiles = ZarrDatasetCopyFiles;
2320 1819 : poDriver->pfnClearCaches = ZarrDriverClearCaches;
2321 1819 : poDriver->pfnInstantiateAlgorithm = ZarrDriverInstantiateAlgorithm;
2322 :
2323 1819 : GetGDALDriverManager()->RegisterDriver(poDriver);
2324 : }
|