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