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 : #include <optional>
39 :
40 : enum PDSLayout
41 : {
42 : PDS_BSQ,
43 : PDS_BIP,
44 : PDS_BIL
45 : };
46 :
47 : /************************************************************************/
48 : /* ==================================================================== */
49 : /* PDSDataset */
50 : /* ==================================================================== */
51 : /************************************************************************/
52 :
53 : class PDSDataset final : public RawDataset
54 : {
55 : VSILFILE *fpImage{}; // image data file.
56 : GDALDataset *poCompressedDS{};
57 :
58 : NASAKeywordHandler oKeywords{};
59 :
60 : bool bGotTransform{};
61 : GDALGeoTransform m_gt{};
62 :
63 : OGRSpatialReference m_oSRS{};
64 :
65 : CPLString osTempResult{};
66 :
67 : CPLString osExternalCube{};
68 : CPLString m_osImageFilename{};
69 :
70 : CPLStringList m_aosPDSMD{};
71 :
72 : void ParseSRS();
73 : int ParseCompressedImage();
74 : int ParseImage(const CPLString &osPrefix,
75 : const CPLString &osFilenamePrefix);
76 : static CPLString CleanString(const CPLString &osInput);
77 :
78 : const char *GetKeyword(const std::string &osPath,
79 : const char *pszDefault = "");
80 : const char *GetKeywordSub(const std::string &osPath, int iSubscript,
81 : const char *pszDefault = "");
82 : const char *GetKeywordUnit(const char *pszPath, int iSubscript,
83 : const char *pszDefault = "");
84 :
85 : CPL_DISALLOW_COPY_ASSIGN(PDSDataset)
86 :
87 : protected:
88 : int CloseDependentDatasets() override;
89 :
90 : CPLErr Close(GDALProgressFunc = nullptr, void * = nullptr) override;
91 :
92 : public:
93 : PDSDataset();
94 : ~PDSDataset() override;
95 :
96 : CPLErr GetGeoTransform(GDALGeoTransform >) const override;
97 : const OGRSpatialReference *GetSpatialRef() const override;
98 :
99 : char **GetFileList(void) override;
100 :
101 : CPLErr IBuildOverviews(const char *, int, const int *, int, const int *,
102 : GDALProgressFunc, void *,
103 : CSLConstList papszOptions) override;
104 :
105 : CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
106 : GDALDataType, int, BANDMAP_TYPE, GSpacing nPixelSpace,
107 : GSpacing nLineSpace, GSpacing nBandSpace,
108 : GDALRasterIOExtraArg *psExtraArg) override;
109 :
110 : bool GetRawBinaryLayout(GDALDataset::RawBinaryLayout &) override;
111 :
112 : char **GetMetadataDomainList() override;
113 : CSLConstList 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 : CSLConstList papszParamList);
119 : };
120 :
121 : /************************************************************************/
122 : /* PDSDataset() */
123 : /************************************************************************/
124 :
125 39 : PDSDataset::PDSDataset()
126 : {
127 39 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
128 39 : }
129 :
130 : /************************************************************************/
131 : /* ~PDSDataset() */
132 : /************************************************************************/
133 :
134 78 : PDSDataset::~PDSDataset()
135 :
136 : {
137 39 : PDSDataset::Close();
138 78 : }
139 :
140 : /************************************************************************/
141 : /* Close() */
142 : /************************************************************************/
143 :
144 76 : CPLErr PDSDataset::Close(GDALProgressFunc, void *)
145 : {
146 76 : CPLErr eErr = CE_None;
147 76 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
148 : {
149 39 : if (PDSDataset::FlushCache(true) != CE_None)
150 0 : eErr = CE_Failure;
151 39 : if (fpImage)
152 : {
153 33 : if (VSIFCloseL(fpImage) != 0)
154 0 : eErr = CE_Failure;
155 : }
156 :
157 39 : PDSDataset::CloseDependentDatasets();
158 39 : if (GDALPamDataset::Close() != CE_None)
159 0 : eErr = CE_Failure;
160 : }
161 76 : return eErr;
162 : }
163 :
164 : /************************************************************************/
165 : /* CloseDependentDatasets() */
166 : /************************************************************************/
167 :
168 39 : int PDSDataset::CloseDependentDatasets()
169 : {
170 39 : int bHasDroppedRef = GDALPamDataset::CloseDependentDatasets();
171 :
172 39 : if (poCompressedDS)
173 : {
174 4 : bHasDroppedRef = FALSE;
175 4 : delete poCompressedDS;
176 4 : poCompressedDS = nullptr;
177 : }
178 :
179 288 : for (int iBand = 0; iBand < nBands; iBand++)
180 : {
181 249 : delete papoBands[iBand];
182 : }
183 39 : nBands = 0;
184 :
185 39 : 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 37 : void PDSDataset::ParseSRS()
291 :
292 : {
293 37 : const char *pszFilename = GetDescription();
294 :
295 74 : CPLString osPrefix;
296 89 : if (strlen(GetKeyword("IMAGE_MAP_PROJECTION.MAP_PROJECTION_TYPE")) == 0 &&
297 52 : strlen(GetKeyword(
298 15 : "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 37 : double dfXDim = 1.0;
310 37 : double dfYDim = 1.0;
311 :
312 37 : const char *value = GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.MAP_SCALE");
313 37 : 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 37 : CPLAtof(CPLGetConfigOption("PDS_SampleProjOffset_Shift", "0.5"));
358 :
359 : // coverity[tainted_data]
360 : const double dfLineOffset_Shift =
361 37 : CPLAtof(CPLGetConfigOption("PDS_LineProjOffset_Shift", "0.5"));
362 :
363 : // coverity[tainted_data]
364 : const double dfSampleOffset_Mult =
365 37 : CPLAtof(CPLGetConfigOption("PDS_SampleProjOffset_Mult", "-1.0"));
366 :
367 : // coverity[tainted_data]
368 : const double dfLineOffset_Mult =
369 37 : CPLAtof(CPLGetConfigOption("PDS_LineProjOffset_Mult", "1.0"));
370 :
371 : /*********** Grab LINE_PROJECTION_OFFSET ************/
372 37 : double dfULYMap = 0.5;
373 :
374 : value =
375 37 : GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.LINE_PROJECTION_OFFSET");
376 37 : 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 37 : double dfULXMap = 0.5;
385 :
386 : value =
387 37 : GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.SAMPLE_PROJECTION_OFFSET");
388 37 : 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 111 : const CPLString target_name = CleanString(GetKeyword("TARGET_NAME"));
402 :
403 : /********** Grab MAP_PROJECTION_TYPE *****/
404 : const CPLString map_proj_name = CleanString(
405 111 : GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.MAP_PROJECTION_TYPE"));
406 :
407 : /****** Grab semi_major & convert to KM ******/
408 : const double semi_major =
409 37 : CPLAtof(GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.A_AXIS_RADIUS")) *
410 37 : 1000.0;
411 :
412 : /****** Grab semi-minor & convert to KM ******/
413 : const double semi_minor =
414 37 : CPLAtof(GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.C_AXIS_RADIUS")) *
415 37 : 1000.0;
416 :
417 : /*********** Grab CENTER_LAT ************/
418 : const double center_lat =
419 37 : CPLAtof(GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.CENTER_LATITUDE"));
420 :
421 : /*********** Grab CENTER_LON ************/
422 : const double center_lon =
423 37 : CPLAtof(GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.CENTER_LONGITUDE"));
424 :
425 : /********** Grab 1st std parallel *******/
426 37 : const double first_std_parallel = CPLAtof(
427 74 : GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.FIRST_STANDARD_PARALLEL"));
428 :
429 : /********** Grab 2nd std parallel *******/
430 37 : const double second_std_parallel = CPLAtof(
431 74 : 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 37 : char bIsGeographic = TRUE;
438 : value =
439 37 : GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.COORDINATE_SYSTEM_NAME");
440 37 : if (EQUAL(value, "PLANETOCENTRIC"))
441 8 : bIsGeographic = FALSE;
442 :
443 : const double dfLongitudeMulFactor =
444 74 : EQUAL(GetKeyword("IMAGE_MAP_PROJECTION.POSITIVE_LONGITUDE_DIRECTION",
445 : "EAST"),
446 : "EAST")
447 : ? 1
448 37 : : -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 37 : CPLDebug("PDS", "using projection %s\n\n", map_proj_name.c_str());
475 :
476 37 : bool bProjectionSet = true;
477 74 : OGRSpatialReference oSRS;
478 37 : oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
479 :
480 37 : if ((EQUAL(map_proj_name, "EQUIRECTANGULAR")) ||
481 58 : (EQUAL(map_proj_name, "SIMPLE_CYLINDRICAL")) ||
482 21 : (EQUAL(map_proj_name, "EQUIDISTANT")))
483 : {
484 16 : oSRS.SetEquirectangular2(0.0, center_lon, center_lat, 0, 0);
485 : }
486 21 : else if (EQUAL(map_proj_name, "ORTHOGRAPHIC"))
487 : {
488 0 : oSRS.SetOrthographic(center_lat, center_lon, 0, 0);
489 : }
490 21 : else if (EQUAL(map_proj_name, "SINUSOIDAL"))
491 : {
492 4 : oSRS.SetSinusoidal(center_lon, 0, 0);
493 : }
494 17 : 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 16 : 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 16 : else if (EQUAL(map_proj_name, "POLAR_STEREOGRAPHIC"))
516 : {
517 0 : oSRS.SetPS(center_lat, center_lon, 1, 0, 0);
518 : }
519 16 : else if (EQUAL(map_proj_name, "TRANSVERSE_MERCATOR"))
520 : {
521 0 : oSRS.SetTM(center_lat, center_lon, 1, 0, 0);
522 : }
523 16 : 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 16 : else if (EQUAL(map_proj_name, "LAMBERT_AZIMUTHAL_EQUAL_AREA"))
529 : {
530 0 : oSRS.SetLAEA(center_lat, center_lon, 0, 0);
531 : }
532 16 : else if (EQUAL(map_proj_name, "CYLINDRICAL_EQUAL_AREA"))
533 : {
534 0 : oSRS.SetCEA(first_std_parallel, center_lon, 0, 0);
535 : }
536 16 : else if (EQUAL(map_proj_name, "MOLLWEIDE"))
537 : {
538 0 : oSRS.SetMollweide(center_lon, 0, 0);
539 : }
540 16 : 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 16 : else if (EQUAL(map_proj_name, "BONNE"))
546 : {
547 0 : oSRS.SetBonne(first_std_parallel, center_lon, 0, 0);
548 : }
549 16 : else if (EQUAL(map_proj_name, "GNOMONIC"))
550 : {
551 0 : oSRS.SetGnomonic(center_lat, center_lon, 0, 0);
552 : }
553 16 : 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 12 : CPLDebug("PDS", "Dataset projection %s is not supported. Continuing...",
584 : map_proj_name.c_str());
585 12 : bProjectionSet = false;
586 : }
587 :
588 37 : 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 as 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 74 : const CPLString osPath = CPLGetPathSafe(pszFilename);
680 74 : const CPLString osName = CPLGetBasenameSafe(pszFilename);
681 : const std::string osPrjFile =
682 74 : CPLFormCIFilenameSafe(osPath, osName, "prj");
683 :
684 37 : VSILFILE *fp = VSIFOpenL(osPrjFile.c_str(), "r");
685 37 : 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 37 : if (dfULXMap != 0.5 || dfULYMap != 0.5 || dfXDim != 1.0 || dfYDim != 1.0)
697 : {
698 25 : bGotTransform = TRUE;
699 25 : m_gt.xorig = dfULXMap;
700 25 : m_gt.xscale = dfXDim;
701 25 : m_gt.xrot = 0.0;
702 25 : m_gt.yorig = dfULYMap;
703 25 : m_gt.yrot = 0.0;
704 25 : m_gt.yscale = 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.xscale - sin_rot * m_gt.yrot;
715 4 : const double gt_2 = cos_rot * m_gt.xrot - sin_rot * m_gt.yscale;
716 4 : const double gt_0 = cos_rot * m_gt.xorig - sin_rot * m_gt.yorig;
717 4 : const double gt_4 = sin_rot * m_gt.xscale + cos_rot * m_gt.yrot;
718 4 : const double gt_5 = sin_rot * m_gt.xrot + cos_rot * m_gt.yscale;
719 4 : const double gt_3 = sin_rot * m_gt.xorig + cos_rot * m_gt.yorig;
720 4 : m_gt.xscale = gt_1;
721 4 : m_gt.xrot = gt_2;
722 4 : m_gt.xorig = gt_0;
723 4 : m_gt.yrot = gt_4;
724 4 : m_gt.yscale = gt_5;
725 4 : m_gt.yorig = gt_3;
726 : }
727 : }
728 :
729 37 : if (!bGotTransform)
730 12 : bGotTransform = GDALReadWorldFile(pszFilename, "psw", m_gt.data());
731 :
732 37 : if (!bGotTransform)
733 12 : bGotTransform = GDALReadWorldFile(pszFilename, "wld", m_gt.data());
734 37 : }
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 13 : const CPLString osFilename = CleanString(osQube);
835 13 : if (CPLHasPathTraversal(osFilename.c_str()))
836 : {
837 0 : CPLError(CE_Failure, CPLE_NotSupported,
838 : "Path traversal detected in %s", osFilename.c_str());
839 0 : return false;
840 : }
841 13 : if (!osFilenamePrefix.empty())
842 : {
843 3 : m_osImageFilename = osFilenamePrefix + osFilename;
844 : }
845 : else
846 : {
847 20 : CPLString osTPath = CPLGetPathSafe(GetDescription());
848 : m_osImageFilename =
849 10 : CPLFormCIFilenameSafe(osTPath, osFilename, nullptr);
850 10 : osExternalCube = m_osImageFilename;
851 : }
852 : }
853 :
854 : /* -------------------------------------------------------------------- */
855 : /* Checks to see if this is raw PDS image not compressed image */
856 : /* so ENCODING_TYPE either does not exist or it equals "N/A". */
857 : /* or "DCT_DECOMPRESSED". */
858 : /* Compressed types will not be supported in this routine */
859 : /* -------------------------------------------------------------------- */
860 :
861 : const CPLString osEncodingType =
862 99 : CleanString(GetKeyword(osPrefix + "IMAGE.ENCODING_TYPE", "N/A"));
863 33 : if (!EQUAL(osEncodingType, "N/A") &&
864 0 : !EQUAL(osEncodingType, "DCT_DECOMPRESSED"))
865 : {
866 0 : CPLError(CE_Failure, CPLE_OpenFailed,
867 : "*** PDS image file has an ENCODING_TYPE parameter:\n"
868 : "*** GDAL PDS driver does not support compressed image types\n"
869 : "found: (%s)\n\n",
870 : osEncodingType.c_str());
871 0 : return FALSE;
872 : }
873 : /**************** end ENCODING_TYPE check ***********************/
874 :
875 : /*********** Grab layout type (BSQ, BIP, BIL) ************/
876 : // AXIS_NAME = (SAMPLE,LINE,BAND)
877 : /*********** Grab samples lines band **************/
878 : /** if AXIS_NAME = "" then Bands=1 and Sample and Lines **/
879 : /** are there own keywords "LINES" and "LINE_SAMPLES" **/
880 : /** if not NULL then CORE_ITEMS keyword i.e. (234,322,2) **/
881 : /***********************************************************/
882 33 : int eLayout = PDS_BSQ; // default to band seq.
883 33 : int nRows, nCols, l_nBands = 1;
884 :
885 99 : CPLString value = GetKeyword(osPrefix + osImageKeyword + ".AXIS_NAME", "");
886 33 : if (EQUAL(value, "(SAMPLE,LINE,BAND)"))
887 : {
888 0 : eLayout = PDS_BSQ;
889 : nCols =
890 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 1));
891 : nRows =
892 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 2));
893 : l_nBands =
894 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 3));
895 : }
896 33 : else if (EQUAL(value, "(BAND,LINE,SAMPLE)"))
897 : {
898 0 : eLayout = PDS_BIP;
899 : l_nBands =
900 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 1));
901 : nRows =
902 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 2));
903 : nCols =
904 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 3));
905 : }
906 33 : else if (EQUAL(value, "(SAMPLE,BAND,LINE)"))
907 : {
908 0 : eLayout = PDS_BIL;
909 : nCols =
910 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 1));
911 : l_nBands =
912 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 2));
913 : nRows =
914 0 : atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 3));
915 : }
916 33 : else if (EQUAL(value, ""))
917 : {
918 33 : eLayout = PDS_BSQ;
919 : nCols =
920 33 : atoi(GetKeyword(osPrefix + osImageKeyword + ".LINE_SAMPLES", ""));
921 33 : nRows = atoi(GetKeyword(osPrefix + osImageKeyword + ".LINES", ""));
922 33 : l_nBands = atoi(GetKeyword(osPrefix + osImageKeyword + ".BANDS", "1"));
923 : }
924 : else
925 : {
926 0 : CPLError(CE_Failure, CPLE_OpenFailed,
927 : "%s layout not supported. Abort\n\n", value.c_str());
928 0 : return FALSE;
929 : }
930 :
931 : CPLString osBAND_STORAGE_TYPE =
932 66 : GetKeyword(osPrefix + "IMAGE.BAND_STORAGE_TYPE", "");
933 33 : if (EQUAL(osBAND_STORAGE_TYPE, "BAND_SEQUENTIAL"))
934 : {
935 12 : eLayout = PDS_BSQ;
936 : }
937 21 : else if (EQUAL(osBAND_STORAGE_TYPE, "PIXEL_INTERLEAVED"))
938 : {
939 0 : eLayout = PDS_BIP;
940 : }
941 21 : else if (EQUAL(osBAND_STORAGE_TYPE, "LINE_INTERLEAVED"))
942 : {
943 2 : eLayout = PDS_BIL;
944 : }
945 19 : else if (!osBAND_STORAGE_TYPE.empty())
946 : {
947 4 : CPLDebug("PDS", "Unhandled BAND_STORAGE_TYPE = %s",
948 : osBAND_STORAGE_TYPE.c_str());
949 : }
950 :
951 : /*********** Grab Qube record bytes **********/
952 33 : int record_bytes = atoi(GetKeyword(osPrefix + "IMAGE.RECORD_BYTES"));
953 33 : if (record_bytes == 0)
954 33 : record_bytes = atoi(GetKeyword(osPrefix + "RECORD_BYTES"));
955 :
956 : // this can happen with "record_type = undefined".
957 33 : if (record_bytes < 0)
958 0 : return FALSE;
959 33 : if (record_bytes == 0)
960 3 : record_bytes = 1;
961 :
962 33 : int nSkipBytes = 0;
963 : try
964 : {
965 33 : if (nQube > 0)
966 : {
967 20 : if (osQube.find("<BYTES>") != CPLString::npos)
968 3 : nSkipBytes = (CPLSM(nQube) - CPLSM(1)).v();
969 : else
970 17 : nSkipBytes = (CPLSM(nQube - 1) * CPLSM(record_bytes)).v();
971 : }
972 13 : else if (nDetachedOffset > 0)
973 : {
974 4 : if (bDetachedOffsetInBytes)
975 2 : nSkipBytes = nDetachedOffset;
976 : else
977 : {
978 2 : nSkipBytes = (CPLSM(nDetachedOffset) * CPLSM(record_bytes)).v();
979 : }
980 : }
981 : else
982 9 : nSkipBytes = 0;
983 : }
984 0 : catch (const CPLSafeIntOverflow &)
985 : {
986 0 : return FALSE;
987 : }
988 :
989 : const int nLinePrefixBytes =
990 33 : atoi(GetKeyword(osPrefix + "IMAGE.LINE_PREFIX_BYTES", ""));
991 33 : if (nLinePrefixBytes < 0)
992 0 : return false;
993 33 : nSkipBytes += nLinePrefixBytes;
994 :
995 : /*********** Grab SAMPLE_TYPE *****************/
996 : /** if keyword not found leave as "M" or "MSB" **/
997 :
998 66 : CPLString osST = GetKeyword(osPrefix + "IMAGE.SAMPLE_TYPE");
999 33 : if (osST.size() >= 2 && osST[0] == '"' && osST.back() == '"')
1000 4 : osST = osST.substr(1, osST.size() - 2);
1001 :
1002 33 : char chByteOrder = 'M'; // default to MSB
1003 64 : if ((EQUAL(osST, "LSB_INTEGER")) || (EQUAL(osST, "LSB")) || // just in case
1004 31 : (EQUAL(osST, "LSB_UNSIGNED_INTEGER")) ||
1005 27 : (EQUAL(osST, "LSB_SIGNED_INTEGER")) ||
1006 27 : (EQUAL(osST, "UNSIGNED_INTEGER")) || (EQUAL(osST, "VAX_REAL")) ||
1007 12 : (EQUAL(osST, "VAX_INTEGER")) ||
1008 76 : (EQUAL(osST, "PC_INTEGER")) || // just in case
1009 12 : (EQUAL(osST, "PC_REAL")))
1010 : {
1011 26 : chByteOrder = 'I';
1012 : }
1013 :
1014 : /**** Grab format type - pds supports 1,2,4,8,16,32,64 (in theory) **/
1015 : /**** I have only seen 8, 16, 32 (float) in released datasets **/
1016 33 : GDALDataType eDataType = GDT_UInt8;
1017 33 : int nSuffixItems = 0;
1018 33 : int nSuffixLines = 0;
1019 33 : int nSuffixBytes = 4; // Default as per PDS specification
1020 33 : double dfNoData = 0.0;
1021 33 : double dfScale = 1.0;
1022 33 : double dfOffset = 0.0;
1023 33 : const char *pszUnit = nullptr;
1024 33 : const char *pszDesc = nullptr;
1025 :
1026 66 : CPLString osSB = GetKeyword(osPrefix + "IMAGE.SAMPLE_BITS", "");
1027 33 : if (!osSB.empty())
1028 : {
1029 33 : const int itype = atoi(osSB);
1030 33 : switch (itype)
1031 : {
1032 23 : case 8:
1033 23 : eDataType = GDT_UInt8;
1034 23 : dfNoData = PDS_NULL1;
1035 23 : break;
1036 5 : case 16:
1037 5 : if (strstr(osST, "UNSIGNED") != nullptr)
1038 : {
1039 3 : dfNoData = PDS_NULL1;
1040 3 : eDataType = GDT_UInt16;
1041 : }
1042 : else
1043 : {
1044 2 : eDataType = GDT_Int16;
1045 2 : dfNoData = PDS_NULL2;
1046 : }
1047 5 : break;
1048 5 : case 32:
1049 5 : eDataType = GDT_Float32;
1050 5 : dfNoData = PDS_NULL3;
1051 5 : break;
1052 0 : case 64:
1053 0 : eDataType = GDT_Float64;
1054 0 : dfNoData = PDS_NULL3;
1055 0 : break;
1056 0 : default:
1057 0 : CPLError(CE_Failure, CPLE_AppDefined,
1058 : "Sample_bits of %d is not supported in this gdal PDS "
1059 : "reader.",
1060 : itype);
1061 0 : return FALSE;
1062 : }
1063 :
1064 33 : dfOffset = CPLAtofM(GetKeyword(osPrefix + "IMAGE.OFFSET", "0.0"));
1065 : dfScale =
1066 33 : CPLAtofM(GetKeyword(osPrefix + "IMAGE.SCALING_FACTOR", "1.0"));
1067 : }
1068 : else /* No IMAGE object, search for the QUBE. */
1069 : {
1070 0 : osSB = GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_ITEM_BYTES", "");
1071 0 : const int itype = atoi(osSB);
1072 0 : switch (itype)
1073 : {
1074 0 : case 1:
1075 0 : eDataType = GDT_UInt8;
1076 0 : break;
1077 0 : case 2:
1078 0 : if (strstr(osST, "UNSIGNED") != nullptr)
1079 0 : eDataType = GDT_UInt16;
1080 : else
1081 0 : eDataType = GDT_Int16;
1082 0 : break;
1083 0 : case 4:
1084 0 : eDataType = GDT_Float32;
1085 0 : break;
1086 0 : default:
1087 0 : CPLError(CE_Failure, CPLE_AppDefined,
1088 : "CORE_ITEM_BYTES of %d is not supported in this gdal "
1089 : "PDS reader.",
1090 : itype);
1091 0 : return FALSE;
1092 : }
1093 :
1094 : /* Parse suffix dimensions if defined. */
1095 0 : value = GetKeyword(osPrefix + "SPECTRAL_QUBE.SUFFIX_ITEMS", "");
1096 0 : if (!value.empty())
1097 : {
1098 0 : value = GetKeyword(osPrefix + "SPECTRAL_QUBE.SUFFIX_BYTES", "");
1099 0 : if (!value.empty())
1100 0 : nSuffixBytes = atoi(value);
1101 :
1102 : nSuffixItems =
1103 0 : atoi(GetKeywordSub(osPrefix + "SPECTRAL_QUBE.SUFFIX_ITEMS", 1));
1104 : nSuffixLines =
1105 0 : atoi(GetKeywordSub(osPrefix + "SPECTRAL_QUBE.SUFFIX_ITEMS", 2));
1106 : }
1107 :
1108 0 : value = GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_NULL", "");
1109 0 : if (!value.empty())
1110 0 : dfNoData = CPLAtofM(value);
1111 :
1112 : dfOffset =
1113 0 : CPLAtofM(GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_BASE", "0.0"));
1114 0 : dfScale = CPLAtofM(
1115 0 : GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_MULTIPLIER", "1.0"));
1116 0 : pszUnit = GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_UNIT", nullptr);
1117 0 : pszDesc = GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_NAME", nullptr);
1118 : }
1119 :
1120 : /* -------------------------------------------------------------------- */
1121 : /* Is there a specific nodata value in the file? Either the */
1122 : /* MISSING or MISSING_CONSTANT keywords are nodata. */
1123 : /* -------------------------------------------------------------------- */
1124 :
1125 33 : const char *pszMissing = GetKeyword(osPrefix + "IMAGE.MISSING", nullptr);
1126 33 : if (pszMissing == nullptr)
1127 30 : pszMissing = GetKeyword(osPrefix + "IMAGE.MISSING_CONSTANT", nullptr);
1128 :
1129 33 : if (pszMissing != nullptr)
1130 : {
1131 9 : if (*pszMissing == '"')
1132 3 : pszMissing++;
1133 :
1134 : /* For example : MISSING_CONSTANT = "16#FF7FFFFB#" */
1135 9 : if (STARTS_WITH_CI(pszMissing, "16#") &&
1136 3 : strlen(pszMissing) >= 3 + 8 + 1 && pszMissing[3 + 8] == '#' &&
1137 0 : (eDataType == GDT_Float32 || eDataType == GDT_Float64))
1138 : {
1139 3 : GUInt32 nVal = PDSConvertFromHex(pszMissing);
1140 : float fVal;
1141 3 : memcpy(&fVal, &nVal, 4);
1142 3 : dfNoData = fVal;
1143 : }
1144 : else
1145 6 : dfNoData = CPLAtofM(pszMissing);
1146 : }
1147 :
1148 : /* -------------------------------------------------------------------- */
1149 : /* Did we get the required keywords? If not we return with */
1150 : /* this never having been considered to be a match. This isn't */
1151 : /* an error! */
1152 : /* -------------------------------------------------------------------- */
1153 66 : if (!GDALCheckDatasetDimensions(nCols, nRows) ||
1154 33 : !GDALCheckBandCount(l_nBands, false))
1155 : {
1156 0 : return FALSE;
1157 : }
1158 :
1159 : /* -------------------------------------------------------------------- */
1160 : /* Capture some information from the file that is of interest. */
1161 : /* -------------------------------------------------------------------- */
1162 33 : nRasterXSize = nCols;
1163 33 : nRasterYSize = nRows;
1164 :
1165 : /* -------------------------------------------------------------------- */
1166 : /* Open target binary file. */
1167 : /* -------------------------------------------------------------------- */
1168 :
1169 33 : if (eAccess == GA_ReadOnly)
1170 : {
1171 33 : fpImage = VSIFOpenL(m_osImageFilename, "rb");
1172 33 : if (fpImage == nullptr)
1173 : {
1174 0 : CPLError(CE_Failure, CPLE_OpenFailed, "Failed to open %s.\n%s",
1175 0 : m_osImageFilename.c_str(), VSIStrerror(errno));
1176 0 : return FALSE;
1177 : }
1178 : }
1179 : else
1180 : {
1181 0 : fpImage = VSIFOpenL(m_osImageFilename, "r+b");
1182 0 : if (fpImage == nullptr)
1183 : {
1184 0 : CPLError(CE_Failure, CPLE_OpenFailed,
1185 : "Failed to open %s with write permission.\n%s",
1186 0 : m_osImageFilename.c_str(), VSIStrerror(errno));
1187 0 : return FALSE;
1188 : }
1189 : }
1190 :
1191 : /* -------------------------------------------------------------------- */
1192 : /* Compute the line offset. */
1193 : /* -------------------------------------------------------------------- */
1194 33 : const int nItemSize = GDALGetDataTypeSizeBytes(eDataType);
1195 :
1196 : // Needed for N1349177584_2.LBL from
1197 : // https://trac.osgeo.org/gdal/attachment/ticket/3355/PDS-TestFiles.zip
1198 33 : int nLineOffset = nLinePrefixBytes;
1199 :
1200 : int nPixelOffset;
1201 : vsi_l_offset nBandOffset;
1202 :
1203 186 : const auto CPLSM64 = [](int x) { return CPLSM(static_cast<int64_t>(x)); };
1204 :
1205 : try
1206 : {
1207 33 : if (eLayout == PDS_BIP)
1208 : {
1209 0 : nPixelOffset = (CPLSM(nItemSize) * CPLSM(l_nBands)).v();
1210 0 : nBandOffset = nItemSize;
1211 : nLineOffset =
1212 0 : (CPLSM(nLineOffset) + CPLSM(nPixelOffset) * CPLSM(nCols)).v();
1213 : }
1214 33 : else if (eLayout == PDS_BSQ)
1215 : {
1216 31 : nPixelOffset = nItemSize;
1217 : nLineOffset =
1218 31 : (CPLSM(nLineOffset) + CPLSM(nPixelOffset) * CPLSM(nCols)).v();
1219 31 : nBandOffset = static_cast<vsi_l_offset>(
1220 31 : (CPLSM64(nLineOffset) * CPLSM64(nRows) +
1221 31 : CPLSM64(nSuffixLines) *
1222 62 : (CPLSM64(nCols) + CPLSM64(nSuffixItems)) *
1223 124 : CPLSM64(nSuffixBytes))
1224 31 : .v());
1225 : }
1226 : else /* assume BIL */
1227 : {
1228 2 : nPixelOffset = nItemSize;
1229 2 : nBandOffset = (CPLSM(nItemSize) * CPLSM(nCols)).v();
1230 : nLineOffset =
1231 2 : (CPLSM(nLineOffset) +
1232 6 : CPLSM(static_cast<int>(nBandOffset)) * CPLSM(l_nBands))
1233 2 : .v();
1234 : }
1235 : }
1236 0 : catch (const CPLSafeIntOverflow &)
1237 : {
1238 0 : CPLError(CE_Failure, CPLE_AppDefined, "Integer overflow");
1239 0 : return FALSE;
1240 : }
1241 :
1242 : /* -------------------------------------------------------------------- */
1243 : /* Create band information objects. */
1244 : /* -------------------------------------------------------------------- */
1245 278 : for (int i = 0; i < l_nBands; i++)
1246 : {
1247 : auto poBand = RawRasterBand::Create(
1248 : this, i + 1, fpImage,
1249 245 : nSkipBytes + static_cast<vsi_l_offset>(nBandOffset) * i,
1250 : nPixelOffset, nLineOffset, eDataType,
1251 : chByteOrder == 'I' || chByteOrder == 'L'
1252 245 : ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
1253 : : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN,
1254 245 : RawRasterBand::OwnFP::NO);
1255 245 : if (!poBand)
1256 0 : return FALSE;
1257 :
1258 245 : if (l_nBands == 1)
1259 : {
1260 : const char *pszMin =
1261 31 : GetKeyword(osPrefix + "IMAGE.MINIMUM", nullptr);
1262 : const char *pszMax =
1263 31 : GetKeyword(osPrefix + "IMAGE.MAXIMUM", nullptr);
1264 31 : const char *pszMean = GetKeyword(osPrefix + "IMAGE.MEAN", nullptr);
1265 : const char *pszStdDev =
1266 31 : GetKeyword(osPrefix + "IMAGE.STANDARD_DEVIATION", nullptr);
1267 31 : if (pszMin != nullptr && pszMax != nullptr && pszMean != nullptr &&
1268 : pszStdDev != nullptr)
1269 : {
1270 0 : poBand->SetStatistics(CPLAtofM(pszMin), CPLAtofM(pszMax),
1271 0 : CPLAtofM(pszMean), CPLAtofM(pszStdDev));
1272 : }
1273 : }
1274 :
1275 245 : poBand->SetNoDataValue(dfNoData);
1276 :
1277 : // Set offset/scale values at the PAM level.
1278 245 : poBand->SetOffset(dfOffset);
1279 245 : poBand->SetScale(dfScale);
1280 245 : if (pszUnit)
1281 0 : poBand->SetUnitType(pszUnit);
1282 245 : if (pszDesc)
1283 0 : poBand->SetDescription(pszDesc);
1284 :
1285 245 : SetBand(i + 1, std::move(poBand));
1286 : }
1287 :
1288 33 : return TRUE;
1289 : }
1290 :
1291 : /************************************************************************/
1292 : /* ==================================================================== */
1293 : /* PDSWrapperRasterBand */
1294 : /* */
1295 : /* proxy for the jp2 or other compressed bands. */
1296 : /* ==================================================================== */
1297 : /************************************************************************/
1298 : class PDSWrapperRasterBand final : public GDALProxyRasterBand
1299 : {
1300 : GDALRasterBand *poBaseBand{};
1301 : double m_dfOffset = 0.0;
1302 : double m_dfScale = 1.0;
1303 : std::optional<double> m_dfNoData{};
1304 :
1305 : CPL_DISALLOW_COPY_ASSIGN(PDSWrapperRasterBand)
1306 :
1307 : protected:
1308 : virtual GDALRasterBand *
1309 : RefUnderlyingRasterBand(bool /*bForceOpen*/) const override;
1310 :
1311 : public:
1312 4 : PDSWrapperRasterBand(GDALRasterBand *poBaseBandIn, double dfOffset,
1313 : double dfScale, std::optional<double> dfNoData)
1314 4 : : m_dfOffset(dfOffset), m_dfScale(dfScale), m_dfNoData(dfNoData)
1315 : {
1316 4 : this->poBaseBand = poBaseBandIn;
1317 4 : eDataType = poBaseBand->GetRasterDataType();
1318 4 : poBaseBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
1319 4 : }
1320 :
1321 1 : double GetScale(int *pbHasVal) override
1322 : {
1323 1 : if (pbHasVal)
1324 1 : *pbHasVal = m_dfScale != 1.0;
1325 1 : return m_dfScale;
1326 : }
1327 :
1328 1 : double GetOffset(int *pbHasVal) override
1329 : {
1330 1 : if (pbHasVal)
1331 1 : *pbHasVal = m_dfOffset != 1.0;
1332 1 : return m_dfOffset;
1333 : }
1334 :
1335 1 : double GetNoDataValue(int *pbHasVal) override
1336 : {
1337 1 : if (pbHasVal)
1338 1 : *pbHasVal = m_dfNoData.has_value();
1339 1 : return m_dfNoData.has_value() ? m_dfNoData.value() : 0.0;
1340 : }
1341 : };
1342 :
1343 : GDALRasterBand *
1344 5 : PDSWrapperRasterBand::RefUnderlyingRasterBand(bool /*bForceOpen*/) const
1345 : {
1346 5 : return poBaseBand;
1347 : }
1348 :
1349 : /************************************************************************/
1350 : /* ParseCompressedImage() */
1351 : /************************************************************************/
1352 :
1353 4 : int PDSDataset::ParseCompressedImage()
1354 :
1355 : {
1356 : const CPLString osFileName =
1357 12 : CleanString(GetKeyword("COMPRESSED_FILE.FILE_NAME", ""));
1358 4 : if (CPLHasPathTraversal(osFileName.c_str()))
1359 : {
1360 0 : CPLError(CE_Failure, CPLE_NotSupported, "Path traversal detected in %s",
1361 : osFileName.c_str());
1362 0 : return false;
1363 : }
1364 :
1365 4 : double dfOffset = 0;
1366 4 : double dfScale = 1;
1367 4 : std::optional<double> dfNoData;
1368 : const std::string osUncompressedFilename =
1369 12 : GetKeyword("COMPRESSED_FILE.UNCOMPRESSED_FILE_NAME", "");
1370 8 : if (!osUncompressedFilename.empty() &&
1371 8 : GetKeyword("UNCOMPRESSED_FILE.FILE_NAME", "") == osUncompressedFilename)
1372 :
1373 : {
1374 4 : dfOffset = CPLAtof(GetKeyword("UNCOMPRESSED_FILE.IMAGE.OFFSET", "0.0"));
1375 4 : dfScale = CPLAtof(
1376 : GetKeyword("UNCOMPRESSED_FILE.IMAGE.SCALING_FACTOR", "1.0"));
1377 : const char *pszNull =
1378 4 : GetKeyword("UNCOMPRESSED_FILE.IMAGE.CORE_NULL", nullptr);
1379 4 : if (pszNull)
1380 4 : dfNoData = CPLAtof(pszNull);
1381 : }
1382 :
1383 8 : const CPLString osPath = CPLGetPathSafe(GetDescription());
1384 : const CPLString osFullFileName =
1385 8 : CPLFormFilenameSafe(osPath, osFileName, nullptr);
1386 :
1387 4 : poCompressedDS =
1388 4 : GDALDataset::FromHandle(GDALOpen(osFullFileName, GA_ReadOnly));
1389 :
1390 4 : if (poCompressedDS == nullptr)
1391 0 : return FALSE;
1392 :
1393 4 : nRasterXSize = poCompressedDS->GetRasterXSize();
1394 4 : nRasterYSize = poCompressedDS->GetRasterYSize();
1395 :
1396 8 : for (int iBand = 0; iBand < poCompressedDS->GetRasterCount(); iBand++)
1397 : {
1398 4 : SetBand(iBand + 1, new PDSWrapperRasterBand(
1399 4 : poCompressedDS->GetRasterBand(iBand + 1),
1400 4 : dfOffset, dfScale, dfNoData));
1401 : }
1402 :
1403 4 : return TRUE;
1404 : }
1405 :
1406 : /************************************************************************/
1407 : /* Open() */
1408 : /************************************************************************/
1409 :
1410 39 : GDALDataset *PDSDataset::Open(GDALOpenInfo *poOpenInfo)
1411 : {
1412 39 : if (!PDSDriverIdentify(poOpenInfo))
1413 0 : return nullptr;
1414 :
1415 39 : const char *pszHdr = reinterpret_cast<char *>(poOpenInfo->pabyHeader);
1416 39 : if (strstr(pszHdr, "PDS_VERSION_ID") != nullptr &&
1417 39 : strstr(pszHdr, "PDS3") == nullptr)
1418 : {
1419 0 : CPLError(
1420 : CE_Failure, CPLE_OpenFailed,
1421 : "It appears this is an older PDS image type. Only PDS_VERSION_ID "
1422 : "= PDS3 are currently supported by this gdal PDS reader.");
1423 0 : return nullptr;
1424 : }
1425 :
1426 : /* -------------------------------------------------------------------- */
1427 : /* Parse the keyword header. Sometimes there is stuff */
1428 : /* before the PDS_VERSION_ID, which we want to ignore. */
1429 : /* -------------------------------------------------------------------- */
1430 39 : VSILFILE *fpQube = poOpenInfo->fpL;
1431 39 : poOpenInfo->fpL = nullptr;
1432 :
1433 39 : PDSDataset *poDS = new PDSDataset();
1434 39 : poDS->SetDescription(poOpenInfo->pszFilename);
1435 39 : poDS->eAccess = poOpenInfo->eAccess;
1436 :
1437 39 : const char *pszPDSVersionID = strstr(pszHdr, "PDS_VERSION_ID");
1438 39 : int nOffset = 0;
1439 39 : if (pszPDSVersionID)
1440 39 : nOffset = static_cast<int>(pszPDSVersionID - pszHdr);
1441 :
1442 39 : if (!poDS->oKeywords.Ingest(fpQube, nOffset))
1443 : {
1444 2 : delete poDS;
1445 2 : VSIFCloseL(fpQube);
1446 2 : return nullptr;
1447 : }
1448 : poDS->m_aosPDSMD.InsertString(
1449 0 : 0, poDS->oKeywords.GetJsonObject()
1450 74 : .Format(CPLJSONObject::PrettyFormat::Pretty)
1451 74 : .c_str());
1452 37 : VSIFCloseL(fpQube);
1453 :
1454 : /* -------------------------------------------------------------------- */
1455 : /* Is this a compressed image with COMPRESSED_FILE subdomain? */
1456 : /* */
1457 : /* The corresponding parse operations will read keywords, */
1458 : /* establish bands and raster size. */
1459 : /* -------------------------------------------------------------------- */
1460 : CPLString osEncodingType =
1461 111 : poDS->GetKeyword("COMPRESSED_FILE.ENCODING_TYPE", "");
1462 :
1463 : CPLString osCompressedFilename =
1464 111 : CleanString(poDS->GetKeyword("COMPRESSED_FILE.FILE_NAME", ""));
1465 :
1466 : const char *pszImageName =
1467 37 : poDS->GetKeyword("UNCOMPRESSED_FILE.IMAGE.NAME", "");
1468 : CPLString osUncompressedFilename =
1469 37 : CleanString(!EQUAL(pszImageName, "")
1470 : ? pszImageName
1471 146 : : poDS->GetKeyword("UNCOMPRESSED_FILE.FILE_NAME", ""));
1472 :
1473 : VSIStatBufL sStat;
1474 74 : CPLString osFilenamePrefix;
1475 :
1476 40 : if (EQUAL(osEncodingType, "ZIP") && !osCompressedFilename.empty() &&
1477 3 : !osUncompressedFilename.empty())
1478 : {
1479 3 : const CPLString osPath = CPLGetPathSafe(poDS->GetDescription());
1480 : osCompressedFilename =
1481 3 : CPLFormFilenameSafe(osPath, osCompressedFilename, nullptr);
1482 : osUncompressedFilename =
1483 3 : CPLFormFilenameSafe(osPath, osUncompressedFilename, nullptr);
1484 3 : if (VSIStatExL(osCompressedFilename, &sStat, VSI_STAT_EXISTS_FLAG) ==
1485 6 : 0 &&
1486 3 : VSIStatExL(osUncompressedFilename, &sStat, VSI_STAT_EXISTS_FLAG) !=
1487 : 0)
1488 : {
1489 3 : osFilenamePrefix = "/vsizip/" + osCompressedFilename + "/";
1490 3 : poDS->osExternalCube = std::move(osCompressedFilename);
1491 : }
1492 3 : osEncodingType = "";
1493 : }
1494 :
1495 37 : if (!osEncodingType.empty())
1496 : {
1497 4 : if (!poDS->ParseCompressedImage())
1498 : {
1499 0 : delete poDS;
1500 0 : return nullptr;
1501 : }
1502 : }
1503 : else
1504 : {
1505 33 : CPLString osPrefix;
1506 :
1507 33 : if (osUncompressedFilename != "")
1508 5 : osPrefix = "UNCOMPRESSED_FILE.";
1509 :
1510 : // Added ability to see into OBJECT = FILE section to support
1511 : // CRISM. Example file: hsp00017ba0_01_ra218s_trr3.lbl and *.img
1512 73 : if (strlen(poDS->GetKeyword("IMAGE.LINE_SAMPLES")) == 0 &&
1513 40 : strlen(poDS->GetKeyword("FILE.IMAGE.LINE_SAMPLES")) != 0)
1514 2 : osPrefix = "FILE.";
1515 :
1516 33 : if (!poDS->ParseImage(osPrefix, osFilenamePrefix))
1517 : {
1518 0 : delete poDS;
1519 0 : return nullptr;
1520 : }
1521 : }
1522 :
1523 : /* -------------------------------------------------------------------- */
1524 : /* Set the coordinate system and geotransform. */
1525 : /* -------------------------------------------------------------------- */
1526 37 : poDS->ParseSRS();
1527 :
1528 : /* -------------------------------------------------------------------- */
1529 : /* Transfer a few interesting keywords as metadata. */
1530 : /* -------------------------------------------------------------------- */
1531 : static const char *const apszKeywords[] = {"FILTER_NAME",
1532 : "DATA_SET_ID",
1533 : "PRODUCT_ID",
1534 : "PRODUCER_INSTITUTION_NAME",
1535 : "PRODUCT_TYPE",
1536 : "MISSION_NAME",
1537 : "SPACECRAFT_NAME",
1538 : "INSTRUMENT_NAME",
1539 : "INSTRUMENT_ID",
1540 : "TARGET_NAME",
1541 : "CENTER_FILTER_WAVELENGTH",
1542 : "BANDWIDTH",
1543 : "PRODUCT_CREATION_TIME",
1544 : "START_TIME",
1545 : "STOP_TIME",
1546 : "NOTE",
1547 : nullptr};
1548 :
1549 629 : for (int i = 0; apszKeywords[i] != nullptr; i++)
1550 : {
1551 592 : const char *pszKeywordValue = poDS->GetKeyword(apszKeywords[i]);
1552 :
1553 592 : if (pszKeywordValue != nullptr)
1554 592 : poDS->SetMetadataItem(apszKeywords[i], pszKeywordValue);
1555 : }
1556 :
1557 : /* -------------------------------------------------------------------- */
1558 : /* Initialize any PAM information. */
1559 : /* -------------------------------------------------------------------- */
1560 37 : poDS->TryLoadXML();
1561 :
1562 : /* -------------------------------------------------------------------- */
1563 : /* Check for overviews. */
1564 : /* -------------------------------------------------------------------- */
1565 37 : poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
1566 :
1567 37 : return poDS;
1568 : }
1569 :
1570 : /************************************************************************/
1571 : /* GetKeyword() */
1572 : /************************************************************************/
1573 :
1574 2021 : const char *PDSDataset::GetKeyword(const std::string &osPath,
1575 : const char *pszDefault)
1576 :
1577 : {
1578 2021 : return oKeywords.GetKeyword(osPath.c_str(), pszDefault);
1579 : }
1580 :
1581 : /************************************************************************/
1582 : /* GetKeywordSub() */
1583 : /************************************************************************/
1584 :
1585 27 : const char *PDSDataset::GetKeywordSub(const std::string &osPath, int iSubscript,
1586 : const char *pszDefault)
1587 :
1588 : {
1589 27 : const char *pszResult = oKeywords.GetKeyword(osPath.c_str(), nullptr);
1590 :
1591 27 : if (pszResult == nullptr)
1592 0 : return pszDefault;
1593 :
1594 27 : if (pszResult[0] != '(')
1595 0 : return pszDefault;
1596 :
1597 : char **papszTokens =
1598 27 : CSLTokenizeString2(pszResult, "(,)", CSLT_HONOURSTRINGS);
1599 :
1600 27 : if (iSubscript <= CSLCount(papszTokens))
1601 : {
1602 27 : osTempResult = papszTokens[iSubscript - 1];
1603 27 : CSLDestroy(papszTokens);
1604 27 : return osTempResult.c_str();
1605 : }
1606 :
1607 0 : CSLDestroy(papszTokens);
1608 0 : return pszDefault;
1609 : }
1610 :
1611 : /************************************************************************/
1612 : /* GetKeywordUnit() */
1613 : /************************************************************************/
1614 :
1615 25 : const char *PDSDataset::GetKeywordUnit(const char *pszPath, int iSubscript,
1616 : const char *pszDefault)
1617 :
1618 : {
1619 25 : const char *pszResult = oKeywords.GetKeyword(pszPath, nullptr);
1620 :
1621 25 : if (pszResult == nullptr)
1622 0 : return pszDefault;
1623 :
1624 : char **papszTokens =
1625 25 : CSLTokenizeString2(pszResult, "</>", CSLT_HONOURSTRINGS);
1626 :
1627 25 : if (iSubscript <= CSLCount(papszTokens))
1628 : {
1629 18 : osTempResult = papszTokens[iSubscript - 1];
1630 18 : CSLDestroy(papszTokens);
1631 18 : return osTempResult.c_str();
1632 : }
1633 :
1634 7 : CSLDestroy(papszTokens);
1635 7 : return pszDefault;
1636 : }
1637 :
1638 : /************************************************************************/
1639 : /* CleanString() */
1640 : /* */
1641 : /* Removes single or double quotes, and converts spaces to underscores. */
1642 : /************************************************************************/
1643 :
1644 198 : CPLString PDSDataset::CleanString(const CPLString &osInput)
1645 :
1646 : {
1647 318 : if ((osInput.size() < 2) ||
1648 120 : ((osInput.at(0) != '"' || osInput.back() != '"') &&
1649 55 : (osInput.at(0) != '\'' || osInput.back() != '\'')))
1650 133 : return osInput;
1651 :
1652 65 : char *pszWrk = CPLStrdup(osInput.c_str() + 1);
1653 :
1654 65 : pszWrk[strlen(pszWrk) - 1] = '\0';
1655 :
1656 1015 : for (int i = 0; pszWrk[i] != '\0'; i++)
1657 : {
1658 950 : if (pszWrk[i] == ' ')
1659 16 : pszWrk[i] = '_';
1660 : }
1661 :
1662 130 : CPLString osOutput(pszWrk);
1663 65 : CPLFree(pszWrk);
1664 65 : return osOutput;
1665 : }
1666 :
1667 : /************************************************************************/
1668 : /* GetMetadataDomainList() */
1669 : /************************************************************************/
1670 :
1671 0 : char **PDSDataset::GetMetadataDomainList()
1672 : {
1673 0 : return BuildMetadataDomainList(nullptr, FALSE, "", "json:PDS", nullptr);
1674 : }
1675 :
1676 : /************************************************************************/
1677 : /* GetMetadata() */
1678 : /************************************************************************/
1679 :
1680 1 : CSLConstList PDSDataset::GetMetadata(const char *pszDomain)
1681 : {
1682 1 : if (pszDomain != nullptr && EQUAL(pszDomain, "json:PDS"))
1683 : {
1684 0 : return m_aosPDSMD.List();
1685 : }
1686 1 : return GDALPamDataset::GetMetadata(pszDomain);
1687 : }
1688 :
1689 : /************************************************************************/
1690 : /* GDALRegister_PDS() */
1691 : /************************************************************************/
1692 :
1693 2066 : void GDALRegister_PDS()
1694 :
1695 : {
1696 2066 : if (GDALGetDriverByName(PDS_DRIVER_NAME) != nullptr)
1697 263 : return;
1698 :
1699 1803 : GDALDriver *poDriver = new GDALDriver();
1700 1803 : PDSDriverSetCommonMetadata(poDriver);
1701 :
1702 1803 : poDriver->pfnOpen = PDSDataset::Open;
1703 :
1704 1803 : GetGDALDriverManager()->RegisterDriver(poDriver);
1705 :
1706 : #ifdef PDS_PLUGIN
1707 : GDALRegister_ISIS3();
1708 : GDALRegister_ISIS2();
1709 : GDALRegister_PDS4();
1710 : GDALRegister_VICAR();
1711 : #endif
1712 : }
|