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