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