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