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