1 /******************************************************************************
2  *
3  * Project:  GDAL Core
4  * Purpose:  Implementation of GDALPamDataset, a dataset base class that
5  *           knows how to persist auxiliary metadata into a support XML file.
6  * Author:   Frank Warmerdam, warmerdam@pobox.com
7  *
8  ******************************************************************************
9  * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com>
10  * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com>
11  *
12  * Permission is hereby granted, free of charge, to any person obtaining a
13  * copy of this software and associated documentation files (the "Software"),
14  * to deal in the Software without restriction, including without limitation
15  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16  * and/or sell copies of the Software, and to permit persons to whom the
17  * Software is furnished to do so, subject to the following conditions:
18  *
19  * The above copyright notice and this permission notice shall be included
20  * in all copies or substantial portions of the Software.
21  *
22  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
23  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
25  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28  * DEALINGS IN THE SOFTWARE.
29  ****************************************************************************/
30 
31 #include "cpl_port.h"
32 #include "gdal_pam.h"
33 
34 #include <cstddef>
35 #include <cstdlib>
36 #include <cstring>
37 #include <string>
38 
39 #include "cpl_conv.h"
40 #include "cpl_error.h"
41 #include "cpl_minixml.h"
42 #include "cpl_progress.h"
43 #include "cpl_string.h"
44 #include "cpl_vsi.h"
45 #include "gdal.h"
46 #include "gdal_priv.h"
47 #include "ogr_core.h"
48 #include "ogr_spatialref.h"
49 
50 CPL_CVSID("$Id: gdalpamdataset.cpp 86d08a5e15b4cd22fb0a94482b0c8343db2d304f 2020-09-12 17:17:49 +0200 Even Rouault $")
51 
52 /************************************************************************/
53 /*                           GDALPamDataset()                           */
54 /************************************************************************/
55 
56 /**
57  * \class GDALPamDataset "gdal_pam.h"
58  *
59  * A subclass of GDALDataset which introduces the ability to save and
60  * restore auxiliary information (coordinate system, gcps, metadata,
61  * etc) not supported by a file format via an "auxiliary metadata" file
62  * with the .aux.xml extension.
63  *
64  * <h3>Enabling PAM</h3>
65  *
66  * PAM support can be enabled (resp. disabled) in GDAL by setting the
67  * GDAL_PAM_ENABLED configuration option (via CPLSetConfigOption(), or the
68  * environment) to the value of YES (resp. NO). Note: The default value is
69  * build dependent and defaults to YES in Windows and Unix builds.
70  *
71  * <h3>PAM Proxy Files</h3>
72  *
73  * In order to be able to record auxiliary information about files on
74  * read-only media such as CDROMs or in directories where the user does not
75  * have write permissions, it is possible to enable the "PAM Proxy Database".
76  * When enabled the .aux.xml files are kept in a different directory, writable
77  * by the user. Overviews will also be stored in the PAM proxy directory.
78  *
79  * To enable this, set the GDAL_PAM_PROXY_DIR configuration option to be
80  * the name of the directory where the proxies should be kept. The configuration
81  * option must be set *before* the first access to PAM, because its value is
82  * cached for later access.
83  *
84  * <h3>Adding PAM to Drivers</h3>
85  *
86  * Drivers for physical file formats that wish to support persistent auxiliary
87  * metadata in addition to that for the format itself should derive their
88  * dataset class from GDALPamDataset instead of directly from GDALDataset.
89  * The raster band classes should also be derived from GDALPamRasterBand.
90  *
91  * They should also call something like this near the end of the Open()
92  * method:
93  *
94  * \code
95  *      poDS->SetDescription( poOpenInfo->pszFilename );
96  *      poDS->TryLoadXML();
97  * \endcode
98  *
99  * The SetDescription() is necessary so that the dataset will have a valid
100  * filename set as the description before TryLoadXML() is called.  TryLoadXML()
101  * will look for an .aux.xml file with the same basename as the dataset and
102  * in the same directory.  If found the contents will be loaded and kept
103  * track of in the GDALPamDataset and GDALPamRasterBand objects.  When a
104  * call like GetProjectionRef() is not implemented by the format specific
105  * class, it will fall through to the PAM implementation which will return
106  * information if it was in the .aux.xml file.
107  *
108  * Drivers should also try to call the GDALPamDataset/GDALPamRasterBand
109  * methods as a fallback if their implementation does not find information.
110  * This allows using the .aux.xml for variations that can't be stored in
111  * the format.  For instance, the GeoTIFF driver GetProjectionRef() looks
112  * like this:
113  *
114  * \code
115  *      if( EQUAL(pszProjection,"") )
116  *          return GDALPamDataset::GetProjectionRef();
117  *      else
118  *          return( pszProjection );
119  * \endcode
120  *
121  * So if the geotiff header is missing, the .aux.xml file will be
122  * consulted.
123  *
124  * Similarly, if SetProjection() were called with a coordinate system
125  * not supported by GeoTIFF, the SetProjection() method should pass it on
126  * to the GDALPamDataset::SetProjection() method after issuing a warning
127  * that the information can't be represented within the file itself.
128  *
129  * Drivers for subdataset based formats will also need to declare the
130  * name of the physical file they are related to, and the name of their
131  * subdataset before calling TryLoadXML().
132  *
133  * \code
134  *      poDS->SetDescription( poOpenInfo->pszFilename );
135  *      poDS->SetPhysicalFilename( poDS->pszFilename );
136  *      poDS->SetSubdatasetName( osSubdatasetName );
137  *
138  *      poDS->TryLoadXML();
139  * \endcode
140  */
141 class GDALPamDataset;
142 
GDALPamDataset()143 GDALPamDataset::GDALPamDataset()
144 {
145     SetMOFlags( GetMOFlags() | GMO_PAM_CLASS );
146 }
147 
148 /************************************************************************/
149 /*                          ~GDALPamDataset()                           */
150 /************************************************************************/
151 
~GDALPamDataset()152 GDALPamDataset::~GDALPamDataset()
153 
154 {
155     if( nPamFlags & GPF_DIRTY )
156     {
157         CPLDebug( "GDALPamDataset", "In destructor with dirty metadata." );
158         GDALPamDataset::TrySaveXML();
159     }
160 
161     PamClear();
162 }
163 
164 /************************************************************************/
165 /*                             FlushCache()                             */
166 /************************************************************************/
167 
FlushCache()168 void GDALPamDataset::FlushCache()
169 
170 {
171     GDALDataset::FlushCache();
172     if( nPamFlags & GPF_DIRTY )
173         TrySaveXML();
174 }
175 
176 /************************************************************************/
177 /*                           SerializeToXML()                           */
178 /************************************************************************/
179 
180 //! @cond Doxygen_Suppress
SerializeToXML(const char * pszUnused)181 CPLXMLNode *GDALPamDataset::SerializeToXML( const char *pszUnused )
182 
183 {
184     if( psPam == nullptr )
185         return nullptr;
186 
187 /* -------------------------------------------------------------------- */
188 /*      Setup root node and attributes.                                 */
189 /* -------------------------------------------------------------------- */
190     CPLXMLNode *psDSTree = CPLCreateXMLNode( nullptr, CXT_Element, "PAMDataset" );
191 
192 /* -------------------------------------------------------------------- */
193 /*      SRS                                                             */
194 /* -------------------------------------------------------------------- */
195     if( psPam->poSRS && !psPam->poSRS->IsEmpty() )
196     {
197         char* pszWKT = nullptr;
198         {
199             CPLErrorStateBackuper oErrorStateBackuper;
200             CPLErrorHandlerPusher oErrorHandler(CPLQuietErrorHandler);
201             if( psPam->poSRS->exportToWkt(&pszWKT) != OGRERR_NONE )
202             {
203                 CPLFree(pszWKT);
204                 pszWKT = nullptr;
205                 const char* const apszOptions[] = { "FORMAT=WKT2", nullptr };
206                 psPam->poSRS->exportToWkt(&pszWKT, apszOptions);
207             }
208         }
209         CPLXMLNode* psSRSNode = CPLCreateXMLElementAndValue( psDSTree, "SRS", pszWKT );
210         CPLFree(pszWKT);
211         const auto& mapping = psPam->poSRS->GetDataAxisToSRSAxisMapping();
212         CPLString osMapping;
213         for( size_t i = 0; i < mapping.size(); ++i )
214         {
215             if( !osMapping.empty() )
216                 osMapping += ",";
217             osMapping += CPLSPrintf("%d", mapping[i]);
218         }
219         CPLAddXMLAttributeAndValue(psSRSNode, "dataAxisToSRSAxisMapping",
220                                    osMapping.c_str());
221     }
222 
223 /* -------------------------------------------------------------------- */
224 /*      GeoTransform.                                                   */
225 /* -------------------------------------------------------------------- */
226     if( psPam->bHaveGeoTransform )
227     {
228         CPLString oFmt;
229         oFmt.Printf( "%24.16e,%24.16e,%24.16e,%24.16e,%24.16e,%24.16e",
230                      psPam->adfGeoTransform[0],
231                      psPam->adfGeoTransform[1],
232                      psPam->adfGeoTransform[2],
233                      psPam->adfGeoTransform[3],
234                      psPam->adfGeoTransform[4],
235                      psPam->adfGeoTransform[5] );
236         CPLSetXMLValue( psDSTree, "GeoTransform", oFmt );
237     }
238 
239 /* -------------------------------------------------------------------- */
240 /*      Metadata.                                                       */
241 /* -------------------------------------------------------------------- */
242     if( psPam->bHasMetadata )
243     {
244         CPLXMLNode *psMD = oMDMD.Serialize();
245         if( psMD != nullptr )
246         {
247             CPLAddXMLChild( psDSTree, psMD );
248         }
249     }
250 
251 /* -------------------------------------------------------------------- */
252 /*      GCPs                                                            */
253 /* -------------------------------------------------------------------- */
254     if( psPam->nGCPCount > 0 )
255     {
256         GDALSerializeGCPListToXML( psDSTree,
257                                    psPam->pasGCPList,
258                                    psPam->nGCPCount,
259                                    psPam->poGCP_SRS );
260     }
261 
262 /* -------------------------------------------------------------------- */
263 /*      Process bands.                                                  */
264 /* -------------------------------------------------------------------- */
265 
266     // Find last child
267     CPLXMLNode* psLastChild = psDSTree->psChild;
268     for( ; psLastChild != nullptr && psLastChild->psNext;
269                                     psLastChild = psLastChild->psNext )
270     {
271     }
272 
273     for( int iBand = 0; iBand < GetRasterCount(); iBand++ )
274     {
275         GDALRasterBand * const poBand = GetRasterBand(iBand+1);
276 
277         if( poBand == nullptr || !(poBand->GetMOFlags() & GMO_PAM_CLASS) )
278             continue;
279 
280         CPLXMLNode * const psBandTree =
281             cpl::down_cast<GDALPamRasterBand *>(poBand)->SerializeToXML( pszUnused );
282 
283         if( psBandTree != nullptr )
284         {
285             if( psLastChild == nullptr )
286             {
287                 CPLAddXMLChild( psDSTree, psBandTree );
288             }
289             else
290             {
291                 psLastChild->psNext = psBandTree;
292             }
293             psLastChild = psBandTree;
294         }
295     }
296 
297 /* -------------------------------------------------------------------- */
298 /*      Save MDArray statistics                                         */
299 /* -------------------------------------------------------------------- */
300     SerializeMDArrayStatistics(psDSTree);
301 
302 /* -------------------------------------------------------------------- */
303 /*      We don't want to return anything if we had no metadata to       */
304 /*      attach.                                                         */
305 /* -------------------------------------------------------------------- */
306     if( psDSTree->psChild == nullptr )
307     {
308         CPLDestroyXMLNode( psDSTree );
309         psDSTree = nullptr;
310     }
311 
312     return psDSTree;
313 }
314 
315 /************************************************************************/
316 /*                       SerializeMDArrayStatistics()                   */
317 /************************************************************************/
318 
SerializeMDArrayStatistics(CPLXMLNode * psDSTree)319 void GDALPamDataset::SerializeMDArrayStatistics(CPLXMLNode* psDSTree)
320 
321 {
322     if( !psPam->oMapMDArrayStatistics.empty() )
323     {
324         CPLXMLNode* psMDArrayStats = CPLCreateXMLNode(
325             psDSTree, CXT_Element, "MDArrayStatistics" );
326         for( const auto& kv: psPam->oMapMDArrayStatistics )
327         {
328             CPLXMLNode* psMDArray = CPLCreateXMLNode(
329                 psMDArrayStats, CXT_Element, "MDArray" );
330             CPLAddXMLAttributeAndValue(psMDArray, "id", kv.first.c_str());
331             CPLCreateXMLElementAndValue(psMDArray,
332                                         "ApproxStats",
333                                         kv.second.bApproxStats ? "1" : "0");
334             CPLCreateXMLElementAndValue(psMDArray,
335                                         "Minimum",
336                                         CPLSPrintf("%.18g", kv.second.dfMin));
337             CPLCreateXMLElementAndValue(psMDArray,
338                                         "Maximum",
339                                         CPLSPrintf("%.18g", kv.second.dfMax));
340             CPLCreateXMLElementAndValue(psMDArray,
341                                         "Mean",
342                                         CPLSPrintf("%.18g", kv.second.dfMean));
343             CPLCreateXMLElementAndValue(psMDArray,
344                                         "StdDev",
345                                         CPLSPrintf("%.18g", kv.second.dfStdDev));
346             CPLCreateXMLElementAndValue(psMDArray,
347                                         "ValidSampleCount",
348                                         CPLSPrintf(CPL_FRMT_GUIB, kv.second.nValidCount));
349         }
350     }
351 }
352 
353 /************************************************************************/
354 /*                           PamInitialize()                            */
355 /************************************************************************/
356 
PamInitialize()357 void GDALPamDataset::PamInitialize()
358 
359 {
360 #ifdef PAM_ENABLED
361     const char * const pszPamDefault = "YES";
362 #else
363     const char * const pszPamDefault = "NO";
364 #endif
365 
366     if( psPam || (nPamFlags & GPF_DISABLED) )
367         return;
368 
369     if( !CPLTestBool( CPLGetConfigOption( "GDAL_PAM_ENABLED",
370                                              pszPamDefault ) ) )
371     {
372         nPamFlags |= GPF_DISABLED;
373         return;
374     }
375 
376     /* ERO 2011/04/13 : GPF_AUXMODE seems to be unimplemented */
377     if( EQUAL( CPLGetConfigOption( "GDAL_PAM_MODE", "PAM" ), "AUX") )
378         nPamFlags |= GPF_AUXMODE;
379 
380     psPam = new GDALDatasetPamInfo;
381     for( int iBand = 0; iBand < GetRasterCount(); iBand++ )
382     {
383         GDALRasterBand *poBand = GetRasterBand(iBand+1);
384 
385         if( poBand == nullptr || !(poBand->GetMOFlags() & GMO_PAM_CLASS) )
386             continue;
387 
388         cpl::down_cast<GDALPamRasterBand *>(poBand)->PamInitialize();
389     }
390 }
391 
392 /************************************************************************/
393 /*                              PamClear()                              */
394 /************************************************************************/
395 
PamClear()396 void GDALPamDataset::PamClear()
397 
398 {
399     if( psPam )
400     {
401         CPLFree( psPam->pszPamFilename );
402         if( psPam->poSRS )
403             psPam->poSRS->Release();
404         if( psPam->poGCP_SRS )
405             psPam->poGCP_SRS->Release();
406         if( psPam->nGCPCount > 0 )
407         {
408             GDALDeinitGCPs( psPam->nGCPCount, psPam->pasGCPList );
409             CPLFree( psPam->pasGCPList );
410         }
411 
412         delete psPam;
413         psPam = nullptr;
414     }
415 }
416 
417 /************************************************************************/
418 /*                              XMLInit()                               */
419 /************************************************************************/
420 
XMLInit(CPLXMLNode * psTree,const char * pszUnused)421 CPLErr GDALPamDataset::XMLInit( CPLXMLNode *psTree, const char *pszUnused )
422 
423 {
424 /* -------------------------------------------------------------------- */
425 /*      Check for an SRS node.                                          */
426 /* -------------------------------------------------------------------- */
427     CPLXMLNode* psSRSNode = CPLGetXMLNode(psTree, "SRS");
428     if( psSRSNode )
429     {
430         if( psPam->poSRS )
431             psPam->poSRS->Release();
432         psPam->poSRS = new OGRSpatialReference();
433         psPam->poSRS->SetFromUserInput( CPLGetXMLValue(psSRSNode, nullptr, "") );
434         const char* pszMapping =
435             CPLGetXMLValue(psSRSNode, "dataAxisToSRSAxisMapping", nullptr);
436         if( pszMapping )
437         {
438             char** papszTokens = CSLTokenizeStringComplex( pszMapping, ",", FALSE, FALSE);
439             std::vector<int> anMapping;
440             for( int i = 0; papszTokens && papszTokens[i]; i++ )
441             {
442                 anMapping.push_back(atoi(papszTokens[i]));
443             }
444             CSLDestroy(papszTokens);
445             psPam->poSRS->SetDataAxisToSRSAxisMapping(anMapping);
446         }
447         else
448         {
449             psPam->poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
450         }
451     }
452 
453 /* -------------------------------------------------------------------- */
454 /*      Check for a GeoTransform node.                                  */
455 /* -------------------------------------------------------------------- */
456     if( strlen(CPLGetXMLValue(psTree, "GeoTransform", "")) > 0 )
457     {
458         const char *pszGT = CPLGetXMLValue(psTree, "GeoTransform", "");
459 
460         char **papszTokens =
461             CSLTokenizeStringComplex( pszGT, ",", FALSE, FALSE );
462         if( CSLCount(papszTokens) != 6 )
463         {
464             CPLError( CE_Warning, CPLE_AppDefined,
465                       "GeoTransform node does not have expected six values.");
466         }
467         else
468         {
469             for( int iTA = 0; iTA < 6; iTA++ )
470                 psPam->adfGeoTransform[iTA] = CPLAtof(papszTokens[iTA]);
471             psPam->bHaveGeoTransform = TRUE;
472         }
473 
474         CSLDestroy( papszTokens );
475     }
476 
477 /* -------------------------------------------------------------------- */
478 /*      Check for GCPs.                                                 */
479 /* -------------------------------------------------------------------- */
480     CPLXMLNode *psGCPList = CPLGetXMLNode( psTree, "GCPList" );
481 
482     if( psGCPList != nullptr )
483     {
484         if( psPam->poGCP_SRS )
485             psPam->poGCP_SRS->Release();
486         psPam->poGCP_SRS = nullptr;
487 
488         // Make sure any previous GCPs, perhaps from an .aux file, are cleared
489         // if we have new ones.
490         if( psPam->nGCPCount > 0 )
491         {
492             GDALDeinitGCPs( psPam->nGCPCount, psPam->pasGCPList );
493             CPLFree( psPam->pasGCPList );
494             psPam->nGCPCount = 0;
495             psPam->pasGCPList = nullptr;
496         }
497 
498         GDALDeserializeGCPListFromXML( psGCPList,
499                                        &(psPam->pasGCPList),
500                                        &(psPam->nGCPCount),
501                                        &(psPam->poGCP_SRS) );
502     }
503 
504 /* -------------------------------------------------------------------- */
505 /*      Apply any dataset level metadata.                               */
506 /* -------------------------------------------------------------------- */
507     if( oMDMD.XMLInit( psTree, TRUE ) )
508     {
509         psPam->bHasMetadata = TRUE;
510     }
511 
512 /* -------------------------------------------------------------------- */
513 /*      Try loading ESRI xml encoded GeodataXform.                      */
514 /* -------------------------------------------------------------------- */
515     if (psPam->poSRS == nullptr)
516     {
517         // ArcGIS 9.3: GeodataXform as a root element
518         CPLXMLNode* psGeodataXform = CPLGetXMLNode(psTree, "=GeodataXform");
519         CPLXMLNode *psValueAsXML = nullptr;
520         if( psGeodataXform != nullptr )
521         {
522             char* apszMD[2];
523             apszMD[0] = CPLSerializeXMLTree(psGeodataXform);
524             apszMD[1] = nullptr;
525             oMDMD.SetMetadata( apszMD, "xml:ESRI");
526             CPLFree(apszMD[0]);
527         }
528         else
529         {
530             // ArcGIS 10: GeodataXform as content of xml:ESRI metadata domain.
531             char** papszXML = oMDMD.GetMetadata( "xml:ESRI" );
532             if (CSLCount(papszXML) == 1)
533             {
534                 psValueAsXML = CPLParseXMLString( papszXML[0] );
535                 if( psValueAsXML )
536                     psGeodataXform = CPLGetXMLNode(psValueAsXML, "=GeodataXform");
537             }
538         }
539 
540         if (psGeodataXform)
541         {
542             const char* pszESRI_WKT = CPLGetXMLValue(psGeodataXform,
543                                 "SpatialReference.WKT", nullptr);
544             if (pszESRI_WKT)
545             {
546                 psPam->poSRS = new OGRSpatialReference(nullptr);
547                 psPam->poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
548                 if( psPam->poSRS->importFromWkt(pszESRI_WKT) != OGRERR_NONE)
549                 {
550                     delete psPam->poSRS;
551                     psPam->poSRS = nullptr;
552                 }
553             }
554 
555             // Parse GCPs
556             const CPLXMLNode* psSourceGCPS = CPLGetXMLNode(psGeodataXform, "SourceGCPs");
557             const CPLXMLNode* psTargetGCPs = CPLGetXMLNode(psGeodataXform, "TargetGCPs");
558             if( psSourceGCPS && psTargetGCPs && !psPam->bHaveGeoTransform )
559             {
560                 std::vector<double> adfSource;
561                 std::vector<double> adfTarget;
562                 bool ySourceAllNegative = true;
563                 for( auto psIter = psSourceGCPS->psChild; psIter; psIter = psIter->psNext )
564                 {
565                     if( psIter->eType == CXT_Element && strcmp(psIter->pszValue, "Double") == 0 )
566                     {
567                         adfSource.push_back(CPLAtof(CPLGetXMLValue(psIter, nullptr, "0")));
568                         if( (adfSource.size() % 2) == 0 && adfSource.back() > 0 )
569                             ySourceAllNegative = false;
570                     }
571                 }
572                 for( auto psIter = psTargetGCPs->psChild; psIter; psIter = psIter->psNext )
573                 {
574                     if( psIter->eType == CXT_Element && strcmp(psIter->pszValue, "Double") == 0 )
575                     {
576                         adfTarget.push_back(CPLAtof(CPLGetXMLValue(psIter, nullptr, "0")));
577                     }
578                 }
579                 if( !adfSource.empty() &&
580                     adfSource.size() == adfTarget.size() &&
581                     (adfSource.size() % 2) == 0 )
582                 {
583                     std::vector<GDAL_GCP> asGCPs;
584                     asGCPs.resize(adfSource.size() / 2);
585                     char szEmptyString[] = { 0 };
586                     for( size_t i = 0; i+1 < adfSource.size(); i+= 2 )
587                     {
588                         asGCPs[i/2].pszId = szEmptyString;
589                         asGCPs[i/2].pszInfo = szEmptyString;
590                         asGCPs[i/2].dfGCPPixel = adfSource[i];
591                         asGCPs[i/2].dfGCPLine =
592                             ySourceAllNegative ? -adfSource[i+1] : adfSource[i+1];
593                         asGCPs[i/2].dfGCPX = adfTarget[i];
594                         asGCPs[i/2].dfGCPY = adfTarget[i+1];
595                         asGCPs[i/2].dfGCPZ = 0;
596                     }
597                     GDALPamDataset::SetGCPs( static_cast<int>(asGCPs.size()),
598                                              &asGCPs[0],
599                                              psPam->poSRS );
600                     delete psPam->poSRS;
601                     psPam->poSRS = nullptr;
602                 }
603             }
604         }
605         if( psValueAsXML )
606             CPLDestroyXMLNode(psValueAsXML);
607     }
608 
609 /* -------------------------------------------------------------------- */
610 /*      Process bands.                                                  */
611 /* -------------------------------------------------------------------- */
612     for( CPLXMLNode *psBandTree = psTree->psChild;
613          psBandTree != nullptr;
614          psBandTree = psBandTree->psNext )
615     {
616         if( psBandTree->eType != CXT_Element
617             || !EQUAL(psBandTree->pszValue,"PAMRasterBand") )
618             continue;
619 
620         const int nBand = atoi(CPLGetXMLValue( psBandTree, "band", "0"));
621 
622         if( nBand < 1 || nBand > GetRasterCount() )
623             continue;
624 
625         GDALRasterBand *poBand = GetRasterBand(nBand);
626 
627         if( poBand == nullptr || !(poBand->GetMOFlags() & GMO_PAM_CLASS) )
628             continue;
629 
630         GDALPamRasterBand *poPamBand = cpl::down_cast<GDALPamRasterBand *>(
631             GetRasterBand(nBand) );
632 
633         poPamBand->XMLInit( psBandTree, pszUnused );
634     }
635 
636 /* -------------------------------------------------------------------- */
637 /*      Load MDArray statistics                                         */
638 /* -------------------------------------------------------------------- */
639     CPLXMLNode* psMDArrayStats = CPLGetXMLNode(psTree, "MDArrayStatistics" );
640     if( psMDArrayStats )
641     {
642         for( CPLXMLNode *psIter = psMDArrayStats->psChild;
643             psIter != nullptr;
644             psIter = psIter->psNext )
645         {
646             if( psIter->eType != CXT_Element
647                 || !EQUAL(psIter->pszValue,"MDArray") )
648                 continue;
649 
650             const char* pszId = CPLGetXMLValue(psIter, "id", nullptr);
651             if( pszId == nullptr )
652                 continue;
653 
654             GDALDatasetPamInfo::Statistics sStats;
655             sStats.bApproxStats = CPLTestBool(
656                 CPLGetXMLValue(psIter, "ApproxStats", "false"));
657             sStats.dfMin = CPLAtofM(
658                 CPLGetXMLValue(psIter, "Minimum", "0"));
659             sStats.dfMax = CPLAtofM(
660                 CPLGetXMLValue(psIter, "Maximum", "0"));
661             sStats.dfMean = CPLAtofM(
662                 CPLGetXMLValue(psIter, "Mean", "0"));
663             sStats.dfStdDev = CPLAtofM(
664                 CPLGetXMLValue(psIter, "StdDev", "0"));
665             sStats.nValidCount = static_cast<GUInt64>(CPLAtoGIntBig(
666                 CPLGetXMLValue(psIter, "ValidSampleCount", "0")));
667             psPam->oMapMDArrayStatistics[pszId] = sStats;
668         }
669     }
670 
671 /* -------------------------------------------------------------------- */
672 /*      Clear dirty flag.                                               */
673 /* -------------------------------------------------------------------- */
674     nPamFlags &= ~GPF_DIRTY;
675 
676     return CE_None;
677 }
678 
679 /************************************************************************/
680 /*                        SetPhysicalFilename()                         */
681 /************************************************************************/
682 
SetPhysicalFilename(const char * pszFilename)683 void GDALPamDataset::SetPhysicalFilename( const char *pszFilename )
684 
685 {
686     PamInitialize();
687 
688     if( psPam )
689         psPam->osPhysicalFilename = pszFilename;
690 }
691 
692 /************************************************************************/
693 /*                        GetPhysicalFilename()                         */
694 /************************************************************************/
695 
GetPhysicalFilename()696 const char *GDALPamDataset::GetPhysicalFilename()
697 
698 {
699     PamInitialize();
700 
701     if( psPam )
702         return psPam->osPhysicalFilename;
703 
704     return "";
705 }
706 
707 /************************************************************************/
708 /*                         SetSubdatasetName()                          */
709 /************************************************************************/
710 
SetSubdatasetName(const char * pszSubdataset)711 void GDALPamDataset::SetSubdatasetName( const char *pszSubdataset )
712 
713 {
714     PamInitialize();
715 
716     if( psPam )
717         psPam->osSubdatasetName = pszSubdataset;
718 }
719 
720 /************************************************************************/
721 /*                         GetSubdatasetName()                          */
722 /************************************************************************/
723 
GetSubdatasetName()724 const char *GDALPamDataset::GetSubdatasetName()
725 
726 {
727     PamInitialize();
728 
729     if( psPam )
730         return psPam->osSubdatasetName;
731 
732     return "";
733 }
734 
735 /************************************************************************/
736 /*                          BuildPamFilename()                          */
737 /************************************************************************/
738 
BuildPamFilename()739 const char *GDALPamDataset::BuildPamFilename()
740 
741 {
742     if( psPam == nullptr )
743         return nullptr;
744 
745 /* -------------------------------------------------------------------- */
746 /*      What is the name of the physical file we are referencing?       */
747 /*      We allow an override via the psPam->pszPhysicalFile item.       */
748 /* -------------------------------------------------------------------- */
749     if( psPam->pszPamFilename != nullptr )
750         return psPam->pszPamFilename;
751 
752     const char *pszPhysicalFile = psPam->osPhysicalFilename;
753 
754     if( strlen(pszPhysicalFile) == 0 && GetDescription() != nullptr )
755         pszPhysicalFile = GetDescription();
756 
757     if( strlen(pszPhysicalFile) == 0 )
758         return nullptr;
759 
760 /* -------------------------------------------------------------------- */
761 /*      Try a proxy lookup, otherwise just add .aux.xml.                */
762 /* -------------------------------------------------------------------- */
763     const char *pszProxyPam = PamGetProxy( pszPhysicalFile );
764     if( pszProxyPam != nullptr )
765         psPam->pszPamFilename = CPLStrdup(pszProxyPam);
766     else
767     {
768         if( !GDALCanFileAcceptSidecarFile(pszPhysicalFile) )
769             return nullptr;
770         psPam->pszPamFilename = static_cast<char*>(CPLMalloc(strlen(pszPhysicalFile)+10));
771         strcpy( psPam->pszPamFilename, pszPhysicalFile );
772         strcat( psPam->pszPamFilename, ".aux.xml" );
773     }
774 
775     return psPam->pszPamFilename;
776 }
777 
778 /************************************************************************/
779 /*                   IsPamFilenameAPotentialSiblingFile()               */
780 /************************************************************************/
781 
IsPamFilenameAPotentialSiblingFile()782 int GDALPamDataset::IsPamFilenameAPotentialSiblingFile()
783 {
784     if (psPam == nullptr)
785         return FALSE;
786 
787 /* -------------------------------------------------------------------- */
788 /*      Determine if the PAM filename is a .aux.xml file next to the    */
789 /*      physical file, or if it comes from the ProxyDB                  */
790 /* -------------------------------------------------------------------- */
791     const char *pszPhysicalFile = psPam->osPhysicalFilename;
792 
793     if( strlen(pszPhysicalFile) == 0 && GetDescription() != nullptr )
794         pszPhysicalFile = GetDescription();
795 
796     size_t nLenPhysicalFile = strlen(pszPhysicalFile);
797     int bIsSiblingPamFile = strncmp(psPam->pszPamFilename, pszPhysicalFile,
798                                     nLenPhysicalFile) == 0 &&
799                             strcmp(psPam->pszPamFilename + nLenPhysicalFile,
800                                    ".aux.xml") == 0;
801 
802     return bIsSiblingPamFile;
803 }
804 
805 /************************************************************************/
806 /*                             TryLoadXML()                             */
807 /************************************************************************/
808 
TryLoadXML(char ** papszSiblingFiles)809 CPLErr GDALPamDataset::TryLoadXML(char **papszSiblingFiles)
810 
811 {
812     PamInitialize();
813 
814 /* -------------------------------------------------------------------- */
815 /*      Clear dirty flag.  Generally when we get to this point is       */
816 /*      from a call at the end of the Open() method, and some calls     */
817 /*      may have already marked the PAM info as dirty (for instance     */
818 /*      setting metadata), but really everything to this point is       */
819 /*      reproducible, and so the PAM info should not really be          */
820 /*      thought of as dirty.                                            */
821 /* -------------------------------------------------------------------- */
822     nPamFlags &= ~GPF_DIRTY;
823 
824 /* -------------------------------------------------------------------- */
825 /*      Try reading the file.                                           */
826 /* -------------------------------------------------------------------- */
827     if( !BuildPamFilename() )
828         return CE_None;
829 
830 /* -------------------------------------------------------------------- */
831 /*      In case the PAM filename is a .aux.xml file next to the         */
832 /*      physical file and we have a siblings list, then we can skip     */
833 /*      stat'ing the filesystem.                                        */
834 /* -------------------------------------------------------------------- */
835     VSIStatBufL sStatBuf;
836     CPLXMLNode *psTree = nullptr;
837 
838     CPLErr eLastErr = CPLGetLastErrorType();
839     int nLastErrNo = CPLGetLastErrorNo();
840     CPLString osLastErrorMsg = CPLGetLastErrorMsg();
841 
842     if( papszSiblingFiles != nullptr && IsPamFilenameAPotentialSiblingFile() &&
843         GDALCanReliablyUseSiblingFileList(psPam->pszPamFilename) )
844     {
845         const int iSibling =
846             CSLFindString( papszSiblingFiles,
847                            CPLGetFilename(psPam->pszPamFilename) );
848         if( iSibling >= 0 )
849         {
850             CPLErrorReset();
851             CPLPushErrorHandler( CPLQuietErrorHandler );
852             psTree = CPLParseXMLFile( psPam->pszPamFilename );
853             CPLPopErrorHandler();
854             CPLErrorReset();
855         }
856     }
857     else
858     if( VSIStatExL( psPam->pszPamFilename, &sStatBuf,
859                     VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG ) == 0
860         && VSI_ISREG( sStatBuf.st_mode ) )
861     {
862         CPLErrorReset();
863         CPLPushErrorHandler( CPLQuietErrorHandler );
864         psTree = CPLParseXMLFile( psPam->pszPamFilename );
865         CPLPopErrorHandler();
866         CPLErrorReset();
867     }
868 
869     if( eLastErr != CE_None )
870         CPLErrorSetState( eLastErr, nLastErrNo, osLastErrorMsg.c_str() );
871 
872 /* -------------------------------------------------------------------- */
873 /*      If we are looking for a subdataset, search for its subtree not. */
874 /* -------------------------------------------------------------------- */
875     if( psTree && !psPam->osSubdatasetName.empty() )
876     {
877         CPLXMLNode *psSubTree = psTree->psChild;
878 
879         for( ;
880              psSubTree != nullptr;
881              psSubTree = psSubTree->psNext )
882         {
883             if( psSubTree->eType != CXT_Element
884                 || !EQUAL(psSubTree->pszValue,"Subdataset") )
885                 continue;
886 
887             if( !EQUAL(CPLGetXMLValue( psSubTree, "name", "" ),
888                        psPam->osSubdatasetName) )
889                 continue;
890 
891             psSubTree = CPLGetXMLNode( psSubTree, "PAMDataset" );
892             break;
893         }
894 
895         if( psSubTree != nullptr )
896             psSubTree = CPLCloneXMLTree( psSubTree );
897 
898         CPLDestroyXMLNode( psTree );
899         psTree = psSubTree;
900     }
901 
902 /* -------------------------------------------------------------------- */
903 /*      If we fail, try .aux.                                           */
904 /* -------------------------------------------------------------------- */
905     if( psTree == nullptr )
906         return TryLoadAux(papszSiblingFiles);
907 
908 /* -------------------------------------------------------------------- */
909 /*      Initialize ourselves from this XML tree.                        */
910 /* -------------------------------------------------------------------- */
911 
912     CPLString osVRTPath(CPLGetPath(psPam->pszPamFilename));
913     const CPLErr eErr = XMLInit( psTree, osVRTPath );
914 
915     CPLDestroyXMLNode( psTree );
916 
917     if( eErr != CE_None )
918         PamClear();
919 
920     return eErr;
921 }
922 
923 /************************************************************************/
924 /*                             TrySaveXML()                             */
925 /************************************************************************/
926 
TrySaveXML()927 CPLErr GDALPamDataset::TrySaveXML()
928 
929 {
930     nPamFlags &= ~GPF_DIRTY;
931 
932     if( psPam == nullptr || (nPamFlags & GPF_NOSAVE) )
933         return CE_None;
934 
935 /* -------------------------------------------------------------------- */
936 /*      Make sure we know the filename we want to store in.             */
937 /* -------------------------------------------------------------------- */
938     if( !BuildPamFilename() )
939         return CE_None;
940 
941 /* -------------------------------------------------------------------- */
942 /*      Build the XML representation of the auxiliary metadata.          */
943 /* -------------------------------------------------------------------- */
944     CPLXMLNode *psTree = SerializeToXML( nullptr );
945 
946     if( psTree == nullptr )
947     {
948         /* If we have unset all metadata, we have to delete the PAM file */
949         CPLPushErrorHandler( CPLQuietErrorHandler );
950         VSIUnlink(psPam->pszPamFilename);
951         CPLPopErrorHandler();
952         return CE_None;
953     }
954 
955 /* -------------------------------------------------------------------- */
956 /*      If we are working with a subdataset, we need to integrate       */
957 /*      the subdataset tree within the whole existing pam tree,         */
958 /*      after removing any old version of the same subdataset.          */
959 /* -------------------------------------------------------------------- */
960     if( !psPam->osSubdatasetName.empty() )
961     {
962         CPLXMLNode *psOldTree, *psSubTree;
963 
964         CPLErrorReset();
965         CPLPushErrorHandler( CPLQuietErrorHandler );
966         psOldTree = CPLParseXMLFile( psPam->pszPamFilename );
967         CPLPopErrorHandler();
968 
969         if( psOldTree == nullptr )
970             psOldTree = CPLCreateXMLNode( nullptr, CXT_Element, "PAMDataset" );
971 
972         for( psSubTree = psOldTree->psChild;
973              psSubTree != nullptr;
974              psSubTree = psSubTree->psNext )
975         {
976             if( psSubTree->eType != CXT_Element
977                 || !EQUAL(psSubTree->pszValue,"Subdataset") )
978                 continue;
979 
980             if( !EQUAL(CPLGetXMLValue( psSubTree, "name", "" ),
981                        psPam->osSubdatasetName) )
982                 continue;
983 
984             break;
985         }
986 
987         if( psSubTree == nullptr )
988         {
989             psSubTree = CPLCreateXMLNode( psOldTree, CXT_Element,
990                                           "Subdataset" );
991             CPLCreateXMLNode(
992                 CPLCreateXMLNode( psSubTree, CXT_Attribute, "name" ),
993                 CXT_Text, psPam->osSubdatasetName );
994         }
995 
996         CPLXMLNode *psOldPamDataset = CPLGetXMLNode( psSubTree, "PAMDataset");
997         if( psOldPamDataset != nullptr )
998         {
999             CPLRemoveXMLChild( psSubTree, psOldPamDataset );
1000             CPLDestroyXMLNode( psOldPamDataset );
1001         }
1002 
1003         CPLAddXMLChild( psSubTree, psTree );
1004         psTree = psOldTree;
1005     }
1006 
1007 /* -------------------------------------------------------------------- */
1008 /*      Try saving the auxiliary metadata.                               */
1009 /* -------------------------------------------------------------------- */
1010 
1011     CPLPushErrorHandler( CPLQuietErrorHandler );
1012     const int bSaved =
1013         CPLSerializeXMLTreeToFile( psTree, psPam->pszPamFilename );
1014     CPLPopErrorHandler();
1015 
1016 /* -------------------------------------------------------------------- */
1017 /*      If it fails, check if we have a proxy directory for auxiliary    */
1018 /*      metadata to be stored in, and try to save there.                */
1019 /* -------------------------------------------------------------------- */
1020     CPLErr eErr = CE_None;
1021 
1022     if( bSaved )
1023         eErr = CE_None;
1024     else
1025     {
1026         const char *pszBasename = GetDescription();
1027 
1028         if( psPam->osPhysicalFilename.length() > 0 )
1029             pszBasename = psPam->osPhysicalFilename;
1030 
1031         const char *pszNewPam = nullptr;
1032         if( PamGetProxy(pszBasename) == nullptr
1033             && ((pszNewPam = PamAllocateProxy(pszBasename)) != nullptr))
1034         {
1035             CPLErrorReset();
1036             CPLFree( psPam->pszPamFilename );
1037             psPam->pszPamFilename = CPLStrdup(pszNewPam);
1038             eErr = TrySaveXML();
1039         }
1040         /* No way we can save into a /vsicurl resource */
1041         else if( !STARTS_WITH(psPam->pszPamFilename, "/vsicurl") )
1042         {
1043             CPLError( CE_Warning, CPLE_AppDefined,
1044                       "Unable to save auxiliary information in %s.",
1045                       psPam->pszPamFilename );
1046             eErr = CE_Warning;
1047         }
1048     }
1049 
1050 /* -------------------------------------------------------------------- */
1051 /*      Cleanup                                                         */
1052 /* -------------------------------------------------------------------- */
1053     CPLDestroyXMLNode( psTree );
1054 
1055     return eErr;
1056 }
1057 
1058 /************************************************************************/
1059 /*                             CloneInfo()                              */
1060 /************************************************************************/
1061 
CloneInfo(GDALDataset * poSrcDS,int nCloneFlags)1062 CPLErr GDALPamDataset::CloneInfo( GDALDataset *poSrcDS, int nCloneFlags )
1063 
1064 {
1065     const int bOnlyIfMissing = nCloneFlags & GCIF_ONLY_IF_MISSING;
1066     const int nSavedMOFlags = GetMOFlags();
1067 
1068     PamInitialize();
1069 
1070 /* -------------------------------------------------------------------- */
1071 /*      Suppress NotImplemented error messages - mainly needed if PAM   */
1072 /*      disabled.                                                       */
1073 /* -------------------------------------------------------------------- */
1074     SetMOFlags( nSavedMOFlags | GMO_IGNORE_UNIMPLEMENTED );
1075 
1076 /* -------------------------------------------------------------------- */
1077 /*      GeoTransform                                                    */
1078 /* -------------------------------------------------------------------- */
1079     if( nCloneFlags & GCIF_GEOTRANSFORM )
1080     {
1081       double adfGeoTransform[6] = { 0.0 };
1082 
1083         if( poSrcDS->GetGeoTransform( adfGeoTransform ) == CE_None )
1084         {
1085             double adfOldGT[6] = { 0.0 };
1086 
1087             if( !bOnlyIfMissing || GetGeoTransform( adfOldGT ) != CE_None )
1088                 SetGeoTransform( adfGeoTransform );
1089         }
1090     }
1091 
1092 /* -------------------------------------------------------------------- */
1093 /*      Projection                                                      */
1094 /* -------------------------------------------------------------------- */
1095     if( nCloneFlags & GCIF_PROJECTION )
1096     {
1097         const auto poSRS = poSrcDS->GetSpatialRef();
1098 
1099         if( poSRS != nullptr )
1100         {
1101             if( !bOnlyIfMissing
1102                 || GetSpatialRef() == nullptr )
1103                 SetSpatialRef( poSRS );
1104         }
1105     }
1106 
1107 /* -------------------------------------------------------------------- */
1108 /*      GCPs                                                            */
1109 /* -------------------------------------------------------------------- */
1110     if( nCloneFlags & GCIF_GCPS )
1111     {
1112         if( poSrcDS->GetGCPCount() > 0 )
1113         {
1114             if( !bOnlyIfMissing || GetGCPCount() == 0 )
1115             {
1116                 SetGCPs( poSrcDS->GetGCPCount(),
1117                          poSrcDS->GetGCPs(),
1118                          poSrcDS->GetGCPSpatialRef() );
1119             }
1120         }
1121     }
1122 
1123 /* -------------------------------------------------------------------- */
1124 /*      Metadata                                                        */
1125 /* -------------------------------------------------------------------- */
1126     if( nCloneFlags & GCIF_METADATA )
1127     {
1128         for( const char* pszMDD: { "", "RPC", "json:ISIS3", "json:VICAR" } )
1129         {
1130             auto papszSrcMD = poSrcDS->GetMetadata(pszMDD);
1131             if(papszSrcMD  != nullptr )
1132             {
1133                 if( !bOnlyIfMissing
1134                     || CSLCount(GetMetadata(pszMDD)) != CSLCount(papszSrcMD) )
1135                 {
1136                     SetMetadata( papszSrcMD, pszMDD );
1137                 }
1138             }
1139         }
1140     }
1141 
1142 /* -------------------------------------------------------------------- */
1143 /*      Process bands.                                                  */
1144 /* -------------------------------------------------------------------- */
1145     if( nCloneFlags & GCIF_PROCESS_BANDS )
1146     {
1147         for( int iBand = 0; iBand < GetRasterCount(); iBand++ )
1148         {
1149             GDALRasterBand *poBand = GetRasterBand(iBand+1);
1150 
1151             if( poBand == nullptr || !(poBand->GetMOFlags() & GMO_PAM_CLASS) )
1152                 continue;
1153 
1154             if( poSrcDS->GetRasterCount() >= iBand+1 )
1155             {
1156                 cpl::down_cast<GDALPamRasterBand *>(poBand)->
1157                     CloneInfo( poSrcDS->GetRasterBand(iBand+1), nCloneFlags );
1158             }
1159             else
1160                 CPLDebug(
1161                     "GDALPamDataset",
1162                     "Skipping CloneInfo for band not in source, "
1163                     "this is a bit unusual!" );
1164         }
1165     }
1166 
1167 /* -------------------------------------------------------------------- */
1168 /*      Copy masks.  These are really copied at a lower level using     */
1169 /*      GDALDefaultOverviews, for formats with no native mask           */
1170 /*      support but this is a convenient central point to put this      */
1171 /*      for most drivers.                                               */
1172 /* -------------------------------------------------------------------- */
1173     if( nCloneFlags & GCIF_MASK )
1174     {
1175         GDALDriver::DefaultCopyMasks( poSrcDS, this, FALSE );
1176     }
1177 
1178 /* -------------------------------------------------------------------- */
1179 /*      Restore MO flags.                                               */
1180 /* -------------------------------------------------------------------- */
1181     SetMOFlags( nSavedMOFlags );
1182 
1183     return CE_None;
1184 }
1185 //! @endcond
1186 
1187 /************************************************************************/
1188 /*                            GetFileList()                             */
1189 /*                                                                      */
1190 /*      Add .aux.xml or .aux file into file list as appropriate.        */
1191 /************************************************************************/
1192 
GetFileList()1193 char **GDALPamDataset::GetFileList()
1194 
1195 {
1196     char **papszFileList = GDALDataset::GetFileList();
1197 
1198     if( psPam && !psPam->osPhysicalFilename.empty()
1199         && GDALCanReliablyUseSiblingFileList(psPam->osPhysicalFilename.c_str())
1200         && CSLFindString( papszFileList, psPam->osPhysicalFilename ) == -1 )
1201     {
1202         papszFileList = CSLInsertString( papszFileList, 0,
1203                                          psPam->osPhysicalFilename );
1204     }
1205 
1206     if( psPam && psPam->pszPamFilename )
1207     {
1208         int bAddPamFile = nPamFlags & GPF_DIRTY;
1209         if (!bAddPamFile)
1210         {
1211             VSIStatBufL sStatBuf;
1212             if (oOvManager.GetSiblingFiles() != nullptr &&
1213                 IsPamFilenameAPotentialSiblingFile() &&
1214                 GDALCanReliablyUseSiblingFileList(psPam->pszPamFilename) )
1215             {
1216                 bAddPamFile = CSLFindString(oOvManager.GetSiblingFiles(),
1217                                   CPLGetFilename(psPam->pszPamFilename)) >= 0;
1218             }
1219             else
1220             {
1221                 bAddPamFile = VSIStatExL( psPam->pszPamFilename, &sStatBuf,
1222                                           VSI_STAT_EXISTS_FLAG ) == 0;
1223             }
1224         }
1225         if (bAddPamFile)
1226         {
1227             papszFileList = CSLAddString( papszFileList, psPam->pszPamFilename );
1228         }
1229     }
1230 
1231     if( psPam && !psPam->osAuxFilename.empty() &&
1232         GDALCanReliablyUseSiblingFileList(psPam->osAuxFilename.c_str()) &&
1233         CSLFindString( papszFileList, psPam->osAuxFilename ) == -1 )
1234     {
1235         papszFileList = CSLAddString( papszFileList, psPam->osAuxFilename );
1236     }
1237     return papszFileList;
1238 }
1239 
1240 /************************************************************************/
1241 /*                          IBuildOverviews()                           */
1242 /************************************************************************/
1243 
1244 //! @cond Doxygen_Suppress
IBuildOverviews(const char * pszResampling,int nOverviews,int * panOverviewList,int nListBands,int * panBandList,GDALProgressFunc pfnProgress,void * pProgressData)1245 CPLErr GDALPamDataset::IBuildOverviews( const char *pszResampling,
1246                                         int nOverviews, int *panOverviewList,
1247                                         int nListBands, int *panBandList,
1248                                         GDALProgressFunc pfnProgress,
1249                                         void * pProgressData )
1250 
1251 {
1252 /* -------------------------------------------------------------------- */
1253 /*      Initialize PAM.                                                 */
1254 /* -------------------------------------------------------------------- */
1255     PamInitialize();
1256     if( psPam == nullptr )
1257         return GDALDataset::IBuildOverviews( pszResampling,
1258                                              nOverviews, panOverviewList,
1259                                              nListBands, panBandList,
1260                                              pfnProgress, pProgressData );
1261 
1262 /* -------------------------------------------------------------------- */
1263 /*      If we appear to have subdatasets and to have a physical         */
1264 /*      filename, use that physical filename to derive a name for a     */
1265 /*      new overview file.                                              */
1266 /* -------------------------------------------------------------------- */
1267     if( oOvManager.IsInitialized() && psPam->osPhysicalFilename.length() != 0 )
1268     {
1269         return oOvManager.BuildOverviewsSubDataset(
1270             psPam->osPhysicalFilename, pszResampling,
1271             nOverviews, panOverviewList,
1272             nListBands, panBandList,
1273             pfnProgress, pProgressData );
1274     }
1275 
1276     return GDALDataset::IBuildOverviews( pszResampling,
1277                                          nOverviews, panOverviewList,
1278                                          nListBands, panBandList,
1279                                          pfnProgress, pProgressData );
1280 }
1281 //! @endcond
1282 
1283 /************************************************************************/
1284 /*                           GetSpatialRef()                            */
1285 /************************************************************************/
1286 
GetSpatialRef() const1287 const OGRSpatialReference *GDALPamDataset::GetSpatialRef() const
1288 
1289 {
1290     if( psPam && psPam->poSRS )
1291         return psPam->poSRS;
1292 
1293     return GDALDataset::GetSpatialRef();
1294 }
1295 
1296 /************************************************************************/
1297 /*                           SetSpatialRef()                            */
1298 /************************************************************************/
1299 
SetSpatialRef(const OGRSpatialReference * poSRS)1300 CPLErr GDALPamDataset::SetSpatialRef( const OGRSpatialReference* poSRS )
1301 
1302 {
1303     PamInitialize();
1304 
1305     if( psPam == nullptr )
1306         return GDALDataset::SetSpatialRef( poSRS );
1307 
1308     if( psPam->poSRS )
1309         psPam->poSRS->Release();
1310     psPam->poSRS = poSRS ? poSRS->Clone() : nullptr;
1311     MarkPamDirty();
1312 
1313     return CE_None;
1314 }
1315 
1316 /************************************************************************/
1317 /*                          GetGeoTransform()                           */
1318 /************************************************************************/
1319 
GetGeoTransform(double * padfTransform)1320 CPLErr GDALPamDataset::GetGeoTransform( double * padfTransform )
1321 
1322 {
1323     if( psPam && psPam->bHaveGeoTransform )
1324     {
1325         memcpy( padfTransform, psPam->adfGeoTransform, sizeof(double) * 6 );
1326         return CE_None;
1327     }
1328 
1329     return GDALDataset::GetGeoTransform( padfTransform );
1330 }
1331 
1332 /************************************************************************/
1333 /*                          SetGeoTransform()                           */
1334 /************************************************************************/
1335 
SetGeoTransform(double * padfTransform)1336 CPLErr GDALPamDataset::SetGeoTransform( double * padfTransform )
1337 
1338 {
1339     PamInitialize();
1340 
1341     if( psPam )
1342     {
1343         MarkPamDirty();
1344         psPam->bHaveGeoTransform = TRUE;
1345         memcpy( psPam->adfGeoTransform, padfTransform, sizeof(double) * 6 );
1346         return( CE_None );
1347     }
1348 
1349     return GDALDataset::SetGeoTransform( padfTransform );
1350 }
1351 
1352 /************************************************************************/
1353 /*                            GetGCPCount()                             */
1354 /************************************************************************/
1355 
GetGCPCount()1356 int GDALPamDataset::GetGCPCount()
1357 
1358 {
1359     if( psPam && psPam->nGCPCount > 0 )
1360         return psPam->nGCPCount;
1361 
1362     return GDALDataset::GetGCPCount();
1363 }
1364 
1365 /************************************************************************/
1366 /*                          GetGCPSpatialRef()                          */
1367 /************************************************************************/
1368 
GetGCPSpatialRef() const1369 const OGRSpatialReference *GDALPamDataset::GetGCPSpatialRef() const
1370 
1371 {
1372     if( psPam && psPam->poGCP_SRS != nullptr )
1373         return psPam->poGCP_SRS;
1374 
1375     return GDALDataset::GetGCPSpatialRef();
1376 }
1377 
1378 /************************************************************************/
1379 /*                               GetGCPs()                              */
1380 /************************************************************************/
1381 
GetGCPs()1382 const GDAL_GCP *GDALPamDataset::GetGCPs()
1383 
1384 {
1385     if( psPam && psPam->nGCPCount > 0 )
1386         return psPam->pasGCPList;
1387 
1388     return GDALDataset::GetGCPs();
1389 }
1390 
1391 /************************************************************************/
1392 /*                              SetGCPs()                               */
1393 /************************************************************************/
1394 
SetGCPs(int nGCPCount,const GDAL_GCP * pasGCPList,const OGRSpatialReference * poGCP_SRS)1395 CPLErr GDALPamDataset::SetGCPs( int nGCPCount, const GDAL_GCP *pasGCPList,
1396                                 const OGRSpatialReference* poGCP_SRS )
1397 
1398 {
1399     PamInitialize();
1400 
1401     if( psPam )
1402     {
1403         if( psPam->poGCP_SRS )
1404             psPam->poGCP_SRS->Release();
1405         if( psPam->nGCPCount > 0 )
1406         {
1407             GDALDeinitGCPs( psPam->nGCPCount, psPam->pasGCPList );
1408             CPLFree( psPam->pasGCPList );
1409         }
1410 
1411         psPam->poGCP_SRS = poGCP_SRS ? poGCP_SRS->Clone() : nullptr;
1412         psPam->nGCPCount = nGCPCount;
1413         psPam->pasGCPList = GDALDuplicateGCPs( nGCPCount, pasGCPList );
1414 
1415         MarkPamDirty();
1416 
1417         return CE_None;
1418     }
1419 
1420     return GDALDataset::SetGCPs( nGCPCount, pasGCPList, poGCP_SRS );
1421 }
1422 
1423 /************************************************************************/
1424 /*                            SetMetadata()                             */
1425 /************************************************************************/
1426 
SetMetadata(char ** papszMetadata,const char * pszDomain)1427 CPLErr GDALPamDataset::SetMetadata( char **papszMetadata,
1428                                     const char *pszDomain )
1429 
1430 {
1431     PamInitialize();
1432 
1433     if( psPam )
1434     {
1435         psPam->bHasMetadata = TRUE;
1436         MarkPamDirty();
1437     }
1438 
1439     return GDALDataset::SetMetadata( papszMetadata, pszDomain );
1440 }
1441 
1442 /************************************************************************/
1443 /*                          SetMetadataItem()                           */
1444 /************************************************************************/
1445 
SetMetadataItem(const char * pszName,const char * pszValue,const char * pszDomain)1446 CPLErr GDALPamDataset::SetMetadataItem( const char *pszName,
1447                                         const char *pszValue,
1448                                         const char *pszDomain )
1449 
1450 {
1451     PamInitialize();
1452 
1453     if( psPam )
1454     {
1455         psPam->bHasMetadata = TRUE;
1456         MarkPamDirty();
1457     }
1458 
1459     return GDALDataset::SetMetadataItem( pszName, pszValue, pszDomain );
1460 }
1461 
1462 /************************************************************************/
1463 /*                          GetMetadataItem()                           */
1464 /************************************************************************/
1465 
GetMetadataItem(const char * pszName,const char * pszDomain)1466 const char *GDALPamDataset::GetMetadataItem( const char *pszName,
1467                                              const char *pszDomain )
1468 
1469 {
1470 /* -------------------------------------------------------------------- */
1471 /*      A request against the ProxyOverviewRequest is a special         */
1472 /*      mechanism to request an overview filename be allocated in       */
1473 /*      the proxy pool location.  The allocated name is saved as        */
1474 /*      metadata as well as being returned.                             */
1475 /* -------------------------------------------------------------------- */
1476     if( pszDomain != nullptr && EQUAL(pszDomain,"ProxyOverviewRequest") )
1477     {
1478         CPLString osPrelimOvr = GetDescription();
1479         osPrelimOvr += ":::OVR";
1480 
1481         const char *pszProxyOvrFilename = PamAllocateProxy( osPrelimOvr );
1482         if( pszProxyOvrFilename == nullptr )
1483             return nullptr;
1484 
1485         SetMetadataItem( "OVERVIEW_FILE", pszProxyOvrFilename, "OVERVIEWS" );
1486 
1487         return pszProxyOvrFilename;
1488     }
1489 
1490 /* -------------------------------------------------------------------- */
1491 /*      If the OVERVIEW_FILE metadata is requested, we intercept the    */
1492 /*      request in order to replace ":::BASE:::" with the path to       */
1493 /*      the physical file - if available.  This is primarily for the    */
1494 /*      purpose of managing subdataset overview filenames as being      */
1495 /*      relative to the physical file the subdataset comes              */
1496 /*      from. (#3287).                                                  */
1497 /* -------------------------------------------------------------------- */
1498     else if( pszDomain != nullptr
1499              && EQUAL(pszDomain,"OVERVIEWS")
1500              && EQUAL(pszName,"OVERVIEW_FILE") )
1501     {
1502         const char *pszOverviewFile =
1503             GDALDataset::GetMetadataItem( pszName, pszDomain );
1504 
1505         if( pszOverviewFile == nullptr
1506             || !STARTS_WITH_CI(pszOverviewFile, ":::BASE:::") )
1507             return pszOverviewFile;
1508 
1509         CPLString osPath;
1510 
1511         if( strlen(GetPhysicalFilename()) > 0 )
1512             osPath = CPLGetPath(GetPhysicalFilename());
1513         else
1514             osPath = CPLGetPath(GetDescription());
1515 
1516         return CPLFormFilename( osPath, pszOverviewFile + 10, nullptr );
1517     }
1518 
1519 /* -------------------------------------------------------------------- */
1520 /*      Everything else is a pass through.                              */
1521 /* -------------------------------------------------------------------- */
1522 
1523     return GDALDataset::GetMetadataItem( pszName, pszDomain );
1524 }
1525 
1526 /************************************************************************/
1527 /*                            GetMetadata()                             */
1528 /************************************************************************/
1529 
GetMetadata(const char * pszDomain)1530 char **GDALPamDataset::GetMetadata( const char *pszDomain )
1531 
1532 {
1533     // if( pszDomain == nullptr || !EQUAL(pszDomain,"ProxyOverviewRequest") )
1534     return GDALDataset::GetMetadata( pszDomain );
1535 }
1536 
1537 /************************************************************************/
1538 /*                             TryLoadAux()                             */
1539 /************************************************************************/
1540 
1541 //! @cond Doxygen_Suppress
TryLoadAux(char ** papszSiblingFiles)1542 CPLErr GDALPamDataset::TryLoadAux(char **papszSiblingFiles)
1543 
1544 {
1545 /* -------------------------------------------------------------------- */
1546 /*      Initialize PAM.                                                 */
1547 /* -------------------------------------------------------------------- */
1548     PamInitialize();
1549     if( psPam == nullptr )
1550         return CE_None;
1551 
1552 /* -------------------------------------------------------------------- */
1553 /*      What is the name of the physical file we are referencing?       */
1554 /*      We allow an override via the psPam->pszPhysicalFile item.       */
1555 /* -------------------------------------------------------------------- */
1556     const char *pszPhysicalFile = psPam->osPhysicalFilename;
1557 
1558     if( strlen(pszPhysicalFile) == 0 && GetDescription() != nullptr )
1559         pszPhysicalFile = GetDescription();
1560 
1561     if( strlen(pszPhysicalFile) == 0 )
1562         return CE_None;
1563 
1564     if( papszSiblingFiles && GDALCanReliablyUseSiblingFileList(pszPhysicalFile) )
1565     {
1566         CPLString osAuxFilename = CPLResetExtension( pszPhysicalFile, "aux");
1567         int iSibling = CSLFindString( papszSiblingFiles,
1568                                       CPLGetFilename(osAuxFilename) );
1569         if( iSibling < 0 )
1570         {
1571             osAuxFilename = pszPhysicalFile;
1572             osAuxFilename += ".aux";
1573             iSibling = CSLFindString( papszSiblingFiles,
1574                                       CPLGetFilename(osAuxFilename) );
1575             if( iSibling < 0 )
1576                 return CE_None;
1577         }
1578     }
1579 
1580 /* -------------------------------------------------------------------- */
1581 /*      Try to open .aux file.                                          */
1582 /* -------------------------------------------------------------------- */
1583     GDALDataset *poAuxDS = GDALFindAssociatedAuxFile( pszPhysicalFile,
1584                                                       GA_ReadOnly, this );
1585 
1586     if( poAuxDS == nullptr )
1587         return CE_None;
1588 
1589     psPam->osAuxFilename = poAuxDS->GetDescription();
1590 
1591 /* -------------------------------------------------------------------- */
1592 /*      Do we have an SRS on the aux file?                              */
1593 /* -------------------------------------------------------------------- */
1594     if( strlen(poAuxDS->GetProjectionRef()) > 0 )
1595         GDALPamDataset::SetProjection( poAuxDS->GetProjectionRef() );
1596 
1597 /* -------------------------------------------------------------------- */
1598 /*      Geotransform.                                                   */
1599 /* -------------------------------------------------------------------- */
1600     if( poAuxDS->GetGeoTransform( psPam->adfGeoTransform ) == CE_None )
1601         psPam->bHaveGeoTransform = TRUE;
1602 
1603 /* -------------------------------------------------------------------- */
1604 /*      GCPs                                                            */
1605 /* -------------------------------------------------------------------- */
1606     if( poAuxDS->GetGCPCount() > 0 )
1607     {
1608         psPam->nGCPCount = poAuxDS->GetGCPCount();
1609         psPam->pasGCPList = GDALDuplicateGCPs( psPam->nGCPCount,
1610                                                poAuxDS->GetGCPs() );
1611     }
1612 
1613 /* -------------------------------------------------------------------- */
1614 /*      Apply metadata. We likely ought to be merging this in rather    */
1615 /*      than overwriting everything that was there.                     */
1616 /* -------------------------------------------------------------------- */
1617     char **papszMD = poAuxDS->GetMetadata();
1618     if( CSLCount(papszMD) > 0 )
1619     {
1620         char **papszMerged =
1621             CSLMerge( CSLDuplicate(GetMetadata()), papszMD );
1622         GDALPamDataset::SetMetadata( papszMerged );
1623         CSLDestroy( papszMerged );
1624     }
1625 
1626     papszMD = poAuxDS->GetMetadata("XFORMS");
1627     if( CSLCount(papszMD) > 0 )
1628     {
1629         char **papszMerged =
1630             CSLMerge( CSLDuplicate(GetMetadata("XFORMS")), papszMD );
1631         GDALPamDataset::SetMetadata( papszMerged, "XFORMS" );
1632         CSLDestroy( papszMerged );
1633     }
1634 
1635 /* ==================================================================== */
1636 /*      Process bands.                                                  */
1637 /* ==================================================================== */
1638     for( int iBand = 0; iBand < poAuxDS->GetRasterCount(); iBand++ )
1639     {
1640         if( iBand >= GetRasterCount() )
1641             break;
1642 
1643         GDALRasterBand * const poAuxBand = poAuxDS->GetRasterBand( iBand+1 );
1644         GDALRasterBand * const poBand = GetRasterBand( iBand+1 );
1645 
1646         papszMD = poAuxBand->GetMetadata();
1647         if( CSLCount(papszMD) > 0 )
1648         {
1649             char **papszMerged =
1650                 CSLMerge( CSLDuplicate(poBand->GetMetadata()), papszMD );
1651             poBand->SetMetadata( papszMerged );
1652             CSLDestroy( papszMerged );
1653         }
1654 
1655         if( strlen(poAuxBand->GetDescription()) > 0 )
1656             poBand->SetDescription( poAuxBand->GetDescription() );
1657 
1658         if( poAuxBand->GetCategoryNames() != nullptr )
1659             poBand->SetCategoryNames( poAuxBand->GetCategoryNames() );
1660 
1661         if( poAuxBand->GetColorTable() != nullptr
1662             && poBand->GetColorTable() == nullptr )
1663             poBand->SetColorTable( poAuxBand->GetColorTable() );
1664 
1665         // histograms?
1666         double dfMin = 0.0;
1667         double dfMax = 0.0;
1668         int nBuckets = 0;
1669         GUIntBig *panHistogram=nullptr;
1670 
1671         if( poAuxBand->GetDefaultHistogram( &dfMin, &dfMax,
1672                                             &nBuckets, &panHistogram,
1673                                             FALSE, nullptr, nullptr ) == CE_None )
1674         {
1675             poBand->SetDefaultHistogram( dfMin, dfMax, nBuckets,
1676                                          panHistogram );
1677             CPLFree( panHistogram );
1678         }
1679 
1680         // RAT
1681         if( poAuxBand->GetDefaultRAT() != nullptr )
1682             poBand->SetDefaultRAT( poAuxBand->GetDefaultRAT() );
1683 
1684         // NoData
1685         int bSuccess = FALSE;
1686         const double dfNoDataValue = poAuxBand->GetNoDataValue( &bSuccess );
1687         if( bSuccess )
1688             poBand->SetNoDataValue( dfNoDataValue );
1689     }
1690 
1691     GDALClose( poAuxDS );
1692 
1693 /* -------------------------------------------------------------------- */
1694 /*      Mark PAM info as clean.                                         */
1695 /* -------------------------------------------------------------------- */
1696     nPamFlags &= ~GPF_DIRTY;
1697 
1698     return CE_Failure;
1699 }
1700 //! @endcond
1701 
1702 /************************************************************************/
1703 /*                        _GetProjectionRef()                           */
1704 /************************************************************************/
1705 
1706 //! @cond Doxygen_Suppress
_GetProjectionRef()1707 const char *GDALPamDataset::_GetProjectionRef()
1708 {
1709     return GetProjectionRefFromSpatialRef(GDALPamDataset::GetSpatialRef());
1710 }
1711 
1712 /************************************************************************/
1713 /*                          _SetProjection()                            */
1714 /************************************************************************/
1715 
_SetProjection(const char * pszProjection)1716 CPLErr GDALPamDataset::_SetProjection( const char *pszProjection )
1717 {
1718     if( pszProjection && pszProjection[0] != '\0' )
1719     {
1720         OGRSpatialReference oSRS;
1721         oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1722         if( oSRS.importFromWkt(pszProjection) != OGRERR_NONE )
1723         {
1724             return CE_Failure;
1725         }
1726         return GDALPamDataset::SetSpatialRef(&oSRS);
1727     }
1728     else
1729     {
1730         return GDALPamDataset::SetSpatialRef(nullptr);
1731     }
1732 }
1733 
1734 /************************************************************************/
1735 /*                        _GetGCPProjection()                           */
1736 /************************************************************************/
1737 
_GetGCPProjection()1738 const char *GDALPamDataset::_GetGCPProjection()
1739 {
1740     return GetGCPProjectionFromSpatialRef(GDALPamDataset::GetGCPSpatialRef());
1741 }
1742 
1743 /************************************************************************/
1744 /*                            _SetGCPs()                                */
1745 /************************************************************************/
1746 
_SetGCPs(int nGCPCount,const GDAL_GCP * pasGCPList,const char * pszGCPProjection)1747 CPLErr GDALPamDataset::_SetGCPs( int nGCPCount,
1748                              const GDAL_GCP *pasGCPList,
1749                              const char *pszGCPProjection )
1750 
1751 {
1752     if( pszGCPProjection && pszGCPProjection[0] != '\0' )
1753     {
1754         OGRSpatialReference oSRS;
1755         oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1756         if( oSRS.importFromWkt(pszGCPProjection) != OGRERR_NONE )
1757         {
1758             return CE_Failure;
1759         }
1760         return GDALPamDataset::SetGCPs(nGCPCount, pasGCPList, &oSRS);
1761     }
1762     else
1763     {
1764         return GDALPamDataset::SetGCPs(nGCPCount, pasGCPList,
1765                        static_cast<const OGRSpatialReference*>(nullptr));
1766     }
1767 }
1768 //! @endcond
1769 
1770 /************************************************************************/
1771 /*                          ClearStatistics()                           */
1772 /************************************************************************/
1773 
ClearStatistics()1774 void GDALPamDataset::ClearStatistics()
1775 {
1776     PamInitialize();
1777     if( !psPam )
1778         return;
1779     for( int i = 1; i <= nBands; ++i )
1780     {
1781         bool bChanged = false;
1782         GDALRasterBand* poBand = GetRasterBand(i);
1783         char** papszOldMD = poBand->GetMetadata();
1784         char** papszNewMD = nullptr;
1785         for( char** papszIter = papszOldMD; papszIter && papszIter[0]; ++papszIter )
1786         {
1787             if( STARTS_WITH_CI(*papszIter, "STATISTICS_") )
1788             {
1789                 MarkPamDirty();
1790                 bChanged = true;
1791             }
1792             else
1793             {
1794                 papszNewMD = CSLAddString(papszNewMD, *papszIter);
1795             }
1796         }
1797         if( bChanged )
1798         {
1799             poBand->SetMetadata(papszNewMD);
1800         }
1801         CSLDestroy(papszNewMD);
1802     }
1803 
1804     if( !psPam->oMapMDArrayStatistics.empty() )
1805     {
1806         MarkPamDirty();
1807         psPam->oMapMDArrayStatistics.clear();
1808     }
1809 }
1810 
1811 /************************************************************************/
1812 /*                       GetMDArrayStatistics()                         */
1813 /************************************************************************/
1814 
1815 //! @cond Doxygen_Suppress
GetMDArrayStatistics(const char * pszMDArrayId,bool * pbApprox,double * pdfMin,double * pdfMax,double * pdfMean,double * pdfStdDev,GUInt64 * pnValidCount)1816 bool GDALPamDataset::GetMDArrayStatistics( const char* pszMDArrayId,
1817                                            bool *pbApprox,
1818                                            double *pdfMin, double *pdfMax,
1819                                            double *pdfMean, double *pdfStdDev,
1820                                            GUInt64 *pnValidCount )
1821 {
1822     PamInitialize();
1823     if( !psPam )
1824     {
1825         return false;
1826     }
1827     auto oIter = psPam->oMapMDArrayStatistics.find(pszMDArrayId);
1828     if( oIter == psPam->oMapMDArrayStatistics.end() )
1829     {
1830         return false;
1831     }
1832     if( pbApprox )
1833         *pbApprox = oIter->second.bApproxStats;
1834     if( pdfMin )
1835         *pdfMin = oIter->second.dfMin;
1836     if( pdfMax )
1837         *pdfMax = oIter->second.dfMax;
1838     if( pdfMean )
1839         *pdfMean = oIter->second.dfMean;
1840     if( pdfStdDev )
1841         *pdfStdDev = oIter->second.dfStdDev;
1842     if( pnValidCount )
1843         *pnValidCount = oIter->second.nValidCount;
1844     return true;
1845 }
1846 
1847 /************************************************************************/
1848 /*                      StoreMDArrayStatistics()                        */
1849 /************************************************************************/
1850 
1851 //! @cond Doxygen_Suppress
StoreMDArrayStatistics(const char * pszMDArrayId,bool bApproxStats,double dfMin,double dfMax,double dfMean,double dfStdDev,GUInt64 nValidCount)1852 void GDALPamDataset::StoreMDArrayStatistics( const char* pszMDArrayId,
1853                                  bool bApproxStats,
1854                                  double dfMin, double dfMax,
1855                                  double dfMean, double dfStdDev,
1856                                  GUInt64 nValidCount )
1857 {
1858     PamInitialize();
1859     if( !psPam )
1860         return;
1861     MarkPamDirty();
1862     GDALDatasetPamInfo::Statistics sStats;
1863     sStats.bApproxStats = bApproxStats;
1864     sStats.dfMin = dfMin;
1865     sStats.dfMax = dfMax;
1866     sStats.dfMean = dfMean;
1867     sStats.dfStdDev = dfStdDev;
1868     sStats.nValidCount = nValidCount;
1869     psPam->oMapMDArrayStatistics[pszMDArrayId] = sStats;
1870 }
1871 //! @endcond
1872