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 : * Permission is hereby granted, free of charge, to any person obtaining a
11 : * copy of this software and associated documentation files (the "Software"),
12 : * to deal in the Software without restriction, including without limitation
13 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 : * and/or sell copies of the Software, and to permit persons to whom the
15 : * Software is furnished to do so, subject to the following conditions:
16 : *
17 : * The above copyright notice and this permission notice shall be included
18 : * in all copies or substantial portions of the Software.
19 : *
20 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 : * DEALINGS IN THE SOFTWARE.
27 : ****************************************************************************/
28 :
29 : #include "zarr.h"
30 :
31 : #include "cpl_json.h"
32 :
33 : /************************************************************************/
34 : /* ZarrSharedResource::ZarrSharedResource() */
35 : /************************************************************************/
36 :
37 979 : ZarrSharedResource::ZarrSharedResource(const std::string &osRootDirectoryName,
38 979 : bool bUpdatable)
39 979 : : m_bUpdatable(bUpdatable)
40 : {
41 979 : m_oObj.Add("zarr_consolidated_format", 1);
42 979 : m_oObj.Add("metadata", CPLJSONObject());
43 :
44 979 : m_osRootDirectoryName = osRootDirectoryName;
45 979 : if (!m_osRootDirectoryName.empty() && m_osRootDirectoryName.back() == '/')
46 : {
47 0 : m_osRootDirectoryName.resize(m_osRootDirectoryName.size() - 1);
48 : }
49 979 : m_poPAM = std::make_shared<GDALPamMultiDim>(
50 979 : CPLFormFilename(m_osRootDirectoryName.c_str(), "pam", nullptr));
51 979 : }
52 :
53 : /************************************************************************/
54 : /* ZarrSharedResource::Create() */
55 : /************************************************************************/
56 :
57 : std::shared_ptr<ZarrSharedResource>
58 979 : ZarrSharedResource::Create(const std::string &osRootDirectoryName,
59 : bool bUpdatable)
60 : {
61 : return std::shared_ptr<ZarrSharedResource>(
62 979 : new ZarrSharedResource(osRootDirectoryName, bUpdatable));
63 : }
64 :
65 : /************************************************************************/
66 : /* ZarrSharedResource::~ZarrSharedResource() */
67 : /************************************************************************/
68 :
69 979 : ZarrSharedResource::~ZarrSharedResource()
70 : {
71 979 : if (m_bZMetadataModified)
72 : {
73 174 : CPLJSONDocument oDoc;
74 174 : oDoc.SetRoot(m_oObj);
75 174 : oDoc.Save(CPLFormFilename(m_osRootDirectoryName.c_str(), ".zmetadata",
76 : nullptr));
77 : }
78 979 : }
79 :
80 : /************************************************************************/
81 : /* ZarrSharedResource::OpenRootGroup() */
82 : /************************************************************************/
83 :
84 676 : std::shared_ptr<ZarrGroupBase> ZarrSharedResource::OpenRootGroup()
85 : {
86 : {
87 1352 : auto poRG = ZarrV2Group::Create(shared_from_this(), std::string(), "/");
88 676 : poRG->SetUpdatable(m_bUpdatable);
89 676 : poRG->SetDirectoryName(m_osRootDirectoryName);
90 :
91 : const std::string osZarrayFilename(
92 676 : CPLFormFilename(m_osRootDirectoryName.c_str(), ".zarray", nullptr));
93 : VSIStatBufL sStat;
94 676 : if (VSIStatL(osZarrayFilename.c_str(), &sStat) == 0)
95 : {
96 494 : CPLJSONDocument oDoc;
97 247 : if (!oDoc.Load(osZarrayFilename))
98 0 : return nullptr;
99 494 : const auto oRoot = oDoc.GetRoot();
100 247 : if (oRoot["_NCZARR_ARRAY"].IsValid())
101 : {
102 : // If opening a NCZarr array, initialize its group from NCZarr
103 : // metadata.
104 : const std::string osGroupFilename(CPLFormFilename(
105 : CPLGetDirname(m_osRootDirectoryName.c_str()), ".zgroup",
106 3 : nullptr));
107 3 : if (VSIStatL(osGroupFilename.c_str(), &sStat) == 0)
108 : {
109 2 : CPLJSONDocument oDocGroup;
110 2 : if (oDocGroup.Load(osGroupFilename))
111 : {
112 2 : if (!poRG->InitFromZGroup(oDocGroup.GetRoot()))
113 1 : return nullptr;
114 : }
115 : }
116 : }
117 : const std::string osArrayName(
118 492 : CPLGetBasename(m_osRootDirectoryName.c_str()));
119 738 : if (!poRG->LoadArray(osArrayName, osZarrayFilename, oRoot, false,
120 738 : CPLJSONObject()))
121 39 : return nullptr;
122 :
123 207 : return poRG;
124 : }
125 :
126 : const std::string osZmetadataFilename(CPLFormFilename(
127 429 : m_osRootDirectoryName.c_str(), ".zmetadata", nullptr));
128 429 : if (CPLTestBool(CSLFetchNameValueDef(GetOpenOptions(), "USE_ZMETADATA",
129 852 : "YES")) &&
130 423 : VSIStatL(osZmetadataFilename.c_str(), &sStat) == 0)
131 : {
132 165 : if (!m_bZMetadataEnabled)
133 : {
134 165 : CPLJSONDocument oDoc;
135 165 : if (!oDoc.Load(osZmetadataFilename))
136 0 : return nullptr;
137 :
138 165 : m_bZMetadataEnabled = true;
139 165 : m_oObj = oDoc.GetRoot();
140 : }
141 165 : poRG->InitFromZMetadata(m_oObj);
142 :
143 165 : return poRG;
144 : }
145 :
146 : const std::string osGroupFilename(
147 264 : CPLFormFilename(m_osRootDirectoryName.c_str(), ".zgroup", nullptr));
148 264 : if (VSIStatL(osGroupFilename.c_str(), &sStat) == 0)
149 : {
150 110 : CPLJSONDocument oDoc;
151 55 : if (!oDoc.Load(osGroupFilename))
152 0 : return nullptr;
153 :
154 55 : if (!poRG->InitFromZGroup(oDoc.GetRoot()))
155 3 : return nullptr;
156 52 : return poRG;
157 : }
158 : }
159 :
160 : // Zarr v3
161 418 : auto poRG_V3 = ZarrV3Group::Create(shared_from_this(), std::string(), "/",
162 836 : m_osRootDirectoryName);
163 209 : poRG_V3->SetUpdatable(m_bUpdatable);
164 :
165 : const std::string osZarrJsonFilename(
166 418 : CPLFormFilename(m_osRootDirectoryName.c_str(), "zarr.json", nullptr));
167 : VSIStatBufL sStat;
168 209 : if (VSIStatL(osZarrJsonFilename.c_str(), &sStat) == 0)
169 : {
170 414 : CPLJSONDocument oDoc;
171 207 : if (!oDoc.Load(osZarrJsonFilename))
172 0 : return nullptr;
173 414 : const auto oRoot = oDoc.GetRoot();
174 207 : if (oRoot.GetInteger("zarr_format") != 3)
175 : {
176 0 : CPLError(CE_Failure, CPLE_AppDefined,
177 : "Unhandled zarr_format value");
178 0 : return nullptr;
179 : }
180 621 : const std::string osNodeType = oRoot.GetString("node_type");
181 207 : if (osNodeType == "array")
182 : {
183 : const std::string osArrayName(
184 106 : CPLGetBasename(m_osRootDirectoryName.c_str()));
185 53 : poRG_V3->SetExplored();
186 53 : if (!poRG_V3->LoadArray(osArrayName, osZarrJsonFilename, oRoot))
187 32 : return nullptr;
188 :
189 21 : return poRG_V3;
190 : }
191 154 : else if (osNodeType == "group")
192 : {
193 154 : return poRG_V3;
194 : }
195 : else
196 : {
197 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unhandled node_type value");
198 0 : return nullptr;
199 : }
200 : }
201 :
202 : // No explicit zarr.json in root directory ? Then recurse until we find
203 : // one.
204 2 : auto psDir = VSIOpenDir(m_osRootDirectoryName.c_str(), -1, nullptr);
205 2 : if (!psDir)
206 2 : return nullptr;
207 0 : bool bZarrJsonFound = false;
208 0 : while (const VSIDIREntry *psEntry = VSIGetNextDirEntry(psDir))
209 : {
210 0 : if (!VSI_ISDIR(psEntry->nMode) &&
211 0 : strcmp(CPLGetFilename(psEntry->pszName), "zarr.json") == 0)
212 : {
213 0 : bZarrJsonFound = true;
214 0 : break;
215 : }
216 0 : }
217 0 : VSICloseDir(psDir);
218 0 : if (bZarrJsonFound)
219 0 : return poRG_V3;
220 :
221 0 : return nullptr;
222 : }
223 :
224 : /************************************************************************/
225 : /* ZarrSharedResource::SetZMetadataItem() */
226 : /************************************************************************/
227 :
228 919 : void ZarrSharedResource::SetZMetadataItem(const std::string &osFilename,
229 : const CPLJSONObject &obj)
230 : {
231 919 : if (m_bZMetadataEnabled)
232 : {
233 1600 : CPLString osNormalizedFilename(osFilename);
234 800 : osNormalizedFilename.replaceAll('\\', '/');
235 800 : CPLAssert(STARTS_WITH(osNormalizedFilename.c_str(),
236 : (m_osRootDirectoryName + '/').c_str()));
237 800 : m_bZMetadataModified = true;
238 : const char *pszKey =
239 800 : osNormalizedFilename.c_str() + m_osRootDirectoryName.size() + 1;
240 1600 : auto oMetadata = m_oObj["metadata"];
241 800 : oMetadata.DeleteNoSplitName(pszKey);
242 800 : oMetadata.AddNoSplitName(pszKey, obj);
243 : }
244 919 : }
245 :
246 : /************************************************************************/
247 : /* ZarrSharedResource::DeleteZMetadataItemRecursive() */
248 : /************************************************************************/
249 :
250 12 : void ZarrSharedResource::DeleteZMetadataItemRecursive(
251 : const std::string &osFilename)
252 : {
253 12 : if (m_bZMetadataEnabled)
254 : {
255 8 : CPLString osNormalizedFilename(osFilename);
256 4 : osNormalizedFilename.replaceAll('\\', '/');
257 4 : CPLAssert(STARTS_WITH(osNormalizedFilename.c_str(),
258 : (m_osRootDirectoryName + '/').c_str()));
259 4 : m_bZMetadataModified = true;
260 : const char *pszKey =
261 4 : osNormalizedFilename.c_str() + m_osRootDirectoryName.size() + 1;
262 :
263 12 : auto oMetadata = m_oObj["metadata"];
264 28 : for (auto &item : oMetadata.GetChildren())
265 : {
266 24 : if (STARTS_WITH(item.GetName().c_str(), pszKey))
267 : {
268 14 : oMetadata.DeleteNoSplitName(item.GetName());
269 : }
270 : }
271 : }
272 12 : }
273 :
274 : /************************************************************************/
275 : /* ZarrSharedResource::RenameZMetadataRecursive() */
276 : /************************************************************************/
277 :
278 12 : void ZarrSharedResource::RenameZMetadataRecursive(
279 : const std::string &osOldFilename, const std::string &osNewFilename)
280 : {
281 12 : if (m_bZMetadataEnabled)
282 : {
283 8 : CPLString osNormalizedOldFilename(osOldFilename);
284 4 : osNormalizedOldFilename.replaceAll('\\', '/');
285 4 : CPLAssert(STARTS_WITH(osNormalizedOldFilename.c_str(),
286 : (m_osRootDirectoryName + '/').c_str()));
287 :
288 8 : CPLString osNormalizedNewFilename(osNewFilename);
289 4 : osNormalizedNewFilename.replaceAll('\\', '/');
290 4 : CPLAssert(STARTS_WITH(osNormalizedNewFilename.c_str(),
291 : (m_osRootDirectoryName + '/').c_str()));
292 :
293 4 : m_bZMetadataModified = true;
294 :
295 : const char *pszOldKeyRadix =
296 4 : osNormalizedOldFilename.c_str() + m_osRootDirectoryName.size() + 1;
297 : const char *pszNewKeyRadix =
298 4 : osNormalizedNewFilename.c_str() + m_osRootDirectoryName.size() + 1;
299 :
300 12 : auto oMetadata = m_oObj["metadata"];
301 32 : for (auto &item : oMetadata.GetChildren())
302 : {
303 28 : if (STARTS_WITH(item.GetName().c_str(), pszOldKeyRadix))
304 : {
305 15 : oMetadata.DeleteNoSplitName(item.GetName());
306 30 : std::string osNewKey(pszNewKeyRadix);
307 15 : osNewKey += (item.GetName().c_str() + strlen(pszOldKeyRadix));
308 15 : oMetadata.AddNoSplitName(osNewKey, item);
309 : }
310 : }
311 : }
312 12 : }
313 :
314 : /************************************************************************/
315 : /* ZarrSharedResource::UpdateDimensionSize() */
316 : /************************************************************************/
317 :
318 7 : void ZarrSharedResource::UpdateDimensionSize(
319 : const std::shared_ptr<GDALDimension> &poDim)
320 : {
321 14 : auto poRG = m_poWeakRootGroup.lock();
322 7 : if (!poRG)
323 0 : poRG = OpenRootGroup();
324 7 : if (poRG)
325 : {
326 7 : poRG->UpdateDimensionSize(poDim);
327 : }
328 : else
329 : {
330 0 : CPLError(CE_Failure, CPLE_AppDefined, "UpdateDimensionSize() failed");
331 : }
332 7 : poRG.reset();
333 7 : }
334 :
335 : /************************************************************************/
336 : /* ZarrSharedResource::AddArrayInLoading() */
337 : /************************************************************************/
338 :
339 801 : bool ZarrSharedResource::AddArrayInLoading(const std::string &osZarrayFilename)
340 : {
341 : // Prevent too deep or recursive array loading
342 801 : if (m_oSetArrayInLoading.find(osZarrayFilename) !=
343 1602 : m_oSetArrayInLoading.end())
344 : {
345 1 : CPLError(CE_Failure, CPLE_AppDefined,
346 : "Attempt at recursively loading %s", osZarrayFilename.c_str());
347 1 : return false;
348 : }
349 800 : if (m_oSetArrayInLoading.size() == 32)
350 : {
351 1 : CPLError(CE_Failure, CPLE_AppDefined,
352 : "Too deep call stack in LoadArray()");
353 1 : return false;
354 : }
355 799 : m_oSetArrayInLoading.insert(osZarrayFilename);
356 799 : return true;
357 : }
358 :
359 : /************************************************************************/
360 : /* ZarrSharedResource::RemoveArrayInLoading() */
361 : /************************************************************************/
362 :
363 799 : void ZarrSharedResource::RemoveArrayInLoading(
364 : const std::string &osZarrayFilename)
365 : {
366 799 : m_oSetArrayInLoading.erase(osZarrayFilename);
367 799 : }
|