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