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