Line data Source code
1 : /******************************************************************************
2 : *
3 : * Name: gdalmultidim_pam.cpp
4 : * Project: GDAL Core
5 : * Purpose: Implementation of GDALPamMDArray and GDALPamMultiDim classes
6 : * Author: Even Rouault <even.rouault at spatialys.com>
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2019, Even Rouault <even.rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include "cpl_error_internal.h"
15 : #include "gdal_multidim.h"
16 : #include "gdal_pam.h"
17 : #include "gdal_pam_multidim.h"
18 : #include "ogr_spatialref.h"
19 :
20 : #include <map>
21 :
22 : #if defined(__clang__) || defined(_MSC_VER)
23 : #define COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT
24 : #endif
25 :
26 : //! @cond Doxygen_Suppress
27 :
28 : /************************************************************************/
29 : /* GDALPamMultiDim::Private */
30 : /************************************************************************/
31 :
32 : struct GDALPamMultiDim::Private
33 : {
34 : std::string m_osFilename{};
35 : std::string m_osPamFilename{};
36 :
37 : struct Statistics
38 : {
39 : bool bHasStats = false;
40 : bool bApproxStats = false;
41 : double dfMin = 0;
42 : double dfMax = 0;
43 : double dfMean = 0;
44 : double dfStdDev = 0;
45 : GUInt64 nValidCount = 0;
46 : };
47 :
48 : struct ArrayInfo
49 : {
50 : std::shared_ptr<OGRSpatialReference> poSRS{};
51 : // cppcheck-suppress unusedStructMember
52 : Statistics stats{};
53 : std::string osOvrFilename{};
54 : };
55 :
56 : typedef std::pair<std::string, std::string> NameContext;
57 : std::map<NameContext, ArrayInfo> m_oMapArray{};
58 : std::vector<CPLXMLTreeCloser> m_apoOtherNodes{};
59 : bool m_bDirty = false;
60 : bool m_bLoaded = false;
61 : };
62 :
63 : /************************************************************************/
64 : /* GDALPamMultiDim */
65 : /************************************************************************/
66 :
67 2741 : GDALPamMultiDim::GDALPamMultiDim(const std::string &osFilename)
68 2741 : : d(new Private())
69 : {
70 2741 : d->m_osFilename = osFilename;
71 2741 : }
72 :
73 : /************************************************************************/
74 : /* GDALPamMultiDim::~GDALPamMultiDim() */
75 : /************************************************************************/
76 :
77 2741 : GDALPamMultiDim::~GDALPamMultiDim()
78 : {
79 2741 : if (d->m_bDirty)
80 37 : Save();
81 2741 : }
82 :
83 : /************************************************************************/
84 : /* GDALPamMultiDim::Load() */
85 : /************************************************************************/
86 :
87 182 : void GDALPamMultiDim::Load()
88 : {
89 182 : if (d->m_bLoaded)
90 169 : return;
91 71 : d->m_bLoaded = true;
92 :
93 71 : const char *pszProxyPam = PamGetProxy(d->m_osFilename.c_str());
94 71 : d->m_osPamFilename =
95 142 : pszProxyPam ? std::string(pszProxyPam) : d->m_osFilename + ".aux.xml";
96 71 : CPLXMLTreeCloser oTree(nullptr);
97 : {
98 142 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
99 71 : oTree.reset(CPLParseXMLFile(d->m_osPamFilename.c_str()));
100 : }
101 71 : if (!oTree)
102 : {
103 58 : return;
104 : }
105 13 : const auto poPAMMultiDim = CPLGetXMLNode(oTree.get(), "=PAMDataset");
106 13 : if (!poPAMMultiDim)
107 0 : return;
108 40 : for (CPLXMLNode *psIter = poPAMMultiDim->psChild; psIter;
109 27 : psIter = psIter->psNext)
110 : {
111 27 : if (psIter->eType == CXT_Element &&
112 27 : strcmp(psIter->pszValue, "Array") == 0)
113 : {
114 16 : const char *pszName = CPLGetXMLValue(psIter, "name", nullptr);
115 16 : if (!pszName)
116 0 : continue;
117 16 : const char *pszContext = CPLGetXMLValue(psIter, "context", "");
118 : const auto oKey =
119 32 : std::pair<std::string, std::string>(pszName, pszContext);
120 :
121 : /* --------------------------------------------------------------------
122 : */
123 : /* Check for an SRS node. */
124 : /* --------------------------------------------------------------------
125 : */
126 16 : const CPLXMLNode *psSRSNode = CPLGetXMLNode(psIter, "SRS");
127 16 : if (psSRSNode)
128 : {
129 : std::shared_ptr<OGRSpatialReference> poSRS =
130 6 : std::make_shared<OGRSpatialReference>();
131 3 : poSRS->SetFromUserInput(
132 : CPLGetXMLValue(psSRSNode, nullptr, ""),
133 : OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS);
134 3 : const char *pszMapping = CPLGetXMLValue(
135 : psSRSNode, "dataAxisToSRSAxisMapping", nullptr);
136 3 : if (pszMapping)
137 : {
138 : char **papszTokens =
139 3 : CSLTokenizeStringComplex(pszMapping, ",", FALSE, FALSE);
140 6 : std::vector<int> anMapping;
141 9 : for (int i = 0; papszTokens && papszTokens[i]; i++)
142 : {
143 6 : anMapping.push_back(atoi(papszTokens[i]));
144 : }
145 3 : CSLDestroy(papszTokens);
146 3 : poSRS->SetDataAxisToSRSAxisMapping(anMapping);
147 : }
148 : else
149 : {
150 0 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
151 : }
152 :
153 : const char *pszCoordinateEpoch =
154 3 : CPLGetXMLValue(psSRSNode, "coordinateEpoch", nullptr);
155 3 : if (pszCoordinateEpoch)
156 3 : poSRS->SetCoordinateEpoch(CPLAtof(pszCoordinateEpoch));
157 :
158 3 : d->m_oMapArray[oKey].poSRS = std::move(poSRS);
159 : }
160 :
161 : const CPLXMLNode *psStatistics =
162 16 : CPLGetXMLNode(psIter, "Statistics");
163 16 : if (psStatistics)
164 : {
165 7 : Private::Statistics sStats;
166 7 : sStats.bHasStats = true;
167 7 : sStats.bApproxStats = CPLTestBool(
168 : CPLGetXMLValue(psStatistics, "ApproxStats", "false"));
169 7 : sStats.dfMin =
170 7 : CPLAtofM(CPLGetXMLValue(psStatistics, "Minimum", "0"));
171 7 : sStats.dfMax =
172 7 : CPLAtofM(CPLGetXMLValue(psStatistics, "Maximum", "0"));
173 7 : sStats.dfMean =
174 7 : CPLAtofM(CPLGetXMLValue(psStatistics, "Mean", "0"));
175 7 : sStats.dfStdDev =
176 7 : CPLAtofM(CPLGetXMLValue(psStatistics, "StdDev", "0"));
177 7 : sStats.nValidCount = static_cast<GUInt64>(CPLAtoGIntBig(
178 : CPLGetXMLValue(psStatistics, "ValidSampleCount", "0")));
179 7 : d->m_oMapArray[oKey].stats = sStats;
180 : }
181 :
182 : const char *pszOverviewFile =
183 16 : CPLGetXMLValue(psIter, "OverviewFile", nullptr);
184 16 : if (pszOverviewFile)
185 : {
186 3 : d->m_oMapArray[oKey].osOvrFilename = pszOverviewFile;
187 16 : }
188 : }
189 : else
190 : {
191 11 : CPLXMLNode *psNextBackup = psIter->psNext;
192 11 : psIter->psNext = nullptr;
193 11 : d->m_apoOtherNodes.emplace_back(
194 11 : CPLXMLTreeCloser(CPLCloneXMLTree(psIter)));
195 11 : psIter->psNext = psNextBackup;
196 : }
197 : }
198 : }
199 :
200 : /************************************************************************/
201 : /* GDALPamMultiDim::Save() */
202 : /************************************************************************/
203 :
204 37 : void GDALPamMultiDim::Save()
205 : {
206 : CPLXMLTreeCloser oTree(
207 74 : CPLCreateXMLNode(nullptr, CXT_Element, "PAMDataset"));
208 41 : for (const auto &poOtherNode : d->m_apoOtherNodes)
209 : {
210 4 : CPLAddXMLChild(oTree.get(), CPLCloneXMLTree(poOtherNode.get()));
211 : }
212 137 : for (const auto &kv : d->m_oMapArray)
213 : {
214 : CPLXMLNode *psArrayNode =
215 100 : CPLCreateXMLNode(oTree.get(), CXT_Element, "Array");
216 100 : CPLAddXMLAttributeAndValue(psArrayNode, "name", kv.first.first.c_str());
217 100 : if (!kv.first.second.empty())
218 : {
219 1 : CPLAddXMLAttributeAndValue(psArrayNode, "context",
220 : kv.first.second.c_str());
221 : }
222 100 : if (kv.second.poSRS)
223 : {
224 86 : char *pszWKT = nullptr;
225 : {
226 172 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
227 86 : const char *const apszOptions[] = {"FORMAT=WKT2", nullptr};
228 86 : kv.second.poSRS->exportToWkt(&pszWKT, apszOptions);
229 : }
230 : CPLXMLNode *psSRSNode =
231 86 : CPLCreateXMLElementAndValue(psArrayNode, "SRS", pszWKT);
232 86 : CPLFree(pszWKT);
233 : const auto &mapping =
234 86 : kv.second.poSRS->GetDataAxisToSRSAxisMapping();
235 172 : CPLString osMapping;
236 258 : for (size_t i = 0; i < mapping.size(); ++i)
237 : {
238 172 : if (!osMapping.empty())
239 86 : osMapping += ",";
240 172 : osMapping += CPLSPrintf("%d", mapping[i]);
241 : }
242 86 : CPLAddXMLAttributeAndValue(psSRSNode, "dataAxisToSRSAxisMapping",
243 : osMapping.c_str());
244 :
245 : const double dfCoordinateEpoch =
246 86 : kv.second.poSRS->GetCoordinateEpoch();
247 86 : if (dfCoordinateEpoch > 0)
248 : {
249 : std::string osCoordinateEpoch =
250 2 : CPLSPrintf("%f", dfCoordinateEpoch);
251 1 : if (osCoordinateEpoch.find('.') != std::string::npos)
252 : {
253 6 : while (osCoordinateEpoch.back() == '0')
254 5 : osCoordinateEpoch.resize(osCoordinateEpoch.size() - 1);
255 : }
256 1 : CPLAddXMLAttributeAndValue(psSRSNode, "coordinateEpoch",
257 : osCoordinateEpoch.c_str());
258 : }
259 : }
260 :
261 100 : if (kv.second.stats.bHasStats)
262 : {
263 : CPLXMLNode *psStats =
264 8 : CPLCreateXMLNode(psArrayNode, CXT_Element, "Statistics");
265 8 : CPLCreateXMLElementAndValue(psStats, "ApproxStats",
266 8 : kv.second.stats.bApproxStats ? "1"
267 : : "0");
268 8 : CPLCreateXMLElementAndValue(
269 8 : psStats, "Minimum", CPLSPrintf("%.17g", kv.second.stats.dfMin));
270 8 : CPLCreateXMLElementAndValue(
271 8 : psStats, "Maximum", CPLSPrintf("%.17g", kv.second.stats.dfMax));
272 8 : CPLCreateXMLElementAndValue(
273 8 : psStats, "Mean", CPLSPrintf("%.17g", kv.second.stats.dfMean));
274 8 : CPLCreateXMLElementAndValue(
275 : psStats, "StdDev",
276 8 : CPLSPrintf("%.17g", kv.second.stats.dfStdDev));
277 8 : CPLCreateXMLElementAndValue(
278 : psStats, "ValidSampleCount",
279 8 : CPLSPrintf(CPL_FRMT_GUIB, kv.second.stats.nValidCount));
280 : }
281 :
282 100 : if (!kv.second.osOvrFilename.empty())
283 : {
284 3 : CPLCreateXMLElementAndValue(psArrayNode, "OverviewFile",
285 : kv.second.osOvrFilename.c_str());
286 : }
287 : }
288 :
289 : int bSaved;
290 74 : CPLErrorAccumulator oErrorAccumulator;
291 : {
292 37 : auto oAccumulator = oErrorAccumulator.InstallForCurrentScope();
293 37 : CPL_IGNORE_RET_VAL(oAccumulator);
294 : bSaved =
295 37 : CPLSerializeXMLTreeToFile(oTree.get(), d->m_osPamFilename.c_str());
296 : }
297 :
298 37 : const char *pszNewPam = nullptr;
299 37 : if (!bSaved && PamGetProxy(d->m_osFilename.c_str()) == nullptr &&
300 0 : ((pszNewPam = PamAllocateProxy(d->m_osFilename.c_str())) != nullptr))
301 : {
302 0 : CPLErrorReset();
303 0 : CPLSerializeXMLTreeToFile(oTree.get(), pszNewPam);
304 : }
305 : else
306 : {
307 37 : oErrorAccumulator.ReplayErrors();
308 : }
309 37 : }
310 :
311 : /************************************************************************/
312 : /* GDALPamMultiDim::GetOverviewFilename() */
313 : /************************************************************************/
314 :
315 : /** Return the file name of the overview filene name for the specified
316 : * array
317 : */
318 : std::string
319 48 : GDALPamMultiDim::GetOverviewFilename(const std::string &osArrayFullName,
320 : const std::string &osContext)
321 : {
322 48 : Load();
323 96 : const auto oKey = std::make_pair(osArrayFullName, osContext);
324 48 : auto oIter = d->m_oMapArray.find(oKey);
325 48 : if (oIter != d->m_oMapArray.end())
326 2 : return oIter->second.osOvrFilename;
327 :
328 46 : return std::string();
329 : }
330 :
331 : /************************************************************************/
332 : /* GDALPamMultiDim::GenerateOverviewFilename() */
333 : /************************************************************************/
334 :
335 : /** Ggenerate an overview filene name for the specified
336 : * array
337 : */
338 : std::string
339 2 : GDALPamMultiDim::GenerateOverviewFilename(const std::string &osArrayFullName,
340 : const std::string &osContext)
341 : {
342 2 : Load();
343 :
344 2 : constexpr int ARBITRARY_ITERATION_COUNT = 1000;
345 3 : for (int i = 0; i < ARBITRARY_ITERATION_COUNT; ++i)
346 : {
347 3 : std::string osOvrFilename(d->m_osFilename);
348 3 : osOvrFilename += '.';
349 3 : osOvrFilename += std::to_string(i);
350 3 : osOvrFilename += ".ovr";
351 : VSIStatBufL sStatBuf;
352 3 : if (VSIStatL(osOvrFilename.c_str(), &sStatBuf) != 0)
353 : {
354 2 : d->m_bDirty = true;
355 4 : const auto oKey = std::make_pair(osArrayFullName, osContext);
356 2 : d->m_oMapArray[oKey].osOvrFilename = osOvrFilename;
357 2 : return osOvrFilename;
358 : }
359 : }
360 0 : CPLError(CE_Failure, CPLE_AppDefined,
361 : "Cannot establish overview filename for array %s",
362 : osArrayFullName.c_str());
363 0 : return std::string();
364 : }
365 :
366 : /************************************************************************/
367 : /* GDALPamMultiDim::GetSpatialRef() */
368 : /************************************************************************/
369 :
370 : std::shared_ptr<OGRSpatialReference>
371 20 : GDALPamMultiDim::GetSpatialRef(const std::string &osArrayFullName,
372 : const std::string &osContext)
373 : {
374 20 : Load();
375 : auto oIter =
376 20 : d->m_oMapArray.find(std::make_pair(osArrayFullName, osContext));
377 20 : if (oIter != d->m_oMapArray.end())
378 2 : return oIter->second.poSRS;
379 18 : return nullptr;
380 : }
381 :
382 : /************************************************************************/
383 : /* GDALPamMultiDim::SetSpatialRef() */
384 : /************************************************************************/
385 :
386 87 : void GDALPamMultiDim::SetSpatialRef(const std::string &osArrayFullName,
387 : const std::string &osContext,
388 : const OGRSpatialReference *poSRS)
389 : {
390 87 : Load();
391 87 : d->m_bDirty = true;
392 87 : if (poSRS && !poSRS->IsEmpty())
393 86 : d->m_oMapArray[std::make_pair(osArrayFullName, osContext)].poSRS.reset(
394 : poSRS->Clone());
395 : else
396 2 : d->m_oMapArray[std::make_pair(osArrayFullName, osContext)]
397 1 : .poSRS.reset();
398 87 : }
399 :
400 : /************************************************************************/
401 : /* GetStatistics() */
402 : /************************************************************************/
403 :
404 16 : CPLErr GDALPamMultiDim::GetStatistics(const std::string &osArrayFullName,
405 : const std::string &osContext,
406 : bool bApproxOK, double *pdfMin,
407 : double *pdfMax, double *pdfMean,
408 : double *pdfStdDev, GUInt64 *pnValidCount)
409 : {
410 16 : Load();
411 : auto oIter =
412 16 : d->m_oMapArray.find(std::make_pair(osArrayFullName, osContext));
413 16 : if (oIter == d->m_oMapArray.end())
414 9 : return CE_Failure;
415 7 : const auto &stats = oIter->second.stats;
416 7 : if (!stats.bHasStats)
417 1 : return CE_Failure;
418 6 : if (!bApproxOK && stats.bApproxStats)
419 0 : return CE_Failure;
420 6 : if (pdfMin)
421 6 : *pdfMin = stats.dfMin;
422 6 : if (pdfMax)
423 6 : *pdfMax = stats.dfMax;
424 6 : if (pdfMean)
425 6 : *pdfMean = stats.dfMean;
426 6 : if (pdfStdDev)
427 6 : *pdfStdDev = stats.dfStdDev;
428 6 : if (pnValidCount)
429 6 : *pnValidCount = stats.nValidCount;
430 6 : return CE_None;
431 : }
432 :
433 : /************************************************************************/
434 : /* SetStatistics() */
435 : /************************************************************************/
436 :
437 8 : void GDALPamMultiDim::SetStatistics(const std::string &osArrayFullName,
438 : const std::string &osContext,
439 : bool bApproxStats, double dfMin,
440 : double dfMax, double dfMean,
441 : double dfStdDev, GUInt64 nValidCount)
442 : {
443 8 : Load();
444 8 : d->m_bDirty = true;
445 : auto &stats =
446 8 : d->m_oMapArray[std::make_pair(osArrayFullName, osContext)].stats;
447 8 : stats.bHasStats = true;
448 8 : stats.bApproxStats = bApproxStats;
449 8 : stats.dfMin = dfMin;
450 8 : stats.dfMax = dfMax;
451 8 : stats.dfMean = dfMean;
452 8 : stats.dfStdDev = dfStdDev;
453 8 : stats.nValidCount = nValidCount;
454 8 : }
455 :
456 : /************************************************************************/
457 : /* ClearStatistics() */
458 : /************************************************************************/
459 :
460 0 : void GDALPamMultiDim::ClearStatistics(const std::string &osArrayFullName,
461 : const std::string &osContext)
462 : {
463 0 : Load();
464 0 : d->m_bDirty = true;
465 0 : d->m_oMapArray[std::make_pair(osArrayFullName, osContext)].stats.bHasStats =
466 : false;
467 0 : }
468 :
469 : /************************************************************************/
470 : /* ClearStatistics() */
471 : /************************************************************************/
472 :
473 1 : void GDALPamMultiDim::ClearStatistics()
474 : {
475 1 : Load();
476 1 : d->m_bDirty = true;
477 3 : for (auto &kv : d->m_oMapArray)
478 2 : kv.second.stats.bHasStats = false;
479 1 : }
480 :
481 : /************************************************************************/
482 : /* GetPAM() */
483 : /************************************************************************/
484 :
485 : /*static*/ std::shared_ptr<GDALPamMultiDim>
486 1530 : GDALPamMultiDim::GetPAM(const std::shared_ptr<GDALMDArray> &poParent)
487 : {
488 1530 : auto poPamArray = dynamic_cast<GDALPamMDArray *>(poParent.get());
489 1530 : if (poPamArray)
490 793 : return poPamArray->GetPAM();
491 737 : return nullptr;
492 : }
493 :
494 : /************************************************************************/
495 : /* GDALPamMDArray */
496 : /************************************************************************/
497 :
498 6222 : GDALPamMDArray::GDALPamMDArray(const std::string &osParentName,
499 : const std::string &osName,
500 : const std::shared_ptr<GDALPamMultiDim> &poPam,
501 0 : const std::string &osContext)
502 : :
503 : #if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT)
504 : GDALAbstractMDArray(osParentName, osName),
505 : #endif
506 6222 : GDALMDArray(osParentName, osName, osContext), m_poPam(poPam)
507 : {
508 6222 : }
509 :
510 : /************************************************************************/
511 : /* GDALPamMDArray::SetSpatialRef() */
512 : /************************************************************************/
513 :
514 87 : bool GDALPamMDArray::SetSpatialRef(const OGRSpatialReference *poSRS)
515 : {
516 87 : if (!m_poPam)
517 0 : return false;
518 87 : m_poPam->SetSpatialRef(GetFullName(), GetContext(), poSRS);
519 87 : return true;
520 : }
521 :
522 : /************************************************************************/
523 : /* GDALPamMDArray::GetSpatialRef() */
524 : /************************************************************************/
525 :
526 20 : std::shared_ptr<OGRSpatialReference> GDALPamMDArray::GetSpatialRef() const
527 : {
528 20 : if (!m_poPam)
529 0 : return nullptr;
530 20 : return m_poPam->GetSpatialRef(GetFullName(), GetContext());
531 : }
532 :
533 : /************************************************************************/
534 : /* GetStatistics() */
535 : /************************************************************************/
536 :
537 16 : CPLErr GDALPamMDArray::GetStatistics(bool bApproxOK, bool bForce,
538 : double *pdfMin, double *pdfMax,
539 : double *pdfMean, double *pdfStdDev,
540 : GUInt64 *pnValidCount,
541 : GDALProgressFunc pfnProgress,
542 : void *pProgressData)
543 : {
544 16 : if (m_poPam && m_poPam->GetStatistics(GetFullName(), GetContext(),
545 : bApproxOK, pdfMin, pdfMax, pdfMean,
546 16 : pdfStdDev, pnValidCount) == CE_None)
547 : {
548 6 : return CE_None;
549 : }
550 10 : if (!bForce)
551 4 : return CE_Warning;
552 :
553 6 : return GDALMDArray::GetStatistics(bApproxOK, bForce, pdfMin, pdfMax,
554 : pdfMean, pdfStdDev, pnValidCount,
555 6 : pfnProgress, pProgressData);
556 : }
557 :
558 : /************************************************************************/
559 : /* SetStatistics() */
560 : /************************************************************************/
561 :
562 8 : bool GDALPamMDArray::SetStatistics(bool bApproxStats, double dfMin,
563 : double dfMax, double dfMean, double dfStdDev,
564 : GUInt64 nValidCount,
565 : CSLConstList /* papszOptions */)
566 : {
567 8 : if (!m_poPam)
568 0 : return false;
569 8 : m_poPam->SetStatistics(GetFullName(), GetContext(), bApproxStats, dfMin,
570 : dfMax, dfMean, dfStdDev, nValidCount);
571 8 : return true;
572 : }
573 :
574 : /************************************************************************/
575 : /* ClearStatistics() */
576 : /************************************************************************/
577 :
578 0 : void GDALPamMDArray::ClearStatistics()
579 : {
580 0 : if (!m_poPam)
581 0 : return;
582 0 : m_poPam->ClearStatistics(GetFullName(), GetContext());
583 : }
584 :
585 : //! @endcond
|