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