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 2082 : ZarrDataset::ZarrDataset(const std::shared_ptr<ZarrGroupBase> &poRootGroup)
38 2082 : : m_poRootGroup(poRootGroup)
39 : {
40 2082 : }
41 :
42 : /************************************************************************/
43 : /* OpenMultidim() */
44 : /************************************************************************/
45 :
46 1669 : GDALDataset *ZarrDataset::OpenMultidim(const char *pszFilename,
47 : bool bUpdateMode,
48 : CSLConstList papszOpenOptionsIn)
49 : {
50 3338 : CPLString osFilename(pszFilename);
51 1669 : if (osFilename.back() == '/')
52 0 : osFilename.pop_back();
53 :
54 3338 : auto poSharedResource = ZarrSharedResource::Create(osFilename, bUpdateMode);
55 1669 : poSharedResource->SetOpenOptions(papszOpenOptionsIn);
56 :
57 3338 : auto poRG = poSharedResource->GetRootGroup();
58 1669 : 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 1561 : 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, GDAL_MDD_SUBDATASETS))
124 0 : return m_aosSubdatasets.FetchNameValue(pszName);
125 54 : if (pszDomain != nullptr && EQUAL(pszDomain, GDAL_MDD_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, GDAL_MDD_SUBDATASETS))
137 11 : return m_aosSubdatasets.List();
138 13 : if (pszDomain != nullptr && EQUAL(pszDomain, GDAL_MDD_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 1685 : GDALDataset *ZarrDataset::Open(GDALOpenInfo *poOpenInfo)
276 : {
277 1685 : 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 1685 : 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 1684 : const bool bKerchunkCached = CPLFetchBool(poOpenInfo->papszOpenOptions,
301 : "CACHE_KERCHUNK_JSON", false);
302 :
303 1684 : 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 1665 : else if (STARTS_WITH(poOpenInfo->pszFilename, JSON_REF_FS_PREFIX) ||
319 1663 : 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 3326 : CPLString osFilename(poOpenInfo->pszFilename);
330 1663 : if (!poOpenInfo->bIsDirectory)
331 : {
332 154 : osFilename = CPLGetPathSafe(osFilename);
333 : }
334 3326 : CPLString osArrayOfInterest;
335 3326 : std::vector<uint64_t> anExtraDimIndices;
336 1663 : if (STARTS_WITH(poOpenInfo->pszFilename, "ZARR:"))
337 : {
338 : const CPLStringList aosTokens(CSLTokenizeString2(
339 109 : poOpenInfo->pszFilename, ":", CSLT_HONOURSTRINGS));
340 109 : if (aosTokens.size() < 2)
341 0 : return nullptr;
342 109 : osFilename = aosTokens[1];
343 :
344 424 : if (!cpl::starts_with(osFilename, JSON_REF_FS_PREFIX) &&
345 193 : !cpl::starts_with(osFilename, JSON_REF_CACHED_FS_PREFIX) &&
346 193 : 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 109 : std::string osErrorMsg;
362 109 : 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 216 : else if (osFilename == "/vsicurl/http" ||
369 108 : osFilename == "/vsicurl/https")
370 : {
371 : osErrorMsg = "There is likely a quoting error of the whole "
372 1 : "connection string.";
373 : }
374 214 : else if (STARTS_WITH(osFilename.c_str(), "http://") ||
375 107 : STARTS_WITH(osFilename.c_str(), "https://"))
376 : {
377 : osErrorMsg =
378 1 : "The filename should likely be prefixed with /vsicurl/";
379 : }
380 109 : if (!osErrorMsg.empty())
381 : {
382 3 : CPLError(CE_Failure, CPLE_AppDefined, "%s", osErrorMsg.c_str());
383 3 : return nullptr;
384 : }
385 106 : 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 1660 : OpenMultidim(osFilename.c_str(), poOpenInfo->eAccess == GA_Update,
398 3320 : poOpenInfo->papszOpenOptions));
399 3217 : if (poDSMultiDim == nullptr ||
400 1557 : (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER) != 0)
401 : {
402 1509 : 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 16 : poNewDS->SetMetadata(poDS->m_aosSubdatasets.List(),
720 8 : GDAL_MDD_SUBDATASETS);
721 : }
722 137 : return poNewDS.release();
723 : }
724 :
725 6 : return poDS.release();
726 : }
727 :
728 : /************************************************************************/
729 : /* ZarrDatasetDelete() */
730 : /************************************************************************/
731 :
732 5 : static CPLErr ZarrDatasetDelete(const char *pszFilename)
733 : {
734 5 : if (STARTS_WITH(pszFilename, "ZARR:"))
735 : {
736 0 : CPLError(CE_Failure, CPLE_AppDefined,
737 : "Delete() only supported on ZARR connection names "
738 : "not starting with the ZARR: prefix");
739 0 : return CE_Failure;
740 : }
741 5 : return VSIRmdirRecursive(pszFilename) == 0 ? CE_None : CE_Failure;
742 : }
743 :
744 : /************************************************************************/
745 : /* ZarrDatasetRename() */
746 : /************************************************************************/
747 :
748 2 : static CPLErr ZarrDatasetRename(const char *pszNewName, const char *pszOldName)
749 : {
750 2 : if (STARTS_WITH(pszNewName, "ZARR:") || STARTS_WITH(pszOldName, "ZARR:"))
751 : {
752 0 : CPLError(CE_Failure, CPLE_AppDefined,
753 : "Rename() only supported on ZARR connection names "
754 : "not starting with the ZARR: prefix");
755 0 : return CE_Failure;
756 : }
757 2 : return VSIRename(pszOldName, pszNewName) == 0 ? CE_None : CE_Failure;
758 : }
759 :
760 : /************************************************************************/
761 : /* ZarrDatasetCopyFiles() */
762 : /************************************************************************/
763 :
764 2 : static CPLErr ZarrDatasetCopyFiles(const char *pszNewName,
765 : const char *pszOldName)
766 : {
767 2 : if (STARTS_WITH(pszNewName, "ZARR:") || STARTS_WITH(pszOldName, "ZARR:"))
768 : {
769 0 : CPLError(CE_Failure, CPLE_AppDefined,
770 : "CopyFiles() only supported on ZARR connection names "
771 : "not starting with the ZARR: prefix");
772 0 : return CE_Failure;
773 : }
774 : // VSISync() returns true in case of success
775 4 : return VSISync((std::string(pszOldName) + '/').c_str(), pszNewName, nullptr,
776 : nullptr, nullptr, nullptr)
777 2 : ? CE_None
778 2 : : CE_Failure;
779 : }
780 :
781 : /************************************************************************/
782 : /* ZarrDriverClearCaches() */
783 : /************************************************************************/
784 :
785 1006 : static void ZarrDriverClearCaches(GDALDriver *)
786 : {
787 1006 : ZarrClearCoordinateCache();
788 1006 : ZarrClearShardIndexCache();
789 1006 : }
790 :
791 : /************************************************************************/
792 : /* ZarrDriver() */
793 : /************************************************************************/
794 :
795 : class ZarrDriver final : public GDALDriver
796 : {
797 : std::recursive_mutex m_oMutex{};
798 : bool m_bMetadataInitialized = false;
799 : void InitMetadata();
800 :
801 : public:
802 : const char *GetMetadataItem(const char *pszName,
803 : const char *pszDomain) override;
804 :
805 446 : CSLConstList GetMetadata(const char *pszDomain) override
806 : {
807 892 : std::lock_guard oLock(m_oMutex);
808 446 : InitMetadata();
809 892 : return GDALDriver::GetMetadata(pszDomain);
810 : }
811 : };
812 :
813 37729 : const char *ZarrDriver::GetMetadataItem(const char *pszName,
814 : const char *pszDomain)
815 : {
816 75458 : std::lock_guard oLock(m_oMutex);
817 37729 : if (EQUAL(pszName, "COMPRESSORS") || EQUAL(pszName, "BLOSC_COMPRESSORS") ||
818 37692 : EQUAL(pszName, GDAL_DMD_CREATIONOPTIONLIST) ||
819 37340 : EQUAL(pszName, GDAL_DMD_MULTIDIM_ARRAY_CREATIONOPTIONLIST))
820 : {
821 390 : InitMetadata();
822 : }
823 75458 : return GDALDriver::GetMetadataItem(pszName, pszDomain);
824 : }
825 :
826 836 : void ZarrDriver::InitMetadata()
827 : {
828 836 : if (m_bMetadataInitialized)
829 626 : return;
830 210 : m_bMetadataInitialized = true;
831 :
832 : {
833 : // A bit of a hack. Normally GetMetadata() should also return it,
834 : // but as this is only used for tests, just make GetMetadataItem()
835 : // handle it
836 420 : std::string osCompressors;
837 420 : std::string osFilters;
838 210 : char **decompressors = CPLGetDecompressors();
839 1680 : for (auto iter = decompressors; iter && *iter; ++iter)
840 : {
841 1470 : const auto psCompressor = CPLGetCompressor(*iter);
842 1470 : if (psCompressor)
843 : {
844 1470 : if (psCompressor->eType == CCT_COMPRESSOR)
845 : {
846 1260 : if (!osCompressors.empty())
847 1050 : osCompressors += ',';
848 1260 : osCompressors += *iter;
849 : }
850 210 : else if (psCompressor->eType == CCT_FILTER)
851 : {
852 210 : if (!osFilters.empty())
853 0 : osFilters += ',';
854 210 : osFilters += *iter;
855 : }
856 : }
857 : }
858 210 : CSLDestroy(decompressors);
859 210 : GDALDriver::SetMetadataItem("COMPRESSORS", osCompressors.c_str());
860 210 : GDALDriver::SetMetadataItem("FILTERS", osFilters.c_str());
861 : }
862 : #ifdef HAVE_BLOSC
863 : {
864 210 : GDALDriver::SetMetadataItem("BLOSC_COMPRESSORS",
865 : blosc_list_compressors());
866 : }
867 : #endif
868 :
869 : {
870 : CPLXMLTreeCloser oTree(
871 420 : CPLCreateXMLNode(nullptr, CXT_Element, "CreationOptionList"));
872 210 : char **compressors = CPLGetCompressors();
873 :
874 : auto psCompressNode =
875 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
876 210 : CPLAddXMLAttributeAndValue(psCompressNode, "name", "COMPRESS");
877 210 : CPLAddXMLAttributeAndValue(psCompressNode, "type", "string-select");
878 210 : CPLAddXMLAttributeAndValue(psCompressNode, "description",
879 : "Compression method");
880 210 : CPLAddXMLAttributeAndValue(psCompressNode, "default", "NONE");
881 : {
882 : auto poValueNode =
883 210 : CPLCreateXMLNode(psCompressNode, CXT_Element, "Value");
884 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "NONE");
885 : }
886 :
887 : auto psFilterNode =
888 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
889 210 : CPLAddXMLAttributeAndValue(psFilterNode, "name", "FILTER");
890 210 : CPLAddXMLAttributeAndValue(psFilterNode, "type", "string-select");
891 210 : CPLAddXMLAttributeAndValue(psFilterNode, "description",
892 : "Filter method (only for ZARR_V2)");
893 210 : CPLAddXMLAttributeAndValue(psFilterNode, "default", "NONE");
894 : {
895 : auto poValueNode =
896 210 : CPLCreateXMLNode(psFilterNode, CXT_Element, "Value");
897 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "NONE");
898 : }
899 :
900 : auto psBlockSizeNode =
901 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
902 210 : CPLAddXMLAttributeAndValue(psBlockSizeNode, "name", "BLOCKSIZE");
903 210 : CPLAddXMLAttributeAndValue(psBlockSizeNode, "type", "string");
904 210 : CPLAddXMLAttributeAndValue(
905 : psBlockSizeNode, "description",
906 : "Comma separated list of chunk size along each dimension");
907 :
908 : auto psChunkMemoryLayout =
909 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
910 210 : CPLAddXMLAttributeAndValue(psChunkMemoryLayout, "name",
911 : "CHUNK_MEMORY_LAYOUT");
912 210 : CPLAddXMLAttributeAndValue(psChunkMemoryLayout, "type",
913 : "string-select");
914 210 : CPLAddXMLAttributeAndValue(psChunkMemoryLayout, "description",
915 : "Whether to use C (row-major) order or F "
916 : "(column-major) order in chunks");
917 210 : CPLAddXMLAttributeAndValue(psChunkMemoryLayout, "default", "C");
918 : {
919 : auto poValueNode =
920 210 : CPLCreateXMLNode(psChunkMemoryLayout, CXT_Element, "Value");
921 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "C");
922 : }
923 : {
924 : auto poValueNode =
925 210 : CPLCreateXMLNode(psChunkMemoryLayout, CXT_Element, "Value");
926 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "F");
927 : }
928 :
929 : auto psStringFormat =
930 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
931 210 : CPLAddXMLAttributeAndValue(psStringFormat, "name", "STRING_FORMAT");
932 210 : CPLAddXMLAttributeAndValue(psStringFormat, "type", "string-select");
933 210 : CPLAddXMLAttributeAndValue(psStringFormat, "default", "STRING");
934 : {
935 : auto poValueNode =
936 210 : CPLCreateXMLNode(psStringFormat, CXT_Element, "Value");
937 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "STRING");
938 : }
939 : {
940 : auto poValueNode =
941 210 : CPLCreateXMLNode(psStringFormat, CXT_Element, "Value");
942 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "UNICODE");
943 : }
944 :
945 : auto psDimSeparatorNode =
946 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
947 210 : CPLAddXMLAttributeAndValue(psDimSeparatorNode, "name", "DIM_SEPARATOR");
948 210 : CPLAddXMLAttributeAndValue(psDimSeparatorNode, "type", "string");
949 210 : CPLAddXMLAttributeAndValue(
950 : psDimSeparatorNode, "description",
951 : "Dimension separator in chunk filenames. Default to decimal point "
952 : "for ZarrV2 and slash for ZarrV3");
953 :
954 1680 : for (auto iter = compressors; iter && *iter; ++iter)
955 : {
956 1470 : const auto psCompressor = CPLGetCompressor(*iter);
957 1470 : if (psCompressor)
958 : {
959 1470 : auto poValueNode = CPLCreateXMLNode(
960 1470 : (psCompressor->eType == CCT_COMPRESSOR) ? psCompressNode
961 : : psFilterNode,
962 : CXT_Element, "Value");
963 1470 : CPLCreateXMLNode(poValueNode, CXT_Text,
964 2940 : CPLString(*iter).toupper().c_str());
965 :
966 : const char *pszOptions =
967 1470 : CSLFetchNameValue(psCompressor->papszMetadata, "OPTIONS");
968 1470 : if (pszOptions)
969 : {
970 : CPLXMLTreeCloser oTreeCompressor(
971 2940 : CPLParseXMLString(pszOptions));
972 : const auto psRoot =
973 1470 : oTreeCompressor.get()
974 1470 : ? CPLGetXMLNode(oTreeCompressor.get(), "=Options")
975 1470 : : nullptr;
976 1470 : if (psRoot)
977 : {
978 1470 : for (CPLXMLNode *psNode = psRoot->psChild;
979 4620 : psNode != nullptr; psNode = psNode->psNext)
980 : {
981 3150 : if (psNode->eType == CXT_Element)
982 : {
983 : const char *pszName =
984 3150 : CPLGetXMLValue(psNode, "name", nullptr);
985 3150 : if (pszName &&
986 3150 : !EQUAL(pszName, "TYPESIZE") // Blosc
987 2940 : && !EQUAL(pszName, "HEADER") // LZ4
988 : )
989 : {
990 2730 : CPLXMLNode *psNext = psNode->psNext;
991 2730 : psNode->psNext = nullptr;
992 : CPLXMLNode *psOption =
993 2730 : CPLCloneXMLTree(psNode);
994 :
995 : CPLXMLNode *psName =
996 2730 : CPLGetXMLNode(psOption, "name");
997 2730 : if (psName &&
998 2730 : psName->eType == CXT_Attribute &&
999 2730 : psName->psChild &&
1000 2730 : psName->psChild->pszValue)
1001 : {
1002 2730 : CPLString osNewValue(*iter);
1003 2730 : osNewValue = osNewValue.toupper();
1004 2730 : osNewValue += '_';
1005 2730 : osNewValue += psName->psChild->pszValue;
1006 2730 : CPLFree(psName->psChild->pszValue);
1007 5460 : psName->psChild->pszValue =
1008 2730 : CPLStrdup(osNewValue.c_str());
1009 : }
1010 :
1011 : CPLXMLNode *psDescription =
1012 2730 : CPLGetXMLNode(psOption, "description");
1013 2730 : if (psDescription &&
1014 2730 : psDescription->eType == CXT_Attribute &&
1015 2730 : psDescription->psChild &&
1016 2730 : psDescription->psChild->pszValue)
1017 : {
1018 : std::string osNewValue(
1019 2730 : psDescription->psChild->pszValue);
1020 2730 : if (psCompressor->eType ==
1021 : CCT_COMPRESSOR)
1022 : {
1023 : osNewValue +=
1024 2520 : ". Only used when COMPRESS=";
1025 : }
1026 : else
1027 : {
1028 : osNewValue +=
1029 210 : ". Only used when FILTER=";
1030 : }
1031 : osNewValue +=
1032 2730 : CPLString(*iter).toupper();
1033 2730 : CPLFree(
1034 2730 : psDescription->psChild->pszValue);
1035 5460 : psDescription->psChild->pszValue =
1036 2730 : CPLStrdup(osNewValue.c_str());
1037 : }
1038 :
1039 2730 : CPLAddXMLChild(oTree.get(), psOption);
1040 2730 : psNode->psNext = psNext;
1041 : }
1042 : }
1043 : }
1044 : }
1045 : }
1046 : }
1047 : }
1048 210 : CSLDestroy(compressors);
1049 :
1050 : auto psGeoreferencingConvention =
1051 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
1052 210 : CPLAddXMLAttributeAndValue(psGeoreferencingConvention, "name",
1053 : "GEOREFERENCING_CONVENTION");
1054 210 : CPLAddXMLAttributeAndValue(psGeoreferencingConvention, "type",
1055 : "string-select");
1056 210 : CPLAddXMLAttributeAndValue(psGeoreferencingConvention, "default",
1057 : "GDAL");
1058 210 : CPLAddXMLAttributeAndValue(psGeoreferencingConvention, "description",
1059 : "Georeferencing convention to use");
1060 :
1061 : {
1062 210 : auto poValueNode = CPLCreateXMLNode(psGeoreferencingConvention,
1063 : CXT_Element, "Value");
1064 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "GDAL");
1065 : }
1066 : {
1067 210 : auto poValueNode = CPLCreateXMLNode(psGeoreferencingConvention,
1068 : CXT_Element, "Value");
1069 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "SPATIAL_PROJ");
1070 : }
1071 :
1072 : {
1073 210 : char *pszXML = CPLSerializeXMLTree(oTree.get());
1074 210 : GDALDriver::SetMetadataItem(
1075 : GDAL_DMD_MULTIDIM_ARRAY_CREATIONOPTIONLIST,
1076 210 : CPLString(pszXML)
1077 : .replaceAll("CreationOptionList",
1078 420 : "MultiDimArrayCreationOptionList")
1079 : .c_str());
1080 210 : CPLFree(pszXML);
1081 : }
1082 :
1083 : {
1084 : auto psArrayNameOption =
1085 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
1086 210 : CPLAddXMLAttributeAndValue(psArrayNameOption, "name", "ARRAY_NAME");
1087 210 : CPLAddXMLAttributeAndValue(psArrayNameOption, "type", "string");
1088 210 : CPLAddXMLAttributeAndValue(
1089 : psArrayNameOption, "description",
1090 : "Array name. If not specified, deduced from the filename");
1091 :
1092 : auto psAppendSubDSOption =
1093 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
1094 210 : CPLAddXMLAttributeAndValue(psAppendSubDSOption, "name",
1095 : "APPEND_SUBDATASET");
1096 210 : CPLAddXMLAttributeAndValue(psAppendSubDSOption, "type", "boolean");
1097 210 : CPLAddXMLAttributeAndValue(psAppendSubDSOption, "description",
1098 : "Whether to append the new dataset to "
1099 : "an existing Zarr hierarchy");
1100 210 : CPLAddXMLAttributeAndValue(psAppendSubDSOption, "default", "NO");
1101 :
1102 : auto psFormat =
1103 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
1104 210 : CPLAddXMLAttributeAndValue(psFormat, "name", "FORMAT");
1105 210 : CPLAddXMLAttributeAndValue(psFormat, "type", "string-select");
1106 210 : CPLAddXMLAttributeAndValue(psFormat, "default", "ZARR_V2");
1107 : {
1108 : auto poValueNode =
1109 210 : CPLCreateXMLNode(psFormat, CXT_Element, "Value");
1110 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "ZARR_V2");
1111 : }
1112 : {
1113 : auto poValueNode =
1114 210 : CPLCreateXMLNode(psFormat, CXT_Element, "Value");
1115 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "ZARR_V3");
1116 : }
1117 :
1118 : auto psCreateZMetadata =
1119 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
1120 210 : CPLAddXMLAttributeAndValue(psCreateZMetadata, "name",
1121 : "CREATE_CONSOLIDATED_METADATA");
1122 210 : CPLAddXMLAttributeAndValue(psCreateZMetadata, "alias",
1123 : "CREATE_ZMETADATA");
1124 210 : CPLAddXMLAttributeAndValue(psCreateZMetadata, "type", "boolean");
1125 210 : CPLAddXMLAttributeAndValue(
1126 : psCreateZMetadata, "description",
1127 : "Whether to create consolidated metadata");
1128 210 : CPLAddXMLAttributeAndValue(psCreateZMetadata, "default", "YES");
1129 :
1130 : auto psSingleArrayNode =
1131 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
1132 210 : CPLAddXMLAttributeAndValue(psSingleArrayNode, "name",
1133 : "SINGLE_ARRAY");
1134 210 : CPLAddXMLAttributeAndValue(psSingleArrayNode, "type", "boolean");
1135 210 : CPLAddXMLAttributeAndValue(
1136 : psSingleArrayNode, "description",
1137 : "Whether to write a multi-band dataset as a single array, or "
1138 : "one array per band");
1139 210 : CPLAddXMLAttributeAndValue(psSingleArrayNode, "default", "YES");
1140 :
1141 : auto psInterleaveNode =
1142 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
1143 210 : CPLAddXMLAttributeAndValue(psInterleaveNode, "name",
1144 : GDALMD_INTERLEAVE);
1145 210 : CPLAddXMLAttributeAndValue(psInterleaveNode, "type",
1146 : "string-select");
1147 210 : CPLAddXMLAttributeAndValue(psInterleaveNode, "default", "BAND");
1148 : {
1149 : auto poValueNode =
1150 210 : CPLCreateXMLNode(psInterleaveNode, CXT_Element, "Value");
1151 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "BAND");
1152 : }
1153 : {
1154 : auto poValueNode =
1155 210 : CPLCreateXMLNode(psInterleaveNode, CXT_Element, "Value");
1156 210 : CPLCreateXMLNode(poValueNode, CXT_Text, "PIXEL");
1157 : }
1158 :
1159 : auto psConvertToParquet =
1160 210 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
1161 210 : CPLAddXMLAttributeAndValue(psConvertToParquet, "name",
1162 : "CONVERT_TO_KERCHUNK_PARQUET_REFERENCE");
1163 210 : CPLAddXMLAttributeAndValue(psConvertToParquet, "type", "boolean");
1164 210 : CPLAddXMLAttributeAndValue(
1165 : psConvertToParquet, "description",
1166 : "Whether to convert a Kerchunk JSON reference store to a "
1167 : "Kerchunk Parquet reference store. (CreateCopy() only)");
1168 :
1169 210 : char *pszXML = CPLSerializeXMLTree(oTree.get());
1170 210 : GDALDriver::SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST, pszXML);
1171 210 : CPLFree(pszXML);
1172 : }
1173 : }
1174 : }
1175 :
1176 : /************************************************************************/
1177 : /* CreateMultiDimensional() */
1178 : /************************************************************************/
1179 :
1180 : GDALDataset *
1181 278 : ZarrDataset::CreateMultiDimensional(const char *pszFilename,
1182 : CSLConstList /*papszRootGroupOptions*/,
1183 : CSLConstList papszOptions)
1184 : {
1185 : const char *pszFormat =
1186 278 : CSLFetchNameValueDef(papszOptions, "FORMAT", "ZARR_V2");
1187 278 : std::shared_ptr<ZarrGroupBase> poRG;
1188 : auto poSharedResource =
1189 834 : ZarrSharedResource::Create(pszFilename, /*bUpdatable=*/true);
1190 278 : const bool bCreateZMetadata = CPLTestBool(CSLFetchNameValueDef(
1191 : papszOptions, "CREATE_CONSOLIDATED_METADATA",
1192 : CSLFetchNameValueDef(papszOptions, "CREATE_ZMETADATA", "YES")));
1193 278 : if (bCreateZMetadata)
1194 : {
1195 494 : poSharedResource->EnableConsolidatedMetadata(
1196 247 : EQUAL(pszFormat, "ZARR_V3")
1197 : ? ZarrSharedResource::ConsolidatedMetadataKind::INTERNAL
1198 : : ZarrSharedResource::ConsolidatedMetadataKind::EXTERNAL);
1199 : }
1200 278 : if (EQUAL(pszFormat, "ZARR_V3"))
1201 : {
1202 318 : poRG = ZarrV3Group::CreateOnDisk(poSharedResource, std::string(), "/",
1203 159 : pszFilename);
1204 : }
1205 : else
1206 : {
1207 238 : poRG = ZarrV2Group::CreateOnDisk(poSharedResource, std::string(), "/",
1208 119 : pszFilename);
1209 : }
1210 278 : if (!poRG)
1211 0 : return nullptr;
1212 :
1213 278 : auto poDS = new ZarrDataset(poRG);
1214 278 : poDS->SetDescription(pszFilename);
1215 278 : return poDS;
1216 : }
1217 :
1218 : /************************************************************************/
1219 : /* Create() */
1220 : /************************************************************************/
1221 :
1222 96 : GDALDataset *ZarrDataset::Create(const char *pszName, int nXSize, int nYSize,
1223 : int nBandsIn, GDALDataType eType,
1224 : CSLConstList papszOptions)
1225 : {
1226 : // To avoid any issue with short-lived string that would be passed to us
1227 192 : const std::string osName = pszName;
1228 96 : pszName = osName.c_str();
1229 :
1230 96 : if (nBandsIn <= 0 || nXSize <= 0 || nYSize <= 0)
1231 : {
1232 1 : CPLError(CE_Failure, CPLE_NotSupported,
1233 : "nBands, nXSize, nYSize should be > 0");
1234 1 : return nullptr;
1235 : }
1236 :
1237 95 : const bool bAppendSubDS = CPLTestBool(
1238 : CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO"));
1239 95 : const char *pszArrayName = CSLFetchNameValue(papszOptions, "ARRAY_NAME");
1240 :
1241 95 : std::shared_ptr<ZarrGroupBase> poRG;
1242 95 : if (bAppendSubDS)
1243 : {
1244 4 : if (pszArrayName == nullptr)
1245 : {
1246 0 : CPLError(CE_Failure, CPLE_AppDefined,
1247 : "ARRAY_NAME should be provided when "
1248 : "APPEND_SUBDATASET is set to YES");
1249 0 : return nullptr;
1250 : }
1251 : auto poDS =
1252 4 : std::unique_ptr<GDALDataset>(OpenMultidim(pszName, true, nullptr));
1253 4 : if (poDS == nullptr)
1254 : {
1255 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s", pszName);
1256 0 : return nullptr;
1257 : }
1258 4 : poRG = std::dynamic_pointer_cast<ZarrGroupBase>(poDS->GetRootGroup());
1259 : }
1260 : else
1261 : {
1262 : VSIStatBufL sStat;
1263 91 : const bool bExists = VSIStatL(pszName, &sStat) == 0;
1264 91 : const char *pszObjType = nullptr;
1265 91 : if (bExists && !VSI_ISDIR(sStat.st_mode))
1266 0 : pszObjType = "File";
1267 182 : else if ((bExists /* && VSI_ISDIR(sStat.st_mode)*/) ||
1268 182 : !CPLStringList(VSIReadDirEx(pszName, 1)).empty())
1269 0 : pszObjType = "Directory";
1270 91 : if (pszObjType)
1271 : {
1272 0 : CPLError(CE_Failure, CPLE_FileIO, "%s %s already exists.",
1273 : pszObjType, pszName);
1274 0 : return nullptr;
1275 : }
1276 :
1277 : const char *pszFormat =
1278 91 : CSLFetchNameValueDef(papszOptions, "FORMAT", "ZARR_V2");
1279 : auto poSharedResource =
1280 273 : ZarrSharedResource::Create(pszName, /*bUpdatable=*/true);
1281 91 : const bool bCreateZMetadata = CPLTestBool(CSLFetchNameValueDef(
1282 : papszOptions, "CREATE_CONSOLIDATED_METADATA",
1283 : CSLFetchNameValueDef(papszOptions, "CREATE_ZMETADATA", "YES")));
1284 91 : if (bCreateZMetadata)
1285 : {
1286 182 : poSharedResource->EnableConsolidatedMetadata(
1287 91 : EQUAL(pszFormat, "ZARR_V3")
1288 : ? ZarrSharedResource::ConsolidatedMetadataKind::INTERNAL
1289 : : ZarrSharedResource::ConsolidatedMetadataKind::EXTERNAL);
1290 : }
1291 91 : if (EQUAL(pszFormat, "ZARR_V3"))
1292 : {
1293 28 : poRG = ZarrV3Group::CreateOnDisk(poSharedResource, std::string(),
1294 14 : "/", pszName);
1295 : }
1296 : else
1297 : {
1298 154 : poRG = ZarrV2Group::CreateOnDisk(poSharedResource, std::string(),
1299 77 : "/", pszName);
1300 : }
1301 91 : poSharedResource->SetRootGroup(poRG);
1302 : }
1303 95 : if (!poRG)
1304 3 : return nullptr;
1305 :
1306 184 : auto poDS = std::make_unique<ZarrDataset>(poRG);
1307 92 : poDS->SetDescription(pszName);
1308 92 : poDS->nRasterYSize = nYSize;
1309 92 : poDS->nRasterXSize = nXSize;
1310 92 : poDS->eAccess = GA_Update;
1311 :
1312 : const auto CleanupCreatedFiles =
1313 160 : [bAppendSubDS, pszName, pszArrayName, &poRG, &poDS]()
1314 : {
1315 : // Make sure all objects are released so that ZarrSharedResource
1316 : // is finalized and all files are serialized.
1317 10 : poRG.reset();
1318 10 : poDS.reset();
1319 :
1320 10 : if (bAppendSubDS)
1321 : {
1322 2 : VSIRmdir(
1323 4 : CPLFormFilenameSafe(pszName, pszArrayName, nullptr).c_str());
1324 : }
1325 : else
1326 : {
1327 : // Be a bit careful before wiping too much stuff...
1328 : // At most 5 files expected for ZARR_V2: .zgroup, .zmetadata,
1329 : // one (empty) subdir, . and ..
1330 : // and for ZARR_V3: zarr.json, one (empty) subdir, . and ..
1331 16 : const CPLStringList aosFiles(VSIReadDirEx(pszName, 6));
1332 8 : if (aosFiles.size() < 6)
1333 : {
1334 40 : for (const char *pszFile : aosFiles)
1335 : {
1336 32 : if (pszArrayName && strcmp(pszFile, pszArrayName) == 0)
1337 : {
1338 0 : VSIRmdir(CPLFormFilenameSafe(pszName, pszFile, nullptr)
1339 : .c_str());
1340 : }
1341 64 : else if (!pszArrayName &&
1342 32 : strcmp(pszFile,
1343 64 : CPLGetBasenameSafe(pszName).c_str()) == 0)
1344 : {
1345 1 : VSIRmdir(CPLFormFilenameSafe(pszName, pszFile, nullptr)
1346 : .c_str());
1347 : }
1348 31 : else if (strcmp(pszFile, ".zgroup") == 0 ||
1349 24 : strcmp(pszFile, ".zmetadata") == 0 ||
1350 17 : strcmp(pszFile, "zarr.json") == 0)
1351 : {
1352 15 : VSIUnlink(CPLFormFilenameSafe(pszName, pszFile, nullptr)
1353 : .c_str());
1354 : }
1355 : }
1356 8 : VSIRmdir(pszName);
1357 : }
1358 : }
1359 10 : };
1360 :
1361 184 : std::string osDimXType, osDimYType;
1362 92 : if (CPLTestBool(
1363 : CSLFetchNameValueDef(papszOptions, "@HAS_GEOTRANSFORM", "NO")))
1364 : {
1365 46 : osDimXType = GDAL_DIM_TYPE_HORIZONTAL_X;
1366 46 : osDimYType = GDAL_DIM_TYPE_HORIZONTAL_Y;
1367 : }
1368 92 : poDS->m_bSpatialProjConvention = EQUAL(
1369 : CSLFetchNameValueDef(papszOptions, "GEOREFERENCING_CONVENTION", "GDAL"),
1370 : "SPATIAL_PROJ");
1371 :
1372 92 : if (bAppendSubDS)
1373 : {
1374 8 : auto aoDims = poRG->GetDimensions();
1375 12 : for (const auto &poDim : aoDims)
1376 : {
1377 12 : if (poDim->GetName() == "Y" &&
1378 4 : poDim->GetSize() == static_cast<GUInt64>(nYSize))
1379 : {
1380 1 : poDS->m_poDimY = poDim;
1381 : }
1382 11 : else if (poDim->GetName() == "X" &&
1383 4 : poDim->GetSize() == static_cast<GUInt64>(nXSize))
1384 : {
1385 1 : poDS->m_poDimX = poDim;
1386 : }
1387 : }
1388 4 : if (poDS->m_poDimY == nullptr)
1389 : {
1390 3 : poDS->m_poDimY =
1391 9 : poRG->CreateDimension(std::string(pszArrayName) + "_Y",
1392 9 : osDimYType, std::string(), nYSize);
1393 : }
1394 4 : if (poDS->m_poDimX == nullptr)
1395 : {
1396 3 : poDS->m_poDimX =
1397 9 : poRG->CreateDimension(std::string(pszArrayName) + "_X",
1398 9 : osDimXType, std::string(), nXSize);
1399 : }
1400 : }
1401 : else
1402 : {
1403 88 : poDS->m_poDimY =
1404 176 : poRG->CreateDimension("Y", osDimYType, std::string(), nYSize);
1405 88 : poDS->m_poDimX =
1406 176 : poRG->CreateDimension("X", osDimXType, std::string(), nXSize);
1407 : }
1408 92 : if (poDS->m_poDimY == nullptr || poDS->m_poDimX == nullptr)
1409 : {
1410 0 : CleanupCreatedFiles();
1411 0 : return nullptr;
1412 : }
1413 :
1414 : const bool bSingleArray =
1415 92 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "SINGLE_ARRAY", "YES"));
1416 92 : const bool bBandInterleave = EQUAL(
1417 : CSLFetchNameValueDef(papszOptions, GDALMD_INTERLEAVE, "BAND"), "BAND");
1418 : std::shared_ptr<GDALDimension> poBandDim(
1419 90 : (bSingleArray && nBandsIn > 1)
1420 121 : ? poRG->CreateDimension("Band", std::string(), std::string(),
1421 29 : nBandsIn)
1422 361 : : nullptr);
1423 :
1424 : const std::string osNonNullArrayName =
1425 184 : pszArrayName ? std::string(pszArrayName) : CPLGetBasenameSafe(pszName);
1426 92 : if (poBandDim)
1427 : {
1428 29 : std::vector<std::shared_ptr<GDALDimension>> apoDims;
1429 29 : if (bBandInterleave)
1430 : {
1431 168 : apoDims = std::vector<std::shared_ptr<GDALDimension>>{
1432 140 : poBandDim, poDS->m_poDimY, poDS->m_poDimX};
1433 : }
1434 : else
1435 : {
1436 5 : apoDims = std::vector<std::shared_ptr<GDALDimension>>{
1437 5 : poDS->m_poDimY, poDS->m_poDimX, poBandDim};
1438 : }
1439 29 : CPL_IGNORE_RET_VAL(poBandDim);
1440 29 : poDS->m_poSingleArray =
1441 87 : std::dynamic_pointer_cast<ZarrArray>(poRG->CreateMDArray(
1442 : osNonNullArrayName.c_str(), apoDims,
1443 87 : GDALExtendedDataType::Create(eType), papszOptions));
1444 29 : if (!poDS->m_poSingleArray)
1445 : {
1446 2 : CleanupCreatedFiles();
1447 2 : return nullptr;
1448 : }
1449 54 : poDS->SetMetadataItem(GDALMD_INTERLEAVE,
1450 : bBandInterleave ? "BAND" : "PIXEL",
1451 27 : GDAL_MDD_IMAGE_STRUCTURE);
1452 27 : if (bBandInterleave)
1453 : {
1454 : const char *pszBlockSize =
1455 26 : CSLFetchNameValue(papszOptions, "BLOCKSIZE");
1456 26 : if (pszBlockSize)
1457 : {
1458 : const CPLStringList aosTokens(
1459 12 : CSLTokenizeString2(pszBlockSize, ",", 0));
1460 6 : if (aosTokens.size() == 3 && atoi(aosTokens[0]) == nBandsIn)
1461 : {
1462 : // Actually expose as pixel interleaved
1463 3 : poDS->SetMetadataItem(GDALMD_INTERLEAVE, "PIXEL",
1464 3 : GDAL_MDD_IMAGE_STRUCTURE);
1465 : }
1466 : }
1467 : }
1468 112 : for (int i = 0; i < nBandsIn; i++)
1469 : {
1470 85 : auto poSlicedArray = poDS->m_poSingleArray->GetView(
1471 170 : CPLSPrintf(bBandInterleave ? "[%d,::,::]" : "[::,::,%d]", i));
1472 170 : poDS->SetBand(i + 1,
1473 170 : std::make_unique<ZarrRasterBand>(poSlicedArray));
1474 : }
1475 : }
1476 : else
1477 : {
1478 : const auto apoDims = std::vector<std::shared_ptr<GDALDimension>>{
1479 252 : poDS->m_poDimY, poDS->m_poDimX};
1480 122 : for (int i = 0; i < nBandsIn; i++)
1481 : {
1482 67 : auto poArray = poRG->CreateMDArray(
1483 61 : nBandsIn == 1 ? osNonNullArrayName.c_str()
1484 6 : : pszArrayName ? CPLSPrintf("%s_band%d", pszArrayName, i + 1)
1485 0 : : CPLSPrintf("Band%d", i + 1),
1486 201 : apoDims, GDALExtendedDataType::Create(eType), papszOptions);
1487 67 : if (poArray == nullptr)
1488 : {
1489 8 : CleanupCreatedFiles();
1490 8 : return nullptr;
1491 : }
1492 59 : poDS->SetBand(i + 1, std::make_unique<ZarrRasterBand>(poArray));
1493 : }
1494 : }
1495 :
1496 82 : return poDS.release();
1497 : }
1498 :
1499 : /************************************************************************/
1500 : /* ~ZarrDataset() */
1501 : /************************************************************************/
1502 :
1503 4164 : ZarrDataset::~ZarrDataset()
1504 : {
1505 2082 : ZarrDataset::FlushCache(true);
1506 4164 : }
1507 :
1508 : /************************************************************************/
1509 : /* FlushCache() */
1510 : /************************************************************************/
1511 :
1512 2126 : CPLErr ZarrDataset::FlushCache(bool bAtClosing)
1513 : {
1514 2126 : CPLErr eErr = GDALDataset::FlushCache(bAtClosing);
1515 :
1516 2126 : if (m_poSingleArray && !m_poSingleArray->Flush())
1517 : {
1518 0 : eErr = CE_Failure;
1519 : }
1520 :
1521 2126 : if (bAtClosing && m_poSingleArray)
1522 : {
1523 27 : bool bFound = false;
1524 112 : for (int i = 0; i < nBands; ++i)
1525 : {
1526 85 : if (papoBands[i]->GetColorInterpretation() != GCI_Undefined)
1527 24 : bFound = true;
1528 : }
1529 27 : if (bFound)
1530 : {
1531 16 : const auto oStringDT = GDALExtendedDataType::CreateString();
1532 24 : auto poAttr = m_poSingleArray->GetAttribute("COLOR_INTERPRETATION");
1533 8 : if (!poAttr)
1534 24 : poAttr = m_poSingleArray->CreateAttribute(
1535 8 : "COLOR_INTERPRETATION", {static_cast<GUInt64>(nBands)},
1536 16 : oStringDT);
1537 8 : if (poAttr)
1538 : {
1539 8 : const GUInt64 nStartIndex = 0;
1540 8 : const size_t nCount = nBands;
1541 8 : const GInt64 arrayStep = 1;
1542 8 : const GPtrDiff_t bufferStride = 1;
1543 16 : std::vector<const char *> apszValues;
1544 32 : for (int i = 0; i < nBands; ++i)
1545 : {
1546 : const auto eColorInterp =
1547 24 : papoBands[i]->GetColorInterpretation();
1548 24 : apszValues.push_back(
1549 24 : GDALGetColorInterpretationName(eColorInterp));
1550 : }
1551 16 : poAttr->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
1552 8 : oStringDT, apszValues.data());
1553 : }
1554 : }
1555 : }
1556 :
1557 2126 : if (m_poRootGroup)
1558 : {
1559 1975 : if (bAtClosing)
1560 : {
1561 1931 : if (!m_poRootGroup->Close())
1562 6 : eErr = CE_Failure;
1563 : }
1564 : else
1565 : {
1566 44 : if (!m_poRootGroup->Flush())
1567 3 : eErr = CE_Failure;
1568 : }
1569 : }
1570 :
1571 2126 : return eErr;
1572 : }
1573 :
1574 : /************************************************************************/
1575 : /* GetRootGroup() */
1576 : /************************************************************************/
1577 :
1578 1842 : std::shared_ptr<GDALGroup> ZarrDataset::GetRootGroup() const
1579 : {
1580 1842 : return m_poRootGroup;
1581 : }
1582 :
1583 : /************************************************************************/
1584 : /* GetSpatialRef() */
1585 : /************************************************************************/
1586 :
1587 2 : const OGRSpatialReference *ZarrDataset::GetSpatialRef() const
1588 : {
1589 2 : if (nBands >= 1)
1590 2 : return cpl::down_cast<ZarrRasterBand *>(papoBands[0])
1591 4 : ->m_poArray->GetSpatialRef()
1592 2 : .get();
1593 0 : return nullptr;
1594 : }
1595 :
1596 : /************************************************************************/
1597 : /* SetSpatialRef() */
1598 : /************************************************************************/
1599 :
1600 68 : CPLErr ZarrDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
1601 : {
1602 194 : for (int i = 0; i < nBands; ++i)
1603 : {
1604 126 : cpl::down_cast<ZarrRasterBand *>(papoBands[i])
1605 126 : ->m_poArray->SetSpatialRef(poSRS);
1606 : }
1607 68 : return CE_None;
1608 : }
1609 :
1610 : /************************************************************************/
1611 : /* GetGeoTransform() */
1612 : /************************************************************************/
1613 :
1614 29 : CPLErr ZarrDataset::GetGeoTransform(GDALGeoTransform >) const
1615 : {
1616 29 : gt = m_gt;
1617 29 : return m_bHasGT ? CE_None : CE_Failure;
1618 : }
1619 :
1620 : /************************************************************************/
1621 : /* SetGeoTransform() */
1622 : /************************************************************************/
1623 :
1624 70 : CPLErr ZarrDataset::SetGeoTransform(const GDALGeoTransform >)
1625 : {
1626 70 : const bool bHasRotatedTerms = (gt.xrot != 0 || gt.yrot != 0);
1627 :
1628 70 : if (bHasRotatedTerms)
1629 : {
1630 1 : if (!m_bSpatialProjConvention)
1631 : {
1632 0 : CPLError(CE_Failure, CPLE_NotSupported,
1633 : "Geotransform with rotated terms not supported with "
1634 : "GEOREFERENCING_CONVENTION=GDAL, but would be with "
1635 : "SPATIAL_PROJ");
1636 0 : return CE_Failure;
1637 : }
1638 : }
1639 69 : else if (m_poDimX == nullptr || m_poDimY == nullptr)
1640 : {
1641 0 : CPLError(CE_Failure, CPLE_AppDefined,
1642 : "SetGeoTransform() failed because of missing X/Y dimension");
1643 0 : return CE_Failure;
1644 : }
1645 :
1646 70 : m_gt = gt;
1647 70 : m_bHasGT = true;
1648 :
1649 70 : if (m_bSpatialProjConvention)
1650 : {
1651 2 : const auto bSingleArray = m_poSingleArray != nullptr;
1652 2 : const int nIters = bSingleArray ? 1 : nBands;
1653 4 : for (int i = 0; i < nIters; ++i)
1654 : {
1655 : auto *poArray = bSingleArray
1656 2 : ? m_poSingleArray.get()
1657 2 : : cpl::down_cast<ZarrRasterBand *>(papoBands[i])
1658 2 : ->m_poArray.get();
1659 4 : auto oAttrDT = GDALExtendedDataType::Create(GDT_Float64);
1660 : auto poAttr =
1661 6 : poArray->CreateAttribute("gdal:geotransform", {6}, oAttrDT);
1662 2 : if (poAttr)
1663 : {
1664 2 : const GUInt64 nStartIndex = 0;
1665 2 : const size_t nCount = 6;
1666 2 : const GInt64 arrayStep = 1;
1667 2 : const GPtrDiff_t bufferStride = 1;
1668 4 : poAttr->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
1669 2 : oAttrDT, m_gt.data());
1670 : }
1671 : }
1672 : }
1673 :
1674 70 : if (!bHasRotatedTerms)
1675 : {
1676 69 : CPLAssert(m_poDimX);
1677 69 : CPLAssert(m_poDimY);
1678 :
1679 69 : const auto oDTFloat64 = GDALExtendedDataType::Create(GDT_Float64);
1680 : {
1681 69 : auto poX = m_poRootGroup->OpenMDArray(m_poDimX->GetName());
1682 69 : if (!poX)
1683 340 : poX = m_poRootGroup->CreateMDArray(
1684 272 : m_poDimX->GetName(), {m_poDimX}, oDTFloat64, nullptr);
1685 69 : if (!poX)
1686 0 : return CE_Failure;
1687 69 : m_poDimX->SetIndexingVariable(poX);
1688 69 : std::vector<double> adfX;
1689 : try
1690 : {
1691 69 : adfX.reserve(nRasterXSize);
1692 5803 : for (int i = 0; i < nRasterXSize; ++i)
1693 5734 : adfX.emplace_back(m_gt.xorig + m_gt.xscale * (i + 0.5));
1694 : }
1695 0 : catch (const std::exception &)
1696 : {
1697 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
1698 : "Out of memory when allocating X array");
1699 0 : return CE_Failure;
1700 : }
1701 69 : const GUInt64 nStartIndex = 0;
1702 69 : const size_t nCount = adfX.size();
1703 69 : const GInt64 arrayStep = 1;
1704 69 : const GPtrDiff_t bufferStride = 1;
1705 138 : if (!poX->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
1706 69 : poX->GetDataType(), adfX.data()))
1707 : {
1708 0 : return CE_Failure;
1709 : }
1710 : }
1711 :
1712 69 : auto poY = m_poRootGroup->OpenMDArray(m_poDimY->GetName());
1713 69 : if (!poY)
1714 272 : poY = m_poRootGroup->CreateMDArray(m_poDimY->GetName(), {m_poDimY},
1715 204 : oDTFloat64, nullptr);
1716 69 : if (!poY)
1717 0 : return CE_Failure;
1718 69 : m_poDimY->SetIndexingVariable(poY);
1719 69 : std::vector<double> adfY;
1720 : try
1721 : {
1722 69 : adfY.reserve(nRasterYSize);
1723 4591 : for (int i = 0; i < nRasterYSize; ++i)
1724 4522 : adfY.emplace_back(m_gt.yorig + m_gt.yscale * (i + 0.5));
1725 : }
1726 0 : catch (const std::exception &)
1727 : {
1728 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
1729 : "Out of memory when allocating Y array");
1730 0 : return CE_Failure;
1731 : }
1732 69 : const GUInt64 nStartIndex = 0;
1733 69 : const size_t nCount = adfY.size();
1734 69 : const GInt64 arrayStep = 1;
1735 69 : const GPtrDiff_t bufferStride = 1;
1736 138 : if (!poY->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
1737 69 : poY->GetDataType(), adfY.data()))
1738 : {
1739 0 : return CE_Failure;
1740 : }
1741 : }
1742 :
1743 70 : return CE_None;
1744 : }
1745 :
1746 : /************************************************************************/
1747 : /* SetMetadata() */
1748 : /************************************************************************/
1749 :
1750 41 : CPLErr ZarrDataset::SetMetadata(CSLConstList papszMetadata,
1751 : const char *pszDomain)
1752 : {
1753 41 : if (nBands >= 1 && (pszDomain == nullptr || pszDomain[0] == '\0'))
1754 : {
1755 82 : const auto oStringDT = GDALExtendedDataType::CreateString();
1756 41 : const auto bSingleArray = m_poSingleArray != nullptr;
1757 41 : const int nIters = bSingleArray ? 1 : nBands;
1758 86 : for (int i = 0; i < nIters; ++i)
1759 : {
1760 : auto *poArray = bSingleArray
1761 45 : ? m_poSingleArray.get()
1762 33 : : cpl::down_cast<ZarrRasterBand *>(papoBands[i])
1763 33 : ->m_poArray.get();
1764 90 : for (auto iter = papszMetadata; iter && *iter; ++iter)
1765 : {
1766 45 : char *pszKey = nullptr;
1767 45 : const char *pszValue = CPLParseNameValue(*iter, &pszKey);
1768 45 : if (pszKey && pszValue)
1769 : {
1770 : auto poAttr =
1771 66 : poArray->CreateAttribute(pszKey, {}, oStringDT);
1772 22 : if (poAttr)
1773 : {
1774 22 : const GUInt64 nStartIndex = 0;
1775 22 : const size_t nCount = 1;
1776 22 : const GInt64 arrayStep = 1;
1777 22 : const GPtrDiff_t bufferStride = 1;
1778 22 : poAttr->Write(&nStartIndex, &nCount, &arrayStep,
1779 : &bufferStride, oStringDT, &pszValue);
1780 : }
1781 : }
1782 45 : CPLFree(pszKey);
1783 : }
1784 : }
1785 : }
1786 41 : return GDALDataset::SetMetadata(papszMetadata, pszDomain);
1787 : }
1788 :
1789 : /************************************************************************/
1790 : /* ZarrRasterBand::ZarrRasterBand() */
1791 : /************************************************************************/
1792 :
1793 144 : ZarrRasterBand::ZarrRasterBand(const std::shared_ptr<GDALMDArray> &poArray)
1794 144 : : m_poArray(poArray)
1795 : {
1796 144 : assert(poArray->GetDimensionCount() == 2);
1797 144 : eDataType = poArray->GetDataType().GetNumericDataType();
1798 144 : nBlockXSize = static_cast<int>(poArray->GetBlockSize()[1]);
1799 144 : nBlockYSize = static_cast<int>(poArray->GetBlockSize()[0]);
1800 144 : }
1801 :
1802 : /************************************************************************/
1803 : /* GetNoDataValue() */
1804 : /************************************************************************/
1805 :
1806 6 : double ZarrRasterBand::GetNoDataValue(int *pbHasNoData)
1807 : {
1808 6 : bool bHasNodata = false;
1809 6 : const auto res = m_poArray->GetNoDataValueAsDouble(&bHasNodata);
1810 6 : if (pbHasNoData)
1811 6 : *pbHasNoData = bHasNodata;
1812 6 : return res;
1813 : }
1814 :
1815 : /************************************************************************/
1816 : /* GetNoDataValueAsInt64() */
1817 : /************************************************************************/
1818 :
1819 0 : int64_t ZarrRasterBand::GetNoDataValueAsInt64(int *pbHasNoData)
1820 : {
1821 0 : bool bHasNodata = false;
1822 0 : const auto res = m_poArray->GetNoDataValueAsInt64(&bHasNodata);
1823 0 : if (pbHasNoData)
1824 0 : *pbHasNoData = bHasNodata;
1825 0 : return res;
1826 : }
1827 :
1828 : /************************************************************************/
1829 : /* GetNoDataValueAsUInt64() */
1830 : /************************************************************************/
1831 :
1832 0 : uint64_t ZarrRasterBand::GetNoDataValueAsUInt64(int *pbHasNoData)
1833 : {
1834 0 : bool bHasNodata = false;
1835 0 : const auto res = m_poArray->GetNoDataValueAsUInt64(&bHasNodata);
1836 0 : if (pbHasNoData)
1837 0 : *pbHasNoData = bHasNodata;
1838 0 : return res;
1839 : }
1840 :
1841 : /************************************************************************/
1842 : /* SetNoDataValue() */
1843 : /************************************************************************/
1844 :
1845 2 : CPLErr ZarrRasterBand::SetNoDataValue(double dfNoData)
1846 : {
1847 2 : return m_poArray->SetNoDataValue(dfNoData) ? CE_None : CE_Failure;
1848 : }
1849 :
1850 : /************************************************************************/
1851 : /* SetNoDataValueAsInt64() */
1852 : /************************************************************************/
1853 :
1854 0 : CPLErr ZarrRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
1855 : {
1856 0 : return m_poArray->SetNoDataValue(nNoData) ? CE_None : CE_Failure;
1857 : }
1858 :
1859 : /************************************************************************/
1860 : /* SetNoDataValueAsUInt64() */
1861 : /************************************************************************/
1862 :
1863 0 : CPLErr ZarrRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
1864 : {
1865 0 : return m_poArray->SetNoDataValue(nNoData) ? CE_None : CE_Failure;
1866 : }
1867 :
1868 : /************************************************************************/
1869 : /* GetOffset() */
1870 : /************************************************************************/
1871 :
1872 2 : double ZarrRasterBand::GetOffset(int *pbSuccess)
1873 : {
1874 2 : bool bHasValue = false;
1875 2 : double dfRet = m_poArray->GetOffset(&bHasValue);
1876 2 : if (pbSuccess)
1877 2 : *pbSuccess = bHasValue ? TRUE : FALSE;
1878 2 : return dfRet;
1879 : }
1880 :
1881 : /************************************************************************/
1882 : /* SetOffset() */
1883 : /************************************************************************/
1884 :
1885 2 : CPLErr ZarrRasterBand::SetOffset(double dfNewOffset)
1886 : {
1887 2 : return m_poArray->SetOffset(dfNewOffset) ? CE_None : CE_Failure;
1888 : }
1889 :
1890 : /************************************************************************/
1891 : /* GetScale() */
1892 : /************************************************************************/
1893 :
1894 2 : double ZarrRasterBand::GetScale(int *pbSuccess)
1895 : {
1896 2 : bool bHasValue = false;
1897 2 : double dfRet = m_poArray->GetScale(&bHasValue);
1898 2 : if (pbSuccess)
1899 2 : *pbSuccess = bHasValue ? TRUE : FALSE;
1900 2 : return dfRet;
1901 : }
1902 :
1903 : /************************************************************************/
1904 : /* SetScale() */
1905 : /************************************************************************/
1906 :
1907 2 : CPLErr ZarrRasterBand::SetScale(double dfNewScale)
1908 : {
1909 2 : return m_poArray->SetScale(dfNewScale) ? CE_None : CE_Failure;
1910 : }
1911 :
1912 : /************************************************************************/
1913 : /* GetUnitType() */
1914 : /************************************************************************/
1915 :
1916 2 : const char *ZarrRasterBand::GetUnitType()
1917 : {
1918 2 : return m_poArray->GetUnit().c_str();
1919 : }
1920 :
1921 : /************************************************************************/
1922 : /* SetUnitType() */
1923 : /************************************************************************/
1924 :
1925 2 : CPLErr ZarrRasterBand::SetUnitType(const char *pszNewValue)
1926 : {
1927 4 : return m_poArray->SetUnit(pszNewValue ? pszNewValue : "") ? CE_None
1928 4 : : CE_Failure;
1929 : }
1930 :
1931 : /************************************************************************/
1932 : /* GetColorInterpretation() */
1933 : /************************************************************************/
1934 :
1935 141 : GDALColorInterp ZarrRasterBand::GetColorInterpretation()
1936 : {
1937 141 : return m_eColorInterp;
1938 : }
1939 :
1940 : /************************************************************************/
1941 : /* SetColorInterpretation() */
1942 : /************************************************************************/
1943 :
1944 32 : CPLErr ZarrRasterBand::SetColorInterpretation(GDALColorInterp eColorInterp)
1945 : {
1946 32 : auto poGDS = cpl::down_cast<ZarrDataset *>(poDS);
1947 32 : m_eColorInterp = eColorInterp;
1948 32 : if (!poGDS->m_poSingleArray)
1949 : {
1950 8 : const auto oStringDT = GDALExtendedDataType::CreateString();
1951 16 : auto poAttr = m_poArray->GetAttribute("COLOR_INTERPRETATION");
1952 8 : if (poAttr && (poAttr->GetDimensionCount() != 0 ||
1953 8 : poAttr->GetDataType().GetClass() != GEDTC_STRING))
1954 0 : return CE_None;
1955 8 : if (!poAttr)
1956 24 : poAttr = m_poArray->CreateAttribute("COLOR_INTERPRETATION", {},
1957 16 : oStringDT);
1958 8 : if (poAttr)
1959 : {
1960 8 : const GUInt64 nStartIndex = 0;
1961 8 : const size_t nCount = 1;
1962 8 : const GInt64 arrayStep = 1;
1963 8 : const GPtrDiff_t bufferStride = 1;
1964 8 : const char *pszValue = GDALGetColorInterpretationName(eColorInterp);
1965 8 : poAttr->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
1966 : oStringDT, &pszValue);
1967 : }
1968 : }
1969 32 : return CE_None;
1970 : }
1971 :
1972 : /************************************************************************/
1973 : /* ZarrRasterBand::IReadBlock() */
1974 : /************************************************************************/
1975 :
1976 2 : CPLErr ZarrRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pData)
1977 : {
1978 :
1979 2 : const int nXOff = nBlockXOff * nBlockXSize;
1980 2 : const int nYOff = nBlockYOff * nBlockYSize;
1981 2 : const int nReqXSize = std::min(nRasterXSize - nXOff, nBlockXSize);
1982 2 : const int nReqYSize = std::min(nRasterYSize - nYOff, nBlockYSize);
1983 2 : GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
1984 2 : static_cast<GUInt64>(nXOff)};
1985 2 : size_t count[] = {static_cast<size_t>(nReqYSize),
1986 2 : static_cast<size_t>(nReqXSize)};
1987 2 : constexpr GInt64 arrayStep[] = {1, 1};
1988 2 : GPtrDiff_t bufferStride[] = {nBlockXSize, 1};
1989 4 : return m_poArray->Read(arrayStartIdx, count, arrayStep, bufferStride,
1990 2 : m_poArray->GetDataType(), pData)
1991 2 : ? CE_None
1992 2 : : CE_Failure;
1993 : }
1994 :
1995 : /************************************************************************/
1996 : /* ZarrRasterBand::IWriteBlock() */
1997 : /************************************************************************/
1998 :
1999 0 : CPLErr ZarrRasterBand::IWriteBlock(int nBlockXOff, int nBlockYOff, void *pData)
2000 : {
2001 0 : const int nXOff = nBlockXOff * nBlockXSize;
2002 0 : const int nYOff = nBlockYOff * nBlockYSize;
2003 0 : const int nReqXSize = std::min(nRasterXSize - nXOff, nBlockXSize);
2004 0 : const int nReqYSize = std::min(nRasterYSize - nYOff, nBlockYSize);
2005 0 : GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
2006 0 : static_cast<GUInt64>(nXOff)};
2007 0 : size_t count[] = {static_cast<size_t>(nReqYSize),
2008 0 : static_cast<size_t>(nReqXSize)};
2009 0 : constexpr GInt64 arrayStep[] = {1, 1};
2010 0 : GPtrDiff_t bufferStride[] = {nBlockXSize, 1};
2011 0 : return m_poArray->Write(arrayStartIdx, count, arrayStep, bufferStride,
2012 0 : m_poArray->GetDataType(), pData)
2013 0 : ? CE_None
2014 0 : : CE_Failure;
2015 : }
2016 :
2017 : /************************************************************************/
2018 : /* IRasterIO() */
2019 : /************************************************************************/
2020 :
2021 51 : CPLErr ZarrRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
2022 : int nXSize, int nYSize, void *pData,
2023 : int nBufXSize, int nBufYSize,
2024 : GDALDataType eBufType, GSpacing nPixelSpaceBuf,
2025 : GSpacing nLineSpaceBuf,
2026 : GDALRasterIOExtraArg *psExtraArg)
2027 : {
2028 51 : const int nBufferDTSize(GDALGetDataTypeSizeBytes(eBufType));
2029 : // If reading/writing at full resolution and with proper stride, go
2030 : // directly to the array, but, for performance reasons,
2031 : // only if exactly on chunk boundaries, otherwise go through the block cache.
2032 51 : if (nXSize == nBufXSize && nYSize == nBufYSize && nBufferDTSize > 0 &&
2033 51 : (nPixelSpaceBuf % nBufferDTSize) == 0 &&
2034 51 : (nLineSpaceBuf % nBufferDTSize) == 0 && (nXOff % nBlockXSize) == 0 &&
2035 51 : (nYOff % nBlockYSize) == 0 &&
2036 51 : ((nXSize % nBlockXSize) == 0 || nXOff + nXSize == nRasterXSize) &&
2037 51 : ((nYSize % nBlockYSize) == 0 || nYOff + nYSize == nRasterYSize))
2038 : {
2039 51 : GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
2040 51 : static_cast<GUInt64>(nXOff)};
2041 51 : size_t count[] = {static_cast<size_t>(nYSize),
2042 51 : static_cast<size_t>(nXSize)};
2043 51 : constexpr GInt64 arrayStep[] = {1, 1};
2044 : GPtrDiff_t bufferStride[] = {
2045 51 : static_cast<GPtrDiff_t>(nLineSpaceBuf / nBufferDTSize),
2046 51 : static_cast<GPtrDiff_t>(nPixelSpaceBuf / nBufferDTSize)};
2047 :
2048 51 : if (eRWFlag == GF_Read)
2049 : {
2050 4 : return m_poArray->Read(
2051 : arrayStartIdx, count, arrayStep, bufferStride,
2052 4 : GDALExtendedDataType::Create(eBufType), pData)
2053 2 : ? CE_None
2054 2 : : CE_Failure;
2055 : }
2056 : else
2057 : {
2058 98 : return m_poArray->Write(
2059 : arrayStartIdx, count, arrayStep, bufferStride,
2060 98 : GDALExtendedDataType::Create(eBufType), pData)
2061 49 : ? CE_None
2062 49 : : CE_Failure;
2063 : }
2064 : }
2065 :
2066 0 : return GDALRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
2067 : pData, nBufXSize, nBufYSize, eBufType,
2068 0 : nPixelSpaceBuf, nLineSpaceBuf, psExtraArg);
2069 : }
2070 :
2071 : /************************************************************************/
2072 : /* ZarrDataset::IRasterIO() */
2073 : /************************************************************************/
2074 :
2075 59 : CPLErr ZarrDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
2076 : int nXSize, int nYSize, void *pData,
2077 : int nBufXSize, int nBufYSize,
2078 : GDALDataType eBufType, int nBandCount,
2079 : BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
2080 : GSpacing nLineSpace, GSpacing nBandSpace,
2081 : GDALRasterIOExtraArg *psExtraArg)
2082 : {
2083 59 : const int nBufferDTSize(GDALGetDataTypeSizeBytes(eBufType));
2084 : // If reading/writing at full resolution and with proper stride, go
2085 : // directly to the array, but, for performance reasons,
2086 : // only if exactly on chunk boundaries, otherwise go through the block cache.
2087 59 : int nBlockXSize = 0, nBlockYSize = 0;
2088 59 : papoBands[0]->GetBlockSize(&nBlockXSize, &nBlockYSize);
2089 91 : if (m_poSingleArray && nXSize == nBufXSize && nYSize == nBufYSize &&
2090 32 : nBufferDTSize > 0 && (nPixelSpace % nBufferDTSize) == 0 &&
2091 32 : (nLineSpace % nBufferDTSize) == 0 &&
2092 32 : (nBandSpace % nBufferDTSize) == 0 && (nXOff % nBlockXSize) == 0 &&
2093 32 : (nYOff % nBlockYSize) == 0 &&
2094 32 : ((nXSize % nBlockXSize) == 0 || nXOff + nXSize == nRasterXSize) &&
2095 123 : ((nYSize % nBlockYSize) == 0 || nYOff + nYSize == nRasterYSize) &&
2096 32 : IsAllBands(nBandCount, panBandMap))
2097 : {
2098 12 : CPLAssert(m_poSingleArray->GetDimensionCount() == 3);
2099 12 : if (m_poSingleArray->GetDimensions().back().get() == m_poDimX.get())
2100 : {
2101 11 : GUInt64 arrayStartIdx[] = {0, static_cast<GUInt64>(nYOff),
2102 11 : static_cast<GUInt64>(nXOff)};
2103 11 : size_t count[] = {static_cast<size_t>(nBands),
2104 11 : static_cast<size_t>(nYSize),
2105 11 : static_cast<size_t>(nXSize)};
2106 11 : constexpr GInt64 arrayStep[] = {1, 1, 1};
2107 : GPtrDiff_t bufferStride[] = {
2108 11 : static_cast<GPtrDiff_t>(nBandSpace / nBufferDTSize),
2109 11 : static_cast<GPtrDiff_t>(nLineSpace / nBufferDTSize),
2110 11 : static_cast<GPtrDiff_t>(nPixelSpace / nBufferDTSize)};
2111 :
2112 11 : if (eRWFlag == GF_Read)
2113 : {
2114 8 : return m_poSingleArray->Read(
2115 : arrayStartIdx, count, arrayStep, bufferStride,
2116 8 : GDALExtendedDataType::Create(eBufType), pData)
2117 4 : ? CE_None
2118 4 : : CE_Failure;
2119 : }
2120 : else
2121 : {
2122 14 : return m_poSingleArray->Write(
2123 : arrayStartIdx, count, arrayStep, bufferStride,
2124 14 : GDALExtendedDataType::Create(eBufType), pData)
2125 7 : ? CE_None
2126 7 : : CE_Failure;
2127 : }
2128 : }
2129 : else
2130 : {
2131 1 : GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
2132 1 : static_cast<GUInt64>(nXOff), 0};
2133 1 : size_t count[] = {static_cast<size_t>(nYSize),
2134 1 : static_cast<size_t>(nXSize),
2135 1 : static_cast<size_t>(nBands)};
2136 1 : constexpr GInt64 arrayStep[] = {1, 1, 1};
2137 : GPtrDiff_t bufferStride[] = {
2138 1 : static_cast<GPtrDiff_t>(nLineSpace / nBufferDTSize),
2139 1 : static_cast<GPtrDiff_t>(nPixelSpace / nBufferDTSize),
2140 1 : static_cast<GPtrDiff_t>(nBandSpace / nBufferDTSize),
2141 1 : };
2142 :
2143 1 : if (eRWFlag == GF_Read)
2144 : {
2145 0 : return m_poSingleArray->Read(
2146 : arrayStartIdx, count, arrayStep, bufferStride,
2147 0 : GDALExtendedDataType::Create(eBufType), pData)
2148 0 : ? CE_None
2149 0 : : CE_Failure;
2150 : }
2151 : else
2152 : {
2153 2 : return m_poSingleArray->Write(
2154 : arrayStartIdx, count, arrayStep, bufferStride,
2155 2 : GDALExtendedDataType::Create(eBufType), pData)
2156 1 : ? CE_None
2157 1 : : CE_Failure;
2158 : }
2159 : }
2160 : }
2161 :
2162 47 : return GDALDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
2163 : nBufXSize, nBufYSize, eBufType, nBandCount,
2164 : panBandMap, nPixelSpace, nLineSpace,
2165 47 : nBandSpace, psExtraArg);
2166 : }
2167 :
2168 : /************************************************************************/
2169 : /* ZarrDataset::CreateCopy() */
2170 : /************************************************************************/
2171 :
2172 : /* static */
2173 52 : GDALDataset *ZarrDataset::CreateCopy(const char *pszFilename,
2174 : GDALDataset *poSrcDS, int bStrict,
2175 : CSLConstList papszOptions,
2176 : GDALProgressFunc pfnProgress,
2177 : void *pProgressData)
2178 : {
2179 52 : if (CPLFetchBool(papszOptions, "CONVERT_TO_KERCHUNK_PARQUET_REFERENCE",
2180 : false))
2181 : {
2182 1 : if (VSIKerchunkConvertJSONToParquet(poSrcDS->GetDescription(),
2183 : pszFilename, pfnProgress,
2184 : pProgressData))
2185 : {
2186 : GDALOpenInfo oOpenInfo(
2187 2 : std::string("ZARR:\"").append(pszFilename).append("\"").c_str(),
2188 2 : GA_ReadOnly);
2189 1 : return Open(&oOpenInfo);
2190 : }
2191 : }
2192 : else
2193 : {
2194 51 : auto poDriver = GetGDALDriverManager()->GetDriverByName(DRIVER_NAME);
2195 : CPLStringList aosCreationOptions(
2196 102 : const_cast<CSLConstList>(papszOptions));
2197 51 : GDALGeoTransform gt;
2198 51 : if (poSrcDS->GetGeoTransform(gt) == CE_None)
2199 : {
2200 50 : aosCreationOptions.SetNameValue("@HAS_GEOTRANSFORM", "YES");
2201 : }
2202 : auto poDS = std::unique_ptr<GDALDataset>(poDriver->DefaultCreateCopy(
2203 51 : pszFilename, poSrcDS, bStrict, aosCreationOptions.List(),
2204 102 : pfnProgress, pProgressData));
2205 51 : if (poDS)
2206 : {
2207 39 : if (poDS->FlushCache() != CE_None)
2208 3 : poDS.reset();
2209 : }
2210 51 : return poDS.release();
2211 : }
2212 0 : return nullptr;
2213 : }
2214 :
2215 : /************************************************************************/
2216 : /* ZARRAddGeoreferencingConventionAlgorithm */
2217 : /************************************************************************/
2218 :
2219 : #ifndef _
2220 : #define _(x) (x)
2221 : #endif
2222 :
2223 : namespace
2224 : {
2225 : class ZARRAddGeoreferencingConventionAlgorithm final : public GDALAlgorithm
2226 : {
2227 : public:
2228 134 : ZARRAddGeoreferencingConventionAlgorithm()
2229 134 : : GDALAlgorithm(
2230 : "add-georeferencing-convention",
2231 268 : std::string("Add a georeferencing convention to an existing ZARR "
2232 : "dataset"),
2233 402 : "/programs/gdal_driver_zarr_add_georeferencing_convention.html")
2234 : {
2235 : AddInputDatasetArg(&m_dataset,
2236 134 : GDAL_OF_MULTIDIM_RASTER | GDAL_OF_UPDATE);
2237 : AddArg("convention", 0, _("Georeferencing convention"),
2238 268 : &m_georeferencingConvention)
2239 134 : .SetRequired()
2240 134 : .SetPositional()
2241 134 : .SetChoices("GDAL", "spatial_proj");
2242 134 : }
2243 :
2244 : protected:
2245 : bool RunImpl(GDALProgressFunc, void *) override;
2246 :
2247 : private:
2248 : GDALArgDatasetValue m_dataset{};
2249 : std::string m_georeferencingConvention{};
2250 : };
2251 :
2252 2 : bool ZARRAddGeoreferencingConventionAlgorithm::RunImpl(GDALProgressFunc, void *)
2253 : {
2254 2 : auto poDS = dynamic_cast<ZarrDataset *>(m_dataset.GetDatasetRef());
2255 2 : if (!poDS)
2256 : {
2257 1 : ReportError(CE_Failure, CPLE_AppDefined, "%s is not a ZARR dataset",
2258 1 : m_dataset.GetName().c_str());
2259 1 : return false;
2260 : }
2261 :
2262 1 : auto poRG = poDS->GetRootGroup();
2263 1 : CPLAssert(poRG);
2264 :
2265 1 : poRG->RecursivelyVisitArrays(
2266 4 : [this](const std::shared_ptr<GDALMDArray> &poArray)
2267 : {
2268 3 : ZarrArray *poZarrArray = dynamic_cast<ZarrArray *>(poArray.get());
2269 3 : if (poZarrArray && poZarrArray->GetSpatialRef())
2270 : {
2271 2 : CPLStringList aosOptions;
2272 : aosOptions.SetNameValue("GEOREFERENCING_CONVENTION",
2273 1 : m_georeferencingConvention.c_str());
2274 1 : poZarrArray->SetCreationOptions(aosOptions.List());
2275 1 : poZarrArray->InvalidateGeoreferencing();
2276 : }
2277 3 : });
2278 :
2279 1 : return true;
2280 : }
2281 : } // namespace
2282 :
2283 : /************************************************************************/
2284 : /* ZarrDriverInstantiateAlgorithm() */
2285 : /************************************************************************/
2286 :
2287 : static GDALAlgorithm *
2288 134 : ZarrDriverInstantiateAlgorithm(const std::vector<std::string> &aosPath)
2289 : {
2290 134 : if (aosPath.size() == 1 && aosPath[0] == "add-georeferencing-convention")
2291 : {
2292 268 : return std::make_unique<ZARRAddGeoreferencingConventionAlgorithm>()
2293 134 : .release();
2294 : }
2295 : else
2296 : {
2297 0 : return nullptr;
2298 : }
2299 : }
2300 :
2301 : /************************************************************************/
2302 : /* GDALRegister_Zarr() */
2303 : /************************************************************************/
2304 :
2305 2135 : void GDALRegister_Zarr()
2306 :
2307 : {
2308 2135 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
2309 263 : return;
2310 :
2311 1872 : VSIInstallKerchunkFileSystems();
2312 :
2313 1872 : GDALDriver *poDriver = new ZarrDriver();
2314 1872 : ZARRDriverSetCommonMetadata(poDriver);
2315 :
2316 : #ifdef HAVE_PCODEC
2317 : poDriver->SetMetadataItem("HAVE_PCODEC", "YES");
2318 : #endif
2319 :
2320 1872 : poDriver->pfnOpen = ZarrDataset::Open;
2321 1872 : poDriver->pfnCreateMultiDimensional = ZarrDataset::CreateMultiDimensional;
2322 1872 : poDriver->pfnCreate = ZarrDataset::Create;
2323 1872 : poDriver->pfnCreateCopy = ZarrDataset::CreateCopy;
2324 1872 : poDriver->pfnDelete = ZarrDatasetDelete;
2325 1872 : poDriver->pfnRename = ZarrDatasetRename;
2326 1872 : poDriver->pfnCopyFiles = ZarrDatasetCopyFiles;
2327 1872 : poDriver->pfnClearCaches = ZarrDriverClearCaches;
2328 1872 : poDriver->pfnInstantiateAlgorithm = ZarrDriverInstantiateAlgorithm;
2329 :
2330 1872 : GetGDALDriverManager()->RegisterDriver(poDriver);
2331 : }
|