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