1 /******************************************************************************
2  *
3  * Project:  Sentinel SAFE products
4  * Purpose:  Sentinel Products (manifest.safe) driver
5  * Author:   Delfim Rego, delfimrego@gmail.com
6  *
7  ******************************************************************************
8  * Copyright (c) 2015, Delfim Rego <delfimrego@gmail.com>
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included
18  * in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  * DEALINGS IN THE SOFTWARE.
27  ****************************************************************************/
28 
29 #include "cpl_minixml.h"
30 #include "cpl_string.h"
31 #include "gdal_frmts.h"
32 #include "gdal_pam.h"
33 #include "ogr_spatialref.h"
34 #include <set>
35 #include <map>
36 #include <utility>
37 #include <vector>
38 
39 CPL_CVSID("$Id: safedataset.cpp b9255d737df77fbaf69edabebaf441cc2aaf3bdd 2020-08-31 22:06:45 +0200 Even Rouault $")
40 
41 /************************************************************************/
42 /* ==================================================================== */
43 /*                               SAFEDataset                            */
44 /* ==================================================================== */
45 /************************************************************************/
46 
47 class SAFEDataset final: public GDALPamDataset
48 {
49     CPLXMLNode *psManifest;
50 
51     int           nGCPCount;
52     GDAL_GCP     *pasGCPList;
53     char         *pszGCPProjection;
54     char        **papszSubDatasets;
55     char         *pszProjection;
56     double        adfGeoTransform[6];
57     bool          bHaveGeoTransform;
58 
59     char        **papszExtraFiles;
60 
61   protected:
62     virtual int         CloseDependentDatasets() override;
63 
64     static CPLXMLNode * GetMetaDataObject(CPLXMLNode *, const char *);
65 
66     static CPLXMLNode * GetDataObject(CPLXMLNode *, const char *);
67     static CPLXMLNode * GetDataObject(CPLXMLNode *, CPLXMLNode *, const char *);
68 
69     static void AddSubDataset(SAFEDataset *poDS, int iDSNum, CPLString osName, CPLString osDesc);
70 
71   public:
72             SAFEDataset();
73     virtual ~SAFEDataset();
74 
75     virtual int    GetGCPCount() override;
76     virtual const char *_GetGCPProjection() override;
GetGCPSpatialRef() const77     const OGRSpatialReference* GetGCPSpatialRef() const override {
78         return GetGCPSpatialRefFromOldGetGCPProjection();
79     }
80     virtual const GDAL_GCP *GetGCPs() override;
81 
82     virtual const char *_GetProjectionRef(void) override;
GetSpatialRef() const83     const OGRSpatialReference* GetSpatialRef() const override {
84         return GetSpatialRefFromOldGetProjectionRef();
85     }
86     virtual CPLErr GetGeoTransform( double * ) override;
87 
88 #ifdef notdef
89     virtual char      **GetMetadataDomainList();
90     virtual char **GetMetadata( const char * pszDomain = "" );
91 #endif
92     virtual char **GetFileList(void) override;
93 
94     static GDALDataset *Open( GDALOpenInfo * );
95     static int Identify( GDALOpenInfo * );
96 
GetManifest()97     CPLXMLNode *GetManifest() { return psManifest; }
98 };
99 
100 /************************************************************************/
101 /* ==================================================================== */
102 /*                    SAFERasterBand                                    */
103 /* ==================================================================== */
104 /************************************************************************/
105 
106 class SAFERasterBand final: public GDALPamRasterBand
107 {
108     GDALDataset     *poBandFile;
109 
110   public:
111             SAFERasterBand( SAFEDataset *poDSIn,
112                                GDALDataType eDataTypeIn,
113                                const char *pszSwath,
114                                const char *pszPol,
115                                GDALDataset *poBandFile );
116     virtual     ~SAFERasterBand();
117 
118     virtual CPLErr IReadBlock( int, int, void * ) override;
119 
120     static GDALDataset *Open( GDALOpenInfo * );
121 };
122 
123 /************************************************************************/
124 /*                            SAFERasterBand                            */
125 /************************************************************************/
126 
SAFERasterBand(SAFEDataset * poDSIn,GDALDataType eDataTypeIn,const char * pszSwath,const char * pszPolarisation,GDALDataset * poBandFileIn)127 SAFERasterBand::SAFERasterBand( SAFEDataset *poDSIn,
128                                 GDALDataType eDataTypeIn,
129                                 const char *pszSwath,
130                                 const char *pszPolarisation,
131                                 GDALDataset *poBandFileIn ) :
132     poBandFile(poBandFileIn)
133 {
134     poDS = poDSIn;
135 
136     GDALRasterBand *poSrcBand = poBandFile->GetRasterBand( 1 );
137 
138     poSrcBand->GetBlockSize( &nBlockXSize, &nBlockYSize );
139 
140     eDataType = eDataTypeIn;
141 
142     if( *pszSwath != '\0' ) {
143         SetMetadataItem( "SWATH", pszSwath );
144     }
145     if( *pszPolarisation != '\0' ) {
146         SetMetadataItem( "POLARISATION", pszPolarisation );
147     }
148 }
149 
150 /************************************************************************/
151 /*                            RSRasterBand()                            */
152 /************************************************************************/
153 
~SAFERasterBand()154 SAFERasterBand::~SAFERasterBand()
155 
156 {
157     if( poBandFile != nullptr )
158         GDALClose( reinterpret_cast<GDALRasterBandH>( poBandFile ) );
159 }
160 
161 /************************************************************************/
162 /*                             IReadBlock()                             */
163 /************************************************************************/
164 
IReadBlock(int nBlockXOff,int nBlockYOff,void * pImage)165 CPLErr SAFERasterBand::IReadBlock( int nBlockXOff, int nBlockYOff,
166                                   void * pImage )
167 
168 {
169 /* -------------------------------------------------------------------- */
170 /*      If the last strip is partial, we need to avoid                  */
171 /*      over-requesting.  We also need to initialize the extra part     */
172 /*      of the block to zero.                                           */
173 /* -------------------------------------------------------------------- */
174     int nRequestYSize;
175     if( (nBlockYOff + 1) * nBlockYSize > nRasterYSize )
176     {
177         nRequestYSize = nRasterYSize - nBlockYOff * nBlockYSize;
178         memset( pImage, 0, (GDALGetDataTypeSize( eDataType ) / 8) *
179             nBlockXSize * nBlockYSize );
180     }
181     else
182     {
183         nRequestYSize = nBlockYSize;
184     }
185 
186 /*-------------------------------------------------------------------- */
187 /*      If the input imagery is tiled, also need to avoid over-        */
188 /*      requesting in the X-direction.                                 */
189 /* ------------------------------------------------------------------- */
190     int nRequestXSize;
191     if( (nBlockXOff + 1) * nBlockXSize > nRasterXSize )
192     {
193         nRequestXSize = nRasterXSize - nBlockXOff * nBlockXSize;
194         memset( pImage, 0, (GDALGetDataTypeSize( eDataType ) / 8) *
195             nBlockXSize * nBlockYSize );
196     }
197     else
198     {
199         nRequestXSize = nBlockXSize;
200     }
201     if( eDataType == GDT_CInt16 && poBandFile->GetRasterCount() == 2 )
202         return
203             poBandFile->RasterIO( GF_Read,
204                                   nBlockXOff * nBlockXSize,
205                                   nBlockYOff * nBlockYSize,
206                                   nRequestXSize, nRequestYSize,
207                                   pImage, nRequestXSize, nRequestYSize,
208                                   GDT_Int16,
209                                   2, nullptr, 4, nBlockXSize * 4, 2, nullptr );
210 
211 /* -------------------------------------------------------------------- */
212 /*      File has one sample marked as sample format void, a 32bits.     */
213 /* -------------------------------------------------------------------- */
214     else if( eDataType == GDT_CInt16 && poBandFile->GetRasterCount() == 1 )
215     {
216         CPLErr eErr
217             = poBandFile->RasterIO( GF_Read,
218                                   nBlockXOff * nBlockXSize,
219                                   nBlockYOff * nBlockYSize,
220                                   nRequestXSize, nRequestYSize,
221                                   pImage, nRequestXSize, nRequestYSize,
222                                   GDT_UInt32,
223                                   1, nullptr, 4, nBlockXSize * 4, 0, nullptr );
224 
225 #ifdef CPL_LSB
226         /* First, undo the 32bit swap. */
227         GDALSwapWords( pImage, 4, nBlockXSize * nBlockYSize, 4 );
228 
229         /* Then apply 16 bit swap. */
230         GDALSwapWords( pImage, 2, nBlockXSize * nBlockYSize * 2, 2 );
231 #endif
232 
233         return eErr;
234     }
235 
236 /* -------------------------------------------------------------------- */
237 /*      The 16bit case is straight forward.  The underlying file        */
238 /*      looks like a 16bit unsigned data too.                           */
239 /* -------------------------------------------------------------------- */
240     else if( eDataType == GDT_UInt16 )
241         return
242             poBandFile->RasterIO( GF_Read,
243                                   nBlockXOff * nBlockXSize,
244                                   nBlockYOff * nBlockYSize,
245                                   nRequestXSize, nRequestYSize,
246                                   pImage, nRequestXSize, nRequestYSize,
247                                   GDT_UInt16,
248                                   1, nullptr, 2, nBlockXSize * 2, 0, nullptr );
249     else if ( eDataType == GDT_Byte )
250         return
251             poBandFile->RasterIO( GF_Read,
252                                   nBlockXOff * nBlockXSize,
253                                   nBlockYOff * nBlockYSize,
254                                   nRequestXSize, nRequestYSize,
255                                   pImage, nRequestXSize, nRequestYSize,
256                                   GDT_Byte,
257                                   1, nullptr, 1, nBlockXSize, 0, nullptr );
258 
259     CPLAssert( false );
260     return CE_Failure;
261 }
262 
263 /************************************************************************/
264 /* ==================================================================== */
265 /*                              SAFEDataset                              */
266 /* ==================================================================== */
267 /************************************************************************/
268 
269 /************************************************************************/
270 /*                             SAFEDataset()                            */
271 /************************************************************************/
272 
SAFEDataset()273 SAFEDataset::SAFEDataset() :
274     psManifest(nullptr),
275     nGCPCount(0),
276     pasGCPList(nullptr),
277     pszGCPProjection(CPLStrdup("")),
278     papszSubDatasets(nullptr),
279     pszProjection(CPLStrdup("")),
280     bHaveGeoTransform(false),
281     papszExtraFiles(nullptr)
282 {
283     adfGeoTransform[0] = 0.0;
284     adfGeoTransform[1] = 1.0;
285     adfGeoTransform[2] = 0.0;
286     adfGeoTransform[3] = 0.0;
287     adfGeoTransform[4] = 0.0;
288     adfGeoTransform[5] = 1.0;
289 }
290 
291 /************************************************************************/
292 /*                            ~SAFEDataset()                            */
293 /************************************************************************/
294 
~SAFEDataset()295 SAFEDataset::~SAFEDataset()
296 
297 {
298     SAFEDataset::FlushCache();
299 
300     CPLDestroyXMLNode( psManifest );
301     CPLFree( pszProjection );
302 
303     CPLFree( pszGCPProjection );
304     if( nGCPCount > 0 )
305     {
306         GDALDeinitGCPs( nGCPCount, pasGCPList );
307         CPLFree( pasGCPList );
308     }
309 
310     SAFEDataset::CloseDependentDatasets();
311 
312     CSLDestroy( papszSubDatasets );
313     CSLDestroy( papszExtraFiles );
314 }
315 
316 /************************************************************************/
317 /*                      CloseDependentDatasets()                        */
318 /************************************************************************/
319 
CloseDependentDatasets()320 int SAFEDataset::CloseDependentDatasets()
321 {
322     int bHasDroppedRef = GDALPamDataset::CloseDependentDatasets();
323 
324     if (nBands != 0)
325         bHasDroppedRef = TRUE;
326 
327     for( int iBand = 0; iBand < nBands; iBand++ )
328     {
329        delete papoBands[iBand];
330     }
331     nBands = 0;
332 
333     return bHasDroppedRef;
334 }
335 
336 /************************************************************************/
337 /*                      GetMetaDataObject()                             */
338 /************************************************************************/
339 
GetMetaDataObject(CPLXMLNode * psMetaDataObjects,const char * metadataObjectId)340 CPLXMLNode * SAFEDataset::GetMetaDataObject(
341     CPLXMLNode *psMetaDataObjects, const char *metadataObjectId)
342 {
343 /* -------------------------------------------------------------------- */
344 /*      Look for DataObject Element by ID.                              */
345 /* -------------------------------------------------------------------- */
346     for( CPLXMLNode *psMDO = psMetaDataObjects->psChild;
347          psMDO != nullptr;
348          psMDO = psMDO->psNext )
349     {
350         if( psMDO->eType != CXT_Element
351             || !(EQUAL(psMDO->pszValue,"metadataObject")) ) {
352             continue;
353         }
354 
355         const char *pszElementID = CPLGetXMLValue( psMDO, "ID", "" );
356 
357         if (EQUAL(pszElementID, metadataObjectId)) {
358             return psMDO;
359         }
360     }
361 
362     CPLError( CE_Warning, CPLE_AppDefined,
363               "MetadataObject not found with ID=%s",
364               metadataObjectId);
365 
366     return nullptr;
367 }
368 
369 /************************************************************************/
370 /*                      GetDataObject()                                 */
371 /************************************************************************/
372 
GetDataObject(CPLXMLNode * psDataObjects,const char * dataObjectId)373 CPLXMLNode * SAFEDataset::GetDataObject(
374     CPLXMLNode *psDataObjects, const char *dataObjectId)
375 {
376 /* -------------------------------------------------------------------- */
377 /*      Look for DataObject Element by ID.                              */
378 /* -------------------------------------------------------------------- */
379     for( CPLXMLNode *psDO = psDataObjects->psChild;
380          psDO != nullptr;
381          psDO = psDO->psNext )
382     {
383         if( psDO->eType != CXT_Element
384             || !(EQUAL(psDO->pszValue,"dataObject")) ) {
385             continue;
386         }
387 
388         const char *pszElementID = CPLGetXMLValue( psDO, "ID", "" );
389 
390         if (EQUAL(pszElementID, dataObjectId)) {
391             return psDO;
392         }
393     }
394 
395     CPLError( CE_Warning, CPLE_AppDefined,
396               "DataObject not found with ID=%s",
397               dataObjectId);
398 
399     return nullptr;
400 }
401 
GetDataObject(CPLXMLNode * psMetaDataObjects,CPLXMLNode * psDataObjects,const char * metadataObjectId)402 CPLXMLNode * SAFEDataset::GetDataObject(
403     CPLXMLNode *psMetaDataObjects, CPLXMLNode *psDataObjects,
404     const char *metadataObjectId)
405 {
406 /* -------------------------------------------------------------------- */
407 /*      Look for MetadataObject Element by ID.                          */
408 /* -------------------------------------------------------------------- */
409     CPLXMLNode *psMDO = SAFEDataset::GetMetaDataObject(
410             psMetaDataObjects, metadataObjectId);
411 
412     if (psMDO!=nullptr) {
413         const char *dataObjectId = CPLGetXMLValue(
414             psMDO, "dataObjectPointer.dataObjectID", "" );
415         if( *dataObjectId != '\0' ) {
416             return SAFEDataset::GetDataObject(psDataObjects, dataObjectId);
417         }
418     }
419 
420     CPLError( CE_Warning, CPLE_AppDefined,
421               "DataObject not found with MetaID=%s",
422               metadataObjectId);
423 
424     return nullptr;
425 }
426 
427 /************************************************************************/
428 /*                            GetFileList()                             */
429 /************************************************************************/
430 
GetFileList()431 char **SAFEDataset::GetFileList()
432 
433 {
434     char **papszFileList = GDALPamDataset::GetFileList();
435 
436     papszFileList = CSLInsertStrings( papszFileList, -1, papszExtraFiles );
437 
438     return papszFileList;
439 }
440 
441 /************************************************************************/
442 /*                             Identify()                               */
443 /************************************************************************/
444 
Identify(GDALOpenInfo * poOpenInfo)445 int SAFEDataset::Identify( GDALOpenInfo *poOpenInfo )
446 {
447     /* Check for the case where we're trying to read the calibrated data: */
448     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "SENTINEL1_CALIB:")) {
449         return TRUE;
450     }
451 
452     /* Check for the case where we're trying to read the subdatasets: */
453     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "SENTINEL1_DS:")) {
454         return TRUE;
455     }
456 
457     /* Check for directory access when there is a manifest.safe file in the
458        directory. */
459     if( poOpenInfo->bIsDirectory )
460     {
461         VSIStatBufL sStat;
462 
463         CPLString osMDFilename =
464             CPLFormCIFilename( poOpenInfo->pszFilename, "manifest.safe", nullptr );
465 
466         if( VSIStatL( osMDFilename, &sStat ) == 0 && VSI_ISREG(sStat.st_mode) )
467         {
468             GDALOpenInfo oOpenInfo( osMDFilename, GA_ReadOnly, nullptr );
469             return Identify(&oOpenInfo);
470         }
471 
472         return FALSE;
473     }
474 
475     /* otherwise, do our normal stuff */
476     if( !EQUAL(CPLGetFilename(poOpenInfo->pszFilename), "manifest.safe") )
477         return FALSE;
478 
479     if( poOpenInfo->nHeaderBytes < 100 )
480         return FALSE;
481 
482     if( strstr((const char *) poOpenInfo->pabyHeader, "<xfdu:XFDU" ) == nullptr)
483         return FALSE;
484 
485     // This driver doesn't handle Sentinel-2 data
486     if( strstr((const char *) poOpenInfo->pabyHeader, "sentinel-2" ) != nullptr)
487         return FALSE;
488 
489     return TRUE;
490 }
491 
492 /************************************************************************/
493 /*                                Open()                                */
494 /************************************************************************/
495 
Open(GDALOpenInfo * poOpenInfo)496 GDALDataset *SAFEDataset::Open( GDALOpenInfo * poOpenInfo )
497 
498 {
499 /* -------------------------------------------------------------------- */
500 /*      Is this a SENTINEL-1 manifest.safe definition?                  */
501 /* -------------------------------------------------------------------- */
502     if ( !SAFEDataset::Identify( poOpenInfo ) ) {
503         return nullptr;
504     }
505 
506 /* -------------------------------------------------------------------- */
507 /*        Get subdataset information, if relevant                       */
508 /* -------------------------------------------------------------------- */
509     CPLString osMDFilename;
510 
511     //Subdataset 1st level selection (ex: for swath selection)
512     CPLString osSelectedSubDS1;
513     //Subdataset 2nd level selection (ex: for polarisation selection)
514     CPLString osSelectedSubDS2;
515 
516     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "SENTINEL1_DS:"))
517     {
518       osMDFilename = poOpenInfo->pszFilename + strlen("SENTINEL1_DS:");
519       size_t nPosColon = osMDFilename.find(':');
520       if (nPosColon == std::string::npos || nPosColon == 0)
521       {
522           CPLError(CE_Failure, CPLE_AppDefined, "Invalid syntax for SENTINEL1_DS:");
523           return nullptr;
524       }
525       osSelectedSubDS1 = osMDFilename.substr(nPosColon+1);
526       osMDFilename.resize( nPosColon );
527 
528       size_t nPosUnderscore = osSelectedSubDS1.find('_');
529       if (nPosUnderscore != std::string::npos && nPosUnderscore != 0)
530       {
531           osSelectedSubDS2 = osSelectedSubDS1.substr(nPosUnderscore+1);
532           osSelectedSubDS1.resize( nPosUnderscore );
533       }
534 
535       //update directory check:
536       VSIStatBufL  sStat;
537       if( VSIStatL( osMDFilename.c_str(), &sStat ) == 0 )
538           poOpenInfo->bIsDirectory = VSI_ISDIR( sStat.st_mode );
539     }
540     else
541     {
542       osMDFilename = poOpenInfo->pszFilename;
543     }
544 
545     if( poOpenInfo->bIsDirectory )
546     {
547         osMDFilename =
548             CPLFormCIFilename( osMDFilename.c_str(), "manifest.safe", nullptr );
549     }
550 
551 /* -------------------------------------------------------------------- */
552 /*      Ingest the manifest.safe file.                                  */
553 /* -------------------------------------------------------------------- */
554     //TODO REMOVE CPLXMLNode *psImageAttributes, *psImageGenerationParameters;
555     CPLXMLNode *psManifest = CPLParseXMLFile( osMDFilename );
556     if( psManifest == nullptr )
557         return nullptr;
558 
559     CPLString osPath(CPLGetPath( osMDFilename ));
560 
561 /* -------------------------------------------------------------------- */
562 /*      Confirm the requested access is supported.                      */
563 /* -------------------------------------------------------------------- */
564     if( poOpenInfo->eAccess == GA_Update )
565     {
566         CPLDestroyXMLNode( psManifest );
567         CPLError( CE_Failure, CPLE_NotSupported,
568                   "The SAFE driver does not support update access to existing"
569                   " datasets.\n" );
570         return nullptr;
571     }
572 
573 /* -------------------------------------------------------------------- */
574 /*      Get contentUnit parent element.                                 */
575 /* -------------------------------------------------------------------- */
576     CPLXMLNode *psContentUnits = CPLGetXMLNode(
577             psManifest,
578             "=xfdu:XFDU.informationPackageMap.xfdu:contentUnit" );
579     if( psContentUnits == nullptr )
580     {
581         CPLDestroyXMLNode( psManifest );
582         CPLError( CE_Failure, CPLE_OpenFailed,
583                   "Failed to find <xfdu:XFDU><informationPackageMap>"
584                   "<xfdu:contentUnit> in manifest file." );
585         return nullptr;
586     }
587 
588 /* -------------------------------------------------------------------- */
589 /*      Get Metadata Objects element.                                   */
590 /* -------------------------------------------------------------------- */
591     CPLXMLNode *psMetaDataObjects
592         = CPLGetXMLNode( psManifest, "=xfdu:XFDU.metadataSection" );
593     if( psMetaDataObjects == nullptr )
594     {
595         CPLDestroyXMLNode( psManifest );
596         CPLError( CE_Failure, CPLE_OpenFailed,
597                   "Failed to find <xfdu:XFDU><metadataSection>"
598                   "in manifest file." );
599         return nullptr;
600     }
601 
602 /* -------------------------------------------------------------------- */
603 /*      Get Data Objects element.                                       */
604 /* -------------------------------------------------------------------- */
605     CPLXMLNode *psDataObjects
606         = CPLGetXMLNode( psManifest, "=xfdu:XFDU.dataObjectSection" );
607     if( psDataObjects == nullptr )
608     {
609         CPLDestroyXMLNode( psManifest );
610         CPLError( CE_Failure, CPLE_OpenFailed,
611                 "Failed to find <xfdu:XFDU><dataObjectSection> in document." );
612         return nullptr;
613     }
614 
615 /* -------------------------------------------------------------------- */
616 /*      Create the dataset.                                             */
617 /* -------------------------------------------------------------------- */
618     SAFEDataset *poDS = new SAFEDataset();
619 
620     poDS->psManifest = psManifest;
621 
622 /* -------------------------------------------------------------------- */
623 /*      Look for "Measurement Data Unit" contentUnit elements.          */
624 /* -------------------------------------------------------------------- */
625     CPLXMLNode *psAnnotation = nullptr;
626     //Map with all measures aggregated by swath
627     std::map<CPLString, std::set<CPLString> > oMapSwaths2Pols;
628     std::vector<std::pair<CPLString, CPLString>> oWVImages;
629 
630     for( CPLXMLNode *psContentUnit = psContentUnits->psChild;
631          psContentUnit != nullptr;
632          psContentUnit = psContentUnit->psNext )
633     {
634         if( psContentUnit->eType != CXT_Element
635             || !(EQUAL(psContentUnit->pszValue,"xfdu:contentUnit")) ) {
636             continue;
637         }
638 
639         const char *pszUnitType = CPLGetXMLValue( psContentUnit,
640                 "unitType", "" );
641 
642         const char *pszAnnotation  = nullptr;
643         const char *pszCalibration = nullptr;
644         const char *pszMeasurement = nullptr;
645 
646         if ( EQUAL(pszUnitType, "Measurement Data Unit") ) {
647             /* Get dmdID and dataObjectID */
648             const char *pszDmdID = CPLGetXMLValue(psContentUnit, "dmdID", "");
649 
650             const char *pszDataObjectID = CPLGetXMLValue(
651                 psContentUnit,
652                 "dataObjectPointer.dataObjectID", "" );
653             if( *pszDataObjectID == '\0' || *pszDmdID == '\0' ) {
654                 continue;
655             }
656 
657             CPLXMLNode *psDataObject = SAFEDataset::GetDataObject(
658                     psDataObjects, pszDataObjectID);
659 
660             const char *pszRepId = CPLGetXMLValue( psDataObject, "repID", "" );
661             if ( !EQUAL(pszRepId, "s1Level1MeasurementSchema") ) {
662                 continue;
663             }
664             pszMeasurement = CPLGetXMLValue(
665                     psDataObject, "byteStream.fileLocation.href", "");
666             if( *pszMeasurement == '\0' ) {
667                 continue;
668             }
669 
670             char** papszTokens = CSLTokenizeString2( pszDmdID, " ",
671                 CSLT_ALLOWEMPTYTOKENS | CSLT_STRIPLEADSPACES
672                 | CSLT_STRIPENDSPACES );
673 
674             for( int j = 0; j < CSLCount( papszTokens ); j++ ) {
675                 const char* pszId = papszTokens[j];
676                 if( *pszId == '\0' ) {
677                     continue;
678                 }
679 
680                 //Map the metadata ID to the object element
681                 CPLXMLNode *psDO = SAFEDataset::GetDataObject(
682                         psMetaDataObjects, psDataObjects, pszId);
683 
684                 if (psDO == nullptr) {
685                     continue;
686                 }
687 
688                 //check object type
689                 pszRepId = CPLGetXMLValue( psDO, "repID", "" );
690 
691                 if( EQUAL(pszRepId, "s1Level1ProductSchema") )
692                 {
693                     /* Get annotation filename */
694                     pszAnnotation = CPLGetXMLValue(
695                             psDO, "byteStream.fileLocation.href", "");
696                     if( *pszAnnotation == '\0' )
697                     {
698                         continue;
699                     }
700                 }
701                 else if( EQUAL(pszRepId, "s1Level1CalibrationSchema") )
702                 {
703                     pszCalibration = CPLGetXMLValue(
704                             psDO, "byteStream.fileLocation.href", "");
705                     if( *pszCalibration == '\0' ) {
706                         continue;
707                     }
708                 }
709                 else
710                 {
711                     continue;
712                 }
713             }
714 
715             CSLDestroy(papszTokens);
716 
717             if (pszAnnotation == nullptr || pszCalibration == nullptr ) {
718                 continue;
719             }
720 
721             //open Annotation XML file
722             CPLString osAnnotationFilePath = CPLFormFilename( osPath,
723                                                        pszAnnotation, nullptr );
724             if( psAnnotation )
725                 CPLDestroyXMLNode(psAnnotation);
726             psAnnotation = CPLParseXMLFile( osAnnotationFilePath );
727             if( psAnnotation == nullptr )
728                 continue;
729 
730 /* -------------------------------------------------------------------- */
731 /*      Get overall image information.                                  */
732 /* -------------------------------------------------------------------- */
733             poDS->nRasterXSize =
734                 atoi(CPLGetXMLValue( psAnnotation,
735                     "=product.imageAnnotation.imageInformation.numberOfSamples",
736                     "-1" ));
737             poDS->nRasterYSize =
738                 atoi(CPLGetXMLValue( psAnnotation,
739                     "=product.imageAnnotation.imageInformation.numberOfLines",
740                     "-1" ));
741             if (poDS->nRasterXSize <= 1 || poDS->nRasterYSize <= 1) {
742                 CPLError( CE_Failure, CPLE_OpenFailed,
743                     "Non-sane raster dimensions provided in manifest.safe. "
744                     "If this is a valid SENTINEL-1 scene, please contact your "
745                     "data provider for a corrected dataset." );
746                 delete poDS;
747                 CPLDestroyXMLNode(psAnnotation);
748                 return nullptr;
749             }
750 
751             CPLString osProductType = CPLGetXMLValue(
752                 psAnnotation, "=product.adsHeader.productType", "UNK" );
753             CPLString osMissionId = CPLGetXMLValue(
754                 psAnnotation, "=product.adsHeader.missionId", "UNK" );
755             CPLString osPolarisation = CPLGetXMLValue(
756                 psAnnotation, "=product.adsHeader.polarisation", "UNK" );
757             CPLString osMode = CPLGetXMLValue(
758                 psAnnotation, "=product.adsHeader.mode", "UNK" );
759             CPLString osSwath = CPLGetXMLValue(
760                 psAnnotation, "=product.adsHeader.swath", "UNK" );
761             CPLString osImageNumber = CPLGetXMLValue(
762                 psAnnotation, "=product.adsHeader.imageNumber", "" );
763 
764             if( osMode == "WV" )
765             {
766                 if( (!osSelectedSubDS1.empty() &&
767                      osSelectedSubDS1 != "WV") ||
768                     (!osSelectedSubDS2.empty() &&
769                      osSelectedSubDS2 != osImageNumber) )
770                 {
771                     continue;
772                 }
773                 if( osSelectedSubDS1.empty() )
774                 {
775                     oWVImages.push_back(std::pair<CPLString, CPLString>(osSwath, osImageNumber));
776                     continue;
777                 }
778             }
779             else
780             {
781                 oMapSwaths2Pols[osSwath].insert(osPolarisation);
782 
783                 if (osSelectedSubDS1.empty()) {
784                     // If not subdataset was selected,
785                     // open the first one we can find.
786                     osSelectedSubDS1 = osSwath;
787                 }
788 
789                 if (!EQUAL(osSelectedSubDS1.c_str(), osSwath.c_str())) {
790                     //do not mix swath, otherwise it does not work for SLC products
791                     continue;
792                 }
793 
794                 if (!osSelectedSubDS2.empty()
795                     && (osSelectedSubDS2.find(osPolarisation)== std::string::npos)) {
796                     // Add only selected polarisations.
797                     continue;
798                 }
799             }
800 
801             poDS->SetMetadataItem("PRODUCT_TYPE", osProductType.c_str());
802             poDS->SetMetadataItem("MISSION_ID", osMissionId.c_str());
803             poDS->SetMetadataItem("MODE", osMode.c_str());
804             poDS->SetMetadataItem("SWATH", osSwath.c_str());
805 
806 /* -------------------------------------------------------------------- */
807 /*      Get dataType (so we can recognize complex data), and the        */
808 /*      bitsPerSample.                                                  */
809 /* -------------------------------------------------------------------- */
810 
811             const char *pszDataType = CPLGetXMLValue(
812                 psAnnotation,
813                 "=product.imageAnnotation.imageInformation.outputPixels",
814                 "" );
815 
816             GDALDataType eDataType;
817             if( EQUAL(pszDataType,"16 bit Signed Integer") )
818                 eDataType = GDT_CInt16;
819             else if( EQUAL(pszDataType,"16 bit Unsigned Integer") )
820                 eDataType = GDT_UInt16;
821             else
822             {
823                 delete poDS;
824                 CPLError( CE_Failure, CPLE_AppDefined,
825                           "dataType=%s: not a supported configuration.",
826                           pszDataType );
827                 CPLDestroyXMLNode(psAnnotation);
828                 return nullptr;
829             }
830 
831             /* Extract pixel spacing information */
832             const char *pszPixelSpacing = CPLGetXMLValue(
833                 psAnnotation,
834                 "=product.imageAnnotation.imageInformation.rangePixelSpacing",
835                 "UNK" );
836             poDS->SetMetadataItem( "PIXEL_SPACING", pszPixelSpacing );
837 
838             const char *pszLineSpacing = CPLGetXMLValue(
839                 psAnnotation,
840                 "=product.imageAnnotation.imageInformation.azimuthPixelSpacing",
841                 "UNK" );
842             poDS->SetMetadataItem( "LINE_SPACING", pszLineSpacing );
843 
844 /* -------------------------------------------------------------------- */
845 /*      Form full filename (path of manifest.safe + measurement file).  */
846 /* -------------------------------------------------------------------- */
847             char *pszFullname =
848                 CPLStrdup(CPLFormFilename( osPath, pszMeasurement, nullptr ));
849 
850 /* -------------------------------------------------------------------- */
851 /*      Try and open the file.                                          */
852 /* -------------------------------------------------------------------- */
853             GDALDataset *poBandFile = reinterpret_cast<GDALDataset *>(
854                 GDALOpen( pszFullname, GA_ReadOnly ) );
855             if( poBandFile == nullptr )
856             {
857                 // NOP
858             }
859             else
860             if (poBandFile->GetRasterCount() == 0)
861             {
862                 GDALClose( (GDALRasterBandH) poBandFile );
863              }
864             else {
865                 poDS->papszExtraFiles = CSLAddString( poDS->papszExtraFiles,
866                                                   osAnnotationFilePath );
867                 poDS->papszExtraFiles = CSLAddString( poDS->papszExtraFiles,
868                                                   pszFullname );
869 
870 /* -------------------------------------------------------------------- */
871 /*      Create the band.                                                */
872 /* -------------------------------------------------------------------- */
873                 SAFERasterBand *poBand
874                     = new SAFERasterBand( poDS, eDataType,
875                                           osSwath.c_str(),
876                                           osPolarisation.c_str(),
877                                           poBandFile );
878 
879                 poDS->SetBand( poDS->GetRasterCount() + 1, poBand );
880             }
881 
882             CPLFree( pszFullname );
883 
884             if( osMode == "WV" )
885             {
886                 break;
887             }
888         }
889     }
890 
891     //loop through all Swath/pols to add subdatasets
892     int iSubDS = 1;
893     for (std::map<CPLString, std::set<CPLString> >::iterator iterSwath=oMapSwaths2Pols.begin();
894          iterSwath!=oMapSwaths2Pols.end(); ++iterSwath)
895     {
896         CPLString osSubDS1 = iterSwath->first;
897         CPLString osSubDS2;
898 
899         for (std::set<CPLString>::iterator iterPol=iterSwath->second.begin();
900             iterPol!=iterSwath->second.end(); ++iterPol)
901         {
902             if (!osSubDS2.empty()) {
903                 osSubDS2 += "+";
904             }
905             osSubDS2 += *iterPol;
906 
907             //Create single band SubDataset
908             SAFEDataset::AddSubDataset(poDS, iSubDS,
909                 CPLSPrintf("SENTINEL1_DS:%s:%s_%s",
910                     osPath.c_str(),
911                     osSubDS1.c_str(),
912                     (*iterPol).c_str()),
913                 CPLSPrintf("Single band with %s swath and %s polarisation",
914                     osSubDS1.c_str(),
915                     (*iterPol).c_str())
916             );
917             iSubDS++;
918         }
919 
920         if (iterSwath->second.size()>1) {
921             //Create single band SubDataset with all polarisations
922             SAFEDataset::AddSubDataset(poDS, iSubDS,
923                 CPLSPrintf("SENTINEL1_DS:%s:%s",
924                     osPath.c_str(),
925                     osSubDS1.c_str()),
926                 CPLSPrintf("%s swath with all polarisations as bands",
927                     osSubDS1.c_str())
928             );
929             iSubDS++;
930         }
931     }
932     for( const auto& pair: oWVImages )
933     {
934         SAFEDataset::AddSubDataset(poDS, iSubDS,
935             CPLSPrintf("SENTINEL1_DS:%s:WV_%s",
936                 osPath.c_str(),
937                 pair.second.c_str()),
938             CPLSPrintf("Image %s of %s swath",
939                 pair.second.c_str(), pair.first.c_str())
940         );
941         iSubDS++;
942     }
943 
944     if (poDS->GetRasterCount() == 0 && oWVImages.empty()) {
945         CPLError( CE_Failure, CPLE_OpenFailed, "Measurement bands not found." );
946         delete poDS;
947         if( psAnnotation )
948             CPLDestroyXMLNode(psAnnotation);
949         return nullptr;
950     }
951 
952 /* -------------------------------------------------------------------- */
953 /*      Collect more metadata elements                                  */
954 /* -------------------------------------------------------------------- */
955 
956 /* -------------------------------------------------------------------- */
957 /*      Platform information                                            */
958 /* -------------------------------------------------------------------- */
959     CPLXMLNode *psPlatformAttrs = SAFEDataset::GetMetaDataObject(
960         psMetaDataObjects, "platform");
961 
962     if (poDS->GetRasterCount() != 0 && psPlatformAttrs != nullptr) {
963         const char *pszItem = CPLGetXMLValue(
964                 psPlatformAttrs,
965                 "metadataWrap.xmlData.safe:platform"
966                 ".safe:familyName", "" );
967         poDS->SetMetadataItem( "SATELLITE_IDENTIFIER", pszItem );
968 
969         pszItem = CPLGetXMLValue(
970                 psPlatformAttrs,
971                 "metadataWrap.xmlData.safe:platform"
972                 ".safe:instrument.safe:familyName.abbreviation", "" );
973         poDS->SetMetadataItem( "SENSOR_IDENTIFIER", pszItem );
974 
975         pszItem = CPLGetXMLValue(
976                 psPlatformAttrs,
977                 "metadataWrap.xmlData.safe:platform"
978                 ".safe:instrument.safe:extension"
979                 ".s1sarl1:instrumentMode.s1sarl1:mode", "UNK" );
980         poDS->SetMetadataItem( "BEAM_MODE", pszItem );
981 
982         pszItem = CPLGetXMLValue(
983                 psPlatformAttrs,
984                 "metadataWrap.xmlData.safe:platform"
985                 ".safe:instrument.safe:extension"
986                 ".s1sarl1:instrumentMode.s1sarl1:swath", "UNK" );
987         poDS->SetMetadataItem( "BEAM_SWATH", pszItem );
988     }
989 
990 /* -------------------------------------------------------------------- */
991 /*      Acquisition Period information                                  */
992 /* -------------------------------------------------------------------- */
993     CPLXMLNode *psAcquisitionAttrs = SAFEDataset::GetMetaDataObject(
994         psMetaDataObjects, "acquisitionPeriod");
995 
996     if (poDS->GetRasterCount() != 0 && psAcquisitionAttrs != nullptr) {
997             const char *pszItem = CPLGetXMLValue(
998             psAcquisitionAttrs,
999             "metadataWrap.xmlData.safe:acquisitionPeriod"
1000             ".safe:startTime", "UNK" );
1001         poDS->SetMetadataItem( "ACQUISITION_START_TIME", pszItem );
1002         pszItem = CPLGetXMLValue(
1003             psAcquisitionAttrs,
1004             "metadataWrap.xmlData.safe:acquisitionPeriod"
1005             ".safe:stopTime", "UNK" );
1006         poDS->SetMetadataItem( "ACQUISITION_STOP_TIME", pszItem );
1007     }
1008 
1009 /* -------------------------------------------------------------------- */
1010 /*      Processing information                                          */
1011 /* -------------------------------------------------------------------- */
1012     CPLXMLNode *psProcessingAttrs = SAFEDataset::GetMetaDataObject(
1013         psMetaDataObjects, "processing");
1014 
1015     if (poDS->GetRasterCount() != 0 && psProcessingAttrs != nullptr) {
1016         const char *pszItem = CPLGetXMLValue(
1017             psProcessingAttrs,
1018             "metadataWrap.xmlData.safe:processing.safe:facility.name", "UNK" );
1019         poDS->SetMetadataItem( "FACILITY_IDENTIFIER", pszItem );
1020     }
1021 
1022 /* -------------------------------------------------------------------- */
1023 /*      Measurement Orbit Reference information                         */
1024 /* -------------------------------------------------------------------- */
1025     CPLXMLNode *psOrbitAttrs = SAFEDataset::GetMetaDataObject(
1026         psMetaDataObjects, "measurementOrbitReference");
1027 
1028     if (poDS->GetRasterCount() != 0 && psOrbitAttrs != nullptr) {
1029         const char *pszItem = CPLGetXMLValue( psOrbitAttrs,
1030             "metadataWrap.xmlData.safe:orbitReference"
1031             ".safe:orbitNumber", "UNK" );
1032         poDS->SetMetadataItem( "ORBIT_NUMBER", pszItem );
1033         pszItem = CPLGetXMLValue( psOrbitAttrs,
1034             "metadataWrap.xmlData.safe:orbitReference"
1035             ".safe:extension.s1:orbitProperties.s1:pass", "UNK" );
1036         poDS->SetMetadataItem( "ORBIT_DIRECTION", pszItem );
1037     }
1038 
1039 /* -------------------------------------------------------------------- */
1040 /*      Collect Annotation Processing Information                       */
1041 /* -------------------------------------------------------------------- */
1042     CPLXMLNode *psProcessingInfo =
1043         CPLGetXMLNode( psAnnotation,
1044                        "=product.imageAnnotation.processingInformation" );
1045 
1046     if ( poDS->GetRasterCount() != 0 && psProcessingInfo != nullptr ) {
1047         OGRSpatialReference oLL, oPrj;
1048 
1049         const char *pszEllipsoidName = CPLGetXMLValue(
1050             psProcessingInfo, "ellipsoidName", "" );
1051         const double minor_axis = CPLAtof(CPLGetXMLValue(
1052             psProcessingInfo, "ellipsoidSemiMinorAxis", "0.0" ));
1053         const double major_axis = CPLAtof(CPLGetXMLValue(
1054             psProcessingInfo, "ellipsoidSemiMajorAxis", "0.0" ));
1055 
1056         if ( EQUAL(pszEllipsoidName, "") || ( minor_axis == 0.0 ) ||
1057              ( major_axis == 0.0 ) )
1058         {
1059             CPLError(CE_Warning,CPLE_AppDefined,"Warning- incomplete"
1060                      " ellipsoid information.  Using wgs-84 parameters.\n");
1061             oLL.SetWellKnownGeogCS( "WGS84" );
1062             oPrj.SetWellKnownGeogCS( "WGS84" );
1063         }
1064         else if ( EQUAL( pszEllipsoidName, "WGS84" ) ) {
1065             oLL.SetWellKnownGeogCS( "WGS84" );
1066             oPrj.SetWellKnownGeogCS( "WGS84" );
1067         }
1068         else {
1069             const double inv_flattening = major_axis/(major_axis - minor_axis);
1070             oLL.SetGeogCS( "","",pszEllipsoidName, major_axis,
1071                            inv_flattening);
1072             oPrj.SetGeogCS( "","",pszEllipsoidName, major_axis,
1073                             inv_flattening);
1074         }
1075 
1076         CPLFree( poDS->pszGCPProjection );
1077         poDS->pszGCPProjection = nullptr;
1078         oLL.exportToWkt( &(poDS->pszGCPProjection) );
1079     }
1080 
1081 /* -------------------------------------------------------------------- */
1082 /*      Collect GCPs.                                                   */
1083 /* -------------------------------------------------------------------- */
1084     CPLXMLNode *psGeoGrid =
1085         CPLGetXMLNode( psAnnotation,
1086                        "=product.geolocationGrid.geolocationGridPointList" );
1087 
1088     if( poDS->GetRasterCount() != 0 && psGeoGrid != nullptr ) {
1089         /* count GCPs */
1090         poDS->nGCPCount = 0;
1091 
1092         for( CPLXMLNode *psNode = psGeoGrid->psChild; psNode != nullptr;
1093              psNode = psNode->psNext )
1094         {
1095             if( EQUAL(psNode->pszValue,"geolocationGridPoint") )
1096                 poDS->nGCPCount++ ;
1097         }
1098 
1099         poDS->pasGCPList = reinterpret_cast<GDAL_GCP *>(
1100             CPLCalloc( sizeof(GDAL_GCP), poDS->nGCPCount ) );
1101 
1102         poDS->nGCPCount = 0;
1103 
1104         for( CPLXMLNode *psNode = psGeoGrid->psChild; psNode != nullptr;
1105              psNode = psNode->psNext )
1106         {
1107             GDAL_GCP *psGCP = poDS->pasGCPList + poDS->nGCPCount;
1108 
1109             if( !EQUAL(psNode->pszValue,"geolocationGridPoint") )
1110                 continue;
1111 
1112             poDS->nGCPCount++ ;
1113 
1114             char szID[32];
1115             snprintf( szID, sizeof(szID), "%d", poDS->nGCPCount );
1116             psGCP->pszId = CPLStrdup( szID );
1117             psGCP->pszInfo = CPLStrdup("");
1118             psGCP->dfGCPPixel = CPLAtof(CPLGetXMLValue(psNode,"pixel","0"));
1119             psGCP->dfGCPLine = CPLAtof(CPLGetXMLValue(psNode,"line","0"));
1120             psGCP->dfGCPX = CPLAtof(CPLGetXMLValue(psNode,"longitude",""));
1121             psGCP->dfGCPY = CPLAtof(CPLGetXMLValue(psNode,"latitude",""));
1122             psGCP->dfGCPZ = CPLAtof(CPLGetXMLValue(psNode,"height",""));
1123         }
1124     }
1125 
1126     CPLDestroyXMLNode(psAnnotation);
1127 
1128 /* -------------------------------------------------------------------- */
1129 /*      Initialize any PAM information.                                 */
1130 /* -------------------------------------------------------------------- */
1131     const CPLString osDescription = osMDFilename;
1132 
1133 /* -------------------------------------------------------------------- */
1134 /*      Initialize any PAM information.                                 */
1135 /* -------------------------------------------------------------------- */
1136     poDS->SetDescription( osDescription );
1137 
1138     poDS->SetPhysicalFilename( osMDFilename );
1139     poDS->SetSubdatasetName( osDescription );
1140 
1141     poDS->TryLoadXML();
1142 
1143 /* -------------------------------------------------------------------- */
1144 /*      Check for overviews.                                            */
1145 /* -------------------------------------------------------------------- */
1146     poDS->oOvManager.Initialize( poDS, ":::VIRTUAL:::" );
1147 
1148     return poDS;
1149 }
1150 
1151 /************************************************************************/
1152 /*                            AddSubDataset()                           */
1153 /************************************************************************/
AddSubDataset(SAFEDataset * poDS,int iDSNum,CPLString osName,CPLString osDesc)1154 void SAFEDataset::AddSubDataset(SAFEDataset *poDS, int iDSNum, CPLString osName, CPLString osDesc)
1155 {
1156     //Create SubDataset
1157     poDS->GDALDataset::SetMetadataItem(
1158         CPLSPrintf("SUBDATASET_%d_NAME", iDSNum),
1159         osName.c_str(),
1160         "SUBDATASETS");
1161 
1162     poDS->GDALDataset::SetMetadataItem(
1163         CPLSPrintf("SUBDATASET_%d_DESC", iDSNum),
1164         osDesc.c_str(),
1165         "SUBDATASETS");
1166 }
1167 
1168 /************************************************************************/
1169 /*                            GetGCPCount()                             */
1170 /************************************************************************/
1171 
GetGCPCount()1172 int SAFEDataset::GetGCPCount()
1173 {
1174     return nGCPCount;
1175 }
1176 
1177 /************************************************************************/
1178 /*                          GetGCPProjection()                          */
1179 /************************************************************************/
1180 
_GetGCPProjection()1181 const char *SAFEDataset::_GetGCPProjection()
1182 {
1183     return pszGCPProjection;
1184 }
1185 
1186 /************************************************************************/
1187 /*                               GetGCPs()                              */
1188 /************************************************************************/
1189 
GetGCPs()1190 const GDAL_GCP *SAFEDataset::GetGCPs()
1191 
1192 {
1193     return pasGCPList;
1194 }
1195 
1196 /************************************************************************/
1197 /*                          GetProjectionRef()                          */
1198 /************************************************************************/
1199 
_GetProjectionRef()1200 const char *SAFEDataset::_GetProjectionRef()
1201 {
1202     return pszProjection;
1203 }
1204 
1205 /************************************************************************/
1206 /*                          GetGeoTransform()                           */
1207 /************************************************************************/
1208 
GetGeoTransform(double * padfTransform)1209 CPLErr SAFEDataset::GetGeoTransform( double * padfTransform )
1210 {
1211     memcpy( padfTransform,  adfGeoTransform, sizeof(double) * 6 );
1212 
1213     if (bHaveGeoTransform)
1214         return CE_None;
1215 
1216     return CE_Failure;
1217 }
1218 
1219 #ifdef notdef
1220 /************************************************************************/
1221 /*                      GetMetadataDomainList()                         */
1222 /************************************************************************/
1223 
GetMetadataDomainList()1224 char **SAFEDataset::GetMetadataDomainList()
1225 {
1226     return BuildMetadataDomainList(GDALDataset::GetMetadataDomainList(),
1227                                    TRUE,
1228                                    "SUBDATASETS", nullptr);
1229 }
1230 
1231 /************************************************************************/
1232 /*                            GetMetadata()                             */
1233 /************************************************************************/
1234 
GetMetadata(const char * pszDomain)1235 char **SAFEDataset::GetMetadata( const char *pszDomain )
1236 {
1237     if( pszDomain != NULL && STARTS_WITH_CI(pszDomain, "SUBDATASETS") &&
1238         papszSubDatasets != NULL)
1239         return papszSubDatasets;
1240 
1241     return GDALDataset::GetMetadata( pszDomain );
1242 }
1243 #endif
1244 
1245 /************************************************************************/
1246 /*                         GDALRegister_SAFE()                          */
1247 /************************************************************************/
1248 
GDALRegister_SAFE()1249 void GDALRegister_SAFE()
1250 {
1251     if( GDALGetDriverByName( "SAFE" ) != nullptr )
1252         return;
1253 
1254     GDALDriver *poDriver = new GDALDriver();
1255 
1256     poDriver->SetDescription( "SAFE" );
1257     poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
1258     poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
1259     poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
1260                                "Sentinel-1 SAR SAFE Product" );
1261     poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "drivers/raster/safe.html" );
1262 
1263     poDriver->pfnOpen = SAFEDataset::Open;
1264     poDriver->pfnIdentify = SAFEDataset::Identify;
1265 
1266     GetGDALDriverManager()->RegisterDriver( poDriver );
1267 }
1268