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