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 = CPLGetPath(pszFilename);
683 72 : const CPLString osName = CPLGetBasename(pszFilename);
684 36 : const char *pszPrjFile = CPLFormCIFilename(osPath, osName, "prj");
685 :
686 36 : VSILFILE *fp = VSIFOpenL(pszPrjFile, "r");
687 36 : if (fp != nullptr)
688 : {
689 0 : VSIFCloseL(fp);
690 :
691 0 : char **papszLines = CSLLoad(pszPrjFile);
692 :
693 0 : m_oSRS.importFromESRI(papszLines);
694 0 : CSLDestroy(papszLines);
695 : }
696 : }
697 :
698 36 : if (dfULXMap != 0.5 || dfULYMap != 0.5 || dfXDim != 1.0 || dfYDim != 1.0)
699 : {
700 25 : bGotTransform = TRUE;
701 25 : adfGeoTransform[0] = dfULXMap;
702 25 : adfGeoTransform[1] = dfXDim;
703 25 : adfGeoTransform[2] = 0.0;
704 25 : adfGeoTransform[3] = dfULYMap;
705 25 : adfGeoTransform[4] = 0.0;
706 25 : adfGeoTransform[5] = dfYDim;
707 :
708 25 : const double rotation = CPLAtof(GetKeyword(
709 50 : osPrefix + "IMAGE_MAP_PROJECTION.MAP_PROJECTION_ROTATION"));
710 25 : if (rotation != 0)
711 : {
712 4 : const double sin_rot =
713 4 : rotation == 90 ? 1.0 : sin(rotation / 180 * M_PI);
714 4 : const double cos_rot =
715 4 : rotation == 90 ? 0.0 : cos(rotation / 180 * M_PI);
716 4 : const double gt_1 =
717 4 : cos_rot * adfGeoTransform[1] - sin_rot * adfGeoTransform[4];
718 4 : const double gt_2 =
719 4 : cos_rot * adfGeoTransform[2] - sin_rot * adfGeoTransform[5];
720 4 : const double gt_0 =
721 4 : cos_rot * adfGeoTransform[0] - sin_rot * adfGeoTransform[3];
722 4 : const double gt_4 =
723 4 : sin_rot * adfGeoTransform[1] + cos_rot * adfGeoTransform[4];
724 4 : const double gt_5 =
725 4 : sin_rot * adfGeoTransform[2] + cos_rot * adfGeoTransform[5];
726 4 : const double gt_3 =
727 4 : sin_rot * adfGeoTransform[0] + cos_rot * adfGeoTransform[3];
728 4 : adfGeoTransform[1] = gt_1;
729 4 : adfGeoTransform[2] = gt_2;
730 4 : adfGeoTransform[0] = gt_0;
731 4 : adfGeoTransform[4] = gt_4;
732 4 : adfGeoTransform[5] = gt_5;
733 4 : adfGeoTransform[3] = gt_3;
734 : }
735 : }
736 :
737 36 : if (!bGotTransform)
738 11 : bGotTransform = GDALReadWorldFile(pszFilename, "psw", adfGeoTransform);
739 :
740 36 : if (!bGotTransform)
741 11 : bGotTransform = GDALReadWorldFile(pszFilename, "wld", adfGeoTransform);
742 36 : }
743 :
744 : /************************************************************************/
745 : /* GetRawBinaryLayout() */
746 : /************************************************************************/
747 :
748 2 : bool PDSDataset::GetRawBinaryLayout(GDALDataset::RawBinaryLayout &sLayout)
749 : {
750 2 : if (!RawDataset::GetRawBinaryLayout(sLayout))
751 0 : return false;
752 2 : sLayout.osRawFilename = m_osImageFilename;
753 2 : return true;
754 : }
755 :
756 : /************************************************************************/
757 : /* PDSConvertFromHex() */
758 : /************************************************************************/
759 :
760 3 : static GUInt32 PDSConvertFromHex(const char *pszVal)
761 : {
762 3 : if (!STARTS_WITH_CI(pszVal, "16#"))
763 0 : return 0;
764 :
765 3 : pszVal += 3;
766 3 : GUInt32 nVal = 0;
767 27 : while (*pszVal != '#' && *pszVal != '\0')
768 : {
769 24 : nVal <<= 4;
770 24 : if (*pszVal >= '0' && *pszVal <= '9')
771 3 : nVal += *pszVal - '0';
772 21 : else if (*pszVal >= 'A' && *pszVal <= 'F')
773 21 : nVal += *pszVal - 'A' + 10;
774 : else
775 0 : return 0;
776 24 : pszVal++;
777 : }
778 :
779 3 : return nVal;
780 : }
781 :
782 : /************************************************************************/
783 : /* ParseImage() */
784 : /************************************************************************/
785 :
786 33 : int PDSDataset::ParseImage(const CPLString &osPrefix,
787 : const CPLString &osFilenamePrefix)
788 : {
789 : /* ------------------------------------------------------------------- */
790 : /* We assume the user is pointing to the label (i.e. .lbl) file. */
791 : /* ------------------------------------------------------------------- */
792 : // IMAGE can be inline or detached and point to an image name
793 : // ^IMAGE = 3
794 : // ^IMAGE = "GLOBAL_ALBEDO_8PPD.IMG"
795 : // ^IMAGE = "MEGT90N000CB.IMG"
796 : // ^IMAGE = ("FOO.IMG",1) -- start at record 1 (1 based)
797 : // ^IMAGE = ("FOO.IMG") -- start at record 1 equiv of
798 : // ("FOO.IMG",1) ^IMAGE = ("FOO.IMG", 5 <BYTES>) -- start at
799 : // byte 5 (the fifth byte in the file) ^IMAGE = 10851 <BYTES>
800 : // ^SPECTRAL_QUBE = 5 for multi-band images
801 : // ^QUBE = 5 for multi-band images
802 :
803 66 : CPLString osImageKeyword = "IMAGE";
804 99 : CPLString osQube = GetKeyword(osPrefix + "^" + osImageKeyword, "");
805 33 : m_osImageFilename = GetDescription();
806 :
807 33 : if (EQUAL(osQube, ""))
808 : {
809 0 : osImageKeyword = "SPECTRAL_QUBE";
810 0 : osQube = GetKeyword(osPrefix + "^" + osImageKeyword);
811 : }
812 :
813 33 : if (EQUAL(osQube, ""))
814 : {
815 0 : osImageKeyword = "QUBE";
816 0 : osQube = GetKeyword(osPrefix + "^" + osImageKeyword);
817 : }
818 :
819 33 : const int nQube = atoi(osQube);
820 33 : int nDetachedOffset = 0;
821 33 : bool bDetachedOffsetInBytes = false;
822 :
823 33 : if (!osQube.empty() && osQube[0] == '(')
824 : {
825 9 : osQube = "\"";
826 9 : osQube += GetKeywordSub(osPrefix + "^" + osImageKeyword, 1);
827 9 : osQube += "\"";
828 : nDetachedOffset =
829 9 : atoi(GetKeywordSub(osPrefix + "^" + osImageKeyword, 2, "1"));
830 9 : if (nDetachedOffset >= 1)
831 9 : nDetachedOffset -= 1;
832 :
833 : // If this is not explicitly in bytes, then it is assumed to be in
834 : // records, and we need to translate to bytes.
835 18 : if (strstr(GetKeywordSub(osPrefix + "^" + osImageKeyword, 2),
836 9 : "<BYTES>") != nullptr)
837 2 : bDetachedOffsetInBytes = true;
838 : }
839 :
840 33 : if (!osQube.empty() && osQube[0] == '"')
841 : {
842 26 : const CPLString osFilename = CleanString(osQube);
843 13 : if (!osFilenamePrefix.empty())
844 : {
845 3 : m_osImageFilename = osFilenamePrefix + osFilename;
846 : }
847 : else
848 : {
849 20 : CPLString osTPath = CPLGetPath(GetDescription());
850 10 : m_osImageFilename = CPLFormCIFilename(osTPath, osFilename, nullptr);
851 10 : osExternalCube = m_osImageFilename;
852 : }
853 : }
854 :
855 : /* -------------------------------------------------------------------- */
856 : /* Checks to see if this is raw PDS image not compressed image */
857 : /* so ENCODING_TYPE either does not exist or it equals "N/A". */
858 : /* or "DCT_DECOMPRESSED". */
859 : /* Compressed types will not be supported in this routine */
860 : /* -------------------------------------------------------------------- */
861 :
862 : const CPLString osEncodingType =
863 99 : CleanString(GetKeyword(osPrefix + "IMAGE.ENCODING_TYPE", "N/A"));
864 33 : if (!EQUAL(osEncodingType, "N/A") &&
865 0 : !EQUAL(osEncodingType, "DCT_DECOMPRESSED"))
866 : {
867 0 : CPLError(CE_Failure, CPLE_OpenFailed,
868 : "*** PDS image file has an ENCODING_TYPE parameter:\n"
869 : "*** GDAL PDS driver does not support compressed image types\n"
870 : "found: (%s)\n\n",
871 : osEncodingType.c_str());
872 0 : return FALSE;
873 : }
874 : /**************** end ENCODING_TYPE check ***********************/
875 :
876 : /*********** Grab layout type (BSQ, BIP, BIL) ************/
877 : // AXIS_NAME = (SAMPLE,LINE,BAND)
878 : /*********** Grab samples lines band **************/
879 : /** if AXIS_NAME = "" then Bands=1 and Sample and Lines **/
880 : /** are there own keywords "LINES" and "LINE_SAMPLES" **/
881 : /** if not NULL then CORE_ITEMS keyword i.e. (234,322,2) **/
882 : /***********************************************************/
883 33 : int eLayout = PDS_BSQ; // default to band seq.
884 33 : int nRows, nCols, l_nBands = 1;
885 :
886 99 : CPLString value = GetKeyword(osPrefix + osImageKeyword + ".AXIS_NAME", "");
887 33 : if (EQUAL(value, "(SAMPLE,LINE,BAND)"))
888 : {
889 0 : eLayout = PDS_BSQ;
890 : nCols =
891 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 1));
892 : nRows =
893 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 2));
894 : l_nBands =
895 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 3));
896 : }
897 33 : else if (EQUAL(value, "(BAND,LINE,SAMPLE)"))
898 : {
899 0 : eLayout = PDS_BIP;
900 : l_nBands =
901 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 1));
902 : nRows =
903 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 2));
904 : nCols =
905 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 3));
906 : }
907 33 : else if (EQUAL(value, "(SAMPLE,BAND,LINE)"))
908 : {
909 0 : eLayout = PDS_BIL;
910 : nCols =
911 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 1));
912 : l_nBands =
913 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 2));
914 : nRows =
915 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 3));
916 : }
917 33 : else if (EQUAL(value, ""))
918 : {
919 33 : eLayout = PDS_BSQ;
920 : nCols =
921 33 : atoi(GetKeyword(osPrefix + osImageKeyword + ".LINE_SAMPLES", ""));
922 33 : nRows = atoi(GetKeyword(osPrefix + osImageKeyword + ".LINES", ""));
923 33 : l_nBands = atoi(GetKeyword(osPrefix + osImageKeyword + ".BANDS", "1"));
924 : }
925 : else
926 : {
927 0 : CPLError(CE_Failure, CPLE_OpenFailed,
928 : "%s layout not supported. Abort\n\n", value.c_str());
929 0 : return FALSE;
930 : }
931 :
932 : CPLString osBAND_STORAGE_TYPE =
933 66 : GetKeyword(osPrefix + "IMAGE.BAND_STORAGE_TYPE", "");
934 33 : if (EQUAL(osBAND_STORAGE_TYPE, "BAND_SEQUENTIAL"))
935 : {
936 12 : eLayout = PDS_BSQ;
937 : }
938 21 : else if (EQUAL(osBAND_STORAGE_TYPE, "PIXEL_INTERLEAVED"))
939 : {
940 0 : eLayout = PDS_BIP;
941 : }
942 21 : else if (EQUAL(osBAND_STORAGE_TYPE, "LINE_INTERLEAVED"))
943 : {
944 2 : eLayout = PDS_BIL;
945 : }
946 19 : else if (!osBAND_STORAGE_TYPE.empty())
947 : {
948 4 : CPLDebug("PDS", "Unhandled BAND_STORAGE_TYPE = %s",
949 : osBAND_STORAGE_TYPE.c_str());
950 : }
951 :
952 : /*********** Grab Qube record bytes **********/
953 33 : int record_bytes = atoi(GetKeyword(osPrefix + "IMAGE.RECORD_BYTES"));
954 33 : if (record_bytes == 0)
955 33 : record_bytes = atoi(GetKeyword(osPrefix + "RECORD_BYTES"));
956 :
957 : // this can happen with "record_type = undefined".
958 33 : if (record_bytes < 0)
959 0 : return FALSE;
960 33 : if (record_bytes == 0)
961 3 : record_bytes = 1;
962 :
963 33 : int nSkipBytes = 0;
964 : try
965 : {
966 33 : if (nQube > 0)
967 : {
968 20 : if (osQube.find("<BYTES>") != CPLString::npos)
969 3 : nSkipBytes = (CPLSM(nQube) - CPLSM(1)).v();
970 : else
971 17 : nSkipBytes = (CPLSM(nQube - 1) * CPLSM(record_bytes)).v();
972 : }
973 13 : else if (nDetachedOffset > 0)
974 : {
975 4 : if (bDetachedOffsetInBytes)
976 2 : nSkipBytes = nDetachedOffset;
977 : else
978 : {
979 2 : nSkipBytes = (CPLSM(nDetachedOffset) * CPLSM(record_bytes)).v();
980 : }
981 : }
982 : else
983 9 : nSkipBytes = 0;
984 : }
985 0 : catch (const CPLSafeIntOverflow &)
986 : {
987 0 : return FALSE;
988 : }
989 :
990 : const int nLinePrefixBytes =
991 33 : atoi(GetKeyword(osPrefix + "IMAGE.LINE_PREFIX_BYTES", ""));
992 33 : if (nLinePrefixBytes < 0)
993 0 : return false;
994 33 : nSkipBytes += nLinePrefixBytes;
995 :
996 : /*********** Grab SAMPLE_TYPE *****************/
997 : /** if keyword not found leave as "M" or "MSB" **/
998 :
999 66 : CPLString osST = GetKeyword(osPrefix + "IMAGE.SAMPLE_TYPE");
1000 33 : if (osST.size() >= 2 && osST[0] == '"' && osST.back() == '"')
1001 4 : osST = osST.substr(1, osST.size() - 2);
1002 :
1003 33 : char chByteOrder = 'M'; // default to MSB
1004 64 : if ((EQUAL(osST, "LSB_INTEGER")) || (EQUAL(osST, "LSB")) || // just in case
1005 31 : (EQUAL(osST, "LSB_UNSIGNED_INTEGER")) ||
1006 27 : (EQUAL(osST, "LSB_SIGNED_INTEGER")) ||
1007 27 : (EQUAL(osST, "UNSIGNED_INTEGER")) || (EQUAL(osST, "VAX_REAL")) ||
1008 12 : (EQUAL(osST, "VAX_INTEGER")) ||
1009 76 : (EQUAL(osST, "PC_INTEGER")) || // just in case
1010 12 : (EQUAL(osST, "PC_REAL")))
1011 : {
1012 26 : chByteOrder = 'I';
1013 : }
1014 :
1015 : /**** Grab format type - pds supports 1,2,4,8,16,32,64 (in theory) **/
1016 : /**** I have only seen 8, 16, 32 (float) in released datasets **/
1017 33 : GDALDataType eDataType = GDT_Byte;
1018 33 : int nSuffixItems = 0;
1019 33 : int nSuffixLines = 0;
1020 33 : int nSuffixBytes = 4; // Default as per PDS specification
1021 33 : double dfNoData = 0.0;
1022 33 : double dfScale = 1.0;
1023 33 : double dfOffset = 0.0;
1024 33 : const char *pszUnit = nullptr;
1025 33 : const char *pszDesc = nullptr;
1026 :
1027 66 : CPLString osSB = GetKeyword(osPrefix + "IMAGE.SAMPLE_BITS", "");
1028 33 : if (!osSB.empty())
1029 : {
1030 33 : const int itype = atoi(osSB);
1031 33 : switch (itype)
1032 : {
1033 23 : case 8:
1034 23 : eDataType = GDT_Byte;
1035 23 : dfNoData = PDS_NULL1;
1036 23 : break;
1037 5 : case 16:
1038 5 : if (strstr(osST, "UNSIGNED") != nullptr)
1039 : {
1040 3 : dfNoData = PDS_NULL1;
1041 3 : eDataType = GDT_UInt16;
1042 : }
1043 : else
1044 : {
1045 2 : eDataType = GDT_Int16;
1046 2 : dfNoData = PDS_NULL2;
1047 : }
1048 5 : break;
1049 5 : case 32:
1050 5 : eDataType = GDT_Float32;
1051 5 : dfNoData = PDS_NULL3;
1052 5 : break;
1053 0 : case 64:
1054 0 : eDataType = GDT_Float64;
1055 0 : dfNoData = PDS_NULL3;
1056 0 : break;
1057 0 : default:
1058 0 : CPLError(CE_Failure, CPLE_AppDefined,
1059 : "Sample_bits of %d is not supported in this gdal PDS "
1060 : "reader.",
1061 : itype);
1062 0 : return FALSE;
1063 : }
1064 :
1065 33 : dfOffset = CPLAtofM(GetKeyword(osPrefix + "IMAGE.OFFSET", "0.0"));
1066 : dfScale =
1067 33 : CPLAtofM(GetKeyword(osPrefix + "IMAGE.SCALING_FACTOR", "1.0"));
1068 : }
1069 : else /* No IMAGE object, search for the QUBE. */
1070 : {
1071 0 : osSB = GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_ITEM_BYTES", "");
1072 0 : const int itype = atoi(osSB);
1073 0 : switch (itype)
1074 : {
1075 0 : case 1:
1076 0 : eDataType = GDT_Byte;
1077 0 : break;
1078 0 : case 2:
1079 0 : if (strstr(osST, "UNSIGNED") != nullptr)
1080 0 : eDataType = GDT_UInt16;
1081 : else
1082 0 : eDataType = GDT_Int16;
1083 0 : break;
1084 0 : case 4:
1085 0 : eDataType = GDT_Float32;
1086 0 : break;
1087 0 : default:
1088 0 : CPLError(CE_Failure, CPLE_AppDefined,
1089 : "CORE_ITEM_BYTES of %d is not supported in this gdal "
1090 : "PDS reader.",
1091 : itype);
1092 0 : return FALSE;
1093 : }
1094 :
1095 : /* Parse suffix dimensions if defined. */
1096 0 : value = GetKeyword(osPrefix + "SPECTRAL_QUBE.SUFFIX_ITEMS", "");
1097 0 : if (!value.empty())
1098 : {
1099 0 : value = GetKeyword(osPrefix + "SPECTRAL_QUBE.SUFFIX_BYTES", "");
1100 0 : if (!value.empty())
1101 0 : nSuffixBytes = atoi(value);
1102 :
1103 : nSuffixItems =
1104 0 : atoi(GetKeywordSub(osPrefix + "SPECTRAL_QUBE.SUFFIX_ITEMS", 1));
1105 : nSuffixLines =
1106 0 : atoi(GetKeywordSub(osPrefix + "SPECTRAL_QUBE.SUFFIX_ITEMS", 2));
1107 : }
1108 :
1109 0 : value = GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_NULL", "");
1110 0 : if (!value.empty())
1111 0 : dfNoData = CPLAtofM(value);
1112 :
1113 : dfOffset =
1114 0 : CPLAtofM(GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_BASE", "0.0"));
1115 0 : dfScale = CPLAtofM(
1116 0 : GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_MULTIPLIER", "1.0"));
1117 0 : pszUnit = GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_UNIT", nullptr);
1118 0 : pszDesc = GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_NAME", nullptr);
1119 : }
1120 :
1121 : /* -------------------------------------------------------------------- */
1122 : /* Is there a specific nodata value in the file? Either the */
1123 : /* MISSING or MISSING_CONSTANT keywords are nodata. */
1124 : /* -------------------------------------------------------------------- */
1125 :
1126 33 : const char *pszMissing = GetKeyword(osPrefix + "IMAGE.MISSING", nullptr);
1127 33 : if (pszMissing == nullptr)
1128 30 : pszMissing = GetKeyword(osPrefix + "IMAGE.MISSING_CONSTANT", nullptr);
1129 :
1130 33 : if (pszMissing != nullptr)
1131 : {
1132 9 : if (*pszMissing == '"')
1133 3 : pszMissing++;
1134 :
1135 : /* For example : MISSING_CONSTANT = "16#FF7FFFFB#" */
1136 9 : if (STARTS_WITH_CI(pszMissing, "16#") &&
1137 3 : strlen(pszMissing) >= 3 + 8 + 1 && pszMissing[3 + 8] == '#' &&
1138 0 : (eDataType == GDT_Float32 || eDataType == GDT_Float64))
1139 : {
1140 3 : GUInt32 nVal = PDSConvertFromHex(pszMissing);
1141 : float fVal;
1142 3 : memcpy(&fVal, &nVal, 4);
1143 3 : dfNoData = fVal;
1144 : }
1145 : else
1146 6 : dfNoData = CPLAtofM(pszMissing);
1147 : }
1148 :
1149 : /* -------------------------------------------------------------------- */
1150 : /* Did we get the required keywords? If not we return with */
1151 : /* this never having been considered to be a match. This isn't */
1152 : /* an error! */
1153 : /* -------------------------------------------------------------------- */
1154 66 : if (!GDALCheckDatasetDimensions(nCols, nRows) ||
1155 33 : !GDALCheckBandCount(l_nBands, false))
1156 : {
1157 0 : return FALSE;
1158 : }
1159 :
1160 : /* -------------------------------------------------------------------- */
1161 : /* Capture some information from the file that is of interest. */
1162 : /* -------------------------------------------------------------------- */
1163 33 : nRasterXSize = nCols;
1164 33 : nRasterYSize = nRows;
1165 :
1166 : /* -------------------------------------------------------------------- */
1167 : /* Open target binary file. */
1168 : /* -------------------------------------------------------------------- */
1169 :
1170 33 : if (eAccess == GA_ReadOnly)
1171 : {
1172 33 : fpImage = VSIFOpenL(m_osImageFilename, "rb");
1173 33 : if (fpImage == nullptr)
1174 : {
1175 0 : CPLError(CE_Failure, CPLE_OpenFailed, "Failed to open %s.\n%s",
1176 0 : m_osImageFilename.c_str(), VSIStrerror(errno));
1177 0 : return FALSE;
1178 : }
1179 : }
1180 : else
1181 : {
1182 0 : fpImage = VSIFOpenL(m_osImageFilename, "r+b");
1183 0 : if (fpImage == nullptr)
1184 : {
1185 0 : CPLError(CE_Failure, CPLE_OpenFailed,
1186 : "Failed to open %s with write permission.\n%s",
1187 0 : m_osImageFilename.c_str(), VSIStrerror(errno));
1188 0 : return FALSE;
1189 : }
1190 : }
1191 :
1192 : /* -------------------------------------------------------------------- */
1193 : /* Compute the line offset. */
1194 : /* -------------------------------------------------------------------- */
1195 33 : const int nItemSize = GDALGetDataTypeSize(eDataType) / 8;
1196 :
1197 : // Needed for N1349177584_2.LBL from
1198 : // https://trac.osgeo.org/gdal/attachment/ticket/3355/PDS-TestFiles.zip
1199 33 : int nLineOffset = nLinePrefixBytes;
1200 :
1201 : int nPixelOffset;
1202 : vsi_l_offset nBandOffset;
1203 :
1204 186 : const auto CPLSM64 = [](int x) { return CPLSM(static_cast<GInt64>(x)); };
1205 :
1206 : try
1207 : {
1208 33 : if (eLayout == PDS_BIP)
1209 : {
1210 0 : nPixelOffset = (CPLSM(nItemSize) * CPLSM(l_nBands)).v();
1211 0 : nBandOffset = nItemSize;
1212 : nLineOffset =
1213 0 : (CPLSM(nLineOffset) + CPLSM(nPixelOffset) * CPLSM(nCols)).v();
1214 : }
1215 33 : else if (eLayout == PDS_BSQ)
1216 : {
1217 31 : nPixelOffset = nItemSize;
1218 : nLineOffset =
1219 31 : (CPLSM(nLineOffset) + CPLSM(nPixelOffset) * CPLSM(nCols)).v();
1220 31 : nBandOffset = static_cast<vsi_l_offset>(
1221 31 : (CPLSM64(nLineOffset) * CPLSM64(nRows) +
1222 31 : CPLSM64(nSuffixLines) *
1223 62 : (CPLSM64(nCols) + CPLSM64(nSuffixItems)) *
1224 124 : CPLSM64(nSuffixBytes))
1225 31 : .v());
1226 : }
1227 : else /* assume BIL */
1228 : {
1229 2 : nPixelOffset = nItemSize;
1230 2 : nBandOffset = (CPLSM(nItemSize) * CPLSM(nCols)).v();
1231 : nLineOffset =
1232 2 : (CPLSM(nLineOffset) +
1233 6 : CPLSM(static_cast<int>(nBandOffset)) * CPLSM(l_nBands))
1234 2 : .v();
1235 : }
1236 : }
1237 0 : catch (const CPLSafeIntOverflow &)
1238 : {
1239 0 : CPLError(CE_Failure, CPLE_AppDefined, "Integer overflow");
1240 0 : return FALSE;
1241 : }
1242 :
1243 : /* -------------------------------------------------------------------- */
1244 : /* Create band information objects. */
1245 : /* -------------------------------------------------------------------- */
1246 278 : for (int i = 0; i < l_nBands; i++)
1247 : {
1248 : auto poBand = RawRasterBand::Create(
1249 : this, i + 1, fpImage,
1250 245 : nSkipBytes + static_cast<vsi_l_offset>(nBandOffset) * i,
1251 : nPixelOffset, nLineOffset, eDataType,
1252 : chByteOrder == 'I' || chByteOrder == 'L'
1253 245 : ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
1254 : : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN,
1255 245 : RawRasterBand::OwnFP::NO);
1256 245 : if (!poBand)
1257 0 : return FALSE;
1258 :
1259 245 : if (l_nBands == 1)
1260 : {
1261 : const char *pszMin =
1262 31 : GetKeyword(osPrefix + "IMAGE.MINIMUM", nullptr);
1263 : const char *pszMax =
1264 31 : GetKeyword(osPrefix + "IMAGE.MAXIMUM", nullptr);
1265 31 : const char *pszMean = GetKeyword(osPrefix + "IMAGE.MEAN", nullptr);
1266 : const char *pszStdDev =
1267 31 : GetKeyword(osPrefix + "IMAGE.STANDARD_DEVIATION", nullptr);
1268 31 : if (pszMin != nullptr && pszMax != nullptr && pszMean != nullptr &&
1269 : pszStdDev != nullptr)
1270 : {
1271 0 : poBand->SetStatistics(CPLAtofM(pszMin), CPLAtofM(pszMax),
1272 0 : CPLAtofM(pszMean), CPLAtofM(pszStdDev));
1273 : }
1274 : }
1275 :
1276 245 : poBand->SetNoDataValue(dfNoData);
1277 :
1278 : // Set offset/scale values at the PAM level.
1279 245 : poBand->SetOffset(dfOffset);
1280 245 : poBand->SetScale(dfScale);
1281 245 : if (pszUnit)
1282 0 : poBand->SetUnitType(pszUnit);
1283 245 : if (pszDesc)
1284 0 : poBand->SetDescription(pszDesc);
1285 :
1286 245 : SetBand(i + 1, std::move(poBand));
1287 : }
1288 :
1289 33 : return TRUE;
1290 : }
1291 :
1292 : /************************************************************************/
1293 : /* ==================================================================== */
1294 : /* PDSWrapperRasterBand */
1295 : /* */
1296 : /* proxy for the jp2 or other compressed bands. */
1297 : /* ==================================================================== */
1298 : /************************************************************************/
1299 : class PDSWrapperRasterBand final : public GDALProxyRasterBand
1300 : {
1301 : GDALRasterBand *poBaseBand;
1302 :
1303 : protected:
1304 : virtual GDALRasterBand *
1305 4 : RefUnderlyingRasterBand(bool /*bForceOpen*/) const override
1306 : {
1307 4 : return poBaseBand;
1308 : }
1309 :
1310 : public:
1311 3 : explicit PDSWrapperRasterBand(GDALRasterBand *poBaseBandIn)
1312 3 : {
1313 3 : this->poBaseBand = poBaseBandIn;
1314 3 : eDataType = poBaseBand->GetRasterDataType();
1315 3 : poBaseBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
1316 3 : }
1317 :
1318 6 : ~PDSWrapperRasterBand()
1319 3 : {
1320 6 : }
1321 : };
1322 :
1323 : /************************************************************************/
1324 : /* ParseCompressedImage() */
1325 : /************************************************************************/
1326 :
1327 3 : int PDSDataset::ParseCompressedImage()
1328 :
1329 : {
1330 : const CPLString osFileName =
1331 9 : CleanString(GetKeyword("COMPRESSED_FILE.FILE_NAME", ""));
1332 :
1333 6 : const CPLString osPath = CPLGetPath(GetDescription());
1334 : const CPLString osFullFileName =
1335 6 : CPLFormFilename(osPath, osFileName, nullptr);
1336 :
1337 3 : poCompressedDS =
1338 3 : GDALDataset::FromHandle(GDALOpen(osFullFileName, GA_ReadOnly));
1339 :
1340 3 : if (poCompressedDS == nullptr)
1341 0 : return FALSE;
1342 :
1343 3 : nRasterXSize = poCompressedDS->GetRasterXSize();
1344 3 : nRasterYSize = poCompressedDS->GetRasterYSize();
1345 :
1346 6 : for (int iBand = 0; iBand < poCompressedDS->GetRasterCount(); iBand++)
1347 : {
1348 3 : SetBand(iBand + 1, new PDSWrapperRasterBand(
1349 3 : poCompressedDS->GetRasterBand(iBand + 1)));
1350 : }
1351 :
1352 3 : return TRUE;
1353 : }
1354 :
1355 : /************************************************************************/
1356 : /* Open() */
1357 : /************************************************************************/
1358 :
1359 38 : GDALDataset *PDSDataset::Open(GDALOpenInfo *poOpenInfo)
1360 : {
1361 38 : if (!PDSDriverIdentify(poOpenInfo))
1362 0 : return nullptr;
1363 :
1364 38 : const char *pszHdr = reinterpret_cast<char *>(poOpenInfo->pabyHeader);
1365 38 : if (strstr(pszHdr, "PDS_VERSION_ID") != nullptr &&
1366 38 : strstr(pszHdr, "PDS3") == nullptr)
1367 : {
1368 0 : CPLError(
1369 : CE_Failure, CPLE_OpenFailed,
1370 : "It appears this is an older PDS image type. Only PDS_VERSION_ID "
1371 : "= PDS3 are currently supported by this gdal PDS reader.");
1372 0 : return nullptr;
1373 : }
1374 :
1375 : /* -------------------------------------------------------------------- */
1376 : /* Parse the keyword header. Sometimes there is stuff */
1377 : /* before the PDS_VERSION_ID, which we want to ignore. */
1378 : /* -------------------------------------------------------------------- */
1379 38 : VSILFILE *fpQube = poOpenInfo->fpL;
1380 38 : poOpenInfo->fpL = nullptr;
1381 :
1382 38 : PDSDataset *poDS = new PDSDataset();
1383 38 : poDS->SetDescription(poOpenInfo->pszFilename);
1384 38 : poDS->eAccess = poOpenInfo->eAccess;
1385 :
1386 38 : const char *pszPDSVersionID = strstr(pszHdr, "PDS_VERSION_ID");
1387 38 : int nOffset = 0;
1388 38 : if (pszPDSVersionID)
1389 38 : nOffset = static_cast<int>(pszPDSVersionID - pszHdr);
1390 :
1391 38 : if (!poDS->oKeywords.Ingest(fpQube, nOffset))
1392 : {
1393 2 : delete poDS;
1394 2 : VSIFCloseL(fpQube);
1395 2 : return nullptr;
1396 : }
1397 : poDS->m_aosPDSMD.InsertString(
1398 0 : 0, poDS->oKeywords.GetJsonObject()
1399 72 : .Format(CPLJSONObject::PrettyFormat::Pretty)
1400 72 : .c_str());
1401 36 : VSIFCloseL(fpQube);
1402 :
1403 : /* -------------------------------------------------------------------- */
1404 : /* Is this a compressed image with COMPRESSED_FILE subdomain? */
1405 : /* */
1406 : /* The corresponding parse operations will read keywords, */
1407 : /* establish bands and raster size. */
1408 : /* -------------------------------------------------------------------- */
1409 : CPLString osEncodingType =
1410 108 : poDS->GetKeyword("COMPRESSED_FILE.ENCODING_TYPE", "");
1411 :
1412 : CPLString osCompressedFilename =
1413 108 : CleanString(poDS->GetKeyword("COMPRESSED_FILE.FILE_NAME", ""));
1414 :
1415 : const char *pszImageName =
1416 36 : poDS->GetKeyword("UNCOMPRESSED_FILE.IMAGE.NAME", "");
1417 : CPLString osUncompressedFilename =
1418 36 : CleanString(!EQUAL(pszImageName, "")
1419 : ? pszImageName
1420 142 : : poDS->GetKeyword("UNCOMPRESSED_FILE.FILE_NAME", ""));
1421 :
1422 : VSIStatBufL sStat;
1423 72 : CPLString osFilenamePrefix;
1424 :
1425 39 : if (EQUAL(osEncodingType, "ZIP") && !osCompressedFilename.empty() &&
1426 3 : !osUncompressedFilename.empty())
1427 : {
1428 3 : const CPLString osPath = CPLGetPath(poDS->GetDescription());
1429 : osCompressedFilename =
1430 3 : CPLFormFilename(osPath, osCompressedFilename, nullptr);
1431 : osUncompressedFilename =
1432 3 : CPLFormFilename(osPath, osUncompressedFilename, nullptr);
1433 3 : if (VSIStatExL(osCompressedFilename, &sStat, VSI_STAT_EXISTS_FLAG) ==
1434 6 : 0 &&
1435 3 : VSIStatExL(osUncompressedFilename, &sStat, VSI_STAT_EXISTS_FLAG) !=
1436 : 0)
1437 : {
1438 3 : osFilenamePrefix = "/vsizip/" + osCompressedFilename + "/";
1439 3 : poDS->osExternalCube = std::move(osCompressedFilename);
1440 : }
1441 3 : osEncodingType = "";
1442 : }
1443 :
1444 36 : if (!osEncodingType.empty())
1445 : {
1446 3 : if (!poDS->ParseCompressedImage())
1447 : {
1448 0 : delete poDS;
1449 0 : return nullptr;
1450 : }
1451 : }
1452 : else
1453 : {
1454 33 : CPLString osPrefix;
1455 :
1456 33 : if (osUncompressedFilename != "")
1457 5 : osPrefix = "UNCOMPRESSED_FILE.";
1458 :
1459 : // Added ability to see into OBJECT = FILE section to support
1460 : // CRISM. Example file: hsp00017ba0_01_ra218s_trr3.lbl and *.img
1461 73 : if (strlen(poDS->GetKeyword("IMAGE.LINE_SAMPLES")) == 0 &&
1462 40 : strlen(poDS->GetKeyword("FILE.IMAGE.LINE_SAMPLES")) != 0)
1463 2 : osPrefix = "FILE.";
1464 :
1465 33 : if (!poDS->ParseImage(osPrefix, osFilenamePrefix))
1466 : {
1467 0 : delete poDS;
1468 0 : return nullptr;
1469 : }
1470 : }
1471 :
1472 : /* -------------------------------------------------------------------- */
1473 : /* Set the coordinate system and geotransform. */
1474 : /* -------------------------------------------------------------------- */
1475 36 : poDS->ParseSRS();
1476 :
1477 : /* -------------------------------------------------------------------- */
1478 : /* Transfer a few interesting keywords as metadata. */
1479 : /* -------------------------------------------------------------------- */
1480 : static const char *const apszKeywords[] = {"FILTER_NAME",
1481 : "DATA_SET_ID",
1482 : "PRODUCT_ID",
1483 : "PRODUCER_INSTITUTION_NAME",
1484 : "PRODUCT_TYPE",
1485 : "MISSION_NAME",
1486 : "SPACECRAFT_NAME",
1487 : "INSTRUMENT_NAME",
1488 : "INSTRUMENT_ID",
1489 : "TARGET_NAME",
1490 : "CENTER_FILTER_WAVELENGTH",
1491 : "BANDWIDTH",
1492 : "PRODUCT_CREATION_TIME",
1493 : "START_TIME",
1494 : "STOP_TIME",
1495 : "NOTE",
1496 : nullptr};
1497 :
1498 612 : for (int i = 0; apszKeywords[i] != nullptr; i++)
1499 : {
1500 576 : const char *pszKeywordValue = poDS->GetKeyword(apszKeywords[i]);
1501 :
1502 576 : if (pszKeywordValue != nullptr)
1503 576 : poDS->SetMetadataItem(apszKeywords[i], pszKeywordValue);
1504 : }
1505 :
1506 : /* -------------------------------------------------------------------- */
1507 : /* Initialize any PAM information. */
1508 : /* -------------------------------------------------------------------- */
1509 36 : poDS->TryLoadXML();
1510 :
1511 : /* -------------------------------------------------------------------- */
1512 : /* Check for overviews. */
1513 : /* -------------------------------------------------------------------- */
1514 36 : poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
1515 :
1516 36 : return poDS;
1517 : }
1518 :
1519 : /************************************************************************/
1520 : /* GetKeyword() */
1521 : /************************************************************************/
1522 :
1523 1965 : const char *PDSDataset::GetKeyword(const std::string &osPath,
1524 : const char *pszDefault)
1525 :
1526 : {
1527 1965 : return oKeywords.GetKeyword(osPath.c_str(), pszDefault);
1528 : }
1529 :
1530 : /************************************************************************/
1531 : /* GetKeywordSub() */
1532 : /************************************************************************/
1533 :
1534 27 : const char *PDSDataset::GetKeywordSub(const std::string &osPath, int iSubscript,
1535 : const char *pszDefault)
1536 :
1537 : {
1538 27 : const char *pszResult = oKeywords.GetKeyword(osPath.c_str(), nullptr);
1539 :
1540 27 : if (pszResult == nullptr)
1541 0 : return pszDefault;
1542 :
1543 27 : if (pszResult[0] != '(')
1544 0 : return pszDefault;
1545 :
1546 : char **papszTokens =
1547 27 : CSLTokenizeString2(pszResult, "(,)", CSLT_HONOURSTRINGS);
1548 :
1549 27 : if (iSubscript <= CSLCount(papszTokens))
1550 : {
1551 27 : osTempResult = papszTokens[iSubscript - 1];
1552 27 : CSLDestroy(papszTokens);
1553 27 : return osTempResult.c_str();
1554 : }
1555 :
1556 0 : CSLDestroy(papszTokens);
1557 0 : return pszDefault;
1558 : }
1559 :
1560 : /************************************************************************/
1561 : /* GetKeywordUnit() */
1562 : /************************************************************************/
1563 :
1564 25 : const char *PDSDataset::GetKeywordUnit(const char *pszPath, int iSubscript,
1565 : const char *pszDefault)
1566 :
1567 : {
1568 25 : const char *pszResult = oKeywords.GetKeyword(pszPath, nullptr);
1569 :
1570 25 : if (pszResult == nullptr)
1571 0 : return pszDefault;
1572 :
1573 : char **papszTokens =
1574 25 : CSLTokenizeString2(pszResult, "</>", CSLT_HONOURSTRINGS);
1575 :
1576 25 : if (iSubscript <= CSLCount(papszTokens))
1577 : {
1578 18 : osTempResult = papszTokens[iSubscript - 1];
1579 18 : CSLDestroy(papszTokens);
1580 18 : return osTempResult.c_str();
1581 : }
1582 :
1583 7 : CSLDestroy(papszTokens);
1584 7 : return pszDefault;
1585 : }
1586 :
1587 : /************************************************************************/
1588 : /* CleanString() */
1589 : /* */
1590 : /* Removes single or double quotes, and converts spaces to underscores. */
1591 : /************************************************************************/
1592 :
1593 193 : CPLString PDSDataset::CleanString(const CPLString &osInput)
1594 :
1595 : {
1596 310 : if ((osInput.size() < 2) ||
1597 117 : ((osInput.at(0) != '"' || osInput.back() != '"') &&
1598 55 : (osInput.at(0) != '\'' || osInput.back() != '\'')))
1599 131 : return osInput;
1600 :
1601 62 : char *pszWrk = CPLStrdup(osInput.c_str() + 1);
1602 :
1603 62 : pszWrk[strlen(pszWrk) - 1] = '\0';
1604 :
1605 967 : for (int i = 0; pszWrk[i] != '\0'; i++)
1606 : {
1607 905 : if (pszWrk[i] == ' ')
1608 16 : pszWrk[i] = '_';
1609 : }
1610 :
1611 124 : CPLString osOutput = pszWrk;
1612 62 : CPLFree(pszWrk);
1613 62 : return osOutput;
1614 : }
1615 :
1616 : /************************************************************************/
1617 : /* GetMetadataDomainList() */
1618 : /************************************************************************/
1619 :
1620 0 : char **PDSDataset::GetMetadataDomainList()
1621 : {
1622 0 : return BuildMetadataDomainList(nullptr, FALSE, "", "json:PDS", nullptr);
1623 : }
1624 :
1625 : /************************************************************************/
1626 : /* GetMetadata() */
1627 : /************************************************************************/
1628 :
1629 1 : char **PDSDataset::GetMetadata(const char *pszDomain)
1630 : {
1631 1 : if (pszDomain != nullptr && EQUAL(pszDomain, "json:PDS"))
1632 : {
1633 0 : return m_aosPDSMD.List();
1634 : }
1635 1 : return GDALPamDataset::GetMetadata(pszDomain);
1636 : }
1637 :
1638 : /************************************************************************/
1639 : /* GDALRegister_PDS() */
1640 : /************************************************************************/
1641 :
1642 1595 : void GDALRegister_PDS()
1643 :
1644 : {
1645 1595 : if (GDALGetDriverByName(PDS_DRIVER_NAME) != nullptr)
1646 302 : return;
1647 :
1648 1293 : GDALDriver *poDriver = new GDALDriver();
1649 1293 : PDSDriverSetCommonMetadata(poDriver);
1650 :
1651 1293 : poDriver->pfnOpen = PDSDataset::Open;
1652 :
1653 1293 : GetGDALDriverManager()->RegisterDriver(poDriver);
1654 :
1655 : #ifdef PDS_PLUGIN
1656 : GDALRegister_ISIS3();
1657 : GDALRegister_ISIS2();
1658 : GDALRegister_PDS4();
1659 : GDALRegister_VICAR();
1660 : #endif
1661 : }
|