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