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