Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: PDS Driver; Planetary Data System Format
4 : * Purpose: Implementation of PDSDataset
5 : * Author: Trent Hare (thare@usgs.gov),
6 : * Robert Soricone (rsoricone@usgs.gov)
7 : *
8 : * NOTE: Original code authored by Trent and Robert and placed in the public
9 : * domain as per US government policy. I have (within my rights) appropriated
10 : * it and placed it under the following license. This is not intended to
11 : * diminish Trent and Roberts contribution.
12 : ******************************************************************************
13 : * Copyright (c) 2007, Frank Warmerdam <warmerdam@pobox.com>
14 : * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
15 : *
16 : * SPDX-License-Identifier: MIT
17 : ****************************************************************************/
18 :
19 : // Set up PDS NULL values
20 : constexpr int PDS_NULL1 = 0;
21 : constexpr int PDS_NULL2 = -32768;
22 : // #define NULL3 -0.3402822655089E+39
23 : // Same as ESRI_GRID_FLOAT_NO_DATA
24 : // #define NULL3 -340282346638528859811704183484516925440.0
25 : constexpr double PDS_NULL3 = -3.4028226550889044521e+38;
26 :
27 : #include "cpl_string.h"
28 : #include "gdal_frmts.h"
29 : #include "gdal_proxy.h"
30 : #include "nasakeywordhandler.h"
31 : #include "ogr_spatialref.h"
32 : #include "rawdataset.h"
33 : #include "cpl_safemaths.hpp"
34 : #include "vicardataset.h"
35 : #include "pdsdrivercore.h"
36 :
37 : enum PDSLayout
38 : {
39 : PDS_BSQ,
40 : PDS_BIP,
41 : PDS_BIL
42 : };
43 :
44 : /************************************************************************/
45 : /* ==================================================================== */
46 : /* PDSDataset */
47 : /* ==================================================================== */
48 : /************************************************************************/
49 :
50 : class PDSDataset final : public RawDataset
51 : {
52 : VSILFILE *fpImage; // image data file.
53 : GDALDataset *poCompressedDS;
54 :
55 : NASAKeywordHandler oKeywords;
56 :
57 : int bGotTransform;
58 : double adfGeoTransform[6];
59 :
60 : OGRSpatialReference m_oSRS{};
61 :
62 : CPLString osTempResult;
63 :
64 : CPLString osExternalCube;
65 : CPLString m_osImageFilename;
66 :
67 : CPLStringList m_aosPDSMD;
68 :
69 : void ParseSRS();
70 : int ParseCompressedImage();
71 : int ParseImage(const CPLString &osPrefix,
72 : const CPLString &osFilenamePrefix);
73 : static CPLString CleanString(const CPLString &osInput);
74 :
75 : const char *GetKeyword(const std::string &osPath,
76 : const char *pszDefault = "");
77 : const char *GetKeywordSub(const std::string &osPath, int iSubscript,
78 : const char *pszDefault = "");
79 : const char *GetKeywordUnit(const char *pszPath, int iSubscript,
80 : const char *pszDefault = "");
81 :
82 : protected:
83 : virtual int CloseDependentDatasets() override;
84 :
85 : CPLErr Close() override;
86 :
87 : public:
88 : PDSDataset();
89 : virtual ~PDSDataset();
90 :
91 : virtual CPLErr GetGeoTransform(double *padfTransform) override;
92 : const OGRSpatialReference *GetSpatialRef() const override;
93 :
94 : virtual char **GetFileList(void) override;
95 :
96 : virtual CPLErr IBuildOverviews(const char *, int, const int *, int,
97 : const int *, GDALProgressFunc, void *,
98 : CSLConstList papszOptions) override;
99 :
100 : virtual CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
101 : GDALDataType, int, BANDMAP_TYPE,
102 : GSpacing nPixelSpace, GSpacing nLineSpace,
103 : GSpacing nBandSpace,
104 : GDALRasterIOExtraArg *psExtraArg) override;
105 :
106 : bool GetRawBinaryLayout(GDALDataset::RawBinaryLayout &) override;
107 :
108 : char **GetMetadataDomainList() override;
109 : char **GetMetadata(const char *pszDomain = "") override;
110 :
111 : static GDALDataset *Open(GDALOpenInfo *);
112 : static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
113 : int nBands, GDALDataType eType,
114 : char **papszParamList);
115 : };
116 :
117 : /************************************************************************/
118 : /* PDSDataset() */
119 : /************************************************************************/
120 :
121 38 : PDSDataset::PDSDataset()
122 38 : : fpImage(nullptr), poCompressedDS(nullptr), bGotTransform(FALSE)
123 : {
124 38 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
125 38 : adfGeoTransform[0] = 0.0;
126 38 : adfGeoTransform[1] = 1.0;
127 38 : adfGeoTransform[2] = 0.0;
128 38 : adfGeoTransform[3] = 0.0;
129 38 : adfGeoTransform[4] = 0.0;
130 38 : adfGeoTransform[5] = 1.0;
131 38 : }
132 :
133 : /************************************************************************/
134 : /* ~PDSDataset() */
135 : /************************************************************************/
136 :
137 76 : PDSDataset::~PDSDataset()
138 :
139 : {
140 38 : PDSDataset::Close();
141 76 : }
142 :
143 : /************************************************************************/
144 : /* Close() */
145 : /************************************************************************/
146 :
147 74 : CPLErr PDSDataset::Close()
148 : {
149 74 : CPLErr eErr = CE_None;
150 74 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
151 : {
152 38 : if (PDSDataset::FlushCache(true) != CE_None)
153 0 : eErr = CE_Failure;
154 38 : if (fpImage)
155 : {
156 33 : if (VSIFCloseL(fpImage) != 0)
157 0 : eErr = CE_Failure;
158 : }
159 :
160 38 : PDSDataset::CloseDependentDatasets();
161 38 : if (GDALPamDataset::Close() != CE_None)
162 0 : eErr = CE_Failure;
163 : }
164 74 : return eErr;
165 : }
166 :
167 : /************************************************************************/
168 : /* CloseDependentDatasets() */
169 : /************************************************************************/
170 :
171 38 : int PDSDataset::CloseDependentDatasets()
172 : {
173 38 : int bHasDroppedRef = GDALPamDataset::CloseDependentDatasets();
174 :
175 38 : if (poCompressedDS)
176 : {
177 3 : bHasDroppedRef = FALSE;
178 3 : delete poCompressedDS;
179 3 : poCompressedDS = nullptr;
180 : }
181 :
182 286 : for (int iBand = 0; iBand < nBands; iBand++)
183 : {
184 248 : delete papoBands[iBand];
185 : }
186 38 : nBands = 0;
187 :
188 38 : return bHasDroppedRef;
189 : }
190 :
191 : /************************************************************************/
192 : /* GetFileList() */
193 : /************************************************************************/
194 :
195 13 : char **PDSDataset::GetFileList()
196 :
197 : {
198 13 : char **papszFileList = RawDataset::GetFileList();
199 :
200 13 : if (poCompressedDS != nullptr)
201 : {
202 2 : char **papszCFileList = poCompressedDS->GetFileList();
203 :
204 2 : papszFileList = CSLInsertStrings(papszFileList, -1, papszCFileList);
205 2 : CSLDestroy(papszCFileList);
206 : }
207 :
208 13 : if (!osExternalCube.empty())
209 : {
210 6 : papszFileList = CSLAddString(papszFileList, osExternalCube);
211 : }
212 :
213 13 : return papszFileList;
214 : }
215 :
216 : /************************************************************************/
217 : /* IBuildOverviews() */
218 : /************************************************************************/
219 :
220 0 : CPLErr PDSDataset::IBuildOverviews(const char *pszResampling, int nOverviews,
221 : const int *panOverviewList, int nListBands,
222 : const int *panBandList,
223 : GDALProgressFunc pfnProgress,
224 : void *pProgressData,
225 : CSLConstList papszOptions)
226 : {
227 0 : if (poCompressedDS != nullptr)
228 0 : return poCompressedDS->BuildOverviews(
229 : pszResampling, nOverviews, panOverviewList, nListBands, panBandList,
230 0 : pfnProgress, pProgressData, papszOptions);
231 :
232 0 : return RawDataset::IBuildOverviews(
233 : pszResampling, nOverviews, panOverviewList, nListBands, panBandList,
234 0 : pfnProgress, pProgressData, papszOptions);
235 : }
236 :
237 : /************************************************************************/
238 : /* IRasterIO() */
239 : /************************************************************************/
240 :
241 0 : CPLErr PDSDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
242 : int nXSize, int nYSize, void *pData, int nBufXSize,
243 : int nBufYSize, GDALDataType eBufType,
244 : int nBandCount, BANDMAP_TYPE panBandMap,
245 : GSpacing nPixelSpace, GSpacing nLineSpace,
246 : GSpacing nBandSpace,
247 : GDALRasterIOExtraArg *psExtraArg)
248 :
249 : {
250 0 : if (poCompressedDS != nullptr)
251 0 : return poCompressedDS->RasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
252 : pData, nBufXSize, nBufYSize, eBufType,
253 : nBandCount, panBandMap, nPixelSpace,
254 0 : nLineSpace, nBandSpace, psExtraArg);
255 :
256 0 : return RawDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
257 : nBufXSize, nBufYSize, eBufType, nBandCount,
258 : panBandMap, nPixelSpace, nLineSpace,
259 0 : nBandSpace, psExtraArg);
260 : }
261 :
262 : /************************************************************************/
263 : /* GetSpatialRef() */
264 : /************************************************************************/
265 :
266 10 : const OGRSpatialReference *PDSDataset::GetSpatialRef() const
267 : {
268 10 : if (!m_oSRS.IsEmpty())
269 10 : return &m_oSRS;
270 0 : return GDALPamDataset::GetSpatialRef();
271 : }
272 :
273 : /************************************************************************/
274 : /* GetGeoTransform() */
275 : /************************************************************************/
276 :
277 9 : CPLErr PDSDataset::GetGeoTransform(double *padfTransform)
278 :
279 : {
280 9 : if (bGotTransform)
281 : {
282 8 : memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
283 8 : return CE_None;
284 : }
285 :
286 1 : return GDALPamDataset::GetGeoTransform(padfTransform);
287 : }
288 :
289 : /************************************************************************/
290 : /* ParseSRS() */
291 : /************************************************************************/
292 :
293 36 : void PDSDataset::ParseSRS()
294 :
295 : {
296 36 : const char *pszFilename = GetDescription();
297 :
298 72 : CPLString osPrefix;
299 86 : if (strlen(GetKeyword("IMAGE_MAP_PROJECTION.MAP_PROJECTION_TYPE")) == 0 &&
300 50 : strlen(GetKeyword(
301 14 : "UNCOMPRESSED_FILE.IMAGE_MAP_PROJECTION.MAP_PROJECTION_TYPE")) != 0)
302 3 : osPrefix = "UNCOMPRESSED_FILE.";
303 :
304 : /* ==================================================================== */
305 : /* Get the geotransform. */
306 : /* ==================================================================== */
307 : /*********** Grab Cellsize ************/
308 : // example:
309 : // MAP_SCALE = 14.818 <KM/PIXEL>
310 : // added search for unit (only checks for CM, KM - defaults to Meters)
311 : // Georef parameters
312 36 : double dfXDim = 1.0;
313 36 : double dfYDim = 1.0;
314 :
315 36 : const char *value = GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.MAP_SCALE");
316 36 : if (strlen(value) > 0)
317 : {
318 25 : dfXDim = CPLAtof(value);
319 25 : dfYDim = CPLAtof(value) * -1;
320 :
321 50 : CPLString osKey(osPrefix + "IMAGE_MAP_PROJECTION.MAP_SCALE");
322 50 : CPLString unit = GetKeywordUnit(osKey, 2); // KM
323 : // value = GetKeywordUnit("IMAGE_MAP_PROJECTION.MAP_SCALE",3); //PIXEL
324 50 : if ((EQUAL(unit, "M")) || (EQUAL(unit, "METER")) ||
325 25 : (EQUAL(unit, "METERS")))
326 : {
327 : // do nothing
328 : }
329 18 : else if (EQUAL(unit, "CM"))
330 : {
331 : // convert from cm to m
332 0 : dfXDim = dfXDim / 100.0;
333 0 : dfYDim = dfYDim / 100.0;
334 : }
335 : else
336 : {
337 : // defaults to convert km to m
338 18 : dfXDim = dfXDim * 1000.0;
339 18 : dfYDim = dfYDim * 1000.0;
340 : }
341 : }
342 :
343 : /* -------------------------------------------------------------------- */
344 : /* Calculate upper left corner of pixel in meters from the */
345 : /* upper left center pixel sample/line offsets. It doesn't */
346 : /* mean the defaults will work for every PDS image, as these */
347 : /* values are used inconsistently. Thus we have included */
348 : /* conversion options to allow the user to override the */
349 : /* documented PDS3 default. Jan. 2011, for known mapping issues */
350 : /* see GDAL PDS page or mapping within ISIS3 source (USGS) */
351 : /* $ISIS3DATA/base/translations/pdsProjectionLineSampToXY.def */
352 : /* -------------------------------------------------------------------- */
353 :
354 : // defaults should be correct for what is documented in the PDS3 standard
355 :
356 : // https://trac.osgeo.org/gdal/ticket/5941 has the history of the default
357 : /* value of PDS_SampleProjOffset_Shift and PDS_LineProjOffset_Shift */
358 : // coverity[tainted_data]
359 : double dfSampleOffset_Shift =
360 36 : CPLAtof(CPLGetConfigOption("PDS_SampleProjOffset_Shift", "0.5"));
361 :
362 : // coverity[tainted_data]
363 : const double dfLineOffset_Shift =
364 36 : CPLAtof(CPLGetConfigOption("PDS_LineProjOffset_Shift", "0.5"));
365 :
366 : // coverity[tainted_data]
367 : const double dfSampleOffset_Mult =
368 36 : CPLAtof(CPLGetConfigOption("PDS_SampleProjOffset_Mult", "-1.0"));
369 :
370 : // coverity[tainted_data]
371 : const double dfLineOffset_Mult =
372 36 : CPLAtof(CPLGetConfigOption("PDS_LineProjOffset_Mult", "1.0"));
373 :
374 : /*********** Grab LINE_PROJECTION_OFFSET ************/
375 36 : double dfULYMap = 0.5;
376 :
377 : value =
378 36 : GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.LINE_PROJECTION_OFFSET");
379 36 : if (strlen(value) > 0)
380 : {
381 25 : const double yulcenter = CPLAtof(value);
382 25 : dfULYMap =
383 25 : ((yulcenter + dfLineOffset_Shift) * -dfYDim * dfLineOffset_Mult);
384 : // notice dfYDim is negative here which is why it is again negated here
385 : }
386 : /*********** Grab SAMPLE_PROJECTION_OFFSET ************/
387 36 : double dfULXMap = 0.5;
388 :
389 : value =
390 36 : GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.SAMPLE_PROJECTION_OFFSET");
391 36 : if (strlen(value) > 0)
392 : {
393 25 : const double xulcenter = CPLAtof(value);
394 25 : dfULXMap =
395 25 : ((xulcenter + dfSampleOffset_Shift) * dfXDim * dfSampleOffset_Mult);
396 : }
397 :
398 : /* ==================================================================== */
399 : /* Get the coordinate system. */
400 : /* ==================================================================== */
401 :
402 : /*********** Grab TARGET_NAME ************/
403 : /**** This is the planets name i.e. MARS ***/
404 108 : const CPLString target_name = CleanString(GetKeyword("TARGET_NAME"));
405 :
406 : /********** Grab MAP_PROJECTION_TYPE *****/
407 : const CPLString map_proj_name = CleanString(
408 108 : GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.MAP_PROJECTION_TYPE"));
409 :
410 : /****** Grab semi_major & convert to KM ******/
411 : const double semi_major =
412 36 : CPLAtof(GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.A_AXIS_RADIUS")) *
413 36 : 1000.0;
414 :
415 : /****** Grab semi-minor & convert to KM ******/
416 : const double semi_minor =
417 36 : CPLAtof(GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.C_AXIS_RADIUS")) *
418 36 : 1000.0;
419 :
420 : /*********** Grab CENTER_LAT ************/
421 : const double center_lat =
422 36 : CPLAtof(GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.CENTER_LATITUDE"));
423 :
424 : /*********** Grab CENTER_LON ************/
425 : const double center_lon =
426 36 : CPLAtof(GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.CENTER_LONGITUDE"));
427 :
428 : /********** Grab 1st std parallel *******/
429 36 : const double first_std_parallel = CPLAtof(
430 72 : GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.FIRST_STANDARD_PARALLEL"));
431 :
432 : /********** Grab 2nd std parallel *******/
433 36 : const double second_std_parallel = CPLAtof(
434 72 : GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.SECOND_STANDARD_PARALLEL"));
435 :
436 : /*** grab PROJECTION_LATITUDE_TYPE = "PLANETOCENTRIC" ****/
437 : // Need to further study how ocentric/ographic will effect the gdal library.
438 : // So far we will use this fact to define a sphere or ellipse for some
439 : // projections Frank - may need to talk this over
440 36 : char bIsGeographic = TRUE;
441 : value =
442 36 : GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.COORDINATE_SYSTEM_NAME");
443 36 : if (EQUAL(value, "PLANETOCENTRIC"))
444 8 : bIsGeographic = FALSE;
445 :
446 : const double dfLongitudeMulFactor =
447 72 : EQUAL(GetKeyword("IMAGE_MAP_PROJECTION.POSITIVE_LONGITUDE_DIRECTION",
448 : "EAST"),
449 : "EAST")
450 : ? 1
451 36 : : -1;
452 :
453 : /** Set oSRS projection and parameters --- all PDS supported types added
454 : if apparently supported in oSRS "AITOFF", ** Not supported in GDAL??
455 : "ALBERS",
456 : "BONNE",
457 : "BRIESEMEISTER", ** Not supported in GDAL??
458 : "CYLINDRICAL EQUAL AREA",
459 : "EQUIDISTANT",
460 : "EQUIRECTANGULAR",
461 : "GNOMONIC",
462 : "HAMMER", ** Not supported in GDAL??
463 : "HENDU", ** Not supported in GDAL??
464 : "LAMBERT AZIMUTHAL EQUAL AREA",
465 : "LAMBERT CONFORMAL",
466 : "MERCATOR",
467 : "MOLLWEIDE",
468 : "OBLIQUE CYLINDRICAL",
469 : "ORTHOGRAPHIC",
470 : "SIMPLE CYLINDRICAL",
471 : "SINUSOIDAL",
472 : "STEREOGRAPHIC",
473 : "TRANSVERSE MERCATOR",
474 : "VAN DER GRINTEN", ** Not supported in GDAL??
475 : "WERNER" ** Not supported in GDAL??
476 : **/
477 36 : CPLDebug("PDS", "using projection %s\n\n", map_proj_name.c_str());
478 :
479 36 : bool bProjectionSet = true;
480 72 : OGRSpatialReference oSRS;
481 36 : oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
482 :
483 36 : if ((EQUAL(map_proj_name, "EQUIRECTANGULAR")) ||
484 56 : (EQUAL(map_proj_name, "SIMPLE_CYLINDRICAL")) ||
485 20 : (EQUAL(map_proj_name, "EQUIDISTANT")))
486 : {
487 16 : oSRS.SetEquirectangular2(0.0, center_lon, center_lat, 0, 0);
488 : }
489 20 : else if (EQUAL(map_proj_name, "ORTHOGRAPHIC"))
490 : {
491 0 : oSRS.SetOrthographic(center_lat, center_lon, 0, 0);
492 : }
493 20 : else if (EQUAL(map_proj_name, "SINUSOIDAL"))
494 : {
495 4 : oSRS.SetSinusoidal(center_lon, 0, 0);
496 : }
497 16 : else if (EQUAL(map_proj_name, "MERCATOR"))
498 : {
499 1 : if (center_lat == 0.0 && first_std_parallel != 0.0)
500 : {
501 1 : oSRS.SetMercator2SP(first_std_parallel, center_lat, center_lon, 0,
502 : 0);
503 : }
504 : else
505 : {
506 0 : oSRS.SetMercator(center_lat, center_lon, 1, 0, 0);
507 : }
508 : }
509 15 : else if (EQUAL(map_proj_name, "STEREOGRAPHIC"))
510 : {
511 0 : if ((fabs(center_lat) - 90) < 0.0000001)
512 : {
513 0 : oSRS.SetPS(center_lat, center_lon, 1, 0, 0);
514 : }
515 : else
516 0 : oSRS.SetStereographic(center_lat, center_lon, 1, 0, 0);
517 : }
518 15 : else if (EQUAL(map_proj_name, "POLAR_STEREOGRAPHIC"))
519 : {
520 0 : oSRS.SetPS(center_lat, center_lon, 1, 0, 0);
521 : }
522 15 : else if (EQUAL(map_proj_name, "TRANSVERSE_MERCATOR"))
523 : {
524 0 : oSRS.SetTM(center_lat, center_lon, 1, 0, 0);
525 : }
526 15 : else if (EQUAL(map_proj_name, "LAMBERT_CONFORMAL_CONIC"))
527 : {
528 0 : oSRS.SetLCC(first_std_parallel, second_std_parallel, center_lat,
529 : center_lon, 0, 0);
530 : }
531 15 : else if (EQUAL(map_proj_name, "LAMBERT_AZIMUTHAL_EQUAL_AREA"))
532 : {
533 0 : oSRS.SetLAEA(center_lat, center_lon, 0, 0);
534 : }
535 15 : else if (EQUAL(map_proj_name, "CYLINDRICAL_EQUAL_AREA"))
536 : {
537 0 : oSRS.SetCEA(first_std_parallel, center_lon, 0, 0);
538 : }
539 15 : else if (EQUAL(map_proj_name, "MOLLWEIDE"))
540 : {
541 0 : oSRS.SetMollweide(center_lon, 0, 0);
542 : }
543 15 : else if (EQUAL(map_proj_name, "ALBERS"))
544 : {
545 0 : oSRS.SetACEA(first_std_parallel, second_std_parallel, center_lat,
546 : center_lon, 0, 0);
547 : }
548 15 : else if (EQUAL(map_proj_name, "BONNE"))
549 : {
550 0 : oSRS.SetBonne(first_std_parallel, center_lon, 0, 0);
551 : }
552 15 : else if (EQUAL(map_proj_name, "GNOMONIC"))
553 : {
554 0 : oSRS.SetGnomonic(center_lat, center_lon, 0, 0);
555 : }
556 15 : else if (EQUAL(map_proj_name, "OBLIQUE_CYLINDRICAL"))
557 : {
558 4 : const double poleLatitude = CPLAtof(GetKeyword(
559 8 : osPrefix + "IMAGE_MAP_PROJECTION.OBLIQUE_PROJ_POLE_LATITUDE"));
560 : const double poleLongitude =
561 4 : CPLAtof(GetKeyword(
562 4 : osPrefix +
563 : "IMAGE_MAP_PROJECTION.OBLIQUE_PROJ_POLE_LONGITUDE")) *
564 4 : dfLongitudeMulFactor;
565 4 : const double poleRotation = CPLAtof(GetKeyword(
566 8 : osPrefix + "IMAGE_MAP_PROJECTION.OBLIQUE_PROJ_POLE_ROTATION"));
567 8 : CPLString oProj4String;
568 : // ISIS3 rotated pole doesn't use the same conventions than PROJ ob_tran
569 : // Compare the sign difference in
570 : // https://github.com/USGS-Astrogeology/ISIS3/blob/3.8.0/isis/src/base/objs/ObliqueCylindrical/ObliqueCylindrical.cpp#L244
571 : // and
572 : // https://github.com/OSGeo/PROJ/blob/6.2/src/projections/ob_tran.cpp#L34
573 : // They can be compensated by modifying the poleLatitude to
574 : // 180-poleLatitude There's also a sign difference for the poleRotation
575 : // parameter The existence of those different conventions is
576 : // acknowledged in
577 : // https://pds-imaging.jpl.nasa.gov/documentation/Cassini_BIDRSIS.PDF in
578 : // the middle of page 10
579 : oProj4String.Printf("+proj=ob_tran +o_proj=eqc +o_lon_p=%.17g "
580 : "+o_lat_p=%.17g +lon_0=%.17g",
581 4 : -poleRotation, 180 - poleLatitude, poleLongitude);
582 4 : oSRS.SetFromUserInput(oProj4String);
583 : }
584 : else
585 : {
586 11 : CPLDebug("PDS", "Dataset projection %s is not supported. Continuing...",
587 : map_proj_name.c_str());
588 11 : bProjectionSet = false;
589 : }
590 :
591 36 : if (bProjectionSet)
592 : {
593 : // Create projection name, i.e. MERCATOR MARS and set as ProjCS keyword
594 75 : const CPLString proj_target_name = map_proj_name + " " + target_name;
595 25 : oSRS.SetProjCS(proj_target_name); // set ProjCS keyword
596 :
597 : // The geographic/geocentric name will be the same basic name as the
598 : // body name 'GCS' = Geographic/Geocentric Coordinate System
599 50 : const CPLString geog_name = "GCS_" + target_name;
600 :
601 : // The datum and sphere names will be the same basic name aas the planet
602 50 : const CPLString datum_name = "D_" + target_name;
603 : // Might not be IAU defined so don't add.
604 50 : CPLString sphere_name = target_name; // + "_IAU_IAG");
605 :
606 : // calculate inverse flattening from major and minor axis: 1/f = a/(a-b)
607 : double iflattening;
608 25 : if ((semi_major - semi_minor) < 0.0000001)
609 18 : iflattening = 0;
610 : else
611 7 : iflattening = semi_major / (semi_major - semi_minor);
612 :
613 : // Set the body size but take into consideration which proj is being
614 : // used to help w/ compatibility Notice that most PDS projections are
615 : // spherical based on the fact that ISIS/PICS are spherical Set the body
616 : // size but take into consideration which proj is being used to help w/
617 : // proj4 compatibility The use of a Sphere, polar radius or ellipse here
618 : // is based on how ISIS does it internally
619 25 : if (((EQUAL(map_proj_name, "STEREOGRAPHIC") &&
620 50 : (fabs(center_lat) == 90))) ||
621 25 : (EQUAL(map_proj_name, "POLAR_STEREOGRAPHIC")))
622 : {
623 0 : if (bIsGeographic)
624 : {
625 : // Geograpraphic, so set an ellipse
626 0 : oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_major,
627 : iflattening, "Reference_Meridian", 0.0);
628 : }
629 : else
630 : {
631 : // Geocentric, so force a sphere using the semi-minor axis. I
632 : // hope...
633 0 : sphere_name += "_polarRadius";
634 0 : oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_minor,
635 : 0.0, "Reference_Meridian", 0.0);
636 : }
637 : }
638 25 : else if ((EQUAL(map_proj_name, "SIMPLE_CYLINDRICAL")) ||
639 16 : (EQUAL(map_proj_name, "EQUIDISTANT")) ||
640 16 : (EQUAL(map_proj_name, "ORTHOGRAPHIC")) ||
641 57 : (EQUAL(map_proj_name, "STEREOGRAPHIC")) ||
642 16 : (EQUAL(map_proj_name, "SINUSOIDAL")))
643 : {
644 : // isis uses the spherical equation for these projections so force a
645 : // sphere
646 13 : oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_major, 0.0,
647 : "Reference_Meridian", 0.0);
648 : }
649 12 : else if (EQUAL(map_proj_name, "EQUIRECTANGULAR"))
650 : {
651 : // isis uses local radius as a sphere, which is pre-calculated in
652 : // the PDS label as the semi-major
653 7 : sphere_name += "_localRadius";
654 7 : oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_major, 0.0,
655 : "Reference_Meridian", 0.0);
656 : }
657 : else
658 : {
659 : // All other projections: Mercator, Transverse Mercator, Lambert
660 : // Conformal, etc. Geographic, so set an ellipse
661 5 : if (bIsGeographic)
662 : {
663 4 : oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_major,
664 : iflattening, "Reference_Meridian", 0.0);
665 : }
666 : else
667 : {
668 : // Geocentric, so force a sphere. I hope...
669 1 : oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_major,
670 : 0.0, "Reference_Meridian", 0.0);
671 : }
672 : }
673 :
674 : // translate back into a projection string.
675 25 : m_oSRS = std::move(oSRS);
676 : }
677 :
678 : /* ==================================================================== */
679 : /* Check for a .prj and world file to override the georeferencing. */
680 : /* ==================================================================== */
681 : {
682 72 : const CPLString osPath = CPLGetPathSafe(pszFilename);
683 72 : const CPLString osName = CPLGetBasenameSafe(pszFilename);
684 : const std::string osPrjFile =
685 72 : CPLFormCIFilenameSafe(osPath, osName, "prj");
686 :
687 36 : VSILFILE *fp = VSIFOpenL(osPrjFile.c_str(), "r");
688 36 : if (fp != nullptr)
689 : {
690 0 : VSIFCloseL(fp);
691 :
692 0 : char **papszLines = CSLLoad(osPrjFile.c_str());
693 :
694 0 : m_oSRS.importFromESRI(papszLines);
695 0 : CSLDestroy(papszLines);
696 : }
697 : }
698 :
699 36 : if (dfULXMap != 0.5 || dfULYMap != 0.5 || dfXDim != 1.0 || dfYDim != 1.0)
700 : {
701 25 : bGotTransform = TRUE;
702 25 : adfGeoTransform[0] = dfULXMap;
703 25 : adfGeoTransform[1] = dfXDim;
704 25 : adfGeoTransform[2] = 0.0;
705 25 : adfGeoTransform[3] = dfULYMap;
706 25 : adfGeoTransform[4] = 0.0;
707 25 : adfGeoTransform[5] = dfYDim;
708 :
709 25 : const double rotation = CPLAtof(GetKeyword(
710 50 : osPrefix + "IMAGE_MAP_PROJECTION.MAP_PROJECTION_ROTATION"));
711 25 : if (rotation != 0)
712 : {
713 4 : const double sin_rot =
714 4 : rotation == 90 ? 1.0 : sin(rotation / 180 * M_PI);
715 4 : const double cos_rot =
716 4 : rotation == 90 ? 0.0 : cos(rotation / 180 * M_PI);
717 4 : const double gt_1 =
718 4 : cos_rot * adfGeoTransform[1] - sin_rot * adfGeoTransform[4];
719 4 : const double gt_2 =
720 4 : cos_rot * adfGeoTransform[2] - sin_rot * adfGeoTransform[5];
721 4 : const double gt_0 =
722 4 : cos_rot * adfGeoTransform[0] - sin_rot * adfGeoTransform[3];
723 4 : const double gt_4 =
724 4 : sin_rot * adfGeoTransform[1] + cos_rot * adfGeoTransform[4];
725 4 : const double gt_5 =
726 4 : sin_rot * adfGeoTransform[2] + cos_rot * adfGeoTransform[5];
727 4 : const double gt_3 =
728 4 : sin_rot * adfGeoTransform[0] + cos_rot * adfGeoTransform[3];
729 4 : adfGeoTransform[1] = gt_1;
730 4 : adfGeoTransform[2] = gt_2;
731 4 : adfGeoTransform[0] = gt_0;
732 4 : adfGeoTransform[4] = gt_4;
733 4 : adfGeoTransform[5] = gt_5;
734 4 : adfGeoTransform[3] = gt_3;
735 : }
736 : }
737 :
738 36 : if (!bGotTransform)
739 11 : bGotTransform = GDALReadWorldFile(pszFilename, "psw", adfGeoTransform);
740 :
741 36 : if (!bGotTransform)
742 11 : bGotTransform = GDALReadWorldFile(pszFilename, "wld", adfGeoTransform);
743 36 : }
744 :
745 : /************************************************************************/
746 : /* GetRawBinaryLayout() */
747 : /************************************************************************/
748 :
749 2 : bool PDSDataset::GetRawBinaryLayout(GDALDataset::RawBinaryLayout &sLayout)
750 : {
751 2 : if (!RawDataset::GetRawBinaryLayout(sLayout))
752 0 : return false;
753 2 : sLayout.osRawFilename = m_osImageFilename;
754 2 : return true;
755 : }
756 :
757 : /************************************************************************/
758 : /* PDSConvertFromHex() */
759 : /************************************************************************/
760 :
761 3 : static GUInt32 PDSConvertFromHex(const char *pszVal)
762 : {
763 3 : if (!STARTS_WITH_CI(pszVal, "16#"))
764 0 : return 0;
765 :
766 3 : pszVal += 3;
767 3 : GUInt32 nVal = 0;
768 27 : while (*pszVal != '#' && *pszVal != '\0')
769 : {
770 24 : nVal <<= 4;
771 24 : if (*pszVal >= '0' && *pszVal <= '9')
772 3 : nVal += *pszVal - '0';
773 21 : else if (*pszVal >= 'A' && *pszVal <= 'F')
774 21 : nVal += *pszVal - 'A' + 10;
775 : else
776 0 : return 0;
777 24 : pszVal++;
778 : }
779 :
780 3 : return nVal;
781 : }
782 :
783 : /************************************************************************/
784 : /* ParseImage() */
785 : /************************************************************************/
786 :
787 33 : int PDSDataset::ParseImage(const CPLString &osPrefix,
788 : const CPLString &osFilenamePrefix)
789 : {
790 : /* ------------------------------------------------------------------- */
791 : /* We assume the user is pointing to the label (i.e. .lbl) file. */
792 : /* ------------------------------------------------------------------- */
793 : // IMAGE can be inline or detached and point to an image name
794 : // ^IMAGE = 3
795 : // ^IMAGE = "GLOBAL_ALBEDO_8PPD.IMG"
796 : // ^IMAGE = "MEGT90N000CB.IMG"
797 : // ^IMAGE = ("FOO.IMG",1) -- start at record 1 (1 based)
798 : // ^IMAGE = ("FOO.IMG") -- start at record 1 equiv of
799 : // ("FOO.IMG",1) ^IMAGE = ("FOO.IMG", 5 <BYTES>) -- start at
800 : // byte 5 (the fifth byte in the file) ^IMAGE = 10851 <BYTES>
801 : // ^SPECTRAL_QUBE = 5 for multi-band images
802 : // ^QUBE = 5 for multi-band images
803 :
804 66 : CPLString osImageKeyword = "IMAGE";
805 99 : CPLString osQube = GetKeyword(osPrefix + "^" + osImageKeyword, "");
806 33 : m_osImageFilename = GetDescription();
807 :
808 33 : if (EQUAL(osQube, ""))
809 : {
810 0 : osImageKeyword = "SPECTRAL_QUBE";
811 0 : osQube = GetKeyword(osPrefix + "^" + osImageKeyword);
812 : }
813 :
814 33 : if (EQUAL(osQube, ""))
815 : {
816 0 : osImageKeyword = "QUBE";
817 0 : osQube = GetKeyword(osPrefix + "^" + osImageKeyword);
818 : }
819 :
820 33 : const int nQube = atoi(osQube);
821 33 : int nDetachedOffset = 0;
822 33 : bool bDetachedOffsetInBytes = false;
823 :
824 33 : if (!osQube.empty() && osQube[0] == '(')
825 : {
826 9 : osQube = "\"";
827 9 : osQube += GetKeywordSub(osPrefix + "^" + osImageKeyword, 1);
828 9 : osQube += "\"";
829 : nDetachedOffset =
830 9 : atoi(GetKeywordSub(osPrefix + "^" + osImageKeyword, 2, "1"));
831 9 : if (nDetachedOffset >= 1)
832 9 : nDetachedOffset -= 1;
833 :
834 : // If this is not explicitly in bytes, then it is assumed to be in
835 : // records, and we need to translate to bytes.
836 18 : if (strstr(GetKeywordSub(osPrefix + "^" + osImageKeyword, 2),
837 9 : "<BYTES>") != nullptr)
838 2 : bDetachedOffsetInBytes = true;
839 : }
840 :
841 33 : if (!osQube.empty() && osQube[0] == '"')
842 : {
843 26 : const CPLString osFilename = CleanString(osQube);
844 13 : if (!osFilenamePrefix.empty())
845 : {
846 3 : m_osImageFilename = osFilenamePrefix + osFilename;
847 : }
848 : else
849 : {
850 20 : CPLString osTPath = CPLGetPathSafe(GetDescription());
851 : m_osImageFilename =
852 10 : CPLFormCIFilenameSafe(osTPath, osFilename, nullptr);
853 10 : osExternalCube = m_osImageFilename;
854 : }
855 : }
856 :
857 : /* -------------------------------------------------------------------- */
858 : /* Checks to see if this is raw PDS image not compressed image */
859 : /* so ENCODING_TYPE either does not exist or it equals "N/A". */
860 : /* or "DCT_DECOMPRESSED". */
861 : /* Compressed types will not be supported in this routine */
862 : /* -------------------------------------------------------------------- */
863 :
864 : const CPLString osEncodingType =
865 99 : CleanString(GetKeyword(osPrefix + "IMAGE.ENCODING_TYPE", "N/A"));
866 33 : if (!EQUAL(osEncodingType, "N/A") &&
867 0 : !EQUAL(osEncodingType, "DCT_DECOMPRESSED"))
868 : {
869 0 : CPLError(CE_Failure, CPLE_OpenFailed,
870 : "*** PDS image file has an ENCODING_TYPE parameter:\n"
871 : "*** GDAL PDS driver does not support compressed image types\n"
872 : "found: (%s)\n\n",
873 : osEncodingType.c_str());
874 0 : return FALSE;
875 : }
876 : /**************** end ENCODING_TYPE check ***********************/
877 :
878 : /*********** Grab layout type (BSQ, BIP, BIL) ************/
879 : // AXIS_NAME = (SAMPLE,LINE,BAND)
880 : /*********** Grab samples lines band **************/
881 : /** if AXIS_NAME = "" then Bands=1 and Sample and Lines **/
882 : /** are there own keywords "LINES" and "LINE_SAMPLES" **/
883 : /** if not NULL then CORE_ITEMS keyword i.e. (234,322,2) **/
884 : /***********************************************************/
885 33 : int eLayout = PDS_BSQ; // default to band seq.
886 33 : int nRows, nCols, l_nBands = 1;
887 :
888 99 : CPLString value = GetKeyword(osPrefix + osImageKeyword + ".AXIS_NAME", "");
889 33 : if (EQUAL(value, "(SAMPLE,LINE,BAND)"))
890 : {
891 0 : eLayout = PDS_BSQ;
892 : nCols =
893 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 1));
894 : nRows =
895 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 2));
896 : l_nBands =
897 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 3));
898 : }
899 33 : else if (EQUAL(value, "(BAND,LINE,SAMPLE)"))
900 : {
901 0 : eLayout = PDS_BIP;
902 : l_nBands =
903 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 1));
904 : nRows =
905 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 2));
906 : nCols =
907 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 3));
908 : }
909 33 : else if (EQUAL(value, "(SAMPLE,BAND,LINE)"))
910 : {
911 0 : eLayout = PDS_BIL;
912 : nCols =
913 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 1));
914 : l_nBands =
915 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 2));
916 : nRows =
917 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 3));
918 : }
919 33 : else if (EQUAL(value, ""))
920 : {
921 33 : eLayout = PDS_BSQ;
922 : nCols =
923 33 : atoi(GetKeyword(osPrefix + osImageKeyword + ".LINE_SAMPLES", ""));
924 33 : nRows = atoi(GetKeyword(osPrefix + osImageKeyword + ".LINES", ""));
925 33 : l_nBands = atoi(GetKeyword(osPrefix + osImageKeyword + ".BANDS", "1"));
926 : }
927 : else
928 : {
929 0 : CPLError(CE_Failure, CPLE_OpenFailed,
930 : "%s layout not supported. Abort\n\n", value.c_str());
931 0 : return FALSE;
932 : }
933 :
934 : CPLString osBAND_STORAGE_TYPE =
935 66 : GetKeyword(osPrefix + "IMAGE.BAND_STORAGE_TYPE", "");
936 33 : if (EQUAL(osBAND_STORAGE_TYPE, "BAND_SEQUENTIAL"))
937 : {
938 12 : eLayout = PDS_BSQ;
939 : }
940 21 : else if (EQUAL(osBAND_STORAGE_TYPE, "PIXEL_INTERLEAVED"))
941 : {
942 0 : eLayout = PDS_BIP;
943 : }
944 21 : else if (EQUAL(osBAND_STORAGE_TYPE, "LINE_INTERLEAVED"))
945 : {
946 2 : eLayout = PDS_BIL;
947 : }
948 19 : else if (!osBAND_STORAGE_TYPE.empty())
949 : {
950 4 : CPLDebug("PDS", "Unhandled BAND_STORAGE_TYPE = %s",
951 : osBAND_STORAGE_TYPE.c_str());
952 : }
953 :
954 : /*********** Grab Qube record bytes **********/
955 33 : int record_bytes = atoi(GetKeyword(osPrefix + "IMAGE.RECORD_BYTES"));
956 33 : if (record_bytes == 0)
957 33 : record_bytes = atoi(GetKeyword(osPrefix + "RECORD_BYTES"));
958 :
959 : // this can happen with "record_type = undefined".
960 33 : if (record_bytes < 0)
961 0 : return FALSE;
962 33 : if (record_bytes == 0)
963 3 : record_bytes = 1;
964 :
965 33 : int nSkipBytes = 0;
966 : try
967 : {
968 33 : if (nQube > 0)
969 : {
970 20 : if (osQube.find("<BYTES>") != CPLString::npos)
971 3 : nSkipBytes = (CPLSM(nQube) - CPLSM(1)).v();
972 : else
973 17 : nSkipBytes = (CPLSM(nQube - 1) * CPLSM(record_bytes)).v();
974 : }
975 13 : else if (nDetachedOffset > 0)
976 : {
977 4 : if (bDetachedOffsetInBytes)
978 2 : nSkipBytes = nDetachedOffset;
979 : else
980 : {
981 2 : nSkipBytes = (CPLSM(nDetachedOffset) * CPLSM(record_bytes)).v();
982 : }
983 : }
984 : else
985 9 : nSkipBytes = 0;
986 : }
987 0 : catch (const CPLSafeIntOverflow &)
988 : {
989 0 : return FALSE;
990 : }
991 :
992 : const int nLinePrefixBytes =
993 33 : atoi(GetKeyword(osPrefix + "IMAGE.LINE_PREFIX_BYTES", ""));
994 33 : if (nLinePrefixBytes < 0)
995 0 : return false;
996 33 : nSkipBytes += nLinePrefixBytes;
997 :
998 : /*********** Grab SAMPLE_TYPE *****************/
999 : /** if keyword not found leave as "M" or "MSB" **/
1000 :
1001 66 : CPLString osST = GetKeyword(osPrefix + "IMAGE.SAMPLE_TYPE");
1002 33 : if (osST.size() >= 2 && osST[0] == '"' && osST.back() == '"')
1003 4 : osST = osST.substr(1, osST.size() - 2);
1004 :
1005 33 : char chByteOrder = 'M'; // default to MSB
1006 64 : if ((EQUAL(osST, "LSB_INTEGER")) || (EQUAL(osST, "LSB")) || // just in case
1007 31 : (EQUAL(osST, "LSB_UNSIGNED_INTEGER")) ||
1008 27 : (EQUAL(osST, "LSB_SIGNED_INTEGER")) ||
1009 27 : (EQUAL(osST, "UNSIGNED_INTEGER")) || (EQUAL(osST, "VAX_REAL")) ||
1010 12 : (EQUAL(osST, "VAX_INTEGER")) ||
1011 76 : (EQUAL(osST, "PC_INTEGER")) || // just in case
1012 12 : (EQUAL(osST, "PC_REAL")))
1013 : {
1014 26 : chByteOrder = 'I';
1015 : }
1016 :
1017 : /**** Grab format type - pds supports 1,2,4,8,16,32,64 (in theory) **/
1018 : /**** I have only seen 8, 16, 32 (float) in released datasets **/
1019 33 : GDALDataType eDataType = GDT_Byte;
1020 33 : int nSuffixItems = 0;
1021 33 : int nSuffixLines = 0;
1022 33 : int nSuffixBytes = 4; // Default as per PDS specification
1023 33 : double dfNoData = 0.0;
1024 33 : double dfScale = 1.0;
1025 33 : double dfOffset = 0.0;
1026 33 : const char *pszUnit = nullptr;
1027 33 : const char *pszDesc = nullptr;
1028 :
1029 66 : CPLString osSB = GetKeyword(osPrefix + "IMAGE.SAMPLE_BITS", "");
1030 33 : if (!osSB.empty())
1031 : {
1032 33 : const int itype = atoi(osSB);
1033 33 : switch (itype)
1034 : {
1035 23 : case 8:
1036 23 : eDataType = GDT_Byte;
1037 23 : dfNoData = PDS_NULL1;
1038 23 : break;
1039 5 : case 16:
1040 5 : if (strstr(osST, "UNSIGNED") != nullptr)
1041 : {
1042 3 : dfNoData = PDS_NULL1;
1043 3 : eDataType = GDT_UInt16;
1044 : }
1045 : else
1046 : {
1047 2 : eDataType = GDT_Int16;
1048 2 : dfNoData = PDS_NULL2;
1049 : }
1050 5 : break;
1051 5 : case 32:
1052 5 : eDataType = GDT_Float32;
1053 5 : dfNoData = PDS_NULL3;
1054 5 : break;
1055 0 : case 64:
1056 0 : eDataType = GDT_Float64;
1057 0 : dfNoData = PDS_NULL3;
1058 0 : break;
1059 0 : default:
1060 0 : CPLError(CE_Failure, CPLE_AppDefined,
1061 : "Sample_bits of %d is not supported in this gdal PDS "
1062 : "reader.",
1063 : itype);
1064 0 : return FALSE;
1065 : }
1066 :
1067 33 : dfOffset = CPLAtofM(GetKeyword(osPrefix + "IMAGE.OFFSET", "0.0"));
1068 : dfScale =
1069 33 : CPLAtofM(GetKeyword(osPrefix + "IMAGE.SCALING_FACTOR", "1.0"));
1070 : }
1071 : else /* No IMAGE object, search for the QUBE. */
1072 : {
1073 0 : osSB = GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_ITEM_BYTES", "");
1074 0 : const int itype = atoi(osSB);
1075 0 : switch (itype)
1076 : {
1077 0 : case 1:
1078 0 : eDataType = GDT_Byte;
1079 0 : break;
1080 0 : case 2:
1081 0 : if (strstr(osST, "UNSIGNED") != nullptr)
1082 0 : eDataType = GDT_UInt16;
1083 : else
1084 0 : eDataType = GDT_Int16;
1085 0 : break;
1086 0 : case 4:
1087 0 : eDataType = GDT_Float32;
1088 0 : break;
1089 0 : default:
1090 0 : CPLError(CE_Failure, CPLE_AppDefined,
1091 : "CORE_ITEM_BYTES of %d is not supported in this gdal "
1092 : "PDS reader.",
1093 : itype);
1094 0 : return FALSE;
1095 : }
1096 :
1097 : /* Parse suffix dimensions if defined. */
1098 0 : value = GetKeyword(osPrefix + "SPECTRAL_QUBE.SUFFIX_ITEMS", "");
1099 0 : if (!value.empty())
1100 : {
1101 0 : value = GetKeyword(osPrefix + "SPECTRAL_QUBE.SUFFIX_BYTES", "");
1102 0 : if (!value.empty())
1103 0 : nSuffixBytes = atoi(value);
1104 :
1105 : nSuffixItems =
1106 0 : atoi(GetKeywordSub(osPrefix + "SPECTRAL_QUBE.SUFFIX_ITEMS", 1));
1107 : nSuffixLines =
1108 0 : atoi(GetKeywordSub(osPrefix + "SPECTRAL_QUBE.SUFFIX_ITEMS", 2));
1109 : }
1110 :
1111 0 : value = GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_NULL", "");
1112 0 : if (!value.empty())
1113 0 : dfNoData = CPLAtofM(value);
1114 :
1115 : dfOffset =
1116 0 : CPLAtofM(GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_BASE", "0.0"));
1117 0 : dfScale = CPLAtofM(
1118 0 : GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_MULTIPLIER", "1.0"));
1119 0 : pszUnit = GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_UNIT", nullptr);
1120 0 : pszDesc = GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_NAME", nullptr);
1121 : }
1122 :
1123 : /* -------------------------------------------------------------------- */
1124 : /* Is there a specific nodata value in the file? Either the */
1125 : /* MISSING or MISSING_CONSTANT keywords are nodata. */
1126 : /* -------------------------------------------------------------------- */
1127 :
1128 33 : const char *pszMissing = GetKeyword(osPrefix + "IMAGE.MISSING", nullptr);
1129 33 : if (pszMissing == nullptr)
1130 30 : pszMissing = GetKeyword(osPrefix + "IMAGE.MISSING_CONSTANT", nullptr);
1131 :
1132 33 : if (pszMissing != nullptr)
1133 : {
1134 9 : if (*pszMissing == '"')
1135 3 : pszMissing++;
1136 :
1137 : /* For example : MISSING_CONSTANT = "16#FF7FFFFB#" */
1138 9 : if (STARTS_WITH_CI(pszMissing, "16#") &&
1139 3 : strlen(pszMissing) >= 3 + 8 + 1 && pszMissing[3 + 8] == '#' &&
1140 0 : (eDataType == GDT_Float32 || eDataType == GDT_Float64))
1141 : {
1142 3 : GUInt32 nVal = PDSConvertFromHex(pszMissing);
1143 : float fVal;
1144 3 : memcpy(&fVal, &nVal, 4);
1145 3 : dfNoData = fVal;
1146 : }
1147 : else
1148 6 : dfNoData = CPLAtofM(pszMissing);
1149 : }
1150 :
1151 : /* -------------------------------------------------------------------- */
1152 : /* Did we get the required keywords? If not we return with */
1153 : /* this never having been considered to be a match. This isn't */
1154 : /* an error! */
1155 : /* -------------------------------------------------------------------- */
1156 66 : if (!GDALCheckDatasetDimensions(nCols, nRows) ||
1157 33 : !GDALCheckBandCount(l_nBands, false))
1158 : {
1159 0 : return FALSE;
1160 : }
1161 :
1162 : /* -------------------------------------------------------------------- */
1163 : /* Capture some information from the file that is of interest. */
1164 : /* -------------------------------------------------------------------- */
1165 33 : nRasterXSize = nCols;
1166 33 : nRasterYSize = nRows;
1167 :
1168 : /* -------------------------------------------------------------------- */
1169 : /* Open target binary file. */
1170 : /* -------------------------------------------------------------------- */
1171 :
1172 33 : if (eAccess == GA_ReadOnly)
1173 : {
1174 33 : fpImage = VSIFOpenL(m_osImageFilename, "rb");
1175 33 : if (fpImage == nullptr)
1176 : {
1177 0 : CPLError(CE_Failure, CPLE_OpenFailed, "Failed to open %s.\n%s",
1178 0 : m_osImageFilename.c_str(), VSIStrerror(errno));
1179 0 : return FALSE;
1180 : }
1181 : }
1182 : else
1183 : {
1184 0 : fpImage = VSIFOpenL(m_osImageFilename, "r+b");
1185 0 : if (fpImage == nullptr)
1186 : {
1187 0 : CPLError(CE_Failure, CPLE_OpenFailed,
1188 : "Failed to open %s with write permission.\n%s",
1189 0 : m_osImageFilename.c_str(), VSIStrerror(errno));
1190 0 : return FALSE;
1191 : }
1192 : }
1193 :
1194 : /* -------------------------------------------------------------------- */
1195 : /* Compute the line offset. */
1196 : /* -------------------------------------------------------------------- */
1197 33 : const int nItemSize = GDALGetDataTypeSize(eDataType) / 8;
1198 :
1199 : // Needed for N1349177584_2.LBL from
1200 : // https://trac.osgeo.org/gdal/attachment/ticket/3355/PDS-TestFiles.zip
1201 33 : int nLineOffset = nLinePrefixBytes;
1202 :
1203 : int nPixelOffset;
1204 : vsi_l_offset nBandOffset;
1205 :
1206 186 : const auto CPLSM64 = [](int x) { return CPLSM(static_cast<int64_t>(x)); };
1207 :
1208 : try
1209 : {
1210 33 : if (eLayout == PDS_BIP)
1211 : {
1212 0 : nPixelOffset = (CPLSM(nItemSize) * CPLSM(l_nBands)).v();
1213 0 : nBandOffset = nItemSize;
1214 : nLineOffset =
1215 0 : (CPLSM(nLineOffset) + CPLSM(nPixelOffset) * CPLSM(nCols)).v();
1216 : }
1217 33 : else if (eLayout == PDS_BSQ)
1218 : {
1219 31 : nPixelOffset = nItemSize;
1220 : nLineOffset =
1221 31 : (CPLSM(nLineOffset) + CPLSM(nPixelOffset) * CPLSM(nCols)).v();
1222 31 : nBandOffset = static_cast<vsi_l_offset>(
1223 31 : (CPLSM64(nLineOffset) * CPLSM64(nRows) +
1224 31 : CPLSM64(nSuffixLines) *
1225 62 : (CPLSM64(nCols) + CPLSM64(nSuffixItems)) *
1226 124 : CPLSM64(nSuffixBytes))
1227 31 : .v());
1228 : }
1229 : else /* assume BIL */
1230 : {
1231 2 : nPixelOffset = nItemSize;
1232 2 : nBandOffset = (CPLSM(nItemSize) * CPLSM(nCols)).v();
1233 : nLineOffset =
1234 2 : (CPLSM(nLineOffset) +
1235 6 : CPLSM(static_cast<int>(nBandOffset)) * CPLSM(l_nBands))
1236 2 : .v();
1237 : }
1238 : }
1239 0 : catch (const CPLSafeIntOverflow &)
1240 : {
1241 0 : CPLError(CE_Failure, CPLE_AppDefined, "Integer overflow");
1242 0 : return FALSE;
1243 : }
1244 :
1245 : /* -------------------------------------------------------------------- */
1246 : /* Create band information objects. */
1247 : /* -------------------------------------------------------------------- */
1248 278 : for (int i = 0; i < l_nBands; i++)
1249 : {
1250 : auto poBand = RawRasterBand::Create(
1251 : this, i + 1, fpImage,
1252 245 : nSkipBytes + static_cast<vsi_l_offset>(nBandOffset) * i,
1253 : nPixelOffset, nLineOffset, eDataType,
1254 : chByteOrder == 'I' || chByteOrder == 'L'
1255 245 : ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
1256 : : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN,
1257 245 : RawRasterBand::OwnFP::NO);
1258 245 : if (!poBand)
1259 0 : return FALSE;
1260 :
1261 245 : if (l_nBands == 1)
1262 : {
1263 : const char *pszMin =
1264 31 : GetKeyword(osPrefix + "IMAGE.MINIMUM", nullptr);
1265 : const char *pszMax =
1266 31 : GetKeyword(osPrefix + "IMAGE.MAXIMUM", nullptr);
1267 31 : const char *pszMean = GetKeyword(osPrefix + "IMAGE.MEAN", nullptr);
1268 : const char *pszStdDev =
1269 31 : GetKeyword(osPrefix + "IMAGE.STANDARD_DEVIATION", nullptr);
1270 31 : if (pszMin != nullptr && pszMax != nullptr && pszMean != nullptr &&
1271 : pszStdDev != nullptr)
1272 : {
1273 0 : poBand->SetStatistics(CPLAtofM(pszMin), CPLAtofM(pszMax),
1274 0 : CPLAtofM(pszMean), CPLAtofM(pszStdDev));
1275 : }
1276 : }
1277 :
1278 245 : poBand->SetNoDataValue(dfNoData);
1279 :
1280 : // Set offset/scale values at the PAM level.
1281 245 : poBand->SetOffset(dfOffset);
1282 245 : poBand->SetScale(dfScale);
1283 245 : if (pszUnit)
1284 0 : poBand->SetUnitType(pszUnit);
1285 245 : if (pszDesc)
1286 0 : poBand->SetDescription(pszDesc);
1287 :
1288 245 : SetBand(i + 1, std::move(poBand));
1289 : }
1290 :
1291 33 : return TRUE;
1292 : }
1293 :
1294 : /************************************************************************/
1295 : /* ==================================================================== */
1296 : /* PDSWrapperRasterBand */
1297 : /* */
1298 : /* proxy for the jp2 or other compressed bands. */
1299 : /* ==================================================================== */
1300 : /************************************************************************/
1301 : class PDSWrapperRasterBand final : public GDALProxyRasterBand
1302 : {
1303 : GDALRasterBand *poBaseBand;
1304 :
1305 : protected:
1306 : virtual GDALRasterBand *
1307 4 : RefUnderlyingRasterBand(bool /*bForceOpen*/) const override
1308 : {
1309 4 : return poBaseBand;
1310 : }
1311 :
1312 : public:
1313 3 : explicit PDSWrapperRasterBand(GDALRasterBand *poBaseBandIn)
1314 3 : {
1315 3 : this->poBaseBand = poBaseBandIn;
1316 3 : eDataType = poBaseBand->GetRasterDataType();
1317 3 : poBaseBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
1318 3 : }
1319 :
1320 6 : ~PDSWrapperRasterBand()
1321 3 : {
1322 6 : }
1323 : };
1324 :
1325 : /************************************************************************/
1326 : /* ParseCompressedImage() */
1327 : /************************************************************************/
1328 :
1329 3 : int PDSDataset::ParseCompressedImage()
1330 :
1331 : {
1332 : const CPLString osFileName =
1333 9 : CleanString(GetKeyword("COMPRESSED_FILE.FILE_NAME", ""));
1334 :
1335 6 : const CPLString osPath = CPLGetPathSafe(GetDescription());
1336 : const CPLString osFullFileName =
1337 6 : CPLFormFilenameSafe(osPath, osFileName, nullptr);
1338 :
1339 3 : poCompressedDS =
1340 3 : GDALDataset::FromHandle(GDALOpen(osFullFileName, GA_ReadOnly));
1341 :
1342 3 : if (poCompressedDS == nullptr)
1343 0 : return FALSE;
1344 :
1345 3 : nRasterXSize = poCompressedDS->GetRasterXSize();
1346 3 : nRasterYSize = poCompressedDS->GetRasterYSize();
1347 :
1348 6 : for (int iBand = 0; iBand < poCompressedDS->GetRasterCount(); iBand++)
1349 : {
1350 3 : SetBand(iBand + 1, new PDSWrapperRasterBand(
1351 3 : poCompressedDS->GetRasterBand(iBand + 1)));
1352 : }
1353 :
1354 3 : return TRUE;
1355 : }
1356 :
1357 : /************************************************************************/
1358 : /* Open() */
1359 : /************************************************************************/
1360 :
1361 38 : GDALDataset *PDSDataset::Open(GDALOpenInfo *poOpenInfo)
1362 : {
1363 38 : if (!PDSDriverIdentify(poOpenInfo))
1364 0 : return nullptr;
1365 :
1366 38 : const char *pszHdr = reinterpret_cast<char *>(poOpenInfo->pabyHeader);
1367 38 : if (strstr(pszHdr, "PDS_VERSION_ID") != nullptr &&
1368 38 : strstr(pszHdr, "PDS3") == nullptr)
1369 : {
1370 0 : CPLError(
1371 : CE_Failure, CPLE_OpenFailed,
1372 : "It appears this is an older PDS image type. Only PDS_VERSION_ID "
1373 : "= PDS3 are currently supported by this gdal PDS reader.");
1374 0 : return nullptr;
1375 : }
1376 :
1377 : /* -------------------------------------------------------------------- */
1378 : /* Parse the keyword header. Sometimes there is stuff */
1379 : /* before the PDS_VERSION_ID, which we want to ignore. */
1380 : /* -------------------------------------------------------------------- */
1381 38 : VSILFILE *fpQube = poOpenInfo->fpL;
1382 38 : poOpenInfo->fpL = nullptr;
1383 :
1384 38 : PDSDataset *poDS = new PDSDataset();
1385 38 : poDS->SetDescription(poOpenInfo->pszFilename);
1386 38 : poDS->eAccess = poOpenInfo->eAccess;
1387 :
1388 38 : const char *pszPDSVersionID = strstr(pszHdr, "PDS_VERSION_ID");
1389 38 : int nOffset = 0;
1390 38 : if (pszPDSVersionID)
1391 38 : nOffset = static_cast<int>(pszPDSVersionID - pszHdr);
1392 :
1393 38 : if (!poDS->oKeywords.Ingest(fpQube, nOffset))
1394 : {
1395 2 : delete poDS;
1396 2 : VSIFCloseL(fpQube);
1397 2 : return nullptr;
1398 : }
1399 : poDS->m_aosPDSMD.InsertString(
1400 0 : 0, poDS->oKeywords.GetJsonObject()
1401 72 : .Format(CPLJSONObject::PrettyFormat::Pretty)
1402 72 : .c_str());
1403 36 : VSIFCloseL(fpQube);
1404 :
1405 : /* -------------------------------------------------------------------- */
1406 : /* Is this a compressed image with COMPRESSED_FILE subdomain? */
1407 : /* */
1408 : /* The corresponding parse operations will read keywords, */
1409 : /* establish bands and raster size. */
1410 : /* -------------------------------------------------------------------- */
1411 : CPLString osEncodingType =
1412 108 : poDS->GetKeyword("COMPRESSED_FILE.ENCODING_TYPE", "");
1413 :
1414 : CPLString osCompressedFilename =
1415 108 : CleanString(poDS->GetKeyword("COMPRESSED_FILE.FILE_NAME", ""));
1416 :
1417 : const char *pszImageName =
1418 36 : poDS->GetKeyword("UNCOMPRESSED_FILE.IMAGE.NAME", "");
1419 : CPLString osUncompressedFilename =
1420 36 : CleanString(!EQUAL(pszImageName, "")
1421 : ? pszImageName
1422 142 : : poDS->GetKeyword("UNCOMPRESSED_FILE.FILE_NAME", ""));
1423 :
1424 : VSIStatBufL sStat;
1425 72 : CPLString osFilenamePrefix;
1426 :
1427 39 : if (EQUAL(osEncodingType, "ZIP") && !osCompressedFilename.empty() &&
1428 3 : !osUncompressedFilename.empty())
1429 : {
1430 3 : const CPLString osPath = CPLGetPathSafe(poDS->GetDescription());
1431 : osCompressedFilename =
1432 3 : CPLFormFilenameSafe(osPath, osCompressedFilename, nullptr);
1433 : osUncompressedFilename =
1434 3 : CPLFormFilenameSafe(osPath, osUncompressedFilename, nullptr);
1435 3 : if (VSIStatExL(osCompressedFilename, &sStat, VSI_STAT_EXISTS_FLAG) ==
1436 6 : 0 &&
1437 3 : VSIStatExL(osUncompressedFilename, &sStat, VSI_STAT_EXISTS_FLAG) !=
1438 : 0)
1439 : {
1440 3 : osFilenamePrefix = "/vsizip/" + osCompressedFilename + "/";
1441 3 : poDS->osExternalCube = std::move(osCompressedFilename);
1442 : }
1443 3 : osEncodingType = "";
1444 : }
1445 :
1446 36 : if (!osEncodingType.empty())
1447 : {
1448 3 : if (!poDS->ParseCompressedImage())
1449 : {
1450 0 : delete poDS;
1451 0 : return nullptr;
1452 : }
1453 : }
1454 : else
1455 : {
1456 33 : CPLString osPrefix;
1457 :
1458 33 : if (osUncompressedFilename != "")
1459 5 : osPrefix = "UNCOMPRESSED_FILE.";
1460 :
1461 : // Added ability to see into OBJECT = FILE section to support
1462 : // CRISM. Example file: hsp00017ba0_01_ra218s_trr3.lbl and *.img
1463 73 : if (strlen(poDS->GetKeyword("IMAGE.LINE_SAMPLES")) == 0 &&
1464 40 : strlen(poDS->GetKeyword("FILE.IMAGE.LINE_SAMPLES")) != 0)
1465 2 : osPrefix = "FILE.";
1466 :
1467 33 : if (!poDS->ParseImage(osPrefix, osFilenamePrefix))
1468 : {
1469 0 : delete poDS;
1470 0 : return nullptr;
1471 : }
1472 : }
1473 :
1474 : /* -------------------------------------------------------------------- */
1475 : /* Set the coordinate system and geotransform. */
1476 : /* -------------------------------------------------------------------- */
1477 36 : poDS->ParseSRS();
1478 :
1479 : /* -------------------------------------------------------------------- */
1480 : /* Transfer a few interesting keywords as metadata. */
1481 : /* -------------------------------------------------------------------- */
1482 : static const char *const apszKeywords[] = {"FILTER_NAME",
1483 : "DATA_SET_ID",
1484 : "PRODUCT_ID",
1485 : "PRODUCER_INSTITUTION_NAME",
1486 : "PRODUCT_TYPE",
1487 : "MISSION_NAME",
1488 : "SPACECRAFT_NAME",
1489 : "INSTRUMENT_NAME",
1490 : "INSTRUMENT_ID",
1491 : "TARGET_NAME",
1492 : "CENTER_FILTER_WAVELENGTH",
1493 : "BANDWIDTH",
1494 : "PRODUCT_CREATION_TIME",
1495 : "START_TIME",
1496 : "STOP_TIME",
1497 : "NOTE",
1498 : nullptr};
1499 :
1500 612 : for (int i = 0; apszKeywords[i] != nullptr; i++)
1501 : {
1502 576 : const char *pszKeywordValue = poDS->GetKeyword(apszKeywords[i]);
1503 :
1504 576 : if (pszKeywordValue != nullptr)
1505 576 : poDS->SetMetadataItem(apszKeywords[i], pszKeywordValue);
1506 : }
1507 :
1508 : /* -------------------------------------------------------------------- */
1509 : /* Initialize any PAM information. */
1510 : /* -------------------------------------------------------------------- */
1511 36 : poDS->TryLoadXML();
1512 :
1513 : /* -------------------------------------------------------------------- */
1514 : /* Check for overviews. */
1515 : /* -------------------------------------------------------------------- */
1516 36 : poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
1517 :
1518 36 : return poDS;
1519 : }
1520 :
1521 : /************************************************************************/
1522 : /* GetKeyword() */
1523 : /************************************************************************/
1524 :
1525 1965 : const char *PDSDataset::GetKeyword(const std::string &osPath,
1526 : const char *pszDefault)
1527 :
1528 : {
1529 1965 : return oKeywords.GetKeyword(osPath.c_str(), pszDefault);
1530 : }
1531 :
1532 : /************************************************************************/
1533 : /* GetKeywordSub() */
1534 : /************************************************************************/
1535 :
1536 27 : const char *PDSDataset::GetKeywordSub(const std::string &osPath, int iSubscript,
1537 : const char *pszDefault)
1538 :
1539 : {
1540 27 : const char *pszResult = oKeywords.GetKeyword(osPath.c_str(), nullptr);
1541 :
1542 27 : if (pszResult == nullptr)
1543 0 : return pszDefault;
1544 :
1545 27 : if (pszResult[0] != '(')
1546 0 : return pszDefault;
1547 :
1548 : char **papszTokens =
1549 27 : CSLTokenizeString2(pszResult, "(,)", CSLT_HONOURSTRINGS);
1550 :
1551 27 : if (iSubscript <= CSLCount(papszTokens))
1552 : {
1553 27 : osTempResult = papszTokens[iSubscript - 1];
1554 27 : CSLDestroy(papszTokens);
1555 27 : return osTempResult.c_str();
1556 : }
1557 :
1558 0 : CSLDestroy(papszTokens);
1559 0 : return pszDefault;
1560 : }
1561 :
1562 : /************************************************************************/
1563 : /* GetKeywordUnit() */
1564 : /************************************************************************/
1565 :
1566 25 : const char *PDSDataset::GetKeywordUnit(const char *pszPath, int iSubscript,
1567 : const char *pszDefault)
1568 :
1569 : {
1570 25 : const char *pszResult = oKeywords.GetKeyword(pszPath, nullptr);
1571 :
1572 25 : if (pszResult == nullptr)
1573 0 : return pszDefault;
1574 :
1575 : char **papszTokens =
1576 25 : CSLTokenizeString2(pszResult, "</>", CSLT_HONOURSTRINGS);
1577 :
1578 25 : if (iSubscript <= CSLCount(papszTokens))
1579 : {
1580 18 : osTempResult = papszTokens[iSubscript - 1];
1581 18 : CSLDestroy(papszTokens);
1582 18 : return osTempResult.c_str();
1583 : }
1584 :
1585 7 : CSLDestroy(papszTokens);
1586 7 : return pszDefault;
1587 : }
1588 :
1589 : /************************************************************************/
1590 : /* CleanString() */
1591 : /* */
1592 : /* Removes single or double quotes, and converts spaces to underscores. */
1593 : /************************************************************************/
1594 :
1595 193 : CPLString PDSDataset::CleanString(const CPLString &osInput)
1596 :
1597 : {
1598 310 : if ((osInput.size() < 2) ||
1599 117 : ((osInput.at(0) != '"' || osInput.back() != '"') &&
1600 55 : (osInput.at(0) != '\'' || osInput.back() != '\'')))
1601 131 : return osInput;
1602 :
1603 62 : char *pszWrk = CPLStrdup(osInput.c_str() + 1);
1604 :
1605 62 : pszWrk[strlen(pszWrk) - 1] = '\0';
1606 :
1607 967 : for (int i = 0; pszWrk[i] != '\0'; i++)
1608 : {
1609 905 : if (pszWrk[i] == ' ')
1610 16 : pszWrk[i] = '_';
1611 : }
1612 :
1613 124 : CPLString osOutput = pszWrk;
1614 62 : CPLFree(pszWrk);
1615 62 : return osOutput;
1616 : }
1617 :
1618 : /************************************************************************/
1619 : /* GetMetadataDomainList() */
1620 : /************************************************************************/
1621 :
1622 0 : char **PDSDataset::GetMetadataDomainList()
1623 : {
1624 0 : return BuildMetadataDomainList(nullptr, FALSE, "", "json:PDS", nullptr);
1625 : }
1626 :
1627 : /************************************************************************/
1628 : /* GetMetadata() */
1629 : /************************************************************************/
1630 :
1631 1 : char **PDSDataset::GetMetadata(const char *pszDomain)
1632 : {
1633 1 : if (pszDomain != nullptr && EQUAL(pszDomain, "json:PDS"))
1634 : {
1635 0 : return m_aosPDSMD.List();
1636 : }
1637 1 : return GDALPamDataset::GetMetadata(pszDomain);
1638 : }
1639 :
1640 : /************************************************************************/
1641 : /* GDALRegister_PDS() */
1642 : /************************************************************************/
1643 :
1644 1682 : void GDALRegister_PDS()
1645 :
1646 : {
1647 1682 : if (GDALGetDriverByName(PDS_DRIVER_NAME) != nullptr)
1648 301 : return;
1649 :
1650 1381 : GDALDriver *poDriver = new GDALDriver();
1651 1381 : PDSDriverSetCommonMetadata(poDriver);
1652 :
1653 1381 : poDriver->pfnOpen = PDSDataset::Open;
1654 :
1655 1381 : GetGDALDriverManager()->RegisterDriver(poDriver);
1656 :
1657 : #ifdef PDS_PLUGIN
1658 : GDALRegister_ISIS3();
1659 : GDALRegister_ISIS2();
1660 : GDALRegister_PDS4();
1661 : GDALRegister_VICAR();
1662 : #endif
1663 : }
|