Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: Polarimetric Workstation
4 : * Purpose: Radarsat 2 - XML Products (product.xml) driver
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2004, Frank Warmerdam <warmerdam@pobox.com>
9 : * Copyright (c) 2009-2013, Even Rouault <even dot rouault at spatialys.com>
10 : * Copyright (c) 2020, Defence Research and Development Canada (DRDC) Ottawa Research Centre
11 : *
12 : * SPDX-License-Identifier: MIT
13 : ****************************************************************************/
14 :
15 : #include "cpl_minixml.h"
16 : #include "gdal_frmts.h"
17 : #include "gdal_pam.h"
18 : #include "ogr_spatialref.h"
19 :
20 : typedef enum eCalibration_t
21 : {
22 : Sigma0 = 0,
23 : Gamma,
24 : Beta0,
25 : Uncalib,
26 : None
27 : } eCalibration;
28 :
29 : /*** Function to test for valid LUT files ***/
30 18 : static bool IsValidXMLFile(const char *pszPath, const char *pszLut)
31 : {
32 18 : if (CPLHasPathTraversal(pszLut))
33 : {
34 0 : CPLError(CE_Failure, CPLE_NotSupported, "Path traversal detected in %s",
35 : pszLut);
36 0 : return false;
37 : }
38 : /* Return true for valid xml file, false otherwise */
39 : char *pszLutFile =
40 18 : VSIStrdup(CPLFormFilenameSafe(pszPath, pszLut, nullptr).c_str());
41 :
42 18 : CPLXMLTreeCloser psLut(CPLParseXMLFile(pszLutFile));
43 :
44 18 : CPLFree(pszLutFile);
45 :
46 18 : return psLut.get() != nullptr;
47 : }
48 :
49 : // Check that the referenced dataset for each band has the
50 : // correct data type and returns whether a 2 band I+Q dataset should
51 : // be mapped onto a single complex band.
52 : // Returns BANDERROR for error, STRAIGHT for 1:1 mapping, TWOBANDCOMPLEX for 2
53 : // bands -> 1 complex band
54 : typedef enum
55 : {
56 : BANDERROR,
57 : STRAIGHT,
58 : TWOBANDCOMPLEX
59 : } BandMapping;
60 :
61 11 : static BandMapping GetBandFileMapping(GDALDataType eDataType,
62 : GDALDataset *poBandFile)
63 : {
64 :
65 11 : GDALRasterBand *poBand1 = poBandFile->GetRasterBand(1);
66 11 : GDALDataType eBandDataType1 = poBand1->GetRasterDataType();
67 :
68 : // if there is one band and it has the same datatype, the band file gets
69 : // passed straight through
70 11 : if (poBandFile->GetRasterCount() == 1 && eDataType == eBandDataType1)
71 10 : return STRAIGHT;
72 :
73 : // if the band file has 2 bands, they should represent I+Q
74 : // and be a compatible data type
75 1 : if (poBandFile->GetRasterCount() == 2 && GDALDataTypeIsComplex(eDataType))
76 : {
77 1 : GDALRasterBand *band2 = poBandFile->GetRasterBand(2);
78 :
79 1 : if (eBandDataType1 != band2->GetRasterDataType())
80 0 : return BANDERROR; // both bands must be same datatype
81 :
82 : // check compatible types - there are 4 complex types in GDAL
83 1 : if ((eDataType == GDT_CInt16 && eBandDataType1 == GDT_Int16) ||
84 0 : (eDataType == GDT_CInt32 && eBandDataType1 == GDT_Int32) ||
85 0 : (eDataType == GDT_CFloat32 && eBandDataType1 == GDT_Float32) ||
86 0 : (eDataType == GDT_CFloat64 && eBandDataType1 == GDT_Float64))
87 1 : return TWOBANDCOMPLEX;
88 : }
89 0 : return BANDERROR; // don't accept any other combinations
90 : }
91 :
92 : /************************************************************************/
93 : /* ==================================================================== */
94 : /* RS2Dataset */
95 : /* ==================================================================== */
96 : /************************************************************************/
97 :
98 : class RS2Dataset final : public GDALPamDataset
99 : {
100 : CPLXMLNode *psProduct;
101 :
102 : int nGCPCount;
103 : GDAL_GCP *pasGCPList;
104 : OGRSpatialReference m_oSRS{};
105 : OGRSpatialReference m_oGCPSRS{};
106 : char **papszSubDatasets;
107 : GDALGeoTransform m_gt{};
108 : bool bHaveGeoTransform;
109 :
110 : char **papszExtraFiles;
111 :
112 : CPL_DISALLOW_COPY_ASSIGN(RS2Dataset)
113 :
114 : protected:
115 : int CloseDependentDatasets() override;
116 :
117 : public:
118 : RS2Dataset();
119 : ~RS2Dataset() override;
120 :
121 : int GetGCPCount() override;
122 : const OGRSpatialReference *GetGCPSpatialRef() const override;
123 : const GDAL_GCP *GetGCPs() override;
124 :
125 : const OGRSpatialReference *GetSpatialRef() const override;
126 : CPLErr GetGeoTransform(GDALGeoTransform >) const override;
127 :
128 : char **GetMetadataDomainList() override;
129 : char **GetMetadata(const char *pszDomain = "") override;
130 : char **GetFileList(void) override;
131 :
132 : static GDALDataset *Open(GDALOpenInfo *);
133 : static int Identify(GDALOpenInfo *);
134 :
135 : CPLXMLNode *GetProduct()
136 : {
137 : return psProduct;
138 : }
139 : };
140 :
141 : /************************************************************************/
142 : /* ==================================================================== */
143 : /* RS2RasterBand */
144 : /* ==================================================================== */
145 : /************************************************************************/
146 :
147 : class RS2RasterBand final : public GDALPamRasterBand
148 : {
149 : GDALDataset *poBandFile = nullptr;
150 :
151 : // 2 bands representing I+Q -> one complex band
152 : // otherwise poBandFile is passed straight through
153 : bool bIsTwoBandComplex = false;
154 :
155 : CPL_DISALLOW_COPY_ASSIGN(RS2RasterBand)
156 :
157 : public:
158 : RS2RasterBand(RS2Dataset *poDSIn, GDALDataType eDataTypeIn,
159 : const char *pszPole, GDALDataset *poBandFile,
160 : bool bTwoBandComplex = false);
161 : ~RS2RasterBand() override;
162 :
163 : CPLErr IReadBlock(int, int, void *) override;
164 :
165 : static GDALDataset *Open(GDALOpenInfo *);
166 : };
167 :
168 : /************************************************************************/
169 : /* RS2RasterBand */
170 : /************************************************************************/
171 :
172 9 : RS2RasterBand::RS2RasterBand(RS2Dataset *poDSIn, GDALDataType eDataTypeIn,
173 : const char *pszPole, GDALDataset *poBandFileIn,
174 9 : bool bTwoBandComplex)
175 9 : : poBandFile(poBandFileIn)
176 : {
177 9 : poDS = poDSIn;
178 :
179 9 : GDALRasterBand *poSrcBand = poBandFile->GetRasterBand(1);
180 :
181 9 : poSrcBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
182 :
183 9 : eDataType = eDataTypeIn;
184 :
185 9 : bIsTwoBandComplex = bTwoBandComplex;
186 :
187 9 : if (*pszPole != '\0')
188 9 : SetMetadataItem("POLARIMETRIC_INTERP", pszPole);
189 9 : }
190 :
191 : /************************************************************************/
192 : /* RSRasterBand() */
193 : /************************************************************************/
194 :
195 18 : RS2RasterBand::~RS2RasterBand()
196 :
197 : {
198 9 : if (poBandFile != nullptr)
199 9 : GDALClose(reinterpret_cast<GDALRasterBandH>(poBandFile));
200 18 : }
201 :
202 : /************************************************************************/
203 : /* IReadBlock() */
204 : /************************************************************************/
205 :
206 40 : CPLErr RS2RasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
207 :
208 : {
209 : /* -------------------------------------------------------------------- */
210 : /* If the last strip is partial, we need to avoid */
211 : /* over-requesting. We also need to initialize the extra part */
212 : /* of the block to zero. */
213 : /* -------------------------------------------------------------------- */
214 : int nRequestYSize;
215 40 : if ((nBlockYOff + 1) * nBlockYSize > nRasterYSize)
216 : {
217 0 : nRequestYSize = nRasterYSize - nBlockYOff * nBlockYSize;
218 0 : memset(pImage, 0,
219 0 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
220 0 : nBlockXSize * nBlockYSize);
221 : }
222 : else
223 : {
224 40 : nRequestYSize = nBlockYSize;
225 : }
226 :
227 : /*-------------------------------------------------------------------- */
228 : /* If the input imagery is tiled, also need to avoid over- */
229 : /* requesting in the X-direction. */
230 : /* ------------------------------------------------------------------- */
231 : int nRequestXSize;
232 40 : if ((nBlockXOff + 1) * nBlockXSize > nRasterXSize)
233 : {
234 0 : nRequestXSize = nRasterXSize - nBlockXOff * nBlockXSize;
235 0 : memset(pImage, 0,
236 0 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
237 0 : nBlockXSize * nBlockYSize);
238 : }
239 : else
240 : {
241 40 : nRequestXSize = nBlockXSize;
242 : }
243 40 : if (eDataType == GDT_CInt16 && poBandFile->GetRasterCount() == 2)
244 0 : return poBandFile->RasterIO(
245 0 : GF_Read, nBlockXOff * nBlockXSize, nBlockYOff * nBlockYSize,
246 : nRequestXSize, nRequestYSize, pImage, nRequestXSize, nRequestYSize,
247 0 : GDT_Int16, 2, nullptr, 4, nBlockXSize * 4, 2, nullptr);
248 :
249 : /* -------------------------------------------------------------------- */
250 : /* File has one sample marked as sample format void, a 32bits. */
251 : /* -------------------------------------------------------------------- */
252 40 : else if (eDataType == GDT_CInt16 && poBandFile->GetRasterCount() == 1)
253 : {
254 0 : CPLErr eErr = poBandFile->RasterIO(
255 0 : GF_Read, nBlockXOff * nBlockXSize, nBlockYOff * nBlockYSize,
256 : nRequestXSize, nRequestYSize, pImage, nRequestXSize, nRequestYSize,
257 0 : GDT_UInt32, 1, nullptr, 4, nBlockXSize * 4, 0, nullptr);
258 :
259 : #ifdef CPL_LSB
260 : /* First, undo the 32bit swap. */
261 0 : GDALSwapWords(pImage, 4, nBlockXSize * nBlockYSize, 4);
262 :
263 : /* Then apply 16 bit swap. */
264 0 : GDALSwapWords(pImage, 2, nBlockXSize * nBlockYSize * 2, 2);
265 : #endif
266 :
267 0 : return eErr;
268 : }
269 :
270 : /* -------------------------------------------------------------------- */
271 : /* The 16bit case is straight forward. The underlying file */
272 : /* looks like a 16bit unsigned data too. */
273 : /* -------------------------------------------------------------------- */
274 40 : else if (eDataType == GDT_UInt16)
275 0 : return poBandFile->RasterIO(
276 0 : GF_Read, nBlockXOff * nBlockXSize, nBlockYOff * nBlockYSize,
277 : nRequestXSize, nRequestYSize, pImage, nRequestXSize, nRequestYSize,
278 0 : GDT_UInt16, 1, nullptr, 2, nBlockXSize * 2, 0, nullptr);
279 40 : else if (eDataType == GDT_Byte)
280 : /* Ticket #2104: Support for ScanSAR products */
281 80 : return poBandFile->RasterIO(
282 40 : GF_Read, nBlockXOff * nBlockXSize, nBlockYOff * nBlockYSize,
283 : nRequestXSize, nRequestYSize, pImage, nRequestXSize, nRequestYSize,
284 40 : GDT_Byte, 1, nullptr, 1, nBlockXSize, 0, nullptr);
285 :
286 0 : CPLAssert(false);
287 : return CE_Failure;
288 : }
289 :
290 : /************************************************************************/
291 : /* ==================================================================== */
292 : /* RS2CalibRasterBand */
293 : /* ==================================================================== */
294 : /************************************************************************/
295 : /* Returns data that has been calibrated to sigma nought, gamma */
296 : /* or beta nought. */
297 : /************************************************************************/
298 :
299 : class RS2CalibRasterBand final : public GDALPamRasterBand
300 : {
301 : private:
302 : // eCalibration m_eCalib;
303 : GDALDataset *m_poBandDataset;
304 : GDALDataType m_eType; /* data type of data being ingested */
305 : float *m_nfTable;
306 : int m_nTableSize;
307 : float m_nfOffset;
308 : char *m_pszLUTFile;
309 :
310 : CPL_DISALLOW_COPY_ASSIGN(RS2CalibRasterBand)
311 :
312 : void ReadLUT();
313 :
314 : public:
315 : RS2CalibRasterBand(RS2Dataset *poDataset, const char *pszPolarization,
316 : GDALDataType eType, GDALDataset *poBandDataset,
317 : eCalibration eCalib, const char *pszLUT);
318 : ~RS2CalibRasterBand() override;
319 :
320 : CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage) override;
321 : };
322 :
323 : /************************************************************************/
324 : /* ReadLUT() */
325 : /************************************************************************/
326 : /* Read the provided LUT in to m_ndTable */
327 : /************************************************************************/
328 2 : void RS2CalibRasterBand::ReadLUT()
329 : {
330 2 : CPLXMLNode *psLUT = CPLParseXMLFile(m_pszLUTFile);
331 :
332 2 : this->m_nfOffset = static_cast<float>(
333 2 : CPLAtof(CPLGetXMLValue(psLUT, "=lut.offset", "0.0")));
334 :
335 2 : char **papszLUTList = CSLTokenizeString2(
336 : CPLGetXMLValue(psLUT, "=lut.gains", ""), " ", CSLT_HONOURSTRINGS);
337 :
338 2 : m_nTableSize = CSLCount(papszLUTList);
339 :
340 2 : m_nfTable = static_cast<float *>(CPLMalloc(sizeof(float) * m_nTableSize));
341 :
342 514 : for (int i = 0; i < m_nTableSize; i++)
343 : {
344 512 : m_nfTable[i] = static_cast<float>(CPLAtof(papszLUTList[i]));
345 : }
346 :
347 2 : CPLDestroyXMLNode(psLUT);
348 :
349 2 : CSLDestroy(papszLUTList);
350 2 : }
351 :
352 : /************************************************************************/
353 : /* RS2CalibRasterBand() */
354 : /************************************************************************/
355 :
356 2 : RS2CalibRasterBand::RS2CalibRasterBand(
357 : RS2Dataset *poDataset, const char *pszPolarization, GDALDataType eType,
358 2 : GDALDataset *poBandDataset, eCalibration /* eCalib */, const char *pszLUT)
359 : : // m_eCalib(eCalib),
360 : m_poBandDataset(poBandDataset), m_eType(eType), m_nfTable(nullptr),
361 2 : m_nTableSize(0), m_nfOffset(0), m_pszLUTFile(VSIStrdup(pszLUT))
362 : {
363 2 : poDS = poDataset;
364 :
365 2 : if (*pszPolarization != '\0')
366 : {
367 2 : SetMetadataItem("POLARIMETRIC_INTERP", pszPolarization);
368 : }
369 :
370 2 : if (eType == GDT_CInt16)
371 0 : eDataType = GDT_CFloat32;
372 : else
373 2 : eDataType = GDT_Float32;
374 :
375 2 : GDALRasterBand *poRasterBand = poBandDataset->GetRasterBand(1);
376 2 : poRasterBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
377 :
378 2 : ReadLUT();
379 2 : }
380 :
381 : /************************************************************************/
382 : /* ~RS2CalibRasterBand() */
383 : /************************************************************************/
384 :
385 4 : RS2CalibRasterBand::~RS2CalibRasterBand()
386 : {
387 2 : CPLFree(m_nfTable);
388 2 : CPLFree(m_pszLUTFile);
389 2 : GDALClose(m_poBandDataset);
390 4 : }
391 :
392 : /************************************************************************/
393 : /* IReadBlock() */
394 : /************************************************************************/
395 :
396 20 : CPLErr RS2CalibRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
397 : void *pImage)
398 : {
399 : /* -------------------------------------------------------------------- */
400 : /* If the last strip is partial, we need to avoid */
401 : /* over-requesting. We also need to initialize the extra part */
402 : /* of the block to zero. */
403 : /* -------------------------------------------------------------------- */
404 : int nRequestYSize;
405 20 : if ((nBlockYOff + 1) * nBlockYSize > nRasterYSize)
406 : {
407 0 : nRequestYSize = nRasterYSize - nBlockYOff * nBlockYSize;
408 0 : memset(pImage, 0,
409 0 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
410 0 : nBlockXSize * nBlockYSize);
411 : }
412 : else
413 : {
414 20 : nRequestYSize = nBlockYSize;
415 : }
416 :
417 : CPLErr eErr;
418 20 : if (m_eType == GDT_CInt16)
419 : {
420 : /* read in complex values */
421 : GInt16 *pnImageTmp = reinterpret_cast<GInt16 *>(
422 0 : CPLMalloc(2 * sizeof(int16_t) * nBlockXSize * nBlockYSize));
423 0 : if (m_poBandDataset->GetRasterCount() == 2)
424 : {
425 0 : eErr = m_poBandDataset->RasterIO(
426 0 : GF_Read, nBlockXOff * nBlockXSize, nBlockYOff * nBlockYSize,
427 : nBlockXSize, nRequestYSize, pnImageTmp, nBlockXSize,
428 0 : nRequestYSize, GDT_Int16, 2, nullptr, 4, nBlockXSize * 4, 2,
429 : nullptr);
430 : }
431 : else
432 : {
433 0 : eErr = m_poBandDataset->RasterIO(
434 0 : GF_Read, nBlockXOff * nBlockXSize, nBlockYOff * nBlockYSize,
435 : nBlockXSize, nRequestYSize, pnImageTmp, nBlockXSize,
436 0 : nRequestYSize, GDT_UInt32, 1, nullptr, 4, nBlockXSize * 4, 0,
437 : nullptr);
438 :
439 : #ifdef CPL_LSB
440 : /* First, undo the 32bit swap. */
441 0 : GDALSwapWords(pImage, 4, nBlockXSize * nBlockYSize, 4);
442 :
443 : /* Then apply 16 bit swap. */
444 0 : GDALSwapWords(pImage, 2, nBlockXSize * nBlockYSize * 2, 2);
445 : #endif
446 : }
447 :
448 : /* calibrate the complex values */
449 0 : for (int i = 0; i < nBlockYSize; i++)
450 : {
451 0 : for (int j = 0; j < nBlockXSize; j++)
452 : {
453 : /* calculate pixel offset in memory*/
454 0 : int nPixOff = (2 * (i * nBlockXSize)) + (j * 2);
455 :
456 0 : reinterpret_cast<float *>(pImage)[nPixOff] =
457 0 : static_cast<float>(pnImageTmp[nPixOff]) /
458 0 : (m_nfTable[nBlockXOff + j]);
459 0 : reinterpret_cast<float *>(pImage)[nPixOff + 1] =
460 0 : static_cast<float>(pnImageTmp[nPixOff + 1]) /
461 0 : (m_nfTable[nBlockXOff + j]);
462 : }
463 : }
464 0 : CPLFree(pnImageTmp);
465 : }
466 : // If the underlying file is NITF CFloat32
467 20 : else if (eDataType == GDT_CFloat32 &&
468 0 : m_poBandDataset->GetRasterCount() == 1)
469 : {
470 0 : eErr = m_poBandDataset->RasterIO(
471 0 : GF_Read, nBlockXOff * nBlockXSize, nBlockYOff * nBlockYSize,
472 : nBlockXSize, nRequestYSize, pImage, nBlockXSize, nRequestYSize,
473 : GDT_CFloat32, 1, nullptr, 2 * static_cast<int>(sizeof(float)),
474 0 : nBlockXSize * 2 * static_cast<GSpacing>(sizeof(float)), 0, nullptr);
475 :
476 : /* calibrate the complex values */
477 0 : for (int i = 0; i < nBlockYSize; i++)
478 : {
479 0 : for (int j = 0; j < nBlockXSize; j++)
480 : {
481 : /* calculate pixel offset in memory*/
482 0 : const int nPixOff = 2 * (i * nBlockXSize + j);
483 :
484 0 : reinterpret_cast<float *>(pImage)[nPixOff] /=
485 0 : m_nfTable[nBlockXOff * nBlockXSize + j];
486 0 : reinterpret_cast<float *>(pImage)[nPixOff + 1] /=
487 0 : m_nfTable[nBlockXOff * nBlockXSize + j];
488 : }
489 : }
490 : }
491 20 : else if (m_eType == GDT_UInt16)
492 : {
493 : /* read in detected values */
494 : GUInt16 *pnImageTmp = reinterpret_cast<GUInt16 *>(
495 0 : CPLMalloc(sizeof(uint16_t) * nBlockXSize * nBlockYSize));
496 0 : eErr = m_poBandDataset->RasterIO(
497 0 : GF_Read, nBlockXOff * nBlockXSize, nBlockYOff * nBlockYSize,
498 : nBlockXSize, nRequestYSize, pnImageTmp, nBlockXSize, nRequestYSize,
499 0 : GDT_UInt16, 1, nullptr, 2, nBlockXSize * 2, 0, nullptr);
500 :
501 : /* iterate over detected values */
502 0 : for (int i = 0; i < nBlockYSize; i++)
503 : {
504 0 : for (int j = 0; j < nBlockXSize; j++)
505 : {
506 0 : int nPixOff = (i * nBlockXSize) + j;
507 :
508 0 : reinterpret_cast<float *>(pImage)[nPixOff] =
509 0 : ((static_cast<float>(pnImageTmp[nPixOff]) *
510 0 : static_cast<float>(pnImageTmp[nPixOff])) +
511 0 : m_nfOffset) /
512 0 : m_nfTable[nBlockXOff + j];
513 : }
514 : }
515 0 : CPLFree(pnImageTmp);
516 : } /* Ticket #2104: Support for ScanSAR products */
517 20 : else if (m_eType == GDT_Byte)
518 : {
519 : GByte *pnImageTmp = reinterpret_cast<GByte *>(
520 20 : CPLMalloc(static_cast<size_t>(nBlockXSize) * nBlockYSize));
521 40 : eErr = m_poBandDataset->RasterIO(
522 20 : GF_Read, nBlockXOff * nBlockXSize, nBlockYOff * nBlockYSize,
523 : nBlockXSize, nRequestYSize, pnImageTmp, nBlockXSize, nRequestYSize,
524 : GDT_Byte, 1, nullptr, 1, 1, 0, nullptr);
525 :
526 : /* iterate over detected values */
527 40 : for (int i = 0; i < nBlockYSize; i++)
528 : {
529 420 : for (int j = 0; j < nBlockXSize; j++)
530 : {
531 400 : int nPixOff = (i * nBlockXSize) + j;
532 :
533 400 : reinterpret_cast<float *>(pImage)[nPixOff] =
534 400 : ((pnImageTmp[nPixOff] * pnImageTmp[nPixOff]) + m_nfOffset) /
535 400 : m_nfTable[nBlockXOff + j];
536 : }
537 : }
538 20 : CPLFree(pnImageTmp);
539 : }
540 : else
541 : {
542 0 : CPLAssert(false);
543 : return CE_Failure;
544 : }
545 20 : return eErr;
546 : }
547 :
548 : /************************************************************************/
549 : /* ==================================================================== */
550 : /* RS2Dataset */
551 : /* ==================================================================== */
552 : /************************************************************************/
553 :
554 : /************************************************************************/
555 : /* RS2Dataset() */
556 : /************************************************************************/
557 :
558 6 : RS2Dataset::RS2Dataset()
559 : : psProduct(nullptr), nGCPCount(0), pasGCPList(nullptr),
560 : papszSubDatasets(nullptr), bHaveGeoTransform(FALSE),
561 6 : papszExtraFiles(nullptr)
562 : {
563 6 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
564 6 : m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
565 6 : }
566 :
567 : /************************************************************************/
568 : /* ~RS2Dataset() */
569 : /************************************************************************/
570 :
571 12 : RS2Dataset::~RS2Dataset()
572 :
573 : {
574 6 : RS2Dataset::FlushCache(true);
575 :
576 6 : CPLDestroyXMLNode(psProduct);
577 :
578 6 : if (nGCPCount > 0)
579 : {
580 6 : GDALDeinitGCPs(nGCPCount, pasGCPList);
581 6 : CPLFree(pasGCPList);
582 : }
583 :
584 6 : RS2Dataset::CloseDependentDatasets();
585 :
586 6 : CSLDestroy(papszSubDatasets);
587 6 : CSLDestroy(papszExtraFiles);
588 12 : }
589 :
590 : /************************************************************************/
591 : /* CloseDependentDatasets() */
592 : /************************************************************************/
593 :
594 12 : int RS2Dataset::CloseDependentDatasets()
595 : {
596 12 : int bHasDroppedRef = GDALPamDataset::CloseDependentDatasets();
597 :
598 12 : if (nBands != 0)
599 6 : bHasDroppedRef = TRUE;
600 :
601 23 : for (int iBand = 0; iBand < nBands; iBand++)
602 : {
603 11 : delete papoBands[iBand];
604 : }
605 12 : nBands = 0;
606 :
607 12 : return bHasDroppedRef;
608 : }
609 :
610 : /************************************************************************/
611 : /* GetFileList() */
612 : /************************************************************************/
613 :
614 2 : char **RS2Dataset::GetFileList()
615 :
616 : {
617 2 : char **papszFileList = GDALPamDataset::GetFileList();
618 :
619 2 : papszFileList = CSLInsertStrings(papszFileList, -1, papszExtraFiles);
620 :
621 2 : return papszFileList;
622 : }
623 :
624 : /************************************************************************/
625 : /* Identify() */
626 : /************************************************************************/
627 :
628 62532 : int RS2Dataset::Identify(GDALOpenInfo *poOpenInfo)
629 : {
630 : /* Check for the case where we're trying to read the calibrated data: */
631 62532 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "RADARSAT_2_CALIB:"))
632 : {
633 2 : return TRUE;
634 : }
635 :
636 : /* Check for directory access when there is a product.xml file in the
637 : directory. */
638 62530 : if (poOpenInfo->bIsDirectory)
639 : {
640 0 : const CPLString osMDFilename = CPLFormCIFilenameSafe(
641 1230 : poOpenInfo->pszFilename, "product.xml", nullptr);
642 :
643 1230 : GDALOpenInfo oOpenInfo(osMDFilename.c_str(), GA_ReadOnly);
644 615 : return Identify(&oOpenInfo);
645 : }
646 :
647 : /* otherwise, do our normal stuff */
648 61915 : if (strlen(poOpenInfo->pszFilename) < 11 ||
649 60284 : !EQUAL(poOpenInfo->pszFilename + strlen(poOpenInfo->pszFilename) - 11,
650 : "product.xml"))
651 61282 : return FALSE;
652 :
653 633 : if (poOpenInfo->nHeaderBytes < 100)
654 620 : return FALSE;
655 :
656 13 : if (strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
657 10 : "/rs2") == nullptr ||
658 10 : strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
659 : "<product") == nullptr)
660 3 : return FALSE;
661 :
662 10 : return TRUE;
663 : }
664 :
665 : /************************************************************************/
666 : /* Open() */
667 : /************************************************************************/
668 :
669 6 : GDALDataset *RS2Dataset::Open(GDALOpenInfo *poOpenInfo)
670 :
671 : {
672 : /* -------------------------------------------------------------------- */
673 : /* Is this a RADARSAT-2 Product.xml definition? */
674 : /* -------------------------------------------------------------------- */
675 6 : if (!RS2Dataset::Identify(poOpenInfo))
676 : {
677 0 : return nullptr;
678 : }
679 :
680 : /* -------------------------------------------------------------------- */
681 : /* Get subdataset information, if relevant */
682 : /* -------------------------------------------------------------------- */
683 6 : const char *pszFilename = poOpenInfo->pszFilename;
684 6 : eCalibration eCalib = None;
685 :
686 6 : if (STARTS_WITH_CI(pszFilename, "RADARSAT_2_CALIB:"))
687 : {
688 1 : pszFilename += 17;
689 :
690 1 : if (STARTS_WITH_CI(pszFilename, "BETA0"))
691 1 : eCalib = Beta0;
692 0 : else if (STARTS_WITH_CI(pszFilename, "SIGMA0"))
693 0 : eCalib = Sigma0;
694 0 : else if (STARTS_WITH_CI(pszFilename, "GAMMA"))
695 0 : eCalib = Gamma;
696 0 : else if (STARTS_WITH_CI(pszFilename, "UNCALIB"))
697 0 : eCalib = Uncalib;
698 : else
699 0 : eCalib = None;
700 :
701 : /* advance the pointer to the actual filename */
702 6 : while (*pszFilename != '\0' && *pszFilename != ':')
703 5 : pszFilename++;
704 :
705 1 : if (*pszFilename == ':')
706 1 : pszFilename++;
707 :
708 : // need to redo the directory check:
709 : // the GDALOpenInfo check would have failed because of the calibration
710 : // string on the filename
711 : VSIStatBufL sStat;
712 1 : if (VSIStatL(pszFilename, &sStat) == 0)
713 1 : poOpenInfo->bIsDirectory = VSI_ISDIR(sStat.st_mode);
714 : }
715 :
716 12 : CPLString osMDFilename;
717 6 : if (poOpenInfo->bIsDirectory)
718 : {
719 : osMDFilename =
720 0 : CPLFormCIFilenameSafe(pszFilename, "product.xml", nullptr);
721 : }
722 : else
723 6 : osMDFilename = pszFilename;
724 :
725 : /* -------------------------------------------------------------------- */
726 : /* Ingest the Product.xml file. */
727 : /* -------------------------------------------------------------------- */
728 6 : CPLXMLNode *psProduct = CPLParseXMLFile(osMDFilename);
729 6 : if (psProduct == nullptr)
730 0 : return nullptr;
731 :
732 : /* -------------------------------------------------------------------- */
733 : /* Confirm the requested access is supported. */
734 : /* -------------------------------------------------------------------- */
735 6 : if (poOpenInfo->eAccess == GA_Update)
736 : {
737 0 : CPLDestroyXMLNode(psProduct);
738 0 : ReportUpdateNotSupportedByDriver("RS2");
739 0 : return nullptr;
740 : }
741 :
742 : CPLXMLNode *psImageAttributes =
743 6 : CPLGetXMLNode(psProduct, "=product.imageAttributes");
744 6 : if (psImageAttributes == nullptr)
745 : {
746 0 : CPLDestroyXMLNode(psProduct);
747 0 : CPLError(CE_Failure, CPLE_OpenFailed,
748 : "Failed to find <imageAttributes> in document.");
749 0 : return nullptr;
750 : }
751 :
752 : CPLXMLNode *psImageGenerationParameters =
753 6 : CPLGetXMLNode(psProduct, "=product.imageGenerationParameters");
754 6 : if (psImageGenerationParameters == nullptr)
755 : {
756 0 : CPLDestroyXMLNode(psProduct);
757 0 : CPLError(CE_Failure, CPLE_OpenFailed,
758 : "Failed to find <imageGenerationParameters> in document.");
759 0 : return nullptr;
760 : }
761 :
762 : /* -------------------------------------------------------------------- */
763 : /* Create the dataset. */
764 : /* -------------------------------------------------------------------- */
765 12 : auto poDS = std::make_unique<RS2Dataset>();
766 :
767 6 : poDS->psProduct = psProduct;
768 :
769 : /* -------------------------------------------------------------------- */
770 : /* Get overall image information. */
771 : /* -------------------------------------------------------------------- */
772 6 : poDS->nRasterXSize = atoi(CPLGetXMLValue(
773 : psImageAttributes, "rasterAttributes.numberOfSamplesPerLine", "-1"));
774 6 : poDS->nRasterYSize = atoi(CPLGetXMLValue(
775 : psImageAttributes, "rasterAttributes.numberofLines", "-1"));
776 6 : if (poDS->nRasterXSize <= 1 || poDS->nRasterYSize <= 1)
777 : {
778 0 : CPLError(
779 : CE_Failure, CPLE_OpenFailed,
780 : "Non-sane raster dimensions provided in product.xml. If this is "
781 : "a valid RADARSAT-2 scene, please contact your data provider for "
782 : "a corrected dataset.");
783 0 : return nullptr;
784 : }
785 :
786 : /* -------------------------------------------------------------------- */
787 : /* Check product type, as to determine if there are LUTs for */
788 : /* calibration purposes. */
789 : /* -------------------------------------------------------------------- */
790 :
791 : const char *pszProductType =
792 6 : CPLGetXMLValue(psImageGenerationParameters,
793 : "generalProcessingInformation.productType", "UNK");
794 :
795 6 : poDS->SetMetadataItem("PRODUCT_TYPE", pszProductType);
796 :
797 : /* the following cases can be assumed to have no LUTs, as per
798 : * RN-RP-51-2713, but also common sense
799 : */
800 6 : bool bCanCalib = false;
801 6 : if (!(STARTS_WITH_CI(pszProductType, "UNK") ||
802 6 : STARTS_WITH_CI(pszProductType, "SSG") ||
803 6 : STARTS_WITH_CI(pszProductType, "SPG")))
804 : {
805 6 : bCanCalib = true;
806 : }
807 :
808 : /* -------------------------------------------------------------------- */
809 : /* Get dataType (so we can recognise complex data), and the */
810 : /* bitsPerSample. */
811 : /* -------------------------------------------------------------------- */
812 : const char *pszDataType =
813 6 : CPLGetXMLValue(psImageAttributes, "rasterAttributes.dataType", "");
814 6 : const int nBitsPerSample = atoi(CPLGetXMLValue(
815 : psImageAttributes, "rasterAttributes.bitsPerSample", ""));
816 :
817 : GDALDataType eDataType;
818 6 : if (nBitsPerSample == 16 && EQUAL(pszDataType, "Complex"))
819 1 : eDataType = GDT_CInt16;
820 5 : else if (nBitsPerSample == 32 &&
821 0 : EQUAL(pszDataType,
822 : "Complex")) // NITF datasets can come in this configuration
823 0 : eDataType = GDT_CFloat32;
824 5 : else if (nBitsPerSample == 16 && STARTS_WITH_CI(pszDataType, "Mag"))
825 0 : eDataType = GDT_UInt16;
826 5 : else if (nBitsPerSample == 8 && STARTS_WITH_CI(pszDataType, "Mag"))
827 5 : eDataType = GDT_Byte;
828 : else
829 : {
830 0 : CPLError(
831 : CE_Failure, CPLE_AppDefined,
832 : "dataType=%s, bitsPerSample=%d: not a supported configuration.",
833 : pszDataType, nBitsPerSample);
834 0 : return nullptr;
835 : }
836 :
837 : /* while we're at it, extract the pixel spacing information */
838 6 : const char *pszPixelSpacing = CPLGetXMLValue(
839 : psImageAttributes, "rasterAttributes.sampledPixelSpacing", "UNK");
840 6 : poDS->SetMetadataItem("PIXEL_SPACING", pszPixelSpacing);
841 :
842 6 : const char *pszLineSpacing = CPLGetXMLValue(
843 : psImageAttributes, "rasterAttributes.sampledLineSpacing", "UNK");
844 6 : poDS->SetMetadataItem("LINE_SPACING", pszLineSpacing);
845 :
846 : /* -------------------------------------------------------------------- */
847 : /* Open each of the data files as a complex band. */
848 : /* -------------------------------------------------------------------- */
849 12 : CPLString osBeta0LUT;
850 12 : CPLString osGammaLUT;
851 12 : CPLString osSigma0LUT;
852 :
853 12 : std::string osPath = CPLGetPathSafe(osMDFilename);
854 : ;
855 6 : const int nFLen = static_cast<int>(osMDFilename.size());
856 :
857 6 : CPLXMLNode *psNode = psImageAttributes->psChild;
858 50 : for (; psNode != nullptr; psNode = psNode->psNext)
859 : {
860 44 : if (psNode->eType != CXT_Element ||
861 44 : !(EQUAL(psNode->pszValue, "fullResolutionImageData") ||
862 33 : EQUAL(psNode->pszValue, "lookupTable")))
863 33 : continue;
864 :
865 29 : if (EQUAL(psNode->pszValue, "lookupTable") && bCanCalib)
866 : {
867 : /* Determine which incidence angle correction this is */
868 : const char *pszLUTType =
869 18 : CPLGetXMLValue(psNode, "incidenceAngleCorrection", "");
870 18 : const char *pszLUTFile = CPLGetXMLValue(psNode, "", "");
871 18 : if (CPLHasPathTraversal(pszLUTFile))
872 : {
873 0 : CPLError(CE_Failure, CPLE_NotSupported,
874 : "Path traversal detected in %s", pszLUTFile);
875 0 : return nullptr;
876 : }
877 : CPLString osLUTFilePath =
878 36 : CPLFormFilenameSafe(osPath.c_str(), pszLUTFile, nullptr);
879 :
880 18 : if (EQUAL(pszLUTType, ""))
881 0 : continue;
882 24 : else if (EQUAL(pszLUTType, "Beta Nought") &&
883 6 : IsValidXMLFile(osPath.c_str(), pszLUTFile))
884 : {
885 12 : poDS->papszExtraFiles =
886 6 : CSLAddString(poDS->papszExtraFiles, osLUTFilePath);
887 :
888 6 : const size_t nBufLen = nFLen + 27;
889 6 : char *pszBuf = static_cast<char *>(CPLMalloc(nBufLen));
890 6 : osBeta0LUT = pszLUTFile;
891 6 : poDS->SetMetadataItem("BETA_NOUGHT_LUT", pszLUTFile);
892 :
893 6 : snprintf(pszBuf, nBufLen, "RADARSAT_2_CALIB:BETA0:%s",
894 : osMDFilename.c_str());
895 6 : poDS->papszSubDatasets = CSLSetNameValue(
896 6 : poDS->papszSubDatasets, "SUBDATASET_3_NAME", pszBuf);
897 12 : poDS->papszSubDatasets =
898 6 : CSLSetNameValue(poDS->papszSubDatasets, "SUBDATASET_3_DESC",
899 : "Beta Nought calibrated");
900 6 : CPLFree(pszBuf);
901 : }
902 18 : else if (EQUAL(pszLUTType, "Sigma Nought") &&
903 6 : IsValidXMLFile(osPath.c_str(), pszLUTFile))
904 : {
905 12 : poDS->papszExtraFiles =
906 6 : CSLAddString(poDS->papszExtraFiles, osLUTFilePath);
907 :
908 6 : const size_t nBufLen = nFLen + 27;
909 6 : char *pszBuf = static_cast<char *>(CPLMalloc(nBufLen));
910 6 : osSigma0LUT = pszLUTFile;
911 6 : poDS->SetMetadataItem("SIGMA_NOUGHT_LUT", pszLUTFile);
912 :
913 6 : snprintf(pszBuf, nBufLen, "RADARSAT_2_CALIB:SIGMA0:%s",
914 : osMDFilename.c_str());
915 6 : poDS->papszSubDatasets = CSLSetNameValue(
916 6 : poDS->papszSubDatasets, "SUBDATASET_2_NAME", pszBuf);
917 12 : poDS->papszSubDatasets =
918 6 : CSLSetNameValue(poDS->papszSubDatasets, "SUBDATASET_2_DESC",
919 : "Sigma Nought calibrated");
920 6 : CPLFree(pszBuf);
921 : }
922 12 : else if (EQUAL(pszLUTType, "Gamma") &&
923 6 : IsValidXMLFile(osPath.c_str(), pszLUTFile))
924 : {
925 12 : poDS->papszExtraFiles =
926 6 : CSLAddString(poDS->papszExtraFiles, osLUTFilePath);
927 :
928 6 : const size_t nBufLen = nFLen + 27;
929 6 : char *pszBuf = static_cast<char *>(CPLMalloc(nBufLen));
930 6 : osGammaLUT = pszLUTFile;
931 6 : poDS->SetMetadataItem("GAMMA_LUT", pszLUTFile);
932 6 : snprintf(pszBuf, nBufLen, "RADARSAT_2_CALIB:GAMMA:%s",
933 : osMDFilename.c_str());
934 6 : poDS->papszSubDatasets = CSLSetNameValue(
935 6 : poDS->papszSubDatasets, "SUBDATASET_4_NAME", pszBuf);
936 12 : poDS->papszSubDatasets =
937 6 : CSLSetNameValue(poDS->papszSubDatasets, "SUBDATASET_4_DESC",
938 : "Gamma calibrated");
939 6 : CPLFree(pszBuf);
940 : }
941 18 : continue;
942 : }
943 :
944 : /* --------------------------------------------------------------------
945 : */
946 : /* Fetch filename. */
947 : /* --------------------------------------------------------------------
948 : */
949 11 : const char *pszBasename = CPLGetXMLValue(psNode, "", "");
950 11 : if (*pszBasename == '\0')
951 0 : continue;
952 11 : std::string osPathImage = osPath;
953 11 : std::string osBasename = pszBasename;
954 22 : if (STARTS_WITH(osBasename.c_str(), "../") ||
955 11 : STARTS_WITH(osBasename.c_str(), "..\\"))
956 : {
957 0 : osPathImage = CPLGetPathSafe(osPath.c_str());
958 0 : osBasename = osBasename.substr(strlen("../"));
959 : }
960 11 : if (CPLHasPathTraversal(osBasename.c_str()))
961 : {
962 0 : CPLError(CE_Failure, CPLE_NotSupported,
963 : "Path traversal detected in %s", osBasename.c_str());
964 0 : return nullptr;
965 : }
966 : /* --------------------------------------------------------------------
967 : */
968 : /* Form full filename (path of product.xml + basename). */
969 : /* --------------------------------------------------------------------
970 : */
971 : const std::string osFullname = CPLFormFilenameSafe(
972 11 : osPathImage.c_str(), osBasename.c_str(), nullptr);
973 :
974 : /* --------------------------------------------------------------------
975 : */
976 : /* Try and open the file. */
977 : /* --------------------------------------------------------------------
978 : */
979 11 : GDALDataset *poBandFile = GDALDataset::Open(
980 : osFullname.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR);
981 11 : if (poBandFile == nullptr)
982 : {
983 0 : continue;
984 : }
985 11 : if (poBandFile->GetRasterCount() == 0)
986 : {
987 0 : delete poBandFile;
988 0 : continue;
989 : }
990 :
991 : /* Some CFloat32 NITF files have nBitsPerSample incorrectly reported */
992 : /* as 16, and get misinterpreted as CInt16. Check the underlying NITF
993 : */
994 : /* and override if this is the case. */
995 11 : if (poBandFile->GetRasterBand(1)->GetRasterDataType() == GDT_CFloat32)
996 0 : eDataType = GDT_CFloat32;
997 :
998 11 : BandMapping b = GetBandFileMapping(eDataType, poBandFile);
999 11 : const bool twoBandComplex = b == TWOBANDCOMPLEX;
1000 :
1001 22 : poDS->papszExtraFiles =
1002 11 : CSLAddString(poDS->papszExtraFiles, osFullname.c_str());
1003 :
1004 : /* --------------------------------------------------------------------
1005 : */
1006 : /* Create the band. */
1007 : /* --------------------------------------------------------------------
1008 : */
1009 11 : if (eCalib == None || eCalib == Uncalib)
1010 : {
1011 : RS2RasterBand *poBand = new RS2RasterBand(
1012 9 : poDS.get(), eDataType, CPLGetXMLValue(psNode, "pole", ""),
1013 9 : poBandFile, twoBandComplex);
1014 :
1015 9 : poDS->SetBand(poDS->GetRasterCount() + 1, poBand);
1016 : }
1017 : else
1018 : {
1019 2 : const char *pszLUT = nullptr;
1020 2 : switch (eCalib)
1021 : {
1022 0 : case Sigma0:
1023 0 : pszLUT = osSigma0LUT;
1024 0 : break;
1025 2 : case Beta0:
1026 2 : pszLUT = osBeta0LUT;
1027 2 : break;
1028 0 : case Gamma:
1029 0 : pszLUT = osGammaLUT;
1030 0 : break;
1031 0 : default:
1032 : /* we should bomb gracefully... */
1033 0 : pszLUT = osSigma0LUT;
1034 : }
1035 : RS2CalibRasterBand *poBand = new RS2CalibRasterBand(
1036 2 : poDS.get(), CPLGetXMLValue(psNode, "pole", ""), eDataType,
1037 : poBandFile, eCalib,
1038 2 : CPLFormFilenameSafe(osPath.c_str(), pszLUT, nullptr).c_str());
1039 2 : poDS->SetBand(poDS->GetRasterCount() + 1, poBand);
1040 : }
1041 : }
1042 :
1043 6 : if (poDS->papszSubDatasets != nullptr && eCalib == None)
1044 : {
1045 5 : const size_t nBufLen = nFLen + 28;
1046 5 : char *pszBuf = static_cast<char *>(CPLMalloc(nBufLen));
1047 5 : snprintf(pszBuf, nBufLen, "RADARSAT_2_CALIB:UNCALIB:%s",
1048 : osMDFilename.c_str());
1049 5 : poDS->papszSubDatasets = CSLSetNameValue(poDS->papszSubDatasets,
1050 : "SUBDATASET_1_NAME", pszBuf);
1051 10 : poDS->papszSubDatasets =
1052 5 : CSLSetNameValue(poDS->papszSubDatasets, "SUBDATASET_1_DESC",
1053 : "Uncalibrated digital numbers");
1054 5 : CPLFree(pszBuf);
1055 : }
1056 1 : else if (poDS->papszSubDatasets != nullptr)
1057 : {
1058 1 : CSLDestroy(poDS->papszSubDatasets);
1059 1 : poDS->papszSubDatasets = nullptr;
1060 : }
1061 :
1062 : /* -------------------------------------------------------------------- */
1063 : /* Set the appropriate MATRIX_REPRESENTATION. */
1064 : /* -------------------------------------------------------------------- */
1065 :
1066 6 : if (poDS->GetRasterCount() == 4 &&
1067 0 : (eDataType == GDT_CInt16 || eDataType == GDT_CFloat32))
1068 : {
1069 0 : poDS->SetMetadataItem("MATRIX_REPRESENTATION", "SCATTERING");
1070 : }
1071 :
1072 : /* -------------------------------------------------------------------- */
1073 : /* Collect a few useful metadata items */
1074 : /* -------------------------------------------------------------------- */
1075 :
1076 : CPLXMLNode *psSourceAttrs =
1077 6 : CPLGetXMLNode(psProduct, "=product.sourceAttributes");
1078 6 : const char *pszItem = CPLGetXMLValue(psSourceAttrs, "satellite", "");
1079 6 : poDS->SetMetadataItem("SATELLITE_IDENTIFIER", pszItem);
1080 :
1081 6 : pszItem = CPLGetXMLValue(psSourceAttrs, "sensor", "");
1082 6 : poDS->SetMetadataItem("SENSOR_IDENTIFIER", pszItem);
1083 :
1084 6 : if (psSourceAttrs != nullptr)
1085 : {
1086 : /* Get beam mode mnemonic */
1087 6 : pszItem = CPLGetXMLValue(psSourceAttrs, "beamModeMnemonic", "UNK");
1088 6 : poDS->SetMetadataItem("BEAM_MODE", pszItem);
1089 6 : pszItem = CPLGetXMLValue(psSourceAttrs, "rawDataStartTime", "UNK");
1090 6 : poDS->SetMetadataItem("ACQUISITION_START_TIME", pszItem);
1091 :
1092 : pszItem =
1093 6 : CPLGetXMLValue(psSourceAttrs, "inputDatasetFacilityId", "UNK");
1094 6 : poDS->SetMetadataItem("FACILITY_IDENTIFIER", pszItem);
1095 :
1096 6 : pszItem = CPLGetXMLValue(
1097 : psSourceAttrs, "orbitAndAttitude.orbitInformation.passDirection",
1098 : "UNK");
1099 6 : poDS->SetMetadataItem("ORBIT_DIRECTION", pszItem);
1100 6 : pszItem = CPLGetXMLValue(
1101 : psSourceAttrs, "orbitAndAttitude.orbitInformation.orbitDataSource",
1102 : "UNK");
1103 6 : poDS->SetMetadataItem("ORBIT_DATA_SOURCE", pszItem);
1104 6 : pszItem = CPLGetXMLValue(
1105 : psSourceAttrs, "orbitAndAttitude.orbitInformation.orbitDataFile",
1106 : "UNK");
1107 6 : poDS->SetMetadataItem("ORBIT_DATA_FILE", pszItem);
1108 : }
1109 :
1110 : CPLXMLNode *psSarProcessingInformation =
1111 6 : CPLGetXMLNode(psProduct, "=product.imageGenerationParameters");
1112 :
1113 6 : if (psSarProcessingInformation != nullptr)
1114 : {
1115 : /* Get incidence angle information */
1116 6 : pszItem = CPLGetXMLValue(
1117 : psSarProcessingInformation,
1118 : "sarProcessingInformation.incidenceAngleNearRange", "UNK");
1119 6 : poDS->SetMetadataItem("NEAR_RANGE_INCIDENCE_ANGLE", pszItem);
1120 :
1121 6 : pszItem = CPLGetXMLValue(
1122 : psSarProcessingInformation,
1123 : "sarProcessingInformation.incidenceAngleFarRange", "UNK");
1124 6 : poDS->SetMetadataItem("FAR_RANGE_INCIDENCE_ANGLE", pszItem);
1125 :
1126 6 : pszItem = CPLGetXMLValue(psSarProcessingInformation,
1127 : "sarProcessingInformation.slantRangeNearEdge",
1128 : "UNK");
1129 6 : poDS->SetMetadataItem("SLANT_RANGE_NEAR_EDGE", pszItem);
1130 :
1131 6 : pszItem = CPLGetXMLValue(
1132 : psSarProcessingInformation,
1133 : "sarProcessingInformation.zeroDopplerTimeFirstLine", "UNK");
1134 6 : poDS->SetMetadataItem("FIRST_LINE_TIME", pszItem);
1135 :
1136 6 : pszItem = CPLGetXMLValue(
1137 : psSarProcessingInformation,
1138 : "sarProcessingInformation.zeroDopplerTimeLastLine", "UNK");
1139 6 : poDS->SetMetadataItem("LAST_LINE_TIME", pszItem);
1140 :
1141 : pszItem =
1142 6 : CPLGetXMLValue(psSarProcessingInformation,
1143 : "generalProcessingInformation.productType", "UNK");
1144 6 : poDS->SetMetadataItem("PRODUCT_TYPE", pszItem);
1145 :
1146 6 : pszItem = CPLGetXMLValue(
1147 : psSarProcessingInformation,
1148 : "generalProcessingInformation.processingFacility", "UNK");
1149 6 : poDS->SetMetadataItem("PROCESSING_FACILITY", pszItem);
1150 :
1151 6 : pszItem = CPLGetXMLValue(psSarProcessingInformation,
1152 : "generalProcessingInformation.processingTime",
1153 : "UNK");
1154 6 : poDS->SetMetadataItem("PROCESSING_TIME", pszItem);
1155 : }
1156 :
1157 : /*--------------------------------------------------------------------- */
1158 : /* Collect Map projection/Geotransform information, if present */
1159 : /* -------------------------------------------------------------------- */
1160 : CPLXMLNode *psMapProjection =
1161 6 : CPLGetXMLNode(psImageAttributes, "geographicInformation.mapProjection");
1162 :
1163 6 : if (psMapProjection != nullptr)
1164 : {
1165 : CPLXMLNode *psPos =
1166 0 : CPLGetXMLNode(psMapProjection, "positioningInformation");
1167 :
1168 : pszItem =
1169 0 : CPLGetXMLValue(psMapProjection, "mapProjectionDescriptor", "UNK");
1170 0 : poDS->SetMetadataItem("MAP_PROJECTION_DESCRIPTOR", pszItem);
1171 :
1172 : pszItem =
1173 0 : CPLGetXMLValue(psMapProjection, "mapProjectionOrientation", "UNK");
1174 0 : poDS->SetMetadataItem("MAP_PROJECTION_ORIENTATION", pszItem);
1175 :
1176 0 : pszItem = CPLGetXMLValue(psMapProjection, "resamplingKernel", "UNK");
1177 0 : poDS->SetMetadataItem("RESAMPLING_KERNEL", pszItem);
1178 :
1179 0 : pszItem = CPLGetXMLValue(psMapProjection, "satelliteHeading", "UNK");
1180 0 : poDS->SetMetadataItem("SATELLITE_HEADING", pszItem);
1181 :
1182 0 : if (psPos != nullptr)
1183 : {
1184 0 : const double tl_x = CPLStrtod(
1185 : CPLGetXMLValue(psPos, "upperLeftCorner.mapCoordinate.easting",
1186 : "0.0"),
1187 : nullptr);
1188 0 : const double tl_y = CPLStrtod(
1189 : CPLGetXMLValue(psPos, "upperLeftCorner.mapCoordinate.northing",
1190 : "0.0"),
1191 : nullptr);
1192 0 : const double bl_x = CPLStrtod(
1193 : CPLGetXMLValue(psPos, "lowerLeftCorner.mapCoordinate.easting",
1194 : "0.0"),
1195 : nullptr);
1196 0 : const double bl_y = CPLStrtod(
1197 : CPLGetXMLValue(psPos, "lowerLeftCorner.mapCoordinate.northing",
1198 : "0.0"),
1199 : nullptr);
1200 0 : const double tr_x = CPLStrtod(
1201 : CPLGetXMLValue(psPos, "upperRightCorner.mapCoordinate.easting",
1202 : "0.0"),
1203 : nullptr);
1204 0 : const double tr_y = CPLStrtod(
1205 : CPLGetXMLValue(psPos, "upperRightCorner.mapCoordinate.northing",
1206 : "0.0"),
1207 : nullptr);
1208 0 : poDS->m_gt[1] = (tr_x - tl_x) / (poDS->nRasterXSize - 1);
1209 0 : poDS->m_gt[4] = (tr_y - tl_y) / (poDS->nRasterXSize - 1);
1210 0 : poDS->m_gt[2] = (bl_x - tl_x) / (poDS->nRasterYSize - 1);
1211 0 : poDS->m_gt[5] = (bl_y - tl_y) / (poDS->nRasterYSize - 1);
1212 0 : poDS->m_gt[0] = (tl_x - 0.5 * poDS->m_gt[1] - 0.5 * poDS->m_gt[2]);
1213 0 : poDS->m_gt[3] = (tl_y - 0.5 * poDS->m_gt[4] - 0.5 * poDS->m_gt[5]);
1214 :
1215 : /* Use bottom right pixel to test geotransform */
1216 0 : const double br_x = CPLStrtod(
1217 : CPLGetXMLValue(psPos, "lowerRightCorner.mapCoordinate.easting",
1218 : "0.0"),
1219 : nullptr);
1220 0 : const double br_y = CPLStrtod(
1221 : CPLGetXMLValue(psPos, "lowerRightCorner.mapCoordinate.northing",
1222 : "0.0"),
1223 : nullptr);
1224 0 : const double testx = poDS->m_gt[0] +
1225 0 : poDS->m_gt[1] * (poDS->nRasterXSize - 0.5) +
1226 0 : poDS->m_gt[2] * (poDS->nRasterYSize - 0.5);
1227 0 : const double testy = poDS->m_gt[3] +
1228 0 : poDS->m_gt[4] * (poDS->nRasterXSize - 0.5) +
1229 0 : poDS->m_gt[5] * (poDS->nRasterYSize - 0.5);
1230 :
1231 : /* Give 1/4 pixel numerical error leeway in calculating location
1232 : based on affine transform */
1233 0 : if ((fabs(testx - br_x) >
1234 0 : fabs(0.25 * (poDS->m_gt[1] + poDS->m_gt[2]))) ||
1235 0 : (fabs(testy - br_y) >
1236 0 : fabs(0.25 * (poDS->m_gt[4] + poDS->m_gt[5]))))
1237 : {
1238 0 : CPLError(CE_Warning, CPLE_AppDefined,
1239 : "Unexpected error in calculating affine transform: "
1240 : "corner coordinates inconsistent.");
1241 : }
1242 : else
1243 : {
1244 0 : poDS->bHaveGeoTransform = TRUE;
1245 : }
1246 : }
1247 : }
1248 :
1249 : /* -------------------------------------------------------------------- */
1250 : /* Collect Projection String Information */
1251 : /* -------------------------------------------------------------------- */
1252 : CPLXMLNode *psEllipsoid =
1253 6 : CPLGetXMLNode(psImageAttributes,
1254 : "geographicInformation.referenceEllipsoidParameters");
1255 :
1256 6 : if (psEllipsoid != nullptr)
1257 : {
1258 12 : OGRSpatialReference oLL, oPrj;
1259 6 : oLL.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1260 6 : oPrj.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1261 :
1262 : const char *pszGeodeticTerrainHeight =
1263 6 : CPLGetXMLValue(psEllipsoid, "geodeticTerrainHeight", "UNK");
1264 6 : poDS->SetMetadataItem("GEODETIC_TERRAIN_HEIGHT",
1265 6 : pszGeodeticTerrainHeight);
1266 :
1267 : const char *pszEllipsoidName =
1268 6 : CPLGetXMLValue(psEllipsoid, "ellipsoidName", "");
1269 : double minor_axis =
1270 6 : CPLAtof(CPLGetXMLValue(psEllipsoid, "semiMinorAxis", "0.0"));
1271 : double major_axis =
1272 6 : CPLAtof(CPLGetXMLValue(psEllipsoid, "semiMajorAxis", "0.0"));
1273 :
1274 6 : if (EQUAL(pszEllipsoidName, "") || (minor_axis == 0.0) ||
1275 : (major_axis == 0.0))
1276 : {
1277 0 : CPLError(CE_Warning, CPLE_AppDefined,
1278 : "Warning- incomplete"
1279 : " ellipsoid information. Using wgs-84 parameters.\n");
1280 0 : oLL.SetWellKnownGeogCS("WGS84");
1281 0 : oPrj.SetWellKnownGeogCS("WGS84");
1282 : }
1283 6 : else if (EQUAL(pszEllipsoidName, "WGS84") ||
1284 1 : EQUAL(pszEllipsoidName, "WGS 1984"))
1285 : {
1286 6 : oLL.SetWellKnownGeogCS("WGS84");
1287 6 : oPrj.SetWellKnownGeogCS("WGS84");
1288 : }
1289 : else
1290 : {
1291 0 : const double inv_flattening =
1292 0 : major_axis / (major_axis - minor_axis);
1293 0 : oLL.SetGeogCS("", "", pszEllipsoidName, major_axis, inv_flattening);
1294 0 : oPrj.SetGeogCS("", "", pszEllipsoidName, major_axis,
1295 : inv_flattening);
1296 : }
1297 :
1298 6 : if (psMapProjection != nullptr)
1299 : {
1300 : const char *pszProj =
1301 0 : CPLGetXMLValue(psMapProjection, "mapProjectionDescriptor", "");
1302 0 : bool bUseProjInfo = false;
1303 :
1304 : CPLXMLNode *psUtmParams =
1305 0 : CPLGetXMLNode(psMapProjection, "utmProjectionParameters");
1306 :
1307 : CPLXMLNode *psNspParams =
1308 0 : CPLGetXMLNode(psMapProjection, "nspProjectionParameters");
1309 :
1310 0 : if ((psUtmParams != nullptr) && poDS->bHaveGeoTransform)
1311 : {
1312 : /* double origEasting, origNorthing; */
1313 0 : bool bNorth = true;
1314 :
1315 : const int utmZone =
1316 0 : atoi(CPLGetXMLValue(psUtmParams, "utmZone", ""));
1317 : const char *pszHemisphere =
1318 0 : CPLGetXMLValue(psUtmParams, "hemisphere", "");
1319 : #if 0
1320 : origEasting = CPLStrtod(CPLGetXMLValue(
1321 : psUtmParams, "mapOriginFalseEasting", "0.0" ), nullptr);
1322 : origNorthing = CPLStrtod(CPLGetXMLValue(
1323 : psUtmParams, "mapOriginFalseNorthing", "0.0" ), nullptr);
1324 : #endif
1325 0 : if (STARTS_WITH_CI(pszHemisphere, "southern"))
1326 0 : bNorth = FALSE;
1327 :
1328 0 : if (STARTS_WITH_CI(pszProj, "UTM"))
1329 : {
1330 0 : oPrj.SetUTM(utmZone, bNorth);
1331 0 : bUseProjInfo = true;
1332 : }
1333 : }
1334 0 : else if ((psNspParams != nullptr) && poDS->bHaveGeoTransform)
1335 : {
1336 0 : const double origEasting = CPLStrtod(
1337 : CPLGetXMLValue(psNspParams, "mapOriginFalseEasting", "0.0"),
1338 : nullptr);
1339 : const double origNorthing =
1340 0 : CPLStrtod(CPLGetXMLValue(psNspParams,
1341 : "mapOriginFalseNorthing", "0.0"),
1342 : nullptr);
1343 0 : const double copLong = CPLStrtod(
1344 : CPLGetXMLValue(psNspParams, "centerOfProjectionLongitude",
1345 : "0.0"),
1346 : nullptr);
1347 0 : const double copLat = CPLStrtod(
1348 : CPLGetXMLValue(psNspParams, "centerOfProjectionLatitude",
1349 : "0.0"),
1350 : nullptr);
1351 0 : const double sP1 = CPLStrtod(
1352 : CPLGetXMLValue(psNspParams, "standardParallels1", "0.0"),
1353 : nullptr);
1354 0 : const double sP2 = CPLStrtod(
1355 : CPLGetXMLValue(psNspParams, "standardParallels2", "0.0"),
1356 : nullptr);
1357 :
1358 0 : if (STARTS_WITH_CI(pszProj, "ARC"))
1359 : {
1360 : /* Albers Conical Equal Area */
1361 0 : oPrj.SetACEA(sP1, sP2, copLat, copLong, origEasting,
1362 : origNorthing);
1363 0 : bUseProjInfo = true;
1364 : }
1365 0 : else if (STARTS_WITH_CI(pszProj, "LCC"))
1366 : {
1367 : /* Lambert Conformal Conic */
1368 0 : oPrj.SetLCC(sP1, sP2, copLat, copLong, origEasting,
1369 : origNorthing);
1370 0 : bUseProjInfo = true;
1371 : }
1372 0 : else if (STARTS_WITH_CI(pszProj, "STPL"))
1373 : {
1374 : /* State Plate
1375 : ASSUMPTIONS: "zone" in product.xml matches USGS
1376 : definition as expected by ogr spatial reference; NAD83
1377 : zones (versus NAD27) are assumed. */
1378 :
1379 : const int nSPZone =
1380 0 : atoi(CPLGetXMLValue(psNspParams, "zone", "1"));
1381 :
1382 0 : oPrj.SetStatePlane(nSPZone, TRUE, nullptr, 0.0);
1383 0 : bUseProjInfo = true;
1384 : }
1385 : }
1386 :
1387 0 : if (bUseProjInfo)
1388 : {
1389 0 : poDS->m_oSRS = std::move(oPrj);
1390 : }
1391 : else
1392 : {
1393 0 : CPLError(CE_Warning, CPLE_AppDefined,
1394 : "Unable to interpret "
1395 : "projection information; check mapProjection info in "
1396 : "product.xml!");
1397 : }
1398 : }
1399 :
1400 6 : poDS->m_oGCPSRS = std::move(oLL);
1401 : }
1402 :
1403 : /* -------------------------------------------------------------------- */
1404 : /* Collect GCPs. */
1405 : /* -------------------------------------------------------------------- */
1406 6 : CPLXMLNode *psGeoGrid = CPLGetXMLNode(
1407 : psImageAttributes, "geographicInformation.geolocationGrid");
1408 :
1409 6 : if (psGeoGrid != nullptr)
1410 : {
1411 : /* count GCPs */
1412 6 : poDS->nGCPCount = 0;
1413 :
1414 180 : for (psNode = psGeoGrid->psChild; psNode != nullptr;
1415 174 : psNode = psNode->psNext)
1416 : {
1417 174 : if (EQUAL(psNode->pszValue, "imageTiePoint"))
1418 174 : poDS->nGCPCount++;
1419 : }
1420 :
1421 12 : poDS->pasGCPList = reinterpret_cast<GDAL_GCP *>(
1422 6 : CPLCalloc(sizeof(GDAL_GCP), poDS->nGCPCount));
1423 :
1424 6 : poDS->nGCPCount = 0;
1425 :
1426 180 : for (psNode = psGeoGrid->psChild; psNode != nullptr;
1427 174 : psNode = psNode->psNext)
1428 : {
1429 174 : GDAL_GCP *psGCP = poDS->pasGCPList + poDS->nGCPCount;
1430 :
1431 174 : if (!EQUAL(psNode->pszValue, "imageTiePoint"))
1432 0 : continue;
1433 :
1434 174 : poDS->nGCPCount++;
1435 :
1436 : char szID[32];
1437 174 : snprintf(szID, sizeof(szID), "%d", poDS->nGCPCount);
1438 174 : psGCP->pszId = CPLStrdup(szID);
1439 174 : psGCP->pszInfo = CPLStrdup("");
1440 174 : psGCP->dfGCPPixel =
1441 174 : CPLAtof(CPLGetXMLValue(psNode, "imageCoordinate.pixel", "0")) +
1442 : 0.5;
1443 174 : psGCP->dfGCPLine =
1444 174 : CPLAtof(CPLGetXMLValue(psNode, "imageCoordinate.line", "0")) +
1445 : 0.5;
1446 174 : psGCP->dfGCPX = CPLAtof(
1447 : CPLGetXMLValue(psNode, "geodeticCoordinate.longitude", ""));
1448 174 : psGCP->dfGCPY = CPLAtof(
1449 : CPLGetXMLValue(psNode, "geodeticCoordinate.latitude", ""));
1450 174 : psGCP->dfGCPZ = CPLAtof(
1451 : CPLGetXMLValue(psNode, "geodeticCoordinate.height", ""));
1452 : }
1453 : }
1454 :
1455 : /* -------------------------------------------------------------------- */
1456 : /* Collect RPC. */
1457 : /* -------------------------------------------------------------------- */
1458 6 : CPLXMLNode *psRationalFunctions = CPLGetXMLNode(
1459 : psImageAttributes, "geographicInformation.rationalFunctions");
1460 6 : if (psRationalFunctions != nullptr)
1461 : {
1462 6 : char **papszRPC = nullptr;
1463 : static const char *const apszXMLToGDALMapping[] = {
1464 : "biasError",
1465 : "ERR_BIAS",
1466 : "randomError",
1467 : "ERR_RAND",
1468 : //"lineFitQuality", "????",
1469 : //"pixelFitQuality", "????",
1470 : "lineOffset",
1471 : "LINE_OFF",
1472 : "pixelOffset",
1473 : "SAMP_OFF",
1474 : "latitudeOffset",
1475 : "LAT_OFF",
1476 : "longitudeOffset",
1477 : "LONG_OFF",
1478 : "heightOffset",
1479 : "HEIGHT_OFF",
1480 : "lineScale",
1481 : "LINE_SCALE",
1482 : "pixelScale",
1483 : "SAMP_SCALE",
1484 : "latitudeScale",
1485 : "LAT_SCALE",
1486 : "longitudeScale",
1487 : "LONG_SCALE",
1488 : "heightScale",
1489 : "HEIGHT_SCALE",
1490 : "lineNumeratorCoefficients",
1491 : "LINE_NUM_COEFF",
1492 : "lineDenominatorCoefficients",
1493 : "LINE_DEN_COEFF",
1494 : "pixelNumeratorCoefficients",
1495 : "SAMP_NUM_COEFF",
1496 : "pixelDenominatorCoefficients",
1497 : "SAMP_DEN_COEFF",
1498 : };
1499 102 : for (size_t i = 0; i < CPL_ARRAYSIZE(apszXMLToGDALMapping); i += 2)
1500 : {
1501 192 : const char *pszValue = CPLGetXMLValue(
1502 96 : psRationalFunctions, apszXMLToGDALMapping[i], nullptr);
1503 96 : if (pszValue)
1504 96 : papszRPC = CSLSetNameValue(
1505 96 : papszRPC, apszXMLToGDALMapping[i + 1], pszValue);
1506 : }
1507 6 : poDS->GDALDataset::SetMetadata(papszRPC, "RPC");
1508 6 : CSLDestroy(papszRPC);
1509 : }
1510 :
1511 : /* -------------------------------------------------------------------- */
1512 : /* Initialize any PAM information. */
1513 : /* -------------------------------------------------------------------- */
1514 12 : CPLString osDescription;
1515 :
1516 6 : switch (eCalib)
1517 : {
1518 0 : case Sigma0:
1519 : osDescription.Printf("RADARSAT_2_CALIB:SIGMA0:%s",
1520 0 : osMDFilename.c_str());
1521 0 : break;
1522 1 : case Beta0:
1523 : osDescription.Printf("RADARSAT_2_CALIB:BETA0:%s",
1524 1 : osMDFilename.c_str());
1525 1 : break;
1526 0 : case Gamma:
1527 : osDescription.Printf("RADARSAT_2_CALIB:GAMMA0:%s",
1528 0 : osMDFilename.c_str());
1529 0 : break;
1530 0 : case Uncalib:
1531 : osDescription.Printf("RADARSAT_2_CALIB:UNCALIB:%s",
1532 0 : osMDFilename.c_str());
1533 0 : break;
1534 5 : default:
1535 5 : osDescription = osMDFilename;
1536 : }
1537 :
1538 6 : if (eCalib != None)
1539 1 : poDS->papszExtraFiles =
1540 1 : CSLAddString(poDS->papszExtraFiles, osMDFilename);
1541 :
1542 : /* -------------------------------------------------------------------- */
1543 : /* Initialize any PAM information. */
1544 : /* -------------------------------------------------------------------- */
1545 6 : poDS->SetDescription(osDescription);
1546 :
1547 6 : poDS->SetPhysicalFilename(osMDFilename);
1548 6 : poDS->SetSubdatasetName(osDescription);
1549 :
1550 6 : poDS->TryLoadXML();
1551 :
1552 : /* -------------------------------------------------------------------- */
1553 : /* Check for overviews. */
1554 : /* -------------------------------------------------------------------- */
1555 6 : poDS->oOvManager.Initialize(poDS.get(), ":::VIRTUAL:::");
1556 :
1557 6 : return poDS.release();
1558 : }
1559 :
1560 : /************************************************************************/
1561 : /* GetGCPCount() */
1562 : /************************************************************************/
1563 :
1564 0 : int RS2Dataset::GetGCPCount()
1565 :
1566 : {
1567 0 : return nGCPCount;
1568 : }
1569 :
1570 : /************************************************************************/
1571 : /* GetGCPSpatialRef() */
1572 : /************************************************************************/
1573 :
1574 0 : const OGRSpatialReference *RS2Dataset::GetGCPSpatialRef() const
1575 :
1576 : {
1577 0 : return m_oGCPSRS.IsEmpty() ? nullptr : &m_oGCPSRS;
1578 : }
1579 :
1580 : /************************************************************************/
1581 : /* GetGCPs() */
1582 : /************************************************************************/
1583 :
1584 0 : const GDAL_GCP *RS2Dataset::GetGCPs()
1585 :
1586 : {
1587 0 : return pasGCPList;
1588 : }
1589 :
1590 : /************************************************************************/
1591 : /* GetSpatialRef() */
1592 : /************************************************************************/
1593 :
1594 0 : const OGRSpatialReference *RS2Dataset::GetSpatialRef() const
1595 :
1596 : {
1597 0 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
1598 : }
1599 :
1600 : /************************************************************************/
1601 : /* GetGeoTransform() */
1602 : /************************************************************************/
1603 :
1604 0 : CPLErr RS2Dataset::GetGeoTransform(GDALGeoTransform >) const
1605 :
1606 : {
1607 0 : gt = m_gt;
1608 :
1609 0 : if (bHaveGeoTransform)
1610 0 : return CE_None;
1611 :
1612 0 : return CE_Failure;
1613 : }
1614 :
1615 : /************************************************************************/
1616 : /* GetMetadataDomainList() */
1617 : /************************************************************************/
1618 :
1619 0 : char **RS2Dataset::GetMetadataDomainList()
1620 : {
1621 0 : return BuildMetadataDomainList(GDALDataset::GetMetadataDomainList(), TRUE,
1622 0 : "SUBDATASETS", nullptr);
1623 : }
1624 :
1625 : /************************************************************************/
1626 : /* GetMetadata() */
1627 : /************************************************************************/
1628 :
1629 1 : char **RS2Dataset::GetMetadata(const char *pszDomain)
1630 :
1631 : {
1632 1 : if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS") &&
1633 0 : papszSubDatasets != nullptr)
1634 0 : return papszSubDatasets;
1635 :
1636 1 : return GDALDataset::GetMetadata(pszDomain);
1637 : }
1638 :
1639 : /************************************************************************/
1640 : /* GDALRegister_RS2() */
1641 : /************************************************************************/
1642 :
1643 2024 : void GDALRegister_RS2()
1644 :
1645 : {
1646 2024 : if (GDALGetDriverByName("RS2") != nullptr)
1647 283 : return;
1648 :
1649 1741 : GDALDriver *poDriver = new GDALDriver();
1650 :
1651 1741 : poDriver->SetDescription("RS2");
1652 1741 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1653 1741 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "RadarSat 2 XML Product");
1654 1741 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/rs2.html");
1655 1741 : poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
1656 1741 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1657 :
1658 1741 : poDriver->pfnOpen = RS2Dataset::Open;
1659 1741 : poDriver->pfnIdentify = RS2Dataset::Identify;
1660 :
1661 1741 : GetGDALDriverManager()->RegisterDriver(poDriver);
1662 : }
|